Windows WOW64 nativeapi 逆向详解,32程序兼容剖析

1 篇文章 0 订阅
1 篇文章 0 订阅

前言

        windows有很多核心的原生api,其中包含sdk声明的和文档未声明的。主要由于ntdll.dll和win32u.dll(服务号0x1000-0x1FFF)导出。

        WOW64 (Windows-on-Windows 64-bit)是一个Windows操作系统的子系统,用于模拟32位环境,使得32位执行程序在x64系统正常运行。

        而64位系统字长变为64位,因此是无法直接执行32位的可执行代码的。因此x64引入了兼容32位执行程序的wow64 子系统。而深度剖析到系统层面,其增加了几个wow64cpu.dll等相关的dll,而切入内核服务时只是做了一些转换和限制。

        ps: 什么是native api

实例解析

        这是wow64程序 ntdll.dll 的ZwClose函数,这是32位的ntdll.dll,这也是进入内核前最后的代码。wow64进程当然也加载了64位的ntdll.dll,但其符号名变成了_ZwClose我猜测可能是给模拟环境的wow64调用的。而32位逻辑代码调用的也是32位ntdll.dll的代码。下面我们就以ZwClose函数作为起点,开始追踪分析wow64切入内核调用的过程。

1、ZwClose入口:

        首先看到服务号的高两字节出现了值,该值其实是和后面计算跳转有关。

        可以看到此处将eax为服务号,eax置为服务号值后直接调用了edx设值的这个函数,因此我们同样跟过去查看该地址的代码。

ntdll.ZwClose  - B8 0F000300    - mov eax,0003000F
ntdll.ZwClose+5- BA 7088BE77    - mov edx,ntdll.RtlInterlockedCompareExchange64+170
ntdll.ZwClose+A- FF D2          - call edx
ntdll.ZwClose+C- C2 0400        - ret 0004
ntdll.ZwClose+F- 90             - nop

2、ntdll.RtlInterlockedCompareExchange64+170:

        此处是一个iat函数表值,继续跟踪

ntdll.RtlInterlockedCompareExchange64+170 - FF 25 2892C877  - jmp dword ptr [ntdll.Wow64Transition]

3、ntdll.Wow64Transition:

        好的,可以看到,此处进行了段转移。该jmp实际的作用是将cs寄存器设值为0x33。熟悉的小伙伴其实应该已经看出来了,这里就是在构造x64汇编执行环境了。x64代码执行时,其cs寄存器为0x33。而这也可以作为一段代码获取自己当前执行环境的办法。

        切换cs段寄存器后代码以及可访问的寄存器都变为了x64状态了。而此处的跳转【r15+0xF8】已经是访问8字节字长(64位)值了。因此在这里它可能就跳到了64位的寻址空间了。即超过4G的地址了。

        这段代码可以说是wow64转换的精髓,此处切换cpu代码环境为x64,也就表明此处开始汇编代码已经进入x86 和x64的统一状态。

wow64cpu.dll+7000 - EA 0970B577 3300      - jmp 0033:wow64cpu.dll+7009
wow64cpu.dll+7007 - 90                    - nop 
wow64cpu.dll+7008 - 90                    - nop 
wow64cpu.dll+7009 - 41 FF A7 F8000000     - jmp qword ptr [r15+000000F8]

         此代码操作处理:x86---》x64汇编环境

 

        [R15+f8] 为wow处理函数地址 

 4、[r15+000000F8]:

         由于此处是通过r15间接寻址跳转的,因此我们通过调试器跟过去,可以到达其跳转的目标位置,即下图的代码位置。

        这里大致就是一些寄存器的赋值什么的,其实此处是进行了一个x86环境的保存。可以看到对寄存器、堆栈、返回地址的值进行了存储。之后取得ecx即 服务号的高2位与 r15相对寻址跳走执行。

        继续跟踪就好

