首页
论坛
课程
招聘
mrMORE
雪    币: 346
活跃值: 活跃值 (11)
能力值: ( LV4,RANK:50 )
在线值:
发帖
1
回帖
1
粉丝
0

[分享][原创]汇编里看Wow64的原理(浅谈32位程序是怎样在windows 64上运行的?)

2017-9-16 00:34 13053

[分享][原创]汇编里看Wow64的原理(浅谈32位程序是怎样在windows 64上运行的?)

2017-9-16 00:34
13053

汇编里看Wow64的原理(浅谈32位程序是怎样在windows 64上运行的?)


Windows操作系统作为PC上最普及的操作系统,面向的用户各种各样,因此在版本升级时,对比其它操作系统,兼容性都要做得好,不需要用户费神DIY处理一些BUG。64位windows上市后大多以前32位的程序依旧正常地运行,当然这里主要指用户态程序。那64位windows是如何支持32位程序运行的呢?之前在看《windows核心编程》一书时只知道这个机制的名字叫wow64,但是具体如何实现的一无所知。为此我上网查阅资料,结果相关文章都讲得很笼统,包括Microsoft官方文档也是从很上层架构上进行了介绍,对于搞逆向的人来说,只了解架构不看代码怎么能忍,windows就在手边,何不亲自研究窥探一把庐山面目呢?说搞就搞,从代码的角度看一下这个wow64的大概。

这里插一句,其实在着手了解wow64机制前,是另外一个问题先引起了我的好奇:64位CPU比32位CPU除了每个寄存器宽度多了32bit,还多了几个通用寄存器:R8, R9, R10, R11, R12, R13, R14, R15,那32位程序在win64上运行时这些新加的寄存器就没用了吗?这个问题最后也会得到解决。


首先,先从宏观分析一下,32位程序的运行需要软硬件两个大方面的支持:

1)硬件上,CPU的解码模式需要是32位模式。64位CPU(我只熟悉INTEL的)是通过GDT表中CS段所对应的表项中L标志位来确定当前解码模式的。这里不展开描述GDT表与CPU运行模式的关系,感兴趣的可以参看http://www.secbox.cn/hacker/program/9875.html

2)软件上,操作系统需要提供32位的用户态运行时环境(C库,WINDOWS API)对32位程序支持,其次因为win64内核是64位模式的,所以32位运行时环境在与64位内核交互时需要有状态转换。

当然另外肯定还有大量其它的兼容32位软件所需要实现的功能,比如资源管理,句柄管理,结构化错误管理等等,这些属于细节就不进行研究了,我这里先看一个大体。


好了,接下来针对上面的分析进行探索。关于32位运行时环境这点,可以在c:/windows/syswow64中发现许多和c:/windows/system32下同名的动态链接库,如kernel32.dll, ntdll.dll, msvcrt.dll, ws2_32.dll等,其实这些都是32位的版本。像wow64名字所传达的含义一样,syswow64文件夹下的这些库相当于在64位windows中构建了一个32位windows子系统环境,我们32位的程序能正常在win64上运行正是靠这个子环境负责与64位环境进行了交互和兼容,所以需要重点探究下这个32位子环境是如何与win64环境交互的。

我这里用到的工具是PCHunter与调试器MDebug,静态分析工具IDA。

了解windows的读者都知道ntdll.dll是用户态与内核态交互的桥梁,所以我选择从ntdll.dll入手,选择了逻辑简单的NtAllocateVirtualMemory函数。首先看一下原生32位操作系统里这个函数是什么样的。我手头有个win8 32bit版本,利用MDebug直接转到NtAllocateVirtualMemory函数查看反汇编,可以看到,在设置好调用号0x19B之后直接就使用sysenter进行了系统调用,中间没有其它操作,下面是相应的反汇编代码:

NtAllocateVirtualMemory:

7778F048 mov eax, 0x19B

7778F04D call sub_7778F055(7778F055)

7778F052 ret 0x18


sub_7778F055:

7778F055 mov edx, esp

7778F057 sysenter

7778F059 ret


