iOS安全防护 - 反调试

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) 加固

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

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,319评论 5 459
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,801评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,567评论 0 319
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,156评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,019评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,090评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,500评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,192评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,474评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,566评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,338评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,212评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,572评论 3 298
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,890评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,169评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,478评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,661评论 2 335

推荐阅读更多精彩内容