macOS内核调试介绍

lucywang 技术 2018年12月21日发布
Favorite收藏

导语:对于技术大咖来说,调试内核是一件非常有趣且具有挑战的事情,除非调试过程中发生严重的异常,否则在一般情况下,他们是不会轻易放弃的。不过,技术开发商也知道这一点,他们也在想法设法让内核调试变得越来越难。

对于技术大咖来说,调试内核是一件非常有趣且具有挑战的事情,除非调试过程中发生严重的异常,否则在一般情况下,他们是不会轻易放弃的。不过,技术开发商也知道这一点,他们也在想法设法让内核调试变得越来越难。比如苹果公司就已经采取了一些措施,让macOS的内核调试变得越来越难,首先,将有关debug引导参数的文档信息隐藏在lock和key下,然后将内核调试工具包转移到Developer Account-only Downloads部分。虽然目前互联网上有很多关于在macOS上调试内核的文章,但其中很多都已经不实用了,比如有的文章会告诉调试人员,通过设置的NVRAM启动参数,但这个方法已经不再有效。甚至还有的文章停留在 “现在调试人员应该设置一个有效的调试会话”这样的层面!在这篇文章中,我尽我所能为大家提供最准确和最新的调试信息,包括正确的调试命令,正确的boot-args参数,当然还有具体的调试示例。

开始在macOS上进行内核调试

调试时,要做的第一件事就是进行调试环境的设置和对测试设备的配置,调试人员需要有一个能进行调试的内核对象(在本文中,我使用的是iMac 2011作为调试器)和一个用于调试的设备(本文使用的是MacBook Pro 2009)。虽然调试人员可以使用我在以下讨论的各种方式将两者连接起来,但在本文的示例中,最好的方法(也是最可靠的)似乎是通过两者之间的火线接口(firewire)(这是因为我的两台设备都有firewire端口)而不是USB-C。

硬件部分设置好以后,我们还需要运行一些软件。理论上调试人员可以调试RELEASE内核,但调试人员不是技术大咖,只是一个初学者时,调试Development内核就会轻松得多。默认情况下,macOS里会自带一个位于/System/Library/ kernel /kernel中的RELEASE融合内核,其中kernel是Mach-O 64-bit executable x86_64。所以,我们可以通过导航到Apple Developer门户并下载内核调试工具包,来获得macOS版本的Development内核。令人惊讶的是,苹果公司只是简单将该套件置于正常的、免费的Apple开发者账户( Apple Developer Account)的lock中,按着我原来的想法,我还以为苹果公司是将其置于付费的Apple开发者账户下载之中。

无论如何,一旦调试人员进入到Apple Developer Portal下载部分,调试人员就将看到如下内容。

1.png

这个过程非常的重要,这是因为调试人员会在以上的这个列表中,找到适合于他们的特定macOS版本的内核调试工具包,下载后,里面包含调试人员将在调试中启动的内核,如果内核与调试人员的macOS版本不匹配,它将无法启动,甚至对对调试人员的文件、计算机等造成损害。

为macOS版本找到合适的内核调试工具包(Kernel Debug Kit)

要找到正确的内核调试工具包,调试人员必须知道他们的macOS版本和实际的内部版本号。调试人员可以很容易的看到你正在运行的macOS版本,只要打开苹果的图标,按下“关于这台Mac”,然后在窗口中阅读出现的版本信息,例如“10.13.6版本”。

对于实际的内部版本号,调试人员可以单击“关于这个Mac”窗口中的“版本”标签,也可以运行终端命令sw_vers | grep BuildVersion。在本文的示例中,运行sw_vers | grep BuildVersion命令会输出 “BuildVersion: 17G65”。

Last login: Sun Dec  2 03:58:16 on ttys000
Isabella:~ geosn0w$ sw_vers | grep BuildVersion
BuildVersion:    17G65
Isabella:~ geosn0w$

所以,就本文而言,我正在运行的macOS High Sierra(10.13.6)的版本号为17G65。查看刚刚下载的工具包,我可以立即找到和我的版本适应的工具,这样我就可以下载包含安装文件的.DMG文件,文件非常少。

3.png

准备调试器以供调试器调试

在调试器(即要调试其内核的设备)上下载调试工具包后,双击安装DMG文件。在DMG文件中,调试人员将找到一个名为KernelDebugKit.pkg的文件,双击它并按照安装向导进行操作。安装完毕,会出现一个询问调试人员macOS登录密码的界面。你可以不理会这个询问,但请不要将此安装程序删掉,调试人员以后还会需要它。