看完原生32位操作系统里的样子,win64中运行一个32位程序时它的进程空间里的NtAllocateVirtualMemory是一番什么情景呢?我手头有win7 64bit版,运行的一个32bit程序进行调试,可以看到NtAllocateVirtualMemory的形式如下:

NtAllocateVirtualMemory:

77C8FAD0   mov       eax,0x15

77C8FAD5   xor        ecx,ecx

77C8FAD7   lea        edx,[esp+0x4]

77C8FADB   call       dword ptr fs:[000000C0]

77C8FAE2   add       esp,4

77C8FAE5   ret        0x18

OK,区别很明显,wow64中的ntdll.dll与原生32位windows中的ntdll.dll有了变动,它不再是与内核交互的最后一个用户态模块,而是call 进了fs:[C0]处的函数,隐约感觉这里就是打开wow64秘密的入口。fs:[C0]是什么呢?Windows操作系统中,fs寄存器用于记录线程环境块TEB,根据TEB结构体定义可以看出0xC0偏移处的定义为:

PVOID WOW32Reserved;  // 0C0

其实在wow64之前还有wow32机制,用于兼容16位程序在32位windows上运行,与wow64异曲同工。所以windows系统在wow64中直接也拿这个保留位置用于进行32位64位环境切换的跳板。单步跟进,发现fs:[C0]处只有一行代码:

752B2320   jmp         0033:752B271E

这里是一个长跳转,目的地址是内存752B271E处,但是MDebug调试器显示752B271E处于未知模块。这时需要借助PCHunter,通过PcHunter发现该地址其实位于一个叫wow64cpu.dll的模块中,值得注意的一点是,该模块来自system32而非syswow64目录,是64位的文件模块,也就是说,wow64下32位程序的进程空间内同时加载了32位与64位的可执行文件模块!在这个32位程序的进程空间里一共有4个来自SYSTEM32目录64位的“客人”:


终于,这里有了真正的64位ntdll.dll的出现。所以很容易可以推断,wow64.dll, wow64win.dll, wow64cpu.dll组成了环境转换模块,而最终依然是ntdll.dll负责与内核交互,wow64中这4个模块在默默地在后台支持着32位程序的运行。

上面说到这里经历了一个长跳转,段寄存器由0x23变换为0x33,在win64中,0x23和0x33所对应的GDT表项中CPU的模式分别为32位与64位。自此,CPU解码模式由32位切换为64位。当然,故事还没结束。不过由于调试器是32位,无法准确捕获接下来发生的事情,单步跟进也没用了,我们转为使用IDA静态分析。

找到752B271E所对应的wow64cpu.dll中的位置:


00000000752B271E   mov         r8d,[esp]             //取出返回地址

00000000752B2723   mov         [r13+0xBC],r8d //保存返回地址

00000000752B272A   mov         [r13+0xC8],esp //保存32位环境堆栈指针

00000000752B2731   mov         rsp,[r12+0x1480] //切换至64位环境堆栈

00000000752B2739   and         qword ptr [r12+0x1480],0x0

00000000752B2742   mov         r11d,edx

00000000752B2745   jmp         qword ptr [r15+rcx*8]


第一句读取[esp]的值其实是把返回地址取出,接着保存到了r13所指向的地方,同时还保存了esp,然后重新赋值了rsp。看了这一小段,我们基本可以猜测到,在wow64中那个幕后的64位环境里其实是有自己的堆栈和执行上下文的,在CPU由32位切换到64位后,堆栈也相应切换。好了,下面要搞最后一句跳向了哪里,也就是[r15+rcx*8]的值,我们上面考察的NtAllocateVirtualMemory有xor ecx, ecx的操作,所以到这里时rcx = 0,所以就我们考察的例子而言,最后就是跳转到了[r15]。那,r15的值是多少?

这是有点棘手的问题。Wow64中32位程序只能由32位调试器调试,但是32位调试器下又无法获得64位模式下才可见的r15的值,怎么办?我这里使用shellcode的方式,利用32位MDebug调试shellcode的功能调试精心准备的一段shellcode,这段shellcode的作用独特而简单:让CPU切换到64位模式下“潇洒走一回”,将R8 ~R15的值记录到堆栈中,接着切换回32位模式。


