ios 逆向 -- 反調(diào)試 和 反反調(diào)試

一: ptrace 作用

ptrace系統(tǒng)調(diào)從名字上看是用于進(jìn)程跟蹤的,它提供了父進(jìn)程可以觀察和控制其子進(jìn)程執(zhí)行的能力,并允許父進(jìn)程檢查和替換子進(jìn)程的內(nèi)核鏡像(包 括寄存器)的值。其基本原理是: 當(dāng)使用了ptrace跟蹤后,所有發(fā)送給被跟蹤的子進(jìn)程的信號(hào)(除了SIGKILL),都會(huì)被轉(zhuǎn)發(fā)給父進(jìn)程,而子進(jìn)程則會(huì)被阻塞,這時(shí)子進(jìn)程的狀態(tài)就會(huì)被 系統(tǒng)標(biāo)注為TASK_TRACED。而父進(jìn)程收到信號(hào)后,就可以對(duì)停止下來的子進(jìn)程進(jìn)行檢查和修改,然后讓子進(jìn)程繼續(xù)運(yùn)行 ,因而可以實(shí)現(xiàn)斷點(diǎn)調(diào)試和系統(tǒng)調(diào)用的跟蹤

使用ptrace,你可以在用戶層 【攔截和修改】系統(tǒng)調(diào)用(sys call)

注意:

被跟蹤的程序在進(jìn)入或者退出某次系統(tǒng)調(diào)用的時(shí)候都會(huì)觸發(fā)一個(gè)SIGTRAP信號(hào),而被父進(jìn)程捕獲

Ptrace其原型為:

include <sys/ptrace.h>

long ptrace(enum __ptrace_request request, pid_t pid, void *addr, void *data);

ptrace有四個(gè)參數(shù):

1). enum __ptrace_request request:指示了ptrace要執(zhí)行的命令。

2). pid_t pid: 指示ptrace要跟蹤的進(jìn)程。

3). void *addr: 指示要監(jiān)控的內(nèi)存地址。

4). void *data: 存放讀取出的或者要寫入的數(shù)據(jù)。

ptrace是如此的強(qiáng)大,以至于有很多大家所常用的工具都基于ptrace來實(shí)現(xiàn),如strace和gdb

反調(diào)試舉例說明:

比如: ptrace的命令PT_DENY_ATTACH 是蘋果增加的一個(gè) ptrace 選項(xiàng),用于阻止 GDB 等調(diào)試器依附到某進(jìn)程,用法如下:

ptrace(PT_DENY_ATTACH, 0, 0, 0);

void anti_gdb_debug() {

    void *handle = dlopen(0, RTLD_GLOBAL | RTLD_NOW);

    ptrace_ptr_t ptrace_ptr = dlsym(handle, "ptrace");

    ptrace_ptr(PT_DENY_ATTACH, 0, 0, 0);

    dlclose(handle);

}

總結(jié)一下:ptrace被廣泛用于反調(diào)試,因?yàn)橐粋€(gè)進(jìn)程只能被ptrace一次,如果事先調(diào)用了ptrace方法,那就可以防止別人調(diào)試我們的程序.

也就是說誰先調(diào)用ptrace 誰說了算,如果我們直接在app 中寫ptrace , 那么調(diào)試的時(shí)候肯定就無法調(diào)試,因?yàn)楸粦?yīng)用內(nèi)的ptrace 搶占了

反反調(diào)試: 如果別人的的app進(jìn)行了ptrace防護(hù),那么你怎么讓他的ptrace不起作用,進(jìn)而調(diào)試其他的app。由于ptrace是系統(tǒng)函數(shù),那么我們可以用fishhook來hook住ptrace函數(shù),然后讓他的app調(diào)用我們自己的ptrace函數(shù),即寫動(dòng)態(tài)庫,如果多個(gè)動(dòng)態(tài)庫hook 了ptrace ,我們可以調(diào)整 Link Binary Libraries的順序加載,假設(shè)人家應(yīng)用自己寫的hook ptrace動(dòng)態(tài)庫肯定會(huì)在自己前面,最后的方式我們可以通過修改macho的二進(jìn)制讓他的ptrace失效【不去執(zhí)行ptrace】,然后進(jìn)行調(diào)試.
最后:在給一種反調(diào)試的方案,這種也只能無法斷點(diǎn)調(diào)試ptrace 函數(shù)

我不想暴露自己的ptrace等系統(tǒng)方法,不想被符號(hào)斷點(diǎn)斷住,可以采用匯編進(jìn)行調(diào)用ptrace

image.png

這樣人家就很難通過斷點(diǎn)的方式去調(diào)試

