逆向工程核心原理-第二十一章-Windows信息钩取
突然发现SYJ学长之前做的DLL注入的博客, 非常详细, 后面关于钩取代码的分析和SetWindowsHookEx函数的参数分析都有参考他的博客, 看雪的博客地址: https://bbs.pediy.com/thread-266862.htm
21.1 钩子(Hook)
在计算机中”钩子”的意思是: 偷看或截取信息时所用的手段或工具.
21.2 消息钩子
计算机与用户之间的交互是通过消息(比如点击一个窗口最大化, 就是一个消息, 向程序输入一个字母, 也是一个消息)来互相传送的, 而媒介就是钩子, 所以我们就通过钩子(hook)来获取或者改变这些消息.
下面是常规的Windows消息流:
- 发生键盘输入事件时, WM_KEYDOWN(按键)消息被添加到OS message queue
- OS判断哪个程序中发生了事件, 从[OS message queue]中取出消息, 添加到相应程序的[application message queue]中.
- 应用程序监视自身的[application message queue], 发现了新添加的WM_KEYDOWN消息, 调用相应的事件处理程序处理
OS消息队列与应用程序消息队列之间存在一条 ” 钩链 ” (Hook Chain), 处于钩链中的键盘消息钩子会比应用程序先看到相应的消息, 可以查看或者修改消息, 也可以拦截消息.
注意链表调用钩子是有顺序的
21.3 SetWindowsHookEx()
使用SetWindowsHookEx()可以实现消息钩子, API定义:
1 | HHOOK SetWindowsHookEx( |
各个参数, 返回值(看了SYJ学长的博客, 写的真的非常详细, 下面对钩子函数的注释也是参考了他在看雪中的博客: https://bbs.pediy.com/thread-266862.htm):
- HHOOK: 返回值, 钩子句柄, 需要保留, 不需要的时候通过UnhookWindWindowsHookEx来卸载掉
- idHook: 钩子拦截消息的类型, 比如这道题的类型是WH_KEYBOARD
- Lpfn: 信息的回调函数地址, 一般填函数名
- hMod: 钩子函数所在的实例的句柄, 对于线程钩子, 该参数为NULL;, 对于系统钩子, 该参数为钩子函数所在的DLL句柄.
- dwThreadld: 钩子所监视的线程线程号, 可通过GetCurrentThreadld()获取线程号, 对于全局钩子, 该参数为NULL(或0)
钩子过程(hook procedure)是由操作系统调用的回调函数. (回调函数: 某个事件发生时被指定调用的函数).
1 | 这是StackOverflow某位大神对回调函数间接的表述: 也就是说,函数 F1 调用函数 F2 的时候, |
安装钩子的时候, 钩子需要存在于某个DLL内部, 该DLL的实例句柄即是参数hMod
使用SetWindowsHookEx()设置好钩子后, 某个进程生成指定消息时, 操作系统(注意主语)会将相关的DLL文件强制注入相应进程, 然后调用注册的”钩子”过程(就是使用钩子).
注入过程我们什么都不用干, 由操作系统完成.
下面是设置钩子的流程:
- 把钩子放到一个DLL中
- 设置一个程序使用SetWindowsHookEx()设置钩子
- 当有另一个进程发生一个符合触发钩子的事件, DLL被操作系统注入到该进程中, 然后使用钩子
21.4 键盘消息钩取练习
在这里我们要知道钩子安装跟DLL注入之间的练习和先后顺序:
- 首先是HookMain.exe运行后先加载了KeyHook.dll后安装了钩子, 同时要注意, 其他程序并没有发生键盘事件, 所以也就是没被强制注入KeyHook.dll
- 随后explore.exe发生了键盘事件, 先被强制注入了KeyHook.dll, 然后安装了键盘钩子.
可以看到, 是先发生了DLL注入, 才安装了键盘钩子, 因为SetWindowsHookEx()是被包含在DLL中的.
21.4.1 练习实例HookMain.exe
运行HookMain.exe
按下q即可退出程序, 但是在Win10环境可以运行, 但是退出时会卡住. 所以这里用的是xp虚拟机
无法在notepad中输入任何东西
使用ProcessExplorer的搜索DLL功能找出我们钩子所在的DLL
在HookMain中键入q即可退出
再次查找keyhook.dll, 发现没有进程中由keyhook.dll.
21.4.2 分析源代码.
HookMain.cpp
主函数的源代码, 跟文件处理非常的相似, 所以理解起来不难
1 |
|
流程:
- 加载KeyHook.dll文件, 并获取其成员函数HookStart()跟HookStop()
- 然后调用HookStart()开始钩取
- 输入”q”时退出
keyhook.dll文件源码
1 |
|
流程:
- HookMain调用HookStart时, SetWindowsHookEx()设置钩子
- 当发生键入消息的时候, keyhook.dll会注入到该进程
- 然后运行dll中的keyboardproc(), 通过进程名拦截消息
关键在标红框的地方, 这里会检查进程名, 如果是notepad就会截断消息, 如果不是就会把消息传入链表中下一个钩子
注意这里截断的实现方法是: 不将消息传入下一个钩子(也可能下一个就是程序), 只是return 1, 这样消息消失了.
21.5 调试HookMain.exe
使用OD打开HookMian.exe
通过字符串查找”q to quit”
在起始位置401000下断点, 调试到这里.
然后可以看到401001和401006处加载了KeyHook.dll
然后是40104B处调用了CALL EBX指令, 就是调用了HookStart()
我们跟进HookStart()
可以看到调用了100010EF处调用了SetWindowsHookExW函数,
前面的两个指令逆序压入了前两个指令
- 上面的第二个参数就是钩子过程的函数(钩子过程就是钩子具体执行处理的函数), 我们要记下其起始地址: 10001020
- 第一个参数为2 , 代表WH_KEYBOARD键盘的钩子类型
21.5.2 调试Notepad.exe进程内的KeyHook.dll
首先调试notepad程序, 按下F9使其正常运行
然后设置OD, 使得在载入新的DLL时调试中断
然后再运行HookMain程序
然后再在notepad中输入字符, 马上就会暂停并弹出模块界面, 可以看到第二条就是我们新载入的keyhook.dll
我们双击keyhook.dll, 跳转到了其EP, 这里跟书中的不太一样, 钩子过程地址在59F1020, 但是因为偏移是一样的所以问题不大.
我们在59F1020地址处打下断点. F9继续运行到59F1020处即是我们的钩子
流程:
- 使用OD运行notepad
- 打开在选项”在导入新模块时中断”
- 运行KeyLogger.exe, 安装钩子
- 在notepad中使用键盘输入 → 发生键盘事件
- KeyLogger.dll被注入到notepad中
- 在钩子进程中设置断点
- 调试
我的一些理解
钩子的顺序
- 使用了HookMain使用了SetWindowsHookEx()设置了钩子(全局钩子)到钩链中, 但是传入钩链的只是钩子KeyboardProc()的地址或者句柄, 实际内存中并没有这个钩子的存在, 所以后面才需要DLL强制注入. 同时需要注意的是只有HookMain调用了HookStart()和HookStop(), 也只有HookStart()调用了SetWindowsHookEx(), 我之前认为加载了DLL就会执行DLLMain也会调用SetWindowsHookEx()是错误的.
- 当一个进程发生键盘消息的时候, 这时候消息被钩子捕获, 但是发现当前虚拟内存空间没有KeyboardProc()回调函数来处理(这是我个人的理解, 我认为钩子所导致的DLL注入是一个被动的过程), 所以强制DLL注入. 接上面我的错误认识, 这里的DLL注入只是为了提供KeyboardProc()给当前进程.
- 使用KeyboardProc回调函数对键盘消息进行处理(这个时候才开始区分是否是notepad.exe)
- 按下’q’键后对钩子进行卸载.
问题
- 具体实现过程中HookStart()跟HookStop()它们干了什么
- 全局钩子跟局部钩子的区别是什么, 是当发生相应消息的时候会先检查进程的PID, 然后决定是否进行DLL注入吗, 链表那里是不是会直接跳过这个钩子