安装完成后的界面看起来如下:

4.png

安装完成后,调试人员会被导航到/Library/Developer/KDKs。在那里,调试人员将获得一个名为KDK_YOUR_VERSION_BUILDNUMBER.kdk的文件夹。在本文的示例中,该文件夹名为KDK_10.13.6_17G65.kdk。打开文件夹,调试人员会在其中找到另一个名为“System”的文件夹。导航到文件夹后,先进入“Library”,然后进入“Kernels”。在该文件夹中,调试人员将找到一些内核二进制文件,一些Xcode调试符号文件(.dSYM)等。一般情况下,调试人员对名为kernel.development的文件感兴趣。

将kernel.development复制并粘贴到在RELEASE内核二进制文件中运行的/ System / Library / Kernels /中。此时,调试人员的macOS上应该安装应该有两个内核,一个是RELEASE内核,另一个是DEVELOPMENT内核。

禁用调试器上的SIP

为了正确调试,调试人员可能需要在要调试其内核的计算机上禁用SIP(系统完整性保护)。为此,调试人员需在恢复模式下重新启动计算机。要做到这一点,必须重新启动设备,当调试人员看到启动界面打开时,按CMD + R,等待几秒钟,使启动进入恢复模式用户界面,然后点击“Terminal”继续。

在恢复终端中,写入csrutil disable,然后重新启动计算机,此时只需正常启动即可。

设置正确的NVRAM boot-args

由于苹果公司一直在更新boot-args,因此调试人员在互联网上找到的设置参数可能已经没有用了。以下boot-args已经过测试,确定可以在2018年的macOS High Sierra运行。

注意!以下boot-args会假设调试人员直接通过火线接口或利用“Thunderbolt”(雷电)适配器通过火线接口执行操作。

如果调试人员在较旧的Mac上,员直接通过火线接口,那在终端中要运行的命令如下所示。

sudo nvram boot-args="debug=0x8146 kdp_match_name=firewire fwdebug=0x40 pmuflags=1 -v"

如果调试人员是利用“Thunderbolt”(雷电)适配器通过火线接口执行操作,那在终端中要运行的命令如下所示。

sudo nvram boot-args="debug=0x8146 kdp_match_name=firewire fwkdp=0x8000 fwdebug=0x40 pmuflags=1 -v"

区别在于fwkdp=0x8000会告诉OFireWireFamily.kext::AppleFWOHCI_KDP使用非内置firewire < – > thunderbolt适配器进行调试会话。

此时,调试器可以在重新启动后进行调试,但是让我先解释一下启动参数所代表的具体含义:

· debug=0x8146:代表调试员可以进行调试了,并允许他们按下电源按钮来触发NMI, NMI代表不可屏蔽中断(即CPU不能屏蔽)   ,它用于调试器连接;

· kdp_match_name=firewire:允许调试人员通过FireWireKDP进行调试;

· fwkdp=0x8000 :正如我之前解释的那样,该参数是告诉kext使用thunderbolt连接到火线接口,如果你是直接使用火线接口,请不要设置该参数;

· fwdebug=0x40:通过启用AppleFWOHCI_KDP驱动程序,得出更详细的输出信息,这对于排除调试过程中的故障很有用;

· muflags=1 :它会禁用看门狗定时器,看门狗定时器(WDT,Watch Dog Timer)是单片机的一个组成部分,它实际上是一个计数器,一般给看门狗一个数字,程序开始运行后看门狗开始倒计数。如果程序运行正常,过一段时间CPU应发出指令让看门狗复位,重新开始倒计数。如果看门狗减到0就认为程序没有正常工作,强制整个系统复位。

· -v :这个参数虽然最简单的,但它会命令计算机启动时要显示详细设备信息,而不是像平常那样只显示苹果的徽标和启动进度条。不仅在调试人员进行调试时,对于故障排除非常有用,而且在Bootloop(无限重启)时也很有用。

除了本文要设置的这些引导参数之外,macOS还支持更多在/osfmk/kern/debug.h中定义的参数,具体的参数我已在下面列出,这些参数来自xnu-4570.41.2。

