前一篇已经完成了对GetMsgAbstract函数的分析,发现,当执行到GetMsgAbstactByElement这一步时,已经可以根据寄存器用来传递的参数获取聊天内容,这篇来根据分析的内容编写用来Hook的dll。
Inline Hook
这里先对Inline Hook做个简要概述,它是一种通过修改指令的方法,转移程序的执行流程,在程序执行某函数前或者某函数后,先执行你定义的Hook函数,拿到需要的参数信息,再根据需要对参数信息进行加工,从而完成Hook。常见的手法如下:
1 | 1. jmp xxxxxxxx (5字节) |
1 | 2. push xxxxxxxx/retn (6字节) |
1 | 3. mov eax, xxxxxxxx/jmp eax (7字节) |
1 | 4. call Hook |
根据需求不同,替换掉原本的指令长度不同,从而选择的手段也不同;本篇中采用6字节的方式
构建DLL
一般构建一个dll要分别去编写头文件,C/C++源文件以及入口点函数。首先我们先从头文件开始。
头文件
打开Visual Studio,新建一个动态链接库(DLL)项目,VS会自动帮忙生成头文件和源文件,点击头文件stdafx.h开始编写
其实VS已经帮忙生成好了大部分,只需要定义自己需要实现的函数和方便自己使用的宏即可
这里为什么不写成下面这种形式呢?
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中,所以我们可以补充第三条语句改成如下:
1 | DWORD modify_addr = (PROC)GetModuleHandle("KernelUtil.dll") + EntryOFFSET; |
同时也可以根据基址确定EntryOFFSET并写在开头。
如何修改?
这里采用Windows提供的ReadProcessMemory和WriteProcessMemory这两个函数,参数非常好理解,当前进程,需要修改的地址位置,修改的字节,修改字节的长度以及一个可以忽略的参数。在读的时候,指定位置的指定大小的字节会被保存进定义的char型数组里,写的时候就是把修改后的字节写回原来的地址。那我们要如何确定该写什么呢?
根据push xxxxxxxx/retn指令,可以确定第一个字节和最后一个字节分别为0x68和0xC3。中间的4个字节填什么?就是执行我们做手脚的函数地址了。Hook函数的作用就是转移程序执行的流程,将程序转移到我们自己定义的函数,我们自己的函数就可以对当前的程序做些手脚,比如读取函数接收的参数,并将其传递出来。
目前为止,经过分析,可以进一步完善源程序。
接下来,就来编写自己的函数,将消息内容传递出来。
功能函数
功能函数其实就是用汇编写一个裸函数,为啥要用裸函数?这样的话,编译器就不会自动帮我们生成如下这三行指令:
1 | push ebp |
而是我们自己平衡堆栈,所以就可以避免很多额外的偏移造成的麻烦,经过上一篇的分析已知,当函数到达GetMsgAbstractByElement的位置处时,可以通过[[ebx+0x28]]+0x18获取消息内容。那就可以采用一下方式:
1 | pushad |
这样只要在外部定义一个变量Msg,即可将消息取到,然后可以利用OutputDebugString将消息内容输出到DbgView里观察。
但这还没有结束,因为之前覆盖掉了GetMsgAbstractByElement,所以这次需要重新再调用一遍。所以我们需要获取GetMsgAbstractByElement的地址,先通过当前地址和基址相减算出偏移0xBE0B0,然后通过KernelUtil.base+Offset确定函数的地址。这时只需要在平衡堆栈后的地方,补上之前替换掉的6个字节即可。
最终功能函数实现如下:
实验结果
我们使用OD,将编写的dll注入进去
注入后此处代码发生了变化
观察DbgView,发现成功拿到消息内容