找回密码
立即注册
搜索
热搜: Java Python Linux Go
发回帖 发新帖

1128

积分

0

好友

146

主题
发表于 昨天 07:03 | 查看: 1| 回复: 0

当我们编写32位和64位的汇编程序时,调用系统API的方式是不同的。

这里是两段汇编代码

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 系统调用完成输出。

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 完成输出。

那么一个很自然的问题就来了:32位和64位程序使用的指令集甚至调用约定都不同,为什么我们能在64位的Windows操作系统上直接运行32位的软件呢?本文将带你探索Windows系统实现这一兼容性的核心机制,并演示如何利用它——这项技术被称为“天堂之门”。

Wow64分析

32位程序在64位Windows上的运行,是通过一个名为 wow64 的模拟器层实现的。

WOW64实现详情截图

根据《深入解析Windows操作系统》中关于 CreateProcess 的流程描述,创建进程时涉及几个关键步骤,其中与Wow64相关的有:

  1. 打开要执行的映像:系统会分析可执行文件的类型。
    应用程序执行流程判断图
  2. 创建Windows进程执行体对象:主要是初始化 EPROCESS 内核对象。如果进程运行在 Wow64 环境下,系统会进行额外处理:
    • 检查是否使用大页面内存。
    • 分配一个辅助结构 EWOW64PROCESS 来管理32位进程的特定信息。
    • 在映射系统DLL时,除了常规的64位 Ntdll.dll,还会专门为Wow64进程映射一份32位的 Ntdll.dll

操作系统相关知识

要理解“天堂之门”,我们需要回顾一些操作系统的基础概念。在x86架构的保护模式下,有几个关键的段寄存器(Segment Register):

  • CS (Code Segment Register):指向当前代码段的基址。
  • DS (Data Segment Register):指向当前数据段的基址。
  • SS (Stack Segment Register):指向当前堆栈段的基址。
  • ES/FS/GS (Extra Segment Registers):额外的数据段寄存器,其中FS在Windows中常用于指向线程环境块(TEB)。

在保护模式下,段寄存器里存储的并非直接的物理地址,而是一个称为 段选择子(Segment Selector) 的结构。它是一个“索引+权限声明”的组合。

CPU通过查询全局描述符表(GDT)或局部描述符表(LDT),根据段选择子找到对应的段描述符,从而获得真正的段基址和访问权限。这就实现了内存隔离和保护。例如,在Windows中,一些常见的代码段选择子含义如下:

CS 值 含义
0x23 32 位用户代码段
0x33 64 位用户代码段
0x10 内核代码段

段选择子的结构通常如下所示(以16位为例):

typedef struct selector
{
    unsigned char RPL :2;   // 请求特权级
    unsigned char TI :1;    // 0=GDT, 1=LDT
    unsigned short index :13; // 描述符表索引
} __attribute__((packed)) selector;

// 位布局
// 15              3 2  1 0
// +----------------+--+--+
// |      Index     |TI|RPL|
// +----------------+--+--+

CS=0x33=0b00110011 为例,解析后:

  • RPL = 11b = 3(用户态)
  • TI = 0
  • index = 110b = 6

CPU 就会去 GDT 的第6项查找,发现那是一个64位、ring3级别的代码段描述符,于是便进入64位用户模式执行。

段描述符内存结构示意图

天堂之门 Heaven‘s Gate

“天堂之门”技术正是利用了Wow64的机制。其核心思想是:在一个32位的Wow64进程中,通过修改 CS 段寄存器为 0x33(64位代码段),并配合远跳转或远调用指令,使CPU切换到64位模式,从而直接执行64位代码。

这样做的一个潜在用途是绕过安全软件的检测。因为许多安全产品(EDR)的钩子(Hook)可能只安装在32位的 ntdll.dll 中,而通过“天堂之门”调用64位的 ntdll 函数,就可能绕过这些钩子。此外,在逆向工程分析时,向32位程序中注入64位Shellcode会导致反汇编工具(如32位IDA)解析错误,增加分析难度。

这种技术的典型代码模式如下:

; 当前处于32位上下文
push 0x33                ; 64位代码段选择子
push entry64             ; 64位代码入口地址
retf                     ; 远返回,切换至64位模式