wow64cpu.+572 - 49 87 E6              - xchg r14,rsp           { 交换 R14,RSP}
wow64cpu.+575 - 45 8B 06              - mov r8d,[r14]          { 返回地址赋值给r8}
wow64cpu.+578 - 49 83 C6 04           - add r14,04             { 00000004 }
wow64cpu.+57C - 45 89 45 3C           - mov [r13+3C],r8d  { __________________________
                                                          { |  以为R13+20为起始地址4字节数组
                                                            |  arry[0]:edi 
wow64cpu.+580 - 45 89 75 48           - mov [r13+48],r14d { |  arry[1]:esi
wow64cpu.+584 - 4D 8D 5E 04           - lea r11,[r14+04]  { |  arry[2]:ebx
wow64cpu.+588 - 41 89 7D 20           - mov [r13+20],edi  { |  arry[3]:2c
wow64cpu.+58C - 41 89 75 24           - mov [r13+24],esi  { |  arry[4]:30
wow64cpu.+590 - 41 89 5D 28           - mov [r13+28],ebx  { |  arry[5]:34
wow64cpu.+594 - 41 89 6D 38           - mov [r13+38],ebp  { |  arry[6]:ebp
wow64cpu.+598 - 9C                    - pushfq            { |  arry[7]:返回地址(3C)
wow64cpu.+599 - 41 58                 - pop r8            { |  arry[8]:40
wow64cpu.+59B - 45 89 45 44           - mov [r13+44],r8d  { |  arry[9]:eflags (44)
wow64cpu._TurboDispatchJumpAddressStart
                      - 8B C8         - mov ecx,eax       { |  arry[10]:esp (48)
                                                         ————————————
wow64cpu.__+2- C1 E9 10    - shr ecx,10                        { R11 上一层返回地址
wow64cpu._+5- 41 FF 24 CF  - jmp qword ptr [r15+rcx*8]         { 跳转以为 服务号高16位为索引的数组地址

 5、 [r15+rcx*8]:

        这里没什么好将的,熟悉编程的应该知道。其实应该是根据服务号不同做了不同的初始化。然后之后需要跳到一些相同逻辑的代码,所以会有这种代码段。

        就是:合-分-合。这样一个逻辑流程,继续跟踪。

wow64cpu._TurboDispatchJumpAddressEnd+4AE   - movsxd  r10,dword ptr [r11]
wow64cpu._TurboDispatchJumpAddressEnd+4B1   - jmp wow64cpu._TurboDispatchJumpAddressEnd+4FE

 6、wow64cpu._TurboDispatchJumpAddressEnd+4FE:

        这里重头戏来了,在第一局就调用了_TurboDispatchJumpAddressEnd+538 处的代码,因此我们应该先看_TurboDispatchJumpAddressEnd+538 处的代码。

        然后惊奇的发现,这不就是Zw系列函数切入内核前最后执行的代码吗。按照条件选择Int 2e终端或者 syscall 切入内核例程,执行相关的服务。

        此处执行完后ret 自然是返回到之前的call 下一行执行,这里可以很明确看到该处是在还原前文提到的保存的环境状态,包含寄存器,堆栈,cs段寄存器,并且此处并不是ret返回。而是使用了jmp far 远跳的形式直接跳转到 ZwClose函数的返回点。

        总结一下,x86的Zw函数切入内核服务例程大致为以下步骤:

        切换x64环境,上下文存储,按照服务号进行一些初始化,到这里开始模拟Zw系列函数,进行切入内核服务例程的操作,完成服务进行环境还原,cs段寄存器切回x86环境,并且使用jmp far回到 Zw函数。

wow64cpu._TurboDispatchJumpAddressEnd+4FE - call wow64cpu._TurboDispatchJumpAddressEnd+538
wow64cpu._TurboDispatchJumpAddressEnd+503 - mov r14,rsp
wow64cpu._TurboDispatchJumpAddressEnd+506 - mov [rsp+04],00000023 { 35 }
wow64cpu._TurboDispatchJumpAddressEnd+50E - mov r8d,0000002B { 43 }
wow64cpu._TurboDispatchJumpAddressEnd+514 - mov ss,r8w
wow64cpu._TurboDispatchJumpAddressEnd+517 - mov r9d,[r13+3C]
wow64cpu._TurboDispatchJumpAddressEnd+51B - mov [rsp],r9d
wow64cpu._TurboDispatchJumpAddressEnd+51F - mov esp,[r13+48]
wow64cpu._TurboDispatchJumpAddressEnd+523 - jmp far [r14]
wow64cpu._TurboDispatchJumpAddressEnd+526 - int 3 
wow64cpu._TurboDispatchJumpAddressEnd+527 - int 3 
wow64cpu._TurboDispatchJumpAddressEnd+528 - int 3 
wow64cpu._TurboDispatchJumpAddressEnd+529 - int 3 
wow64cpu._TurboDispatchJumpAddressEnd+52A - int 3 
wow64cpu._TurboDispatchJumpAddressEnd+52B - int 3 
wow64cpu._TurboDispatchJumpAddressEnd+52C - nop word ptr [rax+rax+00000000]//这是一句长nop,开始call的地方就是这里了。其直接shadow了NtClose函数切入内核的代码
wow64cpu._TurboDispatchJumpAddressEnd+538 - test byte ptr [7FFE0308],01 { (0),1 }
wow64cpu._TurboDispatchJumpAddressEnd+540 - jne wow64cpu._TurboDispatchJumpAddressEnd+545
wow64cpu._TurboDispatchJumpAddressEnd+542 - syscall 
wow64cpu._TurboDispatchJumpAddressEnd+544 - ret 
wow64cpu._TurboDispatchJumpAddressEnd+545 - int 2E { 46 }

补充:ZwClose 64位dll进入内核服务代码

        该段代码是正常的,64位进程调用内核服务切入例程的正常代码。传入中断服务号后直接使用INT 2E或者 syscall进入内核。

ntdll._ZwClose    - mov r10,rcx
ntdll._NtClose+3  - mov eax,0000000F { 15 }
ntdll._NtClose+8  - test byte ptr [7FFE0308],01 { (0),1 }
ntdll._NtClose+10 - jne ntdll._NtClose+15
ntdll._NtClose+12 - syscall 
ntdll._NtClose+14 - ret 
ntdll._NtClose+15 - int 2E { 46 }
ntdll._NtClose+17 - ret 
ntdll._NtClose+18 - nop dword ptr [rax+rax+00000000]

附加

        之前看的时候乱七八糟写的笔记

jmp  地址 0x33 的cs段转移跳


保存 进入 转换点前的 esp



进行系统调用
r10 为第一个参数,	(x86转时复制栈值,x64则复制rcx)
eax=服务号	(没错,是eax)
syscall或者 int 2e   	 (标志为0 使用syscall)
ret
调用完成

rsp栈顶似乎会是返回地址?
esp=保存的esp
ss=0x2b
构建  【保存的返回地址 0x23】 的far jmp 回到x86
       CS

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值