背景介绍
比赛过程中,一部分 Re 题会被加 UPX 壳。正常情况下只需要用 upx -d /path/to/file
即可脱壳,但偶尔有题目使用魔改的 UPX 无法直接脱壳,这时就需要手工脱壳。本文记录了一种借助 x64dbg
及 Scylla
的手工脱壳方法。
准备
首先准备一个 Test 程序,初始情况下用 IDA 打开情况如下:
现在使用 upx -9 /path/to/file
进行加壳,结果如下:
使用 IDA 打开时会提示 Some imported modules will not be visible because the IAT is located outside of memory range of the input file.
,忽略后的结果如图:
这样的代码完全无法阅读,字符串、函数信息等都已经没了。
下面开始手工脱壳。
脱壳
寻找入口点
首先要先在加壳后的程序中定位到原程序的入口点。使用 x64dbg 打开后直接运行一步,发现停在了 pushad
上。该指令将所有寄存器的值压栈,而在 UPX 的执行流程里,这一步之后会加载 UPX 的解压代码用于将原始程序解压。
upx 的工作原理其实是这样的:首先将程序压缩。所谓的压缩包括两方面,一方面在程序的开头或者其他合适的地方插入一段代码,另一方面是将程序的其他地方做压缩。压缩也可以叫做加密,因为压缩后的程序比较难看懂,主要是和原来的代码有很大的不同。最大的表现也就是他的主要作用就是程序本身变小了。变小之后的程序在传输方面有很大的优势。其次就是在程序执行时,实时的对程序解压缩。解压缩功能是在第一步时插入的代码完成的功能。联起来就是:upx 可以完成代码的压缩和实时解压执行。且不会影响程序的执行效率。
upx 和普通的压缩,解压不同点就算在于 upx 是实时解压缩的。实时解压的原理可以使用以下图形表示:
假设 1 是 upx 插入的代码,2,3,4 是压缩后的代码。5,6 是随便的什么东西。程序从 1 开始执行。而 1 的功能是将 2,3,4 解压缩为 7,8,9。7,8,9 就是 2,3,4 在压缩之前的形式:
连起来就是:
参考文献:upx 加壳原理_zacklin 的专栏 - CSDN 博客
所以我们只要跟踪到这部分栈被弹出,就意味着解压完成。之后的代码就是原始代码。
那么如何跟踪这部分栈呢?只需要先 F7
步进, pushad
执行完成后对当前栈顶的内存地址下一个硬件断点即可。
然后 F5
继续执行。再次中断处如图:
可以看到 EIP
上方就是 popad
,说明 UPX 的解压过程已经结束,现在在进行一些清理工作。
看到下方 00D01800
到 00D01806
的代码,它们在将缺失的栈段空间补齐,会循环若干次。故在 00D01809
这个 jmp
上下断点。执行到该语句后再 F7
步进一次,到达其跳转后的地点:
这里就是我们要找的入口点。
脱壳
找到入口点以后使用 x64dbg
自带的 Scylla
进行脱壳。
直接使用 IAT Autosearch
功能,期间可能会提示使用 IAT Search Advanced
,选 “是” 继续。然后 Get Imports
,最后直接 Dump
。
Dump 出来的文件使用 IDA 打开,可能依旧会提示 IAT表
缺失,不管它,直接看 main
函数。
Done 😃