吾爱破解 - LCG - LSG |安卓破解|病毒分析|www.52pojie.cn

 找回密码
 注册[Register]

QQ登录

只需一步,快速开始

查看: 2771|回复: 13
收起左侧

[其他转载] 32位、64位汇编语言混合编程终极大法(续)——operation size篇

[复制链接]
longs75 发表于 2021-10-29 00:30
本帖最后由 longs75 于 2021-10-29 09:51 编辑

上次发了个贴:《32位、64位汇编语言混合编程终极大法》

之后内心隐隐不安:刚刚学到一点儿知识,居然口出狂言声称“终极大法”,万一被高手过来打脸怎么办?毕竟自己也是一瓶不满半瓶晃荡的水平。
幸好,高手没来,大概是嗤之以鼻,不屑一顾,也可能忙着泡妞没时间给我上课,希望是后一种情况,不至于让我灰头土脸……总之,不能再说大话了。

言归正传,高手不来,我自己打脸。

然而我这个帖子重点并不是在32位、64位模式之间进行切换,毕竟我已经实现了,虽然算不上“终极”,但也相当好用。重点要探讨由此引出的对64位模式下operation size的认识。

以下代码用X96dbg(x64dbg+x32dbg)进行调试。

上个帖子实现32位、64位模式切换,实际上是使用了JMP远跳转指令。但是在转移指令中,具备远跳转的不只是 JMP,还有CALL/RET指令组,下面以RET为例进行探讨。

RET指令从堆栈中取出一个值给RIP(EIP),实现程序流程跳转,RET指令同样有NEAR、FAR,功能与JMP基本相同,但RET能自动平衡堆栈指针,要说终极,当然是RET。

下面用RET指令改写32位、64位模式之间进行切换的代码:

1、由32位模式进入64位模式:

00401000          | 6A 33                      | push 0x33                     | 1、把立即数00000033入栈,准备切换64位模式
00401002          | E8 02000000           | call 401009                   | 2、把下一条指令的地址入栈,并跳到ret far执行远返回
00401007          | EB 01                      | jmp 40100A                  | 4、远返回到这里啦!这条指令已经在64位模式下!现在跳出这个圈子
00401009          | CB                          | ret far                            | 3、远返回到: 0x33:00401007,进入64位模式!
0040100A          | 90                          | nop                               | 5、好了,跳到这里,开始自己的64位编程吧

上面这个代码段用ret far 指令实现了由32位模式进入64位模式,而且堆栈是平衡的,不用额外调整,比jmp far舒服多了,就是进入时的流程有点儿怪怪的,这是因为利用了call指令获取当前EIP,避免绝对寻址,如果写成下面这样:

00401040          | 6A 33                      | push 0x33                    |
00401042          | 68 48104000          | push 401048                 | 绝对地址入栈?代码写成这样太失败了
00401047          | CB                          | ret far                            |
00401048          | 90                          | nop                                |

虽然也能进入64位模式,但是68 48104000  | push 401048已经把绝对地址嵌入了代码,换个地址就不能用,相当于一次性使用,玩汇编的人一般都接受不了这种情况。

好了,到目前为止,利用ret far指令由32位模式进入64位模式很顺利。下面要实现由64位模式进入32位模式。

64位模式下,所有操作应当都是64位的吧?(是这样吗?打个问号吧)

查Intel 指令手册,ret指令的操作真是太复杂了,总共有10页!!大多数都是出错信息,鬼才看得懂,我是没耐心、也没能力完整的看,只能找重点。

……
ELSE (* OperandSize = 64 *)     如果操作数尺寸=64;
RIP := Pop();                   弹出RIP;
CS := Pop(); (* 64-bit pop; high-order 48 bits discarded; seg. descriptor loaded *)  弹出CS(弹出64位,高48位丢弃)
……

指令手册上说,如果操作数是64位,则先弹出RIP(64位),再弹出CS(弹出64位,高48位丢弃,CS=低16位)

好象明白了,64位操作系统不就是64位操作数嘛(是这样吗?再打个问号吧),仿照上面进入64位模式的代码,写一下退出64位模式的代码:

000000013FAF1030  | 6A 23                          | push 0x23                      | 1、64位立即数0000000000000023入栈
000000013FAF1032  | E8 02000000               | call 13FAF1039              | 2、把下一条指令的地址(64位)入栈,并跳到ret far执行远返回
000000013FAF1037  | EB 01                          | jmp 13FAF103A             | 真的能返回到这里吗?别做梦了!
000000013FAF1039  | CB                              | ret far                             | 3、希望能远返回到: 0x23:000000013FAF1037,进入32位模式!——结果坐飞机了,弹出异常
000000013FAF103A  | 90                              | nop                                | 这是一段失败的代码,跑飞了