調(diào)試器建立調(diào)試關(guān)系的兩種方式:

用gdb調(diào)試程序[調(diào)試程序也在一個(gè)進(jìn)程里],可以直接gdb ./test,也可以gdb (test的進(jìn)程號(hào))。這對(duì)應(yīng)著使用ptrace建立跟蹤關(guān)系的兩種方式:

  • fork:利用fork+execve執(zhí)行被測(cè)試的程序,子進(jìn)程在執(zhí)行execve之前調(diào)用ptrace(PTRACE_TRACEME),建立了與父進(jìn)程(debugger 調(diào)試程序進(jìn)程)的跟蹤關(guān)系。

  • attach: debugger可以調(diào)用ptrace(PTRACE_ATTACH,pid,…),建立自己與進(jìn)程號(hào)為pid的進(jìn)程間的跟蹤關(guān)系。即利用PTRACE_ATTACH,使自己變成被調(diào)試程序的父進(jìn)程(用ps可以看到)。用attach建立起來的跟蹤關(guān)系,可以調(diào)用ptrace(PTRACE_DETACH,pid,…)來解除。注意attach進(jìn)程時(shí)的權(quán)限問題,如一個(gè)非root權(quán)限的進(jìn)程是不能attach到一個(gè)root進(jìn)程上的。

第一種方式的例子:

ptrace提供了對(duì)子進(jìn)程進(jìn)行單步的功能,ptrace(PTRACE_SINGLESTEP, …) 會(huì)使內(nèi)核在子進(jìn)程的每一條指令執(zhí)行前先將其阻塞,然后將控制權(quán)交給父進(jìn)程

而父進(jìn)程此時(shí)會(huì)使用 wait函數(shù)等待阻塞信號(hào),然后判斷status變量來檢查子進(jìn)程是被ptrace暫停掉還是已經(jīng)運(yùn)行結(jié)束并退出,如果狀態(tài)是ptrace暫停的,則可以獲取子進(jìn)程的寄存器器狀態(tài),

ptrace(PTRACE_GETREGS,child, NULL, ?s),獲取當(dāng)前指令等,在讓ptrace控制單步執(zhí)行
ptrace(PTRACE_SINGLESTEP, child,NULL, NULL);

每一步都去喚醒子進(jìn)程繼續(xù)執(zhí)行,并告訴內(nèi)核在執(zhí)行一條指令后就將其阻塞

最后讓子進(jìn)程恢復(fù)

PTRACE_SYSCALL:繼續(xù),但在下一個(gè)系統(tǒng)調(diào)用入口或出口處停止。

二: sysctl 作用

sysctl命令被用于在內(nèi)核運(yùn)行時(shí)動(dòng)態(tài)地修改內(nèi)核的運(yùn)行參數(shù)

函數(shù)原型

int sysctl (int *name, int nlen, void *oldval, size_t *oldlenp, void *newval, size_t newlen);

Name /* 整形數(shù)組,每個(gè)數(shù)組元素代表系統(tǒng)參數(shù)存取路徑上的一個(gè)文件或目錄名,例如/proc/sys/kernel用CTL_KERN表示*/

oldval /* 當(dāng)讀取系統(tǒng)參數(shù)時(shí),用于存取系統(tǒng)參數(shù)值,也就是/proc/sys/下的某個(gè)文件內(nèi)容*/

Newval /* 當(dāng)寫系統(tǒng)參數(shù)時(shí),記錄所要寫入的新值*/

反調(diào)試舉例:

當(dāng)一個(gè)進(jìn)程被調(diào)試的時(shí)候,該進(jìn)程會(huì)有一個(gè)標(biāo)記來標(biāo)記自己正在被調(diào)試,所以可以通過sysctl去查看當(dāng)前進(jìn)程的信息,看有沒有這個(gè)標(biāo)記位即可檢查當(dāng)前調(diào)試狀態(tài)。

image.png

檢測(cè)到調(diào)試器就退出,或者制造崩潰,或者隱藏工程啥的,當(dāng)然也可以定時(shí)去查看有沒有這個(gè)標(biāo)記

三: syscall 作用

為從實(shí)現(xiàn)從用戶態(tài)切換到內(nèi)核態(tài),系統(tǒng)提供了一個(gè)系統(tǒng)調(diào)用函數(shù)syscall ,所有的系統(tǒng)調(diào)用都可以通過syscall 去實(shí)現(xiàn)

比如: syscall (26,31,0,0) 來調(diào)用系統(tǒng)函數(shù)ptrace,ptrace的系統(tǒng)調(diào)用函數(shù)號(hào)是26

