首页
社区
课程
招聘
[分享]UPX完美脱壳脚本
2013-11-15 15:50 30287

[分享]UPX完美脱壳脚本

2013-11-15 15:50
30287
UPX Unpacker for Dummies
Version: 1.3.0.459



简单地说,脚本做UPX的"-d"选项相同的事。不过在UPX的"-d"选项不可用时,脚本仍然能完美脱壳。

只需要"OllyDbg v1.10"及插件"ODbgScript plugin v1.82.6.110"。
使用方法很简单:
1. OD打开或拖放目标EXE/DLL。在DLL的情况下,OllyDbg会用自身的loaddll.exe加载DLL文件,并停在DllEntryPoint处。
2. 在目标的入口点(EntryPoint)运行脚本。当前eip为EntryPoint是运行脚本的唯一要求。
3. 待ODbgScript显示"Script finished"后,退出OllyDbg。
   不要继续运行或调试目标,脚本使用了目标的内存空间,内容已经变化,继续可能不正常。
4. 运气好的话,在目标的相同文件夹内会生成已脱壳的文件。请检查"Script Log Window"里的内容,有助于排错。

显然,仅支持win32/pe类型的文件,以下讨论也限制在此文件类型。

写脚本时参考了UPX的源码"upx-3.91-src.tar.bz2",在一些细节的处理上还是不太清楚,所以还跟了一下其最近版本"UPX 3.91w(Sep 30th 2013)"的decompress过程,及几个样本自解压的过程。
因为有源码,从技术上讲已经没有难点或秘密之处,但一些不易理解的东西需要揭开面纱,并注意细节。

一.UPX文件结构
经过UPX压缩的win32/pe文件,包含三个区段:UPX0, UPX1, .rsrc或UPX0, UPX1, UPX2(原文件本身无资源时)。
UPX0:在文件中没有内容,它的"Virtual size"加上UPX1的构成了原文件全部区段需要的内存空间,相当于区段合并。
UPX1:起始位置为需解压缩的源数据,目标地址为UPX0基址。紧接着源数据块是"UPX stub",即壳代码。一个典型的pushad/popad结构,所以人们常用"ESP定律"来脱UPX。
.rsrc/UPX2:在原文件有资源时,含有原资源段的完整头部和极少部分资源数据(类型为ICON、GROUP_ICON、VERSION和MANIFEST),以保证explorer.exe能正常显示图标、版本信息。还有就是UPX自己的Imports内容,导出表的库名和函数名(如果有的话)。

这里需要注意的是,压缩的数据是按内存而不是按文件!

二.UPX"-d"选项的烦恼
UPX的"-d"选项在解压缩文件时,需要一个UPX1HEAD结构,它被清除或被修改都会造成UPX拒绝解压。需要借助其他工具来修复或恢复,但并不总能成功。
以"UPX 3.91w"为例来解释这个结构。
[FONT="Courier"]UPX 3.91w (Sep 30th 2013)
-------------------------
2013-09-30  17:51           305,152 upx391.exe[/FONT]


文件偏移000001F0处:
[FONT="Courier"]000001F0:  33 2E 39 31.00 55 50 58.21 0D 09 0E.0A 94 06 BB  3.91 UPX!
00000200:  FF 0E 97 35.8F 93 86 19.00 AC 93 04.00 00 E6 18
00000210:  00 26 13 00.BA[/FONT]


整理一下:
[FONT="Courier"]000001F0:  33 2E 39 31.00       "3.91",压缩时使用的UPX版本ASCII串,无实际意义。
UPX1HEAD(header.S)
000001F5:  55 50 58 21          UPX_MAGIC_LE32: "UPX!",UPX Tag
000001F9:  0D 09 0E 0A          version: 0D; format: 09(UPX_F_WIN32_PE); method: 0E(M_LZMA); level: 0A(--best)
000001FD:  FFBB0694             uncompressed adler32
00000201:  8F35970E             compressed adler32
00000205:  00198693             uncompressed length
00000209:  000493AC             compressed length
0000020D:  0018E600             original file size
00000211:  26 13 00 BA          filter id: 26; filter cto: 13; unused: 00; header checksum: BA[/FONT]


其中:
[FONT="Courier"]version:             PE file=0C/0D;djgpp2/coff=0E;其它=0D。
format:              可执行文件格式;win32/pe文件为09。
method:              压缩算法;多种:NRV,LZMA和DEFLATE(zlib)等。
level:               压缩级别;1~10.,对应选项:-l1~-l9,--best。
uncompressed adler32:数据解压后/原数据的adler32 Hash。
compressed adler32:  数据压缩后的adler32 Hash。
uncompressed length: 数据解压后/原数据的大小。
compressed length:   数据压缩后的大小。
original file size:  原文件大小。注意:不包含附加数据(Overlay)!
filter id/filter cto:涉及数据压缩理论的重要概念,请参阅"upx-3.91-src\doc\filter.txt"。[/FONT]