问题出在哪里?上面打问号的地方是重点!!!

再查Intel指令手册,从OperandSize(操作数尺寸)入手,找出问题所在。手册版本是:Order Number: 325462-075US  June 2021

手册第2部分第2章有这样一节:

2.2.1.7  Default 64-Bit Operand Size  默认的64位操作数尺寸
In 64-bit mode, two groups of instructions have a default operand size of 64 bits (do not need a REX prefix for this
operand size). These are:  在64位模式中,两组指令的操作数尺寸默认是64位(不需要用REX前缀表明是64位操作数尺寸),它们是:
. Near branches. 近转移
. All instructions, except far branches, that implicitly reference the RSP. 所有隐含涉及RSP的指令(远转移除外)。

根据手册描述,ret far 远返回指令,默认的Operand Size不是64位!!!

再查Intel指令手册第2部分第4章第4.3节关于ret指令的描述中,有这么一段:

In 64-bit mode, the default operation size of this instruction is the stack-address size, i.e. 64 bits. This applies to
near returns, not far returns; the default operation size of far returns is 32 bits.

翻译一下:在64位模式中,这条指令默认的操作数尺寸是堆栈的尺寸,即:64位,这适用于近返回,不是远返回;远返回默认的操作数尺寸是32位!

这下全明白了!!

在64位模式中,近转移(jmp\call\ret 的near方式)默认操作数尺寸是64位,远转移(jmp\call\ret 的far方式)默认操作数尺寸是32位!

再看上面那段有问题的代码,PUSH了两个64位数,然后用默认的ret far远返回,而ret far 默认的是32位的操作数,CS寄存器都乱套了,所以直接跑飞。

重写退出64位的代码:

2、由64位模式进入32位模式:

000000013FAF1040  | E8 00000000                          | call 13FAF1045                             | 把下一条指令的地址(64位)入栈,并跳到下一条指令继续执行
000000013FAF1045  | 830424 0E                              | add dword ptr ss:[rsp],E               | [rsp]中是这条指令的地址,dword ptr[rsp]是rip的低32位(即eip),计算一下出口地址
000000013FAF1049  | 66:C74424 04 2300                | mov word ptr ss:[rsp+4],23          | 高32位是00000023,给CS,用来返回32位模式
000000013FAF1050  | CB                                          | ret far                                            | 64位模式下的远返回,默认操作数尺寸是32位!所以上面两个操作数都是32位的
000000013FAF1051  | 90                                          | nop                                                | 这就是正确的32位模式返回地址,这里已经是32位模式了

=========我是分割线,问题解决了,但思考还在继续===========

前面提到64位模式下的REX前缀,它是一个字节,指令手册上对它有详细描述,这个字节的高4位(即第4—7位)固定是0100,即16进制数字“4”,第3位表示operation size, 1:64位,0:其它 。第0位、第2位与寄存器和寻址方式有关

再进一步总结一下:REX前缀用于64位模式,一个主要功能是指示operation size。它是一个字节,范围是40—4F:40—47表示指令是32位operation size,48—4F表示指令是64位operation size。看看下面这些例子:

000000013FAF105B  | 48:8B03                         | mov rax,qword ptr ds:[rbx]                  | REX=48,64位operation size
000000013FAF105E  | 4D:89D0                         | mov r8,r10                                          | REX=4D,64位operation size
000000013FAF1061  | 44:89D8                         | mov eax,r11d                                      | REX=44,32位operation size
000000013FAF1064  | 41:8936                         | mov dword ptr ds:[r14],esi                  | REX=41,32位operation size

上面提到过,64位模式中,只有两种情况下默认的operation size是64位,一种情况是转移指令,一种情况是与堆栈有关的指令。换句话说,其它指令默认的operation size都是32位,再看下面的例子:

000000013FAF1070  | 8B03                            | mov eax,dword ptr ds:[rbx]                  |
000000013FAF1072  | 8932                            | mov dword ptr ds:[rdx],esi                   |
000000013FAF1074  | 89C8                            | mov eax,ecx                                         |

可以看到,前两个指令虽然用到了64位寄存器,但执行的是32位操作,指令operation size都是32位,因此不需要REX前缀。当然,加了REX前缀也无所谓,比如:

000000013FAF101C  | 40:89D8                         | mov eax,ebx                                 |
000000013FAF101F  | 89D8                              | mov eax,ebx                                 |