shellcode的二进制为:

\x6A\x33\xE8\x00\x00\x00\x00\x83\x04\x24\x05\xCB\x48\xB8\x88\x77\x66\x55\x44\x33\x22\x11\x50\x41\x50\x41\x51\x41\x52\x41\x53\x41\x54\x41\x55\x41\x56\x41\x57\x50\xE8\x00\x00\x00\x00\xC7\x44\x24\x04\x23\x00\x00\x00\x83\x04\x24\x0D\xCB

它的反汇编如下:

/*开始时CPU处于32位模式*/

Push 0x33     // cs = 0x33

Call L1

L1: 

add [esp], 5

retf      // far ret,切换CPU状态

/*此时CPU处于64位模式*/

mov rax, 1122334455667788h     //将r8~r15用特殊值与周边数据隔开,方便查看

push rax

push r8

push r9

push r10

push r11

push r12

push r13

push r14

push r15

push rax

Call L2: 

L2:

mov [esp + 4], 0x23       // cs = 0x23

add [esp], 0xd     

retf


虽然32位调试器无法对64位代码运行时下断,但是可以在切换回32位模式后的地方下断点。所以在这段代码后下一个断,运行代码。执行完毕后,查看一下堆栈上的收获:

  地址 内容

0018FEA0  1122334455667788 

0018FEA8  00000000752B2450  r15

0018FEB0  000000000008EC80  r14

0018FEB8  000000000008FD20  r13

0018FEC0  000000007EFDB000  r12

0018FEC8  0000000000000246  r11

0018FED0  0000000000000000  r10

0018FED8  0000000077C8FAFA  r9

0018FEE0  000000000000002B  r8

0018FEE8  1122334455667788 


Bingo!我们成功获得到了32位程序运行环境下R8~R15的值,根据R15的值定位出它同样位于wow64cpu.dll文件模块内,根据R15的值 找到wow64cpu.dll中相应的位置,发现是指向了一堆函数指针:


.text:0000000078B62450  dq offset TurboDispatchJumpAddressEnd 

.text:0000000078B62458    dq offset sub_78B62DBA

.text:0000000078B62460    dq offset sub_78B62BCE

.text:0000000078B62468    dq offset sub_78B62D6A

//后面还有很多函数指针,此处省略


其实在第一次打开wow64cpu.dll寻找752B271E位置时,就可以看到它附近有一个名为CpuSimulate的函数,里面有这样的操作:

.text:0000000078B625F9    mov     r12, gs:30h

.text:0000000078B62602    lea     r15, off_78B62450

可以看到r15是指向了偏移78B62450处,对应动态加载后就是752B2450。所以这也印证了我们的实验结果。另外还可以看到的一点是r12指向了64位下的TEB(64位下gs段寄存器用于记录TEB结构)。


所以很显然,[r15]是指向了TurboDispatchJumpAddressEnd处,就是上文jmp qword ptr [r15+rcx*8]所要跳转到的地方(因为ecx = 0),看一下它的代码:

TurboDispatchJumpAddressEnd:

.text:0000000078B62749    mov     [r13+0A4h], esi

.text:0000000078B62750    mov     [r13+0A0h], edi

.text:0000000078B62757    mov     [r13+0A8h], ebx

.text:0000000078B6275E    mov     [r13+0B8h], ebp   //保存32位环境下的寄存器

.text:0000000078B62765    pushfq

.text:0000000078B62766    pop     rbx

.text:0000000078B62767    mov     [r13+0C4h], ebx    //保存32位环境的eflags

.text:0000000078B6276E    mov     ecx, eax            //调用号

.text:0000000078B62770    call    cs:Wow64SystemServiceEx   //继续完成未尽竟的事业

.text:0000000078B62776    mov     [r13+0B4h], eax

.text:0000000078B6277D    jmp     loc_78B62611


上面的代码最后跳转到78B62611,loc_78B62611的代码如下:

text:0000000078B62611 loc_78B62611:                         

.text:0000000078B62611   and     dword ptr [r13+2D0h], 1

.text:0000000078B62619   jz      loc_78B626CE

