先搭建内核开发环境

windbg驱动调试的环境搭建。

推荐阅读微软官方文档开始使用 WinDbg(内核模式) - Windows drivers | Microsoft Learn

windows系统内核调试 环境搭建(保姆级)_dsigntool-CSDN博客

下载 Windows 驱动程序工具包 (WDK) - Windows drivers | Microsoft Learn

Windows驱动程序逆向工程方法 - 🔰雨苁ℒ🔰

驱动开发:WinDBG 配置内核双机调试 - lyshark - 博客园

备忘录

反调试:

内核下的调试与反调试 - iBinary - 博客园

总结

一些要用到的命令。

推荐串行接口进行调试

1
\\.\pipe\com_2

符号表环境变量

1
_NT_SYMBOL_PATH         SRV*D:\Myself_Software\Windows_soft\symbols*http://msdl.microsoft.com/download/symbols

windbg内核调试操作

开启调试服务(需要关闭安全引导)

1
2
bcdedit /debug on
bcdedit /dbgsettings serial debugport:1 baudrate:115200

windbg attach kernel后重启即可

1
.reload #加载内核符号

windbg常用快捷键

F5 继续

F10 逐过程

F11 逐语句

Shift+F11 单步跳出

F7 运行到行

Ctrl + F9 在突出显示的行上切换启用断点状态

Shift + F9 添加断点

Ctrl+Shift+O 打开脚本

Ctrl+Shift+Enter 执行脚本

Ctrl+S 保存脚本

Alt+S、N 新脚本

控制台常用命令

其实控制台可以用鼠标点击选项进行操作。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
.hh 命令访问参考命令帮助
g 运行
pa [addr] 执行到
.symfix 设置符号表或添加本地符号(.symfix+ path)
.reload 加载符号表 (/f添加路径)
lm 列出所有模块
lm m name v 特性模块信息
x [Options] Module!Symbol 检查符号
x /D model!name 查找符号可用通配符*
sxe ld [name] 拦截模块的加载
e{b|d|q|a|f|D|a} Address [Values] 写入值到内存中
d{a|b|c|d|D|f|p|q|u|w|W} [addr] 打印内存中的值
bl 查看断点
bp 设置一个将一直处于活动状态的断点,直到其所在模块被卸载。
bp /w "@esp < 0x6ff9f8" [addr] 条件断点
bu 设置一个断点,该断点在卸载模块时未解析,并在重新加载模块时重新启用。
bm 为符号设置一个断点。 此命令适当地使用 bu 或 bp,并允许使用通配符(*)对匹配的每个符号(如类中的所有方法)设置断点。
bc 清除列表中的断点。 使用 bc * 清除所有断点。
bd 禁用断点。 使用 bd * 禁用所有断点。
be 启用断点。 使用 be * 启用所有断点。
ba <access> <size> <address> {options} 设置在访问内存位置时触发的断点(e 执行:当 CPU 从地址中提取指令时。r 读/写:当 CPU 读取或写入地址时。w write:当 CPU 写入地址时)
.bpcmds (显示断点命令)
dv 命令来显示给定帧的所有局部变量的名称和值
kp 显示堆栈和参数的完整列表。
kn 允许您查看堆栈以及旁边的帧信息。
!process 调试器扩展来显示或设置进程信息
dv dename!name 查与例程关联的区域设置变量
!process 0 0 显示所有进程的摘要信息。(0 27)输出全部
!thread 命令查看线程
.thread 设置当前线程
r 查看寄存器
!lmi 扩展显示有关模块的详细信息
!dh 扩展显示标头信息
lsf- [filename] 添加源码
.lsrcpath+ 设置本地源路径
.load [dllpath] 加载dll (加载位置待验证)
.chain 查看已加载的dll
脚本:
.scriptproviders(列出脚本提供程序)
.scriptload [jspath](加载脚本)
.scriptload(卸载脚本)
.scriptrun [jspath](运行脚本)
.scriptlist(列出已加载的脚本)

windbg脚本(摘自官方文档JavaScript 调试器脚本 - Windows drivers | Microsoft Learn)

用vscod编写时把C:\Program Files (x86)\Windows Kits\10\Debuggers\x64\winext\JsProvider.d.ts放到当前目录,并在脚本文件前添加

1
/// <reference path="JsProvider.d.ts" />

js脚本可调用api调试器数据模型 - 代码命名空间 - Windows drivers | Microsoft Learn

一个框架,来自官方。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
// Root of Script
host.diagnostics.debugLog("***>; Code at the very top (root) of the script is always run \n");


function initializeScript() //加载脚本时执行
{
// Add code here that you want to run every time the script is loaded.
// We will just send a message to indicate that function was called.
host.diagnostics.debugLog("***>; initializeScript was called \n");
}

function invokeScript() //执行脚本时执行
{
// Add code here that you want to run every time the script is executed.
// We will just send a message to indicate that function was called.
host.diagnostics.debugLog("***>; invokeScript was called \n");
}