.../* Debug boot-args */#define DB_HALT        0x1//#define DB_PRT          0x2 -- obsolete#define DB_NMI        0x4
#define DB_KPRT        0x8
#define DB_KDB        0x10
#define DB_ARP          0x40
#define DB_KDP_BP_DIS   0x80//#define DB_LOG_PI_SCRN  0x100 -- obsolete#define DB_KDP_GETC_ENA 0x200#define DB_KERN_DUMP_ON_PANIC        0x400 /* Trigger core dump on panic*/#define DB_KERN_DUMP_ON_NMI        0x800 /* Trigger core dump on NMI */#define DB_DBG_POST_CORE        0x1000 /*Wait in debugger after NMI core */#define DB_PANICLOG_DUMP        0x2000 /* Send paniclog on panic,not core*/#define DB_REBOOT_POST_CORE        0x4000 /* Attempt to reboot after
                        * post-panic crashdump/paniclog
                        * dump.
                        */#define DB_NMI_BTN_ENA      0x8000  /* Enable button to directly trigger NMI */#define DB_PRT_KDEBUG       0x10000 /* kprintf KDEBUG traces */#define DB_DISABLE_LOCAL_CORE   0x20000 /* ignore local kernel core dump support */#define DB_DISABLE_GZIP_CORE    0x40000 /* don't gzip kernel core dumps */#define DB_DISABLE_CROSS_PANIC  0x80000 /* x86 only - don't trigger cross panics. Only
                                         * necessary to enable x86 kernel debugging on
                                         * configs with a dev-fused co-processor running
                                         * release bridgeOS.
                                         */#define DB_REBOOT_ALWAYS        0x100000 /* Don't wait for debugger connection */...

准备调试器设备

现在既然调试器准备好了,我们就要准备配置运行调试器的设备。为此,我使用了另一台运行El Capitan的macOS设备,但这并不重要。还记得我们在调试器上安装的内核调试工具包吗?我们也需要在调试器设备上安装它。不同之处在于我们不会移动内核,也不会在调试器上设置任何引导参数。我们只是要用到内核而已,因为我们将使用lldb来执行调试。如果调试人员熟悉GDB,则不必担心,这里有一个GDB -> LLDB命令表

注意:即使内核调试工具包没有运行与调试器相同的macOS版本,调试人员应该在调试器上安装相同的macOS内核调试工具包,因为我们不会在调试器上启动任何内核。

安装工具包后,就可以调试内核了。

调试内核

首先,重新启动调试器。此时,调试人员将看到它是在文本模式控制台被启动的,该控制台会输出详细的启动信息。等到屏幕上显示“DSMOS出现!”,然后按一下电源按钮,注意不要按住它不放。在调试器上,调试人员将看到它正在等待连接。

在调试器设备上的连接过程

打开终端窗口并启动fwkdp -v,这是FireWire KDP工具,它将侦听火线接口并将数据重定向到本地主机,这样调试人员就可以将KDP目标设置为localhost或127.0.0.1调试人员也应该得到类似于下面的输出内容。

MacBook-Pro-van-Mac:~ mac$ fwkdp -vFireWire KDP Tool (v1.6)Matched on device 0x00002403
Created plugin interface 0x7f9e50c03548 with result 0x00000000
Created device interface 0x7f9e50c0d508 with result 0x00000000
Opened device interface 0x7f9e50c0d508 with result 0x00000000
Added callback dispatcher with result 0x00000000
Created pseudo address space 0x7f9e50c0d778 at 0xf0430000
Address space enabled.
2018-12-02 05:51:05.453 fwkdp[5663:60796] CFSocketSetAddress listen failure: 102
Created KDP socket listener 0x7f9e50c0d940 with result 0
KDP Proxy and CoreDump-Receive dual mode active.
Use 'localhost' as the KDP target in gdb.
Ready.

现在,在不关闭此窗口的情况下,打开另一个终端窗口并启动lldb调试器,方法是将调试器上安装的kernel.development文件作为内核调试工具包的一部分传递给它。请记住,内核可以在/Library/Developer/KDKs/中找到。在那里,调试人员将会找到一个名为KDK_YOUR_VERSION_BUILDNUMBER.kdk的文件夹。在本文的示例中,该文件夹名为KDK_10.13.6_17G65.kdk,而我所需要的完整内核路径是/Library/Developer/KDKs/KDK_10.13.6_17G65.kdk/System/Library/Kernels/kernel.development。

在本文的示例中,新终端窗口中的命令是xcrun lldb /Library/Developer/KDKs/KDK_10.13.6_17G65.kdk/System/Library/ kernel /kernel.development。

Last login: Sun Dec  2 10:37:51 on ttys000
MacBook-Pro-van-Mac:~ mac$ xcrun lldb /Library/Developer/KDKs/KDK_10.13.6_17G65.kdk/System/Library/Kernels/kernel.development
(lldb) target create "/Library/Developer/KDKs/KDK_10.13.6_17G65.kdk/System/Library/Kernels/kernel.development"
warning: 'kernel' contains a debug script. To run this script in this debug session:
command script import "/Library/Developer/KDKs/KDK_10.13.6_17G65.kdk/System/Library/Kernels/kernel.development.dSYM/Contents/Resources/DWARF/../Python/kernel.py"

