Blog by afx_IDE | linkedin.com/in/oliver-ide | twitter.com/afx_IDE
An Introduction to Local Thread Hijacking
What is a Thread?
As defined by Microsoft in the MSDN:
“A thread is the basic unit to which an operating system allocates processor time.”
Think of a Windows process like a construction team. This construction team has an overall aligned goal of building a house. However, in the same way that each worker in the construction team has individual responsibilities (e.g., electricians, plumbers, roofers, and architects), threads in Windows often serve specific purposes inside of a process. Threads in Windows can communicate with each other inside of their process to more efficiently accomplish a larger task – this is referred to as multithreading.
A process with two threads of execution, running on one processor (Source: Wikimedia)
System resource monitoring tools like Process Hacker can be used to view threads on a system:
What is Thread Context?
Thread context details the state of a thread at that specific moment in time. When or if a thread is paused, the thread context contains all the information needed for the thread to seamlessly resume its execution.
Thread context includes, for example, the state of the thread’s set of CPU registers and its stack. The Windows API function GetThreadContext
can also be used to retrieve the context of a thread and populate a CONTEXT
structure.
The Advantages of Thread Hijacking
Hijacking an existing thread |
Creating a new thread |
||
Pros |
Cons |
Pros |
Cons |
It is stealthier, as it
hijacks the thread’s entry points of a pre-existing benign function |
It interferes with a thread’s
intended function |
It avoids
interference with an existing thread’s execution |
It exposes the base
address of the payload |
It minimizes the use
of system resources |
There is low
cross-compatibility |
The new thread will
have predictable behaviour |
It has a higher use
of system resources |
If you were to create a new thread using the CreateThread
WINAPI, you would have to pass the base address of your payload to the third
parameter, lpStartAddress.
HANDLE CreateThread(
[in,
optional] LPSECURITY_ATTRIBUTES lpThreadAttributes,
[in]
SIZE_T dwStackSize,
[in] LPTHREAD_START_ROUTINE lpStartAddress,
// A pointer to the application-defined function to be executed by the thread. This pointer represents the starting address of the thread.
[in,
optional] __drv_aliasesMem LPVOID lpParameter,
[in] DWORD
dwCreationFlags, [out, optional] LPDWORD lpThreadId
);
When hijacking an existing thread, its entry point will be a normal function – preventing the payload’s address from being seen.
In the example code, we will be creating a sacrificial thread and hijacking it. Please note this is different than creating a new thread solely for the purpose of executing a payload.
Hijacking a Local Thread
To create the target thread, we will use the CreateThread WINAPI and store a benign function inside that thread.
To retrieve the CONTEXT data structure of the thread, the GetThreadContext WINAPI function will be used. Notice that the second parameter, lpContext, is an IN and OUT parameter.
SetThreadContext
will then be used to modify a value in the CONTEXT struct. Either the RIP (for
64-bit processors) or EIP (for 32-bit processors) member of the struct will be
changed to point to the address of the payload.
What is RIP/EIP?
- As mentioned, Threads must be in a suspended state for you to retrieve or modify their context. After modifying the Rip member of the CONTEXT structure to point to our payload, we can resume the execution of our sacrificial thread and our payload will be executed!