syscall是通過軟中斷來實(shí)現(xiàn)從用戶態(tài)到內(nèi)核態(tài),也可以通過匯編svc調(diào)用來實(shí)現(xiàn)。

image.png

比如:arm 32位 #80 就是軟中斷值,r12 存放系統(tǒng)函數(shù)編號(hào),arm 64位 #128 是系統(tǒng)中斷碼,x0存放系統(tǒng)函數(shù)編號(hào)

四: 重簽名防護(hù)

想自己的app不被重簽名,可以在代碼中檢測(cè)簽名信息

查看證書的application-identifier 查看embedded.mobileprovision信息security cms -D -i embedded.mobileprovision 找到<key>application-identifier</key>的value的第一部分就是

在執(zhí)行代碼的時(shí)候檢查簽名是否和我們已知的簽名對(duì)比

void checkCodesign(NSString *id){
 // 描述文件路徑
 NSString *embeddedPath = [[NSBundle mainBundle] pathForResource:@"embedded" ofType:@"mobileprovision"];
 // 讀取application-identifier 注意描述文件的編碼要使用:NSASCIIStringEncoding
 NSString *embeddedProvisioning = [NSString stringWithContentsOfFile:embeddedPath encoding:NSASCIIStringEncoding error:nil];
 NSArray *embeddedProvisioningLines = [embeddedProvisioning componentsSeparatedByCharactersInSet:[NSCharacterSet newlineCharacterSet]];
  
 for (int i = 0; i < embeddedProvisioningLines.count; i++) {
 if ([embeddedProvisioningLines[i] rangeOfString:@"application-identifier"].location != NSNotFound) {
   
  NSInteger fromPosition = [embeddedProvisioningLines[i+1] rangeOfString:@"<string>"].location+8;
   
  NSInteger toPosition = [embeddedProvisioningLines[i+1] rangeOfString:@"</string>"].location;
   
  NSRange range;
  range.location = fromPosition;
  range.length = toPosition - fromPosition;
   
  NSString *fullIdentifier = [embeddedProvisioningLines[i+1] substringWithRange:range];
  NSArray *identifierComponents = [fullIdentifier componentsSeparatedByString:@"."];
  NSString *appIdentifier = [identifierComponents firstObject];
  
  // 對(duì)比簽名ID
  if (![appIdentifier isEqual:id]) {
  //exit
  asm(
   "mov X0,#0\n"
   "mov w16,#1\n"
   "svc #0x80"
   );
  }
  break;
 }
 }
}

五:反反調(diào)試

這里主要針對(duì)ptrace、sysctl、syscall來反反調(diào)試,做法就很簡(jiǎn)單了,hook函數(shù)

比如:

1: hook ptrace 函數(shù), 遇到request = 31, 就知道程序進(jìn)行了反調(diào)試,所以我們可以將request 改掉

image.png

2:hook sysctl , 看其是否在檢查進(jìn)程被追蹤的這個(gè)標(biāo)記TASK_TRACED,我們將其返回信息info_ptr -> kp_proc.p_flag 改掉,讓其檢查的結(jié)果是沒有設(shè)置追蹤標(biāo)識(shí)

image.png

3 hook syscall , 防止ptrace 是通過syscall 的方式去調(diào)用的,

image.png

4:hook dlsym 防止通過這種方式去調(diào)用ptrace 函數(shù)

image.png

5 初始化函數(shù)

image.png

或者使用fishhook

image.png

lldb 反反調(diào)試的

通過lldb下斷點(diǎn),然后修改參數(shù),或者直接返回也可以達(dá)到反反調(diào)試的效果

為了方便直接使用facebook的chisel來增加腳本。

image.png
image.png
image.png

當(dāng)遇到情況為$x0 == 31 時(shí)發(fā)生回掉,將x0 或者r0 的值改成0

image.png

六: App的防護(hù)

1:首先加強(qiáng)現(xiàn)有的密碼檢測(cè)機(jī)制的監(jiān)測(cè)力度,除了密碼長度,數(shù)字、字符甚至特殊符號(hào)的混雜程度,本文著重對(duì)易受攻擊的鍵盤上特定組合碼進(jìn)行檢測(cè);

2:其次完善iOS內(nèi)存保護(hù)機(jī)制,利用Objective-C對(duì)象實(shí)現(xiàn)內(nèi)存安全擦除,保證及時(shí)對(duì)文件數(shù)據(jù)的每個(gè)字節(jié)都做到全覆蓋,防止對(duì)象被跟蹤后信息遭到泄露;

