Injecting Code into Windows Protected Processes using COM — Part 1

( Original text by James Forshaw )

At Recon Montreal 2018 I presented “Unknown Known DLLs and other Code Integrity Trust Violations” withAlex Ionescu. We described the implementation of Microsoft Windows’ Code Integrity mechanisms and how Microsoft implemented Protected Processes (PP). As part of that I demonstrated various ways of bypassing Protected Process Light (PPL), some requiring administrator privileges, others not.In this blog I’m going to describe the process I went through to discover a way of injecting code into a PPL on Windows 10 1803. As the only issue Microsoft considered to be violating a defended security boundary has now been fixed I can discuss the exploit in more detail.

Background on Windows Protected Processes

The origins of the Windows Protected Process (PP) model stretch back to Vista where it was introduced to protect DRM processes. The protected process model was heavily restricted, limiting loaded DLLs to a subset of code installed with the operating system. Also for an executable to be considered eligible to be started protected it must be signed with a specific Microsoft certificate which is embedded in the binary. One protection that the kernel enforced is that a non-protected process couldn’t open a handle to a protected process with enough rights to inject arbitrary code or read memory.In Windows 8.1 a new mechanism was introduced, Protected Process Light (PPL), which made the protection more generalized. PPL loosened some of the restrictions on what DLLs were considered valid for loading into a protected process and introduced different signing requirements for the main executable. Another big change was the introduction of a set of signing levels to separate out different types of protected processes. A PPL in one level can open for full access any process at the same signing level or below, with a restricted set of access granted to levels above. These signing levels were extended to the old PP model, a PP at one level can open all PP and PPL at the same signing level or below, however the reverse was not true, a PPL can never open a PP at any signing level for full access. Some of the levels and this relationship are shown below:

Signing levels allow Microsoft to open up protected processes to third-parties, although at the current time the only type of protected process that a third party can create is an Anti-Malware PPL. The Anti-Malware level is special as it allows the third party to add additional permitted signing keys by registering an Early Launch Anti-Malware (ELAM) certificate. There is also Microsoft’s TruePlay, which is an Anti-Cheat technology for games which uses components of PPL but it isn’t really important for this discussion.I could spend a lot of this blog post describing how PP and PPL work under the hood, but I recommend reading the blog post series by Alex Ionescu instead (Parts 12 and 3) which will do a better job. While the blog posts are primarily based on Windows 8.1, most of the concepts haven’t changed substantially in Windows 10.I’ve written about Protected Processes before [link], in the form of the custom implementation by Oracle in their VirtualBox virtualization platform on Windows. The blog showed how I bypassed the process protection using multiple different techniques. What I didn’t mention at the time was the first technique I described, injecting JScript code into the process, also worked against Microsoft’s PPL implementation. I reported that I could inject arbitrary code into a PPL to Microsoft (see Issue 1336) from an abundance of caution in case Microsoft wanted to fix it. In this case Microsoft decided it wouldn’t be fixed as a security bulletin. However Microsoft did fix the issue in the next major release on Windows (version 1803) by adding the following code to CI.DLL, the Kernel’s Code Integrity library:UNICODE_STRING g_BlockedDllsForPPL[]={
 DECLARE_USTR(«scrobj.dll»),
 DECLARE_USTR(«scrrun.dll»),
 DECLARE_USTR(«jscript.dll»),
 DECLARE_USTR(«jscript9.dll»),
 DECLARE_USTR(«vbscript.dll»)
};

NTSTATUS CipMitigatePPLBypassThroughInterpreters(PEPROCESS Process,
                                                LPBYTE Image,
                                                SIZE_T ImageSize){
if(!PsIsProtectedProcess(Process))
return STATUS_SUCCESS;

 UNICODE_STRING OriginalImageName;
 // Get the original filename from the image resources.
 SIPolicyGetOriginalFilenameAndVersionFromImageBase(
     Image, ImageSize,&OriginalImageName);
for(int i = 0; i < _countof(g_BlockedDllsForPPL);++i){
if(RtlEqualUnicodeString(g_BlockedDllsForPPL[i],
&OriginalImageName, TRUE)){
return STATUS_DYNAMIC_CODE_BLOCKED;
}
}
return STATUS_SUCCESS;
}The fix checks the original file name in the resource section of the image being loaded against a blacklist of 5 DLLs. The blacklist includes DLLs such as JSCRIPT.DLL, which implements the original JScript scripting engine, and SCROBJ.DLL, which implements scriptlet objects. If the kernel detects a PP or PPL loading one of these DLLs the image load is rejected with STATUS_DYNAMIC_CODE_BLOCKED. This kills my exploit, if you modify the resource section of one of the listed DLLs the signature of the image will be invalidated resulting in the image load failing due to a cryptographic hash mismatch. It’s actually the same fix that Oracle used to block the attack in VirtualBox, although that was implemented in user-mode.

Finding New Targets

The previous injection technique using script code was a generic technique that worked on any PPL which loaded a COM object. With the technique fixed I decided to go back and look at what executables will load as a PPL to see if they have any obvious vulnerabilities I could exploit to get arbitrary code execution. I could have chosen to go after a full PP, but PPL seemed the easier of the two and I’ve got to start somewhere. There’s so many ways to inject into a PPL if we could just get administrator privileges, the least of which is just loading a kernel driver. For that reason any vulnerability I discover must work from a normal user account. Also I wanted to get the highest signing level I can get, which means PPL at Windows TCB signing level.The first step was to identify executables which run as a protected process, this gives us the maximum attack surface to analyze for vulnerabilities. Based on the blog posts from Alex it seemed that in order to be loaded as PP or PPL the signing certificate needs a special Object Identifier (OID) in the certificate’s Enhanced Key Usage (EKU) extension. There are separate OID for PP and PPL; we can see this below with a comparison between WERFAULTSECURE.EXE, which can run as PP/PPL, and CSRSS.EXE, which can only run as PPL.

