Windows Process Injection In 2019 - Black Hat Briefings

Transcription

Windows Process Injection in 2019Amit Klein, Itzik KotlerSafebreach LabsIntroductionProcess injection in Windows appears to be a well-researched topic, with many techniques now knownand implemented to inject from one process to the other. Process injection is used by malware to gainmore stealth (e.g. run malicious logic in a legitimate process) and to bypass security products (e.g. AV,DLP and personal firewall solutions) by injecting code that performs sensitive operations (e.g. networkaccess) to a process which is privileged to do so.In late 2018, we decided to take a closer look at process injection in Windows. Part of our researcheffort was to understand the landscape, with a focus on present-day platforms (Windows 10 x64 1803 ,64-bit processes), and there we came across several problems: We could not find a single location with a full list of all injection techniques. There are sometexts that review multiple injection techniques (hat tip to Ashkan Hosseini, EndGame for a nicecollection and-trending-process and to Csaba Fitzl AKA “TheEvilBit” for someimplementations https://github.com/theevilbit/injection), but they’re all very far from capturingall (or almost all) techniques.The texts that describe injection techniques typically lump together “true injection techniques”(the object of this paper) with other related topics, such as process hollowing and stealthyprocess spawning. In this paper, we’re interested only in injection from one 64-bit process(medium integrity) to another, already running 64-bit process (medium integrity).The texts often try to present a complete injection process, therefore mixing writing andexecution techniques, when only one of them is novel.Many texts target 32-bit processes, and it was not clear whether they apply to 64-bit processes.Many texts target pre-Windows 10 platforms, and it is not clear whether they apply to Windows10, with its implementation changes and with its new security features.Some attacks require privilege elevation, and as such are not interesting.The texts that describe process injection lack analysis – discussion of requirements andlimitations, impact of Windows 10 security features, etc.The texts usually provide a PoC, but it’s “too well written” – meaning, the PoC checks for returncode, handles errors, handles 32-bit and 64-bit processes, edge conditions, etc. Also, the PoCimplements an end-to-end injection (not just the novel write/execute technique). As such, thePoC becomes pretty big and difficult to follow.

In this paper, we address all the above issues. We provide the first comprehensive catalogue of trueprocess injection techniques in Windows. We categorize the individual techniques into write primitivesand execution methods. We test the techniques against 64-bit processes (medium integrity) running onWindows 10 x64. We test them with and without process protection techniques (CFG, CIG), we analyzeeach technique and explain its requirements and limitations. Finally, we provide stripped down,minimalistic PoC code that works, and at the same time is short enough to clearly show the technique athand.We tried to be as comprehensive as possible, i.e. really cover all different techniques. But of course, thisis a live document, as new techniques will surely be discovered, and as we probably missed a few. Wealso tried to give credit to the original inventor of the technique, if we could find one. Again, this isprobably imperfect, and readers are encouraged to send us corrections.Finally, we get back to our original goal, and describe a new injection technique that inherently bypassesCFG.Windows Process injection in 2019Classes of Injection TechniquesWe classify injection techniques as follows:1. Process spawning – these methods create a process instance of a legitimate executable binary,and typically modify it before the process starts running. Process spawning is very “noisy” and assuch these techniques are suspicious, and not stealthy.2. Injecting during process initialization – these methods cause processes that are beginning torun, to load their code (e.g. AppInit DLLs). Typically these techniques require UAC elevation (dueto writing to privileged registry keys and/or privileged folders). Additionally, such methods aretypically mitigated by the Extension Point Disable Policy.3. Injecting into running processes (“true process injection”) – these are the most interestingtechniques, which are the focus of this paper.Injecting into running processes typically involves two sub-techniques: preparing memory in the targetprocess (which contains the payload – the logic to be run, either as native code, or as ROP chain stack),and executing logic in the target process.The present time landscape: Windows 10 64-bit (x64), and new security featuresIn recent years, Windows 10 (and the x64 hardware platform) gained a lot of popularity. This change oflandscape has a great impact on process injection techniques:-x64 (vs. x86): In Windows x86, all calling conventions except fastcall place all arguments on thestack. In x64, the calling convention places the first 4 arguments in registers (RCX, RDX, R8 andR9), and the remaining arguments on stack. This makes it harder to design a payload for x64,since such payload must control several registers in order to invoke a function. In x86, a payload

-just needs to correctly arrange the stack in order for a function invocation to succeed.Theoretically this could have been elegantly handled by the single byte instruction POPA(opcode 0x61), which pops all data registers from stack, however this instruction is simply notavailable in x64 mode.New security features: Windows 10 introduced several new process exploitation mitigationfeatures, which can be controlled via the SetProcessMitigationPolicy API (from the targetprocess). These are:o CFG (Control Flow Guard): this is Microsoft’s implementation of the CFI (Control FlowIntegrity) concept for Windows (8.1, 10). The compiler precedes each indirect CALL/JMP(CALL/JMP reg) with a call to guard check icall to check the validity of the call target.Validity is also provided by the compiler as a list of 16-byte aligned valid targets permodule (loaded to memory as a “bitmap” for fast access). Both caller module and calleemodule must support CFG in order for it to be in effect.o Dynamic Code prevention: this feature prevents the calling process from callingVirtualAlloc with PAGE EXECUTE *, MapViewOfFile with FILE MAP EXECUTE option,VirtualProtect with PAGE EXECUTE * etc. and reconfiguring the CFG bitmap viaSetProcessValidCallTargets (fromhttps://www.troopers.de/media/filer he joy of sandbox mitigations export.pdf). Note that for e.g.VirtualProtectEx, the policy enforced is the policy of the caller process.o Binary Signature Policy (CIG – Code Integrity Guard): only allow modules signed byMicrosoft/Microsoft Store/WHQL to be loaded into the process memory. A weakercontrol is Image Load Policy, which can prevent loading modules from remote locationsor files with low integrity label; This is enforced at the calling process.o Extension Point Disable Policy: disable “extensions” that load DLLs into the processspace – AppInilt DLLs, Winsock LSP, Global Windows Hooks, IMEs ons.txt).It should be noted that explorer.exe, the classic injection target, as well as several other nativeWindows processes/applications (e.g. Edge’s broker processes) are protected with CFG, and theEdge broker processes are protected almost to the maximum possible level with the abovetechniques.Defining our scopePer the above, our interest is in true process injection techniques for Windows 10 x64. Specifically: Windows 10 x64 at recent build (1803/1809/1903)All processes (injector/malware, target) are 64-bitAll processes are medium integrityTarget process is already running (i.e. “true process injection” is needed)No privilege elevation required (this rules out AppInit Dlls, AppCertDlls and shims, as the formertwo require writing to privileged registry keys - HKLM\Software\Microsoft and HKLM\Systemrespectively, and the latter one requires UAC to run sbdinst.exe). Same for AddPrintProcessor,

AddPrinterDriver and AddMonitor, all of which require the DLL to reside underC:\Windows\System32.Evaluation is done against fully protected process (CFG, CIG, etc.) or vanilla process (whereapplicable)Evaluating Process injection techniquesBypassing Windows protection mechanismsMicrosoft provides a standard API (SetProcessValidCallTargets) for “whitelisting” (from CFG perspective)an arbitrary address in the target process. Tal Liberman from EnSilo described its internalimplementation as a call to ntdll!NtSetInformationVirtualMemory with VmInformationClass VmCfgCallTargetInformation ted-adding-cfgexceptions).HANDLE p OpenProcess(PROCESS QUERY INFORMATION PROCESS VM OPERATION, FALSE,process id);MEMORY BASIC INFORMATION meminfo;VirtualQueryEx(p, target, &meminfo, sizeof(meminfo));CFG CALL TARGET INFO cfg;cfg.Offset ((DWORD64)target) - (DWORD64)meminfo.AllocationBase;cfg.Flags CFG CALL TARGET VALID;SetProcessValidCallTargets(p, meminfo.AllocationBase, meminfo.RegionSize, 1,&cfg);We found a simple way to deactivate all other Windows protections (specifically CFG cannot bedeactivated in this manner) for Windows 10 version 1803. Microsoft provides a standard API(SetProcessMitigationPolicy) for turning on/off these features in the process itself. This function needsto be run from the target process and provided with 3 arguments – for example,ProcessDynamicCodePolicy, a pointer to an array ofsizeof(PROCESS MITIGATION DYNAMIC CODE POLICY) zeros, and the size of the said array – which issizeof(PROCESS MITIGATION DYNAMIC CODE POLICY). Finding an array of zeros is trivial, e.g. the loadimage address of ntdll.dll 0x20. Running a target function with 3 arguments is possible via invokingntdll!NtQueueApcThread.HANDLE th OpenThread(THREAD SET CONTEXT, FALSE, thread id);ntdll!NtQueueApcThread(th, dePolicy, ((char*)GetModuleHandleA("ntdll")) 0x20,sizeof(PROCESS MITIGATION DYNAMIC CODE POLICY));NOTE: this technique stopped working at Windows 10 version 1809 – once protection is set (bySetProcessMitigationPolicy), it cannot be unset – SetProcessMitigationPolicy returns statusERROR ACCESS DENIED.

Given that CFG can be turned off by the injecting process, why do we need to analyze for CFG? Weanticipate that the mere action of disabling (or attempt to) of a security feature by a process may bemonitored and possibly even prevented by security products. As such, in the future, injecting processesmay prefer to stay away from this exact functionality. Also, at some point in the future, Microsoft maydisable or restrict CFG manipulation (just like they did with SetProcessMitigationPolicy).Steps in true process injectionTypically, process injection follows these 3 steps: Memory allocationMemory writing (using a memory write primitive)ExecutionSometimes the allocation and memory writing are technically carried out in the same step, using thesame API. Sometimes the memory allocation step is implicit, i.e. the memory is pre-allocated.Sometimes it is impossible to separate memory writing from execution.Oftentimes, memory allocation and writing is done multiple times before the execution step.Evaluation CriteriaWe evaluate memory write primitives based on: d vs. uncontrolled write addressStabilityWe evaluated execution methods based on: PrerequisitesLimitationsCFG/CIG-readinessControl over registersCleanup requiredA note about memory allocationIn general, memory writing primitives require the target memory to be allocated. This can happen intwo ways:

1. The injector process can invoke VirtualAllocEx (or NtAllocateVirtualMemory) to allocate newmemory in the target process. In such a case, the injector can request this memory to bereadable and/or writable and/or executable. Note that “the default behavior for executablepages allocated is to be marked valid call targets for CFG” emory/memory-protection-constants).2. The injector process can designate an existing (allocated) memory within the target process, foroverwriting. There are several options:a. Stack – either the stack in use, or area beyond the TOS. The stack is RW. Writing to thestack requires addressing several considerations: (i) when writing beyond TOS, it shouldbe kept in mind that this area may be overwritten by subsequent calls to inner functionsor system functions; (ii) when writing before TOS, it should be kept in mind that thisoverwrites existing stack usedb. Image – the data sections of some DLLs contain “spare” allocation beyond the actualneed of the static variables mapped to there. This “cave” is RW, and initialized withzeros.c. Heap – any data object allocated on the heap, whose address is known to the injectorprocess, can be theoretically used (though the memory area may be modified/recycledas the object is manipulated or destroyed). Again – RW.VirtualProtectEx can be used to assign different privileges (e.g. execution) to a memory region.Note that “the default behavior for VirtualProtect [and VirtualProtectEx] protection change toexecutable is to mark all locations as valid call targets for sktop/Memory/memory-protection-constants).A notable exception is ntdll!NtMapViewOfSection which can be invoked in such way that it allocatesmemory for the section in the target process.A survey and Analysis of injection techniquesNotation: Standard Microsoft Visual Studio coloring schemeBold italics – user parameters. Specifically:o payload – an array of bytes in the injecting process, with the data to copy to the targetprocesso sizeof(payload) – the size (in bytes) of the payload arrayo target payload – the address, in the injected process, into which the payload is injectedo target execution – the address, in the injected process, into which control should betransferred (can be target payload if it is executable, or a ROP gadget e.g. stack pivot,pointing RSP to target payload)o process id / thread id – the target process ID / thread ID to inject toBold (ntdll!NtXXX or ntdll!ZwXXX) – dynamically linked functions (NtXXX/ZwXXX), a shorthandfor fptr tion name); (*fptr)(arguments);Yellow background – cleanup code

