我在集会课程中收到了这个问题:
这个程序是做什么的 以及如何调用它?
push ebp
mov ebp, esp
push esi
mov esi, [ebp+4]
mov eax, [esi]
sub eax, [esi+4]
add esi, 8
mov [ebp+4], esi
pop esi
pop ebp
ret
看起来[ebp+4]
是一个参数而不是返回地址,所以它应该用“jmp”而不是“call”来调用。
我真的不明白应该在[esi+4]
和esi+8
(返回地址)上找到什么
嗯,我真的很困惑,希望你能帮助我
提前谢谢。
答案 0 :(得分:2)
正如您所理解的那样,如果使用call
调用此例程,则[ebp+4]
是返回地址。这并不意味着这是一个坏主意。
假设使用call
调用例程。此时,在堆栈上推送的“返回地址”是紧跟在call
操作码之后的字节的地址。我们称这个地址为 x 。然后,例程从地址 x 中提取两个32位字,一个在地址 x ,一个在地址 x + 4 。它从第一个单词中减去第二个单词,将结果存储在eax
中。最后,例程将值 x + 8 存储在堆栈槽[ebp+4]
中,最终效果是当达到ret
时,执行将在地址处继续执行X + 8 。有了这个假设,例程看起来像是一种减去位于代码中间的整数的方法,如下所示:
call yourroutine
dd 56478634
dd 18943675
mov ebx, eax ; an example instruction
此处,call
返回mov
指令,此时eax
包含值37534959(从56478634减去18943675)。
作为一个代码例程,它并不是非常有用,因为它是一种使用硬编码的常量值加载eax
的复杂方法(代码空间在执行期间通常是只读的)。有人可能会想到,这样的例程可能会出现在某些体系结构上动态链接的运行时支持的一部分(动态链接是一个毛茸茸的主题)。
现在,我们假设使用jmp
调用例程。 [ebp+4]
现在指定堆栈顶部的任何内容。例程取该值(让我们称之为 y ),在地址 y 和 y + 4 中获取两个单词,计算减法结果为eax
,然后将 y + 8 存储回[ebp+4]
广告位。最后,ret
将该槽解释为返回地址,即执行应跳转到的某些代码的地址。在该地址的制作中没有涉及call
操作码并不重要; ret
会跳到它。这次,调用代码可能如下所示:
push foobar
jmp yourroutine
... ; unreached code
foobar:
dd 56478634
dd 18943675
mov ebx, eax ; an example instruction
这一次,这看起来像一个参数化的跳跃,有一些eax
的固有负载。这样的代码可以存在于某些interpreters for threaded code的实现中。然而,作为一个家庭作业问题,我很确定这不是预期的(线程代码解释器甚至比动态链接更毛茸茸)。
答案 1 :(得分:1)
当你调用这个函数时,返回地址(调用后指令地址的eip)被压入堆栈,在第一个函数前同步码之后,[ebp+4]
引用该返回地址。然后,函数体将该地址视为2个整数,将其减去,结果放在eax
中,然后返回地址增加8,即add esi,8
和{{2}整数的大小增加mov [ebp+4], esi
{1}}。 ret
只是让我们回到那个新的返回地址(希望这是一个有效的指令......)。
一个奇怪的函数,在一些自修改代码等中查找...
答案 2 :(得分:1)
不,绝对应该使用call
调用它 - 最后它有一个ret
。
就它的作用而言,你应该坐下来拿一张带有寄存器列表的纸张,然后单步执行代码,随时更新寄存器。然后它应该是显而易见的:
eax:
esp:
ebp:
esi:
以及其他相关内存(例如堆栈顶部周围的区域)。
这是学习编程(无论如何适用于小程序)的理想方式,因为您学会详细分析事物并实际理解它们。而且我担心这和我要做的家庭作业一样多的帮助: - )