如何在32位程序塞入64位代码从而绕过hook拦截
公众号:https://mp.weixin.qq.com/s/DljrsqfemEZcblCyVYNDsQ
这里是两段汇编代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| section .data msg db "Hello, World!", 10 len equ $ - msg
section .text global _start
_start: ; ssize_t write(int fd, const void *buf, size_t count) mov eax, 4 ; sys_write mov ebx, 1 ; stdout mov ecx, msg mov edx, len int 0x80
; void exit(int status) mov eax, 1 ; sys_exit xor ebx, ebx int 0x80
|
使用int 0x80系统调用完成输出
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| section .data msg db "Hello, World!", 10 len equ $ - msg
section .text global _start
_start: ; ssize_t write(int fd, const void *buf, size_t count) mov rax, 1 ; sys_write mov rdi, 1 ; stdout lea rsi, [rel msg] mov rdx, len syscall
; void exit(int status) mov rax, 60 ; sys_exit xor rdi, rdi syscall
|
这里使用的是syscall完成输出
或许在之前你已经了解过DLL注入的相关篇章
https://mp.weixin.qq.com/s/qYO0Cf5MRT4vKCT5WYz1KQ
不禁让人产生疑问:32位和64位程序使用的指令集不同,那么64位系统如何兼容运行32位软件呢?本篇文章在windows上作为探索。
Wow64分析
32位程序在64位windows上的运行时通过wow64模拟器实现的

顺带提一嘴,这里根据《深入解析windows操作系统》3.6 CreateProcess 的创建进程中相关步骤
-
首先是转换并验证参数:主要调度优先级,是否调试,分析参数
-
打开要执行的映像:主要是创建对应的windows映像

-
创建Windows进程执行体对象:主要是设置EPROCESS对象,其中
- 如果处于
Wow64下检查是否使用大页面
Wow64下则随后分配辅助结构EWOW64PROCESS
- 在映射
Ntdll.dll到进程中,对于Wow64进程还需要映射32位的Ntdll.dll
操作系统相关知识
在OS混沌未开之际,便有几位老祖
- CS (Code Segment Register):代码段的段基址
- DS(Data Segment Register):数据段的段基址
- ES(Extra Segment Register):其值为附加数据段的段基值,称为“附加”是因为此段寄存器用途不像其他
sreg 那样固定,可以额外做他用。
- FS(Extra Segment Register):其值为附加数据段的段基值
- GS:同上
- SS(Stack Segment Register):堆栈段寄存器
其存储着os的本源灵气(物理地址)
再后来一生二、二生三,老祖集天地之造化,创结界(保护模式),著天书(全局描述符GDT(Global Descriptor Table)),生韵韵众生于虚幻。老祖(段寄存器)在虚幻中的像,幻化众生心中便为段选择子。
说人话:
段寄存器(CS / DS / SS …)里放的 不是地址,而是:一个 索引 + 权限声明的小结构。
在windows中
| CS |
含义 |
0x23 |
32 位用户代码段 |
0x33 |
64 位用户代码段 |
0x10 |
内核代码段 |
- 怎么做权限隔离?
- 怎么防止用户程序乱访问内核?
- 怎么同时跑不同“模式”的代码?
Intel 的答案:把“段的定义”集中放在一张表里,让 CPU 强制检查

段选择子一般长这样(冒号后面可以理解为二进制的长度(bit长度),注意是小端序)
1 2 3 4 5 6 7 8 9 10 11
| typedef struct selector { unsigned char RPL :2; unsigned char TI :1; unsigned short index :13; } __attribute__((packed)) selector;
|
那么当CS=0x33=0b110011是,按照段选择子的结构体解析:
- RPL = 11 b = 2
- TI = 0 = 0
- index = 110 = 6
那么CPU就回去GDT[6]看这是一个 64 位 ring3 代码段
天堂之门 Heaven’s Gate
利用Wow64机制,就可以在32位程序中运行64位代码,这样EDR 常 hook 32 位 ntdll.dll的时候就可以绕过检测。同时分析32位的程序发现了64位的指令一般是反汇编不出的,例如使用32位的ida分析64位的程序。
这种方式的特征就是使用长跳,类似如下代码:
1 2 3 4 5 6 7 8
| ; 32-bit context far_jump CS=0x33, RIP=entry64
; ===== 64-bit context ===== entry64: ; 64-bit instructions ; e.g. call 64-bit ntdll stub far_return CS=0x23
|
现在结合Minhook编写一段hook ntdll.dll中 测试代码
关于minhook的使用可以参考:https://mp.weixin.qq.com/s/Po_t-JGj0e3dMBKDd9i8cw
为了省去LDR这种通用过程的代码,我使用了 https://github.com/JustasMasiulis/wow64pp
这个框架的长跳部分实现如下:


