动态调试防护 (30) (上)

ptrace (process trace 进程跟踪)

提供一个进程监察和控制另一个进程, 并且可以读取和改变被控制进程的内存和寄存器里面的数据.它就可以用来实现断点调试(修改pc寄存器里的值)和系统调用跟踪.

  1. ptrace -> iOS系统没有, 但是macOS里有
    1. 想看具体的参数, 可以创建一个macOS工程
    2. #import -> 就可以查看ptrace参数
  2. ptrace函数说明 -> ptarce(PT_DENY_ATTACH,0,0,0); -> 拒绝附加进程操作(枚举31)
    1. arg1: 需要ptrace做的事情
    2. arg2: 需要操作的进程ID
    3. arg3(地址)/4(数据): 取决于第一个参数
  3. 调用了就不能进行debug调试了, 但是用户可以点击正常运行

查看ptrace符号

  1. 拿到MachO文件
  2. 查看懒加载和非懒加载符号表
  3. 发现ptrace在懒加载符号表里很显眼

怎么破解ptrace

  1. 导入自己定义的ptrace头文件
  2. InjectCode注入, 用fishhook干掉ptrace
  3. 运行如果报错 -> 注意插件的运行版本要小于手机的版本, 测试的时候可以都调为iOS11

相应的hook代码

//ptrace声明文件
#import "MyPtraceHeader.h"
#import "fishhook.h"

//定义一个函数指针!!
int (*ptrace_p)(int _request, pid_t _pid, caddr_t _addr, int _data);

//自定义的函数
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;
}

+(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);
}

sysctl

  1. 导入头文件 #import
  2. sysctl参数说明 -> sysctl(<#int *#>, <#u_int#>, <#void *#>, <#size_t *#>, <#void *#>, <#size_t#>)
    1. 查询信息数组
    2. 数组中数据类型的大小
    3. 接收信息结构体的指针
    4. 接收信息结构体的大小

判断调试模式的代码

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);
}

& 解析

最后的判断条件 -> info.kp_proc.p_flag & P_TRACED) != 0

  1. 因为p_flag是状态标识
    1. 取值如图
      image.png
    2. 那我们拿的时候的计算方式为 ->
      image.png
  2. 可以写一个定时器来定时调用来检测是否被调试
  3. 如果检测到了
    1. exit(0);
    2. 或者上报服务器
    3. 或者请求控制
  4. 用sysctl这种检测手段, 那么可延展性很好.

用fishhook破解sysctl

#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

  1. 只要比你的rebind_symbols提前执行就OK了
  2. 将防护代码封装到一个framework里面, 这样这个framework里面的代码永远会比你注入的代码提前执行.
  3. 我们经常使用的MonKeyDev是带有自动破解ptrace和sysctl的, 但是这种防护手段也是有效的
  4. 所以只要做了提前执行 -> 你所有的注入手段,都失效了

破解提前注入

修改二进制

  1. 正常情况, 我想运行别人的包, 但是一运行就闪退, 首先打符号断点, 看看是否有ptrace
    1. 如果能断到, 就是有
      image.png
    2. 这时候可以bt 来打印堆栈, 能查看到包含ptrace代码的动态库名称
      image.png
  2. 这时候需要找到两个关键值
    1. 所在的动态库
    2. 地址上图的72bb44
  3. image list -> 搜动态库找到动态库的地址 -> 两个地址相减, 就找到偏移了
    1. 在所在的动态库找到偏移地址
  4. 找到要破解的可执行文件里面找到对应的framework里的macho -> 用hopper打开
    1. 搜索上面得到的地址 -> 注意pc寄存器保存的是下一个指令的地址, 所以这里要找上一条指令
      image.png
    2. [shift + alt + a]修改上面对应调用ptrace的地方, 改为nop
      image.png
    3. nop相当于一个空指令
  5. 修改完后File -> Produce New Executable
  6. 生成的动态库的macho去替换对应的文件
  7. 再用MonkeyDev运行, 发现完美破解防护
    1. 其实MonkeyDev是带有反防护sysctl的代码的, 需要手动打开
    2. image.png
    3. 所以这种二进制的修改破解防护, 只要你找到就搞定了 -> 暴力破解

破解sysctl

  1. 下符号断点
    image.png
  2. 发现block_invoke -> 系统调用的 -> 因为之前我们写防护是在dispath中
  3. 找不到任何的调用代码的痕迹 -> 这里说明用GCD防护很棒
  4. 这时候用hopper打开工程的macho全局搜索sysctl, 发现找不到
    1. 这时候就怀疑的动态库, 所以要用hopper打开我们怀疑的动态库的macho来遍历搜索sysctl
    2. 记住这里我们只是测试, 但是真实的环境中, 这个查找应该很慢
  5. 运行后崩溃, 是因为这里nop修改sysctl后, assert报错(这给我们什么启示)
    image.png

防护总结

  1. 提前执行, 写在自己的私有动态库里
  2. 写在GCD里面
  3. 攻防博弈 -> 找到就赢

你可能感兴趣的:(动态调试防护 (30) (上))