I decided to look for executables which have an embedded signature with these EKU OIDs and that’ll give me a list of all executables to look for exploitable behavior. I wrote the Get-EmbeddedAuthenticodeSignaturecmdlet for my NtObjectManager PowerShell module to extract this information.At this point I realized there was a problem with the approach of relying on the signing certificate, there’s a lot of binaries I expected to be allowed to run as PP or PPL which were missing from the list I generated. As PP was originally designed for DRM there was no obvious executable to handle the Protected Media Pathsuch as AUDIODG.EXE. Also, based on my previous research into Device Guard and Windows 10S, I knew there must be an executable in the .NET framework which could run as PPL to add cached signing level information to NGEN generated binaries (NGEN is an Ahead-of-Time JIT to convert a .NET assembly into native code). The criteria for PP/PPL were more fluid than I expected. Instead of doing static analysis I decided to perform dynamic analysis, just start protected every executable I could enumerate and query the protection level granted. I wrote the following script to test a single executable:Import-Module NtObjectManagerfunction Test-ProtectedProcess {[CmdletBinding()]   param([Parameter(Mandatory, ValueFromPipelineByPropertyName)][string]$FullName,[NtApiDotNet.PsProtectedType]$ProtectedType= 0,[NtApiDotNet.PsProtectedSigner]$ProtectedSigner= 0       )   BEGIN {$config= New-NtProcessConfig abc ProcessFlags ProtectedProcess `           ThreadFlags Suspended TerminateOnDispose `           ProtectedType $ProtectedType `           ProtectedSigner $ProtectedSigner}   PROCESS {$path= Get-NtFilePath $FullName       Write-Host $path       try {           Use-NtObject($p= New-NtProcess $pathConfig $config){$prot=$p.Process.Protection               $props= @{                   Path=$path;                   Type=$prot.Type;                   Signer=$prot.Signer;                   Level=$prot.Level.ToString(«X»);}$obj= New-Object –TypeName PSObject –Prop $props               Write-Output $obj}} catch {}}}When this script is executed a function is defined, Test-ProtectedProcess. The function takes a path to an executable, starts that executable with a specified protection level and checks whether it was successful. If the ProtectedType and ProtectedSigner parameters are 0 then the kernel decides the “best” process level. This leads to some annoying quirks, for example SVCHOST.EXE is explicitly marked as PPL and will run at PPL-Windows level, however as it’s also a signed OS component the kernel will determine its maximum level is PP-Authenticode. Another interesting quirk is using the native process creation APIs it’s possible to start a DLL as main executable image. As a significant number of system DLLs have embedded Microsoft signatures they can also be started as PP-Authenticode, even though this isn’t necessarily that useful. The list of binaries that will run at PPL is shown below along with their maximum signing level.

PathSigning Level
C:\windows\Microsoft.Net\Framework\v4.0.30319\mscorsvw.exeCodeGen
C:\windows\Microsoft.Net\Framework64\v4.0.30319\mscorsvw.exeCodeGen
C:\windows\system32\SecurityHealthService.exeWindows
C:\windows\system32\svchost.exeWindows
C:\windows\system32\xbgmsvc.exeWindows
C:\windows\system32\csrss.exeWindows TCB
C:\windows\system32\services.exeWindows TCB
C:\windows\system32\smss.exeWindows TCB
C:\windows\system32\werfaultsecure.exeWindows TCB
C:\windows\system32\wininit.exeWindows TCB

Injecting Arbitrary Code Into NGEN

After carefully reviewing the list of executables which run as PPL I settled ontrying to attack the previously mentioned .NET NGEN binary, MSCORSVW.EXE. My rationale for choosing the NGEN binary was:

  • Most of the other binaries are service binaries which might need administrator privileges to start correctly.
  • The binary is likely to be loading complex functionality such as the .NET framework as well as having multiple COM interactions (my go-to technology for weird behavior).
  • In the worst case it might still yield a Device Guard bypass as the reason it runs as PPL is to give it access to the kernel APIs to apply a cached signing level. Any bug in the operation of this binary might be exploitable even if we can’t get arbitrary code running in a PPL.

But there is an issue with the NGEN binary, specifically it doesn’t meet my own criteria that I get the top signing level, Windows TCB. However, I knew that when Microsoft fixed Issue 1332 they left in a back door where a writable handle could be maintained during the signing process if the calling process is PPL as shown below:NTSTATUS CiSetFileCache(HANDLE Handle,…){

 PFILE_OBJECT FileObject;
 ObReferenceObjectByHandle(Handle,&FileObject);

if(FileObject->SharedWrite ||
(FileObject->WriteAccess &&
     PsGetProcessProtection().Type != PROTECTED_LIGHT)){
return STATUS_SHARING_VIOLATION;
}

 // Continue setting file cache.
}If I could get code execution inside the NGEN binary I could reuse this backdoor to cache sign an arbitrary file which will load into any PPL. I could then DLL hijack a full PPL-WindowsTCB process to reach my goal.To begin the investigation we need to determine how to use the MSCORSVW executable. Using MSCORSVW is not documented anywhere by Microsoft, so we’ll have to do a bit of digging. First off, this binary is not supposed to be run directly, instead it’s invoked by NGEN when creating an NGEN’ed binary. Therefore, we can run the NGEN binary and use a tool such as Process Monitor to capture what command line is being used for the MSCORSVW process. Executing the command:C:\> NGEN install c:\some\binary.dllResults in the following command line being executed:MSCORSVW -StartupEvent A -InterruptEvent B -NGENProcess C -Pipe DA, B, C and D are handles which NGEN ensures are inherited into the new process before it starts. As we don’t see any of the original NGEN command line parameters it seems likely they’re being passed over an IPC mechanism. The “Pipe” parameter gives an indication that  named pipes are used for IPC. Digging into the code in MSCORSVW, we find the method NGenWorkerEmbedding, which looks like the following:void NGenWorkerEmbedding(HANDLE hPipe){
 CoInitializeEx(nullptr, COINIT_APARTMENTTHREADED);
 CorSvcBindToWorkerClassFactory factory;

 // Marshal class factory.
 IStream* pStm;
 CreateStreamOnHGlobal(nullptr, TRUE,&pStm);
 CoMarshalInterface(pStm,&IID_IClassFactory, &factory,                    MSHCTX_LOCAL,nullptr, MSHLFLAGS_NORMAL);

 // Read marshaled object and write to pipe.
 DWORD length;
 char* buffer = ReadEntireIStream(pStm,&length);
 WriteFile(hPipe,&length,sizeof(length));
 WriteFile(hPipe, buffer, length);
 CloseHandle(hPipe);

 // Set event to synchronize with parent.
 SetEvent(hStartupEvent);

 // Pump message loop to handle COM calls.
 MessageLoop();

 // …
}This code is not quite what I expected. Rather than using the named pipe for the entire communication channel it’s only used to transfer a marshaled COM object back to the calling process. The COM object is a class factory instance, normally you’d register the factory using CoRegisterClassObject but that would make it accessible to all processes at the same security level so instead by using marshaling the connection can be left private only to the NGEN binary which spawned MSCORSVW. A .NET related process using COM gets me interested as I’ve previously described in another blog post how you can exploit COM objects implemented in .NET. If we’re lucky this COM object is implemented in .NET, we can determine if it is implemented in .NET by querying for its interfaces, for example we use the Get-ComInterface command in my OleViewDotNet PowerShell module as shown in the following screenshot.

We’re out of luck, this object is not implemented in .NET, as you’d at least expect to see an instance of the_Object interface. There’s only one interface implemented, ICorSvcBindToWorker so let’s dig into that interface to see if there’s anything we can exploit.Something caught my eye, in the screenshot there’s a HasTypeLib column, for ICorSvcBindToWorker we see that the column is set to True. What HasTypeLib indicates is rather than the interface’s proxy code being implemented using an predefined NDR byte stream it’s generated on the fly from a type library. I’ve abused this auto-generating proxy mechanism before to elevate to SYSTEM, reported as issue 1112. In the issue I used some interesting behavior of the system’s Running Object Table (ROT) to force a type confusion in a system COM service. While Microsoft has fixed the issue for User to SYSTEM there’s nothing stopping us using the type confusion trick to exploit the MSCORSVW process running as PPL at the same privilege level and get arbitrary code execution. Another advantage of using a type library is a normal proxy would be loaded as a DLL which means that it must meet the PPL signing level requirements; however a type library is just data so can be loaded into a PPL without any signing level violations.How does the type confusion work? Looking at the ICorSvcBindToWorker interface from the type library:interface ICorSvcBindToWorker : IUnknown {
   HRESULT BindToRuntimeWorker(
[in] BSTR pRuntimeVersion,
[in] unsigned long ParentProcessID,
[in] BSTR pInterruptEventName,
[in] ICorSvcLogger* pCorSvcLogger,
[out] ICorSvcWorker** pCorSvcWorker);
};The single BindToRuntimeWorker takes 5 parameters, 4 are inbound and 1 is outbound. When trying to access the method over DCOM from our untrusted process the system will automatically generate the proxy and stub for the call. This will include marshaling COM interface parameters into a buffer, sending the buffer to the remote process and then unmarshaling to a pointer before calling the real function. For example imagine a simpler function, DoSomething which takes a single IUnknown pointer. The marshaling process looks like the following:

The operation of the method call is as follow:

  1. The untrusted process calls DoSomething on the interface which is actually a pointer to DoSomethingProxy which was auto-generated from the type library passing an IUnknown pointer parameter.
  2. DoSomethingProxy marshals the IUnknown pointer parameter into the buffer and calls over RPC to the Stub in the protected process.
  3. The COM runtime calls the DoSomethingStub method to handle the call. This method will unmarshal the interface pointer from the buffer. Note that this pointer is not the original pointer from step 1, it’s likely to be a new proxy which calls back to the untrusted process.
  4. The stub invokes the real implemented method inside the server, passing the unmarshaled interface pointer.
  5. DoSomething uses the interface pointer, for example by calling AddRef on it via the object’s VTable.

How would we exploit this? All we need to do is modify the type library so that instead of passing an interface pointer we pass almost anything else. While the type library file is in a system location which we can’t modify we can just replace the registration for it in the current user’s registry hive, or use the same ROT trick from before issue 1112. For example if we modifying the type library to pass an integer instead of an interface pointer we get the following:

The operation of the marshal now changes as follows:

  1. The untrusted process calls DoSomething on the interface which is actually a pointer to DoSomethingProxy which was auto-generated from the type library passing an arbitrary integer parameter.
  2. DoSomethingProxy marshals the integer parameter into the buffer and calls over RPC to the Stub in the protected process.
  3. The COM runtime calls the DoSomethingStub method to handle the call. This method will unmarshal the integer from the buffer.
  4. The stub invokes the real implement method inside the server, passing the integer as the parameter. However DoSomething hasn’t changed, it’s still the same method which accepts an interface pointer. As the COM runtime has no more type information at this point the integer is type confused with the interface pointer.
  5. DoSomething uses the interface pointer, for example by calling AddRef on it via the object’s VTable. As this pointer is completely under control of the untrusted process this likely results in arbitrary code execution.

By changing the type of parameter from an interface pointer to an integer we induce a type confusion which allows us to get an arbitrary pointer dereferenced, resulting in arbitrary code execution. We could even simplify the attack by adding to the type library the following structure:struct FakeObject {
   BSTR FakeVTable;
};If we pass a pointer to a FakeObject instead of the interface pointer the auto-generated proxy will marshal the structure and its BSTR, recreating it on the other side in the stub. As a BSTR is a counted string it can contain NULLs so this will create a pointer to an object, which contains a pointer to an arbitrary byte array which can act as a VTable. Place known function pointers in that BSTR and you can easily redirect execution without having to guess the location of a suitable VTable buffer.To fully exploit this we’d need to call a suitable method, probably running a ROP chain and we might also have to bypass CFG. That all sounds too much like hard work, so instead I’ll take a different approach to get arbitrary code running in the PPL binary, by abusing KnownDlls.

KnownDlls and Protected Processes.

In my previous blog post I described a technique to elevate privileges from an arbitrary object directory creation vulnerability to SYSTEM by adding an entry into the KnownDlls directory and getting an arbitrary DLL loaded into a privileged process. I noted that this was also an administrator to PPL code injection as PPL will also load DLLs from the system’s KnownDlls location. As the code signing check is performed during section creation not section mapping as long as you can place an entry into KnownDlls you can load anything into a PPL even unsigned code.This doesn’t immediately seem that useful, we can’t write to KnownDlls without being an administrator, and even then without some clever tricks. However it’s worth looking at how a Known DLL is loaded to get an understanding on how it can be abused. Inside NTDLL’s loader (LDR) code is the following function to determine if there’s a preexisting Known DLL.NTSTATUS LdrpFindKnownDll(PUNICODE_STRING DllName, HANDLE *SectionHandle){
 // If KnownDll directory handle not open then return error.
if(!LdrpKnownDllDirectoryHandle)
return STATUS_DLL_NOT_FOUND;

 OBJECT_ATTRIBUTES ObjectAttributes;
 InitializeObjectAttributes(&ObjectAttributes,
&DllName,
   OBJ_CASE_INSENSITIVE,
   LdrpKnownDllDirectoryHandle,
nullptr);

return NtOpenSection(SectionHandle,
                      SECTION_ALL_ACCESS,
&ObjectAttributes);
}The LdrpFindKnownDll function calls NtOpenSection to open the named section object for the Known DLL. It doesn’t open an absolute path, instead it uses the feature of the native system calls to specify a root directory for the object name lookup in the OBJECT_ATTRIBUTES structure. This root directory comes from the global variable LdrpKnownDllDirectoryHandle. Implementing the call this way allows the loader to only specify the filename (e.g. EXAMPLE.DLL) and not have to reconstruct the absolute path as the lookup with be relative to an existing directory. Chasing references to LdrpKnownDllDirectoryHandle we can find it’s initialized in LdrpInitializeProcess as follows:NTSTATUS LdrpInitializeProcess(){
 // …
 PPEB peb = // …
 // If a full protected process don’t use KnownDlls.
if(peb->IsProtectedProcess &&!peb->IsProtectedProcessLight){
   LdrpKnownDllDirectoryHandle =nullptr;
}else{
   OBJECT_ATTRIBUTES ObjectAttributes;
   UNICODE_STRING DirName;
   RtlInitUnicodeString(&DirName, L»\\KnownDlls»);
   InitializeObjectAttributes(&ObjectAttributes,
&DirName,
                              OBJ_CASE_INSENSITIVE,
nullptr,nullptr);
   // Open KnownDlls directory.
   NtOpenDirectoryObject(&LdrpKnownDllDirectoryHandle,
                         DIRECTORY_QUERY | DIRECTORY_TRAVERSE,
&ObjectAttributes);
}This code shouldn’t be that unexpected, the implementation calls NtOpenDirectoryObject, passing the absolute path to the KnownDlls directory as the object name. The opened handle is stored in theLdrpKnownDllDirectoryHandle global variable for later use. It’s worth noting that this code checks the PEB to determine if the current process is a full protected process. Support for loading Known DLLs is disabled in full protected process mode, which is why even with administrator privileges and the clever trick I outlined in the last blog post we could only compromise PPL, not PP.How does this knowledge help us? We can use our COM type confusion trick to write values into arbitrary memory locations instead of trying to hijack code execution resulting in a data only attack. As we can inherit any handles we like into the new PPL process we can setup an object directory with a named section, then use the type confusion to change the value of LdrpKnownDllDirectoryHandle to the value of the inherited handle. If we induce a DLL load from System32 with a known name the LDR will check our fake directory for the named section and map our unsigned code into memory, even calling DllMain for us. No need for injecting threads, ROP or bypassing CFG.All we need is a suitable primitive to write an arbitrary value, unfortunately while I could find methods which would cause an arbitrary write I couldn’t sufficiently control the value being written. In the end I used the following interface and method which was implemented on the object returned byICorSvcBindToWorker::BindToRuntimeWorker.interface ICorSvcPooledWorker : IUnknown {
   HRESULT CanReuseProcess(
[in] OptimizationScenario scenario,
[in] ICorSvcLogger* pCorSvcLogger,
[out] long* pCanContinue);
};
In the implementation of CanReuseProcess the target value of pCanContinue is always initialized to 0. Therefore by replacing the [out] long* in the type library definition with [in] long we can get 0 written to any memory location we specify. By prefilling the lower 16 bits of the new process’ handle table with handles to a fake KnownDlls directory we can be sure of an alias between the real KnownDlls which will be opened once the process starts and our fake ones by just modifying the top 16 bits of the handle to 0. This is shown in the following diagram:

Once we’ve overwritten the top 16 bits with 0 (the write is 32 bits but handles are 64 bits in 64 bit mode, so we won’t overwrite anything important) LdrpKnownDllDirectoryHandle now points to one of our fakeKnownDlls handles. We can then easily induce a DLL load by sending a custom marshaled object to the same method and we’ll get arbitrary code execution inside the PPL.

Elevating to PPL-Windows TCB

We can’t stop here, attacking MSCORSVW only gets us PPL at the CodeGen signing level, not Windows TCB. Knowing that generating a fake cached signed DLL should run in a PPL as well as Microsoft leaving a backdoor for PPL processes at any signing level I converted my C# code from Issue 1332 to C++ to generate a fake cached signed DLL. By abusing a DLL hijack in WERFAULTSECURE.EXE which will run as PPL Windows TCB we should get code execution at the desired signing level. This worked on Windows 10 1709 and earlier, however it didn’t work on 1803. Clearly Microsoft had changed the behavior of cached signing level in some way, perhaps they’d removed its trust in PPL entirely. That seemed unlikely as it would have a negative performance impact.After discussing this a bit with Alex Ionescu I decided to put together a quick parser with information from Alex for the cached signing data on a file. This is exposed in NtObjectManager as the Get-NtCachedSigningLevel command. I ran this command against a fake signed binary and a system binary which was also cached signed and immediately noticed a difference:

For the fake signed file the Flags are set to TrustedSignature (0x02), however for the system binary PowerShell couldn’t decode the enumeration and so just outputs the integer value of 66 which is 0x42 in hex. The value 0x40 was an extra flag on top of the original trusted signature flag. It seemed likely that without this flag set the DLL wouldn’t be loaded into a PPL process. Something must be setting this flag so I decided to check what happened if I loaded a valid cached signed DLL without the extra flag into a PPL process. Monitoring it in Process Monitor I got my answer:

The Process Monitor trace shows that first the kernel queries for the Extended Attributes (EA) from the DLL. The cached signing level data is stored in the file’s EA so this is almost certainly an indication of the cached signing level being read. In the full trace artifacts of checking the full signature are shown such as enumerating catalog files, I’ve removed those artifacts from the screenshot for brevity. Finally the EA is set, if I check the cached signing level of the file it now includes the extra flag. So setting the cached signing level is done automatically, the question is how? By pulling up the stack trace we can see how it happens:

Looking at the middle of the stack trace we can see the call to CipSetFileCache originates from the call toNtCreateSection. The kernel is automatically caching the signature when it makes sense to do so, e.g. in a PPL so that subsequent image mapping don’t need to recheck the signature. It’s possible to map an image section from a file with write access so we can reuse the same attack from Issue 1332 and replace the call toNtSetCachedSigningLevel with NtCreateSection and we can fake sign any DLL. It turned out that the call to set the file cache happened after the write check introducted to fix Issue 1332 and so it was possible to use this to bypass Device Guard again. For that reason I reported the bypass as Issue 1597 which was fixed in September 2018 as CVE-2018-8449. However, as with Issue 1332 the back door for PPL is still in place so even though the fix eliminated the Device Guard bypass it can still be used to get us from PPL-CodeGen to PPL-WindowsTCB.

Conclusions

This blog showed how I was able to inject arbitrary code into a PPL without requiring administrator privileges. What could you do with this new found power? Actually not a great deal as a normal user but there are some parts of the OS, such as the Windows Store which rely on PPL to secure files and resources which you can’t modify as a normal user. If you elevate to administrator and then inject into a PPL you’ll get many more things to attack such as CSRSS (through which you can certainly get kernel code execution) or attack Windows Defender which runs as PPL Anti-Malware. Over time I’m sure the majority of the use cases for PPL will be replaced with Virtual Secure Mode (VSM) and Isolated User Mode (IUM) applications which have greater security guarantees and are also considered security boundaries that Microsoft will defend and fix.Did I report these issues to Microsoft? Microsoft has made it clear that they will not fix issues only affecting PP and PPL in a security bulletin. Without a security bulletin the researcher receives no acknowledgement for the find, such as a CVE. The issue will not be fixed in current versions of Windows although it might be fixed in the next major version. Previously confirming Microsoft’s policy on fixing a particular security issue was based on precedent, however they’ve recently published a list of Windows technologies that will or will not be fixed in the Windows Security Service Criteria which, as shown below for Protected Process Light, Microsoft will not fix or pay a bounty for issues relating to the feature. Therefore, from now on I will not be engaging Microsoft if I discover issues which I believe to only affect PP or PPL.

The one bug I reported to Microsoft was only fixed because it could be used to bypass Device Guard. When you think about it, only fixing for Device Guard is somewhat odd. I can still bypass Device Guard by injecting into a PPL and setting a cached signing level, and yet Microsoft won’t fix PPL issues but will fix Device Guard issues. Much as the Windows Security Service Criteria document really helps to clarify what Microsoft will and won’t fix it’s still somewhat arbitrary. A secure feature is rarely secure in isolation, the feature is almost certainly secure because other features enable it to be so.
In part 2 of this blog we’ll go into how I was also able to break into Full PP-WindowsTCB processes using another interesting feature of COM.

Реклама

Injecting .Net Assemblies Into Unmanaged Processes

( origin text )

A detailed analysis of how to inject the .net runtime and arbitrary .net assemblies into unmanaged and managed processes; and how to execute managed code within those processes.

 

Screenshot

Introduction

.Net is a powerful language for developing software quickly and reliably. However, there are certain tasks for which .net is unfit. This paper highlights one particular case, DLL injection. A .net DLL (aka managed DLL) cannot be injected inside a remote process in which the .net runtime has not been loaded. Furthermore, even if the .net runtime is loaded in a process one would like to inject, how can methods within the .net DLL be invoked? What about architecture? Does a 64 bit process require different attention than a 32 bit process? The goal of this paper is to show how to perform all of these tasks using documented APIs. Together we will:

  • Start the .net clr (common language runtime) in an arbitrary process regardless of bitness.
  • Load a custom .net assembly in an arbitrary process.
  • Execute managed code in the context of an arbitrary process.

Back To Fundamentals

Several things need to happen in order for us to achieve our goal. To make the problem more manageable, it will be broken down into several pieces and then reassembled at the end. The steps to solving this puzzle are:

  1. Load The CLR (Fundamentals) — Covers how to start the .net framework inside of an unmanaged process.
  2. Load The CLR (Advanced) — Covers how to load a custom .net assembly and invoke managed methods from unmanaged code.
  3. DLL Injection (Fundamentals) — Covers how to execute unmanaged code in a remote process.
  4. DLL Injection (Advanced) — Covers how to execute arbitrary exported functions in a remote process.
  5. Putting It All Together — A solution presents itself; putting it all together.

Note: The author uses the standard convention of function to refer to c++ functions, and method to refer to c# functions. The term «remote» process refers to any process other than the current process.

Load The CLR

Writing an unmanaged application that can load both the .net runtime and an arbitrary assembly into itself is the first step towards achieving our goal.

Fundamentals

The following sample program illustrates how a c++ application can load the .net runtime into itself:

#include <metahost.h>

#pragma comment(lib, "mscoree.lib")

#import "mscorlib.tlb" raw_interfaces_only \
    high_property_prefixes("_get","_put","_putref") \
    rename("ReportEvent", "InteropServices_ReportEvent")

int wmain(int argc, wchar_t* argv[])
{
    char c;
    wprintf(L"Press enter to load the .net runtime...");
    while (getchar() != '\n');	
    
    HRESULT hr;
    ICLRMetaHost *pMetaHost = NULL;
    ICLRRuntimeInfo *pRuntimeInfo = NULL;
    ICLRRuntimeHost *pClrRuntimeHost = NULL;
    
    // build runtime
    hr = CLRCreateInstance(CLSID_CLRMetaHost, IID_PPV_ARGS(&pMetaHost));
    hr = pMetaHost->GetRuntime(L"v4.0.30319", IID_PPV_ARGS(&pRuntimeInfo));
    hr = pRuntimeInfo->GetInterface(CLSID_CLRRuntimeHost, 
        IID_PPV_ARGS(&pClrRuntimeHost));
    
    // start runtime
    hr = pClrRuntimeHost->Start();
    
    wprintf(L".Net runtime is loaded. Press any key to exit...");
    while (getchar() != '\n');
    
    return 0;
}

In the above code, the calls of interest are:

CLRCreateInstance — given CLSID_CLRMetaHost, gets a pointer to an instance of ICLRMetaHost
ICLRMetaHost::GetRuntime — get a pointer of type ICLRRuntimeInfo pointing to a specific .net runtime
ICLRRuntimeInfo::GetInterface — load the CLR into the current process and get a ICLRRuntimeHost pointer
ICLRRuntimeHost::Start — explicitly start the CLR, implicitly called when managed code is first loaded

At the time of this writing, the valid version values for ICLRMetaHost::GetRuntime are NULL«v1.0.3705»«v1.1.4322»«v2.0.50727», and «v4.0.30319» where NULL loads the latest version of the runtime. The desired runtime should be installed on the system, and the version values above should be present in either %WinDir%\Microsoft.NET\Framework or %WinDir%\Microsoft.NET\Framework64.

Compiling and running the code above produces the following output as seen from the console and Process Hacker:

console screenshot
process hacker screenshot

Once pressing enter it can be observed, via Process Hacker, that the .net runtime has been loaded. Notice the additional tabs referencing .net on the properties pane:

console screenshot
process hacker screenshot

The above sample code is not included in the source download. However, it is recommended as an exercise for the reader to build and run the sample.

Advanced

With the first piece of the puzzle in place, the next step is to load an arbitrary .net assembly into a process and invoke methods inside that .net assembly.

To continue building off the above example, the CLR has just been loaded into the process. This was achieved by obtaining a pointer to the CLR interface; this pointer is stored in the variable pClrRuntimeHost. Using pClrRuntimeHost a call was made to ICLRRuntimeHost::Start to initialize the CLR into the process.

Now that the CLR has been initialized, pClrRuntimeHost can make a call to ICLRRuntimeHost::ExecuteInDefaultAppDomain to load and invoke methods in an arbitrary .net assembly. The function has the following signature:

HRESULT ExecuteInDefaultAppDomain (
    [in] LPCWSTR pwzAssemblyPath,
    [in] LPCWSTR pwzTypeName, 
    [in] LPCWSTR pwzMethodName,
    [in] LPCWSTR pwzArgument,
    [out] DWORD *pReturnValue
);

A brief description of each parameter:

pwzAssemblyPath — the full path to the .net assembly; this can be either an exe or a dll file
pwzTypeName — the fully qualified typename of the method to invoke
pwzMethodName — the name of the method to invoke
pwzArgument — an optional argument to pass into the method
pReturnValue — the return value of the method

Not every method inside a .net assembly can be invoked via ICLRRuntimeHost::ExecuteInDefaultAppDomain. Valid .net methods must have the following signature:

static int pwzMethodName (String pwzArgument);

As a side note, access modifiers such as public, protected, private, and internal do not affect the visibility of the method; therefore, they have been excluded from the signature.

The following .net application will be used in all the examples that follow as the managed .net assembly to be injected inside processes:

using System;
using System.Windows.Forms;

namespace InjectExample
{
    public class Program
    {
        static int EntryPoint(String pwzArgument)
        {
            System.Media.SystemSounds.Beep.Play();

            MessageBox.Show(
                "I am a managed app.\n\n" + 
                "I am running inside: [" + 
                System.Diagnostics.Process.GetCurrentProcess().ProcessName + 
                "]\n\n" + (String.IsNullOrEmpty(pwzArgument) ? 
                "I was not given an argument" : 
                "I was given this argument: [" + pwzArgument + "]"));

            return 0;
        }

        static void Main(string[] args)
        {
            EntryPoint("hello world");
        }
    }
}

The example application above was written in such a way that it can be called via ICLRRuntimeHost::ExecuteInDefaultAppDomain or run standalone; with either approach producing similar behavior. The ultimate goal is that when injected into an unmanaged remote process, the application above will execute in the context of that process, and display a message box showing the remote process name.

Building on top of the sample code from the Fundamentals section, the following c++ program will load the above .net assembly and execute the EntryPoint method:

#include <metahost.h>

#pragma comment(lib, "mscoree.lib")

#import "mscorlib.tlb" raw_interfaces_only \
    high_property_prefixes("_get","_put","_putref") \
    rename("ReportEvent", "InteropServices_ReportEvent")

int wmain(int argc, wchar_t* argv[])
{
    HRESULT hr;
    ICLRMetaHost *pMetaHost = NULL;
    ICLRRuntimeInfo *pRuntimeInfo = NULL;
    ICLRRuntimeHost *pClrRuntimeHost = NULL;
    
    // build runtime
    hr = CLRCreateInstance(CLSID_CLRMetaHost, IID_PPV_ARGS(&pMetaHost));
    hr = pMetaHost->GetRuntime(L"v4.0.30319", IID_PPV_ARGS(&pRuntimeInfo));
    hr = pRuntimeInfo->GetInterface(CLSID_CLRRuntimeHost, 
        IID_PPV_ARGS(&pClrRuntimeHost));
    
    // start runtime
    hr = pClrRuntimeHost->Start();
    
    // execute managed assembly
    DWORD pReturnValue;
    hr = pClrRuntimeHost->ExecuteInDefaultAppDomain(
    	L"T:\\FrameworkInjection\\_build\\debug\\anycpu\\InjectExample.exe", 
    	L"InjectExample.Program", 
    	L"EntryPoint", 
    	L"hello .net runtime", 
    	&pReturnValue);
    
    // free resources
    pMetaHost->Release();
    pRuntimeInfo->Release();
    pClrRuntimeHost->Release();
    
    return 0;
}

The following screenshot shows the output of the application:

test .net assembly outputSo far two pieces of the puzzle are in place. It is now understood how to load the CLR and execute arbitrary methods from within unmanaged code. But how can this be done in arbitrary processes?

DLL Injection

DLL injection is a strategy used to execute code inside a remote process by loading a DLL in the remote process. Many DLL injection tactics focus on code executing inside of DllMain. Unfortunately, attempting to start the CLR from within DllMain will cause the Windows loader to deadlock. This can be independently verified by writing a sample DLL that attempts to start the CLR from within DllMain. Verification is left as an excercise for the reader. For more information regarding .net initialization, the Windows loader, and loader lock; please refer to the following MSDN articles:

Inevitably the result is that the CLR cannot be started while the Windows loader is initializing another module. Each lock is process specific and managed by Windows. Remember, any module that attempts to acquire more than one lock on the loader when a lock has already been acquired will deadlock.

Fundamentals

The issue regarding the Windows loader seems sizable; and whenever a problem seems large it helps to break it down into more manageable pieces. Developing a strategy to inject a barebones DLL inside of a remote process is a good start. Take the following sample code:

#define WIN32_LEAN_AND_MEAN
#include <windows.h>

BOOL APIENTRY DllMain(HMODULE hModule, DWORD  ul_reason_for_call, LPVOID lpReserved)
{
    switch (ul_reason_for_call)
    {
    case DLL_PROCESS_ATTACH:
    case DLL_THREAD_ATTACH:
    case DLL_THREAD_DETACH:
    case DLL_PROCESS_DETACH:
        break;
    }
    return TRUE;
}

The above code implements a simple DLL. To inject this DLL in a remote process, the following Windows APIs are needed:

OpenProcess — acquire a handle to a process
GetModuleHandle — acquire a handle to the given module
LoadLibrary — load a library in the address space of the calling process
GetProcAddress — get the VA (virtual address) of an exported function from a library
VirtualAllocEx — allocate space in a given process
WriteProcessMemory — write bytes into a given process at a given address
CreateRemoteThread — spawn a thread in a remote process

Moving on, the purpose of the function below is to execute an exported function of a DLL that has been loaded inside a remote process:

DWORD_PTR Inject(const HANDLE hProcess, const LPVOID function, 
    const wstring& argument)
{
    // allocate some memory in remote process
    LPVOID baseAddress = VirtualAllocEx(hProcess, NULL, GetStringAllocSize(argument), 
        MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE);
    
    // write argument into remote process	
    BOOL isSucceeded = WriteProcessMemory(hProcess, baseAddress, argument.c_str(), 
        GetStringAllocSize(argument), NULL);
    
    // make the remote process invoke the function
    HANDLE hThread = CreateRemoteThread(hProcess, NULL, 0, 
        (LPTHREAD_START_ROUTINE)function, baseAddress, NULL, 0);
    
    // wait for thread to exit
    WaitForSingleObject(hThread, INFINITE);
    
    // free memory in remote process
    VirtualFreeEx(hProcess, baseAddress, 0, MEM_RELEASE);
    
    // get the thread exit code
    DWORD exitCode = 0;
    GetExitCodeThread(hThread, &exitCode);
    
    // close thread handle
    CloseHandle(hThread);
    
    // return the exit code
    return exitCode;
}

Remember that the goal in this section is to load a library in a remote process. The next question is how can the above function be used to inject a DLL in a remote process? The answer lies in the assumption that kernel32.dll is mapped inside the address space of every process. LoadLibrary is an exported function of kernel32.dll and it has a function signature that matches LPTHREAD_START_ROUTINE, so it can be passed as the start routine to CreateRemoteThread. Recall that the purpose of LoadLibrary is to load a library in the address space of the calling process, and the purpose of CreateRemoteThread is to spawn a thread in a remote process. The following snippet illustrates how to load our test DLL inside of a process with ID 3344:

// get handle to remote process with PID 3344
HANDLE hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, 3344);

// get address of LoadLibrary
FARPROC fnLoadLibrary = GetProcAddress(GetModuleHandle(L"Kernel32"), "LoadLibraryW");

// inject test.dll into the remote process
Inject(hProcess, fnLoadLibrary, L"T:\\test\\test.dll");

To continue building off the above example, once CreateRemoteThread is invoked, a call is made to WaitForSingleObject to wait for the thread to exit. Next a call is made to GetExitCodeThread to obtain the result. Coincidentally, when LoadLibrary is passed into CreateRemoteThread, a successful invocation will result in the lpExitCode of GetExitCodeThread returning the base address of the loaded library in the context of the remote process. This works great for 32-bit applications, but not for 64-bit applications. The reason is that lpExitCode of GetExitCodeThread is a DWORD value even on 64-bit machines, hence the address is truncated.

At this point three pieces of the puzzle are in place. To recap, the following problems have been solved:

  1. Loading the CLR using unmanaged code
  2. Executing arbitrary .net assemblies from unmanaged code
  3. Injecting DLLs

Advanced

Now that loading a DLL in a remote process is understood; discussion on how to start the CLR in a remote process can continue.

When LoadLibrary returns, the lock on the loader will be free. With the DLL in the address space of the remote process, exported functions can be invoked via subsequent calls to CreateRemoteThread; granted that the function signature matches LPTHREAD_START_ROUTINE. However, this invariably leads to more questions. How can exported functions be invoked inside a remote process, and how can pointers to these functions be obtained? Since GetProcAddress does not have a matching LPTHREAD_START_ROUTINE signature, how can the addresses of functions inside the DLL be obtained? Furthermore, even if GetProcAddress could be invoked, it still expects a handle to the remote DLL. How can this handle be obtained for 64-bit machines?

It is time to divide and conquer again. The following function reliably returns the handle (coincidentally the base address) of a given module in a given process on both x86 and x64 systems:

DWORD_PTR GetRemoteModuleHandle(const int processId, const wchar_t* moduleName)
{
    MODULEENTRY32 me32; 
    HANDLE hSnapshot = INVALID_HANDLE_VALUE;
    
    // get snapshot of all modules in the remote process 
    me32.dwSize = sizeof(MODULEENTRY32); 
    hSnapshot = CreateToolhelp32Snapshot(TH32CS_SNAPMODULE, processId);
    
    // can we start looking?
    if (!Module32First(hSnapshot, &me32)) 
    {
    	CloseHandle(hSnapshot);
    	return 0;
    }
    
    // enumerate all modules till we find the one we are looking for 
    // or until every one of them is checked
    while (wcscmp(me32.szModule, moduleName) != 0 && Module32Next(hSnapshot, &me32));
    
    // close the handle
    CloseHandle(hSnapshot);
    
    // check if module handle was found and return it
    if (wcscmp(me32.szModule, moduleName) == 0)
    	return (DWORD_PTR)me32.modBaseAddr;
    
    return 0;
}

Knowing the base address of the DLL in the remote process is a step in the right direction. It is time to devise a strategy for acquiring the address of an arbitrary exported function. To recap, it is known how to call LoadLibraryand obtain a handle to the loaded module in a remote process. Knowing this, it is trivial to invoke LoadLibrarylocally (within the calling process) and obtain a handle to the loaded module. This handle (which is also the base address of the module) may or may not be the same as the one in the remote process, even though it is the same library. Nevertheless, with some basic math we can acquire the address for any exported function we wish. The idea is that although the base address of a module may differ from process to process, the offset of any given function relative to the base address of the module is constant. As an example, take the following exported function found in the Bootstrap DLL project in the source download:

__declspec(dllexport) HRESULT ImplantDotNetAssembly(_In_ LPCTSTR lpCommand)

Before this function can be invoked remotely, the Bootstrap.dll module must first be loaded in the remote process. Using Process Hacker, here is a screenshot of where the Windows loader placed the Bootstrap.dll module in memory when injected into Firefox:

Firefox Bootstap.dllContinuing with our strategy, here is a sample program that loads the Bootstrap.dll module locally (within the calling process):

#include <windows.h>

int wmain(int argc, wchar_t* argv[])
{
    HMODULE hLoaded = LoadLibrary(
        L"T:\\FrameworkInjection\\_build\\release\\x86\\Bootstrap.dll");
    system("pause");
    return 0;
}

When the above program is run, here is a screenshot of where Windows decided to load the Bootstrap.dllmodule:

Test Bootstrap.dllThe next step is, within wmain, to make a call to GetProcAddress to get the address of the ImplantDotNetAssembly function:

#include <windows.h>

int wmain(int argc, wchar_t* argv[])
{
    HMODULE hLoaded = LoadLibrary(
        L"T:\\FrameworkInjection\\_build\\debug\\x86\\Bootstrap.dll");
    
    // get the address of ImplantDotNetAssembly
    void* lpInject = GetProcAddress(hLoaded, "ImplantDotNetAssembly");
    
    system("pause");
    return 0;
}

The address of a function within a module will always be higher than the module’s base address. Here is where basic math comes in to play. Below is a table to help illustrate:

Firefox.exe | Bootstrap.dll @ 0x50d0000 | ImplantDotNetAssembly @ ?
test.exe | Bootstrap.dll @ 0xf270000 | ImplantDotNetAssembly @ 0xf271490 (lpInject)

Test.exe shows that Bootstrap.dll is loaded at address 0xf270000, and that ImplantDotNetAssembly can be found in memory at address 0xf271490. Subtracting the address of ImplantDotNetAssembly from the address of Bootstrap.dll will give the offset of the function relative to the base address of the module. The math shows that ImplantDotNetAssembly is (0xf271490 — 0xf270000) = 0x1490 bytes into the module. This offset can then be added onto the base address of the Bootstrap.dll module in the remote process to reliably give the address of ImplantDotNetAssembly relative to the remote process. The math to compute the address of ImplantDotNetAssembly in Firefox.exe shows that the function is located at address (0x50d0000 + 0x1490) = 0x50d1490. The following function computes the offset of a given function in a given module:

DWORD_PTR GetFunctionOffset(const wstring& library, const char* functionName)
{
    // load library into this process
    HMODULE hLoaded = LoadLibrary(library.c_str());
    
    // get address of function to invoke
    void* lpInject = GetProcAddress(hLoaded, functionName);
    
    // compute the distance between the base address and the function to invoke
    DWORD_PTR offset = (DWORD_PTR)lpInject - (DWORD_PTR)hLoaded;
    
    // unload library from this process
    FreeLibrary(hLoaded);
    
    // return the offset to the function
    return offset;
}

It is worth noting that ImplantDotNetAssembly intentionally matches the signature of LPTHREAD_START_ROUTINE; as should all methods passed to CreateRemoteThread. Armed with the ability to execute arbitrary functions in a remote DLL, the logic to initialize the CLR has been put inside functionImplantDotNetAssembly in Bootstrap.dll. Once Bootstrap.dll is loaded in a remote process, the address of ImplantDotNetAssembly can be computed for the remote instance and then invoked via CreateRemoteThread. This is it, the final piece of the puzzle; now it is time to put it all together.

Putting It All Together

The main reason behind using an unmanaged DLL (Bootstrap.dll) to load the CLR is that if the CLR is not running in the remote process, the only way to start it is from unmanaged code (barring use of a scripting language such as Python, which has its own set of dependencies).

In addition, it would be nice for the Inject application to be flexible enough to accept input on the command line; because who would want to recompile every time their app changes? The Inject application accepts the following options:

 m The name of the managed method to execute. ex~ EntryPoint
 i The fully qualified path of the managed assembly to inject inside of the remote process. ex~ C:\InjectExample.exe
 l The fully qualified type name of the managed assembly. ex~ InjectExample.Program
 a An optional argument to pass into the managed function.
 n The process ID or the name of the process to inject. ex~ 1500 or notepad.exe

The wmain method for Inject looks like:

int wmain(int argc, wchar_t* argv[])
{	
    // parse args (-m -i -l -a -n)
    if (!ParseArgs(argc, argv))
    {
        PrintUsage();
        return -1;
    }
    
    // enable debug privileges
    EnablePrivilege(SE_DEBUG_NAME, TRUE);
    
    // get handle to remote process
    HANDLE hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, g_processId);
    
    // inject bootstrap.dll into the remote process
    FARPROC fnLoadLibrary = GetProcAddress(GetModuleHandle(L"Kernel32"), 
        "LoadLibraryW");
    Inject(hProcess, fnLoadLibrary, GetBootstrapPath()); 
    
    // add the function offset to the base of the module in the remote process
    DWORD_PTR hBootstrap = GetRemoteModuleHandle(g_processId, BOOTSTRAP_DLL);
    DWORD_PTR offset = GetFunctionOffset(GetBootstrapPath(), "ImplantDotNetAssembly");
    DWORD_PTR fnImplant = hBootstrap + offset;
    
    // build argument; use DELIM as tokenizer
    wstring argument = g_moduleName + DELIM + g_typeName + DELIM + 
        g_methodName + DELIM + g_Argument;
    
    // inject the managed assembly into the remote process
    Inject(hProcess, (LPVOID)fnImplant, argument);
    
    // unload bootstrap.dll out of the remote process
    FARPROC fnFreeLibrary = GetProcAddress(GetModuleHandle(L"Kernel32"), 
        "FreeLibrary");
    CreateRemoteThread(hProcess, NULL, 0, (LPTHREAD_START_ROUTINE)fnFreeLibrary, 
        (LPVOID)hBootstrap, NULL, 0);
    
    // close process handle
    CloseHandle(hProcess);
    
    return 0;
}

Below is a screenshot of the Inject.exe app being called to inject the .net assembly InjectExample.exe into notepad.exe along with the command line that was used:

«T:\FrameworkInjection\_build\release\x64\Inject.exe» -m EntryPoint -i «T:\FrameworkInjection\_build\release\anycpu\InjectExample.exe» -l InjectExample.Program -a «hello inject» -n «notepad.exe»

inject notepad.exeIt is worthwhile to mention and distinguish the differences between injecting a DLL that was built targetting x86x64, or AnyCPU. Under normal circumstances, the x86 flavor of Inject.exe and Bootstrap.dll should be used to inject x86 processes, and the x64 flavor should be used to inject x64 processes. It is the responsibility of the caller to ensure the proper binaries are used. AnyCPU is a platform available in .net. Targetting AnyCPU tells the CLR to JIT the assembly for the appropriate architecture. This is why the same InjectExample.exe assembly can be injected into either an x86 or x64 process.

Running The Code

Moving on to the fun stuff! There are a few prerequisites for getting the code to run using the default settings.

To build the code:

  1. Visual Studio 2012 Express +
  2. Visual Studio 2012 Express Update 1 +

To run the code:

  1. .Net Framework 4.0 +
  2. Visual C++ Redistributable for Visual Studio 2012 Update 1 +
  3. Windows XP SP3 +

To simplify the the task of building the code, there is a file called build.bat in the source download that will take care of the heavy lifting provided the prerequisites have been installed. It will compile both the debug and release builds along with the corresponding x86, x64, and AnyCPU builds. Each project can also be built independently, and will compile from Visual Studio. The script build.bat will place the binaries in a folder named _build.

The code is also well commented. In addition, c++ 11.0 and .net 4.0 were chosen because both runtimes will execute on all Windows OSes from XP SP3 x86 to Windows 8 x64. As a side note, Microsoft added XP SP3 support for the C++ 11.0 runtime in VS 2012 U1.

Closing Notes

Traditionally, at this point papers tend to wind down and reflect on the techniques learned and other areas of improvement. While this is an excellent place to perform such an exercise, the author decided to do something different and present the reader with a basket full of:

easter eggsAs mentioned in the Introduction, .net is a powerful language. For example, the Reflection API in .net can be leveraged to obtain type information about assemblies. The significance of this is that .net can be used to scan an assembly and return the valid methods that can be used for injection! The source download contains a .net project called InjectGUI. This project contains a managed wrapper around our unmanaged Inject application. InjectGUI displays a list of running processes, decides whether to invoke the 32 or 64 bit version of Inject, as well as scans .net assemblies for valid injectable methods. Within InjectGUI is a file named InjectWrapper.cs which contains the wrapper logic.

There is also a helper class called MethodItem which has the following definition:

public class MethodItem
{
    public string TypeName { get; set; }
    public string Name { get; set; }
    public string ParameterName { get; set; }
}

The following snippet from the ExtractInjectableMethods method will obtain a Collection of type List<MethodItem>, which matches the required method signature:

// find all methods that match: 
//    static int pwzMethodName (String pwzArgument)

private void ExtractInjectableMethods()
{
    // ...

    // open assembly
    Assembly asm = Assembly.LoadFile(ManagedFilename);

    // get valid methods
    InjectableMethods = 
        (from c in asm.GetTypes()
        from m in c.GetMethods(BindingFlags.Static | 
            BindingFlags.Public | BindingFlags.NonPublic)
        where m.ReturnType == typeof(int) &&
            m.GetParameters().Length == 1 &&
            m.GetParameters().First().ParameterType == typeof(string)
        select new MethodItem
        {
            Name = m.Name,
            ParameterName = m.GetParameters().First().Name,
            TypeName = m.ReflectedType.FullName
        }).ToList();

    // ...
}

Now that the valid (injectable) methods have been extracted, the UI should also know if the process to be injected is 32 or 64 bit. To do this, a little help is needed from the Windows API:

[DllImport("kernel32.dll", SetLastError = true, CallingConvention = 
    CallingConvention.Winapi)]
[return: MarshalAs(UnmanagedType.Bool)]
private static extern bool IsWow64Process([In] IntPtr process, 
    [Out] out bool wow64Process);

IsWow64Process is only defined on 64 bit OSes, and it returns true if the app is 32 bit. In .net 4.0, the following property has been introduced: Environment.Is64BitOperatingSystem. This can be used to help determine if the IsWow64Process function is defined as seen in this wrapper function:

private static bool IsWow64Process(int id)
{
    if (!Environment.Is64BitOperatingSystem)
        return true;

    IntPtr processHandle;
    bool retVal;

    try
    {
        processHandle = Process.GetProcessById(id).Handle;
    }
    catch
    {
        return false; // access is denied to the process
    }

    return IsWow64Process(processHandle, out retVal) && retVal;
}

The logic inside the InjectGUI project is fairly straightforward. Some understanding of WPF and Dependency Properties is necessary to understand the UI, however, all the pertinent logic related to injection is in the InjectWrapper class. The UI is built using Modern UI for WPF, and the icons are borrowed from Modern UI Icons. Both projects are open source, and the author is affiliated with neither. Below is a screenshot of InjectGUI in action:

inject gui

Closing Notes Part Deux

This concludes the discussion on injecting .net assemblies into unmanaged processes. Don’t cry because it’s over. Smile ☺ because it happened. ~ William Shakespeare

Windows Processes

Origin text: Blue Team fundamentals Part Two: Windows Processes.

 

There is a lot of information to be gleaned from Windows processes. The thing with processes is that there are a lot of them, and it can seem massively overwhelming, However with a bit of patience and the aid of a book or three (which I will touch on at the end of this post) you can get really quite far under your own steam.

 

This obviously requires some form of logging of process trees in a multi host/network scenario, there are a good number of products out there that do this now, especially with buzzword bingo firing on all cylinders, if your endpoint product/agent declares itself ‘next gen’ it should do this. We however, will look at these processes from a single host perspective for the sake of this write-up. The rules and theories will be the same though.

First off we need to arm ourselves with ‘Process Explorer’, which is part of the Windows Sys Internals Suite which can be found at https://technet.microsoft.com/en-gb/sysinternals/bb842062

and when fired up looks a bit like this:

All those lovely processes!

So, lets take a look shall we?

System/Idle

These two are special. Why? Well they are not technically full processes. They are not running a user-mode executable as they are both created by ntoskrnl.exe (NT OS Kernel), which as the name implies means it is running in kernel mode. More information on the difference between user mode and kernel mode can be found here:

Some key features of these two processes are:

  • Neither should have a visible parent process
  • In the case of the idle process it should be operating one thread per CPU as seen here on my quad core machine.
  • The PID value for System is always 4:
  • There should only ever be one instance of System:

What can we look for:

  • If either process has a visible parent ID.
  • If there are more idle threads than CPU’s on the host.
  • If the PID for system is not 4.
  • If you have multiple instances of System.

Session Manager Subsystem/SMSS.EXE

smss.exe is executed during the startup of the OS and is the first user-mode process to be started by the kernel. It starts both kernel and user modes of the Win32 subsystem. This includes win32k.sys (kernel-mode), winsrv.dll (user-mode), and csrss.exe (user-mode). Any other subsystems listed in the following required registry value are also started:

HKLM\System\CurrentControlSet\Control\Session Manager\SubSystems

Required is the value that lists which subsystems should be loaded at boot time.

Windows lists the file specification of the Windows subsystem and we can see it contains %SystemRoot%\system32\csrss.exe

Specifics of this process:

  • It’s Parent will always be System
  • It’s user will always be NT AUTHORITY\SYSTEM
  • Runs from \systemroot\System32\smss.exe
  • all of the above can be seen below:
  • There should only ever be one instance SMSS.EXE running. This is actually very important because it is responsible for launching both WINLOGON.EXE, WININIT.EXE and CSRSS.EXE. Now you might be wondering why this is important? Well, after launching these SMSS.EXE exits and a new instance is started, meaning that when viewing either of these processes, they may have Parent PID (Process ID) attached but the process name should be unknown.
Unknown Parent Process
  • The OS Session is started under Session 0
  • The first user Session is started under session 1

What can we look for:

  • Parent/User is not SYSTEM
  • Seen running from outside of \%systemroot%\system32\
  • Either winlogon, wininit or csrss having a parent process (at all).
  • Unexpected subsystem registry entries.

Flying off on a tangent:

As we touched on Sessions I think it’s worth have a slightly more in depth look at them. If you launch WinObj.exe from sys internals (as admin) and browse to the /Sessions folder you will see something similar to this:

As we already said 0 is the OS session instance and 1 is the first user.

Taking a look at one of the OS session we can see a shared network drive:

Whilst taking a look at user session under WindowStations we see:

So what does this prove? Well preemptively jumping ahead and looking at explorer.exe and it’s associated handlers we can see:

This shows we can view the namespace instance for a specific process.

anyway…


Windows Logon Process/WINLOGON.EXE

 
  • Does not have a parent process
  • Runs as NT AUTHORITY\SYSTEM
  • Runs out of \Windows\System32
  • Can spawn child processes if alternate login devices are used such as card /biometric readers etc
  • Secure Authentication Sequence/SAS (Ctrl+Alt+Delete) is handled by winlogon
  • Loads Userinit from registry location:
HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Winlogon

which:

Specifies the programs that Winlogon runs when a user logs on. By default, Winlogon runs Userinit.exe, which runs logon scripts, reestablishes network connections, and then starts Explorer.exe, the Windows user interface.

https://technet.microsoft.com/en-gb/library/cc939862.aspx

  • The registry entry for userinit should be userinit.exe, (The comma is intended). This can be appended with other locations and can be utilised by malware to run things at logon.
  • The shell registry entry in the same location should be Explorer.exe (Windows Explorer). This can also be appended with other values allowing the running of malware at logon.

Registry entries that when modified will load content at logon.
  • As with SMSS.EXE, userinit.exe exits after it has run, meaning anything loaded from registry (explorer.exe, dodgy malwares) will not have a parent process. It also means userinit.exe will not be visible to you in Process Explorer.

What can we look for:

  • Shell and Userinit registry entries with undesirable values.
  • Having a parent process
  • Not running as NT AUTHORITY\SYSTEM
  • Runs out of a location that isn’t \Windows\System32

Explorer.exe / Windows Explorer

As Winlogon runs userinit which spawns explorer, let’s look at the explorer process next.

  • We have already covered the fact that userinit.exe exits after execution meaning no parent process.
  • Again as already covered, the registry entry lives under
HKLM\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Winlogon\Shell
  • The user will be the logged in account (that which was used to logon via the WINLOGON.EXE process).
  • It runs out of \%Systemroot%\Explorer.exe
Take my word for it, that was my username.
  • Explorer spawns child processes
Child processes spawned from explorer.exe

What can we look for:

  • Not running out of system root.
  • Assuming a genuine naming convention in use, running under context of an unrecognised user.
  • Making any TCP/IP connections.
No TCP/IP connections — This is good.

CSRSS.EXE / Client-Server Run

  • Runs as NT AUTHORITY\SYSTEM
  • Runs out of \%SystemRoot%\system32\csrss.exe
  • There is an instance loaded for each session, which loads three DLLs; basesrv.dll, winsrv.dll and csrsrv.dll. These dll’s support the creation and deletion of threads and processes (amongst other stuff).
  • It’s a session 0 process

What can we look for:

  • Running outside \system32
  • Running as non SYSTEM user

WININIT.EXE / Windows Initialisation Process

  • Runs as NT AUTHORITY\SYSTEM
  • Runs from %SystemRoot%\system32\wininit.exe
  • Creates a Window station (Winsta0) and two desktops (Winlogon and Default) for processes to run on in session 0
  • Creates Services.exe
  • Starts Lsass.exe
  • Starts Lsm.exe
  • Always a child of SMSS.EXE (We know this won’t have a Parent process due to smss termination after running)
  • Creates %windir%\temp
  • Runs within session 0

What can we look for:

  • Running outside system32
  • Running as non SYSTEM user
  • Child of an identifiable process.

  • Runs as NT AUTHORITY\SYSTEM
  • Run from %SystemRoot%\System32\services.exe
  • Child to WININIT.EXE
  • Spawns Multiple services which can be seen in registry location:
SYSTEM\CurrentControlSet\Services
  • Run from %SystemRoot%\system32\services.exe
  • Runs as NT AUTHORITY\SYSTEM
  • The process should spawn many generic svchost.exe which are responsible for running the services.

What can we look for:

  • Running outside \system32
  • Running as non SYSTEM user
  • Child of a process that is not WININIT.EXE

SVCHOST.EXE / Service Hosting Process

  • There will be multiple instances of SVCHOST running as per the following screen grabs:
tasklist.exe /svc on the left, compared to process explorer on the right and you can see the PIDS line up.
all the svchosts
an expanded svchost
  • Runs from %SystemRoot%\System32\svchost.exe
  • Can run as any of the following accounts: NT AUTHORITY\SYSTEM, LOCAL SERVICE, or NETWORK SERVICE
  • Always the child process of services.exe
  • It will always follow the command line convention of “svchost.exe -k [name]”
  • -k [name] values should exist within the following registry location:
Software\Microsoft\Windows NT\CurrentVersion\Svchost
Registry entries
Running under svchost context

What can we look for:

  • Processes not running under NT AUTHORITY\SYSTEM, LOCAL SERVICE, or NETWORK SERVICE
  • Processes not using the -k [name] convention
  • Parent not services.exe
  • Running from outside \system32

LSASS.EXE / Local Security Authority

  • Run as NT AUTHORITY\SYSTEM
  • Run from %SystemRoot%\System32\lsass.exe
  • Spawned by WININIT.EXE
  • There should never be more than one LSASS.EXE process.
  • It should never spawn any child processes.
  • Runs within session 0

What can we look for:

  • Not running as SYSTEM.
  • Due to the nature of LSASS it is a high value target for malware and frequently spoofed.
  • lsass not running out of %SystemRoot%\System32\ is highly likely to be malicious.
  • Anything that looks like it has been spawned by lsass is also likely malicious. In most cases this is a malware author using clever font techniques to obfuscate the file; Isass vs lsass for example.

LSM.EXE / Load Session Manager Service

  • Runs as NT AUTHORITY\SYSTEM
  • Runs from %systemroot%\System32\lsm.exe
  • Parent Process should always be wininit.exe
  • The Local Session Manager manages (← shocking) the state of terminal server sessions on the local machine. It sends requests to Smss through the ALPC port SmSsWinStationApiPort to start new sessions.
  • LSM.EXE should never spawn any child processes
  • Runs within session 0

What can we look for:

  • LSM spawning any child process
  • Running outside system32
  • Not running as SYSTEM
  • Parent process no wininit.exe