脱壳系列_2_IAT加密壳_详细版解法1(含脚本)
1 查看壳程序信息
使用ExeInfoPe
分析:
发现这个壳的类型没有被识别出来,Vc 6.0倒是识别出来了,Vc 6.0的特征是 入口函数先调用GetVersion()
2 用OD找OEP
- 拖进OD
发现 这个壳和我们的正常程序很像。但是并不是我们的真正程序入口
因为vc6.0特征的第一个调用的是GetVersion(),给GetVersion()下 硬件断点
//第一次断下来,但是根据栈回溯,调用者并不是我们的模块
//第二次断下来,就应该是了
//找到入口后 栈上右键 反汇编窗口跟随
//如下
在OD看内存布局,一般.rdata的最前面是放的IAT,而且OD数据窗口默认就是.rdata的起始位置。
也可以点一个call /jmp [];看一下来找IAT表
对那个地方下一个硬件写入断点 --DWORD,即当前面的壳程序在修改的时候就能段下来找到壳的加密算法的地方
- 这里只是为了快速脱壳所以下硬件断点,快速定位加密修改IAT的地方,但后面部分将对整个壳详细分析:。
3 对壳详细分析
这个分析的过程,需要自己去啃,是分享不了的。在分析的时候遇到不知道的变量、地址这些先标记留着,后面分析着分析着就知道了。
> PUSH EBP
8BEC MOV EBP,ESP
83EC 0C SUB ESP,0xC
E8 45FFFFFF CALL .004383A0
0043845B A1 MOV EAX,DWORD PTR DS:[<程序基址>]
ADD EAX,DWORD PTR DS:[<代码段偏移>]
F8 MOV DWORD PTR SS:[EBP-0x8],EAX
C745 FC MOV DWORD PTR SS:[EBP-0x4],0x0
8D4D FC LEA ECX,DWORD PTR SS:[EBP-0x4]
PUSH ECX
6A PUSH 0x40
8B15 4C804300 MOV EDX,DWORD PTR DS:[0x43804C]
0043847C PUSH EDX
0043847D 8B45 F8 MOV EAX,DWORD PTR SS:[EBP-0x8]
PUSH EAX
FF15 C0924300 CALL DWORD PTR DS:[<virtualProtect>] ; kernel32.VirtualProtect
E8 04FEFFFF CALL .
0043848C 8D4D FC LEA ECX,DWORD PTR SS:[EBP-0x4]
0043848F PUSH ECX
8B55 FC MOV EDX,DWORD PTR SS:[EBP-0x4]
PUSH EDX
A1 4C804300 MOV EAX,DWORD PTR DS:[0x43804C]
PUSH EAX
0043849A 8B4D F8 MOV ECX,DWORD PTR SS:[EBP-0x8]
0043849D PUSH ECX
0043849E FF15 C0924300 CALL DWORD PTR DS:[<virtualProtect>] ; kernel32.VirtualProtect
004384A4 6A PUSH 0x4
004384A6 2C814300 PUSH .0043812C ; Hello 15PB
004384AB PUSH . ; 欢迎使用免费加壳程序,是否运行主程序?
004384B0 6A PUSH 0x0
004384B2 FF15 BC924300 CALL DWORD PTR DS:[<user.MessageBoxA>] ; user32.MessageBoxA
004384B8 F4 MOV DWORD PTR SS:[EBP-0xC],EAX
004384BB 837D F4 CMP DWORD PTR SS:[EBP-0xC],0x6 ; 当点击提示框的 IDYES 后 跳转
004384BF 0B JNZ SHORT .004384CC ; 如果不是 IDYES 那么就跳到退出程序标签
004384C1 E8 1A000000 CALL <.选择IDYES_THEN>
004384C6 - FF25 3C804300 JMP DWORD PTR DS:[<拟定的真实入口>] ; 02.00409486
004384CC 6A PUSH 0x0
004384CE FF15 B8924300 CALL DWORD PTR DS:[<MessageBoxA>] ; kernel32.ExitProcess
004384D4 8BE5 MOV ESP,EBP
004384D6 5D POP EBP
004384D7 C3 RETN
004384D8 CC INT3
004384D9 CC INT3
004384DA CC INT3
004384DB CC INT3
004384DC CC INT3
004384DD CC INT3
004384DE CC INT3
004384DF CC INT3
004384E0 > PUSH EBX ; 1. 将上一个函数的EBX保存
004384E1 8BDC MOV EBX,ESP ; /将当前main栈顶 保存到 EBX
004384E3 83EC SUB ESP,0x8 ; 2.开辟 8字节的局部空间 作用:将esp 4位对齐 ↓
004384E6 83E4 F0 AND ESP,0xFFFFFFF0 ; /将ESP -- 4位 对齐
004384E9 83C4 ADD ESP,0x4 ; /平4bytes,这三句的作用:将原来的ESP 4位对齐 ↑
004384EC PUSH EBP ; 3.压入main的栈底
004384ED 8B6B MOV EBP,DWORD PTR DS:[EBX+0x4] ; 4.将return 地址给 EBP
004384F0 896C24 MOV DWORD PTR SS:[ESP+0x4],EBP ; 5.将ebp再赋值给return,这两句其实是 mov esp+0x4,ebx+0x4. 也就是将原来未4位对齐之前的return 地址赋值给对齐之后理论应该存放(原对应)的地址
004384F4 8BEC MOV EBP,ESP ; *1 平栈,开辟新的栈帧
004384F6 83EC SUB ESP,0x48 ; *2 开辟局部空间
004384F9 A1 MOV EAX,DWORD PTR DS:[<第一个未知使用:0x4384f9 -- [0x438020]>] ; 0x438020 -- 是啥? 74062457 -->eax
004384FE 33C5 XOR EAX,EBP ; 和return 地址异或 -- 应该是 判断是否相等
FC MOV DWORD PTR SS:[EBP-0x4],EAX ; 把 函数返回地址 xor 未知数相与之后 -》.local1 像在算cookie 一样
PUSH ESI ; 保存ESI
8B35 MOV ESI,DWORD PTR DS:[<程序基址>] ; 把程序基地址 放入ESI
0043850A 8D45 C8 LEA EAX,DWORD PTR SS:[EBP-0x38] ; & local.14 -->eax
0043850D PUSH EDI ; 保存EDI
0043850E 8B3D MOV EDI,DWORD PTR DS:[<遍历的动态偏移>] ; 将[0X43805]放入 EDI 当前是:28c00 看样子是一个PE偏移RVA
PUSH EAX ; 压入EAX 即 &local.14 : 经过后面分析 这是用来保存以前.rdata区段属性的局部变量
A1 5C804300 MOV EAX,DWORD PTR DS:[<.rdata的RVA>] ; 将[0x43805c]放入EAX 当前是 22000 -- .rdata数据段RVA
0043851A 6A PUSH 0x40 ; push 0x40
0043851C FF35 PUSH DWORD PTR DS:[<.rdata的SIZE>] ; 退
03C6 ADD EAX,ESI ; .rdata的真实VA eax = imageBase + EAX ---*****----到这儿恍然大悟:前面的局部数据是区段信息rva 、size
CC MOV DWORD PTR SS:[EBP-0x34],ESI ; local.13 程序的基地址
PUSH EAX ; .rdata的VA 即IAT数组的地址 -- push &IAT
C745 C8 MOV DWORD PTR SS:[EBP-0x38],0x0 ; local.14 = 0
0043852F FF15 C0924300 CALL DWORD PTR DS:[<virtualProtect>] ; kernel32.VirtualProtect
833C37 CMP DWORD PTR DS:[EDI+ESI],0x0 ; 比较在程序虚拟空间偏移为0x28c00的地方的值 是否为0
0F84 D7000000 JE <.遍历结束恢复区段保护属性>
0043853F 8B55 CC MOV EDX,DWORD PTR SS:[EBP-0x34]
83C6 ADD ESI,0x10 ; 程序基地址 + 16个字节? IMPORT_DESCRIPTOR 的FirstThunk字段的起始地址,也就是 IAT的指针
03F7 ADD ESI,EDI ; 程序基地址 + DLL模块 当前的偏移
C4 MOV DWORD PTR SS:[EBP-0x3C],ESI ; 把IAT 的地址 放入 local.15
0043854A 8D9B LEA EBX,DWORD PTR DS:[EBX] ; 这句 》混淆视听?
8B46 FC MOV EAX,DWORD PTR DS:[ESI-0x4] ; dllName字段的RVA
03C2 ADD EAX,EDX ; dllName的VA
PUSH EAX ; 获取模块基址 第一个dll 从010Editor中查看来是 KERNEL32.DLL
FF15 C8924300 CALL DWORD PTR DS:[<LoadLibraryA>] ; kernel32.LoadLibraryA
0043855C 8B3E MOV EDI,DWORD PTR DS:[ESI] ; IAT表的RVA 放在EDI
0043855E 8B55 CC MOV EDX,DWORD PTR SS:[EBP-0x34] ; 程序基地址 放入 EDX
03FA ADD EDI,EDX ; IAT 表的起始VA
C0 MOV DWORD PTR SS:[EBP-0x40],EAX ; DLL 模块基地址 放在 local.16的位置上
8B0F MOV ECX,DWORD PTR DS:[EDI] ; 获取第一个IAT 项 -- 放在 ECX
85C9 TEST ECX,ECX ; 判断是否为0 ,为0 就跳转当前模块的IAT 遍历结束
0043856A 0F84 JE <.模块遍历结束>
8BF7 MOV ESI,EDI
> 8B07 MOV EAX,DWORD PTR DS:[EDI] ; 还是第一个IAT 项 -- 第一个API地址 --》EAX 和前面ECX一样
83C0 ADD EAX,0x2 ; EAX +=2 --- 函数地址 +=2 跳过import_by_name结构体 Hint字段 直接是 name 字段的地址
85C9 TEST ECX,ECX
JS SHORT <.序号导出的函数> ; 判断最高位是否为1 SF =1 则代表着是序号导出
0043857B 03C2 ADD EAX,EDX ; 如果不是序号导出 计算函数名称的 VA
0043857D C745 D0 E8010000 MOV DWORD PTR SS:[EBP-0x30],0x1E8
PUSH EAX ; 函数字符串的地址 --押入站
FF75 C0 PUSH DWORD PTR SS:[EBP-0x40] ; dll模块基地址 入站
C745 D4 00E958EB MOV DWORD PTR SS:[EBP-0x2C],0xEB58E900
0043858F :C745 D8 01E8 MOV WORD PTR SS:[EBP-0x28],0xE801 ; 0x25在下面一点,用来写入GetProcAddress的返回地址
C645 DA B8 MOV BYTE PTR SS:[EBP-0x26],0xB8
C745 DF EB011535 MOV DWORD PTR SS:[EBP-0x21],0x351501EB
004385A0 C745 E3 MOV DWORD PTR SS:[EBP-0x1D],0x15151515
004385A7 C745 E7 EB01FF50 MOV DWORD PTR SS:[EBP-0x19],0x50FF01EB
004385AE C745 EB EB02FF15 MOV DWORD PTR SS:[EBP-0x15],0x15FF02EB
004385B5 C645 EF C3 MOV BYTE PTR SS:[EBP-0x11],0xC3
004385B9 FF15 CC924300 CALL DWORD PTR DS:[<GetProcAddress>] ; kernel32.GetProcAddress
004385BF 6A PUSH 0x40
004385C1 PUSH 0x3000 ; 当IDYES 后那个CALL结束后
004385C6 6A PUSH 0x20
004385C8 XOR EAX,0x15151515
004385CD 6A PUSH 0x0
004385CF DB MOV DWORD PTR SS:[EBP-0x25],EAX ; 放入地址 -- 0x25刚好是用来存放地址的
004385D2 FF15 B4924300 CALL DWORD PTR DS:[<VirtualAlloc>] ; 申请32字节来存放硬编码(除了那4个地址字节都是死的,)
004385D8 F30F6F45 D0 MOVDQU XMM0,DQWORD PTR SS:[EBP-0x30]
004385DD 8B55 CC MOV EDX,DWORD PTR SS:[EBP-0x34]
004385E0 F30F7F00 MOVDQU DQWORD PTR DS:[EAX],XMM0
004385E4 F30F6F45 E0 MOVDQU XMM0,DQWORD PTR SS:[EBP-0x20]
004385E9 F30F7F40 MOVDQU DQWORD PTR DS:[EAX+0x10],XMM0 ; 这几句是把解密IAT的opcode 写入申请的内存中
004385EE MOV DWORD PTR DS:[EDI],EAX ; 申请的内存地址 放入IAT中
004385F0 > 8B4E MOV ECX,DWORD PTR DS:[ESI+0x4] ; 下一个IAT表项
004385F3 83C6 ADD ESI,0x4 ; esi 此时指向下一个 IAT表项
004385F6 8BFE MOV EDI,ESI ; 把EDI 指向下一个表项
004385F8 85C9 TEST ECX,ECX ; 判断是否结束
004385FA ^ 0F85 72FFFFFF JNZ <.IAT 遍历循环起始处> ; 还没有结束的时候跳 回去继续遍历IAT↑
8B75 C4 MOV ESI,DWORD PTR SS:[EBP-0x3C] ; IAT 如果结束了 就把上一个IMP_DESCRIPTOR的最后一个地址,放入ESI
> 83C6 ADD ESI,0x14 ; ESI + 一个IMP_DESCRIPTOR结构体的大小 相当于解析下一个dll
C4 MOV DWORD PTR SS:[EBP-0x3C],ESI ; 再把新的当前的位置存回去0x3c -- local.15的位置
837E F0 CMP DWORD PTR DS:[ESI-0x10],0x0
0043860D ^ 0F85 3DFFFFFF JNZ .
8B75 CC MOV ESI,DWORD PTR SS:[EBP-0x34]
> 8D45 C8 LEA EAX,DWORD PTR SS:[EBP-0x38]
PUSH EAX ; 把 原来的区段属性弹出
0043861A FF75 C8 PUSH DWORD PTR SS:[EBP-0x38]
0043861D A1 5C804300 MOV EAX,DWORD PTR DS:[<.rdata的RVA>]
FF35 PUSH DWORD PTR DS:[<.rdata的SIZE>] ; 退
03C6 ADD EAX,ESI
0043862A PUSH EAX
0043862B FF15 C0924300 CALL DWORD PTR DS:[<virtualProtect>] ; 恢复原来的区段属性
8B4D FC MOV ECX,DWORD PTR SS:[EBP-0x4] ; 就是前面ebp 与一个未知的数的异或加密 类似cookie
5F POP EDI
33CD XOR ECX,EBP
5E POP ESI
//开始怀疑修改进IAT的函数内容--加密代码前面几句--写死的硬编码opcode 到底意欲何为:
在内存窗口跳到[EBX-0X30]选择反汇编观察一下:
- 发现 这就是解密IAT代码。
总结:用硬编码 opcode (用于解密IAT)的首地址替代IAT中的函数地址,然后每次IAT调用的时候都会去调用这个代码块使用。
填入IAT的解密函数 详解:
进入填充硬编码后的 EBP-0X30 , 进去后发现里面包含了很多花指令,我一个个提取出来组合分析:
注意: 数字标号,代表着我的分析顺序
;1.解密程序第一句:
0012FF30 E8 CALL 0012FF36
;--------------------------------
;2.跳转到 0012ff36后
0012FF36 POP EAX;其实这儿主要是为了弹出前面的返回地址,好自己push返回地址
0012FF37 EB JMP SHORT 0012FF3A
;----------------------------------
;3.简单pop EAX 后又添砖0012ff3a:
0012FF3A B8 MOV EAX,0x????;.结合后面的代码,发现是取得的代码0x15异或加密后的代码
0012FF3F EB JMP SHORT 0012FF42
;将EAX 赋值为0x400 ,然后又跳转
;----------------------------------
;.跳转到 0012FF42:
0012FF42 XOR EAX,0x15151515;9.这样就迎刃而解了,这儿是解密。
0012FF47 EB JMP SHORT 0012FF4A
;将EAX和0x 15151515 异或计算加密后 继续跳转 0012FF4A
;-------------------------------------
;.跳转到 0012ff4a:
0012FF4A PUSH EAX;10.把解密的真正地址作为返回地址
0012FF4B EB JMP SHORT 0012FF4F
;push了现在的 40000 XOR 0X15151515后的值作为返回地址,又跳转
;--------------------------------------
;.再结合后面一点的代码:
004385B9 FF15 CC924300 CALL DWORD PTR DS:[<GetProcAddress>]; kernel32.GetProcAddress
004385BF 6A PUSH 0x40
004385C1 PUSH 0x3000
004385C6 6A PUSH 0x20
004385C8 XOR EAX,0x15151515
004385CD 6A PUSH 0x0
004385CF DB MOV DWORD PTR SS:[EBP-0x25],EAX ; 7.放入地址 -- 0x25刚好是用来存放地址的
;------------------------------------------------------------综述:
这段opcode 留了 EBP-0X25这个位置 来填充 xor 0x15 加密后的地址值,待真正call IAT[index]的时候,把call的返回地址pop弹出,把那个亦或的加密后的地址值用 xor 15 来解密,并且push进去代替前面pop出的地址,并且返回。
4 修正前面分析的加密算法
- 这时候我们只需要保存正确的函数地址值到IAT,那么这个程序就能脱掉了。
然后和前面几篇一样的流程dump到本地,用impREC修复一下IAT
运行没毛病!!
附:还可以使用OD脚本修正IAT
思路:
调用getprocAddress之后,立刻使用一个临时变量保存起来,
再待壳修改IAT后,立刻修改回正确的函数地址(前面保存的临时变量)
脚本如下:
//.定义变量
MOV dwGetApiAddr,004385bf
MOV dwWriteAddr,004385f0
MOV dwOEP, //.初始化环境
BC //清除软件断点
BPHWC //清除硬件断点
BPMC //清除内存断点 //下硬件执行断点 BPHWS dwGetApiAddr,"x"
BPHWS dwWriteAddr,"x"
BPHWS dwOEP,"x" //.构建逻辑
/*
-- 用一个临时变量来存储 真正的函数地址
-- 在加密逻辑代码执行完,并且写入IAT后,立刻改回
*/
LOOP0:
RUN //相当于od -- F9
CMP dwGetApiAddr,eip//如果是执行完GetProcAddress后
JNZ CASE1
mov dwTemp,eax
JMP LOOP0 CASE1:
CMP dwWriteAddr,eip
MOV [edi],dwTemp
JMP LOOP0 CASE2: CMP dwOEP,eip
JNZ LOOP0 MSG "改回来了哈哈"我发现另外一种解法,待会儿送上!
脱壳系列_2_IAT加密壳_详细版解法1(含脚本)的更多相关文章
- 脱壳系列_2_IAT加密壳_详细版_解法1_包含脚本
1 查看壳程序信息 使用ExeInfoPe 分析: 发现这个壳的类型没有被识别出来,Vc 6.0倒是识别出来了,Vc 6.0的特征是 入口函数先调用GetVersion() 2 用OD找OEP 拖进O ...
- 脱壳系列_0_FSG壳_详细版
---恢复内容开始--- 1 查看信息 使用ExeInfoPe查看此壳程序 可以看出是很老的FSG壳. 分析: Entry Point : 000000154,熟悉PE结构的知道,入口点(代码)揉进P ...
- 脱壳系列(一) - CrypKeySDK 壳
程序: 运行 用 PEiD 载入程序 PEid 显示找不到相关的壳 脱壳: 用 OD 载入程序 这个是壳的入口地址 因为代码段的入口地址为 00401000 这三个是壳增加的区段 按 F8 往下走程序 ...
- 脱壳系列(五) - MEW 壳
先用 PEiD 看一下 MEW 11 1.2 的壳 用 OD 载入程序 按 F8 进行跳转 往下拉 找到这个 retn 指令,并下断点 然后 F9 运行 停在该断点处后再按 F8 右键 -> 分 ...
- 脱壳系列(四) - eXPressor 壳
先用 PEiD 查一下壳 用 OD 载入程序 这里有一串字符串,是壳的名称和版本号 按 Alt+M 显示内存窗口 这里只有三个区段,后面两个是壳生成的,程序的代码段也包含在里面 利用堆栈平衡 按 F8 ...
- 脱壳系列(二) - EZIP 壳
程序: 运行程序 用 PEiD 查壳 EZIP 1.0 用 OD 打开 按 F8 往下走 这个看似是 OEP 的地方却不是 OEP 因为代码段从 00401000 开始 可以看到,壳伪造了 3 个区段 ...
- 脱壳系列—— 揭开so section加密的美丽外衣
i春秋作家:HAI_ 0×00 前言 对so的加密,https://bbs.pediy.com/thread-191649.htm大神的帖子里已经很详细的说明了.当然加密不是我们研究的重点,如何搞掉这 ...
- Oracle_Database_11g_标准版_企业版__下载地址_详细列表
Oracle_Database_11g_标准版_企业版__下载地址_详细列表 Oracle Database 11g Release 2 Standard Edition and Enterprise ...
- 手把手Maven搭建SpringMVC+Spring+MyBatis框架(超级详细版)
手把手Maven搭建SpringMVC+Spring+MyBatis框架(超级详细版) SSM(Spring+SpringMVC+Mybatis),目前较为主流的企业级架构方案.标准的MVC设计模式, ...
随机推荐
- 节能减排到底如何----google earth engine 告诉你!!
(First,再次严谨说明,本人成果未经允许,切勿发表到相关学术期刊,如果有技术交流,qq1044625113,顺便打个广告,兼职GEE开发,欢迎联系!) 终于过了严寒的冬天,2017年的冬天中国南方 ...
- Centos7 fstab盘符挂载硬盘导致重启系统失败解决办法
服务器拥有多个硬盘插槽,在进行维护或重启时,这些硬盘的相对位置可能发生变化.利用盘符(dev/vda)方式挂载磁盘,可能由于磁盘顺序变化导致重启时读取fstab文件发生错误,从而无法正常重启服务器. ...
- chrome如何查看cookie
以mac为例: 第一步:点击chrome的偏好设置 第二步:点击如下图所示的最下面的高级 第三步:点击内容设置,如下所示 第四步:点击cookie,就会出现查看所有cookie和网站数据
- auth-booster配置和使用(yii1.5)
auth-booster这个是一个yii框架扩展中的一个模块.是非常好用的(但是里面的说明都是英文的,所以国人用还需要改一点里面的汉化) 1.下载auth-booster这个:http://www.y ...
- python 查询 elasticsearch 常用方法(Query DSL)
1. 建立连接 from elasticsearch import Elasticsearch es = Elasticsearch(["localhost:9200"]) 2. ...
- redis源码笔记-内存管理zmalloc.c
redis的内存分配主要就是对malloc和free进行了一层简单的封装.具体的实现在zmalloc.h和zmalloc.c中.本文将对redis的内存管理相关几个比较重要的函数做逐一的介绍 参考: ...
- F#周报2019年第28期
新闻 FableConf门票开始贩售 Bolero的HTML模板支持热加载 Bolero从v0.4到v0.5的升级指南 完整的SAFE-Chat迁移至了Fable 2 为纯函数式3D图形生成领域专用语 ...
- 实战Spring4+ActiveMQ整合实现消息队列(生产者+消费者)
引言: 最近公司做了一个以信息安全为主的项目,其中有一个业务需求就是,项目定时监控操作用户的行为,对于一些违规操作严重的行为,以发送邮件(ForMail)的形式进行邮件告警,可能是多人,也可能是一个人 ...
- Python中字符串常见操作
(1)find 查找 格式:mystr.find(str, start, end) 例如: mystr.find(str, start=0, end=len(mystr)) 作用:检测str是否包含在 ...
- CI工具Jenkins的安装配置【linux】——jenkins集成sonarqube-异常解决
Setup 官网https://jenkins.io/ 下载war包,扔到tomcat下启动即可. 如果有port限制,在iptables中打开商品限制. 访问http://ip:port/jenki ...










