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

思路:

  1. 调用getprocAddress之后,立刻使用一个临时变量保存起来,

  2. 再待壳修改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(含脚本)的更多相关文章

  1. 脱壳系列_2_IAT加密壳_详细版_解法1_包含脚本

    1 查看壳程序信息 使用ExeInfoPe 分析: 发现这个壳的类型没有被识别出来,Vc 6.0倒是识别出来了,Vc 6.0的特征是 入口函数先调用GetVersion() 2 用OD找OEP 拖进O ...

  2. 脱壳系列_0_FSG壳_详细版

    ---恢复内容开始--- 1 查看信息 使用ExeInfoPe查看此壳程序 可以看出是很老的FSG壳. 分析: Entry Point : 000000154,熟悉PE结构的知道,入口点(代码)揉进P ...

  3. 脱壳系列(一) - CrypKeySDK 壳

    程序: 运行 用 PEiD 载入程序 PEid 显示找不到相关的壳 脱壳: 用 OD 载入程序 这个是壳的入口地址 因为代码段的入口地址为 00401000 这三个是壳增加的区段 按 F8 往下走程序 ...

  4. 脱壳系列(五) - MEW 壳

    先用 PEiD 看一下 MEW 11 1.2 的壳 用 OD 载入程序 按 F8 进行跳转 往下拉 找到这个 retn 指令,并下断点 然后 F9 运行 停在该断点处后再按 F8 右键 -> 分 ...

  5. 脱壳系列(四) - eXPressor 壳

    先用 PEiD 查一下壳 用 OD 载入程序 这里有一串字符串,是壳的名称和版本号 按 Alt+M 显示内存窗口 这里只有三个区段,后面两个是壳生成的,程序的代码段也包含在里面 利用堆栈平衡 按 F8 ...

  6. 脱壳系列(二) - EZIP 壳

    程序: 运行程序 用 PEiD 查壳 EZIP 1.0 用 OD 打开 按 F8 往下走 这个看似是 OEP 的地方却不是 OEP 因为代码段从 00401000 开始 可以看到,壳伪造了 3 个区段 ...

  7. 脱壳系列—— 揭开so section加密的美丽外衣

    i春秋作家:HAI_ 0×00 前言 对so的加密,https://bbs.pediy.com/thread-191649.htm大神的帖子里已经很详细的说明了.当然加密不是我们研究的重点,如何搞掉这 ...

  8. Oracle_Database_11g_标准版_企业版__下载地址_详细列表

    Oracle_Database_11g_标准版_企业版__下载地址_详细列表 Oracle Database 11g Release 2 Standard Edition and Enterprise ...

  9. 手把手Maven搭建SpringMVC+Spring+MyBatis框架(超级详细版)

    手把手Maven搭建SpringMVC+Spring+MyBatis框架(超级详细版) SSM(Spring+SpringMVC+Mybatis),目前较为主流的企业级架构方案.标准的MVC设计模式, ...

随机推荐

  1. Hexo+NexT(三):Next主题配置详解

    阅读本篇之前,假定读者已经有了Node.js的基础,如需要补充Node.js知识的,请自行百度. Hexo是在Node.js框架下的一个项目,利用Node.js提供的强大功能,完成从Markdown到 ...

  2. 【LEETCODE】32、LeetCode的第35题,查找插入的位置

    凉凉,看来想做好一个题还不容易啊... 有点难受... 1.看看题目吧 Given a sorted array and a target value, return the index if the ...

  3. Django之Cookie Session详解,CBV,FBV登陆验证装饰器和自定义分页

    Cookie Session和自定义分页   cookie Cookie的由来 大家都知道HTTP协议是无状态的. 无状态的意思是每次请求都是独立的,它的执行情况和结果与前面的请求和之后的请求都无直接 ...

  4. Git--将已有的项目添加到github

    (2). 初始化本地仓库,并提交内容到本地 需要先打开 命令行终端,然后通过 cd 命令切换到需要添加到github 的项目的目录下,然后依次执行如下命令, 具体命令及其含义如下: 1). touch ...

  5. 纯CSS制作加<div>制作动画版哆啦A梦

    纯CSS代码加上<div>制作动画版哆啦A梦(机器猫) 哆啦A梦(机器猫)我们大家一定都很熟悉,今天给大家演示怎么用纯CSS代码,来做一个动画版的哆啦A梦. 效果图: ###下面代码同学可 ...

  6. JS工具整理

    1.获取今日日期:摘抄地址:https://www.cnblogs.com/carekee/articles/1678041.html getTodayFmt('yyyy-MM-dd') getTod ...

  7. GRPC 截止时间与元数据

    截止时间 gRPC 允许客户端在调用一个远程方法前指定一个最后期限值.这个值指定了在客户端可以等待服务端多长时间来应答,超过这个时间值 RPC 将结束并返回DEADLINE_EXCEEDED错误.在服 ...

  8. .NET开发框架(五)-IIS上部署ASP.NET Core项目教程

    系列教程:从初学者到架构师的一步步蜕变 本篇经验将和大家介绍如何在IIS上部署ASP.NET Core项目,希望对初学.NET CORE的童靴入门有所帮助! 1.打开VS,创建项目,选择ASP.NET ...

  9. 巧妙解决element-ui下拉框选项过多的问题

    1. 场景描述 不知道你有没有这样的经历,下拉框的选项很多,上万个选项甚至更多,这个时候如果全部把数据放到下拉框中渲染出来,浏览器会卡死,体验会特别不好 用人会说element-ui的select有一 ...

  10. BZOJ 1061:志愿者招募(单纯型)

    题目链接 题意 中文题意. 思路 单纯型模板题. 单纯型用来解决线性规划问题. 留坑待填. 算法思路 好长 模板 论文 卿学姐视频 #include <bits/stdc++.h> usi ...