; ===== 进入64位上下文 =====
entry64:
    ; 此处可以执行64位指令
    ; 例如调用64位的 ntdll 函数
    ; ...
    push 0x23            ; 32位代码段选择子
    push back_to_32      ; 返回的32位地址
    retf                 ; 远返回,切换回32位模式

下面我们结合一个实际的 C++ 项目来演示。我们将使用 Minhook 库来挂钩函数,并使用一个名为 wow64pp 的辅助框架来简化“天堂之门”的调用。关于 Minhook 的基础使用,可以参考其官方文档。

wow64pp 框架(header-only,仅一个头文件)封装了模式切换的繁琐细节。其内部实现“长跳”进入64位模式的核心汇编逻辑类似下图所示:

进入64位模式的Shellcode示例

远跳转指令反汇编截图

其中的 push 0x33 指令正是为了接下来的远返回(retf)指令准备一个新的64位代码段选择子。

这个框架使用起来很方便,但需要注意两个限制:

  1. 只能调用 ntdll.dll 中的函数。根据社区(如看雪论坛)的讨论,尝试加载 kernel32.dlluser32.dll 可能会使进程状态混乱(同时混用32位和64位的GUI资源等),导致不可预料的问题。
  2. 多参数传参需要小心处理。由于涉及32位到64位调用约定的转换,复杂参数的函数可能无法正确调用。例如,尝试调用 NtCreateFile 可能失败,而调用 NtQuerySystemTime(单个指针参数)则相对容易成功。

以下是演示代码的主要逻辑:

#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 };
    // 1. 正常调用一次32位的 NtQuerySystemTime
    NtQuerySystemTime(&systemTime);

    // 2. Hook 32位的 NtQuerySystemTime 并再次调用(会被钩子捕获)
    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);     // 3. 使用“天堂之门”技术调用64位的 NtQuerySystemTime(绕过32位的钩子)     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        // PLARGE_INTEGER     );     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; }
  • 程序执行后的输出结果类似下图,可以看到32位钩子生效了一次,而通过Wow64调用的64位函数则绕过了该钩子:

    程序运行结果输出截图

    后记与思考

    “天堂之门”技术听起来很厉害,但在现代安全对抗中,其直接的“免杀”或绕过效果已经相当有限。例如,它无法绕过内核层的钩子或ETW(Windows事件跟踪)等更底层的检测机制。同时,模式切换的代码本身具有比较明显的特征,容易被行为检测识别。

    不过,这项技术在逆向工程分析领域仍能制造一些麻烦,因为向32位程序中注入64位的Shellcode会导致反汇编工具解析错误,这确实能干扰分析人员的初步判断。

    如果你对Windows系统架构和底层编程感兴趣,可以通过文末的参考资料和项目代码进行更深入的研究。本文的完整项目结构如下,供复现参考:

    GATE项目目录结构截图

    主要构建文件内容如下:

    extern\minhook\CMakeLists.txt

    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

    cmake_minimum_required(VERSION 3.11)
    project(wow64pp LANGUAGES CXX)
    
    # 创建一个 interface 库
    add_library(${PROJECT_NAME} INTERFACE)
    
    # 添加 include 目录
    target_include_directories(${PROJECT_NAME} INTERFACE
        ${CMAKE_CURRENT_SOURCE_DIR}
    )

    CMakeLists.txt

    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
    )
    
    # Specify MSVC UTF-8 encoding
    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)

    引用与扩展阅读

    1. Microsoft Docs: WOW64 Implementation Details
    2. 看雪论坛相关讨论
    3. Rewolf's blog article on Wow64



    上一篇:OpenCode + Cloudflare Containers:打造个人云端开发环境
    下一篇:从MyBatis到xbatis:一个关于框架进化的技术猜想
    您需要登录后才可以回帖 登录 | 立即注册

    手机版|小黑屋|网站地图|云栈社区 ( 苏ICP备2022046150号-2 )

    GMT+8, 2026-2-5 01:59 , Processed in 0.280293 second(s), 40 queries , Gzip On.

    Powered by Discuz! X3.5

    © 2025-2026 云栈社区.

    快速回复 返回顶部 返回列表