1. 程式人生 > >原始碼級除錯的XNU核心

原始碼級除錯的XNU核心

i春秋翻譯小組-FWorldCodeZ

原始碼級除錯的XNU核心

無論你是在開發核心擴充套件,進行漏洞研究,還是還有其他需要進入macOS / iOS核心,XNU,有時你需要附加偵錯程式。當你這樣做時,使用原始碼執行它是非常好的。Damien DeVilleSnare和其他人都寫過這個過程。以下是他們的一些文章:

事情已經發生了一些變化。雖然你可以從以前的工作中完成所有工作,但有些事情沒有得到解決。所以讓我們從頭開始詳細介紹。

以下是我們的目標:

  • 從macOS環境除錯macOS 10.13.6核心(10.14核心源尚未釋出)
  • 使用虛擬機器目標,這樣我們就不必拖動兩個單獨的Mac
  • 不僅有核心符號,還有核心原始碼
  • 能夠從目標機器的記憶體中漂亮地列印結構
  • 在斷點處,顯示:
    • 來源清單
    • 註冊內容
    • 回溯
    • 當前執行緒堆疊

購物清單

在我們做之前,雖然這裡有你需要的東西的購物清單。

虛擬機器

建立你的macOS來賓計算機。

  • 我推薦一個簡單的使用者名稱和密碼,如“admin”和“a”
  • 你需要啟用SSH並可能自動使用者登入。
  • 請務必在guest虛擬機器中安裝VMware工具
  • 檢查作業系統版本sw_vers
    。我們正在尋找17G65和xnu版本xnu-4570.71.2:

 

​                                          檢查macOS構建和核心版本

為了便於SSH連線到機器,你可能需要使用VMware的DHCP以及主機名來為其提供靜態IP地址/etc/hosts。這是如何做。

  1. 獲取虛擬機器網路介面的MAC地址(通常en0)。我的是00:0c:29:a5:fd:3a
  2. 編輯/Library/Preferences/VMware Fusion/vmnet8/dhcpd.confvmnet1如果你沒有使用VMware的NAT,請替換)
  3. 為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;
}
  1. 將VM的主機名新增到/etc/hosts主機上
  2. 如果你願意,可以在Sharing.prefpane以及命令列中使用設定來賓VM的主機名 scutil --set HostName
  3. 使用ssh金鑰將ssh金鑰複製到vm ssh-copy-id
  4. 關閉VM,退出並重新啟動VMware,然後啟動VM以測試所有內容。
  5. 將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 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.shshell指令碼代替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核心。

務必向我發推文任何評論或更正。


  1. 我相信你真的只需要64位除錯存根,但我添加了兩者。

  2. 如果要解除安裝開發核心並返回釋出核心,則需要:

    1. 刪除以下內容/System/Library/
    • Kernels/kernel.development
    • PrelinkedKernels/prelinkedkernel.development
    • Caches/com.apple.kext.caches/Startup/kernelcache.development
    1. 像以前一樣使核心快取失效
  3. 你將嘗試啟用不可遮蔽中斷或NMI除錯標誌,以便你可以通過按鍵暫停核心。我會救你的麻煩。它不適用於VM。你的主機每次都會捕獲NMI。我驚慌失措地試圖解決這個問題。我認為沒有辦法模擬VM中的NMI。

  4. 除錯標誌不會以任何方式影響VMware的除錯存根。同樣,核心甚至都不知道它。它們僅配置核心自己的KDP除錯存根。也就是說,它們可以很有用,因為它們為你提供了第二種附加偵錯程式的方法。例如,如果系統在斷點之間發生混亂,你通常無法從VMware存根中反省恐慌情境。但是你可以將第二個lldb會話附加到KDP存根以檢視恐慌情況。

  5. 一旦你弄清楚各種lldb咒語,你就不想再想出來了。所以你想要你的命令歷史。

  6. 他親自告訴我這件事。我認為他讓Voltron保持在PyPI只是為了搞砸新手。

作者:shadowfile

翻譯:I春秋翻譯小組-FWorldCodeZ

責任編輯:F0rmat

翻譯來源:https://shadowfile.inode.link/blog/2018/10/source-level-debugging-the-xnu-kernel