第10章 使用WinDbg调试内核

Author Avatar
kabeor 7月 22, 2018

第10章 使用WinDbg调试内核

本章将探索使用WinDbg进行内核调试和Rootkit分析的方法。

10x1 驱动与内核代码

Windows设备驱动简称为驱动,它让第三方开发商在Windows内核模式下运行代码。

由于驱动程序常驻内存,并且负责响应用户态应用程序的请求,因此分析它十分困难。另外,由于应用程序不直接与驱动程序通信,而是直接访问设备对象,向具体的物理设备发送请求,使得
驱动程序更加难以分析。设备对象由驱动程序创建和销毁,可以被用户态的程序直接访问,但它们并非一定是真实的物理设备。

为了系统能够正常工作,驱动程序必须加载到内核空间,这与DLL需加载到进程空间是同样的道理。当驱动首次被加载时,DriverEntry函数将被调用,这与DLLMain相似。

与DLL通过函数导出表提供其功能接口不同,驱动通过注册回调函数来提供功能。当用户态的应用程序请求一个服务时,这些回调函数将会被调用。回调函数在DriverEntry程序中被注册。

Windows会为每个驱动创建一个驱动对象,并以参数形式将其传给DriverEntry函数,DriverEntry函数用回调函数填充这个驱动对象。然后DriverEntry会创建一个可以被用户态应用程序访问的设
备对象,应用程序与驱动的交互请求都将通过这个设备对象进行。

考虑来自用户态应用程序的一个读数据请求。最终这个请求被发送到负责管理硬件并存储读入数据的驱动程序。首先,用户态应用程序应该获得该硬件设备的一个文件句柄,然后在该句柄上调
用函数ReadFile。接着内核将会处理ReadFile函数的请求,最终由驱动程序的回调函数来响应对I/O设备的读请求。

请求内核态恶意组件的最常见函数是DeviceIoControl,它是从用户态模块到内核设备的一种通用请求方法。使用该函数时,用户态应用程序传递一个任意长度的缓冲区数据作为输入,并且接
收一个任意长度的缓冲区数据作为输出。

用户态应用程序到内核态驱动的调用由操作系统完成,这种调用难以被跟踪.如图展示了请求从用户态程序发起并最终到达一个内核驱动的过程。从图中可以看出,请求由用户态程序发起,最后到达内核驱动。在发送到内核的请求中,一部分请求发送到设备驱动去控制硬件设备,而另外一部分仅仅影响一些内核的内部状态。
im

恶意驱动通常不控制硬件设备,而是与Windows操作系统主要的内核组件ntoskrnl. exe、hal.dll进行交互。ntoskrnl.exe组件包含操作系统核心功能的代码,hal.dll包含与主要硬件设备交互的代码。恶意代码常通过从一个或者多个这样的内核组件中导入函数,来操纵内核。

10x2 安装内核调试

内核调试比起用户模式调试来说更加复杂,因为进行内核调试时,操作系统将被冻结,这种情况下不可能运行调试器。因此,调试内核的常用方法是使用VMware。

与用户态调试不同,内核调试需要一些初始化设置。首先需要设置虚拟操作系统并开启内核调试,然后配置VMware使虚拟机与宿主系统之间有一条虚拟化的串口,同时还应该配置宿主操作系统中的WinDbg。

虚拟操作系统的设置是编辑C:\boot.ini(Windows XP下请确保文件夹选项设置为显示隐藏文件)。该文件在系统中通常是隐藏的。建议在编辑boot.ini文件之前,为你的虚拟操作系统做一个快照,如果配置文件错误或者损坏了boot.ini,你可以使用快照还原系统。

[boot loader]
timeout=30
default=multi(0)disk(0)rdisk(0)partition(1)\WINDOWS
[operating systems]
multi(0)disk(0)rdisk(0)partition(1)WINDOWS="Microsoft Windows XP Professional" 编号1
/noexecute=optin /fastdetect
multi(0)disk(0)rdisk(0)partition(1)\WINDOWS="Microsoft Windows XP Professional with Kernel Debugging" 编号2
/noexecute=optin /fastdetect /debug /debugport=COM1/baudrate=115200