The techniques (in chronological order, where known):1. Classic WriteProcessMemory write primitive (prehistoric)a. Make sure the target address is allocated (e.g. with VirtualAllocEx)b. Write data or raw code to memory using WriteProcessMemoryCode:HANDLE h OpenProcess(PROCESS VM WRITE PROCESS VM OPERATION, FALSE,process id);LPVOID target payload VirtualAllocEx(h,NULL,sizeof(payload), MEM COMMIT MEM RESERVE, PAGE EXECUTE READWRITE); // Or any other memory allocation techniqueWriteProcessMemory(h, target payload, payload, sizeof(payload), NULL);Evaluation:oooooPrerequisites: noneLimitations: noneCFG/CIG-readiness: not affectedControlled vs. uncontrolled write address: address is fully controlledStability: stable2. Classic DLL injection execution method (prehistoric)a. Write a malicious 64-bit DLL to disk, DllMain should contain a bootstrap payload (notshown).b. Write memory (DLL path string) using any write primitive, e.g.VirtualAllocEx( ,PAGE READWRITE) WriteProcessMemory (not shown)c. Load (and execute) the DLL usingCreateRemoteThread( ,LoadLibraryA,DLL path string) (internal functionsNtCreateThreadEx and RtlCreateUserThread can also be used)Variant 1: use QueueUserAPC instead of CreateRemoteThread (thread must be in alertablestate)Variant 2: Instead of writing the DLL path to the target process memory, find a NUL-terminatedstring that looks like a valid path in one of the system DLLs, write the DLL to a file in that name,and point the LoadLibrary argument to the string. For example, ntdll.dll contains the NULterminated string “\ntdll\ldrsnap.c”, thus placing the DLL in file C:\ntdll\ldrsnap.c (assumingstandard installation of Windows to drive C:) should do the trick.Code:HANDLE h OpenProcess(PROCESS CREATE THREAD, FALSE, process id);CreateRemoteThread(h, NULL, 0, (LPTHREAD START ROUTINE)LoadLibraryA,target DLL path, 0, NULL);Evaluation:

oooPrerequisites: malicious DLL written to disk, memory write primitive, thread in alertablestate (only when using APC)Limitations: DllMain code runs in with loader-lock locked, hence some restrictions IG-readiness: CIG prevents loading on non-Microsoft signed DLL. An attempt to doso results in error 0xC0000428 (STATUS INVALID IMAGE HASH – “The hash for image%hs cannot be found in the system catalogs. The image is likely corrupt or the victim oftampering.” - aspx)ooControl over registers: none (but typically not a problem due to linking)Cleanup required: none3. CreateRemoteThread execution method (prehistoric)a. Write raw code to memory using any write primitive.b. Execute the code using CreateRemoteThread (requires CFG-valid target)Code:HANDLE h OpenProcess(PROCESS CREATE THREAD, FALSE, process id);CreateRemoteThread(h, NULL, 0, (LPTHREAD START ROUTINE) target execution, RCX, 0,NULL);Evaluation:oooooPrerequisites: target address must be RX at minimum.Limitations: noneCFG/CIG-readiness: target entry point must be CFG-valid.Control over registers: RCXCleanup required: none4. APC execution method (prehistoric)Thread must be in alertable state ileio/alertable-i-o), i.e. in one of 5 functions: SleepEx,WaitForSingleObjectEx, WaitForMultipleObjectsEx, SignalObjectAndWait,MsgWaitForMultipleObjectsEx (probably RealMsgWaitForMultipleObjectsEx).a. Write raw code to memory using any write primitive.b. Execute the code using QueueUserAPC/NtQueueApcThread (requires CFG-allowedtarget)Code:HANDLE h OpenThread(THREAD SET CONTEXT, FALSE, thread id);QueueUserAPC((LPTHREAD START ROUTINE)target execution, h, RCX);or

