将 JNZ(两字节操作码)修补到 JMP 附近(一字节操作码)?

逆向工程 艾达 C++ 函数挂钩
2021-06-20 05:19:44

我一直在修补 Fallout New Vegas,并专注于修改游戏引擎中硬编码的非常简单的记录。它被称为图像空间修改器,这是一个非常基本的着色器,只能调整选定数量的变量。它可以在编辑器中进行修改,但不能停止应用到屏幕上。所以即使它的值设置为零,它仍然由游戏计算。

所以我决定尝试找到调用图像空间修饰符的硬编码函数。我想我明白了,但我遇到了问题。它使用一个 JNZ 来确定是否处理记录,这是一个两字节的操作码指令(0F 85)。但 JMP Near 是一个单一的操作码指令 (E9)。所以我不能只是简单地在内存中修补那个词并总是跳过调用图像空间修饰符的代码。

我需要做什么才能解决这个问题,并使用 JMP 而不是 JNZ?请注意,我用来在运行时修改流程的框架使用 C++,因此它需要在该语言中可行。

截图:https : //i.imgur.com/NSNP04Y.png

2个回答

0f 85 在 32 位和 64 位模式下采用双字或 rel32 或 4 个字节

和 16 位模式下的两个字节或 rel16

我假设是 32 /64 位,因为(屏幕截图中的十六进制视图突出显示了 6 个字节的选择)

0f 85 b5 00 00 00

用 e9 jmp

0f 85 b5 00 00 00更改e9 b6 00 00 00 90

0:000> eb .
00007ffc`01d82dbc cc 0f 
00007ffc`01d82dbd eb 85
00007ffc`01d82dbe 00 b5
00007ffc`01d82dbf 48 00
00007ffc`01d82dc0 83 00
00007ffc`01d82dc1 c4 00
00007ffc`01d82dc2 38
0:000> u . l1
ntdll!LdrpDoDebuggerBreak+0x30:
00007ffc`01d82dbc 0f85b5000000    jne     ntdll!LdrpGetProcApphelpCheckModule+0xab (00007ffc`01d82e77)
0:000> eb .
00007ffc`01d82dbc 0f e9
00007ffc`01d82dbd 85 b6
00007ffc`01d82dbe b5 00
00007ffc`01d82dbf 00 00
00007ffc`01d82dc0 00 00
00007ffc`01d82dc1 00
0:000> u . l1
ntdll!LdrpDoDebuggerBreak+0x30:
00007ffc`01d82dbc e9b6000000      jmp     ntdll!LdrpGetProcApphelpCheckModule+0xab (00007ffc`01d82e77)
0:000>

或者作为 igorsk 在 nop 中编辑第一个字节并修改第二个和第三个字节

0:000> eb .
00007ffc`01d82dbc e9 90
00007ffc`01d82dbd b6 e9
00007ffc`01d82dbe 00 b5
00007ffc`01d82dbf 00 00
00007ffc`01d82dc0 00 00
00007ffc`01d82dc1 00 00
00007ffc`01d82dc2 38
0:000> u . l2
ntdll!LdrpDoDebuggerBreak+0x30:
00007ffc`01d82dbc 90              nop
00007ffc`01d82dbd e9b5000000      jmp     ntdll!LdrpGetProcApphelpCheckModule+0xab (00007ffc`01d82e77)
0:000>

对于无条件跳转,如果修补的字节较少,则立即将字节跳转到修补的指令并不重要。

但是对于将落入下一条指令的其他指令,指令边界确实很重要

假设您修补了 add 到 sub 并且 sub 少了一个字节。
所以在执行少一个字节的 sub 之后,下一条指令将开始在虚假的流氓字节上执行。
这不是本意。我们需要执行原本应该执行的字节,我们不能在半空中飞行。
我们还需要执行虚拟流氓字节

所以我们把它改成一个1字节的无操作指令。

执行 sub 和 nop 然后我们开始执行实际的原始下一条指令。

这就是为什么 nop 在这里对于这条特定指令看起来很傻,但在指令边界内修补所有字节是一个好习惯。

有单字节 nop 以及多字节 nop 或像 mov eax,eax 这样的指令,它们不会改变状态但提供空间填充。

顺便说一句,当您阅读说明手册时,rel32 始终是一个计数器,它从下一条指令的开始开始计数,地址 0x1000 处的简单跳转 eb 00 跳转到 0x1002,因为 eb 00 是两个字节,将 2 添加到 0x1000 将使下一个 $ip 0x1002 所以eb 01->0x1003 , eb 02 -> 0x1004 等等

E9是所谓的近跳转,它采用四字节偏移量 (rel32),因此您实际上无法将其放入两个字节中。如果您有一个两字节jnz( 75 xx),则可以改用短跳转( EB),它采用一字节偏移量 (rel8),就像jnz.

对于 Near jnz( 0F 85 rel32) 你可以用 NOP ( 90)修补第一个字节并替换85E9- 这应该给你相同的目的地,但无条件跳转。

参考:

https://www.felixcloutier.com/x86/jmp

https://www.felixcloutier.com/x86/jcc