为啥将调用函数提前 1 个字节会导致它发生故障?修改啥来解决?

Posted

技术标签:

【中文标题】为啥将调用函数提前 1 个字节会导致它发生故障?修改啥来解决?【英文标题】:Why does moving a call function 1 byte earlier causes it to malfunction? What to modify to resolve it?为什么将调用函数提前 1 个字节会导致它发生故障?修改什么来解决? 【发布时间】:2021-03-18 08:50:13 【问题描述】:

我正在尝试创建一个内联 codecave 来修改一个非常古老的 dosgame (ROTK2)。但是,远调用无法重新定位 - 将函数调用的机器代码提前 1 个字节会导致它发生故障。我需要重新调整哪些参数来纠正问题?

为了更具体地完成所需任务,我需要创建 3 个字节的空间才能添加装配线。因此我需要重新定位代码(由于脚本中缺乏资源,编解码可能不是一个可能的选项,我必须找到内联的 3 个字节)。

整个程序的代码行太多,我不是很理解,但是我正在使用带断点的dosbox调试器来精确定位到相关的行:

代码的这个位置打印出游戏中当前时间点的年、月和季。从第 4EE8 行开始,两次按下 ax 将光标移动到屏幕上的位置 (2,4),因此年份位于 [0x44] 和月份,数字位于 bl [0x46],然后需要将其修改为在其他地方的文本中引用月份。然后打印出字符串,并重复该过程。

... 00004EDF 0E 推 cs 00004EE0 E80500 调用 0x4ee8 00004EE3 0E 推 cs 00004EE4 E85101 呼叫 0x5038 00004EE7 CB retf 00004EE8 B80400 mov ax,0x4 00004EEB 50 推斧 00004EEC B80200 mov ax,0x2 00004EEF 50 推斧 00004EF0 9A3404EF03 调用 0x3ef:0x434 00004EF5 83C404 添加 sp,字节 +0x4 00004EF8 FF364400 推字 [0x44] 00004EFC 8A1E4600 mov bl,[0x46] 00004F00 2AFF 子 bh,bh 00004F02 D1E3 shl bx,1 00004F04 FFB71C36 推字 [bx+0x361c] 00004F08 B8B834 mov ax,0x34b8 00004F0B 50 推斧 00004F0C 9AE806EF03 调用 0x3ef:0x6e8 00004F11 83C406 添加 sp,字节 +0x6 00004F14 B80C00 mov ax,0xc 00004F17 50 推斧 00004F18 B80500 mov ax,0x5 00004F1B 50 推斧 00004F1C 9A3404EF03 调用 0x3ef:0x434 00004F21 83C404 添加 sp,字节 +0x4 00004F24 A04600 移动,[0x46] 00004F27 B103 移动 cl,0x3 00004F29 2AE4 子啊,啊 00004F2B F6F1 div cl 00004F2D 8AD8 mov bl,al 00004F2F 2AFF 子 bh,bh 00004F31 D1E3 shl bx,1 00004F33 FFB7D634 推字 [bx+0x34d6] 00004F37 B8CC34 mov ax,0x34cc 00004F3A 50推斧 00004F3B 9AE806EF03 调用 0x3ef:0x6e8 00004F40 83C404 添加 sp,字节 +0x4 00004F43 CB retf ....

这样做可以使代码更“简洁”而不会破坏代码: ... 00004EDF 0E 推 cs 00004EE0 E80500 调用 0x4ee8 00004EE3 0E 推 cs 00004EE4 E85101 呼叫 0x5038 00004EE7 CB retf 00004EE8 B80400 mov ax,0x4 00004EEB 50 推斧 00004EEC D1E8 shr ax,1 00004EEE 50 推斧;很好,以前的数字是双倍 00004EEF 90 nop 00004EF0 9A3404EF03 调用 0x3ef:0x434 00004EF5 83C404 添加 sp,字节 +0x4 00004EF8 FF364400 推字 [0x44] 00004EFC 8A1E4600 mov bl,[0x46] 00004F00 2AFF 子 bh,bh 00004F02 D1E3 shl bx,1 00004F04 FFB71C36 推字 [bx+0x361c] 00004F08 B8B834 mov ax,0x34b8 00004F0B 50 推斧 00004F0C 9AE806EF03 调用 0x3ef:0x6e8 00004F11 83C406 添加 sp,字节 +0x6 00004F14 B80C00 mov ax,0xc 00004F17 50 推斧 00004F18 B80500 mov ax,0x5 00004F1B 50 推斧 00004F1C 9A3404EF03 调用 0x3ef:0x434 00004F21 83C404 添加 sp,字节 +0x4 00004F24 A04600 移动,[0x46] 00004F27 B103 移动 cl,0x3 00004F29 2AE4 子啊,啊 00004F2B F6F1 div cl 00004F2D 8AD8 mov bl,al 00004F2F 2AFF 子 bh,bh 00004F31 D1E3 shl bx,1 00004F33 FFB7D634 推字 [bx+0x34d6] 00004F37 B8CC34 mov ax,0x34cc 00004F3A 50推斧 00004F3B 9AE806EF03 调用 0x3ef:0x6e8 00004F40 83C404 添加 sp,字节 +0x4 00004F43 CB retf ....

