好友
阅读权限 30
听众
最后登录 1970-1-1
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"为例来解释这个结构。
[Plain Text] 纯文本查看 复制代码
UPX 3.91w (Sep 30th 2013)
-------------------------
2013-09-30 17:51 305,152 upx391.exe
文件偏移000001F0处:
[Plain Text] 纯文本查看 复制代码
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
整理一下:
[Plain Text] 纯文本查看 复制代码
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
其中:
[Plain Text] 纯文本查看 复制代码
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"。
概念性地,"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"的末尾有这样一段注释:
[Plain Text] 纯文本查看 复制代码
/*
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>
*/
初一看,难以理解。这些指针指向的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"。
[Plain Text] 纯文本查看 复制代码
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
UPX.Unpacker.for.Dummies.osc.7z
(10.08 KB, 下载次数: 2894)
免费评分
查看全部评分
发帖前要善用【论坛搜索 】 功能,那里可能会有你要找的答案或者已经有人发布过相同内容了,请勿重复发帖。