溫馨提示×

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

密碼登錄×
登錄注冊(cè)×
其他方式登錄
點(diǎn)擊 登錄注冊(cè) 即表示同意《億速云用戶服務(wù)條款》

查找使用Kernel Task的函數(shù)是什么

發(fā)布時(shí)間:2022-01-07 09:21:54 來源:億速云 閱讀:153 作者:iii 欄目:數(shù)據(jù)安全

這篇文章主要介紹“查找使用Kernel Task的函數(shù)是什么”,在日常操作中,相信很多人在查找使用Kernel Task的函數(shù)是什么問題上存在疑惑,小編查閱了各式資料,整理出簡(jiǎn)單好用的操作方法,希望對(duì)大家解答”查找使用Kernel Task的函數(shù)是什么”的疑惑有所幫助!接下來,請(qǐng)跟著小編一起來學(xué)習(xí)吧!

定位 Kernel Task

為了獲取內(nèi)核信息,我們需要定位到 Kernel Task 的地址,再通過 tfp0 的 kread 讀取內(nèi)容。要定位 Kernel Task,關(guān)鍵是找到獲取 Kernel Task 的代碼,然后嘗試從內(nèi)存中定位這段代碼,再分析指令解出變量的文件偏移即可。

查找使用 Kernel Task 的函數(shù)

在 xnu-4903.221.2 中可以找到訪問 Kernel Task 的如下代碼:

int
proc_apply_resource_actions(void * bsdinfo, __unused int type, int action)
{
    proc_t p = (proc_t)bsdinfo;

    switch(action) {
        case PROC_POLICY_RSRCACT_THROTTLE:
            /* no need to do anything */
            break;

        case PROC_POLICY_RSRCACT_SUSPEND:
            task_suspend(p->task);
            break;

        case PROC_POLICY_RSRCACT_TERMINATE:
            psignal(p, SIGKILL);
            break;

        case PROC_POLICY_RSRCACT_NOTIFY_KQ:
            /* not implemented */
            break;

        case PROC_POLICY_RSRCACT_NOTIFY_EXC:
            panic("shouldn't be applying exception notification to process!");
            break;
    }
    return(0);
}

這里有一段字符串 "shouldn't be applying exception notification to process!" 可用于輔助定位,它在編譯后會(huì)被存儲(chǔ)在 __TEXT,__cstring 段,通過在內(nèi)存中搜索 __TEXT,__cstring 段即可找到字符串地址,我們稱之為 location_str。

定位到函數(shù)中的 String XREF

由于 ARM 的取址常常需要 2 條指令完成,為了定位使用 location_str 的代碼,我們需要對(duì)代碼段進(jìn)行靜態(tài)分析。當(dāng)發(fā)現(xiàn)寄存器中的值等于 location_str 時(shí)即發(fā)現(xiàn)了一個(gè)交叉引用(XREF),通過這種手段我們便能在內(nèi)存中定位到語句 panic("shouldn't be applying exception notification to process!") 對(duì)應(yīng)的指令地址。

回溯找到 Kernel Task XREF

最快定位到 Kernel Task 的方法是回溯到 task_suspend(p->task),在 task_suspend 第一次訪問 p->task 時(shí)一定會(huì)對(duì) task 尋址,我們可以從尋址指令中解出 task 的文件偏移,再加上內(nèi)核在內(nèi)存中的基地址即可得到 Kernel Task 的地址。

