<menu id="guoca"></menu>
<nav id="guoca"></nav><xmp id="guoca">
  • <xmp id="guoca">
  • <nav id="guoca"><code id="guoca"></code></nav>
  • <nav id="guoca"><code id="guoca"></code></nav>

    繞過iOS 基于svc 0x80的ptrace反調試

    VSole2022-08-06 16:17:39

    一、ptrace反調試的原理與實現

    由于廠商對于app安全方面的認識不斷提升,當前iOS上的調試對抗愈演愈烈。而ptrace attach deny作為比較常用的反調試手段,其原理是將相關進程proc的p_lflag加上一個P_LNOATTACH標識位,當外部調試器想要再加載進程時,會返回一個Segmentation fault: 11 的錯誤標識:

    iPhone8k:/usr/local root# debugserver 127.0.0.1:6666 -a Xxxxdebugserver-@(#)PROGRAM:LLDB  PROJECT:lldb-900.3.104 for arm64.Attaching to process Xxxx...Segmentation fault: 11
    

    ptrace源碼,摘自xnu-6153.101.6/bsd/kern/mach_process.c

    intptrace(struct proc *p, struct ptrace_args *uap, int32_t *retval){//....     if (uap->req == PT_DENY_ATTACH) {//....        proc_lock(p);        if (ISSET(p->p_lflag, P_LTRACED)) {            proc_unlock(p);            //...            exit1(p, W_EXITCODE(ENOTSUP, 0), retval);             thread_exception_return();            /* NOTREACHED */        }        SET(p->p_lflag, P_LNOATTACH);//p_lflag |=0x00001000        proc_unlock(p);         return 0;    }....}
    

    廠商為了防止API hook使其失效,開始大量使用基于svc 0x80的服務調用方式,并伴隨著代碼混淆以及代碼膨脹,使得想要快速定位svc 0x80調用并將其patch掉也變得難以實現。

    使用svc方式調用ptrace attach deny

    __asm__("mov X0, #31"        "mov X1, #0"        "mov X2, #0"        "mov X3, #0"        "mov X16, #26"        "svc #0x80"        );
    

    以上是ptrace反調試的簡單介紹,如有疑問可參考下面的文章:

    https://blog.it-securityguard.com/itunes-exploit-development/

    https://cardaci.xyz/blog/2018/02/12/a-macos-anti-debug-technique-using-ptrace/

    二、對抗方案

    ptrace實現PT_DENY_ATTACH,就是對相關進程proc的p_lflag加上P_LNOATTACH標示位。那么要想使得進程和被調試器加載,只需要取消這個標志位。現在的問題是,proc鏈表結構,是位于iOS內核中,所以我們必須要擁有讀寫iOS內核的能力,要獲取這個能力,第一個想到的辦法是對iOS的漏洞利用,畢竟,iOS越獄也是基于這些漏洞,對特定內核位置進行讀寫。

    所幸的是,當前一些越獄工具,提供了tfp0(task for pid 0)接口,可供我們讀寫iOS內核。

    那什么是tfp0呢?theiphonewiki上給出的說明如下:

    In the XNU kernel, task_for_pid is a function that allows a (privileged) process to get the task port of another process on the same host, except the kernel task (process ID 0). A tfp0 patch (or task_for_pid(0) patch) removes this restriction, allowing any executable running as root to call task_for_pid for pid 0 (hence the name) and then use vm_read and vm_write to modify the kernel VM region. The entitlements get-task-allow and task_for_pid-allow are required to make AMFI happy.

    https://www.theiphonewiki.com/wiki/Tfp0_patch

    現在我們可以整理一下思路了:

    1、找到kernproc在內核的地址,然后通tfp0調用讀取kernproc。

    2、找到當前系統所有的進程信息,所有進程都放在了kernproc指向的鏈表中。

    3、找到相當進程的proc,對p_lflag,進行修改。

    三、方案實現

    有了思路,那接下來我們要如何找到kernproc的內核地址呢?

    通過閱讀源碼,我們知道kernproc的是一個全局變量,所以判斷他的地址偏移一定是固定了,而且應該位于kernelcache,并且會在bsd_init過程中被初始化。

    根據上邊的線索,我們可以通過逆向kernelcache鏡像文件找到他的偏移。

    找到偏移后,下一個問題來了,由于ASLR的存在,我們必須要獲取到kernbase才能配合偏移量定位kernproc位置,進行進一步操作。

    索性GeoSn0w大神已經在github上提供了這個功能的代碼,其原理是通過掃描kernel heap 找到指向內核鏡像的指針,再根據這個內核景象向上回溯machO的head。詳細的可以通過閱讀源碼來了解。

    boolkernel_base_init_with_unsafe_heap_scan() {    uint64_t kernel_region_base = 0xfffffff000000000;    uint64_t kernel_region_end  = 0xfffffffbffffc000;    // Try and find a pointer in the kernel heap to data in the kernel image. We'll take the    // smallest such pointer.    uint64_t kernel_ptr = (uint64_t)(-1);    mach_vm_address_t address = 0;    for (;;) {        // Get the next memory region.        mach_vm_size_t size = 0;        uint32_t depth = 2;        struct vm_region_submap_info_64 info;        mach_msg_type_number_t count = VM_REGION_SUBMAP_INFO_COUNT_64;        kern_return_t kr = mach_vm_region_recurse(kernel_task_port, &address, &size,                &depth, (vm_region_recurse_info_t) &info, &count);        if (kr != KERN_SUCCESS) {            break;        }        // Skip any region that is not on the heap, not in a submap, not readable and        // writable, or not fully mapped.        int prot = VM_PROT_READ | VM_PROT_WRITE;        if (info.user_tag != 12            || depth != 1            || (info.protection & prot) != prot            || info.pages_resident * 0x4000 != size) {            goto next;        }        // Read the first word of each page in this region.        for (size_t offset = 0; offset < size; offset += 0x4000) {            uint64_t value = 0;            bool ok = kernel_read(address + offset, &value, sizeof(value));            if (ok                && kernel_region_base <= value                && value < kernel_region_end                && value < kernel_ptr) {                kernel_ptr = value;            }        }next:        address += size;    }    // If we didn't find any such pointer, abort.    if (kernel_ptr == (uint64_t)(-1)) {        return false;    }    printf("found kernel pointer %p", (void *)kernel_ptr);    // Now that we have a pointer, we want to scan pages until we reach the kernel's Mach-O    // header.    uint64_t page = kernel_ptr & ~0x3fff;    for (;;) {        bool found = is_kernel_base(page);        if (found) {            kernel_base = page;            return true;        }        page -= 0x4000;    }    return false;}
    

    好了,萬事俱備了,現在需要的是通過代碼將其實現:

    // ---- Main -------------------------------------------------------------------------------------- //iphone8  ios 13.4  kernel#define TARGET_KERNELCACHE_VERSION_STRING "@(#)VERSION: Darwin Kernel Version 19.4.0: Mon Feb 24 22:04:29 PST 2020; root:xnu-6153.102.3~1/RELEASE_ARM64_T8015" int main() {    kernel_task_init();    uint64_t kb = kernel_base_init();    for (size_t i = 0; i < 8; i++) {        printf("%016llx", kernel_read64(kb + 8 * i));    }    uint64_t versionstraddr = kb + 0x2FB64;    char versionstr[256];    if(kernel_read(versionstraddr, (void *)&versionstr, sizeof(versionstr)))    {        printf("%s", versionstr);        if(strcmp(TARGET_KERNELCACHE_VERSION_STRING,versionstr) == 0)        {            printf("kernel cache hit");            //226AF60  kernproc            uint64_t kernel_proc0 = kernel_read64(kb + 0x226AF60);             struct proc * proc0 =  (void *)malloc(sizeof(struct proc));             if(!kernel_read(kernel_proc0, (void *)proc0, sizeof(struct proc)))            {                printf("proc0 read failed");                return -1;            }             printf("uniqueid offset 0x%llx  comm offset 0x%llx ",(int64_t)&(proc0->p_uniqueid) - (int64_t)proc0, (int64_t)&(proc0->p_comm)- (int64_t)proc0);             struct proc * proc1 =  (struct proc *)malloc(sizeof(struct proc));            uint64_t preptr = (uint64_t)(proc0->p_list.le_prev);            while(preptr){                if(!kernel_read(preptr, (void *)proc1, sizeof(struct proc)))                {                    printf("procnext read failed");                    return -1;                }else{                    if(proc1->p_list.le_prev == 0)                    {                        printf("proc1->p_list.le_prev == 0");                        break;                    }                    int64_t lflagoffset = (int64_t)&(proc1->p_lflag) - (int64_t)proc1;                    int lflagvalue = proc1->p_lflag;                    printf("(%llu)%s  proc = 0x%llx   lflag = 0x%x  lflag offset = 0x%llx"                        ,proc1->p_uniqueid,                        proc1->p_comm,//(char *)((int64_t)proc1 + 0x258),                        preptr,lflagvalue,lflagoffset);                         if(ISSET(lflagvalue, P_LNOATTACH))                        {                            printf(" !!!P_LNOATTACH set");                            CLR(lflagvalue, P_LNOATTACH);                            KERNEL_WRITE32(preptr + lflagoffset, lflagvalue);                        }                        printf("");                     preptr = (uint64_t)(proc1->p_list.le_prev);                }            }             printf("end");            free(proc0);            free(proc1);        }else{            printf("kernel cache version mismatch");        }    }else{        printf("failed to read kernel version string");    }    return 0;}
    

    完整代碼可到github上下載:

    https://github.com/xiaohang99/iOSFuckDenyAttach

    printfptrace
    本作品采用《CC 協議》,轉載必須注明作者和本文鏈接
    由于廠商對于app安全方面的認識不斷提升,當前iOS上的調試對抗愈演愈烈。有了思路,那接下來我們要如何找到kernproc的內核地址呢?根據上邊的線索,我們可以通過逆向kernelcache鏡像文件找到他的偏移。找到偏移后,下一個問題來了,由于ASLR的存在,我們必須要獲取到kernbase才能配合偏移量定位kernproc位置,進行進一步操作。
    目前絕大部分app都會頻繁的使用syscall去獲取設備指紋和做一些反調試,使用常規方式過反調試已經非常困難了,使用內存搜索svc指令已經不能滿足需求了,開始學習了一下通過ptrace/ptrace配合seccomp來解決svc反調試難定位難繞過等問題。seccompLinux 2.6.12中的導入了第一個版本的seccomp,通過向/proc/PID/seccomp接口中寫入“1”來啟動通過濾器只支持幾個函數。BPF程序由一組BPF指令組成,可以在系統調用執行之前對其進行檢查,以決定是否允許執行該系統調用。
    文中使用的示例代碼可以從 這里 獲取。的功能是在終端打印出hello這6個字符(包括結尾的?編譯它們分別生成libtest.so和?存在嚴重的內存泄露問題,每調用一次say_hello函數,就會泄露1024字節的內存。
    大廠基本為了程序的安全,會使用大量內聯SVC去調用系統函數,以此來保護程序的安全。如何實現SVC指令的IO重定向,成為最大的問題。內核態是當Linux需要處理文件,或者進行中斷IO等操作的時候就會進入內核態。當arm系列cpu發現svc指令的時候,就會陷入中斷,簡稱0x80中斷。
    能運行的環境包括I/O,權限控制,系統調用,進程管理,內存管理等多項功能都可以歸結到上邊兩點中。需要注意的是,kernel 的crash 通常會引起重啟。注意大多數的現代操作系統只使用了 Ring 0 和 Ring 3。
    Seccomp BPF與容器安全
    2022-07-17 10:07:03
    本文詳細介紹了關于seccomp的相關概念,包括seccomp的發展歷史、Seccomp BPF的實現原理以及與seccomp相關的一些工具等。此外,通過實例驗證了如何使用seccomp bpf 來保護Docker的安全。
    也防止有人通過inlinehook 直接hook recv ,recvform,recvmsg 直接在收到數據包的時候被攔截和替換掉。
    FartExt是我之前學習脫殼實踐時做的一個自動脫殼機,是基于FART的主動調用思想實現對特定的抽取殼進行優化處理的工具。由于原本的FART沒有配置相關的,所以我增加了配置對指定app脫殼。
    NX實現機制淺析
    2021-10-12 16:53:04
    是否開啟NX取決于參數-z設置,而gcc僅僅是將-z keyword傳遞給linker——ld,并不會真正解析該參數:
    └─50-After-systemd-logind\x2eservice.conf, 50-After-systemd-user-sessions\x2eservice.conf, 50-Description.conf, 50-SendSIGHUP.conf, 50-Slice.conf, 50-TasksMax.con. Active: active since 一 2021-07-12 10:05:01 CST; 4h 52min ago. 真不巧,看起來不是注冊到 systemd 的,那么是誰拉起來的呢?啊,是 crontab非常不巧,我當時一心想找是哪個 service,沒注意到 crontab 的存在,還以為上次的那個挖礦木馬換了個 service 的名字,還去這個路徑找了好久,找了半天也沒有看到惡意的 service 啊突然想到我還沒看 crontab于是打開crontab發現了一條指令他靜靜的呆在那里像是在嘲笑我太菜了,這個套路都沒注意到 :P于是,注釋掉這行,然后對著剛剛 systemd 輸出的三個進程一頓 kill ├─2075 tOAK5Ejl
    VSole
    網絡安全專家
      亚洲 欧美 自拍 唯美 另类