x86 Assembly为什么使用Push / Pop代替Mov?

时间:2019-06-16 12:24:38

标签: assembly x86 exploit shellcode

我有一些来自shell代码有效载荷的示例代码,显示了一个for循环并使用push / pop设置了计数器:

push 9
pop ecx

为什么不能只使用mov?

mov ecx, 9

3 个答案:

答案 0 :(得分:6)

是的,出于性能方面的考虑,通常应该始终使用mov ecx, 9作为{u1} / pop`的运行效率更高,它可以在任意位置运行港口。 (在Agner Fog测试过的所有现有CPU上都是这样:https://agner.org/optimize/


push / push imm8的正常原因是机器代码中没有零字节。这对于必须通过pop r32或任何其他将缓冲区视为以strcpy字节终止的隐式长度C字符串的一部分的缓冲区溢出的 shellcode 非常重要。

0仅适用于32位立即数,因此机器代码看起来像mov ecx, immediate。与B9 09 00 00 00按9; 6a 09 pop ecx。

(ECX是寄存器号591B9的来源:指令的低3位= 59


另一个用例是纯粹的代码大小001是5个字节(使用无ModRM编码,将寄存器号放在操作码的低3位),因为遗憾的是x86缺少针对mov r32, imm32(没有mov)的带符号扩展的imm8操作码。几乎所有可追溯到8086的ALU指令都存在这种情况。

在16位8086中,该编码将不会节省任何空间:3字节短格式mov r/m32, imm8几乎与假设的mov r16, imm16一样好,除了移动到需要mov r/m16, imm8格式(带有ModRM字节)的内存的直接位置。

由于386的32位模式没有添加新的操作码,只是更改了默认的操作数大小和立即宽度,因此32位模式下ISA中的这种“遗漏的优化”始于386。现在,mov r/m16, imm16add r32,imm32长2个字节。参见x86 assembly 16 bit vs 8 bit immediate operand encoding。但是add r/m32, imm8没有该选项,因为没有MOV操作码对其立即数进行符号扩展(或零扩展)。

有趣的事实:mov(即使以速度为代价也进行了大小优化)will compile clang -Ozint foo(){return 9;}push 9

另请参见Codegolf.SE上的Tips for golfing in x86/x64 machine code(该网站通常是出于娱乐目的而优化大小,而不是将代码放入小型ROM或引导扇区中。但是对于机器代码,进行大小优化确实具有实际应用有时,甚至以牺牲性能为代价。)

如果您已经拥有另一个具有已知内容的寄存器,则可以使用3字节的pop rax在另一个寄存器中创建9(如果EAX持有lea ecx, [eax-0 + 9])。只需操作码+ ModRM + disp8。因此,如果您已经将其他任何寄存器的异或为零,则可以避免push / pop hack。 0的效率几乎不及lea,并且在优化速度时可以考虑使用它,因为较小的代码大小在大规模上具有较小的速度优势:L1i高速缓存命中,有时在uop高速缓存中解码还不是很热。

答案 1 :(得分:2)

这可能有不同的原因。

在这种情况下,似乎这样做是因为代码较小:

具有pushpop组合的变量的长度为3个字节,mov指令的长度为5个字节。

但是,我猜想mov变体会更快...

答案 2 :(得分:0)

基本上是一样的东西。将9推入堆栈,然后将其弹出到ecx寄存器中,这与mov ecx,9基本上相同。就我个人而言,我认为9到ecx可能比将9推入堆栈然后将其弹出到ecx中更有效,但是我认为处理时间是没问题,因此考虑到两种方式的代码多么短,他们俩都同样快。