To run all discovered debug scripts in this session:

    settings set target.load-script-from-symbol-file true

Current executable set to '/Library/Developer/KDKs/KDK_10.13.6_17G65.kdk/System/Library/Kernels/kernel.development' (x86_64).

如上所示,lldb表示“内核”所包含的调试脚本。在现在打开的lldb窗口中,运行settings set target.load-script-from-symbol-file true来运行脚本。

Last login: Sun Dec  2 10:37:51 on ttys000
MacBook-Pro-van-Mac:~ mac$ xcrun lldb /Library/Developer/KDKs/KDK_10.13.6_17G65.kdk/System/Library/Kernels/kernel.development
(lldb) target create "/Library/Developer/KDKs/KDK_10.13.6_17G65.kdk/System/Library/Kernels/kernel.development"
warning: 'kernel' contains a debug script. To run this script in this debug session:
command script import "/Library/Developer/KDKs/KDK_10.13.6_17G65.kdk/System/Library/Kernels/kernel.development.dSYM/Contents/Resources/DWARF/../Python/kernel.py"

To run all discovered debug scripts in this session:

    settings set target.load-script-from-symbol-file true

Current executable set to '/Library/Developer/KDKs/KDK_10.13.6_17G65.kdk/System/Library/Kernels/kernel.development' (x86_64).
(lldb) settings set target.load-script-from-symbol-file true

Loading kernel debugging from /Library/Developer/KDKs/KDK_10.13.6_17G65.kdk/System/Library/Kernels/kernel.development.dSYM/Contents/Resources/DWARF/../Python/kernel.py
LLDB version lldb-360.1.70
settings set target.process.python-os-plugin-path "/Library/Developer/KDKs/KDK_10.13.6_17G65.kdk/System/Library/Kernels/kernel.development.dSYM/Contents/Resources/DWARF/../Python/lldbmacros/core/operating_system.py"
settings set target.trap-handler-names hndl_allintrs hndl_alltraps trap_from_kernel hndl_double_fault hndl_machine_check _fleh_prefabt _ExceptionVectorsBase _ExceptionVectorsTable _fleh_undef _fleh_dataabt _fleh_irq _fleh_decirq _fleh_fiq_generic _fleh_dec
command script import "/Library/Developer/KDKs/KDK_10.13.6_17G65.kdk/System/Library/Kernels/kernel.development.dSYM/Contents/Resources/DWARF/../Python/lldbmacros/xnu.py"
xnu debug macros loaded successfully. Run showlldbtypesummaries to enable type summaries.

settings set target.process.optimization-warnings false
(lldb)

现在,我们终于可以通过编写kdp-remote localhost将lldb连接到 动态内核(live kernel)了。如果一切正常,内核应该已经连接成功,且应该出现以下这样的输出内容。一开始,许多文本会涌入调试人员的lldb窗口,不过过一会,内核进入休息状态。

(lldb) kdp-remote localhost
Version: Darwin Kernel Version 17.7.0: Wed Oct 10 23:06:14 PDT 2018; root:xnu-4570.71.13~1/DEVELOPMENT_X86_64; UUID=1718D865-98B4-3F6E-97CF-42BF0D02ADD7; stext=0xffffff802e800000
Kernel UUID: 1718D865-98B4-3F6E-97CF-42BF0D02ADD7
Load Address: 0xffffff802e800000
Kernel slid 0x2e600000 in memory.
Loaded kernel file /Library/Developer/KDKs/KDK_10.13.6_17G3025.kdk/System/Library/Kernels/kernel.development
Loading 152 kext modules warning: Can't find binary/dSYM for com.apple.kec.Libm (BC3F7DA4-03EA-30F7-B44A-62C249D51C10)
.warning: Can't find binary/dSYM for com.apple.kec.corecrypto (B081B8C1-1DFF-342F-8DF2-C3AA925ECA3A)
.warning: Can't find binary/dSYM for com.apple.kec.pthread (E64F7A49-CBF0-3251-9F02-3655E3B3DD31)
.warning: Can't find binary/dSYM for com.apple.iokit.IOACPIFamily (95DA39BB-7C39-3742-A2E5-86C555E21D67)
[...]
.Target arch: x86_64
.. done.
Target arch: x86_64
Instantiating threads completely from saved state in memory.
Process 1 stopped
* thread #2: tid = 0x0066, 0xffffff802e97a8d3 kernel.development`DebuggerWithContext [inlined] current_cpu_datap at cpu_data.h:401, name = '0xffffff80486a2338', queue = '0x0', stop reason = signal SIGSTOP
    frame #0: 0xffffff802e97a8d3 kernel.development`DebuggerWithContext [inlined] current_cpu_datap at cpu_data.h:401 [opt]

