note20200903
note20200903
Ivoripuion专用加密软件
壳
压缩壳
- UPX
- ASPack
加密壳
- ASProtect
- Armadillo
- EXECryptor
- Themida
虚拟机保护软件
书中介绍的虚拟机与P-CODE类似,将一系列指令解释成字节码后放在一个解释引擎中执行,从而实现对软件的保护。
概念
一个虚拟机引擎主要由编译器、解释器、虚拟CPU环境组成,并且搭配一个或多个指令系统。虚拟机在运行时,先根据自定义的指令把已知的x86指令解释成字节码并放在PE文件中,然后将原始代码删除,改成类似这样的代码:
1 | push bytecode |
然后进入虚拟机执行循环。
比较典型的有较好保护强度的是VMProtect,使用起来也极为方便,针对要保护的函数,设置保护,然后点击编译即可:
脱壳技术
基础知识
壳的加载过程
- 保存入口参数
加壳程序初始化会保存各寄存器的值,待外壳执行完毕后回复各寄存器的内容,最后跳到原程序执行。
通常会使用如下一些指令进行保存和恢复寄存器内容:
1 | pushad/popad |
- 获取壳本身需要使用的API地址
一般情况下,外壳的输入表只有GetProcAddress
,GetModuleHandle
,LoadLibrary
三个api函数,从而调用其他api函数。
- 解密原程序各个区块的数据
加壳时加密各个区块,所以脱壳时就会解密各个区块数据。
- IAT初始化
加壳时构造了一个自建输入表,并让PE文件头数据目录表中的输入表指针指向自建的输入表,PE装载器会对自建的输入表进行填写。程序原有的输入表被外壳变形后存储,IAT的填写会由外壳程序实现。
- 处理重定位项
EXE文件在x86 32位系统中一般使用的基址位0x400000,此时就不需要进行重定位,因为系统给的基址也是0x400000,加壳软件此时就会删除PE文件中保持重定位信息的区块(.reloc)。
但是对于DLL动态链接库文件,加壳的DLL就会比加壳的EXE在修正时多一个重定位表格。
- Hook API
外壳为了能够填充输入表,可以填充Hook API代码的地址,从而间接得到程序的控制权。
- 跳转到OEP
此时壳会将控制权还给原程序。一般的加壳工具会使用”Stolen Bytes”技术,即是将OEP代码段搬到外壳的空间再将代码删除,这样就会提高脱壳难度。
手动脱壳
手动脱壳过程一般分为三步:寻找OEP;抓取内存映像文件;重建PE文件。
寻找OEP
一般的非加密壳在执行外壳程序以后,会将原程序解压,还原,并把控制权还给解压后的真正程序,再跳转到原来的程序入口,此时就会有一条明显的”分界线”,解压后的真正的程序入口点称为”OEP”。
根据跨段指令寻找OEP
绝大多数PE加壳程序在被加密的程序上加了一个或多个区块,在外壳代码处理完毕就会跳到程序本身的代码处。所以根据跨段的转移指令就可以找到真正的程序入口点了。
例子加壳前后的入口点RVA对比:
加壳前的入口点RVA=0x1130:
加壳后的入口点RVA=0x13000:
加壳后程序还多了一个”.pediy”区段,这个就是外壳程序:
使用OD单步步入到分配外壳地址的地方:
1 | 00413108 6A 04 push 0x4 |
此时分配的即是外壳第二部分需要的内存空间。
调用Aplib函数:
1 | 0041312C 50 push eax |
跳转到外壳第二部分:
1 | 00413133 5A pop edx ; RebPE_-_.<ModuleEntryPoint> |
外壳的第二部分用于还原各区块数据,初始化原程序,如填充IAT等。
进一步走可以走到解压代码的部分。
申请内存:
1 | 001C00A0 BB A1020000 mov ebx,0x2A1 |
取得需要解压区块的RVA:
1 | 001C00C1 8BC3 mov eax,ebx |
解压数据并将解压的数据写回:
1 | 001C00CE 56 push esi |
离开解压数据的循环就是填充IAT的部分。
对转存后的输入表进行初始化:
1 | 001C0181 8B95 91020000 mov edx,dword ptr ss:[ebp+0x291] |
修正重定位数据:
1 | 001C01F6 8BB5 99020000 mov esi,dword ptr ss:[ebp+0x299] |
走到popad
的地方,此时就将保存的寄存器状态恢复 ,进一步走就是OEP:
1 | 001C0282 61 popad |
OEP:
1 | 00401130 /. 55 push ebp ; SFX 代码真正入口点 |
使用内存访问断点寻找OEP
在主进程模块的.text
段设置内存访问断点:
1 | Memory map, 条目 12 |
f9后程序中断处:
1 | 00413145 A4 movs byte ptr es:[edi],byte ptr ds:[esi] |
这里是程序使用的aPLib解压函数aP_depack_asm
。将内存断点删除,走出该函数,就达到了外壳代码处:
1 | 001C00D6 8B0C2B mov ecx,dword ptr ds:[ebx+ebp] |
这里是不断地将各个区段进行解压。
解压完成在对.text
段设置内存访问断点,f9走就可以到达OEP:
1 | 00401130 /. 55 push ebp ; SFX 代码真正入口点 |
根据栈平衡寻找OEP(ESP定律)
原理就是pushad/popad
,pushfd/popfd
一定会成对出现。这样对push时的某个寄存器的值下硬件访问断点,当恢复寄存器时一定会访问这个地址,然后就会触断。
pushad
后,对0x0019FF58设置硬件访问断点,该地址保存的是esi寄存器的值,那么popad
时一定会访问这个地址。
触断后即到popad
:
进一步就是OEP:
根据编译语言特点寻找OEP
不同的编译器编译出的特点都不相同,书中样例在初始化时会调用GetVersion
函数,对其下断点寻找OEP即可。
Dump映像文件
一般使用LordPE或者OD自带的ODDump即可。
重建输入表
手动重构IAT(书中样例)
- 使用esp定律定位到OEP:
- 确定IAT表格:
- 填充
IMAGE_IMPORT_BY_NAME
结构:
- 填充
IMAGE_THUNK_DATA
数组:
- 构建IID结构:
- 修改输入表:
- 结果(导入表成功):
Ollydump直接导出
达到OEP后直接使用Ollydbg的插件OllyDump导出:
即可。
使用ImportRCE重建输入表(使用书中的RebPE为例)
- 选中调试中的rebpe程序:
- 修正OEP并且寻找IAT的偏移:
get import
获取IAT基本信息:
Fix Dump
选取PE Lord dump出的文件,导出的正常文件为源文件名称后加”_”:
输入表此时会位于”.mackt”段中:
也可以将输入表放在程序中的冗余段中,更改New Import Infos
中的RVA即可。
DLL文件脱壳
正常脱壳(DLL载入时寻找OEP)
- pushad后下硬件断点:
- f9后走一段达到OEP:
DLL退出时寻找OEP
- 在
popad
的地方下断点,DLL装载成功后将loaddll.exe关闭,cpu会再次断在popad
处:
- 跑到判断DLL状态(装载/退出)的代码:
- 走一段到外壳的第二段:
- 再步过一段到OEP:
Dump映像文件
由于在载入DLL文件的过程中使用了重定位,所以直接使用LordPE dump的文件的基地址是重定位之后的地址,所以需要在dump时将系统对DLL的重定位操作跳过。
这里的寻找重定位思路如下:
- 定位到OEP后在OEP以后的代码中寻找使用了重定位之后的代码,这里使用的是位于
0x003c1253
的代码:
- f2重新载入DLL文件,下内存访问断点在
.rdata
段,这样中断以后.text
段时解压完毕的:
- 在数据区对
0x003c1253
后面的40
字节设置内存写入断点,这样当该基地址要被修正为3c
时就会中断:
- 这时cpu中断的地方就是重定位的代码:
- 越过该代码(NOP掉),再到达OEP就可以看到下面原先重定位了的代码已经恢复了:
- 然后使用LordPE进行dump即可。
修正IAT
使用Import REC进行修正,这里需要手动填写IAT的RVA以及大小:
Fix Dump使用刚才LordPE dump的文件即可。
构造重定位表
这里可以直接使用书中给的ReloREC.exe程序添加重定位表,这里实际测试以后会出现错误: