Process injection is a family of malware development techniques allowing an attacker to execute a malicious payload into legitimate addressable memory space of a legitimate process.
These techniques are interesting because the malicious payload is executed by a legitimate process that could be less inspected by a security product such as an EDR.
However, in order to perform this injection, the attacker needs to use specific functions for memory allocation, and use execution primitives to write and execute his payload in the remote process. In standard process injection patterns, these functions are usually the following Win32API:
VirtuallAllocEx
, WriteProcessMemory
and CreateRemoteThread
.Figure 1: Standard process Injection pattern
Security products can use this the mandatory use of this type of functions to detect and fight against process injection by monitoring these API calls. Therefore, in order to keep this type of technique viable, attackers must find other ways to allocate, write and execute memory in a remote process.
This post aims to show an alternate technique allowing execution at an arbitrary memory address on a remote process that can be used to replace the standard
CreateRemoteThread
call.Nirvana Debugger
Definition
In 2015, Alex Ionescu made a presentation about Esoteric Debugging Techniques.
One of the topics tackled is the Nirvana debugging technique. This method allows a process to install a specific hook that will be called right after every syscall it performs.
When a process is performing a syscall, it forwards the execution flow to the kernel. Then, once the kernel returns from the kernel procedure associated to the syscall, it usually forwards back the execution flow to the calling process as shown in the following figure:
Figure 2: Standard process/kernel interaction
With the Nirvana debugging technique, it is possible to register a specific function (executed in userland) that will be called right before the process gets back the execution flow control from the kernel: the kernel will forward the execution flow to this hook instead of the initial process as it is shown in the following figure:
Figure 3: Execution flow is redirected
In this hook, all the information needed during a debugging session is available, including which syscall has been executed, the address from which the syscall was called and the syscall’s return code. This technique was first discussed in 2020 in the article Weaponizing Mapping Injection with Instrumentation Callback for stealthier process injection by @splinter_code.
Implementation
The WIN32API exposes the
NtSetProcessInformation
function that can be used to register a Nirvana callback:Figure 4: Basic Nirvana hook definition
The
NtSetInformationProcess
function takes the process handle (hProc
) as a parameter, which should make it possible to add a hook on a remote process.On a remote process
The
NtSetInformationProcess
prototype shows that it can be used to alter a remote process’s configuration.However, looking at the function code in
ntoskrnl.exe
shows it is only possible to use the function on a remote process when the SE_DEBUG
privilege is enabled:Figure 5: Need to activate SE_DEBUG
The
SE_DEBUG
privilege can be requested by principals allowed in the “Debug programs” user right assignment. Additionally, the SeDebug privilege cannot be requested by processes with an integrity level lower than “high”. On most systems, these requirements translate to the need of running the malicious process with an account member of the local “administrators” group, in elevated mode.Process Injection With NtSetInformationProcess
As established in the previous sections, the
NtSetInformationProcess
WIN32API can be used to register a hook on a remote process. So, it can be used to redirect a remote process execution flow. However, the hook must be located inside the remote process memory space.Nirvana hook wrapper
The final goal is to inject a shellcode in the remote process that will be triggered as a Nirvana hook and will call a CobaltStrike beacon.
The process can be split in two steps:
- First the CobaltStrike beacon is written at the given address
${CSAddr}
in the remote process memory space. - Then the Nirvana Hook, that will perform a
CALL ${CSAddr}
, is written at another address${NirvanaAddr}
in the remote process memory space.
A small kernel debugging on a process with a Nirvana hook installed shows that:
- The kernel only performs a
JMP
on the hook address letting him redirect the execution flow to the calling NT function.
This part is an interesting lesson on Windows internals. As the kernel will be performing aJMP/CALL
on a userland function on behalf of the user mode to run the Nirvana hook, it could be a way to bypass the Windows Control Flow Guard, because this check is usually performed on userland with theLdrpValidateUserCallTarget
function.
Here, the kernel had to reimplement this function under the nameMmValidateUserCallTarget
to ensure the callback address is in the allowed function range:
Figure 6: Control Flow Guard at kernel level
- The calling function address is stored in the
R10
registry. - The syscall’s return address is stored in the
R11
registry.
So, the hook must jump on
R10
once the CobaltStrike beacon has been executed to forward back the execution flow to the calling NT function. A basic ASM code can be used:push rbp
mov rbp, rsp
push rax
push rbx
push rcx
push r9
push rl0
push rll
movabs rax, ${CSAddr}
call rax
pop r11
pop r10
pop r9
pop rcx
pop rbx
pop rax
pop rbp
jmp r10
This shellcode seems ok, but in fact it will create an infinite loop as it will be called everytime a syscall is performed. So, it can be modified in order to be executed only once.
For example, it could be possible to make the code self-modifying to change to replace the
PUSH RBP
by a JMP R10
in order to break the loop:push rbp
mov rbp, rsp
; This will modify the instruction push RBP into JMPR10
mov qword ptr[rip – 15] 0xE2FF41
push rax
push rbx
push rcx
push r9
push rl0
push rll
movabs rax, ${CSAddr}
call rax
pop r11
pop r10
pop r9
pop rcx
pop rbx
pop rax
pop rbp
jmp r10
So, when the hook has been executed once, it will just jump on
R10
without re-executing the beacon.Wrapping it all together
Now the different shellcodes are written, it is possible to perform the injection:
- Open the
notepad.exe
process with your process opening primitive - Allocate a RX buffer in the
notepad.exe
process for the Cobaltstrike beacon - Modify the Nirvana shellcode in order to call the Cobaltstrike beacon address in the remote process
- Allocate an RWX buffer in the
notepad.exe
process for the Nirvana Hook - Write both the shellcode and the Cobaltstrike beacon in their respective buffer
- Add a new Nirvana Hook using the
NtSetInformationProcess
- Wait for the notepad to perform a syscall
The whole code is available on this Github repository: https://github.com/OtterHacker/SetProcessInjection.
Drawbacks
The most important drawback is the fact that
SE_DEBUG
privilege is mandatory for the injection. Therefore, this injection method can only be used during post-exploitation and not during initial access.The other problem that could be fixed, giving some time to it, is that the Nirvana shellcode must be allocated as RWX in a remote buffer as it is a self-rewriting shellcode.
This can be solved by having the shellcode doing a call to
VirtualProtect
by itself or finding another way to break the infinite hook loop (by re-calling NtSetInformationProcess
directly from the shellcode to remove the callback).EDR inspection
The malware has been tested against Microsoft Defender For Endpoint, SentinelOne, TrendMicro and Sophos. None of them raised any alerts regarding the execution primitive.
However, it is not because no alerts are raised that no detection has occurred. For example, if we look at the
ntdll!SetInformationProcess
on a process monitored by SentinelOne, it is possible to see the following userland hook:Figure 7: SentinelOne userland hook
Following the different
JMP
shows that the hook is located at 0x7ffd0160ab00
. Looking at the process loaded DLL, it is possible to retrieve the SentinelOne DLL’s base address:Figure 7: SentinelOne DLL address
So, the hook’s code is stored in the
InProcessClient64.dll
at the 0x7ab00
offset.Disassembling the related function in IDA shows the following function:
Figure 8: SetInformationProcess hook code
We see that the hook is copying the initial parameter in the
SetInfoArgs
structure, pack it in the SentinelHookParams
structure and call the ExecuteHook
function. This function is a succession of different calls leading to the following code:Figure 9: SentinelOne test performed on the hook
This function shows that SentinelOne is performing tests on this hook and it is specifically related to the
ProcessInfomationClass
used for the Nirvana Hook registering.It is possible to look at the different checks that are performed to understand the detection logic set up, but it is not the purpose of this post. However, some obvious checks can be easily observed. The following code shows that the
TTDINJECT.EXE
and TTD.EXE
executables (related to Windows Time Travel Debugging) seem to be whitelisted:Figure 10: TTDINJECT whitelisting
Likewise, it is possible to see additional tests performed when the SentinelOne’s
ProtectDeepHooking
feature is activated:Figure 11: Additional tests performed
The point here is that some EDR are still performing some detection through userland hook to detect the use of this API. However, as every userland detection mechanism, it is possible to bypass it using standard unhooking techniques and no kernel callback have been found to detect and prevent the use of this API.
Conclusion
This conclusion is exactly the same as the one from my LeHack 2023 talk: instead of spending months trying to find a way to bypass EDR and starting from scratch, it can be interesting to just looking up and see if some built-in behavior could not be easily hijacked to serve our purpose.
Security products cannot monitor all WIN32API and while behavioral analysis is kicking in, it is still hard for them to determine if a behavior is legitimate or malicious when using non-standard patterns.
So, be creative, Microsoft has created hundreds of functions, you will surely find one that will satisfy your needs!
It seems that I am not the only one thinking like this, as a Defcon31 talk about token duplication presented by Ron BEN YIZHAK also hijacks a non-standard WIN32API to bypass standard detection by avoiding the classic WIN32API direct call.
Yoann DEQUEKER