样本
感兴趣可自行去下载,看到下面评论也不少在说会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 的很多指令看着还挺亲切
下面进入正题,这里先贴一下王老师课上写的脚本,没有对比就没有伤害,我自己写的留在最后得好好炫耀一下
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
|
/
/
运行到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,上代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
|
$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 也可以清楚的看到调用的函数,收工!
更多【通过x64dbg脚本功能修复IAT表】相关视频教程:www.yxfzedu.com