样本
[PEDIY.华章 Crackme 竞赛 2009] [第十回] –ninejs
https://bbs.pediy.com/thread-97892-1.htm
感兴趣可自行去下载,看到下面评论也不少在说会dump但不会修复IAT表,故开此贴简单介绍一下刚学的方法。
通过栈平衡应该可以很快定位到OEP位置,这种基础操作不再赘述。
确定入口点在0x0047148B
,此处下硬件执行断点,方便下次快速定位。
直接先 dump 一手,有问题再慢慢修。
x64dbg 下方控制台可输入scylla
指令,调出 scylla 工具,直接点上方图标也可以。
确认 OEP 地址是否正确,然后点击 dump,成功后可以看到源文件目录下多了一个后缀为xxx_dump
的文件,双击运行。
不出意外的话是无法正常运行的,这时候不要慌,将此文件拖入 x64dbg,咱们一步步看问题出在哪了。
◆直接 F9 让程序跑起来,等它抛异常就行了,先瞟一眼异常。
◆查看栈顶到底是哪个函数出了问题。
◆好家伙,第一个 call 就卡住了。
◆可以看到他这里报的错是无法访问,也就是 call 的地址有问题,那么这个0x475080
的地址里存放的到底是啥呢,咱也不晓得,咱也不敢问,咱们只能去源文件过一眼他是怎么跑的。
◆源文件这个地址 call 进去是完全没有任何问题的,而且这个地址一下串的老远,离谱的是这个地址还是属于用户模块,这就有点让人怀疑人生,一番苦思冥想,只能得出一个结论,这是他自己申请的内存页,并往其中写入了执行代码,咱们入口住 dump 出来是没有的。先不管其他,咱们先把这条 call 追到底,看看他到底在干嘛,几步一跟就到底了,可以看到他是拿ret
下方的4个字节作为返回地址跳过去,这个地址是系统函数GetVersion
,就是说这个函数用自己的算法变相的调用了GetVersion
,至此我猜测该程序修改了IAT表的值,把表中本该跳转到函数的地址全部替换成了自己的跳转方式。
这里咱们去看一眼 IAT 表,内存窗口跳转至地址0x475080
,确实是 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
# 往期推荐
3、安卓加固脱壳分享
球分享
球点赞
球在看
文章引用微信公众号"看雪学苑",如有侵权,请联系管理员删除!