iOS 逆向 -- 动态调试防护

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

提前执行防护代码

因为fishhookrebind_symbols函数都在load中执行,如果防护代码在load之前就执行了,就可以达到防护的目的

我们可以将防护代码封装成一个动态库,达到提前执行的目的,但是只能在第一次的时候才能检测到防护fishhook在后面会替换掉相应的防护函数

破解 --> 修改二进制文件

  • 可以通过MonkeyDev,通过断点,判断有没有ptrace方法的防护,
  • 然后通过bt打印函数调用栈来获得对应的动态库动态库虚拟内存地址
  • 通过image list找到对应动态库地址
  • 虚拟地址减去动态库地址得到偏移地址
  • 使用Hopper Disassembler查看动态库的二进制地址
  • 定位到偏移地址,查看汇编,修改对应的汇编二进制
  • 导出一个新的可执行文件
  • 替换动态库中的可执行文件

你可能感兴趣的:(iOS 逆向 -- 动态调试防护)