avatar

Catalog
一次简单的Hook(下)

前一篇已经完成了对GetMsgAbstract函数的分析,发现,当执行到GetMsgAbstactByElement这一步时,已经可以根据寄存器用来传递的参数获取聊天内容,这篇来根据分析的内容编写用来Hook的dll。

Inline Hook

这里先对Inline Hook做个简要概述,它是一种通过修改指令的方法,转移程序的执行流程,在程序执行某函数前或者某函数后,先执行你定义的Hook函数,拿到需要的参数信息,再根据需要对参数信息进行加工,从而完成Hook。常见的手法如下:

Code
1
1.  jmp  xxxxxxxx             (5字节)
Code
1
2.  push xxxxxxxx/retn        (6字节)
Code
1
3.  mov eax, xxxxxxxx/jmp eax (7字节)
Code
1
4.  call Hook

根据需求不同,替换掉原本的指令长度不同,从而选择的手段也不同;本篇中采用6字节的方式

构建DLL

一般构建一个dll要分别去编写头文件,C/C++源文件以及入口点函数。首先我们先从头文件开始。

头文件

打开Visual Studio,新建一个动态链接库(DLL)项目,VS会自动帮忙生成头文件和源文件,点击头文件stdafx.h开始编写

其实VS已经帮忙生成好了大部分,只需要定义自己需要实现的函数和方便自己使用的宏即可

这里为什么不写成下面这种形式呢?

c
1
__declspec(dllexport) BOOL WINAPI Msg_Hook();

因为这个dll的主要作用是hook,并不需要有导出函数,即使有了被我们hook的程序也不会主动去调用(因为它的代码里面根本没有调用我的dll的代码),所以干脆就不写了,没什么影响。

入口点函数

VS帮我们生成好的入口点函数如下:

在这里其实只需要把刚刚定义的函数,写在DLL_PROCESS_ATTACH的地方即可,因为在dll加载到进程时,会先调用入口点函数,传入的参数则是DLL_PROCESS_ATTACH,这样就可以调用我们的Hook函数了。

源文件

源文件的编写是比较关键的一步,主要功能的实现都在这里。首先,我们需要实现Hook用的函数。

Hook函数

首先编写一个大致的框架,来分析一下都做了些什么,还缺一些什么。

Inline Hook的核心在于修改指令,从而实现程序流程的转移。具体的流程就是,找到需要修改的位置,修改当前位置的指令。

修改指令的大小

这里采用的是push xxxxxxxx/retn的手法,所以需要创建一个6个字节的char型数组。

修改的位置

修改的位置如何确定?之前在OD分析反汇编程序时,确定在调用GetMsgAbstractByElement之前就可获取到消息内容,发现,调用这个函数的call语句加上之前的push eax,刚好6个字节,这就是Hook点了 这个位置位于KernelUtil.dll中,所以我们可以补充第三条语句改成如下:

Code
1
DWORD modify_addr = (PROC)GetModuleHandle("KernelUtil.dll") + EntryOFFSET;

同时也可以根据基址确定EntryOFFSET并写在开头。

如何修改?

这里采用Windows提供的ReadProcessMemory和WriteProcessMemory这两个函数,参数非常好理解,当前进程,需要修改的地址位置,修改的字节,修改字节的长度以及一个可以忽略的参数。在读的时候,指定位置的指定大小的字节会被保存进定义的char型数组里,写的时候就是把修改后的字节写回原来的地址。那我们要如何确定该写什么呢?

根据push xxxxxxxx/retn指令,可以确定第一个字节和最后一个字节分别为0x68和0xC3。中间的4个字节填什么?就是执行我们做手脚的函数地址了。Hook函数的作用就是转移程序执行的流程,将程序转移到我们自己定义的函数,我们自己的函数就可以对当前的程序做些手脚,比如读取函数接收的参数,并将其传递出来。

目前为止,经过分析,可以进一步完善源程序。

接下来,就来编写自己的函数,将消息内容传递出来。

功能函数

功能函数其实就是用汇编写一个裸函数,为啥要用裸函数?这样的话,编译器就不会自动帮我们生成如下这三行指令:

Code
1
2
3
push ebp
mov ebp, esp
sub esp, 0x20

而是我们自己平衡堆栈,所以就可以避免很多额外的偏移造成的麻烦,经过上一篇的分析已知,当函数到达GetMsgAbstractByElement的位置处时,可以通过[[ebx+0x28]]+0x18获取消息内容。那就可以采用一下方式:

Code
1
2
3
4
5
6
pushad
pushfd
mov eax, [ebx+0x28]
mov eax, [eax]
mov eax, [eax+0x18]
mov Msg, eax

这样只要在外部定义一个变量Msg,即可将消息取到,然后可以利用OutputDebugString将消息内容输出到DbgView里观察。

但这还没有结束,因为之前覆盖掉了GetMsgAbstractByElement,所以这次需要重新再调用一遍。所以我们需要获取GetMsgAbstractByElement的地址,先通过当前地址和基址相减算出偏移0xBE0B0,然后通过KernelUtil.base+Offset确定函数的地址。这时只需要在平衡堆栈后的地方,补上之前替换掉的6个字节即可。

最终功能函数实现如下:

实验结果

我们使用OD,将编写的dll注入进去

注入后此处代码发生了变化

观察DbgView,发现成功拿到消息内容

Author: cataLoc
Link: http://cataloc.gitee.io/blog/2020/03/12/%E4%B8%80%E6%AC%A1%E7%AE%80%E5%8D%95%E7%9A%84Hook-%E4%B8%8B/
Copyright Notice: All articles in this blog are licensed under CC BY-NC-SA 4.0 unless stating additionally.
Donate
  • 微信
    微信
  • 支付寶
    支付寶