脱壳系列_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设计模式, ...
随机推荐
- MCtalk对话尚德机构:AI讲师,假套路还是真功夫?
一间容纳百人的被挤得满满的教室,老师讲.学生听.线下课堂曾是职业教育最普遍的形式.随着移动互联网的普及,大量的学习行为逐渐转化到线上进行,传统教育机构如何抓住这轮技术转型的契机,而不是被它吞噬? 近日 ...
- webpack-simple之vagrant热加载
"dev": "cross-env NODE_ENV=development webpack-dev-server --host 192.168.2.10 --port ...
- PHP中var_dump、&&和GLOBALS的爱恨纠缠
var_dump函数:用来打印显示一个变量的内容与结构: &&:定义一个可变变量.php中,在定义变量时,需要在前面加上一个“&”符号,当加上两个“&&”符号时 ...
- 跟我学SpringCloud | 第九篇:服务网关Zuul初
SpringCloud系列教程 | 第九篇:服务网关Zuul初探 前面的文章我们介绍了,Eureka用于服务的注册于发现,Feign支持服务的调用以及均衡负载,Hystrix处理服务的熔断防止故障扩散 ...
- Codeforces Gym100502G:Outing(缩点+有依赖的树形背包)
http://codeforces.com/gym/100502/attachments 题意:有n个点,容量为tol,接下来n个关系,表示选了第i个点,那么第xi个点就必须被选.问最多可以选多少个点 ...
- .Net Core 通用主机(Core 在控制台应用程序中的应用)
一.介绍 官方文档中说,Microsoft.AspNetCore.App 元包(ASP.NET Core 2.1 或更高版本)包含通用主机的Microsoft.Extensions.Hosting包, ...
- node调试工具--nodemon使用简介
这个工具和node-supervisor基本上是一致的,但是其功能比较强大,个人觉得在开发环境还是用 nodemon,因为配置比较方便,文档也很清晰.所以这里先主要讲 nodemon. nodemon ...
- java网络爬虫,乱码问题终于完美解决
第一次写爬虫,被乱码问题困扰两天,试了很多方法都不可以,今天随便一试,居然好了. 在获取网页时创建了一个缓冲字节输入流,问题就在这个流上,添加标红代码即可 BufferedReader in = nu ...
- springboot+redis实现session共享
1.场景描述 因项目访问压力有点大,需要做负载均衡,但是登录使用的是公司统一提供的单点登录系统,需要做session共享,否则假如在A机器登录成功,在B机器上操作就会存在用户未登录情况. 2. 解决方 ...
- mplayer+ffmpeg 组合截图
mplayer截图的优点:对于一个时长很长的视频,可以任意指定一个时间点截图,mplayer会直接跳到这个时间点开始解码截图: 缺点:由于是直接跳到指定的时间点,也就是直接跳过了之前的帧,这样解码出来 ...