kern_return_t
task_suspend(task_t task)
{
    kern_return_t kr;
    mach_port_t port, send, old_notify;
    mach_port_name_t name;

    if (task == TASK_NULL || task == kernel_task)
        return (KERN_INVALID_ARGUMENT);

    task_lock(task);
    // ...

從上面的分析可以看出問題的關(guān)鍵在于 XREF 的定位,下面我們將分析一種 String Based XREF 定位算法來解決上述問題。

在內(nèi)存中加載 Kernelcache

根據(jù) iPhone Wiki 給出的 Kernelcache 定義[1]:

The kernelcache is basically the kernel itself as well as all of its extensions (AppleImage3NORAccess, IOAESAccelerator, IOPKEAccelerator, etc.) into one file, then packed/encrypted in an IMG3 (iPhone OS 2.0 and above) or 8900 (iPhone OS 1.0 through 1.1.4) container.

即 kernelcache 就是將 kernel 和它的擴(kuò)展打包在一個(gè)文件中并以 IMG3 格式存儲(chǔ)(iOS 2 以上)。

在 上一篇文章 中我們介紹了基于 tfp0 的沙盒逃逸方法,通過沙盒逃逸我們可以從 /System/Library/Caches/com.apple.kernelcaches/kernelcache 讀取 kernelcache,它既是當(dāng)前系統(tǒng)加載的鏡像。

讀者可打開 Undecimus 的 jailbreak.m 文件,搜索 "Initializing patchfinder" 定位到 kernelcache 的加載代碼,加載方法和普通的 Mach-O 文件類似,也是先讀取 Mach HeaderLoad Commands,然后逐段記錄偏移量,具體代碼在 init_kernel 函數(shù)中。

這里不再贅述加載過程,只指出幾個(gè)關(guān)鍵的全局變量:

  1. cstring_basecstring_size__TEXT,__cstring 段的虛擬地址和長(zhǎng)度;

  2. xnucore_basexnucore_size__TEXT,__TEXT_EXEC 段,即代碼段的虛擬地址和長(zhǎng)度;

  3. kerndumpbase 是所有段中最小的虛擬地址,即 kernelcache 加載的虛擬基地址,在普通的 Mach-O 文件中這個(gè)值一般是 __PAGEZERO 段的虛擬地址 0x100000000,在內(nèi)核中似乎是 __TEXT 段的虛擬地址 0xFFFFFFF007004000;

  4. kernel 是 kernelcache 在用戶空間的完整映射,即一份完整加載的內(nèi)核鏡像。

Find String Based XREF

在 Undecimus 中包含一個(gè) find_strref 函數(shù)用于定位字符串的 XREF:

addr_t
find_strref(const char *string, int n, enum string_bases string_base, bool full_match, bool ppl_base)
{
    uint8_t *str;
    addr_t base;
    addr_t size;
    enum text_bases text_base = ppl_base?text_ppl_base:text_xnucore_base;

    switch (string_base) {
        case string_base_const:
            base = const_base;
            size = const_size;
            break;
        case string_base_data:
            base = data_base;
            size = data_size;
            break;
        case string_base_oslstring:
            base = oslstring_base;
            size = oslstring_size;
            break;
        case string_base_pstring:
            base = pstring_base;
            size = pstring_size;
            text_base = text_prelink_base;
            break;
        case string_base_cstring:
        default:
            base = cstring_base;
            size = cstring_size;
            break;
    }
    addr_t off = 0;
    while ((str = boyermoore_horspool_memmem(kernel + base + off, size - off, (uint8_t *)string, strlen(string)))) {
        // Only match the beginning of strings
        // first_string || \0this_string
        if ((str == kernel + base || *(str-1) == '\0') && (!full_match || strcmp((char *)str, string) == 0))
            break;
        // find after str
        off = str - (kernel + base) + 1;
    }
    if (!str) {
        return 0;
    }
    // find xref
    return find_reference(str - kernel + kerndumpbase, n, text_base);
}

它要求傳入字符串 string,引用的序號(hào) n,基準(zhǔn)段 string_base,是否完全匹配 full_match,以及是否位于 __PPLTEXT 段,對(duì)于尋找 Kernel Task 的場(chǎng)景,我們的入?yún)⑷缦拢?/p>

addr_t str = find_strref("\"shouldn't be applying exception notification", 2, string_base_cstring, false, false);

即以 __TEXT,__cstring 為基準(zhǔn),不要求完全匹配,找到第 2 個(gè)交叉引用所在的地址。

定位字符串地址

字符串地址的定位邏輯在 boyermoore_horspool_memmem 函數(shù)中:

static unsigned char *
boyermoore_horspool_memmem(const unsigned char* haystack, size_t hlen,
                           const unsigned char* needle,   size_t nlen)
{
    size_t last, scan = 0;
    size_t bad_char_skip[UCHAR_MAX + 1]; /* Officially called:
                                          * bad character shift */

    /* Sanity checks on the parameters */
    if (nlen <= 0 || !haystack || !needle)
        return NULL;

    /* ---- Preprocess ---- */
    /* Initialize the table to default value */
    /* When a character is encountered that does not occur
     * in the needle, we can safely skip ahead for the whole
     * length of the needle.
     */
    for (scan = 0; scan <= UCHAR_MAX; scan = scan + 1)
        bad_char_skip[scan] = nlen;

    /* C arrays have the first byte at [0], therefore:
     * [nlen - 1] is the last byte of the array. */
    last = nlen - 1;

    /* Then populate it with the analysis of the needle */
    for (scan = 0; scan < last; scan = scan + 1)
        bad_char_skip[needle[scan]] = last - scan;

    /* ---- Do the matching ---- */

    /* Search the haystack, while the needle can still be within it. */
    while (hlen >= nlen)
    {
        /* scan from the end of the needle */
        for (scan = last; haystack[scan] == needle[scan]; scan = scan - 1)
            if (scan == 0) /* If the first byte matches, we've found it. */
                return (void *)haystack;

        /* otherwise, we need to skip some bytes and start again.
           Note that here we are getting the skip value based on the last byte
           of needle, no matter where we didn't match. So if needle is: "abcd"
           then we are skipping based on 'd' and that value will be 4, and
           for "abcdd" we again skip on 'd' but the value will be only 1.
           The alternative of pretending that the mismatched character was
           the last character is slower in the normal case (E.g. finding
           "abcd" in "...azcd..." gives 4 by using 'd' but only
           4-2==2 using 'z'. */
        hlen     -= bad_char_skip[haystack[last]];
        haystack += bad_char_skip[haystack[last]];
    }

    return NULL;
}