以Windows XP为例,代码中编号1处指明默认加载的操作系统。编号2处是为了设置内核调试而加入的一行。通常你系统中的boot.ini与编号1类似.

复制系统中boot.ini最后一行,然后粘贴到boot. ini中新的一行,除了你应该加入的附加选项/debug /debugport=COMl /baudrate=115200外.其他项均与上一行相同(不用考虑行上其他的元素,如multi(0)disk(0),简单的完全复制,只需加入附加选项)。其中/debug标记代表开启内核调试,/debugport=COM1标记告诉系统使用哪个端口来连接调试系统与被调试系统,/baudrate=115200标记用来指定串口数据传输速率。在本例中,我们使用VMware创建的串口。为了方便识别开机系统选项,我们将第二启动项命为“Microsoft Windows XP Professional with Kernel Debugging”。

当下次开机运行你的虚拟操作系统时,系统会提供一个开启内核调试的选项’止你选择。另外,系统会给你30秒的时间,决定是否以调试模式启动系统。如果想要连接内核调试器(即WinDbg),你需要在每次开机时选择调试版本启动项。

下一步,需要设置VMware,在虚拟操作系统和宿主操作系统之间创建一个虚拟连接。为此,我们在VMware上添加一个新的设备来使用宿主系统中的一个命名管道上的串口。下面是添加设备的步骤:

1. 单击VM->Settings.然后会弹出VMware设置对话框。
2. 在VMware设置对话框中,单击右下角的Add按钮,在弹出的设备类型选择窗口中选择Serial Port,然后单击下一步。
3. 在请求串口类型的对话框中,选择Output to Named Pipe,然后单击下一步。
4. 在接下来的窗口中,输入\.\pipe\com_1对管道进行命名,然后选择This end is the server和The other end is an application。
5. 选中Yield CPU on poll单选框。

enter description here

完成虚拟机的配置后启动虚拟机。在宿主操作系统中,使用下列步骤使WinDbg连接虚拟机并开始调试内核。
1.启动WinDbg。
2.选择File—Kemel Debug.单击COM标签,然后输入文件名和先前在boot.ini文件中设置的波特率

如果虚拟操作系统处于运行状态,调试器会在数秒内连接到虚拟机操作系统。如果虚拟操作系统没有运行,调试器将处于等待,直到虚拟操作系统启动,启动过程中调试器将连接到被调试系统。调试器连接后,为了更加全面地获取到调试过程发生的事件,建议在调试过程中启用详细信息输出功能。启用详细信息输出功能后,每当驱动程序被加载和卸载时,你将会得到通知。这些信息在某些情况下可以帮助你识别恶意驱动的加载。

10x3 使用WinDbg

1. 从内存中读取

WinDbg的内存窗口支持直接通过命令来浏览内存。命令d是用来读取如程序数据或堆栈等内存位置的命令。

基本语法

dx addressToRead
这里x是显示数据格式的一个选项。
几种显示数据常用的方式

e命令使用方法相同,来改变内存的值
ex addressToWrite dataToWrite
其中x与dx命令中的x值相同

2. 使用算术操作符

WinDbg支持在命令行中使用简单的算术操作符,对内存和寄存器进行直接操作,如加(+)、减(-)、乘(* )、除(/)。在试图创建一个条件表达式断点时,命令行选项就如快捷方式一样好用。

命令dwo用来解引用一个32位的指针,并查看该指针代表地址的值。

3. 设置断点

在WinDbg调试器中,bp命令用来设置基本断点。同时WinDbg也可以使用一些命令,这些命令可以在断点触发时、控制转给用户之前自动运行。使用go(g)命令在断点处执行一个动作后继续执行,而不用等待用户。

4. 列举模块

