zoukankan      html  css  js  c++  java
  • Windows WoW64浅析

    WOW64(Windows-On-Windows 64bit)是X64 Windows操作系统的一个子系统,为32位应用程序提供运行环境。类似的还有WOW32子系统,负责在32位Windows系统上运行16位应用程序。

    WoW64存在的原因还要从CPU的发展上开始说,X86指令集是一个指令集架构家族,最初在Intel 8086处理器中引入,开始它主要用于16位系统。从Intel 386处理器发布开始升级为32位,并且32位指令集一直保持了很久。了解32位系统的都知道32位CPU将内存空间限制到了4G(单一用户模式进程至少是这样)。随着RAM的越来越大,4G限制就成了瓶颈,系统无法使用更大的内存空间。于是2001年Intel发布了64位的IA64架构,它是一个全新的架构,架构设计非常清晰,比老的X86架构要更好。对于软件来说兼容性很重要,但是IA64处理器无法运行X86代码,这样问题就很严重了,已有的软件无法在新的CPU上运行。于是在2003年AMD发布了AMD64架构,它是对X86架构的增量更新,用于添加64位支持。这种架构的X64处理器可以执行X86代码,所以用户可以在X64处理器上运行现有的程序和操作系统。直到今天,X86和X64依旧是个人计算机和笔记本电脑使用CPU的主流。

    如下为X64和X86指令,与X86指令相对应,X64指令需要增加额外的前缀字节(REX Prefix)表示使用64位寄存器。由于指针等数据大小翻倍,所以结构体中指针偏移大小也可能会增加。

    1
    2
    3
    4
    5
    // X86指令
    0x8B, 0x52, 0x0C, // mov edx, dword ptr [edx+0Ch]

    // X64指令
    0x48, 0x8B, 0x52, 0x18, // mov rdx, qword ptr [rdx+18h]

    一些指令在X86和X64上编码一致,比如短跳转。区分两种指令比较通用的方法是看指令是否携带了REX前缀字节(REX前缀字节用于表示使用64位寄存器或使用64位操作数)。REX前缀字节会覆盖一部分现存X86指令,因此在执行一块代码时需要告知X64处理器按照X86还是X64来解析指令。到底是怎么告知CPU要将代码按照X86解析还是按照X64解析呢?下面看Intel的白皮书给出的关于IA-32e如何区分兼容模式和64位模式。


    图1.64位模式中的代码段描述符


    从图1中的文字可知,Intel的CPU是根据代码段描述符的CS.L字段来确定,代码段描述符的该位在X86架构上没有使用的,这里被用于区分IA-32e下的兼容模式和X64模式。如果CS.L=0且处于IA-32e模式,那么CPU当前执行在兼容模式;如果CS.L=0且处于IA-32e模式,那么CPU当前运行在X64模式中。从这里可以看出,如果将两个代码段分别设置为不同值,其实在Ring3也是可以进行这种CPU模式切换的,这也就是WoW64所使用的方法。

    Windows为了在X64系统上兼容32位程序设计了WoW64,用于在X64系统上执行32位应用程序。WoW64处理X64代码和X86代码之间的切换,并且为X86进程提供一个32位运行世界。由于X64是X86扩展,从32位代码向64位代码切换并不是那么困难,代码段描述符告诉处理器将代码当作X86代码还是X64代码,32位寄存器其实就是64位寄存器忽略高一半内容,32位模式RAM的4GB的编址和64位模式低4GB一样,因此从X86代码调用到X64代码,所有需要做的就是调用一个X64段选择子即完成了切换。

    ###WoW64简介###

    WoW64层处理处理器的32位和64位模式切换以及模拟32位系统的事务。WOW64在用户模式下实现,作为32位ntdll.dll和内核之间的转换层,从技术上说WOW64是通过三个DLL实现,Wow64.dll是Windows NT内核的入口转换模块,实现了Ntoskrnl.exe入口的桩函数,在32位和64位调用之间进行转换,包括指针和调用栈的操控等;Wow64win.dll为32位GUI应用程序提供合适的入口指针,即Win32k.sys入口桩函数;Wow64cpu.dll负责将处理器的32位和64位的模式之间转换。

    如下图1为典型的Wow64的实现原理图,上述的三个模块共同组成了WoW64模拟层:


    图2 Wow64原理图


    虽然从说法上看是在X64平台上提供了X86模拟环境,但实际上32位应用程序的执行并非模拟,而是由CPU直接解析执行,因此WoW64上的32位应用程序执行速度类似于32位Windows系统上的程序执行速度。当然这只是大致上来说,如果细究肯定不是完全一样。毕竟中间插入一层WoW64逻辑,相比于直接在32位系统上运行程序要多执行一部分代码,同时还包括X64和X86转换时的内存复制等操作,这些都会损耗性能,同时还会增加内存使用。

    下面按照如下的几点对WoW64进行简单的分析。

    1. 进程空间布局
    2. 系统调用
    3. 异常分发
    4. 用户APC分发
    5. 控制台支持
    6. 用户回调
    7. 注册表重定向
    8. 文件系统重定向

    ###进程空间布局###

    进程空间布局和32位系统上的进程类似,32位的系统模块会被映射到0x80000000以下的空间中,堆还是从低地址开始向上分布。因为WoW64进程是在X64进程基础上模拟X86环境,里面多出了前面说的三个模块wow64cpu.dllwow64.dllwow64win.dll,同时还包括X64的ntdll.dll。如上WoW64的简介中,三个模拟模块最终都要调用的64位的ntdll.dll中,因为它是进入内核入口。

    进程初始化

    WoW64进程本质上还是64位进程,在64位进程的基础上构造了32位运行环境,所以进程初始化时的第一个线程调用函数还是ntdll!LdrInitializeThunk,初始化安全Cookie后跳转到ntdll!LdrpInitialize()函数,紧接着会调用ntdll!LdrpInitializeProcess初始化进程中的内容,这个函数中会判断是否32位的EXE文件,即是否要加入WoW64层。如果是WoW64进程则加载wow64.dll,同时初始化ntdll.dll中的全局变量,如下所示:

    1
    2
    3
    4
    5
    0:000> x ntdll!Wow64*
    00000000`77482da8 ntdll!Wow64ApcRoutine = <no type information>
    00000000`77482f08 ntdll!Wow64PrepareForException = <no type information>
    00000000`77482e18 ntdll!Wow64Handle = <no type information>
    00000000`77482f10 ntdll!Wow64LdrpInitialize = <no type information>

    初始化WoW64层时,首先调用wow64!Wow64LdrpInitialize,函数中进一步调用wow64!ProcessInit进行进程初始化,初始化内容简单列举如下:

    1. 读取Wow64ExecuteFlags标记,设置进程的提交栈大小和最大栈值。
    2. 加载wow64log.dll初始化WoW64的日志模块。
    3. 初始化WoW64共享信息变量wow64!Wow64SharedInformation,并赋值全局变量Ntdll32KiUserExceptionDispatcherNtdll32KiUserApcDispatcher等用于WoW64的异常处理,APC分发,内核回调user32.dll模块等。
    4. 调用函数Wow64pInitializeFilePathRedirection设置文件路径重定向。
    5. 初始化ServiceTables全局变量,用于WoW64中系统调用时进行调用分发。
    6. 用EXE全路径调用CpuProcessInit()函数,修改X64与X86切换代码内容。

    wow64!ProcessInit函数中开始时对Wow64Info的部分内容进行初始化,Wow64Info的位置有如下的一个指向关系,可以用作想要调试的人参考资料。

    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
    0:000> dt 0x7efdb000 ntdll!_TEB		// X64的 TEB
    +0x000 NtTib : _NT_TIB
    +0x000 ExceptionList : 0x00000000`7efdd000 // X86 TEB指针
    +0x008 StackBase : 0x00000000`000bfd20 Void
    ...
    +0x1478 DeallocationStack : 0x00000000`001d0000 Void
    +0x1480 TlsSlots : [64] (null) // 0x14D0 保存了 WOW64_TLS_WOW64INFO
    ...
    +0x2030 lpPEB32 : // X86 PEB结构体地址
    ...
    +0x20C0 lpX86SwitchTo64BitMode // X86向X64切换的代码 地址

    0:000> dt 7efde000 ntdll32!_PEB // X86的 PEB,X64的TEB中的一项TLS指向PEB后的Wow64Info结构体
    +0x000 InheritedAddressSpace : 0 ''
    ...
    +0x23c pImageHeaderHash : (null)
    +0x240 TracingFlags : 0
    +0x248 Wow64Info ; Wow64Info 结构体

    0:000:x86> dt ntdll32!_TEB 0x7efdd000 // x86 TEB + 0xf70 偏移处 GdiBatchCount
    +0x000 NtTib : _NT_TIB
    ...
    +0xf6c WinSockData : (null)
    +0xf70 GdiBatchCount : 0x7efdb000
    +0xf74 CurrentIdealProcessor : _PROCESSOR_NUMBER
    ...
    1
    2
    3
    4
    5
    typedef struct _WOW64INFO {
    ULONG NativeSystemPageSize; // 模拟器所在本地系统的页面大小
    ULONG CpuFlags;
    WOW64_EXECUTE_OPTIONS Wow64ExecuteFlags;
    } WOW64INFO, *PWOW64INFO;

    读取注册表和加载wow64log.dll进行初始化这两部分很简单!接着就是调用wow64!Wow64GetSharedInformation函数初始化共享信息全局变量(wow64!Wow64SharedInformation)指向的是一个指针表,其成员内容如下所示,它们用于线程初始化,异常分发,APC分发等。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    wow64!Wow64SharedInformation
    0:000> dds 000000007747b120
    00000000`7747b120 775497a9 ntdll32!LdrInitializeThunk // wow64!Ntdll32LoaderInitRoutine
    00000000`7747b124 77520154 ntdll32!KiUserExceptionDispatcher // wow64!Ntdll32KiUserExceptionDispatcher
    00000000`7747b128 77520058 ntdll32!KiUserApcDispatcher // wow64!Ntdll32KiUserApcDispatcher
    00000000`7747b12c 7752010c ntdll32!KiUserCallbackDispatcher // wow64!Ntdll32KiUserCallbackDispatcher
    00000000`7747b130 775af694 ntdll32!LdrHotPatchRoutine
    00000000`7747b134 775427b1 ntdll32!ExpInterlockedPopEntrySListFault
    00000000`7747b138 7754277b ntdll32!ExpInterlockedPopEntrySListResume
    00000000`7747b13c 775427b3 ntdll32!ExpInterlockedPopEntrySListEnd
    00000000`7747b140 775201e4 ntdll32!RtlUserThreadStart
    00000000`7747b144 775b38a0 ntdll32!RtlpQueryProcessDebugInformationRemote
    00000000`7747b148 7756a02d ntdll32!EtwpNotificationThread
    00000000`7747b14c 77510000 ntdll32!`string' <PERF> (ntdll32+0x0) // wow64!NtDll32Base

    调用wow64!Map64BitDlls将X64的一些系统DLL,比如kernel32.dll所占用的基地址分配掉,防止32位相同名字DLL分配到这里?初始化全局变量wow64!ServiceTables,其中包含了Nt类函数分发表,控制台con类函数分发表,Win32GUI类函数分发表和Csr类函数分发表。获取Image名字,然后调用wow64cpu!CpuProcessInit函数判断X86和X64代码切换部分是否完整,不完整进行修改;CpuProcessInit()函数设置了TEB32+0xC0处为X86到X64转换代码指针。

    接下来调用wow64cpu!CpuGetContext函数获取X86开始执行时的CONTEXT内容(打印一个结构内容如下),然后获取起始运行地址,并将地址转化为32位地址。

    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
    0:000> dt 0x00000014e5f0 ntdll32!_Context
    +0x000 ContextFlags : 0x1002f
    +0x004 Dr0 : 0
    +0x008 Dr1 : 0x74d95cd0
    +0x00c Dr2 : 0
    +0x010 Dr3 : 0x74d95388
    +0x014 Dr6 : 0
    +0x018 Dr7 : 0x7737d96e
    +0x01c FloatSave : _FLOATING_SAVE_AREA
    +0x08c SegGs : 0x2b
    +0x090 SegFs : 0x53
    +0x094 SegEs : 0x2b
    +0x098 SegDs : 0x2b
    +0x09c Edi : 0
    +0x0a0 Esi : 0
    +0x0a4 Ebx : 0x7efde000
    +0x0a8 Edx : 0
    +0x0ac Ecx : 0
    +0x0b0 Eax : 0xfbfcdd
    +0x0b4 Ebp : 0
    +0x0b8 Eip : 0x775201e4
    +0x0bc SegCs : 0x23
    +0x0c0 EFlags : 0x202
    +0x0c4 Esp : 0x30fa10
    +0x0c8 SegSs : 0x2b
    +0x0cc ExtendedRegisters : [512] "???"

    wow64!Wow64SetupInitialCall函数中将X64栈上的X86的CONTEXT复制到32位栈上,其中先调用CpuGetStackPointer从TEB64的WOW64_TLS_CPURESERVEDTLS项中保存的32位CPU信息,CPU信息结构中0xC8偏移处保存的32位栈帧信息,栈帧减去0x2CC(CONTEXT结构体大小),然后再减去16字节,将之前获取的CONTEXT内容复制过去,然后再将CONTEXT在栈上指针和ntdll32.dll句柄放到多申请的16字节中,调用CpuSetStackPointer设置回TEB64的TLS的CPU保留信息项中。接着调用wow64cpu!CpuSetInstructionPointerNtdll32LoaderInitRoutine全局变量保存信息(ntdll32!RtlUserThreadStart)设置到CPU保留信息的0xBC偏移处,即CONTEXTEip字段。

    调用wow64!RunCpuSimulation()进入CPU模拟,它调用wow64cpu!CpuSimulate()开始X86环境的模拟。在wow64cpu!CpuSimulate函数中,首先保存X64环境的寄存器到64位栈上,然后将r14设置为X64栈信息,r12寄存器设置为64位TEB地址,r15设置为快速(Turbo)系统调用表基地址,r13设置为X86的CPU保留信息结构体基地址。

    查看X86环境块CONTEXT中是否用了浮点寄存器,如果使用了则将对应信息恢复到浮点寄存器中,并返回执行;否则是普通计算,则恢复各个寄存器内容,执行远跳转指令jmp fword ptr [r14] ds:00000000001deb40=0023:772d97a9跳转到32位开始执行,可反汇编查看32位开始执行的函数为ntdll32!LdrInitializeThunk`,即X86进程的进程初始化函数入口。

    ###系统调用###

    对于应用程序来说比较重要的一部分就是系统调用,它们为应用程序正常运行提供了基础服务。无论X86还是X64的进程,如果能在Ring3层完成功能,则不需要进行系统调用,这个不存在和WoW64交互,直接加载32位模块调用函数即可,比如StringCchCopy等函数;再一种就是必须系统支持,比如读取文件内容kernel32!CreateFileW(),我们知道这些函数调用都会会进入ntdll.dll中,最终进入到内核。这种需要进入内核的调用是需要WoW64处理的内容。下面以kernel32!CreateFileW为例看WoW64如何处理系统调用。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    int wmain(int argc, WCHAR* argv[])
    {
    WCHAR szFilePath[MAX_PATH + 1] = L"C:UsersAdministratorDesktopTestDebugTest.pdb";
    HANDLE hFile = CreateFileW(szFilePath, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, 0, NULL);
    if (hFile == INVALID_HANDLE_VALUE)
    {
    printf("Open File Error!n");
    }

    return 0;
    }

    首先设置断点如下,要X64和X86的ntdll.dll都设置上断点,看一下最终调用到系统内核之前的所有调用栈。

    1
    2
    3
    4
    0:000:x86> bl
    0 e x86 011aa5d0 0001 (0001) 0:**** Test!wmain
    1 e 76d79df0 0001 (0001) 0:**** ntdll!ZwCreateFile
    2 e x86 76ef00e4 0001 (0001) 0:**** ntdll32!ZwCreateFile

    首先进入到X86的ntdll.dll中的ntdll32!ZwCreateFile函数,如果是在X86系统上,那么这个函数内部就会执行中断或快速系统调用进入内核,但是在WoW64上显然这里是无法进入内核的。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    0:000:x86> k
    ChildEBP RetAddr
    0028f86c 769fc76b ntdll32!ZwCreateFile
    0028f910 764d40ae KERNELBASE!CreateFileW+0x35e
    0028f93c 011aa640 kernel32!CreateFileWImplementation+0x69
    0028fc50 011ff506 Test!wmain+0x70 [c:usersadministratordesktoptesttesttest.cpp @ 146]
    0028fc9c 011ff3df Test!__tmainCRTStartup+0x116 [f:ddvctoolscrt_bldself_x86crtsrccrt0.c @ 266]
    0028fca4 764d343d Test!wmainCRTStartup+0xf [f:ddvctoolscrt_bldself_x86crtsrccrt0.c @ 182]
    0028fcb0 76f09832 kernel32!BaseThreadInitThunk+0xe
    0028fcf0 76f09805 ntdll32!__RtlUserThreadStart+0x70
    0028fd08 00000000 ntdll32!_RtlUserThreadStart+0x1b

    反汇编看一下ntdll32!ZwCreateFile函数的内容,可以发现在ecx寄存器中存入系统调用号,edx保存了栈指针,然后调用的是fs:[0C0h]

    1
    2
    3
    4
    5
    6
    7
    ntdll32!ZwCreateFile:
    76ef00e4 b852000000 mov eax,52h
    76ef00e9 33c9 xor ecx,ecx
    76ef00eb 8d542404 lea edx,[esp+4]
    76ef00ef 64ff15c0000000 call dword ptr fs:[0C0h]
    76ef00f6 83c404 add esp,4
    76ef00f9 c22c00 ret 2Ch

    我们知道X86系统上fs寄存器中其实保存的是TEB的基地址,fs:[0C0h]即TEB中0xC0偏移处的内容,如下:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    0:000:x86> dt 0x7efdd000 ntdll32!_TEB
    +0x000 NtTib : _NT_TIB
    ......
    +0x044 User32Reserved : [26] 0
    +0x0ac UserReserved : [5] 0
    +0x0c0 WOW32Reserved : 0x746b2320 Void
    +0x0c4 CurrentLocale : 0x804
    ......
    +0xf6c WinSockData : (null)
    +0xf70 GdiBatchCount : 0x7efdb000
    +0xf74 CurrentIdealProcessor : _PROCESSOR_NUMBER
    ......

    注意这里是X86的TEB,在0xC0偏移处为WOW32Reserved字段,名字就可知它与WoW64相关的内容,反汇编其内容,如下代码。

    1
    2
    wow64cpu!X86SwitchTo64BitMode:
    746b2320 ea1e276b743300 jmp 0033:746B271E

    在上一节中看到过这个全局变量,在wow64cpu!CpuProcessInit中判断,如果没有进行初始化,则会将这块代码进行初始化。它的代码很简单,远跳转到0033:746B271E,根据CPU那块知识可知这是在将CPU从IA32e的兼容模式向64位切换。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    0:000> dg 0x23
    P Si Gr Pr Lo
    Sel Base Limit Type l ze an es ng Flags
    ---- ----------------- ----------------- ---------- - -- -- -- -- --------
    0023 00000000`00000000 00000000`ffffffff Code RE Ac 3 Bg Pg P Nl 00000cfb

    1: kd> dg 0x33
    P Si Gr Pr Lo
    Sel Base Limit Type l ze an es ng Flags
    ---- ----------------- ----------------- ---------- - -- -- -- -- --------
    0033 00000000`00000000 00000000`00000000 Code RE Ac 3 Nb By P Lo 000002fb

    其中Sel表示选择子,Base和Limit分别是基地址和边界,Type是段的类型,RE代表只读和可执行,Ac表示被访问过,Pl为3是ring3特权级,Bg(Big)表示为32位代码,Gran表示粒度,Pg意味着粒度的单位是内存而(4KB) , Pres代表Present即这个段是否在内存中,Long下的N1表示Not Long,意味着这不是64位代码,Lo表示这是64位代码。

    切换到X64模式后,执行的内容如下(从jmp后面的地址可以看到是这里的代码)。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    wow64cpu!CpupReturnFromSimulatedCode:
    00000000`746b271e 67448b0424 mov r8d,dword ptr [esp] ds:00000000`0028f86c=76ef00f6
    00000000`746b2723 458985bc000000 mov dword ptr [r13+0BCh],r8d
    00000000`746b272a 4189a5c8000000 mov dword ptr [r13+0C8h],esp
    00000000`746b2731 498ba42480140000 mov rsp,qword ptr [r12+1480h]
    00000000`746b2739 4983a4248014000000 and qword ptr [r12+1480h],0
    00000000`746b2742 448bda mov r11d,edx
    wow64cpu!TurboDispatchJumpAddressStart:
    00000000`746b2745 41ff24cf jmp qword ptr [r15+rcx*8]

    在上一节中的WoW64环境初始化中知道,r13寄存器指向CPU保留信息,其实是一个指针加上CONTEXT,将返回地址放到CONTEXTEIP字段,esp寄存器放到ESP字段;r12寄存器指向X64环境中的TEB指针,其中0x1480偏移为TLS指针数组,这个偏移处为WOW64_TLS_STACKPTR64,即保存了上次离开X64环境时X64栈的栈顶。这里将栈恢复到rsp寄存器中,继续CpuSimulate函数中跳到X86时的状态继续执行。清空WOW64_TLS_STACKPTR64TLS内容,保存X86的栈指针(edx保存)到r11

    接下来是一个跳转,这个跳转用于选择系统调用方式,WoW64提供了一种快速(Turbo)方式,即X86栈上已经形成了可以进行X64系统调用的形式,根据ecx值确定系统调用参数个数和方式,可以直接用X86栈上内容进行系统调用。这种分发所用的表如下所示,不同的值表示不同的调用方式。

    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
    0:000> dqs @r15
    00000000`746b2450 00000000`746b2749 wow64cpu!TurboDispatchJumpAddressEnd
    00000000`746b2458 00000000`746b2dba wow64cpu!Thunk0Arg
    00000000`746b2460 00000000`746b2bce wow64cpu!Thunk0ArgReloadState
    00000000`746b2468 00000000`746b2d6a wow64cpu!Thunk1ArgSp
    00000000`746b2470 00000000`746b2d7b wow64cpu!Thunk1ArgNSp
    00000000`746b2478 00000000`746b2d77 wow64cpu!Thunk2ArgNSpNSp
    00000000`746b2480 00000000`746b2c8a wow64cpu!Thunk2ArgNSpNSpReloadState
    00000000`746b2488 00000000`746b2d84 wow64cpu!Thunk2ArgSpNSp
    00000000`746b2490 00000000`746b2d66 wow64cpu!Thunk2ArgSpSp
    00000000`746b2498 00000000`746b2d55 wow64cpu!Thunk2ArgNSpSp
    00000000`746b24a0 00000000`746b2d73 wow64cpu!Thunk3ArgNSpNSpNSp
    00000000`746b24a8 00000000`746b2d62 wow64cpu!Thunk3ArgSpSpSp
    00000000`746b24b0 00000000`746b2d8d wow64cpu!Thunk3ArgSpNSpNSp
    00000000`746b24b8 00000000`746b2bc3 wow64cpu!Thunk3ArgSpNSpNSpReloadState
    00000000`746b24c0 00000000`746b2d9e wow64cpu!Thunk3ArgSpSpNSp
    00000000`746b24c8 00000000`746b2d51 wow64cpu!Thunk3ArgNSpSpNSp
    00000000`746b24d0 00000000`746b2d80 wow64cpu!Thunk3ArgSpNSpSp
    00000000`746b24d8 00000000`746b2d6f wow64cpu!Thunk4ArgNSpNSpNSpNSp
    00000000`746b24e0 00000000`746b2d9a wow64cpu!Thunk4ArgSpSpNSpNSp
    00000000`746b24e8 00000000`746b2af4 wow64cpu!Thunk4ArgSpSpNSpNSpReloadState
    00000000`746b24f0 00000000`746b2dab wow64cpu!Thunk4ArgSpNSpNSpNSp
    00000000`746b24f8 00000000`746b2bbf wow64cpu!Thunk4ArgSpNSpNSpNSpReloadState
    00000000`746b2500 00000000`746b2d4d wow64cpu!Thunk4ArgNSpSpNSpNSp
    00000000`746b2508 00000000`746b2d5e wow64cpu!Thunk4ArgSpSpSpNSp
    00000000`746b2510 00000000`746b27be wow64cpu!QuerySystemTime
    00000000`746b2518 00000000`746b2783 wow64cpu!GetCurrentProcessorNumber
    00000000`746b2520 00000000`746b2991 wow64cpu!ReadWriteFile
    00000000`746b2528 00000000`746b28d7 wow64cpu!DeviceIoctlFile
    00000000`746b2530 00000000`746b2a42 wow64cpu!RemoveIoCompletion
    00000000`746b2538 00000000`746b27fc wow64cpu!WaitForMultipleObjects
    00000000`746b2540 00000000`746b2803 wow64cpu!WaitForMultipleObjects32
    00000000`746b2548 00000000`746b2782 wow64cpu!ThunkNone

    如下ntdll32!ZwDelayExecution函数就是用了这种调用方式,它将ecx寄存器设置为6,在进入WoW64后实际调用的函数为wow64cpu!Thunk2ArgNSpNSpReloadState,在该函数中就调用了wow64cpu!CpupSyscallStub直接进入内核。

    1
    2
    3
    4
    5
    6
    7
    8
    0:000:x86> u ntdll32!ZwDelayExecution
    ntdll32!ZwDelayExecution:
    7743fdac b831000000 mov eax,31h
    7743fdb1 b906000000 mov ecx,6
    7743fdb6 8d542404 lea edx,[esp+4]
    7743fdba 64ff15c0000000 call dword ptr fs:[0C0h]
    7743fdc1 83c404 add esp,4
    7743fdc4 c20800 ret 8

    这里看一下另一个用到系统调用的模块,即User32.dll,以user32!CreateWindowExW为例,调用到系统调用层时汇编如下所示,ecx寄存器内容为0,而调用号为0x1076

    1
    2
    3
    4
    5
    6
    7
    8
    0:000:x86> u 75b1a950
    USER32!NtUserCreateWindowEx:
    75b1a950 b876100000 mov eax,1076h
    75b1a955 b900000000 mov ecx,0
    75b1a95a 8d542404 lea edx,[esp+4]
    75b1a95e 64ff15c0000000 call dword ptr fs:[0C0h]
    75b1a965 83c404 add esp,4
    75b1a968 c23c00 ret 3Ch

    但是对于ntdll32.dll转过来的系统调用都不走这种快速方式,而是需要在WoW64中进行参数的调整,然后再进行调用。ecx值为0,这里直接跳转到如下的内容上继续执行。

    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
    wow64cpu!TurboDispatchJumpAddressEnd:
    00000000`746b2749 4189b5a4000000 mov dword ptr [r13+0A4h],esi
    00000000`746b2750 4189bda0000000 mov dword ptr [r13+0A0h],edi
    00000000`746b2757 41899da8000000 mov dword ptr [r13+0A8h],ebx
    00000000`746b275e 4189adb8000000 mov dword ptr [r13+0B8h],ebp
    00000000`746b2765 9c pushfq
    00000000`746b2766 5b pop rbx
    00000000`746b2767 41899dc4000000 mov dword ptr [r13+0C4h],ebx
    00000000`746b276e 8bc8 mov ecx,eax
    00000000`746b2770 ff150ae9ffff call qword ptr [wow64cpu!_imp_Wow64SystemServiceEx
    00000000`746b2776 418985b4000000 mov dword ptr [r13+0B4h],eax
    00000000`746b277d e98ffeffff jmp wow64cpu!CpuSimulate+0x61 (00000000`746b2611)

    //////////////////////////////////////////////////////////////
    0:000> dt ntdll32!_CONTEXT 00000000001dfd24 // CPUReserved
    -0x004 XXX
    +0x000 ContextFlags : 0x1002f
    +0x004 Dr0 : 0
    +0x008 Dr1 : 0
    +0x00c Dr2 : 0
    +0x010 Dr3 : 0
    +0x014 Dr6 : 0
    +0x018 Dr7 : 0
    +0x01c FloatSave : _FLOATING_SAVE_AREA
    +0x08c SegGs : 0x2b
    +0x090 SegFs : 0x53
    +0x094 SegEs : 0x2b
    +0x098 SegDs : 0x2b
    +0x09c Edi : 0
    +0x0a0 Esi : 0
    +0x0a4 Ebx : 0x7efde000
    +0x0a8 Edx : 0
    +0x0ac Ecx : 0
    +0x0b0 Eax : 0
    +0x0b4 Ebp : 0x38fb3c
    +0x0b8 Eip : 0x772c00f6
    +0x0bc SegCs : 0x23
    +0x0c0 EFlags : 0x246
    +0x0c4 Esp : 0x38f7f8
    +0x0c8 SegSs : 0x2b
    +0x0cc ExtendedRegisters : [512] "???"

    继续执行过程中,将X86转过来时寄存器的内容依次放到r13指向的CPU保留信息(包含的CONTEXT)中。保存完CPU信息后,调用wow64!Wow64SystemServiceEx函数进行函数调用分发。

    在该函数中,根据系统调用号,获取系统调用类别(四类,如下wow64.dll全局变量所保存内容,初始化时对该变量进行过初始化。),将系统调用号右移12位,然后与上3,该值用于选取wow64!ServiceTables中的四类之一。从上面ntdll32.dll过来的为第一类,而user32.dll过来的调用为第二类。

    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
    wow64!ServiceTables
    0:000> dqs 7475aa00
    00000000`7475aa00 00000000`747592a0 wow64!sdwhnt32JumpTable
    00000000`7475aa08 00000000`00000000
    00000000`7475aa10 00000000`000003e8
    00000000`7475aa18 00000000`74759fc0 wow64!sdwhnt32Number
    00000000`7475aa20 00000000`00000000
    00000000`7475aa28 00000000`00000000

    00000000`7475aa30 00000000`7470fae0 wow64win!sdwhwin32JumpTable
    00000000`7475aa38 00000000`00000000
    00000000`7475aa40 00000000`000003e8
    00000000`7475aa48 00000000`747114b0 wow64win!sdwhwin32Number
    00000000`7475aa50 00000000`00000000
    00000000`7475aa58 00000000`7470e110 wow64win!sdwhwin32ErrorCase

    00000000`7475aa60 00000000`74711b40 wow64win!sdwhconJumpTable
    00000000`7475aa68 00000000`00000000
    00000000`7475aa70 00000000`000003e8
    00000000`7475aa78 00000000`74711e60 wow64win!sdwhconNumber
    00000000`7475aa80 00000000`00000000
    00000000`7475aa88 00000000`74711820 wow64win!sdwhconErrorCase

    00000000`7475aa90 00000000`747591e0 wow64!sdwhbaseJumpTable
    00000000`7475aa98 00000000`00000000
    00000000`7475aaa0 00000000`000003e8
    00000000`7475aaa8 00000000`74759258 wow64!sdwhbaseNumber
    00000000`7475aab0 00000000`00000000
    00000000`7475aab8 00000000`74759160 wow64!sdwhbaseErrorCase

    这里对于ntdll32.dll调用过来的,会查找wow64!sdwhnt32JumpTable表,我们这里查找到的函数就是wow64!whNtCreateFile了,进入该函数中,将X86上的参数整合到X64栈上,然后调用ntdll!ZwCreateFile,这时的调用栈如下。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    0:000> !wow64exts.k
    Walking 64bit Stack...
    Child-SP RetAddr Call Site
    00000000`0016e028 00000000`7473c25b ntdll!ZwCreateFile
    00000000`0016e030 00000000`7472d18f wow64!whNtCreateFile+0x10f
    00000000`0016e100 00000000`746b2776 wow64!Wow64SystemServiceEx+0xd7
    00000000`0016e9c0 00000000`7472d286 wow64cpu!TurboDispatchJumpAddressEnd+0x2d
    00000000`0016ea80 00000000`7472c69e wow64!RunCpuSimulation+0xa
    00000000`0016ead0 00000000`76d44223 wow64!Wow64LdrpInitialize+0x42a
    00000000`0016f020 00000000`76da9a60 ntdll!LdrpInitializeProcess+0x17e3
    00000000`0016f510 00000000`76d5374e ntdll! ?? ::FNODOBFM::`string'+0x22a50
    00000000`0016f580 00000000`00000000 ntdll!LdrInitializeThunk+0xe

    Walking 32bit Stack...
    ChildEBP RetAddr
    0028f86c 769fc76b ntdll32!NtCreateFile+0x12
    0028f910 764d40ae KERNELBASE!CreateFileW+0x35e
    0028f93c 011aa640 kernel32!CreateFileWImplementation+0x69
    0028fc50 011ff506 Test!wmain+0x70 [c:usersadministratordesktoptesttesttest.cpp @ 146]
    0028fc9c 011ff3df Test!__tmainCRTStartup+0x116 [f:ddvctoolscrt_bldself_x86crtsrccrt0.c @ 266]
    0028fca4 764d343d Test!wmainCRTStartup+0xf [f:ddvctoolscrt_bldself_x86crtsrccrt0.c @ 182]
    0028fcb0 76f09832 kernel32!BaseThreadInitThunk+0xe
    0028fcf0 76f09805 ntdll32!__RtlUserThreadStart+0x70
    0028fd08 00000000 ntdll32!_RtlUserThreadStart+0x1b

    等到ntdll!ZwCreateFilentdll!ZwCreateFile依次返回后,保存系统调用返回结果(一般是错误值,放到CONTEXT的EAX字段),然后继续跳转,wow64cpu!CpuSimulate+0x61这块内容很熟悉了,即初始化时会经过此处,判断是否在协处理器上运行,如果是则恢复浮点寄存器,否则恢复X86环境中的寄存器内容(之前进入X64时已经保存),进而跳转到32位代码环境中继续执行。

    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
    00000000`746b2611 4183a5d002000001 and     dword ptr [r13+2D0h],1 ds:00000000`0016fff0=00000000
    00000000`746b2619 0f84af000000 je wow64cpu!CpuSimulate+0x11e (00000000`746b26ce)
    00000000`746b261f 410f288570010000 movaps xmm0,xmmword ptr [r13+170h]
    00000000`746b2627 410f288d80010000 movaps xmm1,xmmword ptr [r13+180h]
    00000000`746b262f 410f289590010000 movaps xmm2,xmmword ptr [r13+190h]
    00000000`746b2637 410f289da0010000 movaps xmm3,xmmword ptr [r13+1A0h]
    00000000`746b263f 410f28a5b0010000 movaps xmm4,xmmword ptr [r13+1B0h]
    00000000`746b2647 410f28adc0010000 movaps xmm5,xmmword ptr [r13+1C0h]
    00000000`746b264f 418b8db0000000 mov ecx,dword ptr [r13+0B0h]
    00000000`746b2656 418b95ac000000 mov edx,dword ptr [r13+0ACh]
    00000000`746b265d 4183a5d0020000fe and dword ptr [r13+2D0h],0FFFFFFFEh
    00000000`746b2665 418bbda0000000 mov edi,dword ptr [r13+0A0h]
    00000000`746b266c 418bb5a4000000 mov esi,dword ptr [r13+0A4h]
    00000000`746b2673 418b9da8000000 mov ebx,dword ptr [r13+0A8h]
    00000000`746b267a 418badb8000000 mov ebp,dword ptr [r13+0B8h]
    00000000`746b2681 418b85b4000000 mov eax,dword ptr [r13+0B4h]
    00000000`746b2688 4989a42480140000 mov qword ptr [r12+1480h],rsp
    00000000`746b2690 66c74424082300 mov word ptr [rsp+8],23h
    00000000`746b2697 66c74424202b00 mov word ptr [rsp+20h],2Bh
    00000000`746b269e 458b85c4000000 mov r8d,dword ptr [r13+0C4h]
    00000000`746b26a5 4181a5c4000000fffeffff and dword ptr [r13+0C4h],0FFFFFEFFh
    00000000`746b26b0 4489442410 mov dword ptr [rsp+10h],r8d
    00000000`746b26b5 458b85c8000000 mov r8d,dword ptr [r13+0C8h]
    00000000`746b26bc 4c89442418 mov qword ptr [rsp+18h],r8
    00000000`746b26c1 458b85bc000000 mov r8d,dword ptr [r13+0BCh]
    00000000`746b26c8 4c890424 mov qword ptr [rsp],r8
    00000000`746b26cc 48cf iretq

    // 如下恢复X86环境中的寄存器内容
    00000000`746b26ce 418bbda0000000 mov edi,dword ptr [r13+0A0h]
    00000000`746b26d5 418bb5a4000000 mov esi,dword ptr [r13+0A4h]
    00000000`746b26dc 418b9da8000000 mov ebx,dword ptr [r13+0A8h]
    00000000`746b26e3 418badb8000000 mov ebp,dword ptr [r13+0B8h]
    00000000`746b26ea 418b85b4000000 mov eax,dword ptr [r13+0B4h]
    00000000`746b26f1 4989a42480140000 mov qword ptr [r12+1480h],rsp
    00000000`746b26f9 41c7460423000000 mov dword ptr [r14+4],23h
    00000000`746b2701 41b82b000000 mov r8d,2Bh
    00000000`746b2707 418ed0 mov ss,r8w
    00000000`746b270a 418ba5c8000000 mov esp,dword ptr [r13+0C8h]
    00000000`746b2711 458b8dbc000000 mov r9d,dword ptr [r13+0BCh]
    00000000`746b2718 45890e mov dword ptr [r14],r9d
    00000000`746b271b 41ff2e jmp fword ptr [r14] ds:00000000`0016ea30=002376ef00f6

    从这里就回到了X86中ntdll32!ZwCreateFile中继续执行。

    补充

    当然了X64调试器是提供了扩展模块专门用来调试WoW64进程,即wow64exts.dll,在Windbg中可以使用.load wow64exts命令来加载该扩展。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    0:000:x86> !wow64exts.help

    Wow64 debugger extension commands:

    sw: Switch between 32-bit and 64-bit mode
    k <count>: Combined 32/64 stack trace
    info: Dumps information about some important wow64 structures
    r [addr]: Dumps x86 CONTEXT
    lf: Dump/Set log flags
    l2f: Enable logging to file

    比如!wow64exts.info命令执行后列举出了X86和X64的基础信息,包括PEB,TEB,堆栈信息以及TEB64中的TLS内容。

    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
    0:000:x86> !wow64exts.info
    PEB32: 0x7efde000
    PEB64: 0x7efdf000

    Wow64 information for current thread:

    TEB32: 0x7efdd000
    TEB64: 0x7efdb000

    32 bit, StackBase : 0x290000
    StackLimit : 0x28d000
    Deallocation: 0x190000

    64 bit, StackBase : 0x16fd20
    StackLimit : 0x16c000
    Deallocation: 0x130000

    Wow64 TLS slots:
    WOW64_TLS_STACKPTR64: 0x000000000016e9c0 // X64环境转向X86时的栈
    WOW64_TLS_CPURESERVED: 0x000000000016fd20 // CPU保留信息,在0x4偏移处为X86的CONTEXT
    WOW64_TLS_INCPUSIMULATION: 0x0000000000000000
    WOW64_TLS_LOCALTHREADHEAP: 0x0000000000000000
    WOW64_TLS_EXCEPTIONADDR: 0x0000000000000000
    WOW64_TLS_USERCALLBACKDATA: 0x0000000000000000
    WOW64_TLS_EXTENDED_FLOAT: 0x0000000000000000
    WOW64_TLS_APCLIST: 0x0000000000000000
    WOW64_TLS_FILESYSREDIR: 0x0000000000000000
    WOW64_TLS_LASTWOWCALL: 0x0000000000000000
    WOW64_TLS_WOW64INFO: 0x000000007efde248 // WoW64Info 结构体指针,在PEB32末尾

    ###异常分发###

    从前面知道X86环境是WoW64子系统”模拟”出来的,所以WoW64就相当于X86环境的内核,所以对于异常分发来说从内核通过调用ntdll!KiUserExceptionDispatcher函数将异常分发到Ring3尝试找异常处理函数,我们关系的是WoW64对于异常的处理方式,这里只需要看一下从X64内核将异常转到ntdll32!KiUserExceptionDispatcher这一段即可。这里有从网上找来的一段关于WoW64处理异常的论述:

    WoW64通过ntdll.dllKiUserExceptionDispatcher勾住了异常分发过程。无论何时当64位内核将要给一个WoW64进程分发一个异常时,Wow64会捕获住原生的异常以及用户模式下的环境记录(context record),然后准备一个32位异常和环境记录,并且按照原生32位内核所做的那样将他分发出去。

    ntdll!KiUserExceptionDispatcher函数开始看一X64的Ring3异常分发到WoW64的异常分发这个过程,它的汇编代码如下,首先判断全局变量ntdll!Wow64PrepareForException是否赋值,它其实是在加载wow64.dll模块时将该模块同名导出函数赋值给该变量了。

    如果调试的话可以注意到,其实这时代码执行处于X86的栈上,所以要将X86栈上的数据放到X64栈上,并将栈切换到X64栈上去,通过调用ntdll!Wow64PrepareForException来完成这个工作。在ntdll!Wow64PrepareForException函数中会调用wow64!CpuResetToConsistentState,它主要是将异常信息机构复制到wow64cpu!RecoverException64变量中,将异常对应的上下文结果复制到wow64cpu!RecoverContext64,然后切换栈,转向使用64位代码的专用栈。

    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
    ntdll!KiUserExceptionDispatcher:
    00000000`772cb610 fc cld
    00000000`772cb611 488b05f0780c00 mov rax,qword ptr [ntdll!Wow64PrepareForException]
    00000000`772cb618 4885c0 test rax,rax
    00000000`772cb61b 740f je ntdll!KiUserExceptionDispatcher+0x1c (00000000`772cb62c)
    00000000`772cb61d 488bcc mov rcx,rsp
    00000000`772cb620 4881c1f0040000 add rcx,4F0h
    00000000`772cb627 488bd4 mov rdx,rsp
    00000000`772cb62a ffd0 call rax
    00000000`772cb62c 488bcc mov rcx,rsp
    00000000`772cb62f 4881c1f0040000 add rcx,4F0h
    00000000`772cb636 488bd4 mov rdx,rsp
    00000000`772cb639 e8324afdff call ntdll!RtlDispatchException (00000000`772a0070)
    00000000`772cb63e 84c0 test al,al
    00000000`772cb640 740c je ntdll!KiUserExceptionDispatcher+0x3e (00000000`772cb64e)
    00000000`772cb642 488bcc mov rcx,rsp
    00000000`772cb645 33d2 xor edx,edx
    00000000`772cb647 e8c4010000 call ntdll!RtlRestoreContext (00000000`772cb810)
    00000000`772cb64c eb15 jmp ntdll!KiUserExceptionDispatcher+0x53 (00000000`772cb663)
    00000000`772cb64e 488bcc mov rcx,rsp
    00000000`772cb651 4881c1f0040000 add rcx,4F0h
    00000000`772cb658 488bd4 mov rdx,rsp
    00000000`772cb65b 4532c0 xor r8b,r8b
    00000000`772cb65e e85df5ffff call ntdll!NtRaiseException (00000000`772cabc0)
    00000000`772cb663 8bc8 mov ecx,eax
    00000000`772cb665 e886d30500 call ntdll!RtlRaiseStatus (00000000`773289f0)

    紧接着就会调用ntdll!RtlDispatchException函数,这个函数完成异常分发,它的两个参数就是从X86栈上拷贝过来的数据,异常记录和环境结构体,如下所示:

    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
    0:000> dt ntdll!_EXCEPTION_RECORD   00000000003af7f0
    +0x000 ExceptionCode : 0n-1073741819 // 0x00000000c0000005
    +0x004 ExceptionFlags : 0
    +0x008 ExceptionRecord : (null)
    +0x010 ExceptionAddress : 0x00000000`00f6a629 Void
    +0x018 NumberParameters : 2
    +0x020 ExceptionInformation : [15] 1

    0:000> dt ntdll!_CONTEXT 00000000003af300
    +0x000 P1Home : 0x7745304d`50000063
    +0x008 P2Home : 0x77597390
    +0x010 P3Home : 0x151ab0`00000000
    +0x018 P4Home : 0x150000`40000062
    +0x020 P5Home : 0x3af404
    +0x028 P6Home : 0x1000000
    +0x030 ContextFlags : 0x10005f
    +0x034 MxCsr : 0x1f80
    +0x038 SegCs : 0x23
    +0x03a SegDs : 0x2b
    +0x03c SegEs : 0x2b
    +0x03e SegFs : 0x53
    +0x040 SegGs : 0x2b
    +0x042 SegSs : 0x2b
    +0x044 EFlags : 0x10286
    +0x048 Dr0 : 0
    +0x050 Dr1 : 0
    +0x058 Dr2 : 0
    +0x060 Dr3 : 0
    +0x068 Dr6 : 0
    +0x070 Dr7 : 0
    +0x078 Rax : 0
    +0x080 Rcx : 0
    +0x088 Rdx : 0x153058
    +0x090 Rbx : 0x7efde000
    +0x098 Rsp : 0x3afa38
    +0x0a0 Rbp : 0x3afb2c
    +0x0a8 Rsi : 0
    +0x0b0 Rdi : 0x3afb14
    +0x0b8 R8 : 0x2b
    +0x0c0 R9 : 0x7743fb1a
    +0x0c8 R10 : 0
    +0x0d0 R11 : 0x246
    +0x0d8 R12 : 0x7efdb000
    +0x0e0 R13 : 0x23fd20
    +0x0e8 R14 : 0x23ea60
    +0x0f0 R15 : 0x748c2450
    +0x0f8 Rip : 0xf6a629
    +0x100 FltSave : _XSAVE_FORMAT
    +0x100 Header : [2] _M128A
    +0x120 Legacy : [8] _M128A
    +0x1a0 Xmm0 : _M128A
    +0x1b0 Xmm1 : _M128A
    +0x1c0 Xmm2 : _M128A
    +0x1d0 Xmm3 : _M128A
    +0x1e0 Xmm4 : _M128A
    +0x1f0 Xmm5 : _M128A
    +0x200 Xmm6 : _M128A
    +0x210 Xmm7 : _M128A
    +0x220 Xmm8 : _M128A
    +0x230 Xmm9 : _M128A
    +0x240 Xmm10 : _M128A
    +0x250 Xmm11 : _M128A
    +0x260 Xmm12 : _M128A
    +0x270 Xmm13 : _M128A
    +0x280 Xmm14 : _M128A
    +0x290 Xmm15 : _M128A
    +0x300 VectorRegister : [26] _M128A
    +0x4a0 VectorControl : 0x630150`00000000
    +0x4a8 DebugControl : 0x77494dcd`00630150
    +0x4b0 LastBranchToRip : 0
    +0x4b8 LastBranchFromRip : 0
    +0x4c0 LastExceptionToRip : 0
    +0x4c8 LastExceptionFromRip : 0

    这里的异常分发其实是X64下的异常分发,那么它就是要按照X64的异常数据结构进行分发了,看一下当前线程中安装的异常链。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    0:000> !exchain
    8 stack frames, scanning for handlers...
    Frame 0x02: wow64cpu!CpupReturnFromSimulatedCode (00000000`748c271e)
    ehandler wow64cpu!CpupSimulateHandler (00000000`748c2560)
    Frame 0x03: wow64!RunCpuSimulation+0xa (00000000`74c6d286)
    ehandler wow64!_C_specific_handler (00000000`74c8e48e)
    Frame 0x04: wow64!Wow64LdrpInitialize+0x42a (00000000`74c6c69e)
    ehandler wow64!_GSHandlerCheck (00000000`74c8e3d0)
    Frame 0x05: ntdll!LdrpInitializeProcess+0x17e3 (00000000`77294223)
    ehandler ntdll!_GSHandlerCheck (00000000`772c0a54)
    Frame 0x06: ntdll! ?? ::FNODOBFM::`string'+0x22a50 (00000000`772f9a60)
    ehandler ntdll!_C_specific_handler (00000000`772b730c)

    其实对Ring3层异常分发起作用的是为wow64!RunCpuSimulation函数设置的异常处理。当依次调用异常链上的过滤函数都没有响应时,就会执行到这里的异常过滤函数,即wow64!_C_specific_handler,它直接调用X64位的ntdll!_C_specific_handler进行处理。

    ntdll!_C_specific_handler函数的过程就不详细说明了,它会遍历wow64!RunCpuSimulation函数的ScopeTable(分层try),依次调用它们的过滤函数,其实这个里面只有一层,它的过滤函数如下:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    wow64!Wow64pLongJmp+0x652:
    00000000`74c8ec52 4055 push rbp
    00000000`74c8ec54 4883ec20 sub rsp,20h
    00000000`74c8ec58 488bea mov rbp,rdx
    00000000`74c8ec5b 48894d30 mov qword ptr [rbp+30h],rcx
    00000000`74c8ec5f 48894d28 mov qword ptr [rbp+28h],rcx
    00000000`74c8ec63 488b4d28 mov rcx,qword ptr [rbp+28h]
    00000000`74c8ec67 e88cddfdff call wow64!Pass64bitExceptionTo32Bit (00000000`74c6c9f8)
    00000000`74c8ec6c c7452001000000 mov dword ptr [rbp+20h],1
    00000000`74c8ec73 8b4520 mov eax,dword ptr [rbp+20h]
    00000000`74c8ec76 4883c420 add rsp,20h
    00000000`74c8ec7a 5d pop rbp
    00000000`74c8ec7b c3 ret

    函数wow64!Pass64bitExceptionTo32Bit将X64的异常信息转换为X86的异常信息,其实就是CONTEXT和异常记录的构造。构造完成之后调用wow64!Wow64SetupExceptionDispatch函数,该函数中整理X86栈上的信息,并且设置跳回X86时要执行的地址,为全局变量wow64!Ntdll32KiUserExceptionDispatcher的值,它的值在初始化时设置为ntdll32!KiUserExceptionDispatcher,其实就是Ring3的异常分发起始函数。

    1
    2
    00000000`74c6c85f 8b0d3be70200    mov     ecx,dword ptr [wow64!Ntdll32KiUserExceptionDispatcher (00000000`74c9afa0)]
    00000000`74c6c865 ff15c556ffff call qword ptr [wow64!_imp_CpuSetInstructionPointer (00000000`74c61f30)]

    接下来回到ntdll!_C_specific_handler函数时,返回值为EXCEPTION_EXECUTE_HANDLER,则需要进行栈展开。在展开过程中就会调用到SCOPE_TABLE中的JumpTarget字段所指偏移处函数,将该SCOPE_TABLE打印出来,看到偏移0xd288处为跳转指令,继续执行call qword ptr [wow64!_imp_CpuSimulate]

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    0:000> lm m wow64
    start end module name
    00000000`74c60000 00000000`74c9f000 wow64

    0:000> dd wow64 + 00034164
    00000000`74c94164 0000d280 0000d288 0002ec52 0000d288

    0:000> u wow64!RunCpuSimulation
    wow64!RunCpuSimulation:
    00000000`74c6d27c 4883ec48 sub rsp,48h
    00000000`74c6d280 ff15524cffff call qword ptr [wow64!_imp_CpuSimulate (00000000`74c61ed8)]
    00000000`74c6d286 eb00 jmp wow64!RunCpuSimulation+0xc (00000000`74c6d288)
    00000000`74c6d288 ebf6 jmp wow64!RunCpuSimulation+0x4 (00000000`74c6d280) // Handler
    00000000`74c6d28a 4883c448 add rsp,48h
    00000000`74c6d28e c3 ret

    根据前面,已经将X86执行环境设置为异常处理的环境了,起点为ntdll32!KiUserExceptionDispatcher,一旦进入模拟状态就会进行X86下的异常分发。

    ###用户APC分发###

    WoW64通过ntdll!KiUserApcDispatcher也勾住了用户模式APC的递交过程。无论何时当64位内核将要给一个WoW64进程分发一个用户模式APC时,Wow64把32位APC地址映射到一个更高的64位地址空间范围中。然后64位ntdll.dll捕获住原生的APC以及用户模式下的环境记录,将它映射到一个32位地址。然后为它准备一个32位用户模式APC和环境记录,并且按照原生32位内核所做的那样将它分发出去。

    如下为插入用户层APC时的调用栈。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    0:000> !wow64exts.k
    Walking 64bit Stack...
    Child-SP RetAddr Call Site
    00000000`001bdb90 00000000`74c6d18f wow64!whNtQueueApcThread+0x2a
    00000000`001bdbd0 00000000`748c2776 wow64!Wow64SystemServiceEx+0xd7
    00000000`001be490 00000000`74c6d286 wow64cpu!TurboDispatchJumpAddressEnd+0x2d
    00000000`001be550 00000000`74c6c69e wow64!RunCpuSimulation+0xa
    00000000`001be5a0 00000000`77294223 wow64!Wow64LdrpInitialize+0x42a
    00000000`001beaf0 00000000`772f9a60 ntdll!LdrpInitializeProcess+0x17e3
    00000000`001befe0 00000000`772a374e ntdll! ?? ::FNODOBFM::`string'+0x22a50
    00000000`001bf050 00000000`00000000 ntdll!LdrInitializeThunk+0xe

    Walking 32bit Stack...
    ChildEBP RetAddr
    0040fe04 759f3ec6 ntdll32!NtQueueApcThread+0x12
    0040fe2c 013f90ab KERNELBASE!QueueUserAPC+0x6b
    0040ff0c 0146f506 Test!wmain+0x4b [c:usersadministratordesktoptesttesttest.cpp @ 168]
    0040ff58 0146f3df Test!__tmainCRTStartup+0x116 [f:ddvctoolscrt_bldself_x86crtsrccrt0.c @ 266]
    0040ff60 754e343d Test!wmainCRTStartup+0xf [f:ddvctoolscrt_bldself_x86crtsrccrt0.c @ 182]
    0040ff6c 77459832 kernel32!BaseThreadInitThunk+0xe
    0040ffac 77459805 ntdll32!__RtlUserThreadStart+0x70
    0040ffc4 00000000 ntdll32!_RtlUserThreadStart+0x1b

    这里还是需要最终调用到X64的内核插入APC,分发回来同样也是从X64的ntdll.dll开始,即从ntdll!KiUserApcDispatcher开始分发,首先将分发函数(KERNELBASE!RtlDispatchAPC)指针进行解码,判断该指针地址是否超过0x80000000,WoW64的APC来说它的该函数在0x80000000地址之下,那么调用wow64!Wow64ApcRoutine函数。

    函数wow64!Wow64ApcRoutine构建X86的运行时环境,并且设置X86下APC的分发函数,即用wow64!Ntdll32KiUserApcDispatcher全局变量包含值(ntdll32!KiUserApcDispatcher函数地址)。完成环境设置,进入X86模拟环境继续执行即可。

    ###控制台支持###

    因为控制台是由csrss.exe在用户模式下实现的,它只是单个原生二进制可执行文件,所以32应用程序在64位Windows上执行不能执行控制台I/O。类似于专门有一个特殊的rpcrt4.dll用来将32位RPC适配成64位RPC,WoW64的32位kernel32.dll中有专门的代码来调用到Wow中,以便在与Csrss.exeConhost.exe交互过程中对参数进行适配。

    以32位的kernel32!WriteConsoleInternal为例来看一下这个逻辑,如下为该函数的反汇编。

    1
    2
    3
    4
    5
    6
    7
    8
    0:000:x86> u kernel32!WriteConsoleInternal
    kernel32!WriteConsoleInternal:
    754e12d5 b802200000 mov eax,2002h
    754e12da b900000000 mov ecx,0
    754e12df 8d542404 lea edx,[esp+4]
    754e12e3 64ff15c0000000 call dword ptr fs:[0C0h]
    754e12ea 83c404 add esp,4
    754e12ed c21400 ret 14h

    注意这里ecx寄存器值为0,函数调用ID号为0x2002h,根据前面的四类函数映射可知它会使用wow64!ServiceTables表中的第三种映射。它对应的WoW64中函数为wow64win!whWriteConsoleInternal,这个函数接下来调用一系列函数,就如前面所述,整理参数,然后调用64位的RPC到csrss.execonhost.exe中。

    另外一类与此类似即Csr类的函数,如下的例子中,它即使用0x30003的系统调用号,映射到wow64!ServiceTables表中的第四类函数表(索引为3)中。

    1
    2
    3
    4
    5
    6
    7
    8
    0:000:x86> u kernel32!NtWow64CsrBasepCreateProcess
    kernel32!NtWow64CsrBasepCreateProcess:
    754e8de8 b803300000 mov eax,3003h
    754e8ded 33c9 xor ecx,ecx
    754e8def 8d542404 lea edx,[esp+4]
    754e8df3 64ff15c0000000 call dword ptr fs:[0C0h]
    754e8dfa 83c404 add esp,4
    754e8dfd c20400 ret 4

    映射到WoW64中的函数为wow64!whNtWow64CsrBasepCreateProcess,它会将数据转换后调用X64对应的函数进行处理。

    ###用户回调###

    从内核回调用户层的函数,这里主要是调用user32.dll中的回调表中函数。内核返回后进入的函数为ntdll!KiUserCallbackDispatcher,函数汇编如下所示:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    0:000> uf ntdll!KiUserCallbackDispatcher
    ntdll!KiUserCallbackDispatch:
    00000000`772cb5d0 488b4c2420 mov rcx,qword ptr [rsp+20h]
    00000000`772cb5d5 8b542428 mov edx,dword ptr [rsp+28h]
    00000000`772cb5d9 448b44242c mov r8d,dword ptr [rsp+2Ch]
    00000000`772cb5de 65488b042560000000 mov rax,qword ptr gs:[60h]
    00000000`772cb5e7 4c8b4858 mov r9,qword ptr [rax+58h]
    00000000`772cb5eb 43ff14c1 call qword ptr [r9+r8*8]
    00000000`772cb5ef 33c9 xor ecx,ecx
    00000000`772cb5f1 33d2 xor edx,edx
    00000000`772cb5f3 448bc0 mov r8d,eax
    00000000`772cb5f6 e8f5e2ffff call ntdll!NtCallbackReturn (00000000`772c98f0)
    00000000`772cb5fb 8bf0 mov esi,eax
    00000000`772cb5fd 8bce mov ecx,esi
    00000000`772cb5ff e8ecd30500 call ntdll!RtlRaiseStatus (00000000`773289f0)
    00000000`772cb604 ebf7 jmp ntdll!KiUserCallbackDispatcherContinue+0xe (00000000`772cb5fd)

    这里可以看到它取出gs:[60h]处的值,即PEB的地址,然后获取PEB的0x58偏移处的指针,如下,偏移处为KernelCallbackTable字段,即内核回调表。看一下内核回调表的内容,如下所示。

    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
    0:000> dt 7efdf000  ntdll!_PEB
    +0x000 InheritedAddressSpace : 0 ''
    ......
    +0x050 ReservedBits0 : 0y000000000000000000000000000 (0)
    +0x058 KernelCallbackTable : 0x00000000`748d1510 Void
    +0x058 UserSharedInfoPtr : 0x00000000`748d1510 Void
    +0x060 SystemReserved : [1] 0
    ......

    0:000> dqs 748d1510
    00000000`748d1510 00000000`74902868 wow64win!whcbfnCOPYDATA
    00000000`748d1518 00000000`749029fc wow64win!whcbfnCOPYGLOBALDATA
    00000000`748d1520 00000000`74902b40 wow64win!whcbfnDWORD
    00000000`748d1528 00000000`74902dc4 wow64win!whcbfnNCDESTROY
    00000000`748d1530 00000000`74902f10 wow64win!whcbfnDWORDOPTINLPMSG
    00000000`748d1538 00000000`74903080 wow64win!whcbfnINOUTDRAG
    00000000`748d1540 00000000`7490325c wow64win!whcbfnGETTEXTLENGTHS
    00000000`748d1548 00000000`749033cc wow64win!whcbfnINCNTOUTSTRING
    00000000`748d1550 00000000`74903560 wow64win!whcbfnINCNTOUTSTRINGNULL
    00000000`748d1558 00000000`749036ec wow64win!whcbfnINLPCOMPAREITEMSTRUCT
    00000000`748d1560 00000000`74903894 wow64win!whcbfnINLPCREATESTRUCT
    00000000`748d1568 00000000`74903a94 wow64win!whcbfnINLPDELETEITEMSTRUCT
    00000000`748d1570 00000000`74903c04 wow64win!whcbfnINLPDRAWITEMSTRUCT
    00000000`748d1578 00000000`74903d90 wow64win!whcbfnINLPHELPINFOSTRUCT
    00000000`748d1580 00000000`74903f1c wow64win!whcbfnINLPHLPSTRUCT
    00000000`748d1588 00000000`749040a8 wow64win!whcbfnINLPMDICREATESTRUCT

    从表里可知WoW64将X64原始进程的回调表内容替换成了WoW64的内容,以wow64win!whcbfnCOPYDATA为例,在该函数中将WoW64的回调函数ID号映射为X86的,并且将回调数据进行整合,调用wow64!Wow64KiUserCallbackDispatcher函数。

    函数wow64!Wow64KiUserCallbackDispatcher和前面的APC分发类似,设置回调X86时的执行地址为全局变量Ntdll32KiUserCallbackDispatcher(即ntdll32!KiUserCallbackDispatcher),然后将执行切回X86模拟环境。

    进入ntdll32!KiUserCallbackDispatcher函数后就是纯X86的内核回调分发了。

    ###注册表重定向###

    应用程序和组件程序将它们的配置数据保留在注册表中,组件程序在安装的过程中,当它们被注册的时候,通常将配置数据写到注册表中。如果同样的组件即安装注册了一个32位二进制文件,又安装了一个64位二进制文件,那么,最后被注册的组件将会覆盖掉以前组件的注册,因为他们填写在相同的位置上。为了以透明的方式解决这个问题,并且无须对32位组件进行任何代码修饰,注册表被分成了两个部分:原生的和Wow64的。在默认情况下,32位组件访问32位视图,64位组件访问64位视图,这为32位和64位组件提供了一个安全的环境,并且将32位应用程序的状态与64位应用程序的状态隔开来。

    为了实现这一点,Wow64截取了所有要打开注册表的系统调用,并且重新解释这些注册表键的路径,将它们指向注册表的64位视图。注册表的打开逻辑和其他的系统调用流程没有差别,只是在WoW64中在调用X64内核中之前对打开路径进行了修改,以ntdll32!ZwOpenKeyEx为例,它会调用到WoW64中的wow64!whNtOpenKeyEx函数,该函数进一步调用wow64!Wow64NtOpenKey,它会调用wow64!ConstructKernelKeyPath对打开的注册表路径进行修正。在X64上受到重定向影响的注册表路径有如下几个:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    // 64位程序的注册信息存储键
    HKLM/Software
    HKEY_CLASSES_ROOT
    HKEY_CURRENT_USER/Software/Classes
    HKEY_LOCAL_MACHINE/Software
    HKEY_USERS/*/Software/Classes
    HKEY_USERS/*_Classes
    //32位程序的注册信息重定向存储键
    HKLM/Software/WOW6432node
    HKEY_CLASSES_ROOT/WOW6432node
    HKEY_CURRENT_USER/Software/Classes/WOW6432node
    HKEY_LOCAL_MACHINE/Software/WOW6432node
    HKEY_USERS/*/Software/Classes/WOW6432node
    HKEY_USERS/*_Classes/WOW6432node

    在打开注册表路径,获取注册表句柄时,参数中加入KEY_WOW64_64KEY/KEY_WOW64_32KEY分别用于32位程序访问64位程序注册表和64位程序访问32位程序注册表。

    ###文件系统重定向###

    为了维护应用程序的兼容性,已经降低从Win32到64位Windows的应用程序移植代价,系统目录名称仍然保持不变。因此,WindowsSystem32文件夹包含了原生的64位映像文件。因为Wow64勾住了所有的系统调用,所以它会解释所有与路径相关的API,将WindowsSystem32文件夹的名称替换为WindowsSyswow64。Wow64也将WindowsLastGood重定向到WindowsLastGoddSyswow64,将WindowsRegedit.exe重定向到Windowssyswow64Regedit.exe。通过使用系统环境变量,%PROGRAMFILE%环境变量对于32位程序被设置为Program File (x86),而对于64位应用程序被设置为Program File文件夹,CommonProgramFilesCommonProgramFiles(x86)也存在,它们总是指向32位的位置,而ProgramW6432CommonProgramWP6432则无条件指向64位位置。

    X86所有的系统调用和操作都已经被WoW64给截取了,所以只需要在涉及上述这些路径中进行路径修改即可。在WoW64中使用wow64!RedirectObjectAttributes函数将路径进行修改。在wow64.dll中包括wow64!RedirectDosPathUnicodewow64!Wow64ShallowThunkAllocObjectAttributes32TO64_FNCwow64!RedirectObjectName等函数都会调用到它。

    如下两个函数可以用于打开和关闭文件重定向,它们内部会调用ntdll32!RtlWow64EnableFsRedirectionEx,最终用于操作X64的TEB内容。

    1
    2
    kernel32!Wow64DisableWow64FsRedirection	// 关闭系统重定向
    kernel32!Wow64RevertWow64FsRedirection // 打开系统重定向

    转自:https://www.dazhuanlan.com/2019/09/30/5d91a835adf83/

  • 相关阅读:
    Mysql 中的日期时间字符串查询
    PyQt5中的信号与槽,js 与 Qt 对象之间互相调用
    vue学习初探
    【Java】JDBCUtil模板
    【明哥报错簿】之【 "javax.servlet.http.HttpServlet" was not found on the Java Build Path || HttpServletRequest/HttpServletResponse cannot be resolved to a type】
    【开发工具IDE】Eclipse相关配置
    【Java】JAVA开发人员常见环境工具安装
    【Java】自动获取某表某列的最大ID数
    【Java】全站编码过滤器GenericEncodingFilter代码与配置
    【Linux】无法将 Ethernet0 连接到虚拟网络“VMnet8”
  • 原文地址:https://www.cnblogs.com/yilang/p/12064731.html
Copyright © 2011-2022 走看看