The idea behind this technique is to utilize thread context-altering APIs and MOV
gadgets to achieve process injection, and full execution takeover.
WIN32 APIs GetThreadContext()
and SetThreadContext()
are used for this purpose.
The target thread must be in a RUNNING
state, and the possibility of other threads interfering with the target thread must be taken into consideration.
This PoC doesn't address either of those issues, however, both can be resolved quite easily.
The injector tries to find a loop gadget (jmp $
) in the target process as soon as possible, in order to prevent the thread from executing state altering functions, like Sleep()
.
The injector will also by default completely discard the target's stack and allocate a new one. It will also by default discard all acquired gadgets and create a "Hijack Control Gadget", which is basically just a package of all required gadgets, in one location.
See Gadgets and Execution Options for more information.
RemoteAPI.h
contains "remote" alternatives to some commonly used WINAPI functions, like VirtualAlloc()
or WriteProcessMemory()
.
While most of these are executed simply by crafting a context ready for execution of the target API function residing in the remote process, the rWriteProcessMemory()
is an exception, as it doesn't rely on it's equivalent API call at all. Instead, to achieve writing to a remote process, a MOV R/M8, R8
gadget is used as a foundation for off-brand cross-process memcpy()
clone.
Full list of currently implemented RemoteAPI functions:
rVirtualAlloc
- Equivalent
VirtualAlloc()
.
- Equivalent
rVirtualProtect
- Equivalent of
VirtualProtect()
.
- Equivalent of
rVirtualFree
- Equivalent of
VirtualFree()
.
- Equivalent of
rWriteProcessMemory
- Custom implementation of an cross-process
memcpy()
.
- Custom implementation of an cross-process
rLoadLibraryA
- Equivalent of
LoadLibraryA()
.
- Equivalent of
r_puts
- Equivalent of
puts()
.
- Equivalent of
rDirectInvoke
- Custom invoker that calls arbitrary functions by their addresses.
There are two MOV
gadgets used in this demo - MOV R/M8, R8
(MOV8
gadget) and MOV R/M64, R64
(MOV64
gadget). These are dynamically searched for in the target process, and can be different each time the PoC is executed, as the injector searches for different possible combinations of the ModR/M byte, so the source and destination registers can differ. The search is done using a WIN32 function ReadProcessMemory()
. In theory, there's a way around this, but since this is not GhostReading, this is simply out of scope for now
However, in Defs.h
, there is a functionality that will use hardcoded offsets to gadgets in NTDLL
. This eliminates the need of PROCESS_VM_OPERATION
and PROCESS_VM_READ
access rights.
There are a few options in Defs.h
that can be enabled to change some key factors on how the injector works:
KEEP_ORIGINAL_STACK
READ_PROC_ADDR_LOCAL
STEALTH
- This option, despite it's name only mentioning the stack, will prevent discarding and replacing the original stack with a newly crafted one, but will also disable the use of "Hijack Control Gadget", which replaces all of the 3 necessary gadgets that were previously acquired from the process:
MOV8
gadgetMOV64
gadget- Loop gadget
- Enabling this will result in function addresses being fetched locally, via
GetModuleHandle()
+GetProcAddress()
combo, this will also allow for dropping the handle to the target process immediately after hijacking the execution, as the RemoteAPI functions won't enumerate the target's loaded Core DLLs to resolve the function addresses from them. This should be left disabled if any weird hooks/relocations are present, which could cause differences between the Core DLL address mappings between the injector and it's target.
- This option automatically enables both
KEEP_ORIGINAL_STACK
andREAD_PROC_ADDR_LOCAL
, with addition of relying on hardcodedNTDLL
offsets to all required gadgets. This requires manually searching for all required gadgets insideNTDLL
, and updating the offsets to these gadgets within the injector. This is highly unstable and should be generally avoided.
Yes, the main purpose of this PoC is to demonstrate a write primitive that doesn't call WriteProcessMemory
, NtWriteVirtualMemory
, CreateRemoteThread
, or simply any APIs commonly used for WPM during process injection. This is utilized to pop a calc.
It depends on the actions performed by the target. Using the RemoteAPIs will generally not crash the program, but will leave it in an internally unusable state, executing infinite loop.
There is a slightly different implementation of this technique that I demonstrated a while ago, you can watch it on YouTube
This PoC doesn't reuse any code from the listed or any other sources. I simply wanted to write my own implementation of this technique, because I find it interesting.
I politely borrowed the idea, but all of the code was written from scratch.
http://blog.txipinet.com/2007/04/05/69-a-paradox-writing-to-another-process-without-openning-it-nor-actually-writing-to-it/
https://i.blackhat.com/USA-19/Thursday/us-19-Kotler-Process-Injection-Techniques-Gotta-Catch-Them-All-wp.pdf
- Windows 10 21H2, 22H2
- Windows 10 22H2
This project is licensed under the MIT License. The content of this repository exists purely for educational purposes.
Written with StackEdit.