但是一旦我将函数调用提前一个字节(而不是在调用 0x3ef:0x434 之前使用 nop,而是在调用 0x3ef:0x434 然后调用 nop),一切都将停止运行。

nop 优先:dosbox 记录器输出 07D2:0000039F nop EAX:00000002 EBX:00000054 ECX:00000000 EDX:000003CF ESI:00003431 EDI:00000107 EBP:0000E424 ESP:0000E416 DS:32C3 ES3GSA0000 FS:0000 ZSF:0020 :0 SF:0 OF:0 AF:1 PF:0 IF:1 07D2:000003A0致电070C:0434 EAX:00000002 EBX:00000054 ECX:000003CF ESI:00003431 EDI:00000107 EBP:0000E424EP:0000E416 DS:32C3 ES:A000 FS:0000 GS:0000 SS:32C3 CF :0 ZF:0 SF:0 OF:0 AF:1 PF:0 IF:1

没有nop: 07D2:0000039F致电20EF:0434 EAX:00000002 EBX:000000005 EDX:000003CF ESI:00003431 EDI:00000107 EBP:0000E424EP:0000E416 DS:32C3 ES:A000 FS:0000 GS:0000 SS:32C3 CF :0 ZF:0 SF:0 OF:0 AF:1 PF:0 IF:1

我一直在阅读汇编、bp、sp、ss 之类的东西,但我仍然卡住了。因此提出一个问题,即如何在不破坏代码的情况下重新定位函数调用?

【问题讨论】:

跳转和调用使用相对位移,因此如果任何调用越过您插入填充的点,更改两个代码块之间的距离将会破坏内容。 那个远调用函数似乎使用绝对寻址,所以我不确定为什么移动调用会是一个问题,因为你没有移动被调用例程的位置。我能想象的唯一奇怪的事情是,如果被调用函数实际上引用了相对于调用者在堆栈上的返回地址的地址。在汇编程序中做了一些奇怪的事情。 @RufusVS:这只是使用计算的绝对地址进行的反汇编。 x86 没有绝对的直接近调用,只有相对的。看机器码,E80500 - 那是call rel16 @Peter Cordes:但这不是他正在移动的电话。这是他正在移动的电话:00004EF0 9A3404EF03 call 0x3ef:0x434。 9A 操作码是绝对的。 felixcloutier.com/x86/call @RufusVS:哦,我看到你说的只是call far 本身。但是除非你把nop放在它之后,否则后面每条指令的地址也会改变。 ((更新:显然他们是,我错过了那个细节)。它有近乎调用,例如更改 call 0x5038 目标处的内容显然会破坏程序。(为了实际节省空间,你想用 @ 反汇编987654328@ 到 NASM 语法中以获取分支目标的标签,然后重新组装,以获取更新的目标以进行近跳。) 【参考方案1】:

DOS 不会在恒定地址加载程序,因此如果要修改可执行文件,则远调用具有也需要更改的重定位条目。

此外,移动的 call 指令可能是 calljmp 指令的目标,这也需要更改。

【讨论】:

1) 那么如何或在哪里可以找到重定位条目? 2) 到目前为止我还没有到达那里,第一次尝试调用 0x3ef:0x434 已经是错误的,但是考虑到函数的上下文,它不应该在该位置调用指令,因为需要两次推动 ax。

以上是关于为啥将调用函数提前 1 个字节会导致它发生故障?修改啥来解决?的主要内容,如果未能解决你的问题,请参考以下文章

工人 puma 日志提前终止是啥意思,为啥会发生?

为啥函数参数在 x86 上占用至少 4 个字节的堆栈?

为啥增加递归深度会导致堆栈溢出错误?

为啥这个函数调用后数组会改变? [复制]

将OB86的故障信息保存在DB86中去

软件体系架构——质量属性