Local Thread Hijacking

Blog by afx_IDE | linkedin.com/in/oliver-ide | twitter.com/afx_IDE

An Introduction to Local Thread Hijacking

In short, local thread hijacking is a technique for payload execution, which involves suspending a local thread, modifying it, then resuming the thread to get it to execute the payload. 

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:

However, to understand how thread hijacking works, it is important to understand the concept of thread context.

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

The table below explains some of the pros and cons of hijacking an existing thread as opposed to creating a new thread to run a payload:

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

You should be aware that it isn’t possible to hijack the main thread of a process. The reason for this is that the target thread needs to be set to a suspended state. When suspending the main thread of a process the core logic of the application is halted, which could make it unresponsive.

1. Creating a target Thread
  • To create the target thread, we will use the CreateThread WINAPI and store a benign function inside that thread.

2. Modifying target Thread Context:
  • 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. 

In the remarks section of the MSDN article it states that the function “retrieves a selective context based on the value of the ContextFlags member of the context structure.”

In short, the ContextFlags member of the CONTEXT data structure needs to be set to a value before calling the function. To retrieve the values of the registers, ContextFlags is set to CONTEXT_CONTROL. The top comment of this StackOverflow question provides additional information too.

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?

The RIP (64 bit) and EIP (32 bit) are instruction pointers. They are both processor registers that store the memory address of the next instruction to be executed. A great analogy that I am going to steal from HuskyHacks – is that the instruction pointer is like a VCR machine.

The Basics of VCR technology. (Source: columbiaisa.50webs.com)

The video head in the above image is a bit like the instruction pointer in that it is always monitoring for what signal to show next. The instruction pointers are updated after each instruction is executed.

3. Resuming the suspended Thread to execute the payload:
  • 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!
Note: The method described above will only work when hijacking 64 bit threads (because of the use of Rip). To hijack 32 bit threads simply modify the Eip member instead of Rip.

Example code on GitHub has been provided (see below) to demonstrate how local thread hijacking works, however, it is a proof-of-concept and not designed to be evasive. To run the code, it is recommended to either create a Windows Defender exclusion or use a virtual machine (VM).


Please read the code included in the GitHub repo above. It code includes extensive comments written alongside this post that will help you understand. It will also help if you can interpret C code, understand hungarian notation and reference the MSDN for WINAPI functions you don’t recognise.

Thank you for reading! I hope you learned something.