铺垫了这么多内容,再次回到32位、64位模式切换话题,现在的问题是:ret far默认的operation size是32位,给指令加个REX前缀,强制operation size为64位,可以吗?

首先告诉大家,不要翻手册了,手册上没有明确答案,手册给了我们原理,弄明白了原理,下面就是自由发挥的空间:

在ret far 指令前加个REX前缀(48—4F都可以), 强制operation size为64位,重写刚才那个错误的由64位模式进入32位模式的代码:

000000013FAF1030  | 6A 23                            | push 23                                                | 1、64位立即数0000000000000023入栈
000000013FAF1032  | E8 02000000                 | call empty64.13FAF1039                      | 2、把下一条指令的地址(64位)入栈,并跳到ret far执行远返回
000000013FAF1037  | EB 02                            | jmp empty64.13FAF103B                      | 4、成功了!指令返回到这里,进入32位模式,现在跳出圈吧
000000013FAF1039  | 4B:CB                           | ret far                                                     | 3、希望能远返回到: 0x23:000000013FAF1037,进入32位模式!
000000013FAF103B  | 90                                | nop                                                        | 5、好了,跳到这里,继续自己的32位编程

需要注意的是,在调试软件X96dbg(x64dbg+x32dbg)中,没办法输入 4B:CB 对应的汇编语句,X96dbg(x64dbg+x32dbg)使用了两个独立的汇编模块:XEDParse和asmjit都不支持带REX前缀的 ret far,我在github上找的Keystone甚至连远转移指令都不支持,
所以只能以编辑内存数据的方式,手工填入4B CB。



===========这就是底线,另外再补充两句===========

虽然是以32位、64位模式切换为例,但重点探讨了64位模式下的operation size问题,理解了operation size的规则,以及两种特殊情况运用,对64位编程就会有更深刻的理解。上面只是以ret指令为例进行了详细探讨,jmp、call指令与ret也是同样问题,这里就不再详细讲了。

一点儿学习心得与大家共享,有不对的地方,欢迎批评指正。



免费评分

参与人数 6吾爱币 +11 热心值 +6 收起 理由
苏紫方璇 + 7 + 1 欢迎分析讨论交流,吾爱破解论坛有你更精彩!
logafo + 1 + 1 用心讨论,共获提升!
yyb414 + 1 + 1 热心回复!
cutejiajia + 1 + 1 谢谢@Thanks!
Daihui + 1 + 1 用心讨论,共获提升!
sam喵喵 + 1 谢谢@Thanks!格式再编排下就更好看了

查看全部评分

发帖前要善用论坛搜索功能,那里可能会有你要找的答案或者已经有人发布过相同内容了,请勿重复发帖。

 楼主| longs75 发表于 2021-10-29 12:59
wapj3076 发表于 2021-10-29 10:31
太深奥了,看不懂。楼主能编制一个教程供小白们学习吗?

我也是刚刚入门,还远远达不到编教程的水平,就是帖子里的内容,过段时间再看,也有很多不足甚至错误的地方,交流一下还可以,这水平怎么能编教程呢,见笑了。
 楼主| longs75 发表于 2021-10-29 13:01
max2012 发表于 2021-10-29 09:38
排版不是很好,看着像小说,虽说看不太懂,感谢分享

更象是笔记,确实有点儿乱。
爱腾讯爱生活 发表于 2021-10-29 00:57
qiwenchun 发表于 2021-10-29 00:59
谢谢分享!!!
sergin 发表于 2021-10-29 08:02
谢谢楼主分享学习经验。虽然看不懂
hu007 发表于 2021-10-29 09:00
楼主太谦虚了。学习了!
xugdawn 发表于 2021-10-29 09:12
学习到了,感谢楼主
Spa495 发表于 2021-10-29 09:17
感谢楼主分享,学习了
minibeetuaman 发表于 2021-10-29 09:30
仔细研究intel的manual这种精神值得学习
max2012 发表于 2021-10-29 09:38
排版不是很好,看着像小说,虽说看不太懂,感谢分享
wapj3076 发表于 2021-10-29 10:31
太深奥了,看不懂。楼主能编制一个教程供小白们学习吗?
您需要登录后才可以回帖 登录 | 注册[Register]

本版积分规则 警告:本版块禁止灌水或回复与主题无关内容,违者重罚!

快速回复 收藏帖子 返回列表 搜索

RSS订阅|小黑屋|处罚记录|联系我们|吾爱破解 - LCG - LSG ( 京ICP备16042023号 | 京公网安备 11010502030087号 )

GMT+8, 2024-4-27 09:32

Powered by Discuz!

Copyright © 2001-2020, Tencent Cloud.

快速回复 返回顶部 返回列表