.text:0000000078B6261F   movaps  xmm0, xmmword ptr [r13+170h]

.text:0000000078B62627   movaps  xmm1, xmmword ptr [r13+180h]

.text:0000000078B6262F   movaps  xmm2, xmmword ptr [r13+190h]

.text:0000000078B62637   movaps  xmm3, xmmword ptr [r13+1A0h]

.text:0000000078B6263F   movaps  xmm4, xmmword ptr [r13+1B0h]

.text:0000000078B62647   movaps  xmm5, xmmword ptr [r13+1C0h]

.text:0000000078B6264F   mov     ecx, [r13+0B0h]

.text:0000000078B62656   mov     edx, [r13+0ACh]

.text:0000000078B6265D   and     dword ptr [r13+2D0h], 0FFFFFFFEh

.text:0000000078B62665   mov     edi, [r13+0A0h]

.text:0000000078B6266C   mov     esi, [r13+0A4h]

.text:0000000078B62673   mov     ebx, [r13+0A8h]

.text:0000000078B6267A   mov     ebp, [r13+0B8h]

.text:0000000078B62681   mov     eax, [r13+0B4h]      //恢复32位环境寄存器

.text:0000000078B62688   mov     [r12+1480h], rsp      //保存64位环境堆栈指针

.text:0000000078B62690   mov     [rsp+0B8h+var_B0], 23h

.text:0000000078B62697   mov     [rsp+0B8h+var_98], 2Bh

.text:0000000078B6269E   mov     r8d, [r13+0C4h]      //之前保存的32位环境eflags        

.text:0000000078B626A5   and     dword ptr [r13+0C4h], 0FFFFFEFFh

.text:0000000078B626B0   mov     [rsp+0B8h+var_A8], r8d    

.text:0000000078B626B5   mov     r8d, [r13+0C8h]

.text:0000000078B626BC   mov     [rsp+0B8h+var_A0], r8

.text:0000000078B626C1   mov     r8d, [r13+0BCh]

.text:0000000078B626C8   mov     [rsp+0B8h+var_B8], r8

.text:0000000078B626CC   iretq    //排好堆栈,返回至32位模式返回地址处


可以看到,在TurboDispatchJumpAddressEnd代码片段中,调用了一个外部函Wow64SystemServiceEx,由这个函数再继续把下面的事情做完,最终调用64位的ntdll.dll的NtAllocateVirtualMemory来完成整个操作。TurboDispatchJumpAddressEnd最后跳转至78B62611,将CPU主要寄存器值恢复至之前保存好的32位环境中的值,同时在堆栈中排布好返回地址,cs段寄存器值,eflag值,执行iretq,返回至32位环境中,在我们的例子中,即返回到NtAllocateVirtualMemory中call  dword ptr fs:[C0]的下一句,看起来像真的执行了一个普通函数一样。上面讲到跳转的函数指针表是根据r15+rcx*8来得到的,在32位进程空间的那个ntdll.dll里面call  dword ptr fs:[C0]前都有对ecx的赋值,我们可以推测在wow64中,系统调用被分成多类,类别号存在于rcx中,根据rcx的值来进行不同类别的模拟转换。

Wow64SystemServiceEx做的事情就暂时不详细研究了,感兴趣的可以细细钻研。


对这次简单的wow64之旅做个小总结:

1)windows/syswow64目录下的大量DLL库与SYSTEM32目录下的wow64.dll, wow64cpu.dll, wow64win.dll, ntdll.dll支撑着wow64机制

2)Wow64下32位进程中实际有32位和64位两个逻辑子空间,每个子空间都 有各自的数据结构、堆栈,64位子空间负责与操作系统内核交互:

32位用户态模式  <--------->  64位用户态模式  <------------------> 64位内核


3)Wow64模式下,那些不可见的寄存器并不都是闲置不用的,在切换到64位环境后全部启用,和正常64位程序无差别。且经过分析可以知道有确切作用的寄存器有:

R12: 指向64位环境的TEB结构体 

R13:指向保存32位环境CPU的状态的位置

R15: 指向跳转函数指针列表的起始


