汇编中call、push+ret和push+jump之间的区别

7
我有一个跟踪指令,想要提取函数调用和返回。
我发现除了call指令外,push+jmppush+ret也可以用于函数调用。首先我想确定这是否正确?如果是,它们之间的区别是什么?
另外,如果push+ret是一种调用,那么函数的结束或返回会是什么呢?只有ret而没有在其前面添加push指令吗?
2个回答

9

是的,你说得对。

当一个call被调用时,被推入到栈上的返回地址是下一个执行应该继续的地址(即当前指令后面的地址)。实质上,这是一个原子性的push操作,其后跟随一个jmp操作。

这意味着,如果你手动进行pushjmp操作,那么你跳转到的函数可以稍后进行ret操作,并且在函数中所有栈访问都保持平衡的情况下,它将返回到先前推入的地址。

同样地,你可以进行push然后ret操作来模拟调用返回,但这并不能让你能够稍后再次返回。这种行为更常见于扰乱反汇编器,使其更难以确定代码实际上走向哪个地址。


这种行为更常见的做法是为了扰乱反汇编器。您知道IDA Pro是否具有正确解释这些策略作为调用/返回的功能?从一般的角度来看,您建议采取什么方向来反汇编使用这种混淆策略的代码?编辑:更多搜索产生了这个线程,引用了您的答案并回答了我的问题:http://reverseengineering.stackexchange.com/questions/10911/automatically-decode-pushret-call-into-jmp - SullX

8
简单来说:
调用地址 这将把更新后的程序计数器(指向call之后的指令)推入堆栈,然后跳转到指定的地址(可能应用寻址模式)。
ret
此指令内部弹出一个地址并跳转到它。这与call非常匹配,因此可以返回到先前call之后的指令。
jmp 地址 这只是简单地跳转到给定的地址(可能应用寻址模式)。它根本不涉及堆栈。
所以,您还可以这样做:
push address
ret

这会弹出并跳转到之前压入栈中的地址,就像上文所述。这是一种巧妙的方式,在微处理器中进行间接跳转,因为它们的跳转指令不支持间接寻址模式。

该过程:

push address
jmp someplace

这段代码会简单地跳转到某个位置,并且不影响堆栈或使用推入堆栈的地址。如果jmp后面是地址指令,那么这几乎等同于call someplace

对于不支持间接寻址跳转的指令集,我见过一个很好的解决方法:

push address
ret

这会跳转到任何 地址


当作者想要掩盖代码的真实功能时,通常会使用这些不寻常的变体(例如恶意软件和“受版权保护”的商业软件)。 - John Hascall
“ret” 本质上不就是“弹出并跳转到弹出地址的间接跳转”吗? - John Hascall
@JohnHascall 是的,这是我的疏忽。:p 我一直在想“没有call,没有ret”。 :) - lurker
1
请注意,push / ret 对性能不利,因为由于不平衡的调用/返回,会导致分支预测现在和将来的 ret 指令。CPU 使用内部堆栈来预测 ret,假设有一个匹配的 call,因为这是正常代码的行为。http://blog.stuffedcow.net/2018/04/ras-microbenchmarks/ - Peter Cordes

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接