现在我们就连接到动态内核了,不过你可以看到此时进程已停止,这意味着内核已冻结,这就是为什么启动过程会在调试人员离开的位置停止。不过现在调试器已连接,我们只需做到一点就可以安全地继续启动进程到正常的macOS桌面。要做到这一点,我们只需解冻该过程,输入“c” 即可,然后按Enter键直到启动继续(更多文本会出现在调试器屏幕上)。

(lldb) c
Process 1 resuming
Process 1 stopped* thread #2: tid = 0x0066, 0xffffff802e97a8d3 kernel.development`DebuggerWithContext [inlined] current_cpu_datap at cpu_data.h:401, name = '0xffffff80486a2338', queue = '0x0', stop reason = EXC_BREAKPOINT (code=3, subcode=0x0)
    frame #0: 0xffffff802e97a8d3 kernel.development`DebuggerWithContext [inlined] current_cpu_datap at cpu_data.h:401 [opt](lldb) c

一旦调试器完全在macOS中启动,调试人员就可以在桌面上进行任何调试了。要运行调试器命令,调试人员必须再次触发NMI,然后按一下电源按钮。调试器屏幕将冻结,但调试器的lldb屏幕将处于运行状态,此时调试人员可以在动态内核上读/写寄存器,读/写内存,反汇编地址,反汇编函数等。要将其解冻,请再次键入“c” 并在lldb屏幕上按一下Enter键。

内核调试示例

示例1:使用lldb读取所有寄存器并将“AAAAAAAA”写入其中一个寄存器

要读取所有寄存器,按下电源按钮并在打开的lldb窗口中输入register read –all来触发NMI:

(lldb) register read --allGeneral Purpose Registers:
      rax = 0xffffff802f40ba40  kernel.development`processor_master
      rbx = 0x0000000000000000
      rcx = 0xffffff802f40ba40  kernel.development`processor_master
      rdx = 0x0000000000000000
      rdi = 0x0000000000000004
      rsi = 0xffffff7fb1483ff4
      rbp = 0xffffff817e8ccd50
      rsp = 0xffffff817e8ccd10
       r8 = 0x0000000000000000
       r9 = 0x0000000000000001
      r10 = 0x00000000000004d1
      r11 = 0x00000000000004d0
      r12 = 0x0000000000000000
      r13 = 0x0000000000000000
      r14 = 0x0000000000000000
      r15 = 0xffffff7fb1483ff4
      rip = 0xffffff802e97a8d3  kernel.development`DebuggerWithContext + 403 [inlined] current_cpu_datap at cpu.c:220
 kernel.development`DebuggerWithContext + 403 [inlined] current_processor at debug.c:463
 kernel.development`DebuggerWithContext + 403 [inlined] DebuggerTrapWithState + 46 at debug.c:537
 kernel.development`DebuggerWithContext + 357 at debug.c:537
   rflags = 0x0000000000000046
       cs = 0x0000000000000008
       fs = 0x0000000000000000
       gs = 0x0000000000000000
 
Floating Point Registers:
      fcw = 0x0000
      fsw = 0x0000
      ftw = 0x00
      fop = 0x0000
       ip = 0x00000000
       cs = 0x0000
       dp = 0x00000000
       ds = 0x0000
    mxcsr = 0x00000000
 mxcsrmask = 0x00000000
    stmm0 = {0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00}
    stmm1 = {0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00}
    stmm2 = {0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00}
    stmm3 = {0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00}
    stmm4 = {0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00}
    stmm5 = {0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00}
    stmm6 = {0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00}
    stmm7 = {0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00}
     xmm0 = {0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00}
     xmm1 = {0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00}
     xmm2 = {0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00}
     xmm3 = {0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00}
     xmm4 = {0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00}
     xmm5 = {0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00}
     xmm6 = {0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00}
     xmm7 = {0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00}
     xmm8 = {0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00}
     xmm9 = {0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00}
    xmm10 = {0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00}
    xmm11 = {0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00}
    xmm12 = {0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00}
    xmm13 = {0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00}
    xmm14 = {0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00}
    xmm15 = {0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00}
 Exception State Registers:
