通过x64dbg脚本功能修复IAT表

新闻资讯   2023-07-12 17:59   56   0  

样本


[PEDIY.华章 Crackme 竞赛 2009] [第十回] –ninejs

https://bbs.pediy.com/thread-97892-1.htm


感兴趣可自行去下载,看到下面评论也不少在说会dump但不会修复IAT表,故开此贴简单介绍一下刚学的方法。


1. 寻找OEP


通过栈平衡应该可以很快定位到OEP位置,这种基础操作不再赘述。

确定入口点在0x0047148B,此处下硬件执行断点,方便下次快速定位。



2. dump


直接先 dump 一手,有问题再慢慢修。

x64dbg 下方控制台可输入scylla指令,调出 scylla 工具,直接点上方图标也可以。





确认 OEP 地址是否正确,然后点击 dump,成功后可以看到源文件目录下多了一个后缀为xxx_dump的文件,双击运行。


不出意外的话是无法正常运行的,这时候不要慌,将此文件拖入 x64dbg,咱们一步步看问题出在哪了。


3. 定位问题


◆直接 F9 让程序跑起来,等它抛异常就行了,先瞟一眼异常。


◆查看栈顶到底是哪个函数出了问题。


◆好家伙,第一个 call 就卡住了。


◆可以看到他这里报的错是无法访问,也就是 call 的地址有问题,那么这个0x475080的地址里存放的到底是啥呢,咱也不晓得,咱也不敢问,咱们只能去源文件过一眼他是怎么跑的。


◆源文件这个地址 call 进去是完全没有任何问题的,而且这个地址一下串的老远,离谱的是这个地址还是属于用户模块,这就有点让人怀疑人生,一番苦思冥想,只能得出一个结论,这是他自己申请的内存页,并往其中写入了执行代码,咱们入口住 dump 出来是没有的。先不管其他,咱们先把这条 call 追到底,看看他到底在干嘛,几步一跟就到底了,可以看到他是拿ret下方的4个字节作为返回地址跳过去,这个地址是系统函数GetVersion,就是说这个函数用自己的算法变相的调用了GetVersion,至此我猜测该程序修改了IAT表的值,把表中本该跳转到函数的地址全部替换成了自己的跳转方式。


这里咱们去看一眼 IAT 表,内存窗口跳转至地址
0x475080,确实是 IAT 表的结构,就是地址全被改写成了他自己的函数,这种情况我们直接用工具是获取不到导入函数的,只能把表还原才能修复。



4. 修复 IAT 表


手动修复


就是把系统函数地址填回去,比如上面的call [0x475080],我们知道这个 call 等于调了GetVersion,我们就把GetVersion的地址填到0x475080里,下次运行时他就可以直接跳转到系统函数地址。


我本来是这么修的,但是真的好慢,花了半个小时仅把运行需要的函数给修复了,还有很多没用到的都没修,主要是单步跟着真的很累,有的地方还被混肴了,跳来跳去看着都烦,但是这种手动修的方法肯定是可行的,但成本太高,这还是程序导入函数比较少的情况,再多点人不得累死。


然后下面就要说一下 x64dbg 的脚本功能,真的是太舒服了。


脚本修复


