首页
论坛
课程
招聘
[原创]通过x64dbg脚本功能修复IAT表
2022-8-2 01:34 3615

[原创]通过x64dbg脚本功能修复IAT表

2022-8-2 01:34
3615

样本

[PEDIY.华章 Crackme 竞赛 2009] [第十回] –ninejs
感兴趣可自行去下载,看到下面评论也不少在说会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 文档
    幸好在科锐上个阶段项目就是自己写个调试器,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 也可以清楚的看到调用的函数,收工!


[2022夏季班]《安卓高级研修班(网课)》月薪三万班招生中~

收藏
点赞7
打赏
分享
最新回复 (9)
雪    币: 217
活跃值: 活跃值 (327)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
PanLi77 活跃值 2022-8-2 07:20
2
0
分析的很透彻
雪    币: 5088
活跃值: 活跃值 (1806)
能力值: ( LV4,RANK:50 )
在线值:
发帖
回帖
粉丝
sunsjw 活跃值 1 2022-8-2 09:20
3
0
学习了。
雪    币: 10283
活跃值: 活跃值 (6085)
能力值: ( LV13,RANK:375 )
在线值:
发帖
回帖
粉丝
TkBinary 活跃值 5 2022-8-2 09:50
4
0

学习了. 脚本加了个log. 方便看日志. 

$ArrayPtr = 475000
$ArrayEnd = 475120
 
Loop:
cmp dword:[$ArrayPtr], 0    // 跳过模块空隙
je Next
 
EIP = dword:[$ArrayPtr]
log "cur EIP is 0x{x:cip}"
RunToParty 1                // 执行到系统模块断下
log "kernel EIP is {a:cip}"
dword:[$ArrayPtr] = EIP     // 取当前地址

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


最后于 2022-8-2 10:19 被TkBinary编辑 ,原因:
雪    币: 5803
活跃值: 活跃值 (852)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
chengdrgon 活跃值 2022-8-2 11:23
5
0
学习了。
雪    币: 1355
活跃值: 活跃值 (2954)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
风中小筑V 活跃值 2022-8-2 15:23
6
0
挺好使,感谢分享`
雪    币: 601
活跃值: 活跃值 (1480)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
kakasasa 活跃值 2022-8-2 17:04
7
0
感谢分享
雪    币: 666
活跃值: 活跃值 (420)
能力值: ( LV5,RANK:63 )
在线值:
发帖
回帖
粉丝
luoye_ATL 活跃值 2022-8-3 13:56
8
0
感谢分享
雪    币: 248
活跃值: 活跃值 (100)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
kkpojieba 活跃值 2022-8-4 00:24
9
0
大佬就是牛啊
雪    币: 333
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
陈Q 活跃值 3天前
10
0
学习了
游客
登录 | 注册 方可回帖
返回