低成本防静态分析和防动态调试的解决方案
前言
提高程序的逆向成本,是很多保密性高的代码所必备操作。其中高成本的方案,有加壳,虚拟机保护等,这些方案对项目的改动较大。低成本的方案一般是代码混淆之类,可以最大程度减少代码改动。
最近项目刚好需要增强代码安全性,提高逆向难度,经过几天的摸索已经找到了一个低成本高收益的方案。下面会从防静态分析,防止动态调试两个方面入手。
本文针对C语言做处理,其他语言思路一样,可能还有更便利的方法。只不过一般安全性要求高的代码都是比较底层C代码,上层应用级别的代码基本没有值得防护的,当然越高级的语言本身越复杂,静态分析也越难。
防静态分析
我们知道,Mac上的可执行程序一般是macho格式的二进制文件,现有的很多静态分析工具,比如MachOView,Hopper,nm等,都能轻松获取二进制文件中的符号表还有程序段的指令集。比如下面:
// |
上面两个图可以看出用工具能够轻松防汇编出指令集,Hopper还能强大能直接把指令集解释成伪代码,基本和源码一致。
所以对于保密性比较高的代码,需要尽力提高逆向难度,保护公司的代码财产。
移除符号
防止静态分析,首先要做的就是移除内部符号。内部符号,包括数据段符号(全局变量),代码段符号(内部函数名)。
移除符号可以使用strip命令
strip -x TestSymbol -o TestSymbol_nosymbol
Xcode支持配置strip,只需要配置成下面这样即可
运行一下代码,用nm看一下符号,可以发现内部符号全部消失了(类型小写的是内部符号)
敲重点了,可以看到两个全局变量的符号还可以看到(str1,str2),还有一个函数符号(say)。可以在代码中加上attribute告诉编译器把符号隐藏掉,这样符号会变成内部符号然后被剔除。
__attribute__ ((visibility("hidden"))) |
编译后,再一次执行nm,可以看到str1, str2两个符号已经消失了。
代码混淆
代码混淆可以有两部分,一个是加密代码中的常量字符串,另一个是在逻辑代码中增加垃圾代码,这样编译出来的指令里面就多了很多垃圾指令,别人逆向的时候看得头昏眼花的,增加逆向难度。
先说一下加密字符串的,我在知乎上看到一个手工加密并且用宏的方式实现,最大程度较少代码的改动,感觉不错,具体细节可以看这篇文章:# 纯手工混淆C/C++代码(下) ,思路就是写一个小工具,把事先写好的字符串宏,给自动生成另一个同名的宏(成为加密宏),代码中如果要加密字符串的话,就用加密宏即可。具体细节可以看上面文章,很简单的不多说了。
这样编译出来的二进制文件,也看不到明文字符串了,而且代码的改动也非常少,只需要把原来的字符串写成对于宏就可以,连工程配置都不需要修改。
此外还可以通过工具对生成一些垃圾代码,并用宏的形式附加到需要保护的函数中,这些都能提高静态分析的成本。
防动态调试
防止动态调试,主要是防止应用程序被Xcode中的attach功能附加上,这样即便没有源码,也可以对进程进行调试。要防止,那么需要先知道attach的原理。
attach大概原理其实我还没怎么研究,可以试一下,在Xcode上对还没有运行的程序执行attach操作后,运行程序时,程序的父进程是debugserver
,debugserver
会利用ptrace系统调用对子进程进行调试。
如果attach正在运行的进程,其父进程不变依然是launchd
,这种情况下具体如何调试我还没弄清楚,不过最终也会使用到ptrace。
实际上,Mac还是提供了标准的接口供程序阻止其他进程对自己进行动态调试的,具体方法可以参考这篇文章,实现起来就是一个系统调用即可。
下面代码演示了两种防动态调试的方法,一种是直接调用指定标号的系统调用,另一种是调用ptrace函数
// |
也可以直接调用指定编号的系统调用
syscall(26, 31, 0,0,0);
注意,上面代码可以另外加debug宏,不然程序也没法正常debug了。
防注入
注入这个之前的文章说过很多次了,我们的程序在运行的时候会调用依赖的动态库,这种属于dyld对程序的合法注入,当然还有一类是入侵形式的注入,以到达对源程序进行hook的效果,进而改变源程序的功能,防注入也可以在编译时带上参数实现,在 Other Linker Flags 配置上添加下面内容即可:
-Wl,-sectcreate,__RESTRICT,__restrict,/dev/null
总结
本文从代码混淆,移除符号,配置工程三个方面提升程序的逆向成本,增加一些良好的安全防护功能,仅对项目工程做了轻微改动,可以说是低成本高收益的解决方案。