从MachO、DYLD到安全防护
1、MachO
其实MachO是一种文件格式,它包含了可执行文件、动态库、静态库、目标文件、dyld等。
对于我们编写的应用程序,在编译后的app右键显示包内容,就能看到与包名一样的可执行文件,可以通过file 可执行文件
指令查看该文件的具体信息。
上图可执行文件是arm64架构的,而有的应用可执行文件是胖二进制文件(也叫通用二进制文件格式的),会包含多种架构,比如arm64、armv7。我们也可以通过lipo
命令实现拆分架构(通过-thin
指令)和合并架构(通过-create
指令),一般在拆分和合并静态库的时候用到。
MachO结构
- Header 用于快速确认文件,比如cpu类型、文件类型
- LoadCommands 用于指示加载器如何加载二进制文件
- Data 用于存放数据,比如代码、字符串常量、类、方法等
2、DYLD
dyld,动态链接器,用来加载所有的库和可执行文件。
dyld源码是苹果开放出来的,感兴趣的小伙伴可以下载源码查看分析下。
dyld加载流程
- 程序执行从_dyld_start 开始
- 进入dyld:main函数(这里做了很多工作)
- 配置环境变量
- 加载共享缓存库(一开始就判断是否是禁用,iOS无法被禁用)
- 实例化主程序
- 加载动态库
- 最先加载插入的动态库,通过判断环境变量DYLD_INSERT_LIBRARIES是否为空。
- 然后加载系统的动态库
- 再加载weak动态库
- 初始化主程序
- 初始化方法(这里是最关键的地方)
- 经过一系列初始化,调用notifySingle函数,该函数会执行一个回调。通过断点调试可以发现,这个回调是_objc_init 初始化的时候赋值的一个函数load_images,在load_images里面会执行call_load_methods函数,而call_load_methods函数内部会循环调用各个类的load方法。
- doModInitFunctions函数。内部会调用带atrribute((constructor))的c++函数。
- 返回主程序的入口函数。开始进入主程序的main函数。
3、安全防护
(1)RESTRICT
通过对dyld源码的分析,可以找到一个安全防护的突破口,就是在加载动态库的第一步,通过判断环境变量DYLD_INSERT_LIBRARIES
是否为NULL,来决定是否加载插入的动态库,通常越狱插件都是在这个时候加载的。那我们只要想办法把环境变量DYLD_INSERT_LIBRARIES
移除掉,就可以解决这种类型的进攻。系统也确实给我们提供了一个入口,就是通过RESTRICT
段,具体操作如下:
打开自己要做防护的应用程序,选择Target -> Build Settings -> Other Linker Flags -> 添加-Wl,sectcreate,__RESTRICT,__restrict,/dev/null
(友情提示:Xcode11中编译会报错,旧版本Xcode可用,跑不通的小伙伴表打我😂)。添加完成后,编译一下,把可执行文件拖到MachOView里面看一下,就会发现神奇的多了一个__RESTRICT段,你再Tweak一下试试😏
(2)反调试Ptrace
ptrace(PT_DENY_ATTCH,0,0,0)拒绝进程被附加
- 该函数防护的特点:
- 程序被Xcode安装直接闪退
- 通过终端附加失败
- 用户正常启动能运行
- 破解ptrace
- 通过fishhook函数勾住ptrace
- 判断参数1,是否拒绝附加,如果是,就直接返回
(3)反调试sysctl
sysctl这个函数的作用是去检测程序的状态。
- sysctl(查询信息的数组(放字节码),数组的大小,结构信息的结构体指针,结构体大小,和2一样,和3一样)
- 在接受信息的结构体中,kp_proc属性 有一个标记 p_flag
- 查看第12位 是否为 1(1代表着有调试器附加)可通过P_TRACED查询
- 破解sysctl
- 通过fishhook函数勾住sysctl
- 调用原始的函数
- 取出结构一的标记判断
- 通过异或 P_TRACED 取反。修改标记,达到破解的效果。
(4)其他方法
- 提前执行:将检测函数放入Framwork中。
- 二进制修改:找到相关函数,直接修改汇编。
- 混淆类名、方法名:通过宏定义,写入PCH文件进行类名、方法名混淆。
- 字符串加密:所有的字符串常量拆分成字符,然后通过异或运算隐藏字符串常量。
- 防护fishhook:通过 dlopen()拿到模块句柄 、 通过 dlsym()获得函数地址。
- syscall:通过syscall 调用系统函数,使用fishhook和lldb符号断点都拿不到。
- syscall 对应的函数编号在 sys/syscall.h 文件里面
- 汇编软中断的方式触发系统函数
- mov w16,#0 中断根据x16里面的值跳转对应编号函数(和syscall编号一样)
- svc #0x80 这条指令是触发中断
- getenv 函数:该函数可以查询环境变量。我们可以通过查询DYLD_INSERT_LIBRARIES检测是否越狱状态。
防护的手段千千万,这里只是提供给小伙伴一个做防护的思路,毕竟进攻技术在发展,防护也一样😊