脱壳——PESpin 壳
- 程序连接见结尾
PESpin 壳
// 这个壳
1.反硬件断点
2.IAT加密
3.混淆,花指令
- 脱壳步骤:寻找OEP——dump到内存——修复IAT
1 寻找OEP
- 首先可以拖入
Exeinfo PE
查看链接器信息,没收获。。。
- 然后拖入 OD,发现花指令,F7单步查看
- 发现
pushad
,立刻想到 ESP 定律
- 老规矩,ESP 处下硬件断点,期待断在
popad
1.1 反硬件断点
-
F9 直接运行,但是忽然发现,没有断下诶,啥情况呢?
- 应该是有 反硬件断点 的存在,即清除硬件断点(核心在于修改 调试寄存器,有两种方法可以做到)
- API - SetThreadContext,设置 Context 结构体
- 异常回调中修改 Context 结构体
- 应该是有 反硬件断点 的存在,即清除硬件断点(核心在于修改 调试寄存器,有两种方法可以做到)
1.2.1 测试1
- 对 SetThreadContext 这个函数下断
-
注意:直接在跳转后的地址下断(F2软断),程序会崩溃,尝试在它的下一条地址处下断
-
发现程序依然直接运行,那么我们再试试在它的底层函数处下断(上图中红色框处,enter进去下断)
-
发现 F9 运行程序依然不会断下,那么排除方法一
1.2.2 测试2
-
查看异常回调
-
前置设置1(调试选项中不要忽略异常)
-
前置设置2(插件中取消跳过一些异常)
-
重新加载程序,F9直接运行,注意观察左下角状态栏的提示
-
如上图,触发异常后,shift + F9 继续,共触发 7 次异常(其中第三次和第四次异常相同)
-
接下来排除是哪个异常回调里,清除了硬件断点
-
思路:在异常点设置硬件执行断点,查看是否可以断下,如果可以断下,说明当前异常的前一个异常回调函数中,没有清除硬件断点
-
依次在触发异常的位置下 硬件执行断点(*表示):
1(*)2(*)3/(*)/4(*)5(*)6(*)7
-
* 依次运行尝试,最终发现 **第三个异常** 后面的硬件断点不会断下(捂脸 つ﹏⊂,这里截图OD不一样,因为操作时发现有的OD,硬断就是不会断啊,好气)
-
所以第三个异常回调中清除了硬件断点,那么我们查看一下第三个异常的异常回调函数
-
再双击上图中的
0019FF44
那一行,进入查看该异常回调函数
-
上面那段代码清除了硬件断点,在上图
0043AF42
下断,并 dump- 此处为了偷懒,dump 出,用 IDA 去进行分析
- 此处为了偷懒,dump 出,用 IDA 去进行分析
静态分析
-
使用 IDApro,先简述下步骤,因为手机制作的拼图反了,需要从下往上看;
- 先F5,直接查看下IDA给出的源码 -》再添加
context
结构体 -》应用添加的结构体,具体操作看下图(从下往上看哈),最后可以看到,设置了调试寄存器(Dr0~Dr3,硬件断点都清零)
- 先F5,直接查看下IDA给出的源码 -》再添加
-
所以可以选择nop掉,这段代码
- dwNOP, 0043AF59, 1A(1A 是长度,0043AF73 - 0043AF59)
- 因为这个程序有反硬件断点,这里可以用另一种方法寻找OEP
1.2 寻找 OEP 方法2
-
由于没有查到链接器特征,所以先查找有模块间的调用
- 菜单栏
E
-》进入主模块 ,接着在右键-》查找-》所有模块间的调用
- 菜单栏
- 查看 IAT,发现有一部分 IAT 被加密(肉眼看不出是哪个dll导入的);这里选择一个没被加密的,查看其调用特征
-
查看 call -》发现有
FF15
特征,所以猜测 vc 6.0 编写- 即可在
GetVersion
或GetSystemTimeAsFileTime
下断
- 即可在
-
选择在
GetVersion
处下断
-
第一次断下发现返回的不是主模块,再次 F9 运行
-
运行两次后到 主线程 调用(栈区可看到,哪里调用的函数),栈回溯上一层——反汇编中查看,发现特征
SUB ESP,0X58
;从而找到 OEP
* ==dwOEP, 00409486==
2 OEP 处 Dump 内存到文件
-
在刚刚找到的 OEP 处下硬件断点
- 其实如果先按照上面 1.2 方法2 找到OEP后,就会发现 IAT 被加密,这时候需要下硬件写入断点,获取地址(dwGetAPIAddr 和 dwWriteAddr),以方便后面编写脚本
-
按照 1.2.2 中找到反硬件断点地方,
0043AF59
下硬件执行,断在清除硬件断点前;每次运行到这的时候,nop 掉操作调试寄存器的地方,在直接运行,这样OEP 处下的硬件断点就可断下,并对 IAT 写入处下硬件写入断点-
这里需要注意的是,硬件写入断点
0043CFCD
断在硬件执行断点的 OEP 前,所以重新运行后,发现如下图,看不出是哪里填充了 IAT- 这里需要一个小技巧
Ctrl+方向键↑
- 这里需要一个小技巧
-
* 会发现 `mov [edi],al`,但是填充函数地址应该是32位,所以先 F9 直接运行
- 发现写入处,dwWriteAddr, 0043918C
3 修复 IAT
-
接着上面,就是耐心了,F7 单步,同时观察寄存器的变化(因为会进入循环,所以时刻关注哪里可以跳出)
- 一直按 F7,发现下图中的规律,在跳出处
00438F51
下断(软断),查看跳到哪里
- 一直按 F7,发现下图中的规律,在跳出处
- 继续 F7 单步,发现又进入一个循环,同样的方法,查看跳出到哪里
- jmp 后,再次发现跳出
-
继续 F7 运行,要有耐心,同时盯着寄存器
-
F9 运行了,但是 QAQ,居然没断下,再次走了一遍上面的流程,发现比对成功,那我们继续向下单步
- 这时慢慢来,因为比对 hash 成功,应该很快就能获取到 API 地址了,盯住寄存器
-
终于,它来了它来了,dwGetAPIAddr, 00438F9F(这里先不要高兴的太早,虽然集齐了地址,可以套脚本,但是后面有坑 233333333)
-
先上脚本,看看运行后的效果
4 Script
// 1.初始化地址
MOV dwGetAPIAddr,00438F9F //0043902F //00438F9F
MOV dwWriteAddr,0043918C
MOV dwOEP,00409486
MOV dwNOP,0043AF59
// 2.设置断点
BC // 清除所有软件断点
BPHWC // 清除所有硬件断点
BPMC // 清除所有内存断点
BPHWS dwGetAPIAddr, "x" //当执行到此地址时产生中断
BPHWS dwWriteAddr, "x" //当执行到此地址时产生中断
BPHWS dwOEP, "x" // OEP 断点
BPHWS dwNOP, "x" // NOP 修改寄存器处
LOOP1:
RUN // 相当于在OllyDbg中按 F9
CMP dwNOP,eip
JNZ CASE0
FILL dwNOP,1A,90 // 填充 1A 大小的 90
JMP LOOP1
CASE0:
CMP dwGetAPIAddr,eip
JNZ CASE1
MOV dwTemp,eax
JMP LOOP1
CASE1:
CMP dwWriteAddr,eip
JNZ CASE2
MOV [edi],dwTemp
JMP LOOP1
CASE2:
CMP dwOEP,eip
JNZ LOOP1
MSG "修复完成"
4.1 查看脚本修复后的 IAT
- 因为脚本工具 OllySubScript 在虚拟机中,所以又换了一下OD
-
发现 IAT 依然不正确:每一个地址后多了一个 00,需要在内存中重建输入表,使用工具 通用输入表修复工具(UIF)
5 重建 IAT 表
- 通用输入表修复工具(UIF),注意使用管理员权限
-
修复后,再查看IAT(其实还是又瑕疵,手动滑稽,但是我们先走完流程)
-
dump : 右键-》用 OllyDump 脱壳调试进程
-
接着,使用ImportRec 工具(管理员打开)
- 按步骤,其中注意别忘记第二步,修改OEP
- 选择刚刚 dump 好的程序
-
那么,查看下修复后,是否能成功运行呢?
- 由上到下可以看到,刚dump出的程序,打开就崩溃;修复成功的可以正常运行,OD中调试,发现已经脱壳成功
- 其实还是有瑕疵,但是写了好久,先发了,后期再更,hhhhhh
链接:https://pan.baidu.com/s/1RlyV3qXnJ9StD7NT8IR7uA
提取码:v4vs