1. 程式人生 > >iOS防護----反除錯

iOS防護----反除錯

在逆向和保護的過程中,總會涉及到反除錯和反反除錯的問題,這篇文章主要是總結一下幾種常見的反除錯手段。
首先感謝一下猴子大佬的無私奉獻AloneMonkey
本文主要參考了Haris Andrianakis的文章
馬萬旻在掘金上發表的文章
KANGZUBIN的部落格

當我們上線一個 App,當然是不希望自己的 App 被攻擊者研究透徹。雖然說沒有絕對的安全,但是我們可以做一些防護措施,增加攻擊的成本和難度,延緩攻擊者的腳步。

在 iPhone 上執行 App,然後通過 GDB 進行動態除錯,是大多數攻擊者的首選。
本文主要介紹兩種反除錯的方法。

ptrace反除錯,阻止GDB依附

在Unix 系統中,提供了一個系統呼叫 ptrace 用於實現斷點除錯和對程序進行跟蹤和控制,而 PT_DENY_ATTACH 是蘋果增加的一個 ptrace 選項,這個引數用來告訴系統,阻止偵錯程式依附,用法如下:

//ptrace反除錯
#import <dlfcn.h>
#import <sys/types.h>
typedef int (*ptrace_ptr_t)(int _request, pid_t pid, caddr_t _addr, int _data);
#if !defined(PT_DENT_ATTACH)
#define PT_DENT_ATTACH 31
#endif

void disable_gdb() {
    void * handle = dlopen(0, RTLD_GLOBAL|RTLD_NOW);
    ptrace_ptr_t ptrace_ptr = dlsym(handle, "ptrace");
    ptrace_ptr(PT_DENT_ATTACH, 0, 0, 0);
    dlclose(handle);
}

sysctl反除錯

當一個程序被除錯的時候,該程序會有一個標記來標記自己正在被除錯,所以可以通過sysctl去檢視當前程序的資訊,看有沒有這個標記位即可檢查當前除錯狀態。

//sysctl反除錯
#import <sys/sysctl.h>
#import <sys/types.h>
#import <unistd.h>
/*
 函式的返回值若為0時,證明沒有錯誤,其他數字為錯誤碼。
 arg1 傳入一個數組,該陣列中的第一個元素指定本請求定向到核心的哪個子系統。第二個及其後元素依次細化指定該系統的某個部分。
 arg2 陣列中的元素數目
 arg3 一個結構體,指向一個供核心存放該值的緩衝區,存放程序查詢結果
 arg4 緩衝區的大小
 arg5/arg6 為了設定某個新值,arg5引數指向一個大小為arg6引數值的緩衝區。如果不準備指定一個新值,那麼arg5應為一個空指標,arg6因為0.
 */
//int sysctl(int *, u_int, void *, size_t *, void *, size_t);

static bool is_debugger_present(void) {
    int name[4];//存放位元組碼,查詢資訊
    struct kinfo_proc info;//接受程序查詢結果資訊的結構體
    size_t info_size = sizeof(info);//結構體的大小
    
    info.kp_proc.p_flag = 0;
    name[0] = CTL_KERN;//核心檢視
    name[1] = KERN_PROC;//程序檢視
    name[2] = KERN_PROC_PID;//程序ID
    name[3] = getpid();//獲取pid,據說這個可以直接傳0?
    
    int proc_err = sysctl(name, 4, &info, &info_size, NULL, 0);
    if (proc_err == -1) { //判斷是否出現了異常
        exit(-1);
    }
    //info.kp_proc.p_flag中存放的是標誌位(二進位制),在proc.h檔案中有p_flag的巨集定義,通過&運算可知對應標誌位的值是否為0。(若結果值為0則對應標誌位為0)。其中P_TRACED為正在跟蹤除錯過程。
    return ((info.kp_proc.p_flag & P_TRACED) != 0);
}

syscall

直接呼叫這個函式:
其中PT_DENT_ATTACH的值為31,直接填31即可。SYS_ptrace的值為26,可以引入<sys/syscall.h>標頭檔案後直接呼叫巨集定義

#import <sys/syscall.h>
syscall(SYS_ptrace,PT_DENT_ATTACH,0,0,0);

為從實現從使用者態切換到核心態,系統提供了一個系統呼叫函式syscall,上面講到的ptrace也是通過系統呼叫去實現的。而ptrace的編號為26,也就是SYS_ptrace的值:

26. ptrace               801e812c T

其他函式編號可以在這裡Kernel Syscalls查閱,維基百科,需要翻牆。。
但是syscall在iOS10之後廢棄了。代替它的函式在<sys/kdebug_signpost.h>裡,叫kdebug_signpost(),但是搜遍全網也沒找到如何去替代syscall(26,31,0,0,0)。。這裡是原始碼,希望有大牛可以研究一下。

SIGSTOP(當檢測到有斷點觸發時停止除錯)

通過捕獲系統SIGSTOP訊號來判斷。
如果程式沒有斷點。那這個是沒有用的。。

    dispatch_source_t source = dispatch_source_create(DISPATCH_SOURCE_TYPE_SIGNAL, SIGSTOP, 0, dispatch_get_main_queue());
    dispatch_source_set_event_handler(source, ^{
        exit(0);
    });
    dispatch_resume(source);

isatty

isatty方法也可以用來檢測是否正在被除錯

#import <unistd.h>
if (isatty(1)) {
   exit(0);   
}

但是,上述這些方式只能簡單地防止 App 被動態除錯,其實 ptrace、sysctl、syscall 等函式本身也可以被靜態修改或 Hook。而且即便能有效阻止了除錯,App 仍然可以通過 tweak 去 Hook App 內部的方法實現,也可以通過 dylib 注入去修改 App 的功能。

我們只好從多方面考慮,儘可能提高安全性,比如防止 tweak 依附、防止網路請求抓包、對敏感資料進行加解密、程式碼混淆、檢查二進位制 binary 簽名是否匹配;關鍵邏輯用更底層的 C 函式實現(雖然 C 函式也是可以被 Hook,例如 Facebook 開源的 fishhook),等等,同時我們也可以檢查手機是否已經越獄,並對越獄機做特殊處理。

下面介紹幾個比較特殊的寫法,其實原理都一樣,呼叫專門的函式去檢測,只是呼叫方法不同:

內聯 svc + ptrace 實現和內聯 svc + syscall + ptrace 實現

其實這兩種方法都等同於直接或間接使用 ptrace, 此時系統呼叫號是 SYS_ptrace

static __attribute__((always_inline)) void AntiDebugASM() {
#ifdef __arm__
    asm volatile(
                 "mov r0,#31\n"
                 "mov r1,#0\n"
                 "mov r2,#0\n"
                 "mov r12,#26\n"
                 "svc #80\n"
                 );
#endif
#ifdef __arm64__
    asm volatile(
                 "mov x0,#26\n"
                 "mov x1,#31\n"
                 "mov x2,#0\n"
                 "mov x3,#0\n"
                 "mov x16,#0\n"
                 "svc #128\n"
                 );
#endif
}

安全性比較高的防護策略

對於fishhook交換系統函式的進攻方式,我們可以通過將sysctl等函式呼叫放到動態庫中,以保證檢測函式可以在進攻注入的程式碼之前執行。動態庫的載入順序為Build Phases下的Link Binary With Libaraies中的排列順序。