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 等等