手脱UPX壳初探
PS:仅面向做题总结经验,底层细节还有待深挖
测试用例:
buuctf 新年快乐
[NewStarCTF 2023 公开赛道]咳
BaseCTF2024 UPX mini,UPX,UPX PRO,UPX PRO MAX
用到的工具:
upxf.exe,upx.exe(upx-5.0.0-win64)
0.前言
UPX
UPX(Ultimate Packer for eXecutables)是一种常用的可执行文件压缩工具,它通过对程序进行压缩来减小文件体积。
UPX壳是一个开源的压缩壳, 是指在可执行文件中通过UPX压缩后,所形成的一种加壳结构。简单来说,它是一个封装了压缩程序的“外壳”,在执行时会先解压缩程序的内容,再执行原始程序。
主要用于
- 软件保护:通过压缩程序,降低被逆向工程和反汇编的概率。
- 减小文件大小:尤其在网络传输时,压缩后的文件更加节省带宽。
官网:upx/upx: UPX - the Ultimate Packer for eXecutables↗
通过使用官方发布的release
(upx.exe)可以对可执行文件进行压缩,并且使ida不能正常识别。下面以几道题和一个demo为例说明
壳及ESP定律
贴一下使用x64dbg脱壳之开源壳upx↗的解释
- 壳
外壳,英文为shell,在黑客技术中,一种比较高超的加密技术,对可执行文件(例如windows下的exe文件、Linux下的elf文件)进行加密压缩处理的技术。
一般对可执行文件加壳的目的有三:
① 软件加壳,保护数据、防止破解
② 外挂加壳,保护数据、防止破解
③ 病毒加壳,防止被查杀。
常见的壳分为压缩壳和加密壳两种,upx属于压缩壳。 - 脱壳
对加密处理的文件进行反分析,将已有的保护外壳去掉的过程。 - OEP
程序入口点,程序最开始执行的地方。 - 原始OEP
当程序加壳之后,壳会修改程序入口点,会先执行壳代码,会将原程序的入口点隐藏,这里我们把原程序的入口点称为原始OEP。 - dump内存
将内存中的数据或代码转储(dump)到本地 - IAT
导入地址表,windows下可执行文件中文件格式中的一个字段,描述的是导入信息函数地址,在文件中是一个RVA数组,在内存中是一个函数地址数组。(关于PE文件相关的知识可以通过搜索引擎查阅一下) - 修复IAT
脱壳中比较重要的一步,不论是压缩壳还是加密壳,在脱壳过程中都需要修复IAT,因为脱壳时会将内存中的数据转储(dump)到本地,保存成文件,而IAT在文件中是一个RVA数组,在内存中是一个函数地址数组。我们需要将转储出来的文件中的IAT修复成RVA数组的形式,这样程序才算是恢复。 - 脱壳的环境
这个单独出来说,主要原因就是不同的系统脱壳时遇到的问题可能是不一样的,因为脱壳时要修改IAT,而不同系统中同一个模块的API导出的顺序是不一样的,所以修复时一般都会出现点问题。因此,我建议脱壳的环境应该是在32位系统的虚拟机中,以下的所有操作应该在32位系统的虚拟机中操作,64位系统下可能会出现意想不到的问题。
ESP定律:
在程序执行过程中,尤其是当函数调用发生时,ESP的值会发生变化,以反映栈上新的栈帧的创建。当函数返回时,ESP的值会恢复到函数调用之前的值,以保持栈的平衡。这就是堆栈平衡定律,也称ESP定律。
在加了壳的软件中,程序刚开始加载时,首先会执行解密程序,而ESP栈顶指针会在解密程序执行完毕后,跳回到真正的程序时,有一个大幅度的跳转,这个跳转过程中ESP指针会回到执行解密程序之前的值。根据这一特性,通过跟踪ESP指针的归位时刻,可以找到解密程序的结束位置,进而找到程序的入口点(EP点),从而实现脱壳。
1.demo
加壳
以一个简单的test.c程序为例,编译后用upx
加壳和去壳,用Die、ida、010editor和xdbg
观察其变化
#include<stdio.h>
int xor(int a,int b){
return a^b;
}
int main(){
int a=0x30;
int b=0x12;
printf("异或后的值是:\t");
printf("%x",xor(a,b));
return 0;;
}
如图,ida正常分析
然后执行命令加壳
upx.exe test.exe
查壳如下
用ida分析,打开时报错,发现打开时函数很少,且有很大一段内容没有被识别出来
在010中发现明显多了一部分内容,标志位是UPX
去壳
最常用的方法就是怎么加壳就怎么去壳
执行命令
upx.exe -d test.exe
再用工具查看,和没加壳的一样。
但是,做题过程中常常遇到UPX变种,出题人给exe加壳还修改了其他内容,比如UPX的标志位、overlay_offset值等。此时upx工具不再有效,需要进行手动脱壳。
手动脱壳
这里使用xdbg应用ESP定律进行脱壳
首先要先在加壳后的程序中定位到原程序的入口点。使用x64dbg打开后直接运行一步,发现停在了pushad上。该指令将所有寄存器的值压栈,而在UPX的执行流程里,这一步之后会加载UPX的解压代码用于将原始程序解压。
upx的工作原理其实是这样的:首先将程序压缩。所谓的压缩包括两方面,一方面在程序的开头或者其他合适的地方插入一段代码,另一方面是将程序的其他地方做压缩。压缩也可以叫做加密,因为压缩后的程序比较难看懂,主要是和原来的代码有很大的不同。最大的表现也就是他的主要作用就是程序本身变小了。变小之后的程序在传输方面有很大的优势。其次就是在程序执行时,实时的对程序解压缩。解压缩功能是在第一步时插入的代码完成的功能。联起来就是:upx可以完成代码的压缩和实时解压执行。且不会影响程序的执行效率。
upx和普通的压缩,解压不同点就算在于upx是实时解压缩的。实时解压的原理可以使用一下图形表示:
graph LR; 1-->2-->3-->4-->5-->6;
假设1是upx插入的代码,2,3,4是压缩后的代码。5,6是随便的什么东西。程序从1开始执行。而1的功能是将2,3,4解压缩为7,8,9。7,8,9就是2,3,4在压缩之前的形式。
graph LR; 1-->7-->8-->9-->5-->6;
连起来就是:
graph LR; 1==>2-.->3-.->4-.->5; 7==>8==>9==>5==>6; 2-->解密-->7; 3-->解密-->8; 4-->解密-->9;
注意要在选项
-首选项
中把系统断点打开,调试器就可以在pushad
处断下
在解压过程中,UPX壳代码常常会用
pushad
/ popad
这种指令保存和恢复寄存器状态。
- UPX壳起始处的动作比较明显,一般是做一些基本检查(比如判断是不是自己解包过了),然后马上保存现场(pushad) 。
- 由于xdbg在加载完模块后,会根据设置自动打一些”系统断点”(如入口点、TLS、系统API),这样你有机会在UPX壳的早期代码里介入。
- pushad指令因为非常标志性(很少一上来就手动push这么多),所以特别容易成为壳保护程序的一个分水岭。
具体操作
- F9运行两次,来到pushad处
f8步过,走完push,RSP的值变为0x6CCCFFFA38
右键RSP-在转储/内存中跟随,右键内存对应的数据-断点-硬件-访问/存取
-双字(四字节)
f9执行,程序成功断在popad处,说明UPX的解压过程已经结束,现在在进行一些清理恢复工作。
f8步过,有一个循环,在其后下断点,f9过去,来到jmp
处,步进。这里就是真正的OEP(Original Entry Point)
找到入口点后使用Scylla工具脱壳
点击转储/dump
,会在当前文件夹下生成一个test_dmp.exe
文件,
然后点击IAT自动搜索(可能会出现以下情况,选择’是’)
再点击获取导入,最后修复转储,选择刚刚导出的test_dmp.exe
文件,在当前文件夹下生成test_dump_SCY.exe
文件。
脱壳到此结束
禁用ASLR
但是!程序无法正常运行,并且用ida打开还是不对,怎么回事?
问题出在64位程序普遍开了 ASLR(Address Space Layout Randomization),地址空间随机化。使得每次PE文件记载到虚拟内存的起始地址不一样, 而且栈和堆起始地址也不一样。
微软从windows vista/windows server 2008(kernel version 6.0)开始采用ASLR技术,主要目的是为了防止缓冲区溢出
ASLR技术会使PE文件每次加载到内存的起始地址随机变化,并且进程的栈和堆的起始地址也会随机改变。
该技术需要操作系统和编译工具的双重支持(主要是操作系统的支持,编译工具主要作用是生成支持ASLR的PE格式)
若不想使用ASLR功能,可以在VS编译的时候将“配置属性->链接器->高级->随机基址”的值修改为否即可
使用010或者CFF Explore工具打开test.exe文件,修改NT_Header下的Optional Header下的DllCharacteristics值,
当前是0x0160,二进制是0000 0001 0110 0000
每一位代表一个特性,主要的:
位 | 功能 | 含义 |
---|---|---|
0x0100 | NXCompat | 支持DEP(内存保护) |
0x0040 | DynamicBase | 支持地址空间随机化(ASLR) |
0x0020 | NoIsolation | 不使用隔离 |
要做的就是把 0x0040
这位去掉,也就是:
0x0160 - 0x0040 = 0x0120
即把0x0160改为0x0120即可
注意,不同的文件这里的值可能不同,但要去掉ASLR都是减去0x40
修改后保存,重新用xdbg走一遍
可以发现RSP的值已经不是之前那一长串儿了
然后走一样的流程就行,IDA能够正常分析,但是还是有问题——程序无法运行👇👇
注意:如果遇到IAT表导入部分错误需要手动修复IAT表(造成无法运行等的错误),参考文章:通过x64dbg脚本功能修复IAT表↗
(尝试了没有成功,我的xdbg不能运行某系汇编?它报错说无法识别指令:Loop,有没有大佬教教🥲。我的xdbg是在吾爱上下载的x64dbg 中文版
)代码如下
$ArrayPtr = 475000
$ArrayEnd = 475120
Loop:
cmp dword:[$ArrayPtr], 0 // 跳过模块空隙
je Next
EIP = dword:[$ArrayPtr]
RunToParty 1 // 执行到系统模块断下
dword:[$ArrayPtr] = EIP // 取当前地址
Next:
$ArrayPtr += 4 // 指向下一块地址
cmp $ArrayPtr, $ArrayEnd // 判断是否结束
jne Loop
ret
附一张图(小白初次学习,啥都不懂)
2.实战
buuctf 新年快乐
PE32,upx壳
还是ESP定律,32位程序能直接显示pushad
找到OEP,dump+修IAT表
其实这里直接拿dump后的文件丢IDA分析是可以的(会有一个报错,不过无伤大雅),只是不知道为什么不论是否修没修IAT,在IDA中都是显示UPX0:
段而不是正常的text:
段
修复IAT表后的如下(一模一样,也许本来就不需要修?但是不修就没法正常运行,只是IDA能正常静态分析! ):
脱壳结束,可以正常分析了
[NewStarCTF 2023 公开赛道]咳
PE64,UPX壳
一样的方法,具体过程就略过了
其中KE_dump_SCY.exe
和KE_dump.exe
都不能正常运行,但不影响IDA正常分析,动调估计还是不行。
运行不了原因如下
IAT表有问题,但是笔者还不知道怎么修/(ㄒoㄒ)/~~
BaseCTF2024 UPX mini,UPX,UPX PRO,UPX PRO MAX
连放4道,主要说考点
UPX mini
就是upx.exe file.exe
加壳来的,走常规流程就行。但是手脱得到的dump和修补后的程序依然无法运行,还是上面的问题👆UPX
在加壳的基础上修改了标志位,这里是小写upx
,正常的应该是大写UPX
,修改后就能正常脱壳。UPX_dump.exe
无法运行,UPX_dump_SCY.exe
能正常运行,说明IAT表修复成功。
UPX PRO
是一个upx打包的ELF文件
考点是overlay_offset
的值应该是F4而不是4F
- UPX PRO MAX
UPX标志位全改成00了,只能手脱
UPX PRO MAX_dump.exe
和UPX PRO MAX_dump_SCY.exe
都不能运行,看官方WP应该也是静态分析
3.总结
本文以一个demo和几道题为例,介绍使用xdbg
和ESP定律手脱upx
壳的过程
手脱过程中遇到了IAT表不能自动修复从而导致脱壳后的程序只能静态分析,无法正常运行的情况。参考一篇文章的讲解(通过x64dbg脚本功能修复IAT表↗)但遇到了其他问题
PS:还是不够清爽,再看看其他文章找找方法
4.参考资料
主要:
- [原创] 借助 x64dbg 的 UPX 手工脱壳-加壳脱壳-看雪-安全社区|安全招聘|kanxue.com↗
- 修改PE文件头删除ASLR_怎么修改pe文件让他支持aslr-CSDN博客↗
- 👉完美UPX脱壳-之投怀送抱篇(适合所有变形) - 吾爱破解 - 52pojie.cn↗👈
其他:
5.补充
第二天又看了些博客,找到了一些解决方法
CTF中手工脱壳实例(UPX壳) | Reveone‘s Blog↗
Helloctf上的讲解:
修复IAT表后,如果遇见有一个导出失败的表,直接给它删了程序就能正常运行!
爽了~
如果要手动修复IAT的话参考以下文章
【逆向】脱壳后修复IAT并关闭ASLR_iatxp-CSDN博客↗
使用x64dbg脱壳之开源壳upx - CharyGao - 博客园↗