InlineHook和API HOOK从原理开始,一次性掌握劫持技术

关于这个问题,读者可以关注作者的B站账号:画画的北北-giao

关于inlineHook的应用,作者做了两个实例:FPS全屏秒杀、FPS子弹追踪

 回到这个问题的讨论上,我们先说Hook的原理:

我们知道可执行文件是按照区块组织的(如汇编常说的.data、.code),编译之后的代码会放到.code代码段里面,然后运行的时候由解析器将机器字节码翻译成对应的机器指令,我们要执行劫持操作,肯定不能像Windows HOOK API那样调用函数,比如说你要改写执行代码的流程,让这个执行流跑到自己的代码里面去,执行完自己的代码后再回来执行原来的代码或直接跳过原来的代码,或者是对函数的参数进行一些检查,这个时候就需要用到inline Hooke(内联)技术,其实API Hook也是一样的道理,都是通过更改汇编代码来实现的

知道了原理,我们就来说下怎么实现:

我们知道编译好的代码是不能继续在原来的基础上继续更改的,因为这可能会破坏其余机器指令。

在汇编中可以转移指令执行流程的指令是JMP和CALL,我们要干的就是在要将原来的执行流中插入JMP或CALL,在开始写代码之前,你需要想清楚使用JMP还是CALL,如何使用在于你的目的,使用JMP可以保证堆栈偏移不会发生变化,但需要计算两次JMP 的偏移量,而使用CALL则只需要计算一次,就是CALL的偏移量,而回跳地址直接使用ret,但缺点是CALL会更改堆栈的偏移,因为要压入回跳地址,我通常喜欢使用CALL来做劫持,因为堆栈的偏移相对于JMP那种繁琐的计算是不值一提(😄)的。使用CALL要注意堆栈的问题是一个常见的错误(因为我经常犯😆),而且使用CALL的代码易读性比JMP要强,因为可以把CALL看成一个函数,我们只需要在功能上下功夫,而不必去关注JMP到底跳转到哪里(因为CALL的结构简单,上面写功能,结尾写ret)。

说完如何选择指令后,下一个问题就是如何重新计算JMP/CALL的偏移量:

这两个指令有3种操作数:立即数(相对偏移量)、寄存器和内存(绝对地址)。相对偏移 = 目的地址 - 起跳地址 - 指令占用字节数,或相对偏移 = 目的地址 - 返回地址(返回地址是起跳地址的下一条指令地址),而绝对地址只需要填入要跳转的地址就可以了,此时JMP/CALL的操作数就是reg/mem,然后把目的地址直接写入reg/mem,即可实现跳转操作。具体使用哪种取决于个人喜好,相对偏移的优势在于逻辑清晰,而绝对地址的优势在于代码的编写难度低,相对偏移常见的公式:相对偏移 = 目的地址 - 起跳地址 - 5 ,这个跳转只能跳转±2G的范围,这对于大多数情况都是可以使用的,而且32和64位程序都是通用的。至于JMP/CALL处的代码如何写,你只需要用CE写好自动汇编脚本,然后依次拷贝正常情况和劫持情况的代码就可以了。另外需要注意的是被JMP和CALL覆盖的代码,你应该修复这些被破坏的代码(使用CE可以轻松解决)。

最后我们需要关注的是如何自己写代码实现这个功能,我把这个过程归纳为以下这几个步骤:

1、申请内存(VirtualAlloc),写入劫持代码(就是你要实现的功能):要尽量少的使用自己定义的绝对地址,因为需要进行大量的工作,这就是为什么我喜欢使用ret的原因,因为如果代码使用的绝对地址少的话,可以直接不用管这个地方的代码,直接从CE复制过来就可以了,此外,如果要使用到绝对地址(不管是游戏的还是自定义的),将其构造成寄存器相对寻址,如mov eax, 0x11111,这样可以减少后期修复代码的难度

2、修复代码,重定位数据:如果在劫持代码里使用了大量的自定义地址(自己的全局变量),需要使用WriteProcessMemory将这些地址全部进行重新修复(因为每次申请的内存地址不确定)

3、更改劫持点代码,计算跳转偏移量(WriteProcessMemeory):使用CALL和JMP的偏移是一样的,你可以一次性的把机器码算好,然后直接写入(麻烦😭)。也可以分两次写入,第一次写入CALL/JMP offset [ E8 00 00 00 00 ],然后第二次写入算好的偏移量(InjectAddr + 1)

4、开关脚本:关脚本的话直接将原始代码写回去就行了,开脚本的话转移到我们的功能

至此,我们的劫持脚本就已经写好了(😊)

我们的最后一个问题就是API HOOK,实现参数检查和功能替换:

这里我引用了CE教程里的拦截封包的例子,Windows发包使用的是ws2_32.Send(s, buf, len, flags),该函数我们只需要关注2个参数,buf和len。这里使用的仍然是inline hook的技术,直接在该函数的头部使用进行劫持,在32位程序里,使用的是堆栈传递参数(__stdcall),参数是逆序入栈的,因此堆栈偏移如下:

esp = 返回地址

esp + 4 = s

esp + 8 = buf

esp + 12 = len

esp + 16 = flags

在64位程序里,使用的是寄存器加堆栈的参数方式(__fastcall),即前四个参数依次送入RCX、RDX、R8、R9(不逆序),后面的参数是逆序入栈的,偏移为(RSP + 8*n,n = 5, ... ,表示第几个参数)(关于64位堆栈的解释读者可参考本人的《64位汇编语言入门》)

我们要做的是如果发送了 "hello” 则将其替换为 "idot" ,这是非常简单的,你可以在buf内进行扫描,如果找到有替换掉,否则允许通行。当然你也可以直接开一块缓冲区,发送自己的数据

这里我写了一个简单的例子,CS1.6秒杀,只要受到伤害直接就嘎(😀),虽然代码是易语言写的,但是改成C++也是很容易的,直接将所有写入都改成WriteProcessMemroy就行了

可见,inline Hook并没有什么难的,需要注意的是,如果你劫持的代码里面使用了堆栈,而且你还对代码进行了一定的筛选操作,一定要重新计算堆栈偏移,本例中的[esp + 54] 因为call 的缘故更正为了 [esp + 58]

最后,关于inline Hook的讲解,你可以参考我在B站的:《FSP全屏秒杀》,里面有关于这个操作的全部讲解

  • 2
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

这⊙∀⊙!

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值