function uninitializeScript() //卸载脚本时执行(主函数)
{
// Add code here that you want to run every time the script is unloaded.
// We will just send a message to indicate that function was called.
host.diagnostics.debugLog("***>; uninitialize was called\n");
}


function main() //只是一个函数而已没别的作用
{
// main is just another function name in JavaScript
// main is not called by .scriptload or .scriptrun
host.diagnostics.debugLog("***>; main was called \n");
}

条件断点

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
/// <reference path="JsProvider.d.ts" />
// Use JavaScript strict mode
"use strict";

// Define the invokeScript method to handle breakpoints

function invokeScript()
{
var ctl = host.namespace.Debugger.Utility.Control;

//Get the address of my string
var address = host.evaluateExpression("pszCaption");

// The open and save dialogs use the same function
// When we hit the open dialog, continue.
// When we hit the save dialog, break.
if (host.memory.readWideString(address) == "Open") {
// host.diagnostics.debugLog("We're opening, let's continue!\n");
ctl.ExecuteCommand("gc");
}
else
{
//host.diagnostics.debugLog("We're saving, let's break!\n");
}
}

windbg的脚本开发还是挺困难的,教程也比较少微软自己提供了一个项目用于学习microsoft/WinDbg-Samples: Sample extensions, scripts, and API uses for WinDbg.

另外一个使用脚本hugsy/windbg_js_scripts: Toy scripts for playing with WinDbg JS API

进阶使用windbgtips (@windbgtips) / X

时间旅行调试TTD

