加壳保护与程序调试存在天然矛盾:加壳旨在防止逆向分析,而程序崩溃时需定位原始错误位置。下文从加壳原理出发,提出兼容调试的解决方案。
加壳的基本功能
加密/压缩
加密/压缩是加壳最常见的功能,其原理是对程序整个代码段和数据段压缩或加密,防止静态反编译,但是在运行过程中,代码段和数据段是明文,因此不会影响调试。
混淆和虚拟化
不同于加密,混淆和虚拟化都会对程序代码进行不可逆的变换,在运行过程中也不会存在原始的机器指令,如果程序运行过程中崩溃的位置恰好在混淆或虚拟化后的指令,则无法直接定位到具体的代码位置。
混淆和虚拟化后的指令虽然不能还原,但一般不会影响栈回溯,通过栈回溯可以得到上游函数的调用点,配合调试信息可以获取基本的函数信息。
反调试
反调试功能可以在运行过程检测调试器状态,如果对进程附加调试会受影响,但并不影响内存转储(dump/core 文件的生成)。
程序的调试信息
为了方便排查运行时错误,操作系统和编译器都设计了相应的内存转储机制和对应的程序调试信息:
程序对外发布时,应剥离调试信息,否则有安全性问题,并将调试信息保留在开发环境中,方便错误排除。
程序加壳后如何生成调试信息
Windows 程序处理方案
对于Windows 程序(PE 格式),调试信息位于单独的 pdb 文件中,加壳后程序中的函数入口不会受影响,pdb 文件仍然有效,无需再做处理。
Linux/Android 程序处理方案
对于 Linux/Android 程序(ELF格式),调试信息位于 ELF Section 中,可按如下方式处理:
#加壳保护,并保留调试信息
virboxprotector_con <file_path> <options..> --strip-dbginfo=0 -o <protected_file_with_dbginfo>
#对加壳保护后带有调试信息的文件 strip
virboxprotector_con -strip <protected_file_with_dbginfo> -o <protected_file>
按照以上命令,可以得到 <protected_file> 用于发布,并将 <protected_file_with_dbginfo> 保留用作调试。
macOS/iOS 程序处理方案
macOS程序(Mach-O格式),如果调试信息可能位于 dSYM 文件中,需要将 dSYM 与原程序放在一起,并按如下方式处理:
# 直接对程序保护,在输出目录会生成新的 dSYM 文件。
virboxprotector_con <file_path> -o <protected_file>
如果调试信息位于符号表,需按如下方式处理:
#加壳保护,并保留调试信息
virboxprotector_con <file_path> <options...> --strip-dbginfo=0 -o <protected_file_with_dbginfo>
#对加壳保护后带有调试信息的文件 strip
virboxprotector_con -strip <protected_file_with_dbginfo> -o <protected_file>
#使用dsymutil 进一步生成 dSYM(可选)
dsymutil <protected_file_with_dbginfo> -o <dsym_file>
同理,将生成的 <protected_file> 用于发布, <protected_file_with_dbginfo> 和 dSYM 文件保留用作调试。