3:再次在維護(hù)程序在運(yùn)行時(shí)的安全性上提出了一種貫穿程序被調(diào)試的三個(gè)階段的反調(diào)試機(jī)制:從程序開始被調(diào)試、繼續(xù)被跟蹤到最終被惡意修改均進(jìn)行跟蹤測(cè)試,最終阻止被惡意修改的目標(biāo)繼續(xù)的執(zhí)行

針對(duì)上述安全隱患,我們的iOS應(yīng)用安全防護(hù)框架需實(shí)現(xiàn)的任務(wù)大致如下:

  • 防護(hù)

    • ObjC類名方法名等重命名為難以理解的字符

    • 加密靜態(tài)字符串運(yùn)行時(shí)解密

    • 混淆代碼使其難于反匯編

    • 本地存儲(chǔ)文件防篡改

  • 檢測(cè)

    • 調(diào)試狀態(tài)檢測(cè) : 反調(diào)試 ptrace . sysctl

    • 越獄環(huán)境檢測(cè)

    • ObjC的Swizzle檢測(cè)

    • 任意函數(shù)的hook檢測(cè)

    • 指定區(qū)域或數(shù)據(jù)段的校驗(yàn)和檢測(cè)

  • 自修復(fù)

    • 自修復(fù)被篡改的數(shù)據(jù)和代碼段

此外,還需要多層的防護(hù),通過高層保護(hù)低層的方式來保證整個(gè)防護(hù)機(jī)制不失效。 參考IBM移動(dòng)終端安全防護(hù)框架解決方案:

