逆向工程核心原理-第32章-计算器显示中文数字
通过DLL注入钩取API, 是将DLL注入目标进程后, 修改IAT更改进程中调用的特定API的功能
32.1 技术图表
以下注入DLL实现API钩取的所需要的技术, 画下划线就是使用的技术
32.2 选定目标API
再进行钩取API之前, 首先我们要确定钩取哪个API
我们本次实例的目标是”计算器显示的文本”, 能实现这个功能的API: SetWindowTextW(), SetDlgItemTextW(). 而SetDlgItemTextW()中又调用了SetWindowTextW(). 所以我们可以锁定目标API—SetWindowTextW()
我们看看SetWindowTextW()的函数原型
1 | BOOL SetWindowTextW( |
我们想要实现将阿拉伯数字改为中文数字
我们首先使用OD来验证上面的猜想: SetWindowTextW()就是我们要修改的API
首先用OD打开calc
然后查看所有模块引用, 并找到SetWindowTextW()(我的OD加载不出引入函数的符号, 所以换了x32dbg来调试)
在这两个地方设下断点.随后我们F9运行, 先进入了EntryPoint, 然后再F9就停在了SetWindowTextW()前
观察上图的栈区, 可以看到两个参数逆序压入栈中, 我们可以看到第二个参数是”0.”, 这正是计算器程序一开始要输出的内容. 我们再一次F9, 就可以看见计算器的窗口弹出, 并输出了”0.”
现在我们输入7
可以看到第二个参数也变成了L”7.”, 我们尝试把7改成”七”, 首先先转到我们的数据缓冲区中
将0x37改为4e03, 由于小端, 所以在内存中是034e
我们再次运行
可以看到输出变成了”七.”.
由此我们可以确定我们需要钩取的API就是SetWindowTextW()
接下来就正式开始钩取IAT
32.3 IAT钩取工作原理
进程的IAT中保存着程序中调用API的地址
IAT钩取通过修改IAT中保存的API地址来钩取某个API
下面是正常使用IAT调用函数的流程
- 首先1002628处使用了CALL指令调用01001110处的值
- 而01001110处的值为我们API的地址77D0960E
- 实际上调用的是77D0960E
下面是IAT被钩取后的流程
- 01002628地址处调用了[01001110]跟上面是一样的
- 但是[01001110]处存储的实际调用地址被修改成了10001000
- 于是控制流转移到了10001000, 这里就是我们的钩取函数MySetWindowTextW()
- 执行完其语句后再结尾调用了[1000B6B8]
- 而1000B6B8处存储的是实际调用地址77D0960E, 这个地址就是我们原来的SetWindowTextW()
- 随后调用了SetWindowTextW()
- 返回到原来的位置
整个流程就是把原本调用SetWindowTextW()的位置替换成了MySetWindowTextW(), 随后再MySetWindowTextW()中调用了SetWindowTextW().
核心思想是: 在保持运行代码不变的前提下, 将IAT中保存的API起始地址变为我们的钩取函数的起始地址.
32.3.0 注意
我想在这里提一下DLL注入跟IAT钩取之间的关系, DLL是IAT钩取实现的基础, 而IAT钩取是API钩取的实现思路之一(前面的调试钩取也是思路之一). 千万不要把DLL注入跟这些概念搞混了. 后面还会提到调试钩取API跟钩取IAT之间的区别.
32.4 练习示例
我们首先打开calc
查找其PID
随后使用管理员身份打开终端
其中第二个参数表示Inject(注入)
成功注入时, 我们回到calc中可以看到输出不一样了
然后脱钩
第二参数e表示end结束
随后键入一个值
可以看到字符恢复了
32.5 源代码分析
InjectDll.cpp跟前面的DLL注入的InjectDll.cpp时类似的: 通过查找PID获取进程句柄, 同时获取LoadLibrary()API, 然后通过像目标进程(我们已有进程句柄)创建远程线程调用LoadLibrary()API来加载我们像注入的DLL.
32.5.1 DllMain()
1 |
|
流程:
- 首先注入时使用标签DLL_PROCESS_ATTACH: 全局变量保存SetWindowTextW()的API地址, 并挂钩, 即替换IAT
- 当结束钩取时使用标签DLL_PROCESS_DETACH: 脱钩, 即将IAT中的内容复原
这里还是跟上一章一样, 要记住全局变量的内容, 后面还会用到.
32.5.2 MySetWindowTextW()
MySetWindowTextW()为钩取函数
1 | //此时g_pOrgFunc = SetWindowTextW()函数的起始地址 |
至此我们在回头看看整个的思路
- 首先是InjectDll.exe负责注入DLL到目标进程中, 用的方法应该是远程创建线程
- 于是我们来到了DllMain中, 注意此时我们已经进入了目标进程的内存中, 可以对其数据进行修改
- 在DllMian中我们实现了挂钩—将目标进程内存中的IAT进行修改, 使其跳转到DLL中的数据处理函数(该函数也随着DLL的注入而进入了目标进程的内存中, 所以可以正常调用)
- 在钩取函数中修改数据, 然后再调用原来的函数, 就像是加了一个中间加工的过程.
- 然后在DLL卸载的时候, 我们进行了脱钩—将目标进程内存中的IAT复原
我们已经解决了DllMain和数据处理函数, 最关键的挂钩和脱钩操作将在下面的进行分析, 之所以在这里提到这个是因为在书中对源代码的分析不是按照逻辑顺序进行的, 而是从简到难, 所以不梳理以下整个流程会有些混乱.
32.5.2 hook_iat()(挂钩和脱钩函数)
1 | // "user32.dll"的字符串指针 需要替换的函数指针 替换的函数指针 |
32.6 调试被注入的DLL文件
首先打开calc(计算器), 并使用process explorer查看其PID
接下来使用OD(这里换成了x32dbg)附加到calc上
F9使其处于running状态
并设置调试选项
随后开始通过注入DLL来修改IAT从而实现API钩取
调试器会停在hookiat.dll的入口点随后即可开始调试
这里明显不是DllMian, 所以我们需要先找到DllMian的位置, 而这里最简单的方式就是字符串查找
我们已经知道DllMian中在调用GetProcAddress函数时, 使用了两个字符串指针”user32.dll”和”SetWindowTextW”, 所以可以通过这个来找到我们的DllMian
确定了这里是DllMian后我们在该函数的开头下下断点并F9
即可开始调试DLL