您好,登錄后才能下訂單哦!
這篇文章主要介紹“查找使用Kernel Task的函數是什么”,在日常操作中,相信很多人在查找使用Kernel Task的函數是什么問題上存在疑惑,小編查閱了各式資料,整理出簡單好用的操作方法,希望對大家解答”查找使用Kernel Task的函數是什么”的疑惑有所幫助!接下來,請跟著小編一起來學習吧!
為了獲取內核信息,我們需要定位到 Kernel Task 的地址,再通過 tfp0 的 kread 讀取內容。要定位 Kernel Task,關鍵是找到獲取 Kernel Task 的代碼,然后嘗試從內存中定位這段代碼,再分析指令解出變量的文件偏移即可。
在 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!" 可用于輔助定位,它在編譯后會被存儲在 __TEXT,__cstring
段,通過在內存中搜索 __TEXT,__cstring
段即可找到字符串地址,我們稱之為 location_str
。
由于 ARM 的取址常常需要 2 條指令完成,為了定位使用 location_str
的代碼,我們需要對代碼段進行靜態分析。當發現寄存器中的值等于 location_str
時即發現了一個交叉引用(XREF),通過這種手段我們便能在內存中定位到語句 panic("shouldn't be applying exception notification to process!")
對應的指令地址。
最快定位到 Kernel Task 的方法是回溯到 task_suspend(p->task)
,在 task_suspend
第一次訪問 p->task
時一定會對 task 尋址,我們可以從尋址指令中解出 task 的文件偏移,再加上內核在內存中的基地址即可得到 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); // ...
從上面的分析可以看出問題的關鍵在于 XREF 的定位,下面我們將分析一種 String Based XREF 定位算法來解決上述問題。
根據 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 和它的擴展打包在一個文件中并以 IMG3 格式存儲(iOS 2 以上)。
在 上一篇文章 中我們介紹了基于 tfp0 的沙盒逃逸方法,通過沙盒逃逸我們可以從 /System/Library/Caches/com.apple.kernelcaches/kernelcache
讀取 kernelcache,它既是當前系統加載的鏡像。
讀者可打開 Undecimus 的 jailbreak.m
文件,搜索 "Initializing patchfinder" 定位到 kernelcache 的加載代碼,加載方法和普通的 Mach-O
文件類似,也是先讀取 Mach Header
和 Load Commands
,然后逐段記錄偏移量,具體代碼在 init_kernel
函數中。
這里不再贅述加載過程,只指出幾個關鍵的全局變量:
cstring_base
和 cstring_size
是 __TEXT,__cstring
段的虛擬地址和長度;
xnucore_base
和 xnucore_size
是 __TEXT,__TEXT_EXEC
段,即代碼段的虛擬地址和長度;
kerndumpbase
是所有段中最小的虛擬地址,即 kernelcache 加載的虛擬基地址,在普通的 Mach-O
文件中這個值一般是 __PAGEZERO
段的虛擬地址 0x100000000,在內核中似乎是 __TEXT
段的虛擬地址 0xFFFFFFF007004000;
kernel
是 kernelcache 在用戶空間的完整映射,即一份完整加載的內核鏡像。
在 Undecimus 中包含一個 find_strref
函數用于定位字符串的 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,引用的序號 n,基準段 string_base,是否完全匹配 full_match
,以及是否位于 __PPLTEXT
段,對于尋找 Kernel Task 的場景,我們的入參如下:
addr_t str = find_strref("\"shouldn't be applying exception notification", 2, string_base_cstring, false, false);
即以 __TEXT,__cstring
為基準,不要求完全匹配,找到第 2 個交叉引用所在的地址。
字符串地址的定位邏輯在 boyermoore_horspool_memmem
函數中:
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; }
我們首先根據調用分析入參:
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; }
haystack = kernel + base + off,即 __TEXT,__cstring
段的起始地址;
hlen = size - off,即 __TEXT,__cstring
段的長度;
needle = string 即待查找字符串指針;
nlen = strlen(string) 即待查找字符串的長度。
在函數的開頭首先維護了一個 bad_char_skip
數組來記錄當匹配失敗時,應當跳過多少個字符來避免無意義的匹配。整個算法采用了倒序掃描的方式,不斷從 haystack[needle_len - 1]
向前掃描并檢查 haystack[i] == needle[i]
,當匹配到 haystack[0]
時如果依然滿足條件,說明找到了字符串的地址,否則根據匹配失敗的字符查 bad_char_skip
表將 haystack 指針后移繼續匹配。
需要注意的是,在匹配成功后得到的字符串地址是相對于用戶空間的 kernelcache 映射 kernel
的,并非是字符串在內核中的實際地址。
在獲取到字符串在用戶空間的地址 str
后,首先需要計算它在 kernelcache 中的虛擬地址:
addr_t str_vmaddr = str - kernel + kerndumpbase;
內核代碼中對 str 的引用一定涉及到對 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
的尾部調用了 return find_reference(str_vmaddr, n, text_base)
,find_reference
對 __TEXT_EXEC,__text
進行了靜態分析,對尋址相關的指令模擬了寄存器運算,主要邏輯在 xref64
函數中,當發現寄存器中的值等于 str_vmaddr
時即找到了一條對 str 的交叉引用。
這里的代碼主要是對機器碼的解碼和運算操作,篇幅較長不再貼出,讀者有興趣可以自行閱讀。
上文中我們已經得到了目標函數 proc_apply_resource_actions
中對 str 的引用地址,隨后需要向上回溯定位 task_suspend
函數的調用指令:
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; }
整個過程主要分 3 步:
回溯找到 bl _task_suspend
的調用點,解出 task_suspend
函數的地址;
從 task_suspend
函數向后搜尋第一條 adrp 指令,即是對 Kernel Task 的尋址;
從尋址指令中解出 Kernel Task 地址。
我們再回過頭來看 proc_apply_resource_actions
函數片段:
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; }
編譯時不一定會按照 case 的順序生成機器碼,因此我們需要根據 str XREF 找到 kernelcache 中的實際表示,一個簡單地辦法是在 find_strref("\"shouldn't be applying exception notification", 2, string_base_cstring, false, false)
后打一個斷點來獲取 str XREF 的文件偏移,再利用二進制分析工具反匯編 kernelcache 中的這個部分。
通過斷點調試可知 str XREF 位于 0x0000000000f9f084,這應該是一條 add 指令:
/** adrp x8, str@PAGE str --> add x8, x8, str@PAGEOFF bl _panic */
在 Mach-O
查看器中打開可以發現,0x0000000000f9f084 確實是一條 add 指令:
要定位 task_suspend(p->task)
有兩種方式,其一是 p->task
是一個基于偏移量的結構體成員尋址有明顯特征,第二個是看函數調用前的參數準備。在 0xf9f074 處有一個 +16 的偏移量尋址,顯然這是對 p->task
地址的計算,因此 0xf9f078 處即是 task_suspend(p->task)
的調用。
所以從 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
函數的起始地址開始向后搜尋第一個 adrp 指令即可找到對 Kernel Task 的 adrp 語句,靜態分析 adrp & add 即可計算出 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;
需要注意的是,如果要在內核中讀取 Kernel Task,這個地址需要加上 kernel_slide 才可以。
到此,關于“查找使用Kernel Task的函數是什么”的學習就結束了,希望能夠解決大家的疑惑。理論與實踐的搭配能更好的幫助大家學習,快去試試吧!若想繼續學習更多相關知識,請繼續關注億速云網站,小編會繼續努力為大家帶來更多實用的文章!
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。