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.