Thread Hijacking using _KTRAP_FRAME

Old Tricks New tools

Whilst looking for further POC’s to perform using all the information and techniques we already gathered, one of the main things we looked for is a strong method which by we will inject the EDR with malicious code in order to have the EDR run malicious activity.

This type of attack is perceived by us as nothing but beautiful. Taking a product built with endless manhours and budget, and causing it to do the exact things it is meant to prevent felt almost poetic to us.

It is needless to say, that by toggling PPL off, removing kernel callbacks and turning off ETW, most EDRS are already prone to injection.

As:

1. No PPL = No memory protection

2.No Callbacks = no monitoring on injection API

3.No ETW = no logging on injection API from kernel

Just for the sake of our tool and POCs, we have implemented two types of injections, Remote thread and Thread Hijacking.

For both of those we have used the same technique in order to inject the shellcode itself, and they only differ in execution.

For our injections we have used Shared Section injections, an old and familiar technique which you may google but long story short:

1.Create a new section in memory with READ WRITE and EXECUTE permissions

2.Map the section from the injecting process

3.Open handle to the target process and elevate it

4.Map the same section for the target process

5.Copy the shellcode memory to the section mapped in the injecting process

6.Close the mapping for the section

7.Your section address now contains the shellcode

Another nice to have we have implemented in our injections, is Heap Allocation:

1.Open a handle to the shellcode file

2.Calculate the size of the shellcode using the handle

3.Allocate memory in the Heap of the injecting process in the size of the shellcode

4.Read the file from the disk to the newly allocated heap space.

5.Your shellcode now resides in the heap without directly injecting it or reading it to a variable

6.You may copy from the heap to the section as explained.

The only thing that prevented us from performing these steps so far was Low privileged handles, PPL, ETW and CALLBACKS, now that these are taken care of, this will work flawlessly for any protected process including EDRs

For our first injection, we have chosen to keep it classic and execute the injected shellcode using NtCreateThreadEx

Although this did not feel as enough, we felt like there is more nice tricks to perform in the kernel and by using all the information, abilities and upper hand we already got.

In order to better understand the next section, we would need to understand the flow of remote thread hijacking first:

1.A handle is opened to the target process

2.Memory is allocated for the shellcode

3.The shellcode is injected

4.A snapshot of the process is created, and a target thread is selected

5.The thread is suspended

6.While suspended, the context of the thread is copied, which provides the register information of the thread

7.The instruction pointer is changed to point to the beginning of the injected shellcode

8.The thread is resumed.

This type of attack intrigued us in our research’s context, as it included manipulating a thread and executing shellcode without an API call that performs any execution and essentially change the instructions of the thread by a single pointer.

As mentioned before, we have already gained the ability to inject a protected process, and as such all that we wanted to focus on, was the thread hijacking itself.

We boiled it down to this:

1.Choose a thread

2.Suspend it

3.Change its RIP via kernel

4.Continue

This has thought has resulted in the following path:

1.Get the EPROCESS for the target process

2.Find the ThreadListHead member which points to the beginning of the _ETHREAD list of the process

3.Iterate _ETHREAD list until the relevant thread id is found

4.The first member of _ETHREAD is the _KTHREAD member, jump to its tcb

5.In the tcb there is a member called waitPrcb of type _PKPRCB

6.In that struct there is a member of type _KPROCESSOR_STATE, jump to it

7.Find RIP and modify it

8.Done

As beautiful as this sounds, we have initially found no luck with this. the processor state has been there but always empty. This was actually our mistake as we found out that this flow would only be good for a thread that is in Kernel state.

Each Thread and Process may run in two different modes: Kernel Mode (KTHREAD, KPROCESS) and Executive mode (ETHREAD, EPROCESS) and as such, as long as the thread is not in kernel mode, the stack frame information will not be included in the kernel structure just like that.

Needless to say, learning about that, was even more motivating to get this to work.

The next step we took to understand if there is another good candidate for thread hijacking, was to keep looking for another struct that includes the instruction pointer, which will not be empty when we read or write to it, and without forcing the thread into kernel mode, since that would most likely kill the program or thread.

Another member of the KTHRED structure, is _KTRAP_FRAME. As the name suggests, it consists of all the frame members just like context or the _KPROCESSOR_STATE structure.

This is actually a great candidate for us, although when we suspended the thread and jumped to the correct place both programmatically, or in WinDbg, we have found that it is empty as well!

Although further investigation concluded that the KTRAP_FRAME will fill with the relevant values in two states:

1.Thread is going through a context switch from Executive to Kernel – e.g running a syscall

2.The thread is being Debugged!!

This finding has been perfect for us, since we could simply swap the thread suspension, with process debugging!

PPL is off, so debugging is allowed, also our handle is FULL_CONTROL, so we have the correct permissions!

This has resulted in the following flow:

1.Inject the process with a shellcode (as shown above)

2.Choose a thread to hijack

3.Create a Debug object and place the process in debug mode

4.Find the relevant KTHREAD and locate its trap frame

5.Write the shellcode address into the instruction pointer member

6.Take the process out of debug mode

This code will have the exact same results of Remote Thread Hijacking, without suspending the thread, and without setting the context in user mode, using no API to change RIP.

Last updated