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中的排列順序。