简介:提供一套已编译通过的 UPX(Ultimate Packer for eXecutables)集成工程,面向需要在 Visual Studio 2013 下使用 UPX 源码进行二次开发或理解其原理的 C/C++ 开发者。工程以 rar 包发布,共 388 个文件,主要包括 161 个 .h 头文件、53 个 .cpp 与 37 个 .c 源文件、90 个 obj 中间文件,同时带有 VS2013 的工程配置(sln/vcxproj)、调试信息(pdb/idb/ilk)及编译日志,压缩包仅 11.12MB,可在 VS2013 中直接打开并编译运行。通过该工程,开发者能系统掌握 UPX 的 API 调用方式、动态链接库引用方法、命令行参数处理逻辑以及可执行文件的压缩与解压实现;还能从源码中追踪从底层压缩算法到命令行入口的完整调用链,并学习 VS2013 下工程属性配置、链接器设置、断点调试、单步跟踪和错误处理等实用技巧。已有 305 人学习下载,对刚接触 UPX 或希望在 VS2013 中集成压缩功能的初学者来说,是一份结构清晰、可直接对照实践的参考资料。 UPX这个工具,搞软件打包和逆向分析的人应该都熟——给exe、dll套一层压缩壳,体积能缩到三分之一甚至更小,程序运行的时候自己解压,对最终用户完全透明。我最近因为一个老项目维护,构建环境被严格锁定在VS2013上,需要从源码编译一份自用的UPX,本以为就是cmake三连的事,结果从版本选择开始就一路踩坑。把整个流程整理出来,给同样在老工具链上折腾UPX的朋友做个参考。
这篇文章按实操顺序来:先讲清楚UPX编译这件事背后的版本约束和依赖关系,再逐个编译UCL、zlib、LZMA这几个前置库,最后编译UPX主体,中间穿插所有我碰到的报错和排查思路。无论你是要完整复现编译过程,还是只想确认某个错误怎么解决,都可以直接跳到对应章节。
1. 项目背景:为什么要在VS2013上编译UPX
1.1 UPX到底解决了什么问题
UPX全称Ultimate Packer for eXecutables,是一个开源的“可执行文件压缩器”。它的原理说穿了不复杂:把PE、ELF、Mach-O这些可执行格式里的代码和数据做一次压缩,然后在文件头部附加一小段解压桩(stub)。程序启动时,stub先执行,把压缩内容在内存里还原,再跳转到原始入口点。对使用者来说,压缩后的文件和原文件没有任何使用上的区别,只是体积变小了、启动时多了几毫秒的解压开销。
举一个实际例子:一个5MB的exe,用UPX加壳后可能只剩1.5MB左右。对于要分发给客户的工具集、带附属DLL的绿色软件,这个体积缩减非常可观。也正因为它会在内存里动态还原原始代码段,很多软件加载完成后进程内存里看到的其实是完整代码,所以UPX也常被当作一个“轻量壳”用,防止别人直接把exe拖进反编译器里看代码。
关键点在于,UPX对目标文件格式和平台非常敏感。不同格式要匹配不同的stub,所以“能不能编译、编译出来能不能用”,直接取决于你手上的UPX源码版本和目标平台。UPX是纯C/C++写的,跨平台能力不错,Linux、Windows、macOS都能编译,但在某个具体编译器和系统组合下能不能一次通过,版本匹配是决定性因素。
1.2 为什么非VS2013不可
可能有人会问:UPX这么老牌的工具,直接去GitHub下Release版exe不就行了,何必自己编译。这里有个背景:很多企业级老项目,尤其是工控、金融、嵌入式上位机这类领域,构建环境是被严格锁定的。项目用了VC2013编译的第三方库,或者运行目标机器只装了VC12运行时,这时候所有新增工具都必须用VS2013重新编译一遍,否则可能混入VC2015/2017的运行时依赖,拖到现场就是经典的“找不到MSVCP140.dll”。
VS2013对应的MSVC版本是VC12,编译器版本号是18.00。在C++标准支持方面,VS2013对C++11只完成了部分支持,变参模板、右值引用这些能用,但另一些特性是半吊子状态。这就直接导致UPX版本选择的硬约束:UPX 4.x源码用了一些VS2013不支持的语言特性,强行编译会报出成百上千行错误,没有任何性价比。
所以结论是:在VS2013环境下,老老实实选UPX 3.09,不要碰4.x。
| UPX版本 | 发布时间 | 构建方式 | VS2013兼容性 | 说明 |
|---|---|---|---|---|
| 3.08 | 2016 | 传统Makefile | 可编译 | 老项目里比较常见 |
| 3.09 | 2018 | CMake + Makefile | 兼容性最好 | 建议直接选这个 |
| 4.0.x | 2023 | CMake | 不兼容 | 要求C++11完整支持,CMake版本也更新 |
选择3.09而不是3.08的原因很简单:3.09是3.x系列的收尾版本,修了不少问题,而且引入了CMake构建文件,用VS2013配合老版本CMake可以直接生成工程。3.08虽然也能编,但需要手动处理Makefile,麻烦得多。
2. 依赖库的角色分工与版本配对
2.1 三个前置库分别是干什么的
UPX源码编译不是孤立的,它依赖几个外部压缩库,每个库的角色不一样:
- zlib:提供DEFLATE压缩算法,UPX在部分场景下会用到它做处理。这个库大家应该很熟,几乎所有跨平台C/C++项目都绕不开它。
- UCL:提供NRV(Not Really Vanished)系列压缩算法,是UPX早期最核心的压缩器。UCL是一个纯C语言的极小库,编译难度几乎为零,但压缩率不如LZMA。
- LZMA SDK:提供LZMA/LZMA2算法,UPX 3.x开始把LZMA作为主力压缩算法,压缩率高,解压速度也还可以。
补充说明一个容易混淆的点:UPX的压缩流程不是“整个exe压成一个包”那么简单。它会把原始代码分段,先用LZMA或NRV做一次压缩,再用自定义的过滤器做优化,最后生成带stub的解压代码。zlib不是UPX的核心路径,但在某些构建配置里会被用到。实际使用中,三个库都准备齐是最省心的。
2.2 版本配比建议
我实测下来最稳的一套组合是:
- UPX 3.09源码
- zlib 1.2.11
- UCL 1.03
- LZMA SDK 19.00(兼容性较好的一版)
这里要特别强调一句:不要拿zlib 1.3.x或者zlib-ng分支去配VS2013。虽然理论上也能编译,但它们的CMake配置会要求比较新的特性,或者产生大量噪音警告,完全没必要给自己找麻烦。老工具链就配老依赖,这是我在折腾老环境时最深的体会。
3. 环境准备:VS2013和CMake的坑
3.1 VS2013安装时容易漏掉的东西
如果你机器上还没装VS2013,或者装的是精简版,用之前一定要确认两件事:
- 是否安装了VC++编译工具集。VS2013默认安装不会把所有C++组件都装上,特别是“Visual C++”和“Windows SDK”这两项,很多人装完发现C++项目报找不到cl.exe,就是这里漏了。安装时在功能选择里手动勾上。
- 是否打了Update 5补丁。这个补丁修复了大量标准库和编译器的bug,建议装上,不然编译某些模板代码可能出莫名其妙的递归实例化错误。
操作上,建议直接用“VS2013 x86 Native Tools Command Prompt”(VS2013本机工具命令提示符)。如果坚持用普通cmd,需要手动把cl.exe和nmake.exe所在目录加进PATH,位置一般在:
C:\Program Files (x86)\Microsoft Visual Studio 12.0\VC\bin C:\Program Files (x86)\Microsoft Visual Studio 12.0\Common7\IDE但手动配PATH容易漏掉rc.exe(资源编译器)和一堆DLL依赖,还是用官方命令提示符最省心。
3.2 CMake版本选择:不能用太新的
另一个容易翻车的点:CMake版本太新的时候,连“Visual Studio 12 2013”这个generator都不认识了,或者直接标成deprecated。我用的CMake 3.16.5,配置VS2013非常顺利。如果你机器上已经装了CMake 3.25以上,建议另装一个绿色版的3.16或3.18专门用于这个项目。
判断CMake支不支持VS2013,在命令行执行:
cmake --help看generator列表里有没有“Visual Studio 12 2013”这一项即可。没有就说明版本太新。
4. 依赖库编译实操:UCL和zlib
4.1 编译UCL 1.03
UCL源码包解压后,根目录就有CMakeLists.txt,直接用CMake配就行。打开“VS2013 x86 Native Tools Command Prompt”,进入源码目录,执行:
mkdir build cd build cmake -G "Visual Studio 12 2013" -A Win32 -DCMAKE_INSTALL_PREFIX=D:/deps/ucl .. cmake --build . --config Release --target install几点说明:
-A Win32指定生成32位库。UPX主要使用场景是32位PE文件压缩,依赖统一用32位最稳妥。如果目标平台是64位,也可以全部改x64。- 如果configure阶段报“Could NOT find C compiler”,多半是你没用VS2013命令提示符,CMake找不到cl.exe。
- UCL编译完会在
D:/deps/ucl下生成include和lib目录,里面是ucl.lib静态库和头文件。
UCL编译非常快,基本几秒钟就完事。
4.2 编译zlib 1.2.11
zlib在Windows下最省事的办法不是CMake,而是官方源码自带的VS工程文件。进入zlib-1.2.11/contrib/vstudio/vc12/,打开zlibvc.sln,这个工程就是给VS2012/2013准备的。用VS2013打开,选Release + Win32,生成。
编译后输出在contrib/vstudio/vc12/x86/Release Zlib/之类的目录,主要关注两种文件:
zlib.lib:静态链接库zlibwapi.dll和zlibwapi.lib:动态链接版本
我编译UPX时用的是静态库,在解决方案里右键zlibstatic项目单独生成,然后把include目录(zlib.h、zconf.h)和zlib.lib拷贝到D:/deps/zlib下,目录结构保持:
D:/deps/zlib/include/zlib.h D:/deps/zlib/lib/zlib.lib这里有个容易卡住的点:zlib生成时默认采用/MD运行时库(Release配置),UPX默认也是/MD,所以能对上。如果出现运行时库不一致的链接错误(LNK2038),回zlib工程改成和UPX一致的/MD重新编译即可。
5. UPX 3.09主体编译实战
5.1 CMake配置和生成
依赖就绪后,解压UPX 3.09源码,在源码根目录下执行:
mkdir build cd build cmake -G "Visual Studio 12 2013" -A Win32 -DCMAKE_PREFIX_PATH=D:/deps/ucl;D:/deps/zlib -DCMAKE_INSTALL_PREFIX=D:/deps/upx ..CMAKE_PREFIX_PATH是给CMake的find_package用的查找路径,两个依赖库用分号分隔。如果你的LZMA SDK也是单独编译的,可以一起加进来:
cmake -G "Visual Studio 12 2013" -A Win32 -DCMAKE_PREFIX_PATH=D:/deps/ucl;D:/deps/zlib;D:/deps/lzma -DCMAKE_INSTALL_PREFIX=D:/deps/upx ..UPX 3.x对LZMA SDK的查找方式比较随缘。有的环境能自动找到,有的不行。如果你的configure阶段提示找不到LZMA,又实在不想单独编译SDK,可以加一个参数把LZMA支持关掉:
cmake -G "Visual Studio 12 2013" -A Win32 -DCMAKE_PREFIX_PATH=D:/deps/ucl;D:/deps/zlib -DUPX_USE_LZMA=OFF ..只保留UCL也能完成编译,代价是压缩率会明显下降。我个人建议还是保留LZMA,毕竟这是UPX最重要的压缩路径。
配置成功后,目录下会生成UPX.sln。直接用VS2013打开,配置改成Release + Win32,生成解决方案。也可以继续在命令行构建:
msbuild UPX.sln /p:Configuration=Release /p:Platform=Win32编译完成后,src/Release目录下会出现upx.exe。
5.2 三个真实会踩的编译报错
第一个是error C2039: 'snprintf': is not a member of 'std'。老VC的<cstdio>没有把snprintf注入std命名空间,UPX 3.09部分代码用了std::snprintf。解决方案:在报错源文件顶部加#include <cstdio>,然后把std::snprintf改成_snprintf,或者define一个兼容宏。实际UPX 3.09官方修过这个问题,但某些分支版本还是会触发,改起来不麻烦。
第二个是链接报错,类似LNK2019: unresolved external symbol _ucl_nrv2b_decompress_8。这基本是UCL库没链接上,或者链接的是Debug版、架构不对的库。检查CMake配置时CMAKE_PREFIX_PATH有没有指向UCL安装目录,UCL是不是Release + x86编译的。反复出现时直接用Dependencies工具查看UPX项目实际链接了哪个库文件,十有八九是路径指向了旧版本。
第三个是LNK2038: mismatch detected for '_MSC_VER'。意思是某个依赖库不是用VS2013编译的。比如机器上之前用VS2017编过一个zlib库,VS2013在链接时检测到编译器版本不匹配,直接拒绝。这种没有捷径,所有依赖必须统一用VS2013重编。这也是老项目最坑的地方,依赖库来源一多就乱,最好建一个专门的D:/deps目录统一管理。
5.3 编译成功后的快速验证
在命令行执行:
upx --version如果输出类似“UPX 3.09 Markus & Laszlo ver.”的字样,说明编译成功。再找个测试exe试一下:
upx -9 C:\test\demo.exeUPX会显示压缩前后的体积对比和压缩率。5MB左右的exe压到1.5MB是很常见的效果。
6. 常见问题排查速查表
| 现象 | 可能原因 | 处理办法 |
|---|---|---|
| CMake报Generator not found | CMake版本太新,Visual Studio 12 2013被移出 | 换用CMake 3.16到3.20 |
| 找不到zlib.h或ucl.h | CMAKE_PREFIX_PATH没配置,或依赖目录结构不对 | 检查include和lib子目录层级 |
| 链接时一堆unresolved external | UCL或zlib库没找到,或架构不匹配 | 检查库路径,统一Release + x86 |
| LNK2038 _MSC_VER不匹配 | 依赖库由其他VS版本编译 | 所有库统一用VS2013重编 |
| 生成的upx.exe运行报0xc000007b | 32位exe在64位系统缺少VC运行时 | 安装VS2013的VC12 Redistributable x86 |
| 加壳后的exe被杀软误报 | UPX壳特征明显 | 无解,只能数字签名或换方案 |
| 压缩后的程序启动变慢 | 解压需要时间,尤其大文件 | 换更低压缩级别,或评估是否值得加壳 |
另外提醒一句:UPX本质是压缩,不是加密。不要把它当安全壳用。用upx -d就能解开壳还原原始代码,所以涉及安全需求的项目必须换真正的加壳或加密方案。UPX的定位就是纯粹的体积优化。
7. 最后再分享一点个人体会
折腾完整个流程,我最大的感受是:老工具链编译开源项目,最大的成本不是编译本身,而是版本的“对账”。UPX 3.09配UCL 1.03配zlib 1.2.11,这一个组合我是验证过可以用VS2013一路编译通过的。如果你也被老环境锁死,这个组合可以直接抄。
实际使用中还有一个技巧:编译好的upx.exe可以不用只当手工工具。在VS2013工程的后构建事件(Post-build event)里加一行命令:
"$(SolutionDir)tools\upx.exe" -9 "$(TargetPath)"这样每次Release编译完成就自动压缩输出文件,对交付体积敏感的项目非常实用。不过Debug阶段别开这个,压缩过会拖慢调试。老项目维护这件事,很多时候就是靠这些不起眼的小流程优化,一点点把效率找回来的。
本文还有配套的精品资源,点击获取