3 registers were unavailable.(lldb)

现在让我们对其中一个寄存器进行写入,注意不要在未设置为0x0000000000000000的寄存器中写入,因为调试人员将覆盖某些内容,找一个空的进行写入。在本文的示例中,R13是空的(r13 = 0x0000000000000000),所以我可以其中写入来证明我的观点。为了向寄存器写入一个AAAs字符串,我可以将它的值替换为0x414141414141414141,其中0x41是ASCII字符“A”的十六进制表示。要覆盖寄存器,我可以使用register write r13 0x4141414141414141命令。果然,如果我们再次读取寄存器,就会发现已经覆盖了。

(lldb) register write R13 0x4141414141414141(lldb) register read --allGeneral Purpose Registers:
      rax = 0xffffff802f40ba40  kernel.development`processor_master
      rbx = 0x0000000000000000
      rcx = 0xffffff802f40ba40  kernel.development`processor_master
      rdx = 0x0000000000000000
      rdi = 0x0000000000000004
      rsi = 0xffffff7fb1483ff4
      rbp = 0xffffff817e8ccd50
      rsp = 0xffffff817e8ccd10
       r8 = 0x0000000000000000
       r9 = 0x0000000000000001
      r10 = 0x00000000000004d1
      r11 = 0x00000000000004d0
      r12 = 0x0000000000000000
      r13 = 0x4141414141414141 <-- Yee overwritten this.
      r14 = 0x0000000000000000
      r15 = 0xffffff7fb1483ff4
      rip = 0xffffff802e97a8d3  kernel.development`DebuggerWithContext + 403 [inlined] current_cpu_datap at cpu.c:220
 kernel.development`DebuggerWithContext + 403 [inlined] current_processor at debug.c:463
 kernel.development`DebuggerWithContext + 403 [inlined] DebuggerTrapWithState + 46 at debug.c:537
 kernel.development`DebuggerWithContext + 357 at debug.c:537
   rflags = 0x0000000000000046
       cs = 0x0000000000000008
       fs = 0x0000000000000000
       gs = 0x0000000000000000
 
Floating Point Registers:
      fcw = 0x0000
      fsw = 0x0000
      ftw = 0x00
      fop = 0x0000
       ip = 0x00000000
       cs = 0x0000
       dp = 0x00000000
       ds = 0x0000
    mxcsr = 0x00000000
 mxcsrmask = 0x00000000
    stmm0 = {0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00}
    stmm1 = {0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00}
    stmm2 = {0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00}
    stmm3 = {0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00}
    stmm4 = {0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00}
    stmm5 = {0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00}
    stmm6 = {0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00}
    stmm7 = {0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00}
     xmm0 = {0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00}
     xmm1 = {0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00}
     xmm2 = {0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00}
     xmm3 = {0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00}
     xmm4 = {0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00}
     xmm5 = {0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00}
     xmm6 = {0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00}
     xmm7 = {0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00}
     xmm8 = {0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00}
     xmm9 = {0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00}
    xmm10 = {0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00}
    xmm11 = {0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00}
    xmm12 = {0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00}
    xmm13 = {0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00}
    xmm14 = {0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00}
    xmm15 = {0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00}
 Exception State Registers:
3 registers were unavailable. 
(lldb)

注意:当调试人员想要读取单个寄存器时,他们不必输入register read –all,调试人员可以简单地用register read [register] 指定寄存器,例如register read r13。

示例2:在运行uname -a时更改内核版本和名称

是时候在内核做一些真正的内存读写了,调试人员可能知道,终端中的uname -a命令会列出了内核的名称、版本、版本号和构建日期,那我们如何将其更改为我们想要的内容呢?

首先,我们不知道内核存储名称、版本、版本号和构建日期信息的位置,因此我们要首先找到信息的存储位置。为此,我们可以使用任何反汇编程序,如IDA Pro,Hopper Disassembler,Jtool,Binary Ninja等。

我在本文的示例中使用的是IDA Pro,要做的就是加载内核。在IDA Pro中开发文件,并让IDA分析它。由于内核太大,分析可能需要一段时间,所以请耐心等待。当IDA完成时,输出应该或多或少看起来像以下这样。调试人员会知道IDA何时完成,因为它会在左下角显示“AU: idle”。

14.png

现在,我们必须找到位置字符串。。我们知道,当在终端中执行uname -a命令时,内核名是Darwin,所以为了在IDA中查找位置字符串,我们要到顶部栏 – >查看 – >打开子视图 – >字符串。这样,我们将看到一个新的字符串窗口,如果调试人员在其中按CTRL + F,搜索框将显示在底部,搜索Darwin,整个字符串都会显示出来。

