iOS安全防護 - 反調試

1. 防止 DYLD_INSERT_LIBRARIES 動態(tài)插入

* 可以防 dumpdecrypt 砸殼;
* 可以防越獄環(huán)境下tweak插件安裝.

DYLD_INSERT_LIBRARIES : 通過bundle id 獲取進程, 插入動態(tài)庫!

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 插入動態(tài)庫!


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

此函數(shù)提供了一個進程, 監(jiān)聽控制另外一個進程, 并且可以檢測被控制進程的內存的寄存器里面的數(shù)據! 它可以用來實現(xiàn)斷點調試和系統(tǒng)調用跟蹤.
debugserve r的實現(xiàn)就是基于 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     :數(shù)據, 默認寫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

// 定義函數(shù)指針. 保存原來函數(shù)地址
int (* ptrace_p) (int _request, pid_t _pid, caddr_t _addr, int _data);

// 定義新的函數(shù)
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";  // 函數(shù)符號
    ptraceBD.replacement = myPtrace; // 新函數(shù)地址
    ptraceBD.replaced = (void *)&ptrace_p; // 原始函數(shù)地址的指針
    
    // 創(chuàng)建數(shù)組
    struct rebinding rebinds[]={ptraceBD};
    // 重綁定
    rebind_symbols(rebinds, 1);
     
}

@end

2.3 防 ptrace 針對 fishhook 加固

防fishhook rebind 系統(tǒng)函數(shù)的殺手锏 -- 自定義動態(tài)庫.
通過動態(tài)庫注入方式注入的動態(tài)庫, 都在項目動態(tài)庫加載之后!

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

3. 防護 - 反調試 - sysctl

sysctl

// 檢測是否被調試
BOOL isDebugger () {
    // 控制碼
    int name[4]; // 里面放字節(jié)碼, 查詢信息
    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>


// 原始函數(shù)地址
int (*sysctl_p)(int *, u_int, void *, size_t *, void *, size_t);

// 自定義新函數(shù)
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) 加固

使用動態(tài)庫的方式, 來防護 !!!

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
  • 序言:七十年代末娇斑,一起剝皮案震驚了整個濱河市触徐,隨后出現(xiàn)的幾起案子芬探,更是在濱河造成了極大的恐慌,老刑警劉巖频轿,帶你破解...
    沈念sama閱讀 216,372評論 6 498
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異烁焙,居然都是意外死亡航邢,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,368評論 3 392
  • 文/潘曉璐 我一進店門骄蝇,熙熙樓的掌柜王于貴愁眉苦臉地迎上來膳殷,“玉大人,你說我怎么就攤上這事九火∽裕” “怎么了?”我有些...
    開封第一講書人閱讀 162,415評論 0 353
  • 文/不壞的土叔 我叫張陵岔激,是天一觀的道長勒极。 經常有香客問我,道長鹦倚,這世上最難降的妖魔是什么河质? 我笑而不...
    開封第一講書人閱讀 58,157評論 1 292
  • 正文 為了忘掉前任,我火速辦了婚禮震叙,結果婚禮上掀鹅,老公的妹妹穿的比我還像新娘。我一直安慰自己媒楼,他們只是感情好乐尊,可當我...
    茶點故事閱讀 67,171評論 6 388
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著划址,像睡著了一般扔嵌。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上夺颤,一...
    開封第一講書人閱讀 51,125評論 1 297
  • 那天痢缎,我揣著相機與錄音,去河邊找鬼世澜。 笑死独旷,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播嵌洼,決...
    沈念sama閱讀 40,028評論 3 417
  • 文/蒼蘭香墨 我猛地睜開眼案疲,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了麻养?” 一聲冷哼從身側響起褐啡,我...
    開封第一講書人閱讀 38,887評論 0 274
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎鳖昌,沒想到半個月后备畦,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經...
    沈念sama閱讀 45,310評論 1 310
  • 正文 獨居荒郊野嶺守林人離奇死亡许昨,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 37,533評論 2 332
  • 正文 我和宋清朗相戀三年萍恕,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片车要。...
    茶點故事閱讀 39,690評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡允粤,死狀恐怖,靈堂內的尸體忽然破棺而出翼岁,到底是詐尸還是另有隱情类垫,我是刑警寧澤,帶...
    沈念sama閱讀 35,411評論 5 343
  • 正文 年R本政府宣布琅坡,位于F島的核電站悉患,受9級特大地震影響,放射性物質發(fā)生泄漏榆俺。R本人自食惡果不足惜售躁,卻給世界環(huán)境...
    茶點故事閱讀 41,004評論 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望茴晋。 院中可真熱鬧陪捷,春花似錦、人聲如沸诺擅。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,659評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽烁涌。三九已至苍碟,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間撮执,已是汗流浹背微峰。 一陣腳步聲響...
    開封第一講書人閱讀 32,812評論 1 268
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留抒钱,地道東北人蜓肆。 一個月前我還...
    沈念sama閱讀 47,693評論 2 368
  • 正文 我出身青樓掂榔,卻偏偏與公主長得像,于是被迫代替她去往敵國和親症杏。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 44,577評論 2 353

推薦閱讀更多精彩內容