Native程序,这里是指操作系统原生的二进制格式的程序,如Windows PE格式,Linux/Android下的ELF格式以及 macOS/iOS下的MachO格式。
Native程序常见扩展名
操作系统(格式) | 主程序 | 动态库/共享库 | 驱动程序 |
---|---|---|---|
Windows (PE) | .exe | .dll | .sys |
Linux/Android (ELF) | - | .so | .ko |
macOS/iOS (MachO) | - | .dylib | - |
Native 程序一般由 C/C++/Objective-C/Swift/Delphi/Go 等语言开发,具有资源消耗小,运行效率高等特点,运行在 PC、服务器、移动端、IoT等设备上。
#
Native程序的安全性问题虽然经过编译生成的Native程序一般不会存在原始的函数名、变量名,逆向分析难度较高,但由于相应的反编译工具也成熟强大,依然可以反编译为类C伪代码,再加上特征库定位、交叉引用分析等高级功能,仍然可以将代码高度还原,对于高安全性需要的场景,不经过保护很容易被逆向破解。
测试程序举例(Android ARM架构):
源代码
反编译效果
虽然函数中变量名等信息丢失,通过符号表(ELF)、导入/导出函数、字符串等信息的辅助分析,函数依然可以反编译为易读的代码。
#
Virbox Protector功能介绍Virbox Protector 对Native程序支持基础保护和函数级的保护,可以起到防逆向、防调试、防篡改以及运行时环境检测等功能。
#
基础保护基础保护,是对程序整体的保护,对程序的性能影响小,操作简单,可以达到防反编译、防篡改、防调试等效果。
#
压缩压缩,是将程序中的代码、数据以及一些格式相关的程序数据(如导入表、重定位表、部分资源信息)进行打包、压缩和加密,再将程序入口替换为壳代码,运行时由壳代码将代码和数据解密并还原,并修复一些必须的程序数据,再跳转到原始的程序入口(OEP) 执行。
压缩加密了整个代码段和数据段(ELF程序不会加密可写数据段),可以防止程序被静态反汇编、反编译,一定程度上起到防止文件补丁的效果。
#
内存校验内存校验,可以在程序加载时校验自身完整性,如果发现程序被篡改,则会退出进程。
如果需要在运行时动态进行校验,可以使用SDK标签
在代码中调用。
#
导入表保护(PE)导入表描述了程序的依赖库、依赖函数信息和导入地址,是模块间调用的边界,很容易暴露代码逻辑。
经过导入表保护后,原始的导入表被清除,模块间调用替换为壳的修复代码。
#
资源节加密(PE)Windows PE程序可以嵌入资源,一般存放界面布局、多语言字符串、图标等,开发者可能会将一些敏感数据以资源的形式嵌入到程序中,资源位于程序的资源节中(一般为 .rsrc节),很容易被提取和篡改。
资源节加密,可以将整个资源节加密,只保留图标和版本号等必要的信息,可以防止资源的提取和篡改。
#
附加数据加密(PE)附加数据是“拼接”到程序结尾的数据,某些音视频、PPT播放器等程序,会将资源、数据库等以附加数据的形式拼接到程序结尾。
附加数据加密,可以将附加数据加密,防止重要资源被提取。
#
移除调试信息(ELF)ELF程序中有时会包含Debug节,静态符号表,其中包含了函数名、函数地址等信息,对外发布时如果携带会降低程序的安全性。
移除调试信息,会将ELF程序中的 .debug 节,静态符号表移除。
#
检测调试器调试是逆向分析时的重要手段,可以在庞大的二进制指令中迅速定位到相关的逻辑。
调测调试器,可以检测当前模块的进程是否被 x64dbg/OllyDbg/IDA Pro/Windbg 等工具调试,被调试则退出阻止运行。
#
检测虚拟机(PE)检测程序是否在 VMWare, Virtual Box 等虚拟机中运行,检测到则退出阻止运行。
#
函数级保护函数保护以函数为粒度,提供了精细化,安全性高的针对性保护,适用于加密方案、客户端/服务端认证,通信加密等高安全性要求的场景。
#
代码加密代码加密是通过自修改代码(SMC)的方式,在函数被调用时解密自身,再跳转到正确的函数指令中执行。可以防止
代码加密后在运行时执行还是原始指令,因此几乎无性能影响,可以防脱壳,防止静态反编译。
保护前:
保护后:
#
代码混淆代码混淆是将函数中原始的指令,通过等价变换、立即数加密、间接跳转、虚假分支、花指令加扰、指令切片等手段,将原始指令转换为难以阅读的随机的指令片段。
功能特性
- 防止函数被反编译为可阅读函数,提升逆向分析的难度。
- 运行时也不会还原,干扰静态分析和动态调试分析。
- 函数体被拆分为无数个随机分布的指令块,破坏函数边界。
- 对内存访问和跳转指令进行转换,破坏反编译工具的交叉引用分析。
- 使函数没有指令特征,防止被特征定位。
- 混淆的指令中包含暗桩,可检测调试器Run Trace追踪。
保护前:
保护后:
#
代码虚拟化代码虚拟化,是保护过程中将函数中原始的汇编指令,转换为自定义的虚拟指令,运行时在自定义的虚拟机中执行,模拟了汇编指令中的内存访问、条件判断、寄存器状态等。
功能特性
- 虚拟指令每次保护时都是随机生成,需要逆向虚拟机代码才能理解虚拟指令对应的真实操作。
- 针对算法类的代码保护效果更好(函数调用较少的代码)。
- 虚拟机代码经过再度混淆,加强了自身安全性。
保护前:
保护后:
虚拟化后会在程序中插入解释器的汇编指令,还有虚拟机入口指令(用于加载虚拟指令,设置状态等)等,这些指令还会再度进行混淆,上图中只有部分入口跳转指令。
#
插件#
DSProtectorDSProtector 插件可以用于加密程序使用到的只读文件
,如脚本语言、音视频、图片、只读配置文件、数据库等,防止敏感文件被直接窃取。
#
支持范围#
操作系统与指令支持操作系统 | x86 | x64 | arm32 | arm64 |
---|---|---|---|---|
Windows | ✔️ | ✔️ | ✖️ | ✖️ |
Linux | ✔️ | ✔️ | ✔️ | ✔️ |
macOS | ✖️ | ✔️ | ✖️ | ✔️ |
Android | ✔️ | ✔️ | ✔️ | ✔️ |
iOS | ✖️ | ✖️ | ✖️ | ✔️ |
#
功能与操作系统支持保护选项 | Windows | Linux | macOS | Android | iOS |
---|---|---|---|---|---|
压缩 | ✔️ | ✔️ | ✖️ | ✔️ | ✖️ |
内存校验 | ✔️ | ✔️ | ✔️ | ✔️ | ✔️ |
导入表保护 | ✔️ | ✖️ | ✖️ | ✖️ | ✖️ |
附加数据加密 | ✔️ | ✔️ | ✔️ | ✔️ | ✔️ |
调试器检测 | ✔️ | ✔️ | ✔️ | ✔️ | ✔️ |
虚拟机检测 | ✔️ | ✖️ | ✖️ | ✖️ | ✖️ |
移除调试信息 | ✖️ | ✔️ | ✖️ | ✔️ | ✖️ |
[E] 代码加密 | ✔️ | ✔️ | ✔️ | ✔️ | ✔️ |
[M] 代码混淆 | ✔️ | ✔️ | ✔️ | ✔️ | ✔️ |
[V] 代码虚拟化 | ✔️ | ✔️ | ✔️ | ✔️ | ✔️ |
#
自动化集成#
命令行工具Virbox Protector
支持命令行选项,可以指定基础保护选项和函数选项,方便自动化集成。
命令行工具 virboxprotector_con
的默认路径位于:
Windows:C:\Program Files\senseshield\Virbox Protector 3\binLinux:/usr/share/virboxprotector/binmacOS:/Applications/Virbox Protector 3.app/Contents/MacOS/bin
#
使用配置文件使用Virbox Protector工具界面进行保护,在被保护的程序所在目录会生成 .ssp
配置文件,然后调用virboxprotector_con
:
virboxprotector_con<input_file>-o<output_file>
virboxprotector_con
会自动查找 <input_file>.ssp 作为配置文件开始保护。
#
SDK标签Virbox Protector 还支持SDK标签的方式对程序保护,可以精细地控制保护哪些函数或代码片段,也可以用于加密数据或字符串以及校验内存完整性。SDK标签支持C/C++等语言的调用集成,可以在安装目录 <install_dir>/example/sdk
目录下查看使用示例。
SDK标签支持的功能有:
- 常量数据加密(包括字符串)
- 内存校验
- 代码混淆
- 代码虚拟化
数据保护
如果程序中包含了涉及安全性的问题的敏感字符串或数据(如密钥,证书等),建议对数据也进行保护,可以使用SDK标签标记要加密的数据或字符串,标记后可以被Virbox Protector自动识别,在每次保护时会随机生成密钥,保证每次加密后的结果不同。
内存校验
Virbox Protector 加密选项中的内存校验,仅在程序启动时校验一次,如果破解者通过动态内存补丁的方式破解,可能会绕过检测,开发者可以通过 SDK 标签调用 VBProtectVerifyImage
进行校验,将校验时机与业务代码混合,提升校验的安全性。
#
保护选项PE
选项 | 命令行 | 默认选项 |
---|---|---|
压缩 | --pack= | 1 |
内存校验 | --mem-check= | 1 |
导入表保护 | --imp-protect= | 1 |
资源节加密 | --res-sect-enc= | 1 |
附加数据加密 | --overlay-enc= | 1 |
调试器检测 | --detect-dbg= | 0 |
虚拟机检测 | --detect-vm= | 0 |
ELF
选项 | 命令行 | 默认选项 |
---|---|---|
压缩 | --pack= | 1 |
内存校验 | --mem-check= | 1 |
调试器检测 | --detect-dbg= | 0 |
剥离符号表 | --strip-dbginfo= | 1 |
MachO
选项 | 命令行 | 默认选项 |
---|---|---|
内存校验 | --mem-check= | 1 |
调试器检测 | --detect-dbg= | 0 |
#
函数选项选项 | 命令行 |
---|---|
忽略不支持的函数 | --ignore-unsupported=<value> |
代码加密 | -e |
代码混淆 | -m |
代码虚拟化 | -v |
支持指定函数名称或规则保护,使用 ;
号隔开,支持通配符 *
,举例:
-m "function1;function2" -v "function3;function4" -e "test*" --ignore-unsupported=1
#
举例对Windows主程序保护:
virboxprotector_con test.exe --pack=1--imp-protect=1--mem-check=1--res-sec-enc=1--detect-dbg=1-o protected/test.exe
对Linux程序保护:
virboxprotector_con libhello.so --pack=1--mem-check=1--detect-dbg=0-o protected/libhello.so
对Linux程序保护(同时生成调试版和发布版):
virboxprotector_con libhello.so --pack=1--mem-check=1--strip-dbginfo=0-o protected/libhello_with_sym.sovirboxprotector_con -strip protected/libhello_with_sym.so -o protected/libhello.so
对macOS程序保护:
virboxprotector_con libtest.dylib --detect-dbg=1--mem-check=1-e"*"-o protected/libtest.dylib
#
保护指引#
普通的保护方式如果对安全性没有较高的要求,一般不需要函数级别的保护,只需要调整基础保护选项即可实现防反编译和防内存Dump脱壳,无需复杂的配置选项,直接用命令行工具即可完成保护。
保护选项 | 保护建议 | 保护效果 |
---|---|---|
压缩 | 勾选 | 压缩可以加密整个代码段,隐藏一些原程序的结构信息(导入表、重定位等),防止直接反编译。 |
内存校验 | 勾选 | 防止程序被修改。 |
导入表保护 | 勾选 | 加密导入表,防止脱壳,防止查看API引用。 |
资源节加密 | 勾选 | 加密PE资源节(.rsrc),防止资源信息被提取。 |
附加数据加密 | 有附加数据则勾选 | 部分打包工具会生成附加数据,此功能会将程序结尾的附加数据加密,防止提取。 |
调试器检测 | 每个进程只需要保护一个模块(如主程序exe);如果模块做为SDK发布供第三方调用,则不勾选 | 检测调试器,发现程序被调试则退出。 |
去除调试信息 | 建议勾选;如果要保留调试信息,请保护完毕调用命令行 -strip 后将调试信息移除再对外发布 | 去除 .debug 节,去除静态符号表。 |
[E] 代码加密 | 使用默认选项(仅加密入口函数) | 加密指定的函数 |
[M] 代码混淆 | 无需选择 | 混淆函数的指令 |
[V] 代码虚拟化 | 无需选择 | 虚拟化函数的指令 |
#
高安全性保护如果对安全性有较高要求,需要使用代码混淆
和代码虚拟化
对关键函数进行保护,比如授权验证,加密算法及流程,协议封装流程等重要逻辑。
#
保护前准备- 准备静态分析(反汇编/反编译)工具,用于保护效果的查看和评估。
- 原程序对应的符号文件:Windows 平台的 .pdb/.map文件,Linux 平台需要保留符号表(编译选项加上
-g
即可),如果使用SDK标签的方式则无需符号。
静态分析工具推荐:
IDA Pro(收费)
Ghidra(免费开源):Releases · NationalSecurityAgency/ghidra (github.com)
#
函数级保护对于高安全性需求的场景,需要用到代码混淆
和代码虚拟化
,这里着重介绍代码混淆
和代码虚拟化
的使用场景。
对于破解者来说,最核心的步骤是定位代码
,直接逆向关键代码逻辑
或修改代码
完成破解。例如对于协议分析,一般来说只需要逆向其中的协议、加解密或签名算法,不需要对程序修改即可达到目的(用于直接制作机器人或写脚本模拟封包)。对于功能的破解,往往需要对原程序的验证逻辑打补丁进行篡改,或者对认证数据进行填充。
代码混淆
用于模糊函数边界,拆散函数的分布(对函数切片并随机分配),加密指令中的立即数,去除指令原有的特征,还可以使得静态分析工具的交叉引用功能失效。简而言之,就是可以起到“一眼望去不知道这个函数是干什么的”,还可以让分析工具“无法直接搜索特征和交叉引用”,以及”让分析工具的反编译功能失效“,往往需要破解者动态跟踪去理解函数。
交叉引用:是指静态分析工具直接搜索函数和数据(包括全局变量、字符串等)之间的引用关系,快速定位相关的代码。
函数特征:通过常用库(包括运行时库、密码学库)里包含的特定指令序列或立即数,可以直接定位函数。
代码混淆对性能的有一定的影响,常用于保护:
- 核心代码及其调用关系的上下游函数。
- 密码学库函数的入口和特征位置(如AES SBox、ECC曲线特征)。
- 协议的封装流程。
- 重要的函数边界。
代码虚拟化
代码虚拟化,是将函数原本的汇编指令,转换自定义的虚拟机指令。函数被虚拟化保护后,可以将其看成一个黑盒,代码的核心逻辑是由对应的虚拟机解释执行的,安全性极高。如果一个加密方案可以逼迫破解者去分析虚拟化的代码,说明这个方案是非常成功的,需要分析的代码(破解点)越多,破解的难度就越大
。
代码虚拟化对程序的性能影响较大,不可大面积使用,常用于以下几类函数:
- 对安全性要求较高的一些自定义算法。
- 可能被篡改的函数(如关键的验证逻辑)。
#
对外发布注意事项- 对外发布的 Windows 系统的程序,不可携带符号文件(.pdb/.map)
- 对于Linux/Android程序,需要去除符号表(请务必勾选
移除调试信息
)
#
常见问题#
不支持的函数Native程序中的函数是通过反汇编以及引用分析解析出来的,由于编译器优化、代码复杂度、保护技术等原因,有些函数是无法被保护的,这类函数在保护过程中日志窗口会有提示,这里列出一些常见的不支持的函数和原因。
函数太小
原因:
- 函数过于简单,指令字节数太少,无法容纳基本的跳转指令(具体大小要求不同CPU架构会有不同)。
解决办法:
- 将函数声明为内联函数
- 使用SDK标签
未分配的栈
原因:
- 函数简单,没有调用任何函数,编译器优化后的指令,未分配栈空间直接使用(常见于GCC和Clang),由于混淆和虚拟化都是基于栈的,保护后会导致数据访问错误。
解决办法:
- 使用SDK标签,防止编译器优化
结构化异常处理函数
原因:
- 由于Windows异常处理的技术细节,代码在混淆和虚拟化后会导致
栈展开
过程出现错误,仅PE程序有此问题。
解决办法:
- 不使用异常处理
- 不保护
分析失败
原因:
- 在反汇编分析过程时逻辑出现错误,未正确分析函数的基本块,非常复杂的代码可能会出现
解决办法:
- 提交工单和问题样本,由产品开发人员解决
#
运行崩溃原因:
- 函数分析不正确,未正确识别外部调用。
解决办法:
- 请勿选择没有函数名,或不知道函数作用的函数进行保护。
- 提交工单和问题样本,由产品开发人员解决。
#
没有函数名原因:
- 没有pdb文件(PE)
- 程序经过strip,或使用 Release编译(ELF)
解决办法:
- 将pdb文件放在被保护程序同一目录下 (PE)
- 编译选项加入
-g
(ELF) - 使用SDK标签
#
杀毒软件误报原因:
- 程序被加密后,无法扫描特征,一般病毒和木马也借此逃避杀毒软件,因此杀毒软件常将加密后的程序直接定义为可疑文件。(一般Windows下的程序容易出现)
解决办法:
- 使用数字签名,可以大幅减轻杀毒软件误报
- 在杀软平台提交审核