手脱UPX壳初探

手脱UPX壳初探

周日 4月 27 2025
3401 字 · 13 分钟

手脱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​观察其变化

C
#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正常分析

image

image

然后执行命令加壳

BASH
upx.exe test.exe

image

查壳如下

image

用ida分析,打开时报错,发现打开时函数很少,且有很大一段内容没有被识别出来

image

在010中发现明显多了一部分内容,标志位是UPX

image

去壳

最常用的方法就是怎么加壳就怎么去壳

执行命令

BASH
upx.exe -d test.exe

image

再用工具查看,和没加壳的一样。

但是,做题过程中常常遇到UPX变种,出题人给exe加壳还修改了其他内容,比如UPX的标志位、overlay_offset值等。此时upx工具不再有效,需要进行手动脱壳。

手动脱壳

这里使用xdbg应用ESP定律进行脱壳

首先要先在加壳后的程序中定位到原程序的入口点。使用x64dbg打开后直接运行一步,发现停在了pushad上。该指令将所有寄存器的值压栈,而在UPX的执行流程里,这一步之后会加载UPX的解压代码用于将原始程序解压。

upx的工作原理其实是这样的:首先将程序压缩。所谓的压缩包括两方面,一方面在程序的开头或者其他合适的地方插入一段代码,另一方面是将程序的其他地方做压缩。压缩也可以叫做加密,因为压缩后的程序比较难看懂,主要是和原来的代码有很大的不同。最大的表现也就是他的主要作用就是程序本身变小了。变小之后的程序在传输方面有很大的优势。其次就是在程序执行时,实时的对程序解压缩。解压缩功能是在第一步时插入的代码完成的功能。联起来就是:upx可以完成代码的压缩和实时解压执行。且不会影响程序的执行效率。

upx和普通的压缩,解压不同点就算在于upx是实时解压缩的。实时解压的原理可以使用一下图形表示:

PYTHON
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在压缩之前的形式。

PYTHON
graph LR;
1-->7-->8-->9-->5-->6;

连起来就是:

PYTHON
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这么多),所以特别容易成为壳保护程序的一个分水岭。

image

具体操作

  • F9运行两次,来到pushad处

image

f8步过,走完push,RSP的值变为0x6CCCFFFA38

image

右键RSP-在转储/内存中跟随,右键内存对应的数据-断点-硬件-访问/存取​-双字(四字节)

image

f9执行,程序成功断在popad处,说明UPX的解压过程已经结束,现在在进行一些清理恢复工作。

image

f8步过,有一个循环,在其后下断点,f9过去,来到jmp​处,步进。这里就是真正的OEP(Original Entry Point)

image

image

找到入口点后使用Scylla工具脱壳

image

点击转储/dump​,会在当前文件夹下生成一个test_dmp.exe​文件,

然后点击IAT自动搜索(可能会出现以下情况,选择’是’)

image

再点击获取导入,最后修复转储,选择刚刚导出的test_dmp.exe​文件,在当前文件夹下生成test_dump_SCY.exe​文件。

image

脱壳到此结束

image

禁用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值,

image

image

当前是0x0160,二进制是0000 0001 0110 0000

每一位代表一个特性,主要的:

功能含义
0x0100NXCompat支持DEP(内存保护)
0x0040DynamicBase支持地址空间随机化(ASLR)
0x0020NoIsolation不使用隔离

要做的就是把 0x0040​ 这位去掉,也就是:

PLAINTEXT
0x0160 - 0x0040 = 0x0120

即把0x0160改为0x0120即可

注意,不同的文件这里的值可能不同,但要去掉ASLR都是减去0x40

修改后保存,重新用xdbg走一遍

image

可以发现RSP的值已经不是之前那一长串儿了

然后走一样的流程就行,IDA能够正常分析,但是还是有问题——程序无法运行👇👇

注意:如果遇到IAT表导入部分错误需要手动修复IAT表(造成无法运行等的错误),参考文章:通过x64dbg脚本功能修复IAT表

(尝试了没有成功,我的xdbg不能运行某系汇编?它报错说无法识别指令:Loop,有没有大佬教教🥲。我的xdbg是在吾爱上下载的x64dbg 中文版​)代码如下

ASM
$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

附一张图(小白初次学习,啥都不懂)

image


2.实战

buuctf 新年快乐

PE32,upx壳

image

还是ESP定律,32位程序能直接显示pushad

image

找到OEP,dump+修IAT表

image

其实这里直接拿dump后的文件丢IDA分析是可以的(会有一个报错,不过无伤大雅),只是不知道为什么不论是否修没修IAT,在IDA中都是显示UPX0:​段而不是正常的text:​段

image

修复IAT表后的如下(一模一样,也许本来就不需要修?但是不修就没法正常运行,只是IDA能正常静态分析! ):

image

脱壳结束,可以正常分析了

[NewStarCTF 2023 公开赛道]咳

PE64,UPX壳

image

一样的方法,具体过程就略过了

image

其中KE_dump_SCY.exe​和KE_dump.exe​都不能正常运行,但不影响IDA正常分析,动调估计还是不行。

运行不了原因如下

image

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表修复成功。

image

  • UPX PRO​是一个upx打包的ELF文件

image

考点是overlay_offset​的值应该是F4而不是4F

image

  • UPX PRO MAX

UPX标志位全改成00了,只能手脱

image

UPX PRO MAX_dump.exe​和UPX PRO MAX_dump_SCY.exe​都不能运行,看官方WP应该也是静态分析

image

3.总结

本文以一个demo和几道题为例,介绍使用xdbg​和ESP定律手脱upx​壳的过程

手脱过程中遇到了IAT表不能自动修复从而导致脱壳后的程序只能静态分析,无法正常运行的情况。参考一篇文章的讲解(通过x64dbg脚本功能修复IAT表)但遇到了其他问题

PS:还是不够清爽,再看看其他文章找找方法

4.参考资料

主要:

其他:

5.补充

第二天又看了些博客,找到了一些解决方法

CTF中手工脱壳实例(UPX壳) | Reveone‘s Blog

image

Helloctf上的讲解:

image

修复IAT表后,如果遇见有一个导出失败的表,直接给它删了程序就能正常运行!

爽了~

如果要手动修复IAT的话参考以下文章

【逆向】脱壳后修复IAT并关闭ASLR_iatxp-CSDN博客

使用x64dbg脱壳之开源壳upx - CharyGao - 博客园

通过x64dbg脚本功能修复IAT表


Thanks for reading!

手脱UPX壳初探

周日 4月 27 2025
3401 字 · 13 分钟