排行榜 统计
  • 文章总数:179 篇
  • 评论总数:1 条
  • 分类总数:7 个
  • 最后更新:3天前

Native 程序保护指引

本文阅读 22 分钟
首页 软件安全 正文

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架构):

源代码

src

反编译效果

decompile

虽然函数中变量名等信息丢失,通过符号表(ELF)、导入/导出函数、字符串等信息的辅助分析,函数依然可以反编译为易读的代码。

Virbox Protector功能介绍#

Virbox Protector 对Native程序支持基础保护和函数级的保护,可以起到防逆向、防调试、防篡改以及运行时环境检测等功能。

基础保护#

基础保护,是对程序整体的保护,对程序的性能影响小,操作简单,可以达到防反编译、防篡改、防调试等效果。

压缩#

压缩,是将程序中的代码、数据以及一些格式相关的程序数据(如导入表、重定位表、部分资源信息)进行打包、压缩和加密,再将程序入口替换为壳代码,运行时由壳代码将代码和数据解密并还原,并修复一些必须的程序数据,再跳转到原始的程序入口(OEP) 执行。

压缩加密了整个代码段和数据段(ELF程序不会加密可写数据段),可以防止程序被静态反汇编、反编译,一定程度上起到防止文件补丁的效果。

内存校验#

内存校验,可以在程序加载时校验自身完整性,如果发现程序被篡改,则会退出进程。

如果需要在运行时动态进行校验,可以使用SDK标签在代码中调用。

导入表保护(PE)#

导入表描述了程序的依赖库、依赖函数信息和导入地址,是模块间调用的边界,很容易暴露代码逻辑。

imp

imp

经过导入表保护后,原始的导入表被清除,模块间调用替换为壳的修复代码。

imp_protected

资源节加密(PE)#

Windows PE程序可以嵌入资源,一般存放界面布局、多语言字符串、图标等,开发者可能会将一些敏感数据以资源的形式嵌入到程序中,资源位于程序的资源节中(一般为 .rsrc节),很容易被提取和篡改。

res

资源节加密,可以将整个资源节加密,只保留图标和版本号等必要的信息,可以防止资源的提取和篡改。

附加数据加密(PE)#

附加数据是“拼接”到程序结尾的数据,某些音视频、PPT播放器等程序,会将资源、数据库等以附加数据的形式拼接到程序结尾。

附加数据加密,可以将附加数据加密,防止重要资源被提取。

移除调试信息(ELF)#

ELF程序中有时会包含Debug节,静态符号表,其中包含了函数名、函数地址等信息,对外发布时如果携带会降低程序的安全性。

移除调试信息,会将ELF程序中的 .debug 节,静态符号表移除。

检测调试器#

调试是逆向分析时的重要手段,可以在庞大的二进制指令中迅速定位到相关的逻辑。

调测调试器,可以检测当前模块的进程是否被 x64dbg/OllyDbg/IDA Pro/Windbg 等工具调试,被调试则退出阻止运行。

检测虚拟机(PE)#

检测程序是否在 VMWare, Virtual Box 等虚拟机中运行,检测到则退出阻止运行。

函数级保护#

函数保护以函数为粒度,提供了精细化,安全性高的针对性保护,适用于加密方案、客户端/服务端认证,通信加密等高安全性要求的场景。

代码加密#

代码加密是通过自修改代码(SMC)的方式,在函数被调用时解密自身,再跳转到正确的函数指令中执行。可以防止

代码加密后在运行时执行还是原始指令,因此几乎无性能影响,可以防脱壳,防止静态反编译。

保护前:

fn_enc_before

保护后:

fn_enc_after

代码混淆#

代码混淆是将函数中原始的指令,通过等价变换、立即数加密、间接跳转、虚假分支、花指令加扰、指令切片等手段,将原始指令转换为难以阅读的随机的指令片段。

功能特性

  • 防止函数被反编译为可阅读函数,提升逆向分析的难度。
  • 运行时也不会还原,干扰静态分析和动态调试分析。
  • 函数体被拆分为无数个随机分布的指令块,破坏函数边界。
  • 对内存访问和跳转指令进行转换,破坏反编译工具的交叉引用分析。
  • 使函数没有指令特征,防止被特征定位。
  • 混淆的指令中包含暗桩,可检测调试器Run Trace追踪。

