简介
背景知识
ASLR (地址空间布局随机化): 是一种安全机制,用来防止内存攻击。它通过随机化程序在内存中的地址来防止攻击者预测代码的位置。DLL(如 kernel32.dll 和 ntdll.dll)的加载基址可能不同于不同的进程启动时。
导入表 (Import Address Table, IAT): 每个进程的IAT用于将导入的函数名称解析为实际的函数地址。这个表在进程加载时由操作系统填充。当一个进程调用一个导入的函数(例如 CloseHandle),它实际上通过IAT间接调用该函数的真实地址。
在之前的章节中,笔者曾介绍过有关于远程线程注入的知识,将后门.dll文件注入explorer.exe中实现绕过防火墙反弹后门。但一个.exe文件总要在注入时捎上一个.dll文件着实是怪麻烦的,那么有没有什么方法能够不适用.dll文件实现注入呢?
答案是有的,我们可以直接将功能写在线程函数中,然后直接将整个函数注入,这个方法相较之于DLL注入会稍微复杂一些,适用于对一些体积比较小的程序进行注入,实际上这种方式属于注入动态shellcode。但是要注意动态链接库的地址重定位问题,因为在不同进程中只有kernel32.dll、ntdll、kernelbase的基地址是相同的,而其他DLL由于ASLR,会导致这三个dll外的函数已经被随机化或者指向了错误的内存区域,这就导致注入进程的崩溃。所以最好要在远程线程函数中手动利用LoadLibrary和GetProcessAddress函数强制加载一遍DLL文件。
第二个问题是下面代码为啥要传入CloseHandle的指针,然后在远程线程函数中使用函数指针而不是直接调用对应函数呢?虽然Kernel32.dll在不同进程的基地址一样,在Kernel32.dll中相同的函数名的函数在不同进程的地址也应该一样,但是不同进程的导入表地址不一样 ,我们在代码中直接调用一个系统函数,实际上是从导入表中去调用的,导入表在开启ASLR进程下就会发生变化,如何直接调用CloseHandle,是将你原进程里面导入表地址用到了远程进程里,自然就导致注入进程崩溃了。
这就是一个典型的导入表调用汇编:
具体解释:
1.导入表问题: CloseHandle 函数在源进程的导入表中已经被解析为某个地址。但在远程进程中,由于每个进程的导入表是不同的,CloseHandle 的地址可能并不指向目标进程中的正确位置。目标进程中的 CloseHandle 地址也许已经被随机化或者指向了错误的内存区域,这就导致了崩溃。
2.ASLR的影响: 虽然 kernel32.dll 在所有进程的基地址是一样的,但每个进程的导入表(IAT)的位置可能不同,并且导入表本身可能由于 ASLR 而被随机化。直接调用源进程中的 CloseHandle 实际上是在使用源进程的IAT,而不是目标进程的IAT,这会导致引用无效的内存地址。
直接使用 CloseHandle(这是一个普通的函数调用,而不是函数指针调用),此调用依赖于导入表。在注入的远程线程中,导入表的地址是随机的(由于 ASLR),因此直接调用会导致目标进程崩溃,因为它试图通过一个不正确的地址(对应于源进程的导入表)调用 CloseHandle
总结:直接使用了普通函数调用(依赖导入表),导致在目标进程中,地址不正确,从而引发注入进程崩溃。
**解决方法:**Visual Studio在编译此类功能的文件时建议关闭编译器的“/GS”选项或者使用关键字__declspec(safebuffers),因为开启GS插入栈保护代码,这其中调用的函数由于重定位的问题会导致导致注入进程的崩溃。
typedef struct _RemoteProcessInfo {
DWORD pid; // 进程ID
WCHAR processName[MAX_PATH]; // 进程名称
}RemoteProcessInfo;
//远程线程函数参数
typedef struct _RemoteParam
{
LPVOID lpvMP32FWAdress;
LPVOID lpvMP32NWAddress;
LPVOID lpvMCHdlAddress;
LPVOID lpvVirAllocAdress;
LPVOID lpvCTH32SnapshotAddress;
LPVOID lpvProcessInfoAddress;
size_t nProcessSize;
}RemoteParam;
注入代码:
/************************************************************************
* Function:远程线程函数(主体)
* Name : ThreadProc
* Param: lprp 传到远程线程的参数
* Return: success->0,fail->win32 error code
* **********************************************************************/
__declspec(safebuffers) DWORD WINAPI ThreadProc(RemoteParam* lprp) {
// 类型定义
typedef LPVOID(WINAPI* MVirtualAllocEx)(IN LPVOID lpAddress, IN SIZE_T dwSize, IN DWORD flAllocationType, IN DWORD flProtect);
typedef HANDLE(WINAPI* MCreateToolhelp32Snapshot)(IN DWORD dwFlags, IN DWORD th32ProcessID);
typedef BOOL(WINAPI* MProcess32FirstW)(IN HANDLE hSnapshot, OUT PROCESSENTRY32W* lppe);
typedef BOOL(WINAPI* MProcess32NextW)(IN HANDLE hSnapshot, OUT PROCESSENTRY32W* lppe);
typedef BOOL(WINAPI* MCloseHandle)(IN HANDLE hObject);
// 初始化函数指针
MVirtualAllocEx MAllocMem = (MVirtualAllocEx)lprp->lpvVirAllocAdress;
MCreateToolhelp32Snapshot MCreateSnapshot = (MCreateToolhelp32Snapshot)lprp->lpvCTH32SnapshotAddress;
MProcess32FirstW MProcessFirstW = (MProcess32FirstW)lprp->lpvMP32FWAdress;
MProcess32NextW MProcessNextW = (MProcess32NextW)lprp->lpvMP32NWAddress;
MCloseHandle MCloseHd = (MCloseHandle)lprp->lpvMCHdlAddress;
if (!MCreateSnapshot || !MAllocMem || !MProcessFirstW || !MProcessNextW || !MCloseHd) {
return 0;
}
// 拍摄进程快照
HANDLE hSnapshot = MCreateSnapshot(TH32CS_SNAPPROCESS, 0);
if (hSnapshot == INVALID_HANDLE_VALUE) {
return 0;
}
PROCESSENTRY32W pe32;
pe32.dwSize = sizeof(PROCESSENTRY32W);
// 首先计算所需的内存大小
size_t processCount = 0;
// 获取进程数量
if (MProcessFirstW(hSnapshot, &pe32)) {
do {
processCount++;
} while (MProcessNextW(hSnapshot, &pe32));
}
// 根据进程数量动态分配内存
SIZE_T allocSize = sizeof(RemoteProcessInfo) * processCount;
RemoteProcessInfo* pProcessArray = (RemoteProcessInfo*)MAllocMem(NULL, allocSize, MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE);
if (!pProcessArray) {
MCloseHd(hSnapshot);
return 0;
}
// 填充进程信息
int i = 0;
if (MProcessFirstW(hSnapshot, &pe32)) {
do {
if (i >= processCount) break;
pProcessArray[i].pid = pe32.th32ProcessID;
wcscpy(pProcessArray[i].processName, pe32.szExeFile);
i++;
} while (MProcessNextW(hSnapshot, &pe32));
}
// 将分配的内存地址和大小存回到 RemoteParam 结构体
lprp->lpvProcessInfoAddress = pProcessArray;
lprp->nProcessSize = processCount;
MCloseHd(hSnapshot);
return 0;
}
/************************************************************************
* Function:从目标进程获取进程信息的函数
* Name : GetProcessInfoByR3Injection
* Param: vecProcessInfo 应用层进程信息
* Return: success->0,fail->win32 error code
* **********************************************************************/
DWORD GetProcessInfoByR3Injection(std::vector<PROCESS_INFORMATION*>& vecProcessInfo)
{
ULONG pid = 0;
HANDLE hSnapshot = NULL;
PROCESSENTRY32 pe32 = { 0 };
DWORD dwResult = ERROR_SUCCESS;
// 启用调试权限
EnableDebugPrivilege();
dwResult = GetProcessPidByName(L"Taskmgr.exe", &pid);
if (dwResult != ERROR_SUCCESS) {
LOGI("get Taskmgr pid fail,%d", dwResult);
dwResult = GetProcessPidByName(L"explorer.exe", &pid);
if (dwResult != ERROR_SUCCESS) {
LOGE("get explorer pid fail,%d", dwResult);
return dwResult;
}
else {
LOGI("get explorer pid success:%d", pid);
}
}
HANDLE hProc = OpenProcess(PROCESS_ALL_ACCESS, FALSE, pid);
if (hProc == NULL) {
dwResult = GetLastError();
LOGE("OpenProcess fail, pid:%d, err:%d", pid, dwResult);
return dwResult;
}
RemoteParam* rp = (RemoteParam*)malloc(sizeof(RemoteParam));
if (!rp) {
LOGE("Failed to allocate memory for RemoteParam.");
CloseHandle(hProc);
return ERROR_OUTOFMEMORY;
}
ZeroMemory(rp, sizeof(RemoteParam));
HMODULE hMod = GetModuleHandle(L"Kernel32.dll");
if (hMod == NULL) {
free(rp);
CloseHandle(hProc);
dwResult = GetLastError();
LOGE("get Kernel32.dll handle fail, err:%d", dwResult);
return dwResult;
}
rp->lpvMP32FWAdress = (LPVOID)GetProcAddress(hMod, "Process32FirstW");
rp->lpvMP32NWAddress = (LPVOID)GetProcAddress(hMod, "Process32NextW");
rp->lpvVirAllocAdress = (LPVOID)GetProcAddress(hMod, "VirtualAlloc");
rp->lpvCTH32SnapshotAddress = (LPVOID)GetProcAddress(hMod, "CreateToolhelp32Snapshot");
rp->lpvMCHdlAddress = (LPVOID)GetProcAddress(hMod, "CloseHandle");
// 在目标进程中分配内存用于存储 RemoteParam
RemoteParam* pRemoteParam = (RemoteParam*)VirtualAllocEx(hProc, 0, sizeof(RemoteParam), MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE);
if (!pRemoteParam) {
LOGE("Failed to allocate memory in target process.");
free(rp);
CloseHandle(hProc);
return ERROR_OUTOFMEMORY;
}
// 将参数数据写入目标进程
if (!WriteProcessMemory(hProc, pRemoteParam, rp, sizeof(RemoteParam), 0)) {
dwResult = GetLastError();
LOGE("WriteProcessMemory failed. Error code: %d", dwResult);
VirtualFreeEx(hProc, pRemoteParam, 0, MEM_RELEASE);
free(rp);
CloseHandle(hProc);
return dwResult;
}
// 分配远程线程代码空间
LPVOID pRemoteThread = VirtualAllocEx(hProc, 0, 1024 * 4, MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE);
if (!pRemoteThread) {
dwResult = GetLastError();
LOGE("Failed to allocate 4K memory in target process.");
VirtualFreeEx(hProc, pRemoteParam, 0, MEM_RELEASE);
free(rp);
CloseHandle(hProc);
return dwResult;
}
// 将线程函数写入到目标进程
if (!WriteProcessMemory(hProc, pRemoteThread, &ThreadProc, 1024 * 4, 0)) {
dwResult = GetLastError();
LOGE("WriteProcessMemory failed. Error code: %d", dwResult);
VirtualFreeEx(hProc, pRemoteThread, 0, MEM_RELEASE);
VirtualFreeEx(hProc, pRemoteParam, 0, MEM_RELEASE);
free(rp);
CloseHandle(hProc);
return dwResult;
}
// 在目标进程中创建远程线程来执行 ThreadProc
HANDLE hThread = CreateRemoteThread(hProc, NULL, 0, (LPTHREAD_START_ROUTINE)pRemoteThread, (LPVOID)pRemoteParam, 0, NULL);
if (hThread == NULL) {
dwResult = GetLastError();
LOGE("CreateRemoteThread fail, pid:%d, err:%d", pid, dwResult);
VirtualFreeEx(hProc, pRemoteThread, 0, MEM_RELEASE);
VirtualFreeEx(hProc, pRemoteParam, 0, MEM_RELEASE);
free(rp);
CloseHandle(hProc);
return dwResult;
}
else {
LOGI("Create remote thread success, tid:%p,", hThread);
}
// 等待远程线程完成
DWORD dwWait = 5 * 1000; // 5 秒超时
WaitForSingleObject(hThread, dwWait);
// 从目标进程读取 RemoteParam 信息
if (!ReadProcessMemory(hProc, pRemoteParam, rp, sizeof(RemoteParam), NULL)) {
dwResult = GetLastError();
LOGE("Failed to read remote process memory, error:%d", dwResult);
VirtualFreeEx(hProc, pRemoteThread, 0, MEM_RELEASE);
VirtualFreeEx(hProc, pRemoteParam, 0, MEM_RELEASE);
free(rp);
CloseHandle(hThread);
CloseHandle(hProc);
return dwResult;
}
// 确保读取的 nProcessSize 值是有效的
if (rp->nProcessSize == 0) {
LOGE("Invalid process size value from remote process.");
if (rp->lpvProcessInfoAddress) { // 释放远程线程分配的内存
VirtualFreeEx(hProc, rp->lpvProcessInfoAddress, 0, MEM_RELEASE);
}
VirtualFreeEx(hProc, pRemoteThread, 0, MEM_RELEASE);
VirtualFreeEx(hProc, pRemoteParam, 0, MEM_RELEASE);
free(rp);
CloseHandle(hThread);
CloseHandle(hProc);
return ERROR_INVALID_PARAMETER;
}
// 读取远程线程分配的内存中的进程信息
SIZE_T processInfoSize = rp->nProcessSize * sizeof(RemoteProcessInfo);
RemoteProcessInfo* pProcessInfoArray = (RemoteProcessInfo*)malloc(processInfoSize);
if (!pProcessInfoArray) {
LOGE("Failed to allocate memory on heap.");
if (rp->lpvProcessInfoAddress) { // 释放远程线程分配的内存
VirtualFreeEx(hProc, rp->lpvProcessInfoAddress, 0, MEM_RELEASE);
}
VirtualFreeEx(hProc, pRemoteThread, 0, MEM_RELEASE);
VirtualFreeEx(hProc, pRemoteParam, 0, MEM_RELEASE);
free(rp);
CloseHandle(hThread);
CloseHandle(hProc);
return ERROR_OUTOFMEMORY;
}
// 从目标进程读取实际的进程信息
if (!ReadProcessMemory(hProc, rp->lpvProcessInfoAddress, pProcessInfoArray, processInfoSize, NULL)) {
dwResult = GetLastError();
LOGE("Failed to read process information from remote process memory, error:%d", dwResult);
free(pProcessInfoArray);
if (rp->lpvProcessInfoAddress) { // 释放远程线程分配的内存
VirtualFreeEx(hProc, rp->lpvProcessInfoAddress, 0, MEM_RELEASE);
}
VirtualFreeEx(hProc, pRemoteThread, 0, MEM_RELEASE);
VirtualFreeEx(hProc, pRemoteParam, 0, MEM_RELEASE);
free(rp);
CloseHandle(hThread);
CloseHandle(hProc);
return dwResult;
}
// 将获取到的信息转换为 vecProcessInfo 格式
for (size_t i = 0; i < rp->nProcessSize; ++i) {
if (pProcessInfoArray[i].pid == 0 || wcslen(pProcessInfoArray[i].processName) == 0) {
continue;
}
PROCESS_INFORMATION* pProcessInfo = (PROCESS_INFORMATION*)malloc(sizeof(PROCESS_INFORMATION));
if (pProcessInfo == nullptr) {
dwResult = ERROR_OUTOFMEMORY;
LOGE("Failed to allocate memory for process information.");
break;
}
memset(pProcessInfo, 0, sizeof(PROCESS_INFORMATION));
pProcessInfo->Pid = pProcessInfoArray[i].pid;
std::wstring wstrFilePath = std::wstring(pProcessInfoArray[i].processName);
auto pTempPath = wcharTochar(wstrFilePath.c_str());
if (pTempPath) {
strcpy_s(pProcessInfo->szProcessName, MAX_PATH, pTempPath.get());
}
vecProcessInfo.push_back(pProcessInfo);
}
// 释放远程线程分配的内存
if (rp->lpvProcessInfoAddress) {
VirtualFreeEx(hProc, rp->lpvProcessInfoAddress, 0, MEM_RELEASE);
}
// 释放分配的内存和资源
free(pProcessInfoArray);
free(rp);
VirtualFreeEx(hProc, pRemoteThread, 0, MEM_RELEASE);
VirtualFreeEx(hProc, pRemoteParam, 0, MEM_RELEASE);
CloseHandle(hThread);
CloseHandle(hProc);
return dwResult;
}
参考:
1.https://www.cnblogs.com/PeterZ1997/p/9532065.html
2.https://blog.csdn.net/sandeel/article/details/5387708