ptrace
debugserver
通过ptrace
函数调试app
ptrace
是系统函数,此函数提供一个进程去监听和控制另一个进程,并且可以检测被控制进程的内存和寄存器里面的数据。ptrace可以用来实现断点调试和系统调用跟踪。
ptrace防护
- 导入
MyPtraceHeader.h
到自己工程,其中代码如下
#ifndef _SYS_PTRACE_H_
#define _SYS_PTRACE_H_
#include
#include
enum {
ePtAttachDeprecated __deprecated_enum_msg("PT_ATTACH is deprecated. See PT_ATTACHEXC") = 10
};
#define PT_TRACE_ME 0 /* child declares it's being traced */
#define PT_READ_I 1 /* read word in child's I space */
#define PT_READ_D 2 /* read word in child's D space */
#define PT_READ_U 3 /* read word in child's user structure */
#define PT_WRITE_I 4 /* write word in child's I space */
#define PT_WRITE_D 5 /* write word in child's D space */
#define PT_WRITE_U 6 /* write word in child's user structure */
#define PT_CONTINUE 7 /* continue the child */
#define PT_KILL 8 /* kill the child process */
#define PT_STEP 9 /* single step the child */
#define PT_ATTACH ePtAttachDeprecated /* trace some running process */
#define PT_DETACH 11 /* stop tracing a process */
#define PT_SIGEXC 12 /* signals as exceptions for current_proc */
#define PT_THUPDATE 13 /* signal for thread# */
#define PT_ATTACHEXC 14 /* attach to running process with signal exception */
#define PT_FORCEQUOTA 30 /* Enforce quota for root */
#define PT_DENY_ATTACH 31
#define PT_FIRSTMACH 32 /* for machine-specific requests */
__BEGIN_DECLS
int ptrace(int _request, pid_t _pid, caddr_t _addr, int _data);
__END_DECLS
#endif /* !_SYS_PTRACE_H_ */
- 在合适的地方加入下面方法
//告诉系统,当前进程.拒绝被debugserver附加!
//arg1:ptrace要做到事情
//arg2:需要操作的进程
//arg3:附加的地址
//arg4:附加的数据
//arg4/arg3:取决于第一个参数!
ptrace(PT_DENY_ATTACH, 0, 0, 0);
//如果附加就闪退!!
破解ptrace防护
因为ptrace
是系统函数,所有它最终在间接符号表
中,我们可以通过符号断点ptrace
,来定位到当前的执行方法,通过fishhook
符号的重绑定来进行fishhook
#import "InjectCode.h"
#import "fishhook.h"
#import "MyPtraceHeader.h"
@implementation InjectCode
//定义函数指针!
int (*ptrace_p)(int _request, pid_t _pid, caddr_t _addr, int _data);
+(void)load
{
//交换
struct rebinding ptraceBd;
ptraceBd.name ="ptrace";
ptraceBd.replacement = my_ptrace;
ptraceBd.replaced = (void *)&ptrace_p;
struct rebinding bds[] = {ptraceBd};
rebind_symbols(bds, 1);
}
//自定义
int my_ptrace (int _request, pid_t _pid, caddr_t _addr, int _data){
if (_request != PT_DENY_ATTACH) {//如果不是拒绝附加,保持调用!
return ptrace_p(_request,_pid,_addr,_data);
}
return 0;
}
@end
sysctl
通过sysctl
函数检测是否被动态调试比ptrace
相对隐蔽点,可延展性强
-
stsctl
函数可以检测进程状态
sysctl防护代码
#import "ViewController.h"
#import
@interface ViewController ()
@end
@implementation ViewController
BOOL isDebugger(){
int name[4];//里面放字节码。查询的信息
name[0] = CTL_KERN;//内核查询
name[1] = KERN_PROC;//查询进程
name[2] = KERN_PROC_PID;//传递的参数是进程的ID(PID)
name[3] = getpid();//PID的值告诉它!
struct kinfo_proc info;//接收进程信息的结构体
size_t info_size = sizeof(info);
/**
1、查询信息数组
2、数组中数据类型的大小
3、接受信息结构体的指针
4、接受信息结构体的大小的指针
*/
int error = sysctl(name, sizeof(name)/sizeof(*name), &info, &info_size, 0, 0);
assert(error == 0);//0 就是没有错误,其他就是错误码!
return ((info.kp_proc.p_flag & P_TRACED) != 0);
}
static dispatch_source_t timer;
void debugCheck(){
timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, dispatch_get_global_queue(0, 0));
dispatch_source_set_timer(timer, DISPATCH_TIME_NOW, 1.0 * NSEC_PER_SEC, 0.0 * NSEC_PER_SEC);
dispatch_source_set_event_handler(timer, ^{
if (isDebugger()) {
NSLog(@"检测到了调试!");
}else{
NSLog(@"正常!!");
}
});
dispatch_resume(timer);
}
- (void)viewDidLoad {
[super viewDidLoad];
debugCheck();
}
@end
sysctl破解
同理通过fishhook
破解
#import "InjectCode.h"
#import
#import "fishhook.h"
@implementation InjectCode
+(void)load
{
rebind_symbols((struct rebinding[1]){{"sysctl",my_sysctl,(void *)&sysctl_p}}, 1);
}
//原始函数地址
int (*sysctl_p)(int *, u_int, void *, size_t *, void *, size_t);
//定义新的函数
int my_sysctl(int *name, u_int namelen, void *info, size_t *infosize, void *newinfo, size_t newinfosize){
if (namelen == 4
&& name[0] == CTL_KERN
&& name[1] == KERN_PROC
&& name[2] == KERN_PROC_PID
&& info) {
int err = sysctl_p(name,namelen,info,infosize,newinfo,newinfosize);
struct kinfo_proc * myinfo = (struct kinfo_proc *)info;
if (myinfo->kp_proc.p_flag & P_TRACED) {
//使用异或可以取反!
myinfo->kp_proc.p_flag ^= P_TRACED;
}
return err;
}
return sysctl_p(name,namelen,info,infosize,newinfo,newinfosize);
}
@end
提前执行防护代码
因为fishhook
的rebind_symbols
函数都在load
中执行,如果防护代码在load
之前就执行了,就可以达到防护的目的
我们可以将防护代码封装成一个动态库
,达到提前执行的目的,但是只能在第一次
的时候才能检测到防护
,fishhook
在后面会替换掉相应的防护函数
破解 --> 修改二进制文件
- 可以通过
MonkeyDev
,通过断点,判断有没有ptrace
方法的防护, - 然后通过
bt
打印函数调用栈来获得对应的动态库
和动态库虚拟内存地址
, - 通过
image list
找到对应动态库地址
, - 用
虚拟地址
减去动态库地址
得到偏移地址
- 使用
Hopper Disassembler
查看动态库
的二进制地址 - 定位到
偏移地址
,查看汇编,修改对应的汇编二进制 - 导出一个新的
可执行文件
- 替换
动态库
中的可执行文件