ntdll!NtQueueApcThread(h, (LPTHREAD START ROUTINE)target execution, RCX, RDX, R8);Evaluation:oooooPrerequisites: Target address must be RX (at least). Thread must be in alertable stateLimitations: noneCFG/CIG-readiness: target entry point must be CFG-valid.Control over registers: RCX (also RDX and R8 if using NtQueueApcThread)Cleanup required: none.5. Thread Execution Hijacking “Suspend-Inject-Resume” execution method (prehistoric?)a. Write code/data to memory using any write primitive e.g.VirtualAllocEx( ,PAGE EXECUTE READWRITE) WriteProcessMemory (not shown).b. Execute the code using SetThreadContext (the thread needs to be suspended andresumed) – set RIP to point at the code written in step (a) or to a ROP gadget, andmaybe RSP to point at a new stack.Variant: use NtQueueApcThread(thread,SetThreadContext,-2 /* GetCurrentThread pseudohandle */,context,NULL) instead of SetThreadContext (thread must be in alertable state)Code for executable memory:HANDLE t OpenThread(THREAD SET CONTEXT, FALSE, thread id);SuspendThread(t);CONTEXT ctx;ctx.ContextFlags CONTEXT CONTROL;ctx.Rip (DWORD64)target execution;SetThreadContext(t, s: execution target must be RX (at least)Limitations: noneCFG/CIG-readiness: RSP (if set) must be within stack limits (this is enforced bySetThreadContext)Control over registers: see “SetThreadContext anomaly”Cleanup required: yes; the original thread needs to resume execution and for that, itsregisters and stack must be restored.The SetThreadContext anoma

Windows Process Injection in 2019 Amit Klein, Itzik Kotler Safebreach Labs Introduction Process injection in Windows appears to b