1:越獄檢測(cè)的方法:

   1》使用NSFileManager判斷設(shè)備是否安裝了如下越獄常用工具

       /Applications/Cydia.app

       /Library/MobileSubstrate/MobileSubstrate.dylib

       /bin/bash

        /usr/sbin/sshd

        /etc/apt

      這種方式不要寫成Bool 方式去檢查 ,容易被攻擊者h(yuǎn)ook

        ![image.png](https://upload-images.jianshu.io/upload_images/1974361-6b0840859fa3ba63.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)

       注意: 攻擊者可能會(huì)改變這些工具的安裝路徑,躲過你的判斷。

   2》可以嘗試打開cydia應(yīng)用注冊(cè)的URL scheme,后面應(yīng)該是你知道某個(gè)應(yīng)用URL scheme

        if([[UIApplication sharedApplication] canOpenURL:[NSURL URLWithString:@"[cydia://package/com.example.package](cydia://package/com.example.package)"]]){

             NSLog(@"Device is jailbroken");

        }

       但是不是所有的工具都會(huì)注冊(cè)URL scheme,而且攻擊者可以修改任何應(yīng)用的URL scheme。

   3》你可以嘗試讀取下應(yīng)用列表,看看有無權(quán)限獲?。?
image.png
   攻擊者可能會(huì)hook NSFileManager 的方法,讓你的想法不能如愿

     4》你可以回避 NSFileManager,使用stat系列函數(shù)檢測(cè)Cydia等工具:

        ![image.png](https://upload-images.jianshu.io/upload_images/1974361-cfbf09cba56d9648.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)

攻擊者可能會(huì)利用 Fishhook原理 hook了stat

   5》你可以看看stat是不是出自系統(tǒng)庫,有沒有被攻擊者換掉
image.png

使用dladdr方法可以獲得一個(gè)函數(shù)所在的模塊.從而判斷該函數(shù)是否被替換掉

如果結(jié)果不是 /usr/lib/system/libsystem_kernel.dylib 的話,那就100%被攻擊了。

如果 libsystem_kernel.dylib 都是被攻擊者替換掉的…

也可以判斷ios 的方法在什么庫中,通過該方法驗(yàn)證指定類的方法是否都來自指定模塊

image.png

建議使用inline方式編譯,像這樣以內(nèi)聯(lián)函數(shù)的形式編譯,攻擊者必須修改每一處調(diào)用該函數(shù)的的地方

檢查所有方法判斷是否來之某個(gè)模塊

image.png
    6》檢索一下自己的應(yīng)用程序是否被鏈接了異常動(dòng)態(tài)庫,列出所有已鏈接的動(dòng)態(tài)庫:
image.png

通常情況下,會(huì)包含越獄機(jī)的輸出結(jié)果會(huì)包含字符串: Library/MobileSubstrate/MobileSubstrate.dylib

攻擊者可能會(huì)給MobileSubstrate改名,但是原理都是通過DYLD_INSERT_LIBRARIES注入動(dòng)態(tài)庫

       7》可以通過檢測(cè)當(dāng)前程序運(yùn)行的環(huán)境變量:
image.png

未越獄設(shè)備返回結(jié)果是null,越獄設(shè)備就各有各的精彩了,尤其是老一點(diǎn)的iOS版本越獄環(huán)境

上述越獄檢查總結(jié)如下:

  • 不要用NSFileManager,這是最容易被hook掉的。

  • 檢測(cè)方法中所用到的函數(shù)盡可能用底層的C,如文件檢測(cè)用stat函數(shù)(iPod7.0,越獄機(jī)檢測(cè)越獄常見的會(huì)安裝的文件只能檢測(cè)到此步驟,下面的檢測(cè)不出來)

  • 再進(jìn)一步,就是檢測(cè)stat是否出自系統(tǒng)庫

  • 再進(jìn)一步,就是檢測(cè)鏈接動(dòng)態(tài)庫(盡量不要,appStore可能審核不過)

  • 再進(jìn)一步,檢測(cè)程序運(yùn)行的環(huán)境變量

即使這樣還是不能完全檢查

比如: 用戶可能安裝越獄檢測(cè)繞過插件(xCon),對(duì)于越獄檢測(cè),很大程度上都還是針對(duì)某些目錄下某個(gè)文件名字是否換了或者文件被替換了等等去檢測(cè);

檢測(cè)代碼
- (BOOL)mgjpf_isJailbroken

{

    //以下檢測(cè)的過程是越往下,越獄越高級(jí)
   // /Applications/Cydia.app, /privte/var/stash

    BOOL jailbroken = NO;

    NSString *cydiaPath = @"/Applications/Cydia.app";

    NSString *aptPath = @"/private/var/lib/apt/";

    if ([[NSFileManager defaultManager] fileExistsAtPath:cydiaPath]) {

        jailbroken = YES;

    }

    if ([[NSFileManager defaultManager] fileExistsAtPath:aptPath]) {

        jailbroken = YES;

    }

    //可能存在hook了NSFileManager方法,此處用底層C stat去檢測(cè)

    struct stat stat_info;

    if (0 == stat("/Library/MobileSubstrate/MobileSubstrate.dylib", &stat_info)) {

        jailbroken = YES;

    }

    if (0 == stat("/Applications/Cydia.app", &stat_info)) {

        jailbroken = YES;

    }

    if (0 == stat("/var/lib/cydia/", &stat_info)) {

        jailbroken = YES;

    }

    if (0 == stat("/var/cache/apt", &stat_info)) {

        jailbroken = YES;

    }

//    /Library/MobileSubstrate/MobileSubstrate.dylib 最重要的越獄文件,幾乎所有的越獄機(jī)都會(huì)安裝MobileSubstrate

//    /Applications/Cydia.app/ /var/lib/cydia/絕大多數(shù)越獄機(jī)都會(huì)安裝

//    /var/cache/apt /var/lib/apt /etc/apt

//    /bin/bash /bin/sh

//    /usr/sbin/sshd /usr/libexec/ssh-keysign /etc/ssh/sshd_config

    //可能存在stat也被hook了,可以看stat是不是出自系統(tǒng)庫,有沒有被攻擊者換掉

    //這種情況出現(xiàn)的可能性很小

    int ret;

    Dl_info dylib_info;

    int (*func_stat)(const char *,struct stat *) = stat;

    if ((ret = dladdr(func_stat, &dylib_info))) {

        NSLog(@"lib:%s",dylib_info.dli_fname);      //如果不是系統(tǒng)庫,肯定被攻擊了

        if (strcmp(dylib_info.dli_fname, "/usr/lib/system/libsystem_kernel.dylib")) {   //不相等,肯定被攻擊了,相等為0

            jailbroken = YES;

        }

    }

    //還可以檢測(cè)鏈接動(dòng)態(tài)庫,看下是否被鏈接了異常動(dòng)態(tài)庫,但是此方法存在appStore審核不通過的情況,這里不作羅列

    //通常,越獄機(jī)的輸出結(jié)果會(huì)包含字符串: Library/MobileSubstrate/MobileSubstrate.dylib——之所以用檢測(cè)鏈接動(dòng)態(tài)庫的方法,是可能存在前面的方法被hook的情況。這個(gè)字符串,前面的stat已經(jīng)做了

    //如果攻擊者給MobileSubstrate改名,但是原理都是通過DYLD_INSERT_LIBRARIES注入動(dòng)態(tài)庫

    //那么可以,檢測(cè)當(dāng)前程序運(yùn)行的環(huán)境變量

    char *env = getenv("DYLD_INSERT_LIBRARIES");

    if (env != NULL) {

        jailbroken = YES;

    }

    return jailbroken;

}

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容