[InsightEngineering/Time Travel Debugging (TTD) at main · DebugPrivilege/InsightEngineering](https://github.com/DebugPrivilege/InsightEngineering/tree/main/Time Travel Debugging (TTD))

时间旅行调试 - 概述 - Windows drivers | Microsoft Learn

[原创]TTD调试与ttd-bindings逆向工程实践-软件逆向-看雪-安全社区|安全招聘|kanxue.com

驱动开发环境配置

微软的教程非常的好用,下面我只提出几点需要记住的比较重要的事项。

编写 Hello World Windows 驱动程序(内核模式) - Windows drivers | Microsoft Learn

我这里使用的是windows11 + vs2022的主机和用于调试的windows 10的虚拟机。开发框架采用KWDF。

KWDF是以调用回调函数的来执行操作的,每一个函数都对应一个事件,通过事件的触发来调用回调函数进行数据处理。由于封装的方法较多,开发比较简单。

根据微软的教程,配置好环境,我们在编写完后可以用vs直接把驱动部署在虚拟机上,其中的驱动文件位置在

1
%systemdrive%\drivertest\drivers

把devcon.exe放到虚拟机的驱动位置下,通过命令来进行驱动程序的安装。(需要管理员权限)

1
devcon install XXX.inf root\XXX

root\后面的内容可以在inf文件中找到(驱动程序路径)

1
2
[Standard.NT$ARCH$]
%KmdfHelloWorld.DeviceDesc%=KmdfHelloWorld_Device, Root\KmdfHelloWorld

源码调试时用,把pdb文件所在位置设置为本地源路径,.reload后会自动加载源码的符号。

1
.lsrcpath+ 设置本地源路径

在windbg的左上角点击文件,选择source file添加源代码(直接显示源代码窗口),或者用lsf- [filename] 添加源码(无窗口)。根据微软文档的解释,如果使用 WinDbg,只要程序计数器位于调试器具有其源信息的代码中,就会立即显示“源”窗口。即用源代码进行调试。在源模式下调试 - Windows drivers | Microsoft Learn

跟随官方文档,我们的环境差不多安装完成,接下来正式进行代码编写。

驱动安装

一般驱动

工具安装:Downloads:Driver Loader

自注册服务:

1
2
3
4
sc create [name] type= kernel start= demand binPath= [path.sys]
sc start [name]
sc stop [name]
sc delete [name]

inf文件:

这里令驱动名字为Driver1,文件为Driver1.sys和Driver1.inf(由vs生成)

devcon安装

1
devcon.exe install XXX.inf root\[name]

右键文件安装也可行。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
;
; Driver1.inf
;

[Version]
Signature = "$WINDOWS NT$"
Class = System ; TODO: specify appropriate Class
ClassGuid = {4d36e97d-e325-11ce-bfc1-08002be10318} ; TODO: specify appropriate ClassGuid
Provider = %ManufacturerName%
CatalogFile = Driver1.cat
DriverVer = ; TODO: set DriverVer in stampinf property pages
PnpLockdown = 1

[DestinationDirs]
DefaultDestDir = 13

[SourceDisksNames]
1 = %DiskName%,,,""

[SourceDisksFiles]
Driver1.sys = 1,,

;*****************************************
; Install Section
;*****************************************

[Manufacturer]
%ManufacturerName% = Standard,NT$ARCH$.10.0...16299 ; %13% support introduced in build 16299

[Standard.NT$ARCH$.10.0...16299]
%Driver1.DeviceDesc% = Driver1_Device, Root\Driver1 ; TODO: edit hw-id

[Driver1_Device.NT]
CopyFiles = File_Copy

[File_Copy]
Driver1.sys

;-------------- Service installation
[Driver1_Device.NT.Services]
AddService = Driver1,%SPSVCINST_ASSOCSERVICE%, Driver1_Service_Inst

; -------------- Driver1 driver install sections
[Driver1_Service_Inst]
DisplayName = %Driver1.SVCDESC%
ServiceType = 1 ; SERVICE_KERNEL_DRIVER
StartType = 3 ; SERVICE_DEMAND_START
ErrorControl = 1 ; SERVICE_ERROR_NORMAL
ServiceBinary = %13%\Driver1.sys

[Driver1_Device.NT.Wdf]
KmdfService = Driver1, Driver1_wdfsect

[Driver1_wdfsect]
KmdfLibraryVersion = $KMDFVERSION$

[Strings]
SPSVCINST_ASSOCSERVICE = 0x00000002
ManufacturerName = "huanghunr" ;TODO: Replace with your manufacturer name
DiskName = "Driver1 Installation Disk"
Driver1.DeviceDesc = "Driver1 Device"
Driver1.SVCDESC = "Driver1 Service"

CTF中的驱动开发结构分析与学习

下面通过一个以diviceiocontrol进行通信的程序为例子分析一个驱动程序的一些主要要素。

Deviceiocontrol:应用程序与驱动程序通信 DeviceIoControl - 沉疴 - 博客园

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
#include <ntddk.h>
#include <wdf.h>
#include <initguid.h>

// 自定义 IOCTL 控制码,用于deviceiocontrol通信执行操作 参数(irp生成函数,设备对象类型,ioct控制码(0x800--0xfff),
// 访问权限)
#define Driver_IOCT CTL_CODE(FILE_DEVICE_UNKNOWN, 0x800, METHOD_BUFFERED, FILE_ANY_ACCESS)

// 回调函数声明
DRIVER_INITIALIZE DriverEntry; //声明入口
EVT_WDF_DRIVER_DEVICE_ADD HelloKMDFEvtDeviceAdd; //声明在设备连接时调用的函数
EVT_WDF_IO_QUEUE_IO_DEVICE_CONTROL HelloKMDFEvtIoDeviceControl; //deviceiocontrol消息处理函数

// 驱动入口 参数(驱动对象,注册表路径)
NTSTATUS DriverEntry(PDRIVER_OBJECT DriverObject, PUNICODE_STRING RegistryPath) {
WDF_DRIVER_CONFIG config; //WDF_DRIVER_CONFIG结构体,保存了驱动的一些信息
WDF_DRIVER_CONFIG_INIT(&config, HelloKMDFEvtDeviceAdd); //初始化回调函数(把回调函数注册到驱动的信息中)

KdPrint(("HelloKMDF: DriverEntry\n")); //输出信息
//KMDF创建并注册驱动对象,这代表我们已经完成所有回调函数的部署,KMDF框架自动帮我们注册驱动。
return WdfDriverCreate(DriverObject, RegistryPath, WDF_NO_OBJECT_ATTRIBUTES, &config, WDF_NO_HANDLE);
}

// 设备添加时回调(由WDF_DRIVER_CONFIG_INIT()注册)
NTSTATUS HelloKMDFEvtDeviceAdd(WDFDRIVER Driver, PWDFDEVICE_INIT DeviceInit) {
UNREFERENCED_PARAMETER(Driver);

WDFDEVICE device;
NTSTATUS status;

// 设置设备名字
DECLARE_CONST_UNICODE_STRING(devName, L"\\Device\\HelloKMDF");
WdfDeviceInitAssignName(DeviceInit, &devName);

// 创建设备
status = WdfDeviceCreate(&DeviceInit, WDF_NO_OBJECT_ATTRIBUTES, &device);
if (!NT_SUCCESS(status)) return status;

// 创建默认队列处理 DeviceIoControl
WDF_IO_QUEUE_CONFIG ioQueueConfig;
WDF_IO_QUEUE_CONFIG_INIT_DEFAULT_QUEUE(&ioQueueConfig, WdfIoQueueDispatchSequential);
ioQueueConfig.EvtIoDeviceControl = HelloKMDFEvtIoDeviceControl;

return WdfIoQueueCreate(device, &ioQueueConfig, WDF_NO_OBJECT_ATTRIBUTES, WDF_NO_HANDLE);
}

// 处理用户态 IOCTL 请求
VOID HelloKMDFEvtIoDeviceControl(WDFQUEUE Queue, WDFREQUEST Request,
size_t OutputBufferLength, size_t InputBufferLength,
ULONG IoControlCode) {
UNREFERENCED_PARAMETER(Queue);
UNREFERENCED_PARAMETER(OutputBufferLength);
UNREFERENCED_PARAMETER(InputBufferLength);

if (IoControlCode == IOCTL_HELLOKMDF) {
KdPrint(("HelloKMDF: 收到 IOCTL_HELLOKMDF 指令!\n"));
}

WdfRequestComplete(Request, STATUS_SUCCESS);
}

之后再去了解一下其他通信,总结点模板。