压缩壳的学习

压缩壳学习

参考文章

https://www.cnblogs.com/iBinary/p/7764483.html

https://bbs.pediy.com/thread-265766.htm

https://www.52pojie.cn/thread-727090-1-1.html

upx源码

https://www.cnblogs.com/ichunqiu/p/7245329.html

壳的介绍

壳根据不同可以分成压缩壳、加密壳、其他

压缩壳

压缩壳,顾名思义,就是让程序变小,但是程序执行是没有问题的,主要有UPX和Aspack等

加密壳

加密壳就是为了保护程序而设计的,主要用于商业程序,主要有ASProtect、Armadillo、EXECryptor、Themida

其他

主要是一些病毒的壳

UPX压缩壳

原理

压缩能让我们程序的体积变小,就类似于123456使用%x替换,672324ADB使用%C替换,这样程序代码体积就变小了

压缩壳的工作原理首先在程序开头或者其他合适的地方插入一段代码(包括解压缩的代码),然后再将程序的其他地方进行压缩

压缩的数据主要是PE文件的节的数据

压缩

当程序执行时,实现对程序的解压缩,所以不会影响程序的执行

正常程序

加壳后的程序

程序解压缩

运行过程

因为程序需要解压缩,但是原来的节的位置已经被占据,那么壳是怎么恢复数据的呢

首先我们知道原PE的节的个数以及大小,那么此时我们生成的新的带壳PE,则会获得大小。然后在其带壳PE的下面申请怎么大小的节用来占位置即可,也就是说运行时,shell会进行偏移,不会占用解压缩的节的位置

那么此时我们解压的数据,则会写到我们占位置的地方。

壳的加载过程

1、保存入口参数

加壳程序在初始化时会保存各寄存器的值,等到外壳执行完毕,再恢复各个寄存器的值,最后跳转到原程序执行。通常使用Push ad/pop ad、pushfd/popfd指令来保存和恢复现场

2、获取壳本身需要使用的API地址

在一般情况下,外壳的输入表只有GetProcAddress、GetModuleHandle和LoadLibary这三个API函数,甚至只有Kernel32.dll及GetProcAddress。如果需要使用其他API函数,可以通过LoadLibaryA或者LoadLibaryExA将DLL文件映像映射到调用进程的地址空间中,函数返回的HINSTANCE值用于标识文件映像所映射的虚拟内存地址。也就相当于DLL的基址

LoadLibrary函数

原型如下

1
2
3
HINSTANCE LoadLibary{
LPCTSTR lpLibFIlenam;//DLL文件名地址
}

返回值:成功则返回模块的句柄,失败则返回"NULL"。

GetModuleHandle函数

如果DLL文件已被映射到调用进程的地址空间中,可以调用GetModuleHandleA函数获取DLL模块的句柄,函数原型如下

1
2
3
HMODULE GetModuleHandle{
LPCTSTR lpModuleName//DLL文件名地址
}

GetProcAddress函数

一旦DLL模块被加载,线程就可以调用GetProcAddress函数来获取输入函数的地址了,该函数原型如下

1
2
3
4
FARPROC GetProcAddress{
HMODULE hModule,//DLL模块句柄
LPCSTR lpProName //函数名
}

但是有些壳为了提高强度,不使用系统提供的GetProcAddress函数,而是自己编写函数来替代这个API函数,以提高函数调用的隐蔽性

3、解密原程序的各个区块的数据

为了保护原程序代码和数据,壳一般会加密原程序文件的各个区块。在执行时进行解密,因为壳一般是按区块进行加密的,所以在解密的时候也是按照区块解密的,并且将解密的区块数据按照区块的定义放入内存中合适的位置

4、IAT的初始化

IAT的填写本来是由 PE装载器实现,但是由于在加壳时构造了一个自建输入表,并且让PE文件头数据目录表的输入表指针指向新建的输入表,PE装载器就会对自建输入表进行填写。而程序的原始输入表被被外壳变形后储存,所以IAT表的填写需要外壳程序来实现。外壳需要做的就是将这个变形输入表从头到尾扫描一遍,重新获取每一个DLL引入的所有函数的地址,并将其填写在IAT表中

5、重定位项的处理

对于EXE文件,Windows会尽量满足其需求。比如EXE文件的基址一般是4000000h,那么加载到内存中就是4000000,这种情况下就不需要进行重定位。

但是对DLL就不一样了,因为Windows系统没办法保证每次运行时都提供相同的基地址,所以需要重定位,所以加壳的DLL一般会多一个重定位表

6、Hook API

在程序文件中,输入表的作用是让windows操作系统在程序运行时将API的实际地址提供给程序使用。在程序第一行代码被执行前,Windows操作系统就完成了这个操作。

而壳大多在修改原程序文件的输入表之后自己模仿Windows操作系统的工作流程,向输入表填充相关数据,在填充过程中,外壳可以Hook API代码的地址,从而间接获得程序的控制权

7、跳转到原程序入口-OEP

从这个时候开始,壳将控制权还给原程序

图解

引用了别人的图,看起来比较清晰,不过这个是x64下的

可以发现在磁盘文件中,upx0是不存在的,使用工具也可以看到

但是看UPX1的VirtualAddress可以知道这两个节中间有很大的空余区域,这些区域是存放解压缩后的原程序数据的

脱壳

这里以UPX壳为例

首先我们知道压缩壳会修改程序入口,拖进OD发现断在了push ad

寻找OEP

根据跨段指令寻找OEP

https://www.cnblogs.com/dilex/p/5547241.html

https://www.52pojie.cn/thread-294773-1-1.html

用内存访问断点寻找OEP

先码着

https://blog.csdn.net/jyk764717697/article/details/7306621?spm=1001.2101.3001.6661.1&utm_medium=distribute.pc_relevant_t0.none-task-blog-2~default~BlogCommendFromBaidu~Rate-1.pc_relevant_default&depth_1-utm_source=distribute.pc_relevant_t0.none-task-blog-2~default~BlogCommendFromBaidu~Rate-1.pc_relevant_default&utm_relevant_index=1

根据栈平衡寻找OEP-ESP定律

原理

首先,我们知道壳相当于一个子程序,它在程序运行时首先获得控制权并对程序进行压缩,同时隐藏程序的OEP。

在编写加壳软件时,必须保证外壳初始化的现场环境(各寄存器的值)与原程序的现场环境是一致的(主要是ESP、EBP等重要的寄存器)。加壳程序在初始化时保存各寄存器的值,待外壳执行完毕恢复现场环境。通常用的指令前面也介绍了。

在脱壳时,根据栈平衡原理(因为壳相当于子程序嘛,所以我们要保证壳运行完,将控制权交换程序的时候堆栈要平衡)对ESP下断,很快就能找到OEP

示例

可以看到刚载入时断在了push ad,这时候我们F8

刚载入

F8前寄存器和堆栈

F8之后,ESP发生改变,寄存器的值也被压入了栈中

F8之后

对ESP下硬件访问断点

也可以现在右键ESP,在数据窗口跟随,然后选中前四个字节右键->断点->硬件访问->DWORD

数据窗口设置断点

设置好断点后F9

F9之后

然后一直单步到jmp

这里有个往上跳的,只需要在jnz下一行处按F4即可

jmp之后

这时候已经到达OEP了,可以发现堆栈和刚载入时一样

对比

dump下来即可->右键->使用OllyDump脱壳调试进程即可

Dump