Friday, 26 May 2017

Reading Your Way Around UAC (Part 2)

We left Part 1 with the knowledge that normal user processes in a split-token admin logon can get access to TerminateQueryLimitedInformation and Synchronize process access rights to elevated processes. This was due to the normal user and admin user having a Default DACL which grants Execute access to the current Logon Session which is set for all tokens on the same desktop. The question we're left with is how can this possibly be used to elevate your privileges? Let's see how we can elevate our privileges prior to Windows 10.

Of the 3 access rights we have, both Terminate and Synchronize are really not that interesting. Sure you could be a dick to yourself I suppose and terminate your processes, but that doesn't seem much of interest. Instead it's QueryLimitedInformation which is likely to provide the most amusement, what information can we get with that access right? A quick hop, skip and jump to MSDN is in order. The following is from a page on Process Security and Access Rights:

PROCESS_QUERY_INFORMATION (0x0400)
Required to retrieve certain information about a process, such as its token, exit code, and priority class (see OpenProcessToken).

PROCESS_QUERY_LIMITED_INFORMATION (0x1000)
Required to retrieve certain information about a process (see GetExitCodeProcess, GetPriorityClass, IsProcessInJob, QueryFullProcessImageName). A handle that has the PROCESS_QUERY_INFORMATION access right is automatically granted PROCESS_QUERY_LIMITED_INFORMATION.
Windows Server 2003 and Windows XP:  This access right is not supported.

This at least confirms one thing from Part 1, that if you have QueryInformation access you automatically get QueryLimitedInformation as well. So it'd seem to make sense that QueryLimitedInformation just gives you a subset of what you could access from the full QueryInformation. And if this documentation is anything to go by all the things you could access are dull. But QueryInformation highlights something which would be very interesting to get hold of, the process token. We can double check I suppose, let's look at the documentation for OpenProcessToken to see what it says about required access.


ProcessHandle [in]
A handle to the process whose access token is opened. The process must have the PROCESS_QUERY_INFORMATION access permission.
Well that seals it, nothing to see here, move along. Wait, never believe anything you read. Perhaps this is really "Fake Documentation" (*topical* if you're reading this in 2020 from a nuclear fallout shelter just ignore it). Why don't we just try it and see (make sure your previously elevated copy of mmc.exe is still running):

Use-NtObject($ps = Get-NtProcess -Name mmc.exe) { Get-NtToken -Primary -Process $ps[0] } | Format-List -Property User, TokenType, GrantedAccess, IntegrityLevel

And then where we might expect to see an error message we instead get:

User : domain\user TokenType : Primary GrantedAccess : AssignPrimary, Duplicate, Impersonate, Query,
QuerySource, ReadControl IntegrityLevel : High

This shows we've opened the process' primary token, been granted a number of rights and to be sure we print the IntegrityLevel property to prove it's really a privileged token (more or less for reasons which will become clear).

What's going on? Basically the documentation is wrong, you don't need QueryInformation to open the process token only QueryLimitedInformation. You can disassemble NtOpenProcessTokenEx in the kernel if you don't believe me:

NTSTATUS NtOpenProcessTokenEx(HANDLE ProcessHandle, ACCESS_MASK DesiredAccess, DWORD HandleAttributes, PHANDLE TokenHandle) { EPROCESS* ProcessObject; NTSTATUS status = ObReferenceObjectByHandle( ProcessHandle, PROCESS_QUERY_LIMITED_INFORMATION, PsProcessType, &ProcessObject, NULL); ... }

Going back to Vista it's always been the case that only QueryLimitedInformation was needed, contrary to the documentation. While you still need to be able to access the token through it's DACL it turns out that Token objects also use the Default DACL so it grants Read and Execute access to the Logon Session SID. But doesn't the Token have the same mandatory policy as Processes? Well let's look, we can modify the IL Policy dump script from Part 1 to use a token object:

# Get current primary token's mandatory label $sacl = $(Get-NtToken -Primary).SecurityDescriptor.Sacl Write-Host "Policy is $([NtApiDotNet.MandatoryLabelPolicy]$sacl[0].Mask)"

And the result is: "Policy is NoWriteUp". So while we can't modify the token (we couldn't anyway due to the Default DACL) we can at least read it. But again this might not seem especially interesting, what use is Read access? As shown earlier Read gives you gives you a few interesting rights, AssignPrimary, Duplicate and Impersonate. What's to stop you now creating a new Process, or Impersonating the token? Well I'd refer you to my presentation at Shakacon/Blackhat on this very topic. To cut a long story short creating a new process is virtually impossible due to the the limits imposed by the kernel function SeIsTokenAssignableToProcess (and the lack of the SeAssignPrimaryTokenPrivilege) but on the other hand impersonation takes a different approach, calling SeTokenCanImpersonate as shown in the following diagram.


The diagram is the rough flow chart for deciding whether a process can impersonate another token (assuming you don't have SeImpersonatePrivilege, which we don't). We can meet every criteria, except one. The kernel checks if the current process's IL is greater-or-equal to the token being impersonated. If the process IL is less than the token's IL then the impersonation token is dropped to Identification level stopping us using it to elevate our privileges. While we can't increase a token's IL we can reduce it, so all we need to do is set the token IL to the same as the process' IL before impersonating and in theory we should become a Medium IL administrator.

There is one small issue to deal with before we do that, setting the IL is a write operation, and we don't have write access to the token. However it turns out that as we have Duplicate we can call DuplicateToken which clones the entire token. We'd need get an impersonation token anyway which requires duplication so this isn't a major issue. The important fact is the resulting duplicated token gives us a Read, Write, and Execute access to the token object. As the Token object's Mandatory Label is set to the caller's IL (which is Medium) not the IL inside the token. This results in the kernel being able to grant us full access to the new token object, confusing I know. Note that this isn't giving us Write access to the original token, just a copy of it. Time for PoC||GtfO:

$token = Use-NtObject($ps = Get-NtProcess -Name mmc.exe) { Get-NtToken -Primary -Process $ps[0] -Duplicate ` -ImpersonationLevel Impersonation ` -TokenType Impersonation ` -IntegrityLevel Medium } Use-NtObject($token.Impersonate()) { [System.IO.File]::WriteAllText("C:\windows\test.txt", "Hello") }

And you should see it creates a text file, C:\Windows\test.txt with the contents Hello. Or you could use the New-Service cmdlet to create a new service which will run as LocalSystem, you're an administrator after all even if running at Medium IL. You might be tempted to just enable SeDebugPrivilege and migrate to a system process directly, but if you try that something odd happens:

# Will indicate SeDebugPrivilege is disabled $token.GetPrivilege("SeDebugPrivilege").Enabled # Try enabling the privilege. $token.SetPrivilege("SeDebugPrivilege", $true) # Check again, will still be disabled. $token.GetPrivilege("SeDebugPrivilege").Enabled

You'll find that no matter how hard you try SeDebugPrivilege (and things like SeBackupPrivilege, SeRestorePrivilege) just cannot be enabled. This is another security measure that the UAC designers chose which in practice makes little realistic difference. You can't enable a small set of GOD privileges if the IL of the token is less than High. However you can still enable things like SeMountVolumePrivilege (could have some fun with that) or SeCreateSymbolicLinkPrivilege. We'll get back to this behavior later as it turns out to be important. Most importantly this behavior doesn't automatically disable the Administrators group which means we can still impersonate as a privileged user.

This works amazingly well as long as you run the example on Windows Vista, 7, 8 or 8.1. However on Windows 10 you'll get an error such as the following:

Use-NtObject : Exception calling "WriteAllText" with "2" argument(s): "Either a required impersonation level was not provided, or the provided impersonation level is invalid.

This error message means that the SeTokenCanImpersonate check check failed and the impersonation token got reverted to an Identification token. Clearly Microsoft know's something we don't. So that's where I'll leave it for now, come back when I post Part 3 for the conclusion, specifically getting this to work on Windows 10 and bypassing the new security checks.


Thursday, 25 May 2017

Reading Your Way Around UAC (Part 1)

I'm currently in the process of trying to do some improvements to the Chrome sandbox. As part of that I'm doing updates to my Sandbox Attack Surface Analysis Toolset as I want to measure whether what I'm doing to Chrome is having a tangible security benefit. Trouble is I keep walking into UAC bypasses while I'm there which is seriously messing up the flow. So in keeping with my previous blog post on a UAC bypass let me present another. As we go I'll show some demos using the latest version of my NtObjectManager Powershell module (so think of this blog as a plug for that as well, make sure you follow the install instructions on that link before running any of the scripts).

I don't recall every seeing this issue documented (but I'm sure someone can tell me if it has been), however MS clearly know, as we'll see in a later part. Bear in mind this demonstrates just how broken UAC is in its default configuration. UAC doesn't really help you much even if you prevent the auto-elevation as this technique works as long as there exists any elevated process in the same logon session. Let this be a PSA, one of many over the years, that split-token administrator in UAC just means MS get to annoy you with prompts unnecessarily but serves very little, if not zero security benefit.

Before I start I have to address/rant about the "fileless" moniker which is bandied around for UAC bypasses. My previous blog post said it was a fileless bypass, but I still had to write to the registry (which is backed by a file) and of course some sort of executable still needs to be running (which is backed at some point by the page file) and so on. Basically all a fileless bypass means is it doesn't rely on the old IFileOperation tricks to hijack a DLL. Doesn't mean that at no point would some file end on disk somewhere, I suppose it's more a DFIR kind of term. Anyway enough, on to technical content.

One Weird Design Decision

Oh to be a fly on the wall when Microsoft were designing UAC (or LUA as it was probably still known back then). Many different attack vectors were no doubt covered to reduce the chance of escalation from a normal user to administrator. For example Shatter Attacks (and general UI driving) was mitigated using UIPI. COM DLL planting was mitigated by making administrator processes only use HKLM for COM registrations (not especially successfully I might add). And abusing a user's resources were mitigated using Mandatory Integrity Labels to prevent write access from Low to High levels. 

Perhaps there was a super secure version of UAC developed at one point, but the trouble is it would have probably been unusable. So no doubt many of the security ideas got relaxed. One of particular interest is that a non-administrator user can query some, admittedly limited, process information about administrator processes in the same desktop. This has surprising implications as we'll see.

So how much access do normal applications get? We can answer that pretty easily by running the following PS script as a normal split-token admin user.

Import-Module NtObjectManager

# Start mmc.exe and ensure it elevates (not really necessary for mmc) Start-Process -Verb runas mmc.exe Use-NtObject($ps = Get-NtProcess -Name mmc.exe) { $ps | Format-Table -Property ProcessId, Name, GrantedAccess }

This should result in MMC elevating and the following printed to the PS console:

ProcessId Name GrantedAccess --------- ---- ------------- 17000 mmc.exe Terminate, QueryLimitedInformation, Synchronize

So it shows we've got 3 access rights, Terminate, QueryLimitedInformation and Synchronize. This kind of makes sense, after all it would be a pain if you couldn't kill processes on your desktop, or wait for them to finish, or get their name. It's at this point that the first UAC design decision comes into play, there exists a normal QueryInformation process access right, however there's a problem with using that access right, and that's down to the default Mandatory Label Policy (I'll refer to it just as IL Policy from now on) and how it's enforced on Processes.

The purpose of IL Policy is to specify which of the Generic Access Rights, Read, Write and Execute a low IL user can get on a resource. This is the maximum permitted access, the IL Policy doesn't itself grant any access rights. The user would still need to be granted the appropriate access rights in the DACL. So for example if the policy allows a lower IL process to get Read and Execute, but not Write (which is the default for most resources) then if the user asks for a Write access right the kernel access check will return Access Denied before even looking at the DACL. So let's look at the IL Policy and Generic Access Rights for a process object:

# Get current process' mandatory label $sacl = $(Get-NtProcess -Current).SecurityDescriptor.Sacl Write-Host "Policy is $([NtApiDotNet.MandatoryLabelPolicy]$sacl[0].Mask)" # Get process type's GENERIC_MAPPING $mapping = $(Get-NtType Process).GenericMapping Write-Host "Read: $([NtApiDotNet.ProcessAccessRights]$mapping.GenericRead)" Write-Host "Write: $([NtApiDotNet.ProcessAccessRights]$mapping.GenericWrite)" Write-Host "Execute: $([NtApiDotNet.ProcessAccessRights]$mapping.GenericExecute)"

Which results in the following output:

Policy is NoWriteUp, NoReadUp Read: VmRead, QueryInformation, ReadControl Write: CreateThread, VmOperation, VmWrite, DupHandle, *Snip* Execute: Terminate, QueryLimitedInformation, ReadControl, Synchronize

I've highlighted the important points. The default policy for Processes is to not allow a lower IL user to either Read or Write, so all they can have is Execute access which as we can see is what we have. However note that QueryInformation is a Read access right which would be blocked by the default IL Policy. The design decision was presumably thus, "We can't give read access, as we don't want lower IL users reading memory out of a privileged process. So let's create a new access right, QueryLimitedInformation which we'll assign to Execute and just transfer some information queries to that new right instead". Also worth noting on Vista and above you can't get QueryInformation access without also implicitly having QueryLimitedInformation so clearly MS thought enough to bodge that rather than anything else. (Thought for the reader: Why don't we get ReadControl access?)

Of course you still need to be able to have access in the DACL for those access, how come a privileged process gives these access at all? The default security of a process comes from the Default DACL inside the access token which is used as the primary token for the new process, let's dump the Default DACL using the following script inside a normal user PS console and an elevated PS console:

# Get process token. Use-NtObject($token = Get-NtToken -Primary) { $token.DefaultDalc | Format-Table @{Label="User";Expression={$_.Sid.Name}}, @{Label="Mask";Expression=
{[NtApiDotNet.GenericAccessRights]$_.Mask}} }

The output as a normal user:

User Mask ---- ---- domain\user GenericAll NT AUTHORITY\SYSTEM GenericAll NT AUTHORITY\LogonSessionId_0_295469990 GenericExecute, GenericRead

And again as the admin user:

User Mask ---- ---- BUILTIN\Administrators GenericAll NT AUTHORITY\SYSTEM GenericAll NT AUTHORITY\LogonSessionId_0_295469990 GenericExecute, GenericRead

Once again the important points are highlighted, while the admin DACL doesn't allow the normal user access there is this curious LogonSessionId user which gets Read and Execute access. It would seem likely therefore that this must be what's giving us Execute access (as Read would be filtered by IL Policy). We can prove this just by dumping what groups a normal user has in their token:

Use-NtObject($token = Get-NtToken -Primary) { $token.Groups | Where-Object {$_.Sid.Name.Contains("LogonSessionId")} | Format-List }

Name : NT AUTHORITY\LogonSessionId_0_295469990 Sid : S-1-5-5-0-295469990 Attributes : Mandatory, EnabledByDefault, Enabled, LogonId

Yup we have that group, and it's enabled. So that solves the mystery of why we get Execute access. This was a clear design decision on Microsoft's part to make it so a normal user could gain some level of access to an elevated process. Of course at this point you might be thinking so what? You can read some basic information from a process, how could being able to read be an issue? Well, let's see how dangerous this access is in Part 2.






Monday, 15 May 2017

Exploiting Environment Variables in Scheduled Tasks for UAC Bypass

The Windows Task Scheduler is a great place to go and find privilege escalations, it's typically abused to add SUID style capabilities to Windows in a nice easy to misunderstand package. It can execute programs as LocalSystem, it can auto-elevate applications for UAC, it can even host arbitrary COM objects. All in all it's a mess, which is why finding bugs in the scheduler itself or in the tasks isn't especially difficult. For example here's a few I've found before. This short blog is about a quick and dirty UAC bypass I discovered which works silently even with UAC is set to the highest prompt level and can be executed without dropping any files (other that a registry key) to disk.

Anyway I'm technically on a sabbatical from finding bugs in Microsoft products (best not ask why) so I'll keep this brief. However sometimes while I'm not looking you just sort of trip over a bug. I was poking around various scheduled tasks and noticed one which looked interesting, SilentCleanup. The reason this is interesting is it's a marked as auto-elevating (so will silently run code as UAC admin if the caller is a split-token administrator) and it can be manually started by the non-administrator user.

It turns out I'm not alone in noticing this is interesting, Matt Nelson already found a UAC bypass in this scheduled task but as far as can be determined it's already been fixed, so is there still a way of exploiting it? Let's dump some of the task's properties using Powershell to find out.


We can see the Principal property, which determines what account the task runs as and the Actions property which determines what to run. In the Principal property we can see the Group to run as is Authenticated Users which really means it will run as the logged on user starting the task. We also see the RunLevel is set to Highest which means the Task Scheduler will try and elevate the task to administrator without any prompting. Now look at the actions, it's specifying a path, but notice something interesting? It's using an environment variable as part of the path, and in UAC scenarios these can be influenced by a normal user by writing to the registry key HKEY_CURRENT_USER\Enviroment and specifying a REG_SZ value.

So stop beating around the bush, let's try and exploit it. I dropped a simple executable to c:\dummy\system32\cleanmgr.exe, set the windir environment variable to c:\dummy and started the scheduled task I immediately get administrator privileges. So let's automate the process, I'll use everyone's favourite language, BATCH as we can use the reg and schtasks commands to do all the work we need. Also as we don't want to drop a file to disk we can abuse the fact that the executable path isn't quoted by the Task Scheduler, meaning we can inject arbitrary command line arguments and just run a simple CMD shell.

The BATCH file first sets the windir environment variable to "cmd /K" with a following script which deletes the original windir enviroment variable then uses REM to comment the rest of the line out. Executing this on Windows 10 Anniversary Edition and above as a split token admin will get you a shell running as an administrator. I've not tested it on any earlier versions of Windows so YMMV. I didn't send this to MSRC but through a friend confirmed that it should already be fixed in a coming version of RS3, so it really looks like MS are serious about trying to lock UAC back down, at least as far as it can be. If you want to mitigate now you should be able to reconfigure the task to not use environment variables using the following Powershell script run as administrator (doing this using the UAC bypass is left as an exercise for reader).

If you want to find other potential candidates the following Powershell script will find all tasks with
executable actions which will auto elevate. On my system there are 4 separate tasks, but only one (the SilentCleanup task) can be executed as a normal user, so the rest are not exploitable. Good thing I guess.

Sunday, 12 March 2017

Getting Code Execution on Windows by Abusing Default Kernel Debugging Setting

TL;DR; This blog post comes from an on-site pentest I did a long time ago. While waiting for some other testing to complete the customer was interested to see if I could get code execution on one of their Windows workstations (the reasons for this request are unimportant). Needless to say I had physical access to the workstation so it should be pretty simple thing to achieve. The solution I came up with abused the default Windows Kernel Debugging settings to get arbitrary code execution without needing to permanently modify the system configuration or open the case.

The advantage of this technique is it requires a minimum amount of kit which you could bring with you on a job, just in case. However it does require that the target has an enabled COM1 serial port which isn't necessarily guaranteed, and the machine cannot be using TPM enforced Bitlocker or similar.

And before anyone complains I'm fully aware that physical access typically means that you've already won, this is why I'm not claiming this is some sort of world ending vulnerability against Windows machines. It's not, but it's a common default configuration which administrators probably don't know to change. It also looks pretty awesome if the stars line up, let's face it, from a customer's perspective it makes you look like some bad-ass hacker. Bonus points for using the command line CDB instead of WinDBG ;-)

And just in case you misunderstand me:

THIS IS NOT A VULNERABILITY!!!!

With that said, let's look at it in more detail.

The Scenario

You find yourself in a room filled with Windows workstations (hopefully legally) and you're tasked with getting code execution on one of them. Your immediate thoughts to achieve this might be one or more of the following:

  • Change boot settings to boot off a CD/USB device and modify HDD
  • Crack open the case, pull the HDD, modify contents and put it back in.
  • Abuse Firewire DMA access to read/write memory.
  • Abuse the network connection coming out the back of the machine, either to try and PXE boot or MitM network/domain traffic on the machine.
Looking at the workstation though booting up you notice that the boot order is configured to boot off the HDD first but a BIOS password prevents you circumventing it (assuming no bug in the BIOS). The case actually has a physical lock on it, probably not something you couldn't pick or crowbar but the customer probably wouldn't be amused if I left the workstation in bits. And finally the workstation didn't have Firewire or any external PCI bus to speak of to perform DMA attacks. I didn't test the network connection, but it might not be easy to PXE boot or MitM'ing the traffic might encounter IPSec.

What these workstations did have though was a classic 9 pin serial port. This got me thinking, I knew that by default Windows configured kernel debugging on COM1, however kernel debugging isn't enabled. Was there a way of enabling kernel debugging on a system without having administrator login rights? Turns out that yes there is, so lets see how you could exploit this scenario.

Coming Prepared


Before you can exploit this feature you'll need a few things to hand:
  • A serial port on your test machine (this is pretty obvious of course). A USB to Serial is sufficient with the right drivers
  • A local installation of Windows. This is more for simplicity, perhaps there's tools to do Windows Kernel Debugging available these days for Linux/macOS which support everything you need but I doubt it.
  • Assuming you're using Windows an installation of Debugging Tools for Windows, specifically WinDBG.
  • A Null Modem cable, you'll need this to connect your test machine's serial port to the workstation serial port.
Now on your test machine ensure everything is installed and setup WinDBG to use your local COM port for kernel debugging. Using kernel debugging just requires you to open WinDBG then from the menu select File -> Kernel Debug or press CTRL + K. You should see a dialog which looks like the following:



Fill in the Port field to match the COM port your USB to Serial adapter was assigned. You shouldn't need to change the Baud Rate as the Windows default is for 115200. You can verify this on another system using an administrator command prompt and running the command bcdedit /dbgsettings



You could also do this via the following command line if you're so inclined: windbg -k com:port=COMX,baud=115200

Enabling Kernel Debugging on Windows 7

Enabling kernel debugging on Windows 7 is really easy (this should also work on Vista, but really who uses that anymore?). Reboot the workstation and after the BIOS POST screen has completed mash (the official technical term) the F8 key. If successful you'll be greeted with the following screen:


Scroll down with the cursor keys, select Debugging Mode and hit Enter. Windows should start to boot. Hopefully if you look at WinDBG you should now see the boot information being displayed (full disclosure, I'm doing this using a VM ;-)).



If this doesn't happen it's possible that the COM port's disabled, the kernel debugging configuration has changed or you've got a terrible USB to Serial adapter.

Enabling Kernel Debugging on Windows 8-10

So moving on to more modern versions of Windows, you can try the F8 trick again, but don't be shocked when it does NOTHING. This was an intentional change Microsoft has made to boot process since Windows 8. With the prevalence of SSDs and various changes to Windows' boot process there's no longer enough time (in their opinion) for mashing the F8 key. Instead, while the option to enable kernel debugging is still present you need to configure it through the fancy UEFI alike menus. 

This presents us with a problem. We're assuming we don't have access to the BIOS (through say a password) so it would seem we couldn't access the UEFI configuration options and the main way you can configure this is going through the Settings App (at least on Windows 10) and choose to restart into Advanced Startup mode or you can pass the /r /o options to shutdown in the command prompt.


None of these options are going to help us. Fortunately there's a "documented' alternative way, if you hold the Shift key when selecting Restart from the start menu it will also reboot into the advanced startup options mode. This doesn't immediately sound like it's going to help you any more than the other options if you've got to login, fortunately on the login screen there's an option to reboot the workstation, and it just so happens that the Shift trick also works here. So go to the power options (on the lower right corner of the login screen on Windows 10), hold left Shift and click Restart. If successful you'll be greeted with the following screen.


 Click the highlighted "Troubleshoot" and you'll get to a new screen.


From here select "Advanced options", going to the *sigh* next screen:


At this screen you'll want to click "Startup Settings" which will bring you the following screen. You might be inclined to think you could click "Command Prompt" to get a system command prompt but that's going to require a password for a local administrator user anyway, which if you've got already you don't need to do this. Also I'm not saying there's no tricks you can't play with recovery mode etc, I'm showing you this just for giggles  :-)


After hitting "Restart" the workstation will reboot and you should be presented with the following:


Finally, you can hit F1 to enable kernel debugging. Phew... bring back F8. If all went according to plan you should see the boot messages again.


Getting Code Execution

You've now got a kernel debugger attached to the machine, the final step is to bypass the login screen. One common trick when using Firewire DMA attacks is to search for a particular pattern in memory which corresponds to LSASS's password check and kill it. Now any login password will work, this is fine but not very sneaky (for example you'd end up with event log entries showing the login). Plus you'd need to know an appropriate user account, it's possible the local Administrator account has been renamed.

Instead we'll do a more targeted attack which is possible because we've got the kernel's view of the system available, not a physical memory view. We'll abuse the fact that the login screen has a button for launching Accessibility tools, this will execute a new process on the login desktop as SYSTEM. We can hijack this process creation to spawn a command prompt and do whatever we like.



First things first we'll want to configure symbols for the machine we're trying to attack. Without symbols we can't enumerate the necessary kernel structures to find the bits of the system to attack. The simplest way to ensure symbols are configured correctly is to type the commands, !symfix+ then !reload into the debugger command window. Now to test issue the command !process 0 0 winlogon.exe which will find the process which is responsible for displaying the login window. If successful it should look something like the following:



The highlighted value is the kernel address of the EPROCESS structure. Copy that value value now to get an "Interactive" debugging session for that process using the command .process /i EPROCESS. Type g, then Enter (or hit F5) and you should see the following:



Now with this interactive session we can enumerate the user modules and load their symbols. Run the command !reload -user to do that. Then we can set a breakpoint on CreateProcessInternalW, which is what will be run whenever a new process is about to be created. Where this function is depends on the Windows version, on Windows 7 it's in the kernel32 DLL, on Windows 8+ it's in kernelbase DLL. So set the breakpoint using bp MODULE!CreateProcessInternalW replacing MODULE with the name appropriate for your system.

With the breakpoint set, click the Ease of Access button on the login screen and hopefully the breakpoint should hit. Now just to be sure issue the commands r and k to dump the current registers and show a back trace. It should look something like the following:


We can see in the stack trace that we've got calls to things like WlAccessibilityStartShortcutTool which seem to be related to accessibility. Now CreateProcessInternalW takes many parameters, but the only one we're really interested in is the third parameter, which is a pointer a NUL terminated command line string. We can modify this string to instead refer to the command prompt and we should get out desired code execution. First just to be sure we'll dump the string using the dU command, for x64 pass dU r8 (as the third parameter is stored in the r8 register), for x86 issue dU poi(@esp+c) (on 32 bit all parameters are passed on the stack). Hopefully you'll see the following:


So WinLogon is trying to create an instance of utilman.exe, that's good. Now this string must be writable (there's a dumb behaviour of CreateProcess that if it's not you'll get a crash) so we can just overwrite it. Issue the command ezu r8 "cmd" or ezu poi(@esp+c) "cmd" depending on your bitness and then type g and enter to continue. Bathe in your awesomeness.


Downsides

So there are a number of downsides to this technique:
  • The workstation MUST have a serial port on it, which isn't a given at least these days, and it must be configured as COM1
  • The workstation must be rebooted, this means that you can't get access to any logged on user credentials or things left in memory. Another issue with this is if the workstation has a boot password you might not be able to reboot it anyway.
  • The configuration of the kernel debugging must be the default.
  • In the prescence of TPM enforced Bitlocker you shouldn't be able to change the debugger configuration without also invalidating the boot measurement meaning Bitlocker won't unlock the drive.
Still in the end the setup costs are so low, it wouldn't take much to carry a USB to Serial adapter and a Null Modem cable in your travel bag if you're going on site somewhere. 

Mitigations

It's all very well and good that you can do this, but is there anyway to prevent it? Well of course as many will point out if you've already got physical access it's Game Over Man (R.I.P. Bill) but there are some configuration changes you can make to remove this attack vector:

  • Change the default debugging settings to Local kernel debugging. This is a mode which means only a local debugger running as administrator can debug the kernel (and debugging must also be on). You can change it at an administrator command prompt with the command bcdedit /dbgsettings LOCAL. You could almost certainly automate this across your estate with a login script or GPO option.
  • Don't buy Workstations with serial ports. Sounds dumb, and you probably have little choice but don't get things on your purchased devices which serve no useful purpose. Presumably some vendors still provide a configuration option for this.
  • If you do have serial ports disable them in the BIOS or, if you can't disable them outright change the default I/O port from 0x3F8. Legacy COM ports are not plug and play, Windows will use an explicit I/O port to talk to COM1, if your COM port isn't configured as COM1 Windows can't use it. This is also important if you've installed aftermarket COM port cards, while they tend not to be configured as COM1 they _could_ be.
  • Finally use Bitlocker with a TPM, this is a good idea regardless as it would also block someone being able to pull the HDD out and modify it offline (or just up and stealing the thing for the information on the disk). Bitlocker + TPM would prevent someone enabling debugging on a system without knowing the Bitlocker recovery key. At least on Windows 8+ entering the System Settings option requires changing the boot configuration temporarily, which will cause the TPM boot measurement to fail. I've not tested this on Windows 7 though, hitting F8 might not change the boot measurement as I believe that menu is in the winload.exe boot process, at that point Bitlocker's key has already been unsealed. If anyone has a Windows 7 machine with Bitlocker and TPM let me know the result of testing that :-)
Another interesting thing is the latest version of Windows 10 available when I'm writing this (1607, Anniversary Edition) now configures kernel debugging to Local only by default. However it's possible this isn't changed during upgrades so you'd still want to take a look.

Conclusions

So this is a fun, but not particularly serious issue if someone's got physical access to your machine and you've covered a number of the common attack vectors (like HDD access, BIOS, Firewire). Good advice is physical access a potential attack vector for external but also internal threats and it pays to do everything you can to lock your estate down. You should also consider deploying Bitlocker even if they device isn't portable, it makes it more difficult to compromise a workstation through logical attacks on the boot process and also makes it harder to someone to extract sensitive data from a stolen machine.