iOS安全防护 - 反调试

作者: reboot_q | 来源:发表于2018-06-14 18:36 被阅读63次

    1. 防止 DYLD_INSERT_LIBRARIES 动态插入

    * 可以防 dumpdecrypt 砸壳;
    * 可以防越狱环境下tweak插件安装.
    

    DYLD_INSERT_LIBRARIES : 通过bundle id 获取进程, 插入动态库!

    1.1 dyld.cpp

    // pruneEnvironmentVariables
     if ( gLinkContext.processIsRestricted ) {
            pruneEnvironmentVariables(envp, &apple);
            // set again because envp and apple may have changed or moved
            setContext(mainExecutableMH, argc, argv, envp, apple);
        }
    
    // processIsRestricted
     if ( issetugid() || hasRestrictedSegment(mainExecutableMH) ) {
            gLinkContext.processIsRestricted = true;
        }
    
    // hasRestrictedSegment
    static bool hasRestrictedSegment(const macho_header* mh)
    {
        const uint32_t cmd_count = mh->ncmds;
        const struct load_command* const cmds = (struct load_command*)(((char*)mh)+sizeof(macho_header));
        const struct load_command* cmd = cmds;
        for (uint32_t i = 0; i < cmd_count; ++i) {
            switch (cmd->cmd) {
                case LC_SEGMENT_COMMAND:
                {
                    const struct macho_segment_command* seg = (struct macho_segment_command*)cmd;
                    
                    //dyld::log("seg name: %s\n", seg->segname);
                    if (strcmp(seg->segname, "__RESTRICT") == 0) {
                        const struct macho_section* const sectionsStart = (struct macho_section*)((char*)seg + sizeof(struct macho_segment_command));
                        const struct macho_section* const sectionsEnd = &sectionsStart[seg->nsects];
                        for (const struct macho_section* sect=sectionsStart; sect < sectionsEnd; ++sect) {
                            if (strcmp(sect->sectname, "__restrict") == 0) 
                                return true;
                        }
                    }
                }
                break;
            }
            cmd = (const struct load_command*)(((char*)cmd)+cmd->cmdsize);
        }
            
        return false;
    }
    
    // seg->segname, "__RESTRICT"
    // sect->sectname, "__restrict"
    
    

    1.2 开始防护

    在 MachO 中添加一个 restrict Section, 防止 tweak 插件通过 DYLD_INSERT_LIBRARIES 插入动态库!

    
    -Wl,-sectcreate,__RESTRICT,__restrict,/dev/null
    
    
    antiRestrict.png
    • 查看编译后的 MachO


      antiSection.png
    • 第一层攻破
      但是通过 synalyzeit 十六进制编辑器可以修改 restrict segment, 攻破第一层防护!

    • 第二层防护
      通过在代码中检测, restrict segment 是否被修改, 做二次防护.

    1.3 反修改MachO 中的 restrict 段

    #import <mach-o/loader.h>
    #import <mach-o/dyld.h>
    
    // ARM and x86_64 are the only architecture that use cpu-sub-types
    #define CPU_SUBTYPES_SUPPORTED  ((__arm__ || __arm64__ || __x86_64__) && !TARGET_IPHONE_SIMULATOR)
    
    #if __LP64__
    #define LC_SEGMENT_COMMAND        LC_SEGMENT_64
    #define LC_SEGMENT_COMMAND_WRONG LC_SEGMENT
    #define LC_ENCRYPT_COMMAND        LC_ENCRYPTION_INFO
    #define macho_segment_command    segment_command_64
    #define macho_section            section_64
    
    #define macho_header            mach_header_64
    #else
    #define macho_header            mach_header
    
    #define LC_SEGMENT_COMMAND        LC_SEGMENT
    #define LC_SEGMENT_COMMAND_WRONG LC_SEGMENT_64
    #define LC_ENCRYPT_COMMAND        LC_ENCRYPTION_INFO_64
    #define macho_segment_command    segment_command
    #define macho_section            section
    #endif
     
    @implementation XXX
    
    static bool hasRestrictedSegment(const struct macho_header* mh)
    {
        const uint32_t cmd_count = mh->ncmds;
        const struct load_command* const cmds = (struct load_command*)(((char*)mh)+sizeof(struct macho_header));
        const struct load_command* cmd = cmds;
        for (uint32_t i = 0; i < cmd_count; ++i) {
            switch (cmd->cmd) {
                case LC_SEGMENT_COMMAND:
                {
                    const struct macho_segment_command* seg = (struct macho_segment_command*)cmd;
                    printf("segmentname=%s\n", seg->segname);
                    //dyld::log("seg name: %s\n", seg->segname);
                    if (strcmp(seg->segname, "__RESTRICT") == 0) {
                        const struct macho_section* const sectionsStart = (struct macho_section*)((char*)seg + sizeof(struct macho_segment_command));
                        const struct macho_section* const sectionsEnd = &sectionsStart[seg->nsects];
                        for (const struct macho_section* sect=sectionsStart; sect < sectionsEnd; ++sect) {
                            if (strcmp(sect->sectname, "__restrict") == 0)
                                return true;
                        }
                    }
                }
                    break;
            }
            cmd = (const struct load_command*)(((char*)cmd)+cmd->cmdsize);
        }
        
        return false;
    }
    
    
    + (void)load {
        // 获取 MachO 的 imageHeader
        // dyld 启动 App 的时候, 最先加载的是自己的 MachO, index = 0(也可以 LLDB: image list 来查看)
        struct mach_header *mach_header = _dyld_get_image_header(0);
        NSLog(@"%d", mach_header->filetype);
        
        if(hasRestrictedSegment(mach_header)) {
            NSLog(@"防护成功");
        }else{
            
            NSLog(@"防护被攻破, rescrict 二进制被改");
        }
    }
    
    

    2. 防 LLDB 调试

    lldb 之所以能够调试手机上的 app, 原因在于 xcode 连接手机后, 在手机上安装了 debugsever!
    在xcode 中路径

    /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/DeviceSupport/10.0/DeveloperDiskImage.dmg/usr/bin/debugserver
    

    在iphone 中路径

    /Developer/usr/bin/debugserver
    
    • debugserver 使用
    $ /Developer/usr/bin/debugserver *:12346 -a 3705(pid)
    
    • lldb 启用
    $  lldb
    $ process connect connect://localhost:12346   // USB链接
    $ exit                                        //退出 lldb
    
    • 配置 debugserver 权限

      导出权限文件 - 如果调试报错, 需要添加以下权限

    $ ldid -e debugserver > debugserver.entitlements
    
    //  添加字段
    get-task-allow       YES
    task-for-pid-allow  YES
    
    // 用新的描述文件重签debugserver
    $ ldid -S debugserver.entitlements debugserver
    
    // 将新的 debugserver Copy 到手机 /usr/bin 目录下
    $ scp -P 12345 xxx/debugserver root@localhost:/usr/bin/
    

    2.1 ptrace (process trace) 进程跟踪 - 专防 dubugserver(xcode, lldb)

    此函数提供了一个进程, 监听控制另外一个进程, 并且可以检测被控制进程的内存的寄存器里面的数据! 它可以用来实现断点调试和系统调用跟踪.
    debugserve r的实现就是基于 ptrace (OSX).

    
    #ifndef    _SYS_PTRACE_H_
    #define    _SYS_PTRACE_H_
    
    #include <sys/appleapiopts.h>
    #include <sys/cdefs.h>
    
    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_ */
    

    只需要在防护的地方, 调用一下方法, 就能防止 xcode 调试, lldb, 调试, debugserver 调试!

     /*
         int _request: ptrace 要做的事
         pid_t _pid  : 进程 id
         caddr_t _addr:地址 默认写0
         int _data     :数据, 默认写0
         
         */
        ptrace(PT_DENY_ATTACH, 0, 0, 0);
    
    ptrace.png

    2.2 攻破 ptrace - fishhook

    #import "InjectCode.h"
    
    #import "fishhook.h"
    #import "PtraceHeader.h"
    
    @implementation InjectCode
    
    // 定义函数指针. 保存原来函数地址
    int (* ptrace_p) (int _request, pid_t _pid, caddr_t _addr, int _data);
    
    // 定义新的函数
    int myPtrace (int _request, pid_t _pid, caddr_t _addr, int _data){
        
        if(_request != PT_DENY_ATTACH){
            return myPtrace(_request, _pid, _addr, _data);
        }
        // 如果拒绝加载, 破坏此防护
        return 0;
    }
    
    
    
    + (void)load {
        // rebinding
        struct rebinding ptraceBD; //
        ptraceBD.name = "ptrace";  // 函数符号
        ptraceBD.replacement = myPtrace; // 新函数地址
        ptraceBD.replaced = (void *)&ptrace_p; // 原始函数地址的指针
        
        // 创建数组
        struct rebinding rebinds[]={ptraceBD};
        // 重绑定
        rebind_symbols(rebinds, 1);
         
    }
    
    @end
    

    2.3 防 ptrace 针对 fishhook 加固

    防fishhook rebind 系统函数的杀手锏 -- 自定义动态库.
    通过动态库注入方式注入的动态库, 都在项目动态库加载之后!

    #import "AntiFishhookCode.h"
    #import "PtraceHeader.h"
    @implementation AntiFishhookCode
    + (void)load {
        /*
         int _request: ptrace 要做的事
         pid_t _pid  : 进程 id
         caddr_t _addr:地址 默认写0
         int _data     :数据, 默认写0
         
         */
        ptrace(PT_DENY_ATTACH, 0, 0, 0);
        NSLog(@"我就是搞你 --- fishhook!!!");
    }
    @end
    
    

    3. 防护 - 反调试 - sysctl

    sysctl

    // 检测是否被调试
    BOOL isDebugger () {
        // 控制码
        int name[4]; // 里面放字节码, 查询信息
        name[0] = CTL_KERN; // 内核查看
        name[1] = KERN_PROC; // 查询进程
        name[2] = KERN_PROC_PID; // pid
        name[3] = getpid();      // 查询 pid
        
        /** int    sysctl(
         int *,
         u_int,  // size
         void *, // 返回结果(kinfo_proc)
         size_t *,// 结构体大小
         void *,  //
         size_t
         ); */
        
        // 接收进程查询结果的结构体
        struct kinfo_proc info;
        // 结构体的大小
        size_t info_size = sizeof(info);
        
        int error = sysctl(name, sizeof(name)/sizeof(*name), &info, &info_size, 0, 0);
        // 0就是没有错误, 其他就是错误码
        assert(error == 0);
        
        // 判断二进制某一位 是1 还是0, 按位与 00001000000
        // != 0, 就是正在被调试
        if((info.kp_proc.p_flag & P_TRACED) != 0) {
            return YES;
        }
        
        return NO;
    }
    
    if (isDebugger()) {
                NSLog(@"被调试了-------");
            }else{
                NSLog(@"安全运行");
            }
    
    

    3.1 fishHook 攻破 sysctl

    #import "fishhook.h"
    #import <sys/sysctl.h>
    
    
    // 原始函数地址
    int (*sysctl_p)(int *, u_int, void *, size_t *, void *, size_t);
    
    // 自定义新函数
    int mySysctl(int *name, u_int nameSize, void *info, size_t *infoSize, void *newInfo, size_t newInfoSize) {
        
        if (nameSize == 4
            && name[1] == CTL_KERN
            && name[2] == KERN_PROC
            && name[3] == KERN_PROC_PID
            && info
            && (int) *infoSize == sizeof(struct kinfo_proc)
            ) {
            int err = sysctl_p(name, nameSize, info, infoSize, newInfo, newInfoSize);
            
            // 判断info
            struct kinfo_proc *myInfo = (struct kinfo_proc *)info;
            if ((myInfo->kp_proc.p_flag & P_TRACED) != 0) {
                // 如果设置了防调试, 在此破解
                myInfo->kp_proc.p_flag ^= P_TRACED;
            }
            
            return err;
        }
        
        return sysctl_p(name, nameSize, info, infoSize, newInfo, newInfoSize);
    }
    
    
    @implementation InjectCode
    + (void)load {
       
        struct rebinding rebind;
        rebind.name = "sysctl";
        rebind.replacement = mySysctl;
        rebind.replaced = (void *)&sysctl_p;
        
        rebind_symbols((struct rebinding []){rebind} , 1);
    }
    @end
    

    3.2 防护 - 反调试 (sysctl) 加固

    使用动态库的方式, 来防护 !!!

    相关文章

      网友评论

        本文标题:iOS安全防护 - 反调试

        本文链接:https://www.haomeiwen.com/subject/aehleftx.html