上面是针对win7下做的一个wow64机制小探索,我也简单看了下在win8和win10下的wow64过程,在反汇编代码上有些小不同,但是逻辑原理是完全相同的,感兴趣的读者可以搞一把。


PS:写完后发现windbg可以通过wow64的扩展组件更好地调试wow64程序。吐血。。。。。。




[公告]看雪论坛2020激励机制上线了!发帖不减雪币了!如何获得积分快速升级?

打赏 + 22.00
打赏次数 3 金额 + 22.00
 
赞赏  一位没有留下痕迹的看雪读者   +1.00 2017/09/18
赞赏  accessd   +20.00 2017/09/18
赞赏  cvcvxk   +1.00 2017/09/16
最新回复 (29)
halu
雪    币: 39
活跃值: 活跃值 (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
0
回帖
2
粉丝
0
halu 活跃值 2017-9-16 01:27
2
0
前排学习
AntBean
雪    币: 532
活跃值: 活跃值 (10)
能力值: ( LV12,RANK:390 )
在线值:
发帖
34
回帖
139
粉丝
0
AntBean 活跃值 9 2017-9-16 07:16
3
0
排队学习
ugvjewxf
雪    币: 605
活跃值: 活跃值 (13)
能力值: ( LV4,RANK:40 )
在线值:
发帖
20
回帖
328
粉丝
1
ugvjewxf 活跃值 2017-9-16 08:12
4
0
赶紧学习
wx_有福
雪    币: 1
活跃值: 活跃值 (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
0
回帖
1
粉丝
0
wx_有福 活跃值 2017-9-16 08:25
5
0
牛逼
jackandkx
雪    币: 5960
活跃值: 活跃值 (35)
能力值: ( LV12,RANK:520 )
在线值:
发帖
33
回帖
151
粉丝
0
jackandkx 活跃值 10 2017-9-16 09:04
6
0
66666
zylyy
雪    币: 138
活跃值: 活跃值 (12)
能力值: ( LV2,RANK:10 )
在线值:
发帖
11
回帖
328
粉丝
0
zylyy 活跃值 2017-9-16 09:51
7
0
那段精心构造的代码不错,哈哈。实现64位环境和32位环境的穿透
我是哥布林
雪    币: 526
活跃值: 活跃值 (21)
能力值: ( LV3,RANK:20 )
在线值:
发帖
4
回帖
87
粉丝
0
我是哥布林 活跃值 2017-9-16 10:01
8
0
学习一个
wx_fragwil
雪    币: 1
活跃值: 活跃值 (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
0
回帖
1
粉丝
0
wx_fragwil 活跃值 2017-9-16 12:00
9
0
学习了!
cvcvxk
雪    币: 2648
活跃值: 活跃值 (60)
能力值: ( LV12,RANK:750 )
在线值:
发帖
126
回帖
3102
粉丝
36
cvcvxk 活跃值 10 2017-9-16 13:59
10
0
没看到windbg的插件的人都是大手子。
fengyunabc
雪    币: 188
活跃值: 活跃值 (30)
能力值: ( LV2,RANK:50 )
在线值:
发帖
8
回帖
257
粉丝
4
fengyunabc 活跃值 1 2017-9-16 14:25
11
0
学习!
kanxue
雪    币: 48
活跃值: 活跃值 (250)
能力值: (RANK:350 )
在线值:
发帖
2275
回帖
16358
粉丝
23
kanxue 活跃值 8 2017-9-16 18:13
12
0
文章不错
linziqingl
雪    币: 217
活跃值: 活跃值 (10)
能力值: ( LV9,RANK:190 )
在线值:
发帖
54
回帖
327
粉丝
1
linziqingl 活跃值 4 2017-9-17 20:54
13
0
PPTV
雪    币: 4989
活跃值: 活跃值 (20)
能力值: ( LV2,RANK:10 )
在线值:
发帖
4
回帖
147
粉丝
0
PPTV 活跃值 2017-9-17 22:08
14
0
来学习!
蒼V嵐
雪    币: 1769
活跃值: 活跃值 (14)
能力值: ( LV8,RANK:130 )
在线值:
发帖
3
回帖
8
粉丝
0
蒼V嵐 活跃值 3 2017-9-17 23:23
15
0
厉害  学习!
holing
雪    币: 5263
活跃值: 活跃值 (15)
能力值: ( LV15,RANK:680 )
在线值:
发帖
35
回帖
412
粉丝
3
holing 活跃值 15 2017-9-18 00:00
16
0
厉害
LawrenceLee
雪    币: 0
活跃值: 活跃值 (13)
能力值: ( LV2,RANK:10 )
在线值:
发帖
0
回帖
14
粉丝
0
LawrenceLee 活跃值 2017-9-18 10:13
17
0
受教了楼主,  向你学习.
囧囧
雪    币: 466
活跃值: 活跃值 (69)
能力值: ( LV5,RANK:60 )
在线值:
发帖
65
回帖
306
粉丝
1
囧囧 活跃值 2017-9-18 10:39
18
0
后排点赞~
实都
雪    币: 72
活跃值: 活跃值 (33)
能力值: ( LV2,RANK:10 )
在线值:
发帖
0
回帖
112
粉丝
0
实都 活跃值 2017-9-18 11:20
19
0
干货啊,哈哈哈哈  学习了
jgs
雪    币: 4091
活跃值: 活跃值 (30)
能力值: ( LV2,RANK:10 )
在线值:
发帖
1
回帖
321
粉丝
0
jgs 活跃值 2017-9-18 14:10
20
0
学习,支持楼主
mrMORE
雪    币: 346
活跃值: 活跃值 (11)
能力值: ( LV4,RANK:50 )
在线值:
发帖
1
回帖
1
粉丝
0
mrMORE 活跃值 1 2017-9-18 20:18
21
0
第一次发帖子,被各位大神抬举,实不敢不敢。。。。
accessd
雪    币: 1075
活跃值: 活跃值 (10)
能力值: ( LV7,RANK:100 )
在线值:
发帖
21
回帖
311
粉丝
0
accessd 活跃值 2 2017-9-18 20:43
22
0
不错,再接再厉
聖blue
雪    币: 7077
活跃值: 活跃值 (24)
能力值: ( LV2,RANK:10 )
在线值:
发帖
2
回帖
362
粉丝
0
聖blue 活跃值 2017-9-19 21:44
23
0
不错!!!!!!
myqqi
雪    币: 322
活跃值: 活跃值 (10)
能力值: ( LV4,RANK:50 )
在线值:
发帖
29
回帖
160
粉丝
1
myqqi 活跃值 1 2017-9-21 10:59
24
0
真厉害
uvbs
雪    币: 80
活跃值: 活跃值 (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
4
回帖
196
粉丝
0
uvbs 活跃值 2017-9-26 14:21
25
0
cvcvxk 没看到windbg的插件的人都是大手子。
老v什么都知道啊
kkHAIKE
雪    币: 1932
活跃值: 活跃值 (15)
能力值: ( LV12,RANK:460 )
在线值:
发帖
22
回帖
36
粉丝
0
kkHAIKE 活跃值 9 2017-9-26 17:57
26
0
http://blog.rewolf.pl/blog/
最开始在这个博客发现这种技术,博主已经开发出  sdk了,发给大家研究
名字zhanghu
雪    币: 40
活跃值: 活跃值 (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
0
回帖
9
粉丝
0
名字zhanghu 活跃值 2017-9-28 21:19
27
0
666666666666
Archar
雪    币: 22
活跃值: 活跃值 (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
7
回帖
52
粉丝
0
Archar 活跃值 2017-10-26 00:19
28
0
FaEry
雪    币: 2190
活跃值: 活跃值 (11)
能力值: ( LV12,RANK:250 )
在线值:
发帖
11
回帖
62
粉丝
7
FaEry 活跃值 5 2018-3-31 17:45
29
0
airbus
雪    币: 246
活跃值: 活跃值 (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
0
回帖
216
粉丝
0
airbus 活跃值 2019-1-4 11:09
30
0
这么好的技术,可以考虑用在商业产品上不?依赖第三方dll,第三方dll有32和64位的.
游客
登录 | 注册 方可回帖
返回