保护前:

fn_obfus_before

保护后:

fn_obfus_after

代码虚拟化#

代码虚拟化,是保护过程中将函数中原始的汇编指令,转换为自定义的虚拟指令,运行时在自定义的虚拟机中执行,模拟了汇编指令中的内存访问、条件判断、寄存器状态等。

convert

功能特性

  • 虚拟指令每次保护时都是随机生成,需要逆向虚拟机代码才能理解虚拟指令对应的真实操作。
  • 针对算法类的代码保护效果更好(函数调用较少的代码)。
  • 虚拟机代码经过再度混淆,加强了自身安全性。

保护前:

fn_vm_before

保护后:

fn_vm_after

虚拟化后会在程序中插入解释器的汇编指令,还有虚拟机入口指令(用于加载虚拟指令,设置状态等)等,这些指令还会再度进行混淆,上图中只有部分入口跳转指令。

插件#

DSProtector#

DSProtector 插件可以用于加密程序使用到的只读文件,如脚本语言、音视频、图片、只读配置文件、数据库等,防止敏感文件被直接窃取。

支持范围#

操作系统与指令支持#

操作系统x86x64arm32arm64
Windows✔️✔️✖️✖️
Linux✔️✔️✔️✔️
macOS✖️✔️✖️✔️
Android✔️✔️✔️✔️
iOS✖️✖️✖️✔️

功能与操作系统支持#

保护选项WindowsLinuxmacOSAndroidiOS
压缩✔️✔️✖️✔️✖️
内存校验✔️✔️✔️✔️✔️
导入表保护✔️✖️✖️✖️✖️
附加数据加密✔️✔️✔️✔️✔️
调试器检测✔️✔️✔️✔️✔️
虚拟机检测✔️✖️✖️✖️✖️
移除调试信息✖️✔️✖️✔️✖️
[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] 代码虚拟化无需选择虚拟化函数的指令

高安全性保护#

如果对安全性有较高要求,需要使用代码混淆代码虚拟化对关键函数进行保护,比如授权验证,加密算法及流程,协议封装流程等重要逻辑。

保护前准备#

  1. 准备静态分析(反汇编/反编译)工具,用于保护效果的查看和评估。
  2. 原程序对应的符号文件:Windows 平台的 .pdb/.map文件,Linux 平台需要保留符号表(编译选项加上 -g即可),如果使用SDK标签的方式则无需符号。

静态分析工具推荐:

函数级保护#

对于高安全性需求的场景,需要用到代码混淆代码虚拟化,这里着重介绍代码混淆代码虚拟化 的使用场景。

对于破解者来说,最核心的步骤是定位代码,直接逆向关键代码逻辑修改代码完成破解。例如对于协议分析,一般来说只需要逆向其中的协议、加解密或签名算法,不需要对程序修改即可达到目的(用于直接制作机器人或写脚本模拟封包)。对于功能的破解,往往需要对原程序的验证逻辑打补丁进行篡改,或者对认证数据进行填充。

代码混淆

用于模糊函数边界,拆散函数的分布(对函数切片并随机分配),加密指令中的立即数,去除指令原有的特征,还可以使得静态分析工具的交叉引用功能失效。简而言之,就是可以起到“一眼望去不知道这个函数是干什么的”,还可以让分析工具“无法直接搜索特征和交叉引用”,以及”让分析工具的反编译功能失效“,往往需要破解者动态跟踪去理解函数。

交叉引用:是指静态分析工具直接搜索函数和数据(包括全局变量、字符串等)之间的引用关系,快速定位相关的代码。

函数特征:通过常用库(包括运行时库、密码学库)里包含的特定指令序列或立即数,可以直接定位函数。

代码混淆对性能的有一定的影响,常用于保护:

  • 核心代码及其调用关系的上下游函数。
  • 密码学库函数的入口和特征位置(如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下的程序容易出现)

解决办法:

  • 使用数字签名,可以大幅减轻杀毒软件误报
  • 在杀软平台提交审核


本文来自投稿,不代表本站立场,如若转载,请注明出处:https://firsource.cn/soft/181.html
.NET程序保护指引
« 上一篇 01-19
使用create-react-app脚手架创建react项目
下一篇 » 01-21