Overriding Thread Context

While goofing around with Win32 threads, I bumped into a function called SetThreadContext. The documentation is light. The function is described with one sentence.

Sets the context for the specified thread.

My first thought was “There is no way you can override a thread’s context. That’s just crazy.”.

I was wrong. This function is a hell lot of fun.

The CONTEXT structure

The most interesting part of SetThreadContext is the input argument. It takes a CONTEXT object that is briefly mentioned in MSDN.

The structure, according to the documentation, is tied to specific processor architecture. The actual structure can only be found through WinNT.h.

So digging into WinNT.h, I found the definition of this really cool structure.

typedef struct _CONTEXT {

// ... many things here

    DWORD   Edi;
    DWORD   Esi;
    DWORD   Ebx;
    DWORD   Edx;
    DWORD   Ecx;
    DWORD   Eax;
    DWORD   Ebp;
    DWORD   Eip;

// ... many things here

} CONTEXT;

The CONTEXT data structure contains all the main x86 registers plus other debug registers for a thread. These registers can be changed with a simple function call.

Fun Hacking

Putting my black hat on, what if I …

  1. Spin up a thread, and let it run for a bit.
  2. Hijack its thread context and force it to do something malicious.
  3. When that malicious task is completed, revert its thread context to its original state.
  4. The thread seamlessly recovers its original task, and the hijacker leaves absolutely no trace.

Below is such a program (minus the malicious part). A thread is spun up to calculate pi. Somewhere along the way, its instruction pointer is hijacked to calculate e. When the e is calculated, the thread is restored to calculate pi.

#include <windows.h>
#include <iostream>

// A simple E approximator found online.
double calculateE(int n)
{
    double value = 0;
    double factorial = 1;
    for(int i = 0; i <= n; i++)
    {
        for(int j = 1; j <= i; j++)
        {
            factorial *= j;
        }
        value += 1 / factorial;
        factorial = 1;
    }
    return value;
}

// A simple Pi approximator found online.
double calculatePi()
{
	std::cout << "Calculating Pi" << std::endl;
	double retPi = 0;
	for (LONGLONG denom = 1; denom <= 300000000; denom += 2)
	{
		if ((denom - 1) % 4)
			retPi -= (4.0 / denom);
		else
			retPi += (4.0 / denom);

	}
	return retPi;
}

CONTEXT originalContext; // thread's original context
HANDLE threadHandle;     // handle of the victim thread
volatile int intrusionDone; // global flag to indicate intrusion is completed

void Intrusion()
{
	__asm
	{
		push ebp
		mov ebp,esp
	}
	std::cout << "Running intrusion" << std::endl;
	std::cout << "E is " << calculateE(105) << std::endl;
	std::cout << "Completed intrusion" << std::endl;
	intrusionDone = 1;
	Sleep(1000);

	__asm
	{
		pop ebp
		mov ebp,esp
	}
}

DWORD WINAPI ThreadProc( LPVOID lpParam )
{
	double pi = calculatePi();
	std::cout << "Pi is " << pi << std::endl;
	return 0;
}

int main()
{
	intrusionDone = 0;
	threadHandle = CreateThread(
		NULL,
		0,
		ThreadProc,
		NULL,
		CREATE_SUSPENDED,
		NULL);

	// Let the thread run a little bit, then suspend it.
	ResumeThread(threadHandle);
	Sleep(100);
	SuspendThread(threadHandle);

	// Save the thread's context object
	originalContext.ContextFlags = CONTEXT_ALL;
	GetThreadContext(threadHandle, &originalContext);

	// Get the thread's context object again, but overwrite it this time.
	CONTEXT c;
	ZeroMemory(&c, sizeof(c));
	c.ContextFlags = CONTEXT_ALL;
	GetThreadContext(threadHandle, &c);

	// overwrite the instruction pointer to call Instruction directly.
	c.Eip = reinterpret_cast<DWORD>(Intrusion);

	// Update the thread context, and let it run again
	SetThreadContext(threadHandle, &c);
	ResumeThread(threadHandle);

	// Let it run for a bit and then suspend the thread.
	while(0 == intrusionDone) { Sleep(1); }
	SuspendThread(threadHandle);

	// Now revert the thread to its original context and let it finish its
	// job
	SetThreadContext(threadHandle, &originalContext);
	ResumeThread(threadHandle);

	WaitForSingleObject(threadHandle, INFINITE);

	return 0;
}
Output of the program

	Calculating Pi
	Running intrusion
	E is 2.71828
	Completed intrusion
	Pi is 3.14159

Thoughts

After some internet searches, apparently this technique is part of the method for Dll Injection.

Tools: Visual Studio 2008, Window 7

3 thoughts on “Overriding Thread Context

Leave a comment