【问题标题】:JMP to absolute address (op codes)JMP 到绝对地址(操作码)
【发布时间】:2026-01-19 15:00:01
【问题描述】:

我正在尝试编写一个 exe 打包程序/保护程序,以此来了解更多关于汇编程序、c++ 以及 PE 文件如何工作的信息。我目前已经让它工作了,所以包含 EP 的部分与一个密钥进行异或,并创建了一个包含我的解密代码的新部分。一切都很好,除非我在解密后尝试 JMP 到原始 EP。

基本上我是这样做的:

DWORD originalEntryPoint = optionalHeader->AddressOfEntryPoint;
// -- snip -- //
    crypted.put(0xE9);
 crypted.write((char*)&orginalEntryPoint, sizeof(DWORD)); 

但 ollydbg 并没有跳转到入口点,而是显示这段代码反汇编为:

00404030   .-E9 00100000    JMP 00405035 ; should be 00401000 =[

当我尝试在 olly 中手动更改它时,新的操作码显示为

00404030    -E9 CBCFFFFF    JMP crypted.00401000

0xCBCFFFFF 是从哪里来的?我将如何从 C++ 端生成它?

【问题讨论】:

标签: assembly x86 executable masm opcode


【解决方案1】:

我认为E9 是相对跳转的操作码:它的操作数指定要跳转的相对距离,从下一条指令开始的正负。

如果你想让操作数指定一个绝对地址,你需要一个不同的操作码。

【讨论】:

  • 对于 E9 的替代品有什么想法吗?
  • 跳跃通常是相对的。有一个操作码EA 用于跳转到绝对远地址,而操作码用于跳转到间接地址(其中操作数指定包含要跳转到的地址的内存位置)。
  • 这没有回答问题,只是确认OP的答案有误。
  • @baordog 问题是,“0xCBCFFFFF 是从哪里来的?”无论如何,显然 OP 理解了答案,和/或对评论中的EA 感到满意。
【解决方案2】:

你可以使用:

push DESTINATION_VA
ret

mov eax,DESTINATION_VA
jmp eax

相对 E9 jmp 编码是这样使用的:

CURRENT_RVA: jmp (DESTINATION_RVA - CURRENT_RVA - 5 [sizeof(E9 xx xx xx xx)])

push + ret 是最好的解决方案,如果你有 VA 地址并且图像没有重新定位

【讨论】:

【解决方案3】:

绝对间接跳转的操作码是 FF + 4byte 地址。这最常用于存储在数据中的地址的跳转表。

绝对地址在未加载到预期地址时确实需要重定位,因此通常首选相对地址。相对跳转的代码也小了 2 个字节。

英特尔优化手册指出 cpu 期望 call 和 ret 成对使用,因此答案 2 中建议的没有调用的 ret 会导致他们所谓的“性能损失”。

此外,如果代码没有加载到编译器假定的相同地址,则 ret 可能会使程序崩溃。计算相对地址会更安全。

【讨论】:

  • 也许你的意思是 FF 25 4byte.. FF 4 byte 根本没有意义。
  • @Laie 它仍然有意义,因为FF 是操作码,我们知道助记符是jmp。可能是FF /4FF /5,因为它们都是绝对间接跳跃——一个是近跳,另一个是远跳。但是,是的,FF 25 aa bb cc dd 是您在机器代码中观察 FF /4(两者中更常见的一种)的方式。
  • 最后一段不对:push imm32/ret不会崩溃,重定位代码不会改变ret跳转到的绝对目标地址。问题在于性能:不平衡返回地址堆栈会使一些未来的返回错误预测。 mov reg,imm32 + 2 字节寄存器间接跳转可能是您最好的选择(而不是从内存中加载指针),如果由于某种原因您不能只编码 jmp rel32 from 已知地址,。 Call an absolute pointer in x86 machine code.
  • 如果能提及 64 位模式的匹配 jmp 操作码也很好。