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

article/2025/9/26 8:32:25

前言

        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 回到x86CS


http://chatgpt.dhexx.cn/article/jKErhQHg.shtml

相关文章

mysql版本wow64平台_WOW64最佳实现

WOW64简介 WOW64(Windows 32-bit On Windows 64-bit)是x64平台上运行win32应用程序的模拟器,它在系统层提供了中间层,将win32的系统调用转换成x64进行调用,并且将x64返回的结果转换成win32形式返回给win32程序。下图描述了win32程序如何在x64…

WOW64机制

WOW64: Windows On Windows64 是一种在64位OS中支持运行的32位应用程序的机制. 64位Windows中, 64位应用程序会加载64位的kernel32.dll, ntdll.dll, 32位应用程序会加载32位的kernel32.dll, ntdll.dll. WOW64则会将32位的dll文件的API请求重定向到64位的dll文件. 注意内核模式…

java for数组遍历数组_Java foreach操作(遍历)数组

语法: 我们分别使用 for 和 foreach 语句来遍历数组 运行结果: 练习: import java.util.Arrays; public class HelloWorld {public static void main(String[] args) {// 定义一个整型数组,保存成绩信息 int[] scores = { 89, 72, 64, 58, 93 }; // 对Arrays类对数组进行排…

【有趣的实验】JAVA 遍历数组的几种方式的耗时对比

一、前言 出于对遍历方式的耗时想法&#xff0c;是普通for循环、fori、foreach、迭代器 iterator、还是steam流的形式哪种耗时更少呢&#xff1f; 首先添加一个List 集合&#xff0c;这边采用ArraryList ArrayList<Integer> list new ArrayList<Integer>();list …

Java高逼格遍历数组的方式

在普通for循环遍历或取数组元素或做其他操作时&#xff0c;总会以将要超出数组最大索引作为循环结束的标志&#xff0c;也就是说每一次循环都要去判断此条件是否满足 而forEach强就强在它无需这一步流程&#xff0c;所以效率要高 但在今天我无意写出了一种这样的遍历方式&…

java遍历数组的三种方式

for循环遍历 用for循环遍历数组是很常见的一种方法&#xff0c;Java语言中通过数组的length属性可获得数组的长度。 package demo; publicclasstest{ public static void main(String[] args){ int [] array {1,2,3,4,5};for(int i 0;i < array.length;i){ System.out.print…

Java基础-遍历数组

Java基础-遍历数组 1、语法简介2、一维数组3、二维数组4、三维数组 1、语法简介 在Java中&#xff0c;对for语句的功能给予了扩充、加强&#xff0c;以便更好的遍历数组。 语法格式如下&#xff1a; for(声明循环变量:数组的名字){......... }其中&#xff0c;声明的循环变量…

JAVA 遍历数组求平均值与最大值

求平均值与最大值 实现前需要懂得如何获取数组长度实现数组遍历 例如&#xff1a; int[] array {2,3,6,8,18}; for(int i 0 ; i<array.length; i){ System.out.print(array[i] ","); } 输出结果&#xff1a;2,3,6,8,18 求最大值时则采用擂台制现默认一个数…

java遍历数组(Java遍历数组拿取对应的)

java中怎样遍历数组对象的值 int[] a {1,2,3}; for(int i0;i System.out.println(a[i]); }//定义一个string类型的一维数组 string[] namesnew string[5]; //为数组赋值 for (int i 0; i < names.length; i) { names[i]integer.tostring(i); //使用foreach遍历数组 int in…

java遍历数组最简洁的方法,java遍历数组的方式有哪些?

我们也了解Java也已经很久了&#xff0c;那今天小编想问大家是否知道java遍历数组的方式有哪些?是不是内心已经已经有答案了?让就跟着小编的步伐一起看看吧。 1. for循环遍历 这是最基本的遍历方式 通常遍历数组都是使用for循环来实现。遍历一维数组很简单&#xff0c;遍历二…

java中遍历数组方法

1循环遍历数组方法 在main主函数中给数组赋值即可调用该方法 public static void fun07(int[] array) {System.out.print("[");//输出一个左中括号if (array ! null) { //如果数组不为空&#xff0c;执行下面的语句for (int i 0; i < array.length; i) { /…

Java中数组遍历的五种方法

遍历&#xff1a;Stream遍历&#xff0c;循环遍历 //TODO 为Java提供的一种书签import java.util.Vector; import java.util.Enumeration; import java.util.Iterator; import java.util.Random;/*** 数组多种遍历偶数方法* * author 轩**/ public class Main {public static v…

Java:遍历数组的三种方法

1、for循环遍历数组 用for循环遍历数组是很常见的一种方法&#xff0c;Java语言中通过数组的length属性可获得数组的长度。 package demo;public class test {public static void main(String[] args) {int [] array {1,2,3,4,5};for(int i 0;i < array.length;i) {Syste…

java中数组遍历的三种方式

1.for循环遍历 通常遍历数组都是使用for循环来实现。遍历一维数组很简单&#xff0c;遍历二维数组需要使用双层for循环&#xff0c;通过数组的length属性可获得数组的长度。 2.Arrays工具类中toString静态方法遍历 利用Arrays工具类中的toString静态方法可以将一维数组转化为字…

Oracle--常用数据库对象(视图、序列、索引、同义词)详解

常见的数据库对象 对象描述表基本的数据存储集合&#xff0c;由行和列组成视图从表中抽出的逻辑上相关的数据集合序列提供有规律的数值索引提高查询的效率同义词给对象起别名 视图 什么是视图&#xff1f; 视图是一个 虚 表 \color{red}{虚表} 虚表视图建立在已有表的基础上…

Oracle【基础函数、视图、索引】

1. 认识oracle数据库 Oracle是非常强大的数据库软件。默认端口&#xff1a;1521。与MySQL不同的是&#xff0c;Oracle数据库的概念是一个操作系统装的就是一个大的数据库。一个数据库可以有很多个实例&#xff0c;每个实例占用一系列的进程和内存。通常一台机器只用一个实例。每…

主要是sql查询符合在圆形,多边形区域经纬度的数据

主要是sql查询符合在圆形&#xff0c;多边形区域经纬度的数据 查询 圆形 中心点 距离 drop table if exists demo; CREATE TABLE demo (id int(5) NOT NULL AUTO_INCREMENT COMMENT 主键,shop_name varchar(50) DEFAULT NULL COMMENT 商品名称,lng DECIMAL( 11, 8 ) DEFA…

mysql计算相关系数_用sql实现相关系数的计算

登录后查看更多精彩内容~ 您需要 登录 才可以下载或查看,没有帐号?立即注册 x 本帖最后由 静言_GRMC 于 2018-5-25 13:07 编辑 平时工作中要用到好多统计查询功能,一直用sql比较顺手,最近需要做相关系数方面的统计,发现除了Oracle自带了函数外,ms sql和mysql都没有自带计…

【SQL数据库基础05-2】常见约束与标识列

文章目录 常见约束01 基本概念1.1 含义&#xff1a;1.2 分类&#xff1a;六大约束1.3 添加约束的时机&#xff1a;1.4 约束的添加分类&#xff1a;1.5 主键和唯一的大对比&#xff1a;1.6 外键&#xff1a; 02 具体讲解2.1 创建表时添加约束1.添加列级约束2.添加表级约束 2.2 修…

关系数据库标准语言SQL视图模块

目录 一. 视图的特点 二. 基于视图的操作 2.1 建立视图 1&#xff09;行列子集视图举例&#xff1a; 2&#xff09;基于多个基表的视图&#xff1a; 3&#xff09;基于视图的视图&#xff1a; 4&#xff09;带表达式的视图&#xff1a; 5&#xff09;建立分组视图&am…