其中的push 0x33只是为“远返回 / 远跳转”准备一个新的段选择子
框架是header-only的,只有一个.h文件,非常轻松的就能使用,但是这个框架目前存在两个问题:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75
| #define NOMINMAX #include "extern/minhook/minhook.h" #include "extern/wow64pp/wow64pp.hpp"
#include <iostream> #include <Windows.h> #include <winternl.h>
typedef NTSTATUS(NTAPI* NtQuerySystemTime_t)( PLARGE_INTEGER SystemTime ); NtQuerySystemTime_t fpNtQuerySystemTime = nullptr;
NTSTATUS NTAPI HookedNtQuerySystemTime( PLARGE_INTEGER SystemTime ) { std::cout << "[hook] NtQuerySystemTime called (x86 stub), ";
NTSTATUS status = fpNtQuerySystemTime(SystemTime);
if (NT_SUCCESS(status)) { std::cout << "[hook] SystemTime = " << SystemTime->QuadPart << std::endl; }
return status; }
int main() {
LARGE_INTEGER systemTime = { 0 }; NtQuerySystemTime(&systemTime);
if (MH_Initialize() != MH_OK) { std::cerr << "[x] init hook failed\n"; return 1; }
LPVOID pTarget = GetProcAddress(GetModuleHandleW(L"ntdll.dll"), "NtQuerySystemTime"); std::cout << "[*] now hook NtQuerySystemTime: 0x" << std::hex << pTarget << std::endl;
if (MH_CreateHook(pTarget, &HookedNtQuerySystemTime, reinterpret_cast<LPVOID*>(&fpNtQuerySystemTime)) != MH_OK) { std::cerr << "[x] create hook failed\n"; return 1; }
MH_EnableHook(pTarget);
NtQuerySystemTime(&systemTime);
auto ntdllHandle = wow64pp::module_handle("ntdll.dll"); std::cout << "[*] found ntdll (x64): 0x" << std::hex << ntdllHandle << "\n";
auto NtQuerySystemTime64 = wow64pp::import(ntdllHandle, "NtQuerySystemTime");
std::cout << "[*] found NtQuerySystemTime (x64): 0x" << std::hex << NtQuerySystemTime64 << "\n";
auto status = wow64pp::call_function( NtQuerySystemTime64, &systemTime );
std::cout << "[wow64] NtQuerySystemTime status: 0x" << std::hex << status << "\n"; std::cout << "[wow64] SystemTime (100ns since 1601): " << systemTime.QuadPart << "\n";
MH_DisableHook(pTarget);
return 0; }
|
- 正常调用
NtQuerySystemTime
- Hook
NtQuerySystemTime并调用
- 使用Wow64进行64位的
NtQuerySystemTime调用

后记
看似很nb但是现在这种技巧已经不怎么行了
比如上面的绕过hook,这种应用层的很明显是无法绕过内核/etw hook的,同时有明显的shellcode特征。不过可以恶心一手逆向的人,因为这样可以向32位的程序狠狠塞64位的shellcode,造成反汇编指令识别的错误,但也只能恶心了。
关于项目结构这里给出来便于复现。

extern\minhook\CMakeLists.txt
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29
| cmake_minimum_required(VERSION 3.11) project(minhook)
set(MINHOOK_SOURCES buffer.c hook.c trampoline.c hde/hde32.c hde/hde64.c )
set(MINHOOK_INCLUDE buffer.h trampoline.h hde/hde32.h hde/hde64.h hde/pstdint.h hde/table32.h hde/table64.h )
add_library(${PROJECT_NAME} STATIC ${MINHOOK_SOURCES} ${MINHOOK_INCLUDE} )
target_include_directories(${PROJECT_NAME} PUBLIC ${CMAKE_CURRENT_SOURCE_DIR} )
|
extern\wow64pp\CMakeLists.txt
1 2 3 4 5 6 7 8 9 10
| cmake_minimum_required(VERSION 3.11) project(wow64pp LANGUAGES CXX)
add_library(${PROJECT_NAME} INTERFACE)
target_include_directories(${PROJECT_NAME} INTERFACE ${CMAKE_CURRENT_SOURCE_DIR} )
|
CMakeLists.txt
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| cmake_minimum_required(VERSION 3.11) project(gate_example LANGUAGES CXX) set(CMAKE_INCLUDE_CURRENT_DIR ON) set(CMAKE_CXX_STANDARD 14) set(CMAKE_CXX_STANDARD_REQUIRED ON)
add_subdirectory(extern/minhook) add_subdirectory(extern/wow64pp)
set(PROJECT_INCLUDE include/HeavensGate.hpp ) set(PROJECT_SOURCE src/main.cpp )
add_compile_options("$<$<C_COMPILER_ID:MSVC>:/utf-8>") add_compile_options("$<$<CXX_COMPILER_ID:MSVC>:/utf-8>")
add_executable(${PROJECT_NAME} ${PROJECT_INCLUDE} ${PROJECT_SOURCE}) target_link_libraries(${PROJECT_NAME} PRIVATE minhook wow64pp) target_compile_definitions(${PROJECT_NAME} PRIVATE UNICODE _UNICODE)
|
引用
https://learn.microsoft.com/zh-cn/windows/win32/winprog64/wow64-implementation-details
https://bbs.kanxue.com/thread-270153.htm
http://blog.rewolf.pl/blog/?p=102