不同项目的安全性要求不同,对于保密性要求高的代码,需要提高程序的逆向成本。
防静态分析
静态分析工具很容易从Mach-O二进制文件中获取到其中的符号表,比如MachOView、Hopper等。默认情况下,我们程序中的函数名、全局变量等都是会被导出的,也就是从符号表中可以查阅得到。有意义的符号名显然对于后续的静态分析是有很大帮助的。
符号Strip
防止静态分析,首先可以做的是移除内部符号,包括数据段符号和代码段符号(全局变量及内部函数名)。
因此对于项目中不需要导出的符号,我们尽量不导出。可以在链接后进行strip掉,移除符号可以使用strip命令。Xcode支持配置(Strip Style),并且可以使用编译声明__attribute__ ((visibility("hidden")))
隐藏符号。
另一方面可以进行符号混淆,或者更广范围的代码混淆。
代码混淆
符号混淆需要将有意义函数符号进行替代,比如C系列的可以通过宏在编译前进行替代。符号混淆需要注意的,一个是动态语言的代码中可能会使用反射,需要规避符号混淆引起Crash;另一方面则是需要处理符号解析的问题。
除此之外,代码混淆还有两部分:①代码中的常量字符串加密,比如一些关键的key;②逻辑代码中增加垃圾代码/指令。都是为了增加逆向的难度。
字符串加密,很常用的方式便是将常量字符串转换成字符数组,再进一步可以通过预处理附加加解密功能,即使很简单的加密也会让逆向难度陡增。
逻辑代码混淆,一种简单的方式是在需要保护的逻辑中特意添加无意义代码;另外指令混淆上有些工具可以支持,比如有人通过修改LLVM在指令级别进行混淆。这些都是为了提高静态分析的成本。
防动态调试
防止动态调试,主要是防止应用程序被其他进程附加并在程序运行时检测应用程序逻辑。类似开发环境调试,实际上Xcode中的也是通过attach功能,来实现调试的,debugserver是调试进程。
Unix
系统中,提供ptrace
的系统调用来实现调试以及对进程进行跟踪。但同时苹果也提供了PT_DENY_ATTACH
选项,用于阻止调试器依附到进程。
防动态调试一个主要的实现方式,就是禁止外部进程使用ptrace
调用附加到本进程。常见的方式如下:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16#import <dlfcn.h>
#import <sys/types.h>
#import <sys/ptrace.h>
typedef int (*ptrace_ptr_t)(int _request, pid_t _pid, caddr_t _addr, int _data);
#if undefined(PT_DENY_ATTACH)
#define PT_DENY_ATTACH 31
#endif
void AntiAppAttach() {
void *handle = dlopen(0, RTLD_GLOBAL | RTLD_NOW);
ptrace_ptr_t ptrace_ptr = dlsym(handle, "ptrace");
ptrace_ptr(PT_DENY_ATTACH, 0, 0, 0); //关闭动态库,并且卸载
dlclose(handle);
}
调试、反调试、反反调试就像猫鼠游戏。逆向开发也知道部分App会进行反调试,所以也可能会hook了ptrace
、sysctl
和syscall
这样的函数来避免被反调试。要hook系统函数,通常就是通过动态库注入。
防注入
动态库注入是另一种常见的动态调试以及入侵源程序的方式。比如Xcode提供的一些工具libMainthreadChecker
也正是Xcode注入的动态库。
越狱环境下,可以通过DYLD_INSERT_LIBRARIES
来插入动态库并执行。非越狱环境下,则可以可以通过修改Mach-O文件并重签名。
在iOS10之前,防注入可以在编译时,在 Other Linker Flags 配置上添加下面内容:1
-Wl,-sectcreate,__RESTRICT,__restrict,/dev/null
更高版本,则需要在App中检测是否被DYLD_INSERT_LIBRARIES
的方式注入了动态库了:1
char *env = getenv("DYLD_INSERT_LIBRARIES");
未被注入的情况下,env
是null
的。
其他的手段比如检测越狱、校验App签名(需要避免被hook的情况),结合帐号设备封禁等。
Comments