内核调试之双机调试环境的搭建
前言
在进行内核学习/编程之前需要创建属于自己的双机调试环境。通过不断的查阅资料和亲身实验,总结了使用Visual Studio 2017或windbg进行调试虚拟机的经验。
内核调试需要使用一台计算机用于被调试,另一台计算机用于使用调试器来调试前面所述的计算机。为了能在同一台计算机上进行内核调试,通常使用虚拟机来运行被调试的计算机。另外,使用虚拟机运行被调试的机器比较安全,因为虚拟机可以随便的使用,无需担心调试过程中导致的系统损坏。而调试器所在的计算机通常使用物理机。
为了简单叙述,我们将设置分成两个部分,分别是被调试的虚拟机和调试器所属的物理机,这里使用WIndows XP 32位虚拟机或者Windows 7 64位虚拟机作为被调试的计算机。而物理机则使用Windows 10 64位 1809版本。使用的调试器是windbg或者安装了WDK的Visual studio 2017(安装WDK的vs会默认安装windbg)。
Windows7虚拟机的设置
新建立的Windows7虚拟机,做好系统内的基本设置后,安装vmware tools,然后关闭Windows7虚拟机。
注意:windows 7及以后的操作系统版本都可以使用此方法进行设置。
1.在windows7虚拟机的配置向里面删除打印机这个虚拟设备,并添加新的串行端口。操作方式如下面的4个图:
首先移除这个打印机虚拟设备
然后使用添加硬件向导添加一个串行端口虚拟设备
选择“输出到命名管道”,然后单击“下一步”
最后设置命名管道的名称和这个管道的属性,该端是服务器表示该管道由这个虚拟机进行创建,其他应用程序只能通过客户端方式进行连接来实现与这个虚拟机的通信,且必须在该虚拟机启动时才能连接成功。
2重启这个Windows7虚拟机。
现在创建调试模式:以管理员方式启动cmd命令行,然后执行下面的命令行(注意:“Windows7-双机调试模式”是系统启动是的选项卡名字,这个名字之间如果含有空格请在两端添加英语格式的双引号),然后设置开机选择进入方式的等待时间为10秒
bcdedit /copy {current} /d Windows7-双机调试模式
bcdedit /timeout 10
3.重新启动Windows7虚拟机,在开机时有选择如下界面,这个界面是让我们选择启动方式,其中“Windows7-双机调试模式”是我们刚增加的启动方式。选择这个启动方式并进入Window7虚拟机的系统。
接下来在管理员模式启动cmd命令行执行以下命令行:
①关闭强制数字签名
bcdedit -set loadoptions DDISABLE_INTEGRITY_CHECKS
注意:关闭时使用bcdedit -set loadoptions DENABLE_INTEGRITY_CHECKS
②开启系统测试模式
bcdedit /set testsigning on
注意:关闭时使用bcdedit /set testsigning off
③开启调试功能
bcdedit /debug ON
bcdedit /bootdebug ON
④查看我们的调试配置是否与预想的一致
bcdedit /dbgsettings
这里查看到的结果是这样的:
这表示使用串口的com1进行通信,波特率是115200 bit/s
这里需要确认的是,vmware为windows7虚拟机添加的端口应当与此处的端口号一致,因为只有二者匹配了才能正确的建立调试的通信。
如果二者不一致,可以通过如下命令进行调整:
bcdedit /dbgsettings serial debugport:1 baudrate:115200
然后关闭Windows7虚拟机。
注意:debugport:1中的1是指使用Windows7虚拟机的串行端口COM1,baudrate:115200是指波特率为115200。另外,这些设置都可以在WINDOWS+R执行msconfig来使用图形化界面操作。
4.重新启动windows7虚拟机,选择调试模式选项卡并进入系统,此时的开机界面是这样的:
可以看到,第二个选项相比之前多了“[启用调试程序]”字样。
从现在开始,如果选择“Windows7-双机调试模式”进入系统,需要使用Windbg与之进行通信才行,否则系统启动时会由于出发调试中断等待Windbg连接,让Windbg使用“g”命令才能继续启动。
到目前为止,使用windbg进行调试Windows7虚拟机中Windows7虚拟机的设置已经OK,只需要在开机时选择“Windows7-双机调试模式”进入系统,然后等待Windbg的连接即可。
因此,可以在此次关机后创建虚拟机的快照,方便以后的使用。
设置Windows10主机的Windbg
为了能够方便的打开windbg,应当设置windbg的快捷启动方式。因为这样,可以将命令行一并设置到快捷方式中,后续使windbg用双机调试Windows7时只需要双击此快捷方式就可以按照预先设置的命令行参数运行windbg。
注意:由于这里的Windows7虚拟机是64位的,因此使用x64 debug。
首先找到安装的64位windbg调试器的位置,然后右键发送到桌面快捷方式,最后将桌面上新产生的快捷方式右键属性设置如下:
其参数如下:
"C:\Program Files (x86)\Windows Kits\10\Debuggers\x64\windbg.exe" -b -k com:pipe,port=\\.\pipe\com_1,baud=115200,reconnect –y SRV*D:\Reverse\Windows7Symbols\symbols*http://msdl.microsoft.com/download/symbols
注意:
"C:\Program Files (x86)\Windows Kits\10\Debuggers\x64\windbg.exe"是本快捷方式关联的windbg可执行文件的绝对路径。
“\\.\pipe\com_1”是虚拟机添加串行端口时使用的命名管道名称,这个要与之前Windows7虚拟机的设置一致。
“D:\Reverse\Windows7Symbols\symbols”是我们使用的符号文件默认保存位置,可以根据自己喜好进行设置。
设置完毕后,我们双机这个快捷方式,当弹出的窗口显示“Waiting to reconnect...”时就开启Windows7虚拟机,并且在启动画面选择“Windows7-双机调试模式”。
之后在Windows7虚拟机启动直到进入桌面的过程中windbg会有多次遇到int 3断点,只需要在windbg的命令栏输入“g“命令即让Windows7虚拟机继续启动,直到进入桌面。
当Windows7虚拟机进入桌面后,在windbg上只需要点击“break”按钮(或者菜单栏->debug->break菜单)就可以触发int3断点,使Windows7虚拟机中断运行。
注意:
1.当Windbg接收到int 3 中断时Windows7虚拟机将暂停运行。只有在windbg中输入“g“命令时才能继续。
2.当Window7虚拟机进入桌面一切进入就绪后,就在Windbg中菜单栏的debug->break进入中断。
3.只要Windows7虚拟机处于中断模式下,windbg都可以连接上这个虚拟机,并进行调试。
4.以“启用调试程序”方式启动的Windows7虚拟机在启动和关机时都会有几次触发int 3中断,等待windbg连接并作出命令。否则Window7虚拟机无法继续向前运行。
5. 以“启用调试程序”方式启动的Windows7虚拟机在启动完成进入桌面后,桌面的右下角会出现下图文字,表明此系统当前处于内核调试模式。
Windows XP实现用windbg的双机调试
首先创建windowsXP虚拟机后安装vmware tools工具后关闭WindowsXP虚拟机。
如同Windows7虚拟机一样先删除打印设备,然后为WindowsXP虚拟机添加一个虚拟串行端口设备。
然后启动windows XP虚拟机,在系统盘的根目录下找到boot.ini文件。
注意:该文件是隐藏文件,需要在“文件夹选项”设置取消“隐藏受保护的操作系统文件”选项才能看到该文件。
用记事本打开后,在文件末尾增加如下一行内容
multi(0)disk(0)rdisk(0)partition(1)\WINDOWS="Microsoft Windows XP Professional Debug" /fastdetect /debugport=com1 /baudrate=115200
然后回车保存。重启WindowsXP后将会观察到如下界面。
开机时选择带“启用调试程序”的一项即可。
在我们启动后,可以通过windbg x86版本连接这台虚拟机进行调试。其windebug的设置如同windows7虚拟机的windebug设置同理。
注意:当被调试的虚拟机在正常运行时,windbg无法连接到虚拟机,我们只需要找能够产生DbgPrint信息即可让windbg再次连接这台被调试的虚拟机。也即当被调试的虚拟机在产生调试信息的时候,会主动向调试器发出信息。这个时候调试器会连接上被调试的虚拟机,并接收到调试信息。
设置Visual Studio 2017进行双机调试
使用windbg进行双机调试有两种方式:一种是使用纯windbg,另一种是使用集成了windbgg工具的visual studio。使用vs 2017进行调试的时候在查看数据的时候比较方便。
下面,进行设置vs2017实现windbg的双机调试。
首先应当准备一份安装了vs2017并且安装了适合版本的wdk。
当安装WDK后,vs的菜单栏会出现DRIVER菜单:
点击DRIVER->Test->Configure Devices...,如下图所示
然后会弹出如下界面。我们单击“Add New Device”按钮添加新的待调试设备。
弹出如下界面:
如上图所示,我们可以为这个配置写一个有意义的名字,然后在host name填写被调试的计算机的host name(这里随便填写也没关系,因为实际上使用的是串口,而不填写却无法点击“下一步”),然后选择第二项设置。单击下一步,之后弹出下图所示画面,如下图所示,由于是通过命名管道与虚拟机进行模拟串口通信。因此其设置如下图中一样。最后一项“Target Port”无需设置。这里需要注意的是,设置的命名管道名称应当与被调试的虚拟机的虚拟化串口设备的命名管道保持一致。
注意:命名管道实际上是操作系统实现进程间通信的一种方式,这里是完成了虚拟机程序和物理机的调试器之间的通信,而虚拟机则将管道通信的数据再虚拟成虚拟机的串行端口。通过这种方式完成来了虚拟机和物理机之间的串口通信。
当单击下一步后,可以直接单击完成。只要以上参数设置保持正确,无需关心后一步的测试结果。
设置完成后,单击“Apply”按钮,最后单击“OK”按钮。
注意:请忽视vs2017的测试结果,其测试结果无参考意义。
之后,我们就可以使用vs2017进行双机调试了。
使用vs2017进行双机调试
当完成vs2017的双机测试设置后,就可以开始进行调试了。
使用vs2017新建一个空的WDM驱动程序,然后添加源代码文件,编写代码。
创建项目:
为创建的项目添加一个源文件main.c:
然后在其中编写测试代码:
代码的文本形式:
#include <ntddk.h> NTSTATUS DriverUnload(PDRIVER_OBJECT driver) { DbgPrint("MYDEVICE:%ws Unload\n", driver->DriverName.Buffer); return STATUS_SUCCESS; } NTSTATUS DriverEntry(PDRIVER_OBJECT driver, PUNICODE_STRING reg_path) { DbgPrint("MYDEVICE:%ws\n", reg_path->Buffer); driver->DriverUnload = DriverUnload; return STATUS_SUCCESS; }
然后,为了能够在xp系统下使用这个驱动程序,我们需要对项目的属性进行设置。
首先是C/C++的设置,这些设置是为了能够正常编译通过。其中警告等级可以设置的更低。
其次是对Driver Setting选项的设置。
注意:当前的设置都是针对Win 32的debug模式,如果修改了编译模式,例如变为release模式,则还需要对release模式的项目属性重新设置。
设置完以上项目属性后就可以进行编译了。
当编已完成后,会在该解决方案所在目录下的debug目录下生成.sys文件。
现在开始进行调试,选择菜单->调试->附加到进程
弹出新的窗口进行如下设置,其中“连接目标“依据之前设置的配置名称进行选择。最后点击“附加“按钮即可。
之后弹出如下界面:
如上图所示,白色区域是windbg的命令行输出,当前输出信息表示windbg已经成功连接到命名管道上,但是却无法连接到被调试机器。
此时,我们需要对被调试的虚拟机进行操作才能使windbg与被调试机器建立连接。
有如下方式可以让被调试的虚拟机与windebug建立连接:
方式一:让虚拟机以“启用调试程序“模式启动,然后可以看到如下所示的windbg输出
方式二:在被调试的机器启动进入桌面后,主动进行调试内核调试信息的输出。
例如,将编译好的驱动使用DriverMonitor进行加载,并启动。
这里是因为,这里编写的驱动测试程序自身带有调试信息输出,所以当这个驱动被加载并运行时会输出调试信息。然后被调试的虚拟机会主动通过串口发送调试信息。当VS2017中的windebug收到串口数据后就会表示已经与被调试的机器建立了调试连接。
方式三:当前被调试的虚拟机处于调试中断状态。
当被调试的虚拟机处于调试中断状态时,该虚拟机处于假死状态。而该被调试虚拟机会不断的通过串口尝试与windbg进行连接,因此这种情况下,只要使用了正确连接参数的windbg就能立刻与被调试虚拟机建立调试连接。
方式四:在确保windbg或VS的调试设置无误情况下直接使用break命令强制让虚拟机进入中断状态。
当调试器的设置没有问题时,我们可以通过调试器主动发送串口消息,让串口的另一端接收消息,而这里是让调试器主动发出中断指令,串口的另一端(虚拟机)接收到消息后会立马作出反应并进行应答,而使二者建立调试连接
注意:以上这些方式只是意在说明调试连接建立的不同情况,但是在实际操作过程中方式三和方式四更为实用,这主要是因为快速和方便。通常开发人员会将处于中断状态的虚拟机保存快照,这样每次需要调试的时候,只需要恢复快照,就可以在windbg或vs2017上以最快速的方式建立调试连接。
有时候,windbg会显示如下情况:
如上图所示,显示正在等待连接到命名管道,这是因为命名管道的连接方式时服务端建立管道后,客户端才能与之建立管道通信,然而服务端并未开启该命名管道,因此作为客户端的windbg不能正确的与该管道建立通信。
此时应当考虑的问题的可能原因:
1.是否开启了该被调试的虚拟机,当虚拟机未打开时,虚拟机软件并不会调用Windows API创建该命名管道。因此正确的做法是应当打开这个虚拟机。这里的打开虚拟机和开启虚拟机是不同的意思,如下图所示,表示已经打开了虚拟机,而开启虚拟机是指开启了虚拟机的操作系统。
2.所设置的命名管道与被调试虚拟机设置的管道名称不一致。
现在,进行调试已经编写好的驱动程序。
1.首先将编译好的debug模式下的驱动程序拷贝到被调试的虚拟机内,直接放在桌面上就好。
2.当VS2017内的windbg显示已连接时使用VS2017的中断按钮让虚拟机中断(请务必要让虚拟机中断一次)。
3.待中断成功后在vs2017内项源代码设置断点,然后在VS2017内让被调试的虚拟机继续运行。注意断点应当设置在能最先命中的位置,比如时驱动程序的入口。
4.在被调试的虚拟机内使用DriverMonitor加载这个驱动,当这个驱动程序启动时就会发现vs2017已经命中断点。
后面的工作就是不断的进行调试工作了。
注意:被调试的驱动程序所属的项目应当是“启动项目”。
小结
可以看到,vs2017的双机调试其实质是使用windbg。而windbg与被调试虚拟机的调试建立则是使用命名管道进行通信。另外,被调试虚拟机主动发出调试信息才使得二者成功建立调试连接。
题外话:双虚拟机调试
双虚拟机调试是指被调试计算机和调试器所在的计算机都处于虚拟机内。而前面所述的调试设置都是属于被调试机器是虚拟机,调试器所在计算机是物理机。
这里,我们介绍双虚拟机调试的情况。
其实,在看了前面的介绍后,会有一个比较清楚的连接实质的认识:调试器和被调试机器总要通过某种介质进行建立通信。
双虚拟机的通信介质仍然是命名管道。只不过命名管道的客户端是被调试的虚拟机,命名管道的服务端却是调试器所在的虚拟机。命名管道在两台虚拟机内已经被虚拟成两台虚拟机的物理端口。
因此建立通信的方式就是两个串行端口。
命名管道的设置上需要有一点区别,就是另一端应当是虚拟机。
接下来以windows xp虚拟机作为被调试虚拟机,windows 10虚拟机作为调试器所在的虚拟机进行设置。
Windows XP虚拟机串行端口的设定:
Windows 10虚拟机的串行端口设定:
注意:这里被调试虚拟机内的设置,请参考前面的介绍。
然后开启Windows 10虚拟机,然后开启这台虚拟机内安装的vs2017,最后打开用于测试的驱动程序工程。
在vs里面对双击调试的配置应当有所不同,这里相当于使用本机内的“物理串口“进行通信,因此配置如下图所示,值得注意的是,需要确认当前windows 10虚拟机存在的”物理串口“编号是多少!
然后同样的方式使用菜单栏->调试->附加到进程进行调试。
如上图所示,白色区域的windbg输出的信息表示,已经成功的打开了com1端口,然后等待连接。于是在这个时候启动windows XP虚拟机,选择“启用调试程序“模式进入系统,能够看到windbg与被调试虚拟机建立了连接并输出信息。
可以看到,在VS2017中点击中断按钮成功使被调试的Windows XP虚拟机进入中断。
注意:双虚拟机调试对物理机的性能要求较高。另外,双虚拟机的串口通讯速率会比较慢,这是因为层层虚拟化,损失了很多性能。
附录:Windbg常用的命令
u(f) [地址/函数名/寄存器] [Lxxx] | 反汇编(全部)函数,从当面eip位置/地址/函数名/寄存器指定地址或其它地址处反汇编,[反汇编出xxx条指令]。执行此命令后继续回车会继续往下反汇编 |
dq/dd/dw/db 地址/寄存器/函数名/导出符号名/pdb中的符号名 [Lxxx] | 分别以8(qword)、4(dword)、2(word)、1(byte)字节方式查看内存数据,[查看xxx个单位(条)的数据] |
~1 | 切换到cpu的第一个核,当cpu只有一个核时不用切换.多cpu的系统多个cpu需要分别调试。核的编号是从0开始 |
dv | 在有源码状态下,查看的那个前调用函数的局部变量的值 |
r 寄存器 | 查看寄存器的值 |
dt _结构体名 [地址/寄存器] | 对某地址以结构体形式展示信息 |
!vad VadRoot地址 | 列出进程空间的所有内存关系(vad的地址是内核区,在_EPROCESS结构体中) |
!process 0 0 [processname] | 列出所有的进程信息/列出进程名为processname的进程信息,注意processname是exe的文件名(包含后缀),不是窗口名 |
.process /i eprocess地址 |
切换调试的进程,默认中断下调试的是sysyem进程。该命令让CPU运行一会直到切换为目标进程的时间片后才会断下。 此命令执行成功后,cr3寄存器的值是目标进程的页目录基址 |
.formats 16进制数据 | 对数据进行格式转换成各种进制和类型解析,如二进制,整数,浮点数,八进制等类型 |
!vtop 进程的页目录机制 虚拟地址 | 将虚拟地址转换成物理地址,会显示具体的转换过程 |
!pte 虚拟地址 | 查看当前地址的页面属性,不写虚拟地址默认为0地址 |
!threads | 查看当前进程的所有线程信息 |
~0s | 切换到0线程 |
!handle 0 0 | 查看当前进程的句柄表 |
bp [/1] 地址/符号名/寄存器/函数名 [第几次执行到这里时启用断点] | 设置int3断点 [一次断点] 在某地址处 |
ba r/w/e长度 地址/寄存器/函数名/符号 | 设置硬件断点,r是读取时断下、w是写入时断下、e是执行时断下、长度(字节)可以是1,2,4,8(64bit CPU).注意当是e时长度必须是1。例如: ba w4 rip |
bl | 查看设置的所有断点(包含硬件断点) |
bc 断点编号 | 删除指定的断点,编号从0开始,编号为*表示所有断点 |
注:学习Windbg的使用请参考windbg的使用手册,手册和Windbg可执行文件在同一目录。