15.png

双击字符串,调试人员将被重定向到一个名为_version的常量。所以现在我们知道了,常量就是所谓的“version”,这也是我们要寻找的字符串。调试人员可能倾向于从IDA反汇编中复制常量的地址,但这是不对的,因为内核使用KASLR或内核地址空间布局随机化,因此地址将不相同。但调试人员其实是不需要知道地址的,他们可以在调试器设备上用lldb轻松搞定。

16.png

此时得到的就是“version”常量的地址,它实际上非常简单。按下电源按钮触发NMI(如果继续该过程)并写入print &(version)。

(lldb) print &(version)
(const char (*)[101]) $8 = 0xffffff802f0f68f0
(lldb)

在本文的示例中,const char version 位于地址0xffffff802f0f68f0。果然,当我列出字符数组时,它会显示如下内容。

(lldb) print version
(const char [101]) $9 = {
  [0] = 'D'
  [1] = 'a'
  [2] = 'r'
  [3] = 'w'
  [4] = 'i'
  [5] = 'n'
  [6] = ' '
  [7] = 'K'
  [8] = 'e'
  [9] = 'r'
  [10] = 'n'
  [11] = 'e'
  [12] = 'l'
  [13] = ' '
  [14] = 'V'
  [15] = 'e'
  [16] = 'r'
  [17] = 's'
  [18] = 'i'
  [19] = 'o'
  [20] = 'n'
  [21] = ' '
  [22] = '1'
  [23] = '7'
  [24] = '.'
  [25] = '7'
  [26] = '.'
  [27] = '0'
  [28] = ':'
  [29] = ' '
  [30] = 'W'
  [31] = 'e'
  [32] = 'd'
  [33] = ' '
  [34] = 'O'
  [35] = 'c'
  [36] = 't'
  [37] = ' '
  [38] = '1'
  [39] = '0'
  [40] = ' '
  [41] = '2'
  [42] = '3'
  [43] = ':'
  [44] = '0'
  [45] = '6'
  [46] = ':'
  [47] = '1'
  [48] = '4'
  [49] = ' '
  [50] = 'P'
  [51] = 'D'
  [52] = 'T'
  [53] = ' '
  [54] = '2'
  [55] = '0'
  [56] = '1'
  [57] = '8'
  [58] = ';'
  [59] = ' '
  [60] = 'r'
  [61] = 'o'
  [62] = 'o'
  [63] = 't'
  [64] = ':'
  [65] = 'x'
  [66] = 'n'
  [67] = 'u'
  [68] = '-'
  [69] = '4'
  [70] = '5'
  [71] = '7'
  [72] = '0'
  [73] = '.'
  [74] = '7'
  [75] = '1'
  [76] = '.'
  [77] = '1'
  [78] = '3'
  [79] = '~'
  [80] = '1'
  [81] = '/'
  [82] = 'D'
  [83] = 'E'
  [84] = 'V'
  [85] = 'E'
  [86] = 'L'
  [87] = 'O'
  [88] = 'P'
  [89] = 'M'
  [90] = 'E'
  [91] = 'N'
  [92] = 'T'
  [93] = '_'
  [94] = 'X'
  [95] = '8'
  [96] = '6'
  [97] = '_'
  [98] = '6'
  [99] = '4'
  [100] = '\0'
}
(lldb)

实际上,使用x <address>命令,我就可以将内存内容转储到该地址。

(lldb) x 0xffffff802f0f68f0
0xffffff802f0f68f0: 44 61 72 77 69 6e 20 4b 65 72 6e 65 6c 20 56 65  Darwin Kernel Ve
0xffffff802f0f6900: 72 73 69 6f 6e 20 31 37 2e 37 2e 30 3a 20 57 65  rsion 17.7.0: We
(lldb)

它看起来像是对0xffffff802f0f6900的继续,于是我决定继续转储。

(lldb) x 0xffffff802f0f6900
0xffffff802f0f6900: 65 72 73 69 6f 6e 20 36 39 2e 30 30 20 57 65 65  rsion 17.7.0: We
0xffffff802f0f6910: 64 20 4f 63 74 20 31 30 20 32 33 3a 30 36 3a 31  d Oct 10 23:06:1
(lldb)

此时,就可以看到44 61 72 77 69 6e了,这是Darwin这个词的十六进制表示。如果我把它改成十六进制中的“GeoSn0w”,基本上可以改变内核名称,所有的版本均是如此。

