众所周知,安全攻防,是一个互相博弈的过程,知彼知己才能百战不殆,想要做好防御,自然也要知道敌人是怎么进攻的.那么我们在尝试逆向
(以学习为目的)
他人的App
的时候,除了先进行必要的砸壳
,重签名
等步骤后,首先要进行的工作,肯定是通过动态调试
的方式,尝试找到切入点
.既然是调试,自然会想到LLDB
,我们在做普通开发的时候,可以通过Xcode断点
的方式,调试自己的App
,那我们怎么才能调试别人的App
呢?这就需要了解到LLDB
的原理了.
LLDB
与 debugserver
Xcode
能够通过LLDB
调试App
,是因为Xcode
和iPhone
上都有debugserver
环境Xcode
内的debugserver
在DeviceSupport
文件夹内,具体路径是/Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/DeviceSupport/
,在任意版本路径下的DeveloperDiskImage.dmg
文件内的/usr/bin
下面,如下图:iPhone
内的debugserver
需要在越狱环境
的手机才能看到,而且需要通过Xcode
运行并安装到手机上以后才有,具体路径是/Developer/usr/bin
,如图:- 在
越狱环境
下,我们可以用终端
通过LLDB
连接到手机上的debugserver
,对越狱手机上的App
进行调试 - 通过
Xcode
附加进程可以更加方便的调试,甚至能够进行UI调试
.
debugserver
能够调试App
,是通过一个系统函数ptrace
ptrace
即process trace
,进程跟踪.debugserver
想要附加到其他进程,必须通过ptrace
函数来获取系统的允许.ptrace
是一个系统函数,此函数能够通过一个进程去监听并控制另一个进程(即被调试的App
)ptrace
甚至可以读取并修改被控制进程内存
和寄存器
里的数据- 通过修改
pc寄存器
的值,能够实现断点调试
利用ptrace
防护debugserver
动态调试
- 我们知道了
动态调试
的原理是debugserver
,而debugserver
又使用了ptrace
函数,那么显而易见的就要通过ptrace
函数下手,进行防护. ptrace
函数在mac
平台可以直接通过引用头文件#import <sys/ptrace.h>
后进行使用,但是在iOS
环境中并无法直接引用这个头文件,但实际上ptrace
在iOS
环境中也是有实现的,所以我们直接自己声明或者创建一个xxx.h
头文件,然后将mac
端的sys/ptrace.h
头文件的内容直接copy
过来,即可调用ptrace
函数,头文件的实现如下:ptrace
函数说明- 函数声明:
int ptrace(int _request, pid_t _pid, caddr_t _addr, int _data);
_request
:需要ptrace做的事情,值可以查看头文件内的宏定义#define PT_DENY_ATTACH 31
表示拒绝附加
_pid
:要操作的进程id
,0
代表当前进程_addr & _data
:地址和数据,根据参数_request
决定是否需要传递,不需要默认传0
ptrace
函数的使用- 在
load
中调用- 此时用
Xcode
附加调试会直接断开连接
- 此时用
- 在
constructor
中调用- 此时用
Xcode
附加调试会直接断开连接
- 此时用
- 在
main
函数中调用- 此时用
Xcode
附加调试会直接断开连接
- 此时用
- 在
didFinishLaunchingWithOptions
中调用- 此时用
Xcode
附加调试会直接闪退
- 此时用
总结
- 由于
load
和constructor
会在main
函数之前执行,所以经过以上验证可知,ptrace
在main
函数之前调用会停止进程附加
,在main
函数之后调用会使App
直接闪退
. - 调用
ptrace
只会影响debugserver LLDB
调试,不会影响App
的正常使用.
- 由于
- 在
利用fishhook
干掉ptrace
防护
fishhook
是Facebook
提供的一个动态修改链接machO
文件的工具。利用machO
文件加载原理,通过修改懒加载表(Lazy Symbol Pointers
)和非懒加载表(Non-Lazy Symbol Pointers
)这两个表的指针达到hook C函数
的目的。- 既然调用
ptrace
函数能够防止动态调试
,那我们只要将ptrace
函数hook
掉,就可以废掉ptrace
防护 - 通过
framework
对目标App
进行代码注入
,实现ptrace hook
: - 增加的
framework
会被dyld
一并加载,在load
方法中通过fishhook
替换掉ptrace
函数,即可使ptrace
失效.
利用sysctl
进行防护
fishhook
竟然如此轻松的就将ptrace
防护废掉了,那有没有什么方式能够躲过fishhook
呢?答案就是sysctl
.
sysctl
是在usr/include > sys > sysctl.h
中的一个函数,能够根据传递的参数去访问部分进程信息,通过sysctl
函数能够获取到当前是否正在调用ptrace
,也就能够得知是否有人正在debug
我们的程序,sysctl
的函数声明和参数释义如下:
int sysctl(int *, u_int, void *, size_t *, void *, size_t);
int *
查询信息的数组u_int
数组中数据类型的大小void *
接收信息的结构体的指针size_t *
接收信息的结构体的大小void *
默认传0,依据前面的参数决定传什么size_t *
默认传0,依据前面的参数决定传什么
sysctl
用法示例:- 通过上图的
isDebugger()
函数,就能够获知当前进程是否正在被调试,因为在调用sysctl
函数后,我们就能通过结构体指针info
接收到我们要查询的信息,下面我们就看一下kinfo_proc
结构体,并探究一下如何通过p_flag & P_TRACED
来得知当前进程是否被调试:- 首先是
kinfo_proc
结构体: - 其中
extern_proc
结构体kp_proc
是我们需要关注的: - 在
kp_proc
结构体中,我们看到有一个p_flag
,我们来看一下它都可以设置哪些值,就能清楚的得知它能够做哪些事了: - 通过注释我们可以知道,
extern_proc.p_flag
可以配置如图中的许多宏定义的值,这些值是按位运算
来设计的,其中我们要找的,正是#define P_TRACED
,通过注释可以得知,当p_flag
值包含P_TRACED
时,我们的进程就正在被Debug
!
- 首先是
- 所以,与运算
info.kp_proc.p_flag & P_TRACED
即能检测ptrace()
函数的调用情况,既然能检测到动态调试,我们就可以随意设计相应的防护逻辑了! - 补充:检测调试的逻辑可以通过定时器的方式来间断性检测,更安全,而且
GCD
的代码块能够让函数调用栈更模糊,当对方想要查找我们调用sysctl
函数的位置时,将更加的困难,如下:
干掉sysctl
没有绝对安全的防护,sysctl
亦然,有经验的逆向人员可以通过符号断点
获知当前进程时候有调用sysctl
函数,然后简简单单,注入代码,hook
掉sysctl
,篡改p_flag
的值,让检测的逻辑失效,如下:
先发制人,克制fishhook
- 我们发现,无论是
ptrace
也好,sysctl
也罢,不管是直接干掉被debug
的进程,还是对debug
行为进行检测并处理,都能够被有经验的逆向人员通过fishhook
的方式简简单单就干掉,既然fishhook
这么牛,我们何不站在逆向角度
来思考,我们是怎么被干掉的?答案就是先发制人
. fishhook
能够hook
到我们的函数,是通过framework
代码注入的方式,而framwork
在dyld
加载过程中被优先加载并执行了load
方法,我们的防护函数在执行之前就被fishhook
篡改了!- 反制措施也就不言而喻了,我们将
ptrace
或者sysctl
的防护代码,转移到我们自己的framework
中,优先加载并执行,即使逆向人员能够通过fishhook
或者monkey工程(其原理也是fishhook)
搞事情,但我们早在对方代码注入之前
,就已经得知有人的debug
我们的进程了.
攻防永无止境
是不是我们将防护代码(ptrace
和sysctl
)转移到framework
中就一定能安全了呢?答案是否定的,攻防永远没有尽头,我们的程序代码
和framework
最终会被build
为mach-O
文件,而在逆向领域,有很多能够对二进制文件进行分析和修改的工具,下面就大致介绍一下逆向的思路:
- 通过符号断点发现当前进程正在通过
ptrace
或sysctl
尝试进行防护 bt
指令查看当前函数调用栈,找到当前函数的动态库
名称和地址image list
指令拿到首地址,两两相减拿到偏移值
- 通过
Hooper
或IDA
等工具分析framework
的mach-O
文件,通过偏移值
找到当前的汇编
调用指令 - 现在在你眼前能的,就是类似于
bl ptrace
这种特别明显的汇编跳转函数指令! - 将指令修改为
nop
,即空指令,不做任何操作,直接废掉ptrace
和sysctl
!
总结:隐藏很重要,发现即胜利
经过几番博弈,我们发现攻防就像两名狙击手,谁先发现对方的位置,就能想办法先发制人,或者绕过去!所以隐藏也是极其重要的防护手段,藏得好,就能让逆向人员耗费更多的精力,大大提高逆向的难度,我们的程序自然也就更加安全.
而关于如何隐藏自己,我们后续再另起一篇进行探究,攻防无止境,大家共勉!