在OllyDbg调试中,内存映射可以列举出所有内存段与加载模块,但是WinDbg却没有相似的功能。然而,WinDbg的lm命令可以列举出加载到进程空间的所有模块,包括用户模式下的可执行模块,DLL以及内核模式下的内核驱动,同时也会列举出每个模块的起始与结束地址。

10x4 微软符号表

调试符号表提供了有限的源代码信息,来帮助理解汇编代码。微软提供的调试符号表中包含某些函数和变量的名字。

在这里所说的调试符号就是某个特定内存地址的一个名称,大多数调试符号提供一个地址名称来表示一个函数,也有一些地址名称表示数据地址。

1. 搜索符号

WinDbg中符号的格式如下所示
moduleName!symbolName

这种语法可以在任意一个拥有正常地址的地方使用。其中moduleName表示.exe、.dll或者.sys格式文件的文件名(不包括其扩展名)。symbolName是与这个地址相关联一个名字。然向,ntoskrnl.exe是一个特例,它的module Name是nt,而不是ntoskrnl。

bu命令允许你用符号在没有加载的代码中设置一个延迟断点。延迟断点是一个断点,仅当加载一个名字匹配的模块时,延迟断点才会被设置。

x命令允许你使用通配符来搜索函数或符号。

Ln用来列出最接近给定内存地址的符号,它可以用来确认指针指向的函数。

2. 查看结构信息

微软符号也包含多个数据结构的类型信息,包括没有被公开的内部类型。对于恶意代码分析人员,这些信息非常有用,因为恶意代码经常操作未公开的数据结构。

3. 配置Windows符号表

符号表依赖于被分析文件的具体版本,它们随着文件的更新或修复而改变。如果配置正确。WinDbg将查询微软的服务器,自动获得正在调试文件的正确符号表。你可以通过选择File->Symbol File Path,来设置符号文件路径。为了配置WinDbg,让其使用在线符号服务器,输入以F路径:
SRV*c:\websymbols*http://msdl.microsoft.com/download/symbols
SRV配置了一个服务器,路径C:\websymbols是符号信息的本地缓存,网址则是微软符号服务器的
固定位置。

如果要调试的机器并不能一直连接互联网,你可以根据你使用操作系统、服务包、机器的体系结构等信息,手动从微软服务器下载指定的符号包。符号文件通常有几百兆大小,因为它们包含操作系统和服务包的所有修改和补丁版本的符号信息。

10x5 内核调试实践

对于恶意代码编码者,从内核空间写文件的好处是更加难以被觉察。虽然不是以秘密方式写入一个文件,但是它可以绕过一些安全产品,同时也可以误导那些试图查找用户空间中CreateFile、WriteFile函数调用证据的恶意代码分析师。呈现在恶意代码编写者面前的一个挑战是:在内核模式中,普通Win32函数不能直接被调用。但在内核模式下有相似的函数,这些函数可以被内核态的恶意代码所使用。由于CreateFile和WriteFile函数在内核模式下不可用,所以内核模式下提供NtCreateFile和NtWriteFile函数作为替代。

1. 用户空间的代码

2. 内核模式的代码

3. 查找驱动对象

10x6 Rootkit

Rootkit通过修改操作系统内部函数,来隐藏自己存在的痕迹。通过这种修改, Rootkit可以隐藏一个正在运行程序的文件、进程、网络连接以及其他资源。这使得其恶意活动难以被反病毒产品、管理员以及安全分析员发现。

现在大部分 Rootkit都是通过采用某种方式修改操作系统内核来工作的。尽管 Rootkit可以使用多种隐藏技术,但在实际应用中,系统服务描述表(SSDT: System Service Descriptor Table)挂钩技术的使用程度远远超过其他技术。这种技术已经有几年的历史,与其他 rootkits技术相比,它更容易被探测。然而,由于它容易理解、实现灵活且容易,因此到现在它依然被恶意代码所使用。

