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:\Users\Administrator\Desktop\Test\Debug\Test.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/


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM