
大家好你们可以叫我凌是个16岁的网络安全学习者。今天延续上篇文章内容正式讲解本栏目内容。今天讲的是如何不使用WindowsAPI来定位kernel32.dll文件。不使用WindowsAPI定位的缘由如果要调用WindowsAPI注入器时需要调用 GetModuleHandleA(kernel32.dll) 来获取基址但有条注意事项杀软/EDR的Hook本质上就是在 GetModuleHandle 的代码入口处插了双眼睛。只要程序调用了 GetModuleHandle EDR会立刻记录➢ 我们访问了哪个DLL➢ 我们的调用栈是怎样的➢ 我们紧接着用它做了什么显而易见这样子在之后的操作中那报毒是说来就来极为容易被发现大大降低了隐蔽性。解决办法就是绕过这个函数直接去内存里把它要返回的那个值手工抄出来。 而这个“值”kernel32.dll 的基址就记录在Windows内核为每个进程维护的一张“户口本” 里——这个户口本的名字叫 PEB进程环境块。以下是我利用WindowsAPI编写的简易进程注入程序可以看出没有丝毫隐私 详细代码见 这里涉及的核心结构体/宏那在开始之前我们得先理解其中涉及到的核心结构体以及相关宏那我们直接来看下吧。UNICODE_STRINGWindows 内核通用字符串结构typedef struct _UNICODE_STRING { USHORT Length; // 字符串当前长度字节数不含结尾 \0 USHORT MaximumLength; // 缓冲区总容量字节数 PWSTR Buffer; // 指向宽字符数组的指针 } UNICODE_STRING; 注意Length是字节数转换成字符数需Length / 2宽字符wchar_t占2字节。LIST_ENTRY双向链表节点typedef struct _LIST_ENTRY { struct _LIST_ENTRY Flink; // 指向下一个节点Forward Link struct _LIST_ENTRY Blink; // 指向上一个节点Backward Link } LIST_ENTRY; Flink前进方向从头部向后遍历时使用 Blink后退方向PEB 关键字段进程环境块typedef struct _PEB { BYTE Reserved1[0x18]; // 0x00~0x17 保留/填充 PPEB_LDR_DATA Ldr; // 0x18 ← 指向加载器数据结构的指针 // ... 后续字段省略 } PEB;PEB_LDR_DATA 关键字段加载器数据typedef struct _PEB_LDR_DATA { ULONG Length; // 0x00 BOOLEAN Initialized; // 0x04 PVOID SsHandle; // 0x08 LIST_ENTRY InLoadOrderModuleList; // 0x10 LIST_ENTRY InMemoryOrderModuleList; // 0x20 ← 我们遍历的链表头 LIST_ENTRY InInitializationOrderModuleList; // 0x30 } PEB_LDR_DATA;LDR_DATA_TABLE_ENTRY 关键字段struct MY_LDR_DATA_TABLE_ENTRY { LIST_ENTRY InLoadOrderLinks; // 0x00 LIST_ENTRY InMemoryOrderLinks; // 0x10 ← 链表节点指向前后模块 LIST_ENTRY InInitializationOrderLinks;// 0x20 PVOID DllBase; // 0x30 ← 模块基址核心输出 PVOID EntryPoint; // 0x38 ULONG SizeOfImage; // 0x40 UNICODE_STRING FullDllName; // 0x48 完整路径 UNICODE_STRING BaseDllName; // 0x58 文件名如 Lkernel32.dll // 后续字段省略不影响遍历 };关键字段说明 DllBase模块在进程内存中的起始地址。 BaseDllName模块名称不含路径用于匹配目标 DLL。CONTAINING_RECORD核心宏反推结构体首地址CONTAINING_RECORD(pEntry, MY_LDR_DATA_TABLE_ENTRY, InMemoryOrderLinks)作用通过结构体成员的地址反推整个结构体的起始地址。数学原理结构体首地址 成员地址 - 成员在结构体内的偏移量实现原理宏展开((LDR_DATA_TABLE_ENTRY)((PCHAR)(pEntry) - (ULONG_PTR)(((LDR_DATA_TABLE_ENTRY)0)-InMemoryOrderLinks)))__readgsqword编译器内联函数uintptr_t pPeb __readgsqword(0x60);作用读取gs段寄存器偏移0x60处的8字节QWORD数据。为什么是 gsx64下gs段寄存器固定指向当前线程的TEB线程环境块。为什么是0x60TEB 偏移0x60处存储着当前进程的PEB指针。具体的结构体和宏我就不过多赘述了因为这本身网络上内容就丰富可以自行查找。所以我们就先直接跳转到完整利用链来看看。完整的利用链内存寻址链路图硬件层│├── gs 段寄存器x64│ ││ └── 指向当前线程的 TEB线程环境块│ ││ └── [TEB 0x60] → PEB进程环境块│ ││ └── [PEB 0x18] → PEB_LDR_DATA加载器数据│ ││ └── [Ldr 0x20] → InMemoryOrderModuleList 链表头LIST_ENTRY│ ││ └── 遍历头节点的 Flink│ ││ ▼│ 第 1 个 LDR_DATA_TABLE_ENTRY│ ├── InMemoryOrderLinks → LIST_ENTRY 节点│ ├── [偏移 0x30] DllBase → 模块基址│ └── [偏移 0x58] BaseDllName → 模块名│ ││ ▼│ 第 2 个 LDR_DATA_TABLE_ENTRY│ └── ...以此类推│└── 遍历直至 Flink 重新指向链表头循环结束关键步骤文字总结➢ gs:[0x60] → 拿到PEB指针。➢ PEB 0x18 → 拿到LdrPEB_LDR_DATA。➢ Ldr 0x20 → 拿到InMemoryOrderModuleList的头部这是个LIST_ENTRY结构。➢ ListHead-Flink → 取出第一个实际模块的 LIST_ENTRY 节点。➢ CONTAINING_RECORD → 通过该 LIST_ENTRY 节点反推出完整的 LDR_DATA_TABLE_ENTRY。➢ 读取 Entry-DllBase偏移 0x30获取基址读取 Entry-BaseDllName.Buffer偏 移 0x58获取模块名。➢ 继续 Flink 遍历直到回到链表头。核心偏移量速查表(Win10/11 x64) 提醒这些偏移量在 Windows 10 22H2 / Windows 11 22H2 x64 上长期稳定。但不同 Windows 版本如 Win7、 Win8可能存在细微差异偏移 ± 0x8。代码演示那看了这么多我们直接来看现成的CPP代码都是如何写的吧代码示例 基于偏移量#include windows.h #include iostream #include cwchar int main() { uintptr_t pPeb __readgsqword(0x60); uintptr_t pLdr *(uintptr_t*)(pPeb 0x18); uintptr_t pHead pLdr 0x20; // PEB_LDR_DATA.InMemoryOrderModuleList uintptr_t pEntry *(uintptr_t*)pHead; while (pEntry ! pHead) { uintptr_t pModule pEntry - 0x10; // LDR_DATA_TABLE_ENTRY.InMemoryOrderLinks uintptr_t dllBase *(uintptr_t*)(pModule 0x30); if (dllBase 0) { pEntry *(uintptr_t*)pEntry; continue; } USHORT nameLen *(USHORT*)(pModule 0x58); // BaseDllName.Length wchar_t* namePtr *(wchar_t**)(pModule 0x60); // BaseDllName.Buffer if (namePtr nameLen 0) { std::wstring dllName(namePtr, nameLen / 2); if (_wcsicmp(dllName.c_str(), Lkernel32.dll) 0) { std::wcout L[] kernel32.dll: 0x std::hex dllBase std::dec std::endl; } else if (_wcsicmp(dllName.c_str(), Lntdll.dll) 0) { std::wcout L[] ntdll.dll: 0x std::hex dllBase std::dec std::endl; } } pEntry *(uintptr_t*)pEntry; } }代码示例 基于结构体#include windows.h #include iostream #include iomanip #include string // 重定义UNICODE_STRING是Windows内核中表示Unicode字符串的结构体 struct MY_UNICODE_STRING{ USHORT Length; // 字符串长度字节数 USHORT MaximumLength; // 缓冲区大小 PWSTR Buffer; // 宽字符串指针 }; // 重定义LDR_DATA_TABLE_ENTRY是PEB中Ldr模块链表的节点结构体 struct MY_LDR_DATA_TABLE_ENTRY{ LIST_ENTRY InLoadOrderLinks; // 0x00 LIST_ENTRY InMemoryOrderLinks; // 0x10 ← 遍历的链表节点 LIST_ENTRY InInitializationOrderLinks;// 0x20 PVOID DllBase; // 0x30 模块基址 PVOID EntryPoint; // 0x38 ULONG SizeOfImage; // 0x40 MY_UNICODE_STRING FullDllName; // 0x48 完整路径 MY_UNICODE_STRING BaseDllName; // 0x58 文件名不含路径 // 后续字段省略... }; int main(){ // 获取PEB地址 uintptr_t pPeb __readgsqword(0x60); // 获取Ldr结构指针 uintptr_t pLdr *(uintptr_t*)(pPeb 0x18); // 获取InMemoryOrderModuleList链表头 LIST_ENTRY* pListHead (LIST_ENTRY*)(pLdr 0x20); LIST_ENTRY* pEntry pListHead-Flink; // 首个节点 while(pEntry ! pListHead){ // 使用 CONTAINING_RECORD 反推结构体首地址 MY_LDR_DATA_TABLE_ENTRY* pModule CONTAINING_RECORD(pEntry, MY_LDR_DATA_TABLE_ENTRY, InMemoryOrderLinks); // 读取模块基址 uintptr_t dllBase (uintptr_t)pModule-DllBase; if(dllBase 0){ pEntry pEntry-Flink; continue; } // 读取模块文件名 MY_UNICODE_STRING* pName pModule-BaseDllName; if(pName-Buffer pName-Length 0){ // 将宽字符串转换为std::wstring std::wstring dllName(pName-Buffer, pName-Length / 2); if(_wcsicmp(dllName.c_str(), Lkernel32.dll) 0){ std::cout [] kernel32.dll 基址: 0x std::hex dllBase std::dec std::endl;} else if(_wcsicmp(dllName.c_str(), Lntdll.dll) 0){ std::cout [] ntdll.dll 基址: 0x std::hex dllBase std::dec std::endl; } pEntry pEntry-Flink; } } }运行出来应该是以下内容[] ntdll.dll 基址: 0x7fface5a0000[] kernel32.dll 基址: 0x7ffacdb00000以下为代码里的“间接调用链”映射到硬件的过程CPU 寄存器 (gs)│├── 读取 gs:[0x60] → 得到 PEB 虚拟地址 (例如 0x00007FFE0000)│├── 读取 PEB0x18 → 得到 Ldr 虚拟地址 (例如 0x00007FFE1000)│├── 读取 Ldr0x20 → 得到链表头 ListHead 地址│├── 读取 ListHead-Flink → 获取第一个节点指针│├── 通过 CONTAINING_RECORD 得到 LDR_DATA_TABLE_ENTRY 起始地址│├── 读取 0x30 得到 DllBase└── 读取 0x58 得到 BaseDllName.Buffer好了今天就先到这里了其余的我就不进行过多赘述了。具体内容可以自行查找了解或看Github内容因为我本身也是将本栏目作为笔记存放所以就没有那么详细。 具体内容见这里(Github仓库)