WoW64的切换细节
在64位的Windows中可以运行32位的应用程序,这就是所谓的WoW64技术,全称即Windows 32-bit(Applications) on Windows 64-bit(OS)。如何实现的呢?细说话长,关键是如下几个要点:
1)32位代码和64位的内核之间有一个转接层,称为thunk layer。形象的说,转接层的作用是“欺上瞒下”,对于内核来说,好像上面跑得就是一个64位的应用,而对于应用来讲,也会觉得还是跑在32位的内核上。
2)转接层本身主要是64位代码(有一点32指令,见下文)。除了转接层外,Wow进程中的其它用户态模块必须都是32位的,包括kernel32.dll,
user32.dll等模块。也正因为此,为了支持WoW64,在Windows系统目录下的SysWow64下完完整整的安装着一套32位的系统DLL。刚刚看了下正在用的这个64 Win7的SysWow64目录,居然有1.25GB。这个目录下的模块都是32位的,是专门给32位应用程序用的,虽然目录名中有64。顺便说下,在Win64中,system32目录下放的都是64位模块。有点晕么?全都是为了兼容么 :-)
理论枯燥,就说这么多,下面看看代码吧:
USER32!NtUserCallOneParam:
765e60cd b802100000 mov
eax,1002h
765e60d2 b900000000 mov
ecx,0
765e60d7 8d542404 lea
edx,[esp+4]
765e60db 64ff15c0000000 call
dword ptr fs:[0C0h]
fs:0053:000000c0=00000000
765e60e2 83c404 add esp,4
765e60e5 c20800 ret
8
首先,上面是32位的代码,来自USER32.dll。是窗口API的内部实现,所谓的Service Stub,用来调用系统服务的,1002是服务的编号。本来这个代码中应该使用syscall这样的指令发起系统调用的。但是这里调用的一个函数指针,位置是fs段的c0字段。看着《软件调试》的朋友一定知道fs指向的是线程的环境块,即TEB。Dt一下TEB便知道偏移C0的地方叫WOW32Reserved,真的与WoW有关,以前没有注意这个字段的真正用途,现在可以明确知道它保存的是一个函数指针了。
+0x0c0
WOW32Reserved : Ptr32 Void
单步跟踪一下,发现这个函数指针指向的是下面这个内容:
wow64cpu!X86SwitchTo64BitMode:
74772320 ea1e2777743300 jmp
0033:7477271E
这两行的信息量很大,根据符号提示,这个代码属于wow64cpu模块,是上面提到的转接层的重要模块之一。从这条指令的“函数名”来看,X86SwitchTo64BitMode,明显是切换用的。
再看这条指令的段描述符,是33,也很特别,因为32代码用的代码段选择子总是23。其实这就是奥妙所在。对于支持Win64的x64 CPU来说,运行64位代码的叫长模式(Long mode),为了兼容32位代码,还有一个所谓的32位兼容模式。CPU可以非常轻松的在长模式和兼容模式之间切换,只要用上面这样的长跳转指令就可以了。CPU会检查段的描述符,如果描述符中有Long标志,那么就切换到长模式,否则就切换到兼容模式。
单步跟踪上面的jmp指令后,就立刻转到64位了,r看一下:
0:000> r
rax=000000000000110e
rbx=0000000000000003 rcx=0000000000000005
rdx=000000000018a318
rsi=000000000018a478 rdi=0000000000000005
rip=000000007477271e
rsp=000000000018a310 rbp=000000000018a46c
r8=000000000000002b r9=00000000765f0de3 r10=0000000000000000
r11=0000000000000246
r12=000000007efdb000 r13=000000000008fd20
r14=000000000008b100
r15=0000000074772450
iopl=0 nv up ei pl zr na po nc
cs=0033 ss=002b
ds=002b es=002b fs=0053
gs=002b efl=00000246
wow64cpu!CpupReturnFromSimulatedCode:
00000000`7477271e
67448b0424 mov r8d,dword ptr [esp]
ds:00000000`0018a310=76647d6c
全是64位的了。注意指令的地址,00000000`7477271e,与上面jmp的偏移刚好一致。函数名很有意思,CpupReturnFromSimulatedCode,“从虚拟环境的代码回来了”。