修复 IAT 重定向(一)
使用工具
- OllyDbg 1.10原版,简称
OD
; OD
汉化
和插件
均来自互联网;- UnPackMe来自互联网,仅供学习使用;
- Dump 工具为 OD 插件 OllyDump,来自互联网;
- IAT 重建工具为 ImportREC,来自互联网;
- 文中特殊数字均是
HEX
,为了书写方便采用DEC
;
输入表加密
在脱壳过程中,输入表修复是一个重点,修复关键是得到未加密的 IAT,对与 IAT 相关的位置设断,从而找到外壳处理 IAT 的代码,然后找对策;
加壳程序处理输入表有以下几种情况:
完整地保留了输入表,外壳加载时未对 IAT 加密;
当外壳解压数据时,完整的输入表会在内存中出现;
外壳用显式装载 DLL 的方式获得各函数的地址,并将地址填充到 IAT 中;
脱壳时,可以在内存映像文件刚生成时抓取输入表,此时,外壳还没来得及破坏原始的输入表;
此类壳有 ASPack、PECompact 等;
完整地保留了原输入表,当外壳加载时对 IAT 进行加密处理
当外壳解压数据时,完整的输入表会在内存中出现;
外壳用显式装载 DLL 的方式获得各函数的地址,并对这些地址进行处理(即 Hook API);
最后将 Hook API 的外壳代码的地址填充到 IAT 中;
由于 IAT 已经被加密,直接使用 ImportREC 是无法重建输入表的,但可以在外壳还没来得及加密 IAT 时抓取输入表,或者跳过对 IAT 进行加密的代码;
此类壳有 tElock 等;
加壳时破坏了原输入表,外壳装载时未对 IAT 进行加密处理;
外壳已经完全破坏原输入表,在外壳刚解压的映像文件中的是输入函数的字符串;
外壳用显式装载 DLL 的方式获得这些函数的地址,直接将函数地址填充到 IAT 中;
因为 IAT 未加密,所以在脱壳时可以用 ImportREC 根据 IAT 重建一个输入表;
此类壳有 UPX 等;
加壳时破坏了原输入表,装载外壳时对 IAT 进行加密处理;
如果外壳已经完全破坏了原输入表,外壳将用显式装载 DLL 的方式获取各函数地址,并对地址进行处理(即 Hook API),最后将 Hook API 的外壳代码填充到 IAT 中;
在脱壳时,不仅可以利用 ImportREC 的一些插件来对付这些加密的 IAT,也可以修改外壳处理输入函数地址的代码,使其生成的 IAT 不被加密,然后用 ImportREC 重建输入表;
此类壳有 ASProtect 等;
操作流程
本次 CM 为第 2 种情况, tElock 壳;
将 CM 导入 OD,可以使用
最后一次异常法
或编译语言特点法
(GetVersion)来到达 OEP,栈平衡法
和内存访问法
无法到达 OEP,原因是有硬件断点和 CC 断点检测;到达 OEP 后,定位 IAT,然后查看:
可以看到 IAT 中间有大段的未知数据,但这些数据所在的区段是存在的,可能不是无效数据;
如果不是无效数据,既然 IAT 中有加密的函数,也有未加密的函数,那么外壳程序中一定会有判断和跳转来分别处理这些函数;
记录当前区段信息,然后重载程序进行比较:
可以看到,程序载入 OD 后,在没有运行的情况下,其它区段是不存在的,也就是说,多出来的区段是由外壳生成的;
所以,IAT 中处于这些区段的未知数据应该不是无效信息,可能是被加密了(Hook API);
再次来到 OEP,通过查看代码也可以确认这些数据不是无效信息:
可以看到,指令中有 CALL 指向了 IAT 中的未知数据,如果是无效信息,这是不成立的;
单步步入到 CALL,是否能获得有用信息:
也就是说,这个 CALL 其实是调用了 kernel32 中的 GetVersion 函数,而 460ADC 中 存储的未知信息就是 Hook API 的地址;
确认这些未知数据是否无效的终极大招是尝试一下:
使用 OllyDump 将内存中的数据 Dump 下来并保存到可执行文件;
然后使用 ImportREC 获取并剔除未知数据后修复程序,程序并不能运行,所以这些数据不是无效的,而是加密的;
既然 IAT 中有加密的函数也有未加密的函数,如果设置写入断点,能否获取到外壳处理 IAT 的关键代码呢?值得一试:
- 以 IAT 表中第 1 个未加密的函数 ADVAPI32.RegCloseKey 为例,写入地址为 00460818,写入数据为 77DA6C27;
- 还需要一个加密的函数,以 kernel32.GetVersion 为例,这个是当前已知的加密函数,写入地址为 00460ADC,写入数据为 00A206F7;
重载程序后,在数据窗口 goto 到 00460818 位置,设置内存写入断点,然后运行程序:
程序会中断多次,但通过查看相关信息,都不是关键位置,直到中断在此处;
这里的指令执行后,会将 EAX 中的数据拷贝至 EDI,而 EAX 中的数据就是对应的 IAT 中的数据 77DA6C27,而 EDI 对应的则是 IAT 中的写入地址 00460818;
记录当前指令的地址,然后 F8 单步执行程序,来到起始位置,记录起始地址;
重载程序,在数据窗口 goto 到 00460ADC 位置,设置内存写入断点,然后运行程序:
同样的流程后,程序来到了相似的位置且提示窗口和寄存器窗口都显示了相关信息,写入地址为 00460ADC,写入数据为 00A206F,也是 IAT 中对应的数据;
而且两次中断指令的位置很接近,应该在同一个函数逻辑中,F8 单步运行程序,来到起始位置:
对比两次的起始地址相同,确认此处为外壳加密 IAT 的关键代码;
分析代码后,确认此处为关键跳转,修改为 JMP,然后给代码段设置内存访问断点并取消其它断点,然后运行程序:
到达 OEP 后定位 IAT,然后查看数据:
可以看到,断点之后的所有未知数据已经解密了,说明前面修改的跳转确实为关键跳转;
重载程序,在反汇编窗口中跟随到关键代码的起始地址,发现 goto 到此处的指令与运行到此处的指令不一致,看来,外壳程序是边运行边解压;
接下来就是获取完整的原程序 IAT 了,由于不知道外壳程序会先解密哪个 IAT,所以给 IAT 的所有字节设置内存写入断点,然后运行程序:
第 1 次中断是访问 IAT 的起始地址,在 CPU 窗口 goto 到关键代码的起始地址,发现代码已经解压恢复了:
修改关键跳转为 JMP,取消所有断点并给代码段设置内存访问断点,然后运行程序:
到达 OEP 后查看 IAT 已经全部解密了;
使用 ImportREC 获取解密后的输入表,然后修复 dumped.exe:
全部获取到了,没有无效信息;
程序正常运行,且导入 OD 后查看 IAT 也正常,脱壳完成;