系统服务描述表(SSDT),也称为系统服务分发表,微软使用它来查找进入内核的系统调用,它通常不被第三方应用程序或者驱动程序访问。内核态代码只能被用户态的
SYSCALL、 SYSENTER或INT 0x2E指令来访问。当前 Windows版本( Windows XP之后)使用 SYSENTER指令,它从存储在寄存器EAX的函数代码中获取指令。

1. Rootkit分析实践

一个 Rootkit挂钩SSDT的例子

分析一个假设已感染的系统,并认为系统已经安装有恶意驱动。

首先最直接的方式是检查SSDT是否被挂钩,在 WinDbg中通过存放在nt!KeServiceDescriptorTable表中的偏移量查看SSDT表。SSDT表中所有函数偏移量都应该指向位于NT模块地址范围内的函数,所以我们首先要做的工作就是获取NT模块的地址边界。如果 Rootkit挂钩了其中的某个函数,则这个函数指针可能不在NT模块的地址范围内。当我们检查SSDT时,应该观察其函数指针是否在NT模块的地址范围内。

2. 中断

有时.Rootkit会使用中断来干扰系统事件。现代的处理器实现了用硬件方式触发软件事件的中断。系统发送一条命令到硬件,硬件处理完请求事件后会中断处理器。

有时,驱动或者Rootkit会利用中断来执行代码,驱动程序调用IoConnectlnterrupt函数为特定中断注册一个处理程序,然后为这个中断指定一个中断服务例程(ISR),每当触发该中断时,系统都会调用注册的中断服务例程。

中断描述表(IDT)存储着ISR的信息,在WinDbg中可以通过lidt命令查看

如果中断位于一个没有名字、没有签名或可疑的驱动中,则表明存在 Rootkit或者恶意代码。

10x7 加载驱动

假设你拥有一个恶意的驱动程序,但没有用户态应用程序安装它,这个时候就可以用如 OSR Driver Loader的加载工具来加载它。 OSR Driver Loader驱动加载器非常容易使用而且免费,但需要注册。一旦安装了 OSR Driver Loader,只需在它运行后指定需要加载的驱动,然后单击 Register Service和 Start Service就可以启动驱动。

10x8 Windows vista、 Windows7和x64版本的内核问题

Vista及之后的Windows版本使用一个名为BCDEdit的程序来编辑引导配置数据,因此你可以在新版本的Windows操作系统中,使用BCDEdit开启内核调试。

在安全方面,最大的改变是使用了一种内核保护补丁机制,通常被称为PatchGuard,这种机制从Windows XP开始在x64系统上实施。内核补丁保护能够阻止第三方程序修改内核,这包括修改内核代码自身、修改系统服务表、修改IDT以及其他补丁修改技术。引入这种功能时存在一定程度上的争议,因为不仅恶意程序使用内核补丁,正常程序也使用它。

同时,在64位系统中,内核补丁保护也能干扰调试过程,因为调试器在插入断点时会修改代码。因此.如果内核调试器在系统引导时附加到系统的话,补丁保护将不会运行。如果系统引导结束后再将内核调试器附加到系统.PatchGuard将会使系统崩溃。

从64位版本的Vista开始.Windows强制执行驱动签名机制,这也就意味着在没有数字签名的情况下,你不能将驱动加载到Windows Vista系统中。因为恶意代码通常不会使用数字签名,所以这是一种对抗恶意内核驱动的有效安全措施。事实上.x64系统上的恶意内核驱动实际上还不存在,但是随着x64版本的Windows越来越普及,恶意代码无疑将会解决这一个障碍。如果你需要加载一个未签名的驱动到x64的Vista系统中,你可以使用BCDEdit工具去修改引导项。具体来说就是将要求驱动签名的功能关闭,即关闭nointegritychecks。

From https://kabeor.github.io/第10章 使用WinDbg调试内核/ bye

This blog is under a CC BY-NC-SA 4.0 Unported License
本文链接:https://kabeor.github.io/第10章 使用WinDbg调试内核/