0x00 什么是壳
壳是另外在PE文件中包含的代码,并且不影响PE文件正常的执行。而壳也分为很多种,这里从UPX壳开始介绍。
0x01 压缩壳
压缩的分类
压缩的目的就是将体积大的可执行文件缩小的过程。分为损失压缩和非损失压缩两种。损失压缩是指不能100%还原的压缩,常见的有JPG,MP4等格式。非损失压缩就是可以100%还原的压缩方式,一般有ZIP,7z等格式。
压缩壳
压缩壳的目的就是将PE文件变小。压缩有UPX, ASPack等方式。
Run-Time Packing
将原本的可执行文件的代码和数据压缩之后加入解压代码。压缩后的可执行文件很难进行静态分析。
可执行文件的起始地址称为OEP
压缩后的可执行文件起始地址称为EP
Unpacking
unpacking就是将packing后的可执行文件还原的过程,一般有以下几个步骤。
- 找出解除压缩时的地址
- 在压缩后的可执行文件中找出OEP地址
- 找到OEP地址中断后取得原本可执行文件的内存数据
- 最后将内存数据以可执行文件的方式重新整合
0x02 实验需要用到的工具
UPX: 用于压缩
https://github.com/upx/upx
OllyDumpEx: 找到OEP后可以直接通过这个工具还原导入表
https://low-priority.appspot.com/ollydumpex/
0x03 开始实验
首先准备个C语言的简单程序
#include<stdio.h>
int add(int a, int b) {
return a + b;
}
int a = 0x11;
int main() {
printf("Hello World!\n");
add(a, 0x22);
return 0;
}
找到可执行文件之后拖入DIE后看一下程序大小
然后用UPX加了压缩壳之后的文件进行对比
可以发现经过压缩之后的可执行文件大小缩小了,这里可以用HxD打开看一下。之前的data,bss段等都被UPX0,UPX1代替,只有rsrc段和压缩前相同。
0x04 ESP定律
由于在程序自解密或者自解压过程中, 不少壳会先将当前寄存器状态压栈, 如使用pushad, 在解压结束后, 会将之前的寄存器值出栈, 如使用popad. 因此在寄存器出栈时, 往往程序代码被恢复, 此时硬件断点触发. 然后在程序当前位置, 只需要少许单步操作, 就很容易到达正确的 OEP 位置.
ESP定律法 - CTF WIKI
首先打开经过upx压缩过的程序,F9来到程序起始的位置,并且F8单步执行pushad指令保存寄存器状态。观察旁边寄存器的状态可以得知,ESP的值发生了变化。
在寄存器视图中右键在ESP上设置断点。然后按下F9前往ESP所指向的地址。
跳转到00AD7FEF之后观察这里的汇编代码,我们会发现在00AD7FFC处有一个jmp指令,这是一次大跳转,极有可能是OEP所处的地址。在00AD7FFC处设置断点,F9运行至此处。
按下F8单步进入,下面这个00AD1633就是OEP的位置。
最后来恢复整个程序,在插件中使用Dump process,选择Get EIP as OEP,最后Dump文件并保存。
但是有时会出现Dump了程序却依然无法打开的情况,这是因为在Dump程序的过程中IAT表可能出现了丢失的情况。这个时候用Scylla来修复IAT表,在入OEP的位置打开Scylla,按下IAT Autosearch -> Get Imports 将 Import 中红色的部分删去最后再Fix Dump覆盖之前的程序,或者是重新生成一个Dump的程序。
如果文章中存在错误,请评论或者私信我