Menooker / PFishHook

An x64 inline hook library
Apache License 2.0
30 stars 10 forks source link

Replace old function with regenerated instructions #9

Closed jacksonfu closed 3 months ago

jacksonfu commented 3 months ago

我们的进程(可能是动态库比较多?)mmap出来的位置至少比旧函数的地址大INT32_MAX,原代码中会限制32位的偏移。 这里设想一个方案,重新生成RIP relative的代码,如果原先地址范围过小不足以容纳hook后需要改变的,就重新生成64位的,用encoder的话可能会更符合汇编语法,如果更改偏移是不合法的,encode也会返回错误。 不过jne类似的指令,还是不能超过32位。这里是不是能够重新再生成这段指令,将jne的比较转为比较更复杂的,读取内存地址,再比较?上面的更改中只是简单将偏移范围改变。 测试代码里面我从libc里面找了几个满足lea,comp,mov RIP相对便宜的函数执行hook,跟以前的测试代码相比,还缺少了个jmp RIP。

jacksonfu commented 3 months ago

@Menooker 大佬能帮忙看看这种重新生成地址偏移的方案合理码?

Menooker commented 3 months ago

非常感谢您能提供这么大的改进!我觉得你的改进总体来说是合理的。不过我有几个疑问和建议:

如果原先地址范围过小不足以容纳hook后需要改变的,就重新生成64位的

请问这个是具体在哪里做到的呢?我只看到了对于32位偏移的操作。另外,如果原来的指令是8位偏移(例如jnz),是不是自动改成了32位呢(变成32位的jnz)? 我不是很清楚zydis的encoder是不是可以通过偏移大小,选择合适的指令?

不过jne类似的指令,还是不能超过32位。这里是不是能够重新再生成这段指令,将jne的比较转为比较更复杂的,读取内存地址,再比较?

原先代码处理过这类情况。为了处理8位偏移的跳转指令,先把这个跳转改成跳转到shadow func的尾部,然后再加一条无条件64位jmp (用GenerateJmpLarge)。假设hook之前oldfunc原始指令是

other_instructions
jne 0x20(%rip)   ;;;; <<< jump 到orig_jne_target
other_instructions

label_continue:
other_instructions

orig_jne_target:
other_instructions

hook后,shadow func大概的生成的代码是:

other_instructions
jne jump_bed
jump_large label_continue  ;;;;; <<<<<< 这里是正常shadow func的结尾

jump_bed:
jump_large orig_jne_target

我建议可以分两个PR来做。第一步先完成你说的通过zydis重写指令的工作。然后考虑如何处理jmp。有一点之前没有考虑的是如果这个jump的target本来就在shadow func内部,其实偏移是完全不需要改的

Menooker commented 3 months ago

我提交了一个PR https://github.com/Menooker/PFishHook/pull/10 可以启用CI和自动测试。 你添加的这几个test能否通过这个新的方法完成测试呢?

另外,从hook-test的log可以看到目前主分支处理jmp的方式:

==============================
testfunc
==============================
Before Hook:
0x55a94bd77009: jnbe 0x000055A94BD77010
0x55a94bd7700b: call 0x000055A94BD65695
0x55a94bd77010: call 0x000055A94BD65695

================
Hook status=0
After Hook:
0x55a94bd77009: jmp 0x000055A94BD644A9
0x55a94bd7700e: int3
0x55a94bd7700f: int3
0x55a94bd77010: call 0x000055A94BD65695

Shadow Func:
0x55a94ad77018: jnbe 0x000055A94AD7702D
0x55a94ad7701a: call 0x000055A94BD65695
0x55a94ad7701f: jmp [0x000055A94AD77025]
0x55a94ad77025: [data64]
0x55a94ad7702d: jmp [0x000055A94AD77033]
0x55a94ad77033: [data64]
==============================
jacksonfu commented 3 months ago

大佬,感谢您提供了很多建议!

请问这个是具体在哪里做到的呢?我只看到了对于32位偏移的操作。

我之前设想是在出问题的偏移上(>int32_max)上能生成出来64位的偏移(所有过小的取值全改成64位的),因为对汇编不太熟悉,我以为其实是能够生成出来64位的偏移的。实际上我少测试了一种情况(>int32_max),lea应该是不能带上(>int32_max)相对偏移的。 刚刚测试了下,zydis的encoder也会报错。 是不是在这种情况下,只能改掉原本hook函数的指令(比如lea改成mov),用绝对地址来取到对应的原位置的值?

为了处理8位偏移的跳转指令,先把这个跳转改成跳转到shadow func的尾部,然后再加一条无条件64位jmp (用GenerateJmpLarge)。 有一点之前没有考虑的是如果这个jump的target本来就在shadow func内部,其实偏移是完全不需要改的

感谢大佬给了jump bed详细的例子,之前确实忽略了这种情况。

jacksonfu commented 3 months ago

我提交了一个PR #10 可以启用CI和自动测试。 你添加的这几个test能否通过这个新的方法完成测试呢?

我的测试确实不够完善,还是应该去校验生成出来的代码是否符合以前旧函数偏移

jacksonfu commented 3 months ago

我先关闭这个pr了,我去issue里面提一下这个问题(>int32_max)