概念性地,"filtering"是指在数据压缩前进行预处理,增加字节串的匹配长度,以提高压缩率。
最常见的就是代码部分的E8xxxxxxxx/E9xxxxxxxx(Call/Jmp Near)处理,预处理扫描代码所有的E8/E9指令以找出一个字节作为标识(filter cto),标识字节不应是紧接着E8/E9后的那些字节之一;解压后的"unfiltering"代码会根据这个标识来还原。
当然具体实现时远比这个复杂。印象中ASProtect也有相似的E8/E9处理过程。

解压时,先按源数据的大小计算adler32,比较之,若不相符,终止不再继续;然后按format/method执行相应的解压模块,比较解压后的大小,再计算解压后的adler32,比较之,不符合同样挂掉。
所以在用UPX的"-d"选项来解压缩文件时,没有UPX1HEAD或其中的字段有问题就一定失败。

“提高压缩率”是UPX的宗旨!但也造成了它的某些缺陷,比如对win32/pe类型的文件,--exact选项不可用,永远无法还原出与原文件完全相同的内容。
UPX自己也提到文件映像的“证书”问题,即一个经数字签名的可执行文件在压缩、解压缩后,由于内容已经改变使得签名毫无意义。这点需要注意!

三.脚本的工作原理
脚本不依赖UPX1HEAD,靠"UPX stub"自身来实现数据解压。只要目标能正常运行,解压一定正确,脚本完美脱壳就成为可能。
"UPX stub"的全称为"UPX assembly stub",它是用汇编写的一个模板(Template)或框架(Framework)。写得非常精巧,且具前瞻性。十年前的结构和现在相比也没有太大的变化,这是UPX的强大之处,也是脚本能通用的前提条件。
结构非常灵活,根据压缩时的选项,采用“宏”的方式来填充具体代码。看得出来,其中每一个字节都经过仔细推敲,且得到时间的检验和不断完善,再次赞叹其强大!
喜爱汇编的人一定不要错过这部分内容,很值得学习研究!

脚本完全遵循这个框架的处理流程:解压,unfiltering,导入表(IMPORT)、重定位表(RELOCATION)、导出表(EXPORT)、资源表(RESOURCE)的处理。
资源表的恢复是脚本必须的,"UPX stub"无需再作处理,没有相应的代码。

1. 解压(DECOMPRESSION)
脚本直接利用stub的代码,在解压完成处设一个断点,停下来,从解压出的数据里获得文件还原的关键信息。这里包含原文件的PE Header+Sections Table,及重建导入表和重定位表所用到的重要指针。
在源码"upx-3.91-src\src\p_w32pe.cpp"的末尾有这样一段注释:
[FONT="Courier"][COLOR="Green"]/*
 extra info added to help uncompression:

 <ih sizeof(pe_head)>
 <pe_section_t objs*sizeof(pe_section_t)>
 <start of compressed imports 4> - optional           \
 <start of the names from uncompressed imports> - opt /
 <start of compressed relocs 4> - optional   \
 <relocation type indicator 1> - optional    /
 <icondir_count 2> - optional
 <offset of extra info 4>
*/[/COLOR][/FONT]

初一看,难以理解。这些指针指向的UPX自定义结构又不清楚,在跟过几个样本后就明白了,这里不详细说它,篇幅会很长。感兴趣的请阅脚本的相应部分,自认为有较好的注释。

这里脚本并不关心它的解压算法。在每一个目标中,对应算法的解压代码就摆在那里的,直接用就好。

2. unfiltering
脚本在它完成处再设一个断点,停下来后,stub的代码已经替我们完成了unfiltering。
到这里stub对我们已经没有多大用处了,只移植了后面一小段处理重定位表的代码用于重建。
因为从这以后,stub处理那些表的方式和我们恢复文件不一样:它会继续完成导入、重定位等过程,而我们需要重建导入表、重定位表等等。

3. 重建导入表
从这以后,脚本并未严格按源码的方式,它有太多的类,比较繁琐。基于我对PE文件的理解,所以存在错误或bug!
这里脚本进行的工作非常类似于大家所熟悉的工具ImportREC(Import REConstructor)。
视压缩时的选项,导入表有两种压缩方式,要分别处理。
a) 方式1
压缩时"Import Directory"和"Import Address Table"的内容被完全清空,以提高压缩率;需要全部重建。
b) 方式2
压缩时保留了完整的IAT和INT,"Import Directory"指向的那些IMAGE_IMPORT_DESCRIPTOR里仅有导入库名称指针(Name)。需要填写FirstThunk、导入库名的ASCII字符串及按IAT恢复导入函数名。