我們首先根據(jù)調(diào)用分析入?yún)ⅲ?/p>

addr_t base = cstring_base;
addr_t off = 0;
while ((str = boyermoore_horspool_memmem(kernel + base + off, size - off, (uint8_t *)string, strlen(string)))) {
    // Only match the beginning of strings
    // first_string || \0this_string
    if ((str == kernel + base || *(str-1) == '\0') && (!full_match || strcmp((char *)str, string) == 0))
        break;
    // find after str
    off = str - (kernel + base) + 1;
}
  1. haystack = kernel + base + off,即 __TEXT,__cstring 段的起始地址;

  2. hlen = size - off,即 __TEXT,__cstring 段的長(zhǎng)度;

  3. needle = string 即待查找字符串指針;

  4. nlen = strlen(string) 即待查找字符串的長(zhǎng)度。

在函數(shù)的開頭首先維護(hù)了一個(gè) bad_char_skip 數(shù)組來記錄當(dāng)匹配失敗時(shí),應(yīng)當(dāng)跳過多少個(gè)字符來避免無意義的匹配。整個(gè)算法采用了倒序掃描的方式,不斷從 haystack[needle_len - 1] 向前掃描并檢查 haystack[i] == needle[i],當(dāng)匹配到 haystack[0] 時(shí)如果依然滿足條件,說明找到了字符串的地址,否則根據(jù)匹配失敗的字符查 bad_char_skip 表將 haystack 指針后移繼續(xù)匹配。

需要注意的是,在匹配成功后得到的字符串地址是相對(duì)于用戶空間的 kernelcache 映射 kernel 的,并非是字符串在內(nèi)核中的實(shí)際地址。

搜索對(duì)字符串所在地址的尋址操作

在獲取到字符串在用戶空間的地址 str 后,首先需要計(jì)算它在 kernelcache 中的虛擬地址:

addr_t str_vmaddr = str - kernel + kerndumpbase;

內(nèi)核代碼中對(duì) str 的引用一定涉及到對(duì) str_vmaddr 的尋址,主要的尋址方式有以下幾種:

; 1
adrp xn, str@PAGE
add xn, xn, str@PAGEOFF

; 2
ldr xn, [xm, #imm]

; 3
ldr xn, =#imm

; 4
adr xn, #imm

; 5
bl #addr

find_strref 的尾部調(diào)用了 return find_reference(str_vmaddr, n, text_base)find_reference 對(duì) __TEXT_EXEC,__text 進(jìn)行了靜態(tài)分析,對(duì)尋址相關(guān)的指令模擬了寄存器運(yùn)算,主要邏輯在 xref64 函數(shù)中,當(dāng)發(fā)現(xiàn)寄存器中的值等于 str_vmaddr 時(shí)即找到了一條對(duì) str 的交叉引用。

這里的代碼主要是對(duì)機(jī)器碼的解碼和運(yùn)算操作,篇幅較長(zhǎng)不再貼出,讀者有興趣可以自行閱讀。

通過 String XREF 定位變量地址

上文中我們已經(jīng)得到了目標(biāo)函數(shù) proc_apply_resource_actions 中對(duì) str 的引用地址,隨后需要向上回溯定位 task_suspend 函數(shù)的調(diào)用指令:

addr_t find_kernel_task(void) {
    /**
             adrp x8,     str@PAGE
     str --> add  x8, x8, str@PAGEOFF
             bl   _panic
     */
    addr_t str = find_strref("\"shouldn't be applying exception notification", 2, string_base_cstring, false, false);
    if (!str) return 0;
    str -= kerndumpbase;

    // find bl _task_suspend
    addr_t call = step64_back(kernel, str, 0x10, INSN_CALL);
    if (!call) return 0;

    addr_t task_suspend = follow_call64(kernel, call);
    if (!task_suspend) return 0;

    addr_t adrp = step64(kernel, task_suspend, 20*4, INSN_ADRP);
    if (!adrp) return 0;

    addr_t kern_task = calc64(kernel, adrp, adrp + 0x8, 8);
    if (!kern_task) return 0;

    return kern_task + kerndumpbase;
}

整個(gè)過程主要分 3 步:

  1. 回溯找到 bl _task_suspend 的調(diào)用點(diǎn),解出 task_suspend 函數(shù)的地址;

  2. task_suspend 函數(shù)向后搜尋第一條 adrp 指令,即是對(duì) Kernel Task 的尋址;

  3. 從尋址指令中解出 Kernel Task 地址。

我們?cè)倩剡^頭來看 proc_apply_resource_actions 函數(shù)片段:

switch(action) {
    case PROC_POLICY_RSRCACT_THROTTLE:
        /* no need to do anything */
        break;

    case PROC_POLICY_RSRCACT_SUSPEND:
        task_suspend(p->task);
        break;

    case PROC_POLICY_RSRCACT_TERMINATE:
        psignal(p, SIGKILL);
        break;

    case PROC_POLICY_RSRCACT_NOTIFY_KQ:
        /* not implemented */
        break;

    case PROC_POLICY_RSRCACT_NOTIFY_EXC:
        panic("shouldn't be applying exception notification to process!");
        break;
}

編譯時(shí)不一定會(huì)按照 case 的順序生成機(jī)器碼,因此我們需要根據(jù) str XREF 找到 kernelcache 中的實(shí)際表示,一個(gè)簡(jiǎn)單地辦法是在 find_strref("\"shouldn't be applying exception notification", 2, string_base_cstring, false, false) 后打一個(gè)斷點(diǎn)來獲取 str XREF 的文件偏移,再利用二進(jìn)制分析工具反匯編 kernelcache 中的這個(gè)部分。

通過斷點(diǎn)調(diào)試可知 str XREF 位于 0x0000000000f9f084,這應(yīng)該是一條 add 指令:

/**
         adrp x8,     str@PAGE
 str --> add  x8, x8, str@PAGEOFF
         bl   _panic
 */

Mach-O 查看器中打開可以發(fā)現(xiàn),0x0000000000f9f084 確實(shí)是一條 add 指令:

查找使用Kernel Task的函數(shù)是什么

要定位 task_suspend(p->task) 有兩種方式,其一是 p->task 是一個(gè)基于偏移量的結(jié)構(gòu)體成員尋址有明顯特征,第二個(gè)是看函數(shù)調(diào)用前的參數(shù)準(zhǔn)備。在 0xf9f074 處有一個(gè) +16 的偏移量尋址,顯然這是對(duì) p->task 地址的計(jì)算,因此 0xf9f078 處即是 task_suspend(p->task) 的調(diào)用。

所以從 add 指令處向前回溯 3 條指令即可,找到這條 CALL 指令后,即可從中解出 task_suspend的地址:

// find bl _task_suspend
addr_t call = step64_back(kernel, str, 0x10, INSN_CALL);
if (!call) return 0;

addr_t task_suspend = follow_call64(kernel, call);
if (!task_suspend) return 0;

隨后我們從 task_suspend 函數(shù)的起始地址開始向后搜尋第一個(gè) adrp 指令即可找到對(duì) Kernel Task 的 adrp 語句,靜態(tài)分析 adrp & add 即可計(jì)算出 Kernel Task 的地址:

addr_t adrp = step64(kernel, task_suspend, 20*4, INSN_ADRP);
if (!adrp) return 0;

addr_t kern_task = calc64(kernel, adrp, adrp + 0x8, 8);
if (!kern_task) return 0;

注意這里我們得到的依然是 fileoff,需要加上 kerndumpbase 得到虛擬地址:

return kern_task + kerndumpbase;

需要注意的是,如果要在內(nèi)核中讀取 Kernel Task,這個(gè)地址需要加上 kernel_slide 才可以。

到此,關(guān)于“查找使用Kernel Task的函數(shù)是什么”的學(xué)習(xí)就結(jié)束了,希望能夠解決大家的疑惑。理論與實(shí)踐的搭配能更好的幫助大家學(xué)習(xí),快去試試吧!若想繼續(xù)學(xué)習(xí)更多相關(guān)知識(shí),請(qǐng)繼續(xù)關(guān)注億速云網(wǎng)站,小編會(huì)繼續(xù)努力為大家?guī)砀鄬?shí)用的文章!

向AI問一下細(xì)節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權(quán)請(qǐng)聯(lián)系站長(zhǎng)郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI