Skip to content

PoC implementation of the GhostWriting injection technique for x64 Windows

License

Notifications You must be signed in to change notification settings

x0reaxeax/GhostWriting64

Repository files navigation

GhostWriting-x64

PoC implementation of the GhostWriting injection technique

What does this do?

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

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().
  • rVirtualProtect
    • Equivalent of VirtualProtect().
  • rVirtualFree
    • Equivalent of VirtualFree().
  • rWriteProcessMemory
    • Custom implementation of an cross-process memcpy().
  • rLoadLibraryA
    • Equivalent of LoadLibraryA().
  • r_puts
    • Equivalent of puts().
  • rDirectInvoke
    • Custom invoker that calls arbitrary functions by their addresses.

Gadgets

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.

Execution Options

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

KEEP_ORIGINAL_STACK:

  • 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 gadget
    • MOV64 gadget
    • Loop gadget

READ_PROC_ADDR_LOCAL:

  • 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.

STEALTH:

  • This option automatically enables both KEEP_ORIGINAL_STACK and READ_PROC_ADDR_LOCAL, with addition of relying on hardcoded NTDLL offsets to all required gadgets. This requires manually searching for all required gadgets inside NTDLL, and updating the offsets to these gadgets within the injector. This is highly unstable and should be generally avoided.

FAQ

Can you please speak English?

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.

Will this crash the target program?

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.

Can I see how it works without running it?

There is a slightly different implementation of this technique that I demonstrated a while ago, you can watch it on YouTube

Sources

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

Tested on

  • Windows 10 21H2, 22H2
  • Windows 10 22H2

DISCLAIMER

This project is licensed under the MIT License. The content of this repository exists purely for educational purposes.

Written with StackEdit.