这里,所有IMAGE_IMPORT_BY_NAME里的Hint压缩时没有保存,无法恢复。这也是win32/pe类型的文件,--exact选项不可用的原因之一。方式1的INT全为空白,理论上与IAT相同,可复制一份,但没有必要,如果不需要对恢复的文件进行Binding的话。
另外注意到,UPX的"-d"选项在复制导入函数名时,使用“Compact”方式:因为IMAGE_IMPORT_BY_NAME.Hint全为0000,其低字节会作为前一个IMAGE_IMPORT_BY_NAME.Name字符串的结尾NULL。
脚本处理时坚持每个IMAGE_IMPORT_BY_NAME按字(Word)对齐。

4. 重建重定位表
这部分全部用脚本语句来实现时,发现实在太慢,不得已编写了一小段汇编代码。

5. 重建导出表
与重建导入表类似,不过要简单很多。IMAGE_EXPORT_DIRECTORY里的三张表:AddressOfFunctions、AddressOfNames和AddressOfNameOrdinals都完整保存,只需作地址转换后恢复到文件的对应位置、复制导出库名和函数名。

6. 重建资源表
需要恢复资源表的头部,压缩时移到".rsrc"段的ICON、GROUP_ICON、VERSION和MANIFEST数据也需要复制回原来的地方,并修改指针。
由于IMAGE_RESOURCE_DIRECTORY大多是嵌套的,所以学习源码,设计了一个递归子程序来遍历。有意思,用ODbgScript也可实现递归。在资源项比较多时,脚本处理比较耗时,但舍不得改为汇编。
提醒一下:在调试脚本重建导入表的子程序时发现ODbgScript的memcpy命令可能存在Bug(某几个函数名总是只复制一个DWORD的长度),详见脚本注释。

7. 附加数据(Overlay)的处理
脚本是支持Overlay的。因为不知道怎么用脚本来取得文件的实际大小,所以写了一小段汇编用API来实现。

脚本只经过有限的测试,包括还原UPX自身:UPX 1.20w(May 23rd 2001),UPX 1.25w(Jun 29th 2004),UPX 2.02w(Aug 13th 2006),UPX 3.09w(Feb 18th 2013)及UPX 3.91w (Sep 30th 2013)。
一些选项组合压缩的EXE和DLL测试样本。问题肯定是有的,欢迎指教!并借用UPX的一句话:
"This script comes with ABSOLUTELY NO WARRANTY!"

附件为脚本及以前写的一个"EmEditor Syntax File for ODbgScript"。
[FONT="Courier"]2013-11-14  23:15    39,599 UPX.Unpacker.for.Dummies.osc   MD5: 25EC5A0E5245B380FA33E0CD8957DB79
2013-01-31  01:21     6,874 ODbgScript.esy                 MD5: 3BFEA5E7B4A00FAC3097A0D880EBC957[/FONT]


[培训]内核驱动高级班,冲击BAT一流互联网大厂工作,每周日13:00-18:00直播授课

上传的附件:
收藏
点赞1
打赏
分享
最新回复 (12)
雪    币: 6528
活跃值: (2532)
能力值: ( LV3,RANK:30 )
在线值:
发帖
回帖
粉丝
Kisesy 2013-11-15 18:05
2
0
好文章~
雪    币: 263
活跃值: (342)
能力值: ( LV3,RANK:20 )
在线值:
发帖
回帖
粉丝
ntzwq 2013-11-15 18:18
3
0
顶下,应该加精。
雪    币: 212
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
liyantao 2013-11-15 18:48
4
0
其实我就是来看看
雪    币: 66
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
yax 2014-7-5 16:38
5
0
非常咱. 52pojie 还要注册帐号
雪    币: 200
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
风土人情 2014-7-8 10:11
6
0
学习,支持一下!
雪    币: 26
活跃值: (45)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
harborian 2014-9-19 10:00
7
0
Oops! Sorry, It's not packed by UPX or unsupported version.
在处理upx壳时,出现这个错误
雪    币: 26
活跃值: (45)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
harborian 2014-9-19 10:05
8
1
用upx已经完成脱壳,我是想重建reloc表。upx在压缩exe时,清空了reloc表,但是在脱壳后,由于imagebase的变化,程序不能正常运行。谁有什么工具或者高见?
雪    币: 26
活跃值: (45)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
harborian 2014-9-19 10:42
9
0
非常棒的工具,是我搞错了。我以为需要象手工脱壳那样运行到脱完壳跳转的地方,其实根本不用,只要用OD加载就可以了。再次感谢作者!
雪    币: 43
活跃值: (30)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
hbcld 2014-9-21 11:09
10
1
不错,看到这篇文章有些晚,赞一个
雪    币: 144
活跃值: (38)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
zylyy 2014-9-21 11:15
11
0
标记一下,也许会成为我下一个研究的主题哦。
雪    币: 190
活跃值: (12)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
xinoe 2014-10-22 10:55
12
0
去試試,,,如果可以脫掉就好了。。。。
雪    币: 2
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
fanghongjian 2018-8-11 18:35
13
0
感谢分享
游客
登录 | 注册 方可回帖
返回