关于脚本的所有指令都在官方的在线文档里:x64dbg 文档(https://help.x64dbg.com/en/latest/index.html)

幸好在科锐上个阶段项目就是自己写个调试器,x64dbg 的很多指令看着还挺亲切。


下面进入正题,这里先贴一下王老师课上写的脚本,没有对比就没有伤害,我自己写的留在最后得好好炫耀一下。


// 运行到OEP
bph 0x47148b // 设定硬件执行断点
g

// 导入表范围
// 表范围可以通过内存页跳转至 0x475080 直接看出来
impStart = 0x00475000
impEnd = 0x00475120

WHILEBEGIN:
//取出一项
mov itaItem, dword:[impStart]

//跳过0
cmp itaItem, 0
jz WHILECONTINUE

//设为新的EIP
mov eip, itaItem

//单步,直到遇到ret
SETBEGIN:
sti // 置单步

// 判断是否到达ret
mov code, byte:[eip]
// ret的机器码为0xC3
cmp code, 0xC3
jnz SETBEGIN

//从栈顶取出API地址,存入IAT对应项
mov apiAddr, dword:[esp]
mov dword:[impStart],apiAddr

WHILECONTINUE:
add impStart, 4
cmp impStart, impEnd
jb WHILEBEGIN

ret


这个 x64dbg 的脚本语法和汇编还挺像,里面可以用变量,可以用条件跳,可以添加段声明,这里简单说一下老王的思路。


他首先确定了 IAT 表的范围,然后直接对 IAT 表进行遍历,把 EIP 依次设为表中的地址开始跑,每跑完一次就把获取到的地址写回,跑完即可把 IAT 表修复
但他这个判定方法很奇葩,还记得我们上面说的
call [0x475080],这个函数是通过ret下方的四个字节作为跳板跳到系统 API,他这就是吃定了每个函数都用这种方法跳转。


但现实可能真的这么简单吗?然后课堂上就被打脸了,有的地方用的是
jmp xxx,有的地方是call reg,这种跳转的,脚本根本无法正常获取到地址,最后老王只能灰溜溜的手动修复了剩下的部分。


欸嘿,是时候到我表演真正的技术了!经过我花了数小时仔仔细细,认认真真把 x64dbg 手册翻了个遍,终于我悟了,老王他不行,我行!开玩笑哈,我就是吹水比他强。


首先咱们还是基于老王这个思想进行架构,遍历没问题,修改
EIP没问题,但置单步和这个判定逻辑咱们得改,自己置单步实在是太慢了,我们可以用 x64dbg 的 api,让他帮我们跑,这样效率会更高,比如说像“跟踪”菜单里的“步进直到条件满足”,直接在这里写暂停条件肯定比我们自己置单步判断条件来的快,我这里主要用到了一个重要 API:RunToParty,这个函数的功能是运行到指定模块,参数0为用户模块,参数1为系统模块,我的想法很简单,我管你是用什么方式跳转的,反正你肯定要跳转到系统模块,只要在这个时候断下,我们就能拿到系统函数的首地址,我本来这里用的是模块地址范围做逻辑判断的,正好看到有这么一个函数,倒省了我一番心思,ok,上代码。


$ArrayPtr = 475000
$ArrayEnd = 475120

Loop:

cmp dword:[$ArrayPtr], 0 // 跳过模块空隙
je Next

EIP = dword:[$ArrayPtr]
RunToParty 1 // 执行到系统模块断下
dword:[$ArrayPtr] = EIP // 取当前地址

Next:

$ArrayPtr += 4 // 指向下一块地址
cmp $ArrayPtr, $ArrayEnd // 判断是否结束
jne Loop
ret


我的这种写法 x64dbg 也是支持的,是不是十分的简洁明了,运行完脚本之后,可以看到 IAT 表已经被完美修复。



收尾




这时候我们再打开scylla工具,确定好 OEP,直接点击 IAT AutoSearch,然后点击 Get Imports,此时表已经能被加载出来,点击右侧的 Fix Dump,选择我们之前 dump 出来无法正常运行的文件,大功告成,新生成的xxx_dump_SCY已经能够完美运行,拖进 IDA 也可以清楚的看到调用的函数,收工!





看雪ID:彼岸风

https://bbs.kanxue.com/user-home-937323.htm

*本文为看雪论坛优秀文章,由 彼岸风 原创,转载请注明来自看雪社区


# 往期推荐

1、在 Windows下搭建LLVM 使用环境

2、深入学习smali语法

3、安卓加固脱壳分享

4、Flutter 逆向初探

5、一个简单实践理解栈空间转移

6、记一次某盾手游加固的脱壳与修复




球分享

球点赞

球在看

文章引用微信公众号"看雪学苑",如有侵权,请联系管理员删除!

博客评论
还没有人评论,赶紧抢个沙发~
发表评论
说明:请文明发言,共建和谐网络,您的个人信息不会被公开显示。