所以,我需要一个Text to Hex转换器(一个非常好的文本字符转换16进制小工具,它支持文本转换16进制,也支持16进制转换文本),这个工具可以从网上下载到。但需要注意的是,我们如果要写入更长的字符串,就要先覆盖一些原先的内容。注意,写入的字符串不要超过现有字符串中的字符限制。

经过精心设计,我的十六进制字符串如下所示。

47 65 6f 53 6e 30 77 20 4b 65 72 6e 65 6c 20 56 = "GeoSn0w Kernel V"

65 72 73 69 6f 6e 20 36 39 2e 30 30 20 57 65 65 = "ersion 69.00 Wee"

不过现在,我们还不能把它写成这样的两个地址。因为我们必须在所有字符前面加上“0x”,最终的结果如下。

0x47 0x65 0x6f 0x53 0x6e 0x30 0x77 0x20 0x4b 0x65 0x72 0x6e 0x65 0x6c 0x20 0x56 = "GeoSn0w Kernel V"

0x65 0x72 0x73 0x69 0x6f 0x6e 0x20 0x36 0x39 0x2e 0x30 0x30 0x20 0x57 0x65 0x65 = "ersion 69.00 Wee"

现在我们可以将字节写入内存,让我们从第一个地址开始。在本文的示例中,命令看起来像这样:

(lldb) memory write 0xffffff802f0f68f0 0x47 0x65 0x6f 0x53 0x6e 0x30 0x77 0x20 0x4b 0x65 0x72 0x6e 0x65 0x6c 0x20 0x56
(lldb) x 0xffffff802f0f68f0
0xffffff802f0f68f0: 47 65 6f 53 6e 30 77 20 4b 65 72 6e 65 6c 20 56  GeoSn0w Kernel V
0xffffff802f0f6900: 72 73 69 6f 6e 20 31 37 2e 37 2e 30 3a 20 57 65  rsion 17.7.0: We
(lldb)

以下就是在0xffffff802f0f6900地址中,写入字符串后的结果。

(lldb) memory write 0xffffff802f0f6900 0x65 0x72 0x73 0x69 0x6f 0x6e 0x20 0x36 0x39 0x2e 0x30 0x30 0x20 0x57 0x65 0x65
(lldb) x 0xffffff802f0f6900
0xffffff802f0f6900: 65 72 73 69 6f 6e 20 36 39 2e 30 30 20 57 65 65  ersion 69.00 Wee
0xffffff802f0f6910: 64 20 4f 63 74 20 31 30 20 32 33 3a 30 36 3a 31  d Oct 10 23:06:1
(lldb)

现在让我们在调试器上解冻内核:

(lldb) c
Process 1 resuming
(lldb) Loading 1 kext modules warning: Can't find binary/dSYM for com.apple.driver.AppleXsanScheme (79D5E92F-789E-3C37-BE0E-7D1EAD697DD9)
. done.
Unloading 1 kext modules . done.
Unloading 1 kext modules . done.
(lldb)

在调试器终端运行uname -a命令:

26.png

此时,调试人员会看到显示出来的字符串。

Last login: Sun Dec  2 07:12:19 on ttys000
Isabella:~ geosn0w$ uname -a
Darwin Isabella.local 17.7.0 GeoSn0w Kernel Version 69.00 Weed Oct 10 23:06:14 PDT 2018; root:xnu-4570.71.13~1/DEVELOPMENT_X86_64 x86_64
Isabella:~ geosn0w$

以上就是我在macOS上进行内核调试的示例结果,希望调试人员喜欢它。不要忘记,在完成调试之后,调试人员应该再次将boot-args设置为stock,以便启动正常的RELEASE内核。调试人员可以在调试器的终端上运行sudo nvram boot-args=""命令,然后进入/System/Library/ kernel /删除kernel.development文件。

Isabella:~ geosn0w$ sudo nvram boot-args=""
Password:
Isabella:~ geosn0w$

现在在终端中输入以下两个命令,让kextcache无效:

sudo touch /Library/Extensions
sudo touch /System/Library/Extensions

然后重新启动,此时计算机将启动正常的RELEASE内核。

本文翻译自:https://geosn0w.github.io/Debugging-macOS-Kernel-For-Fun/如若转载,请注明原文地址: http://www.4hou.com/technology/15156.html
点赞 3
  • 分享至
取消

感谢您的支持,我会继续努力的!

扫码支持

打开微信扫一扫后点击右上角即可分享哟

发表评论