大家好你们可以叫我凌。今天我们来学习下 WOW64兼容性检测与32/64位PEB差异。本篇章具体文档及代码WOW64兼容性检测与32及64位PEB差异Github仓库https://github.com/LingBale/My-Learning-Journey/tree/63c6b8841fd9e53f40e43d2ebdca90431179a5ab/Windows%20%E7%BA%A2%E9%98%9F%E5%AE%9E%E6%88%98/%E7%AC%AC2%E8%AF%BE.WOW64%E5%85%BC%E5%AE%B9%E6%80%A7%E6%A3%80%E6%B5%8B%E4%B8%8E32%E5%8F%8A64%E4%BD%8DPEB%E5%B7%AE%E5%BC%82为什么“位数”是个问题Windows 系统有两套完全不同的内存布局逻辑➢ 64位进程原生运行在64位地址空间PEB在gs:[0x60]指针是8字节。➢ 32位进程在64位Windows上通过WOW64Windows32-bitonWindows64-bit 子系统运行PEB在fs:[0x30]指针是4字节。关键陷阱如果用64位的偏移量比如PEB0x18找Ldr去读32位进程的PEB会读到 一堆错位的数据轻则输出乱码重则直接崩溃。所以在跨进程读取PEB、遍历模块、注入Shellcode之前必须先判断目标进程的位数。WOW64机制的物理本质建立画面➢ 一个32位程序mushiai_chs.exe运行在64位Windows上。➢ 系统为它在C:\Windows\SysWOW64\里准备了32位的ntdll.dll、kernel32.dll。➢ 它的fs:[0x30]指向32位PEB。➢ 但同时系统还会在64位地址空间里为这个WOW64进程维护一个64位PEB用 来跟内核沟通。涉及的核心结构体与类型pNtQueryInformationProcess函数指针类型定义typedef NTSTATUS (NTAPI * NtQueryInformationProces)( HANDLE, PROCESSINFOCLASS, PVOID, ULONG, PULONG );为什么需要自定义函数指针类型NtQueryInformationProcess不在kernel32.lib或标准C库中无法直接链接调用。必须通过GetProcAddress动态获取其地址再强转为该函数指针类型才能调用。PROCESSINFOCLASS枚举类型typedef enum _PROCESSINFOCLASS { ProcessBasicInformation 0, ProcessWow64Information 26, // 本节使用的参数 ProcessImageFileName 27, // ... 更多枚举值 } PROCESSINFOCLASS;ProcessWow64Information 值为 26查询目标进程的 WOW64 状态。ULONG_PTR自适应指针类型ULONG_PTR wow64Info 0;➢ 在32位编译环境下ULONG_PTR 为 4 字节unsigned int➢ 在64位编译环境下ULONG_PTR 为 8 字节unsigned __int64➢ 用它接收 NtQueryInformationProcess 的返回结果可以自适应位数避免在32/64 位环境下长度不匹配。NTSTATUS原生API返回类型NTSTATUS status NtQuery(hProcess, ...);➢ NTSTATUS 是 LONG 的别名4字节有符号整数➢ status 0即 STATUS_SUCCESS表示成功➢ 非0值表示错误如 0xC0000005 表示访问违规涉及的关键宏与常量ProcessWow64Information(值 26)NtQuery(hProcess, ProcessWow64Information, wow64Info, sizeof(wow64Info), NULL);作用告诉 NtQueryInformationProcess我们要查询的是“目标进程是否为 WOW64”。返回值规律➢ wow64Info 0 → 目标进程为纯64位➢ wow64Info ! 0 → 目标进程为32位(WOW64)且该值就是该进程的64位PEB地址NTAPI调用约定宏typedef NTSTATUS(NTAPI pNtQueryInformationProcess)(...);➢ 展开为__stdcallWindows原生API统一使用__stdcall调用约定参数从右向左入栈 被调用者清理栈。➢ 为何必加如果漏写NTAPI编译器默认用__cdecl调用时栈会失衡导致程序崩溃。STATUS_SUCCESS(值为 0)if (status ! 0) { /* 失败 */ }➢ 原生API的标准成功返回码。➢ 等价于status 0。两个辅助APIGetModuleHandleA API介绍作用给一个模块名比如ntdll.dll返回这个模块当前加载在内存里的基址。HMODULEhNtdllGetModuleHandleA(ntdll.dll); //如果成功hNtdll就是ntdll.dll 的基址 //如果失败返回NULLGetProcAddress API介绍作用给一个模块基址和一个函数名返回这个函数的内存地址。//假设hNtdll 是 ntdll.dll 的基址 pNtQueryInformationProcess NtQuery(pNtQueryInformationProcess)GetProcAddress(hNtdll,NtQueryInformationProcess); //如果成功NtQuery就是NtQueryInformationProcess 函数的地址利用链路第 1 步获取 ntdll.dll 基址GetModuleHandleA(ntdll.dll)│└── 返回 HMODULE模块基址失败返回 NULL第 2 步获取 NtQueryInformationProcess 函数地址GetProcAddress(hNtdll, NtQueryInformationProcess)│└── 返回 FARPROC函数地址失败返回 NULL│└── 强转为 pNtQueryInformationProcess 类型第 3 步调用函数查询目标进程位数NtQuery(hProcess, ProcessWow64Information, wow64Info, sizeof(wow64Info), NULL)│└── status 0 → 调用成功│└── 读取 wow64Info 的值第 4 步判断结果┌──────────────────────────────────┐│ wow64Info 0 → 纯64位进程 ││ wow64Info ! 0 → 32位进程(WOW64) ││ 且值 64位PEB地址 │└──────────────────────────────────┘第 5 步关闭句柄清理资源CloseHandle(hProcess)总结➢ GetModuleHandleA拿到ntdll.dll基址这个DLL是原生API的“仓库”。➢ GetProcAddress从ntdll.dll导出表中找到NtQueryInformationProcess函数地址。➢ 强转类型后调用传入ProcessWow64Information参数。➢ 检查status是否为0若成功则读取wow64Info判断位数。➢ 清理hProcess句柄。代码示例以下为相关代码示例以供参考#include windows.h #include winternl.h #include tlhelp32.h #include iostream #include string // 定义函数指针类型为后续通过 GetProcAddress 动态获取做准备 typedef NTSTATUS (NTAPI * NtQueryInformationProcess)( HANDLE, PROCESSINFOCLASS, PVOID, ULONG, PULONG ); // // 判断目标进程架构 // bool IsProcess64Bit(HANDLE Process){ HMODULE hNtdll GetModuleHandleA(ntdll.dll); if(!hNtdll){ std::cerr [-] 无法获取 ntdll.dll 句柄 std::endl; return false; } NtQueryInformationProcess NtQuery (NtQueryInformationProcess)GetProcAddress(hNtdll, NtQueryInformationProcess); if(!NtQuery){ std::cerr [-] 获取 NtQueryInformationProces 失败 std::endl; return false; } ULONG_PTR Wow64Infor{}; NTSTATUS status NtQuery(Process, ProcessWow64Information, Wow64Infor, sizeof(ULONG_PTR), NULL); if(status ! 0){ std::cerr [-] ProcessWow64Information 调用失败 std::endl; return false; } if(Wow64Infor 0){ std::cout [*] 目标为64位进程 std::endl; return true; }else{ std::cout [*] 目标为32位进程64位PEB地址为 std::hex Wow64Infor std::dec std::endl; return false; } } // // 通过进程名获取 PID // DWORD GetProcessPidByName(const std::string ProcessName){ HANDLE hSnap CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0); if(hSnap INVALID_HANDLE_VALUE){ std::cerr [-] 进程快照拍摄失败 std::endl; return false; } PROCESSENTRY32 pe; pe.dwSize sizeof(PROCESSENTRY32); if(Process32First(hSnap, pe)){ do{ if(ProcessName pe.szExeFile){ CloseHandle(hSnap); return pe.th32ProcessID; } }while(Process32Next(hSnap, pe)); } CloseHandle(hSnap); std::cout [-] 未找到目标进程 std::endl; return 0; } // // 主函数 // int main(){ std::string ProcessName; std::cout 请输入目标进程名; getline(std::cin, ProcessName); DWORD ProcessPID GetProcessPidByName(ProcessName); if(ProcessPID 0){ std::cerr [-] 获取目标PID失败 std::endl; system(pause); return 0; } HANDLE hProcess OpenProcess(PROCESS_QUERY_INFORMATION | PROCESS_VM_READ, FALSE, ProcessPID); if(!hProcess){ std::cout [-] 进程打开失败 std::endl; system(pause); return 0; } IsProcess64Bit(hProcess); CloseHandle(hProcess); system(pause); }32/64位PEB偏移差异对照表目标内容64 位PEB偏移32位PEB偏移LdrPEB0x18PEB0x0CInMemoryOrderModuleListLdr0x20Ldr0x14LDR_DATA_TABLE_ENTRY 的 InMemoryOrderLinks0x100x08DllBase0x300x18BaseDllNameUNICODE_STRING0x580x2CEntryPoint0x380x1CSizeOfImage0x400x20