原始碼級除錯的XNU核心
i春秋翻譯小組-FWorldCodeZ
原始碼級除錯的XNU核心
無論你是在開發核心擴充套件,進行漏洞研究,還是還有其他需要進入macOS / iOS核心,XNU,有時你需要附加偵錯程式。當你這樣做時,使用原始碼執行它是非常好的。Damien DeVille,Snare和其他人都寫過這個過程。以下是他們的一些文章:
事情已經發生了一些變化。雖然你可以從以前的工作中完成所有工作,但有些事情沒有得到解決。所以讓我們從頭開始詳細介紹。
以下是我們的目標:
- 從macOS環境除錯macOS 10.13.6核心(10.14核心源尚未釋出)
- 使用虛擬機器目標,這樣我們就不必拖動兩個單獨的Mac
- 不僅有核心符號,還有核心原始碼
- 能夠從目標機器的記憶體中漂亮地列印結構
- 在斷點處,顯示:
- 來源清單
- 註冊內容
- 回溯
- 當前執行緒堆疊
購物清單
在我們做之前,雖然這裡有你需要的東西的購物清單。
- 來自Mac App Store的Xcode(主要用於lldb)
- macOS High Sierra 10.13.6安裝程式
- Kernel Debug Kit build 17G65
- XNU source code for xnu-4570.71.2
- 最近的版本VMware Fusion。其他虛擬化工具可能有效,但我們在這裡使用VMware Fusion Pro 11。
- Snare的excellent Voltron lldb UI
- lldb 的x86_64 target definition file
虛擬機器
建立你的macOS來賓計算機。
- 我推薦一個簡單的使用者名稱和密碼,如“admin”和“a”
- 你需要啟用SSH並可能自動使用者登入。
- 請務必在guest虛擬機器中安裝VMware工具
- 檢查作業系統版本
sw_vers
檢查macOS構建和核心版本
為了便於SSH連線到機器,你可能需要使用VMware的DHCP以及主機名來為其提供靜態IP地址/etc/hosts
。這是如何做。
- 獲取虛擬機器網路介面的MAC地址(通常
en0
)。我的是00:0c:29:a5:fd:3a
- 編輯
/Library/Preferences/VMware Fusion/vmnet8/dhcpd.conf
(vmnet1
如果你沒有使用VMware的NAT,請替換) - 為VM的靜態DHCP租約新增一個節:
####### VMNET DHCP Configuration. End of "DO NOT MODIFY SECTION" #######
host gargleblaster{
hardware ethernet 00:0c:29:a5:fd:3a;
fixed-address 192.168.44.10;
}
- 將VM的主機名新增到
/etc/hosts
主機上 - 如果你願意,可以在Sharing.prefpane以及命令列中使用設定來賓VM的主機名
scutil --set HostName
- 使用ssh金鑰將ssh金鑰複製到vm
ssh-copy-id
- 關閉VM,退出並重新啟動VMware,然後啟動VM以測試所有內容。
- 將VM引導至恢復模式並使用禁用SIP
csrutil
VMware的GDB stub
除錯XNU的官方方法是使用內建的除錯存根,使用核心除錯協議或KDP進行通訊。它可以在各種傳輸上工作,包括序列,FireWire,我相信Thunderbolt。但是對於除錯VM,你要使用UDP,這對於核心除錯來說是超級好的。lldb
經常不同步或失去與除錯伺服器的聯絡,並且核心處於永久停止狀態。我認為這是因為除錯伺服器是核心本身的一部分,結合了UDP不可靠的特性。所以核心停止,停止與偵錯程式通訊,然後lldb
放棄。
更可靠的機制是VMware提供的“硬體”除錯工具。這使VM可以模擬核心下的硬體偵錯程式。在這種情況下,核心在自己的除錯中不起作用; 它甚至沒有“意識到”它正在被除錯。這種方法不是100%可靠,但它通常比KDP更穩定。你也可以(通常)使用^ C中斷,就好像你已連線到正常的使用者空間程序一樣。設定很簡單:
繼續關閉VM。然後編輯.vmx
在.vmwarevm
捆綁包中找到的檔案。將以下第1行新增到檔案中:
debugStub.listen.guest32 = "TRUE"
debugStub.listen.guest64 = "TRUE"
如果要從主機以外的計算機(例如另一個來賓VM)進行除錯,則可以新增遠端偵聽器:
debugStub.listen.guest32.remote = "TRUE"
debugStub.listen.guest64.remote = "TRUE"
核心除錯工具包
從Apple Developer Portal下載核心除錯工具包。下載與VM中的macOS構建相匹配的KDK構建版本至關重要。我相信你需要使用Apple ID登入開發人員入口網站,但我認為你不需要為開發者帳戶付費。
你需要在主機和來賓中安裝KDK。從技術上講,只需將開發核心複製到guest虛擬機器即可,但只需簡單地安裝整個KDK即可。
在guest虛擬機器中,將開發核心從KDK位置複製到核心所在的目錄:
$ sudo cp /Library/Developer/KDKs/KDK_10.13.6_17G65.kdk/System/Library/Kernels/kernel.development /System/Library/Kernels/
由於系統實際上並不啟動核心,而是預先連結的核心快取,因此需要使現有核心快取無效,從而導致重建。該kextcache
命令執行此操作。它有很多選項,但為了簡單起見,你可以告訴它“重新啟動你在啟動捲上所知道的一切” 2:
$ sudo kextcache -i /
值得在KDK中尋找安裝它的東西。看看/Library/Developer/KDKs/KDK_10.13.6_17G65.kdk
。在其中,你會發現很多核心和kexts的符號包,這非常好。不過,你不必擔心它們。LLDB將使用Spotlight通過UUID找到它們,並在需要時載入它們。真正有趣的是核心dSYM。其中有大量的Python lldb巨集。LLDB載入其中一些,但大多數不載入。它們大部分都沒有記錄,但有些非常有用。我們稍後會看幾下。
引導args
有些指南會讓你在訪客中設定各種啟動引數,例如kcsuffix
。根據我的經驗,你無需執行任何特殊操作即可啟動開發核心。只要它存在(或者更重要的是核心快取存在),它將優先於釋出核心。重新啟動VM並檢查核心版本以確保啟動了Development核心:
admins-Mac:~ admin$ uname -a
Darwin admins-Mac.local 17.7.0 Darwin Kernel Version 17.7.0: Thu Jun 21 22:53:14 PDT 2018; root:xnu-4570.71.2~1/DEVELOPMENT_X86_64 x86_64
你還可以在debug=
引導arg 3 4中設定各種除錯標誌,但不需要它們。它們不會以任何方式影響VMware的gdb伺服器存根。但是,如果你在VMWare的除錯存根之外使用kdp,它們可能會很有用。標誌是一個位域,其值與OR一起。例如,debug=0x1
告訴作業系統在啟動時暫停並等待偵錯程式。可能一組有用的標誌開頭是debug=0x141
。Apple 在這裡有部分除錯標誌列表,如果找不到滿足你需求的除錯標誌,osfmk/kern/debug.c
可能是你的下一個最佳參考。你還可以在核心源中檢查除錯引導arg的其他位置grep其他:
-==< [email protected]:~/src/xnu-4570.71.2 >==-
(0) $ grep -rn 'PE_parse_boot_argn\(\"debug\"' .
設定LLDB
為了lldb
理解我們正在除錯的東西,我們需要給它一些配置。如果你還沒有,請建立一個~/.lldb
目錄來儲存某些lldb
特定檔案。~/.lldbinit
如果你還沒有空檔案,也要建立一個空檔案。
把x86_64_target_definition.py
你先前下載的內容放在這裡。當你進行任何其他一般的lldb調整或python指令碼時,你也可以進入這裡。然後你可以從你的來源獲取它們.lldbinit
。
有一些構建/核心版本特定的配置,所以它不能都是共同的.lldbinit
。我喜歡有一個git repo來跟上我的各種lldb init指令碼,但是現在,讓我們假設你正在建立~/.lldb/kernel-debugging
。
首先,我們需要告訴lldb
我們正在除錯x86_64目標。lldb
非常靈活,可以除錯各種目標架構,甚至是之前從未聽說過的架構。目標定義檔案描述了該體系結構。不應該lldb
知道開箱即用的x86_64?是的,它通常會,但不幸的是,我們要連線的遠端gdb存根不能告訴我們的偵錯程式它正在除錯什麼架構。所以我們提前告訴偵錯程式。將此新增到特定於核心的lldb init指令碼:
settings set plugin.process.gdb-remote.target-definition-file ~/.lldb/x86_64_target_definition.py
說到x86,你可能想要將反彙編的風格設定為英特爾,而不是AT&T。你懂。因為你是一名專業人士:
settings set target.x86-disassembly-flavor intel
還記得dSYM包中的那些python指令碼嗎?現在我們需要告訴lldb
他們自動載入它們(或者至少是它所需要的那些載入它們)。
settings set target.load-script-from-symbol-file true
KDK參考核心中的dSYM源自Apple構建核心時的任何位置。這通常就像是/BuildRoot/Library/Caches/com.apple.xbs/something/something
。當然lldb
無法在該路徑上找到核心原始碼(除非你把它們放在那裡),所以我們需要告訴它要翻譯。以下設定適用於此構建,但其他核心的路徑可能不同。查詢來自的錯誤訊息lldb
。
settings set target.source-map /BuildRoot/Library/Caches/com.apple.xbs/Sources/xnu/xnu-4570.71.2 /Users/zach/src/xnu-4570.71.2
我們需要lldb
從核心dSYM載入一些超級有用的巨集。應該拿起來xnu.py
,但還有更多memory.py
。
command script import "/Library/Developer/KDKs/KDK_10.13.6_17G65.kdk/System/Library/Kernels/kernel.development.dSYM/Contents/Resources/Python/lldbmacros/memory.py"
你可以配置許多其他設定lldb
,其中大多數預設為合理的設定。檢查help settings
列表。請特別注意名稱中帶有“darwin”的設定。在某些情況下,可能很難找出可用於設定的可能值。在這種情況下,諮詢lldb
來源可能是最容易的。
此時,你的.lldb/kernel-debugging
指令碼應該類似於:
#幫助lldb弄清楚我們正在除錯x86_64
settings set plugin.process.gdb-remote.target-definition-file ~/.lldb/x86_64_target_definition.py
#使用合理的反彙編語法
settings set target.x86-disassembly-flavor intel
#告訴載入隱藏在.dSYM檔案中的任何lldb指令碼和巨集
settings set target.load-script-from-symbol-file true
#告訴lldb源目錄到底在哪裡
settings set target.source-map /BuildRoot/Library/Caches/com.apple.xbs/Sources/xnu/xnu-4570.71.2 /Users/zach/src/xnu-4570.71.2
#這應該在我們設定目標可執行檔案時自動載入
#命令指令碼匯入
"/Library/Developer/KDKs/KDK_10.13.6_17G65.kdk/System/Library/Kernels/kernel.development.dSYM/Contents/Resources/Python/lldbmacros/xnu.py"
#這似乎沒有自動載入,所以我們在這裡載入它。
command script import "/Library/Developer/KDKs/KDK_10.13.6_17G65.kdk/System/Library/Kernels/kernel.development.dSYM/Contents/Resources/Python/lldbmacros/memory.py"
# 載入我們將要除錯的核心二進位制檔案。
target create /Library/Developer/KDKs/KDK_10.13.6_17G65.kdk/System/Library/Kernels/kernel.development
啟動VM後,如果執行lldb
(沒有目標二進位制檔案),則會得到基本(lldb)
提示。現在使用以下內容獲取核心除錯指令碼
command source ~/.lldb/kernel-debugging
並留意任何錯誤:
如果你已經正確設定了一切,你應該能夠連線gdb-remote
命令:
(lldb) gdb-remote 8864
你應該進入核心,可能是在空閒執行緒的中間。假設lldb
發現核心,符號和原始碼沒問題,你應該在斷點處看到一個簡短的原始碼片段:
我不確定是什麼決定了偵錯程式進入的執行緒。我懷疑這只是機會。如果你的計算機繁忙,你可能會進入一個非空閒的執行緒,甚至可能在核心擴充套件中執行。在這種情況下,你將在一個沒有原始碼的地方。嘗試在經常呼叫的核心函式上設定斷點,例如dofileread()
並繼續。
(lldb) breakpoint set -n dofileread
如果你正確地破壞了核心而不是擴充套件,那麼你應該看到原始碼。
此時應凍結VM。試著c
繼續跑步; VM應該再次互動。看看^ C是否中斷。
要從VM分離,請執行以下操作:
(lldb) c
Process 1 resuming
(lldb) detach
Process 1 detached
(lldb) target delete
1 targets deleted.
(lldb) quit
我發現我的lldb命令歷史記錄無法可靠地儲存5,除非我經歷了分離,刪除目標和退出的整個過程。在分離之前,你可能希望清除任何斷點breakpoint delete
。分離應該清除斷點,但根據我的經驗它並不總是,然後你的目標可以隨機掛起。
漂亮的Printing 結構
如果你能夠按名稱打破函式並顯示原始碼,那麼你應該將所有設定為漂亮的Printing結構和來自核心記憶體的其他物件。這對於具有大量C巨集和條件定義的非常大的結構特別有用。列印它們可以lldb
讓你輕鬆檢視結構的實際組成方式。
這是一個簡單的例子。設定斷點dofileread()
:
(lldb) breakpoint set -n dofileread
Breakpoint 1: where = kernel`dofileread + 51 at sys_generic.c:359, address = 0xffffff8015f46eb3
(lldb) c
偵錯程式應該立即打到你的斷點; 檔案讀取是一種超常用的操作。當它發生時,你應該看到lldb
函式原型的檢視:
kernel`dofileread(ctx=0xffffff8ce861bf00, fp=0xffffff8028c332e8, bufp=140465093751296, nbyte=65536, offset=-1, flags=0, retval=<unavailable>)
嘗試用print
命令列印一些函式引數。你會看到它ctx
是型別vfs_context_t
(實際上是一個typedefed指標),並且fp
是型別fileproc *
。要列印這些結構,你需要將lldb
它們作為指標進行解釋並取消引用它們:
(lldb) print ctx
(vfs_context_t) $44 = 0xffffff8ce861bf00
(lldb) print *(vfs_context_t)ctx
(vfs_context) $45 = {
vc_thread = 0xffffff802a029a10
vc_ucred = 0xffffff8024d14520
}
(lldb) print fp
(fileproc *) $46 = 0xffffff8028c332e8
(lldb) print *(fileproc *)fp
(fileproc) $47 = {
f_flags = 0
f_iocount = 1
f_fglob = 0xffffff802ffc9960
f_wset = 0x0000000000000000
}
這是它的實際截圖:
建立Voltron
所以我們用符號和原始碼成功除錯核心。但是lldb
並沒有給我們太多的使用者介面。如果我們能夠看到更多的上下文,如暫存器,堆疊,指令指標的反彙編,當前執行緒的回溯,那就太好了。你懂。偵錯程式做的事情。那麼,而不是使用者介面,lldb
為你提供API。我想如果我必須在半實現的使用者介面和非常好的API之間做出選擇,我會選擇後者。這就是我們如何獲得Snare的Voltron。
如果你還沒有,請從https://github.com/snare/voltron獲取Voltron 。你很想安裝它pip
,但不要。使用包含的install.sh
shell指令碼代替6。此指令碼指出你安裝的偵錯程式以及它們使用的Python版本。它還有助於解決Voltron的six
依賴與使用Python系統安裝的依賴之間的衝突。
完成安裝後,它應該在你的附加中新增類似於以下內容的行.lldbinit
:
command script import /Users/zach/Library/Python/2.7/lib/python/site-packages/voltron/entry.py
確保它在那裡。還要確保將bin
上面使用的任何Python路徑下的目錄新增到shell中$PATH
。例如:
export PATH=$PATH:$HOME/Library/Python/2.7/bin
現在,在單獨的終端視窗(或tmux
窗格或其他)中,你可以啟動單獨的Voltron檢視。在主窗格中,lldb
正常啟動。然後根據你的選擇配置你的voltron窗格。voltron view registers
例如,從shell執行中,可以檢視在每個斷點處更新的暫存器。這是幫助輸出:
$ voltron view -h
usage: voltron view [-h]
{backtrace,t,bt,back,registers,r,reg,register,breakpoints,b,bp,break,command,c,cmd,memory,m,mem,disasm,d,dis,stack,s,st}
...
optional arguments:
-h, --help show this help message and exit
views:
valid view types
{backtrace,t,bt,back,registers,r,reg,register,breakpoints,b,bp,break,command,c,cmd,memory,m,mem,disasm,d,dis,stack,s,st}
additional help
backtrace (t,bt,back)
backtrace view
registers (r,reg,register)
register values
breakpoints (b,bp,break)
breakpoints view
command (c,cmd) run a command each time the debugger host stops
memory (m,mem) display a chunk of memory
disasm (d,dis) disassembly view
stack (s,st) display a chunk of stack memory
這是我的設定。為巨型截圖道歉。
就是這樣。你正在使用符號,源和Voltron除錯macOS核心。
務必向我發推文任何評論或更正。
-
我相信你真的只需要64位除錯存根,但我添加了兩者。
-
如果要解除安裝開發核心並返回釋出核心,則需要:
- 刪除以下內容/System/Library/
Kernels/kernel.development
PrelinkedKernels/prelinkedkernel.development
Caches/com.apple.kext.caches/Startup/kernelcache.development
- 像以前一樣使核心快取失效
-
你將嘗試啟用不可遮蔽中斷或NMI除錯標誌,以便你可以通過按鍵暫停核心。我會救你的麻煩。它不適用於VM。你的主機每次都會捕獲NMI。我驚慌失措地試圖解決這個問題。我認為沒有辦法模擬VM中的NMI。
-
除錯標誌不會以任何方式影響VMware的除錯存根。同樣,核心甚至都不知道它。它們僅配置核心自己的KDP除錯存根。也就是說,它們可以很有用,因為它們為你提供了第二種附加偵錯程式的方法。例如,如果系統在斷點之間發生混亂,你通常無法從VMware存根中反省恐慌情境。但是你可以將第二個
lldb
會話附加到KDP存根以檢視恐慌情況。 -
一旦你弄清楚各種
lldb
咒語,你就不想再想出來了。所以你想要你的命令歷史。 - 他親自告訴我這件事。我認為他讓Voltron保持在PyPI只是為了搞砸新手。
作者:shadowfile
翻譯:I春秋翻譯小組-FWorldCodeZ
責任編輯:F0rmat
翻譯來源:https://shadowfile.inode.link/blog/2018/10/source-level-debugging-the-xnu-kernel