Windows内核

环境配置:

学习系统内核程序是如何运行的,分析程序的运行就必须用到调试,要调试就要能下断点

1720966652633

Windows系统当处于调试状态的时候,所有进程都会被挂起,除了一个调试子系统,用来与本机(Windbg)通过串口进行交互,传递调试指令

Win7+WmWare+Windbg双机调试看这个博客吧,很详细

https://cloud.tencent.com/developer/article/1470241

嘿嘿嘿配好了

1720968085117

注意注意,一定要在系统启动的时候,打开Windbg才能调试上!

X86的Windbg有个bug,就是寄存器窗口没有值,这个可以去github上面下载一个dll

1721009527519

放在同个文件夹即可

1721009550696

然后执行命令:

1
!WingDbg.regfix #注意大小写

值得注意的是,命令为:Wingdbg而不是Windbg,虽然我也不知道为什么

这下寄存器窗口就有值了

1721009601615

建议界面可以弄得和OD和x96dbg一样,然后点击保存工作界面:
1721010599761

然后再次进入Windbg的时候,如果没有自动还原工作界面,可以自行打开

1721010784767

Open Workspace

调试驱动程序

旧版Windbg

首先我们要编译Debug模式下的驱动程序,因为这样可以生成PDB文件,在Windbg下可以进行源码级调试

一般是在入口函数造一个异常,这样调试器就可以收到一个异常了

1
__asm int 3;

但是X64不允许内联汇编

所以我们用Windows提供的API,DbgBreakPoint这个API去造一个异常。但是Release版这玩意也会卡住,所以我们一般用KdBreakPoint

在Windbg可以查看API的具体实现,但是必须指定是哪个模块的

lm遍历所有模块

例如查看nt模块下的DbgBreakPoint

1
u nt!DbgBreakPoint

1721012470829

如何加载指定pdb?

1721012680333

打开符号路径,在原来的基础加个分号,然后把路径复制上去就行了,点击OK

1721012878068

加完路径以后,一启动带有KdBreakPoint的程序,就会自动跳出源码,这下就可以进行源码级的调试了

1721013622180

新版Windbg

新版的Windbg如何双机调试呢?

其实是差不多的(必须要管理员启动)

1721033416062

配置信息如上图所示

记得一定要在系统启动的时候连接,否则很容易卡死或者连不上,我也不知道为什么。还有就是无论是新版的还是旧版的,虚拟机里面都必须打开DebugView,这样Windbg才能接收到调试信息,这尼玛确实有点难受了,但是问题好像又不大,不知道如何解决就很淦啊

但是新版的我调试起来很卡,不知道为什么,所以我选择用旧版,顶多就是汇编那里没高亮

1721034266316

然后箭头指的方向是选定Disasmbly级别调试还是源码级别调试

Windbg常见指令:

1.符号命令

  • reload

    • 加载当前需要的符号
  • reload /f *

    • 加载所有的符号
  • 添加自己的pdb符号

1721043454938

打开符号路径,在最后加一个分号,然后把新的路径加上去即可

2. 进程命令

  • .attach pid 附加进程

  • .detach 取消附加

  • !process 0 0 列出系统进程信息,0代表遍历的信息复杂程度,0代表简单遍历。(必须要有符号)

  • !process 0 7 列出系统进程的详细信息(慎用,数据非常庞大)

  • !process EPROCESS 7 列出进程的详细信息

  • .process /p EPROCESS 进入该进程的上下文(强制切换线程使用)这个很有用,通过列出系统进程的信息,拿到PROCESS的编号,就可以强制进入这个进程,此时下的断点就是会断在这个进程。例如我想断在某个进程调用某个API,那么就强制切到这个进程,然后在这个API下断点,这样就是断在这个进程使用API这里了

    1
    bp /p eprocess address/func 
  • .process /i EPROCESS 运行这个指令,在按下g就可以断在指定的进程中

3. 线程

  • ~* 显示所有线程
  • ~. 显示引起异常的线程
  • ~* kb 显示所有线程的栈
  • .thread ETHREAD 进入该线程的上下文
  • **!thread ETHREAD 查看线程结构 **用!process EPROCESS 7遍历线程,就可以知道具体Thread的信息了

4.断点

  • bp address

  • bp model!func 符号断点 ( 如果符号未加载,bp 命令将无法设置断点,必须等到符号加载后重新设置。 )

  • bu model!func 符号断点( 适用于模块或符号尚未加载的情况。 )

  • bm model!fu*c 正则符号断点

  • ba w4/r4/e4/i4 address 内存断点
    w:写操作断点(write)

    r:读操作断点(read)

    e:执行断点(execute)

    i:输入/输出断点(input/output),一般用于端口访问
    后面的4是指字节数

  • bp /p eprocess address/func ( 这个命令用于设置一个进程范围的断点,只在特定进程内触发。(内核模式下有多个进程) )(这个很有用!否则会在全部进程只要调用这个函数就会断下!)

  • bp /t ehtread address/func ( 这个命令用于设置一个线程范围的断点,只在特定线程内触发。 )

  • ba /p /t

    进程线程断点

    1
    ba w4 /p fffffa8001d4b060 /t fffffa8001e3c080 0x00400000

    1721053898849

  • bl (Break List) bl 命令用于列出当前所有设置的断点及其状态。它提供了断点编号、地址、命令和条件等信息。

  • bc

    1
    2
    bc <断点编号>  #要删除的断点编号。
    bc * #删除所有断点。
  • be (Break Enable) 命令用于启用指定的断点。

    1
    be <断点编号>
  • bd ( Break Disable ) 命令用于禁用指定的断点。

    1
    bd <断点编号>

条件断点

设置断点:首先设置一个普通断点。

添加条件:使用 j 命令来添加条件。

使用 WinDbg 提供的 j 命令(条件分支命令)添加条件。

例1:

1
bp MyModule!MyFunction "j (poi(@esp+0x20) == 10) 'gc'; 'g'"

在这个示例中:

  • bp MyModule!MyFunction 设置在 MyFunction 上的断点。

  • poi(@esp+0x20) == 10 是条件,检查局部变量 x 是否等于 10。

    poi (Pointer to Integer)

    poi 操作符用于获取指定内存地址处的指针值。它可以用来解引用指针,即获取指针所指向的内存地址的值。

  • 'gc'; 'g'
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14

    是当条件满足和不满足时执行的命令。

    - `'gc'` (Go from conditional breakpoint) 表示当条件满足时,继续执行。
    - `'g'` 表示当条件不满足时,继续执行(即不触发断点)。





    例2:

    ```bash
    bp MyModule!MyFunction "j (@$t0 = dwo(MyModule!g_Var), @$t0 == 5) 'gc'; 'g'"

dwo(MyModule!g_Var) 获取全局变量 g_Var 的值。

@$t0 == 5 检查 g_Var 是否等于 5。

dwo (DWORD)
dwo 操作符用于获取指定内存地址处的双字(DWORD)值。一个 DWORD 是 4 字节。

@$t0 是一个 WinDbg 的伪寄存器,用于存储临时值。WinDbg 提供了一组伪寄存器(@$t0@$t19),可以在调试会话中用来存储和传递临时数据。

5. 跟踪命令

T指令单步执行

TA单步跟踪到指定地址,如果没有参数将运行到断点处

TB执行到分支指令,分支指令包括calls,return,jumps,counted,loops,and while loops

TC指令执行到call指令

WT Trace and Watch Data 一条强大的指令,可以对执行流程做分析

6. 内存命令

db 以一个字节显示值和ASSCII字符

dw 显示两个字节的值

dd 显示四个字节的值

dq 显示8个字节的值

dp 显示指针长度的值(32位等同于dd,64等同于dq)

qD 显示double实数(8字节)的值

df 显示float实数(4字节)的值

da 显示asscii值

du 显示unicode值

ds 显示ANSI_STRING值

dS 显示UNICODE_STRING的值

ddp: ddp 指令是一种内存显示命令,用于显示内存内容并尝试将其解释为指针值。ddpdd 命令的扩展,dd 命令用于以双字(DWORD)格式显示内存内容,而 ddp 则会显示内存中的每个双字(DWORD)并尝试将其解析为指针,指向的内容也会被显示。

dda: (Display memory as ASCII strings): dda 命令用于将内存内容以 ASCII 字符串格式显示。

ddu 命令用于将内存内容以 Unicode 字符串格式显示。

ddS 命令用于将内存内容以符号(symbol)格式显示。ddS 会尝试将内存地址解释为符号地址,并显示相应的符号信息。

7.修改内存

eb/ed/eD/ef/ep/eq/ew

ed address value

ea/eu

eza/ezu

修改寄存器是用r命令

8.堆栈命令

查看调用堆栈的信息

kv 3个参数 调用约定

kb 3个参数

kp 全部参数

9.其他命令

lm查看当前载入的模块

!peb 查看当前进程环境块 (PEB)

x 查看模块的符号,如 x mydriver!* FastIo ,显示所有与 * FastIo 匹配的符号列表

dt 查看类型数据,还可以用来查看模块类型的符号列表,如dt dgguarder!_IMAGE_DOS_HEADER 00400000
从 0x400000处查看 _IMAGE_DOS_HEADER

解析蓝屏命令:

  • !analyze -v

当蓝屏的时候,调用这条命令会分析蓝屏的原因。

这个能显示当时的参数和寄存器,调用堆栈,崩溃的原因(错误码),如果有pdb还会显示当时崩溃的代码的行数

还能显示是哪个驱动崩了

1721055498893

让电脑蓝屏时保存环境

如果没有双机调试的时候,碰巧发生了蓝屏,这时候就没办法分析错误原因了吗?当然不是

以Win7为例子,我们打开计算机的属性,点击高级系统设置,点击启动和故障恢复的设置,在系统失败这里设置将事件写入系统日志,完全内存转储(文件会很大),但是一般我们用的是小内存转储,这样省空间

1721055068322

然后dump下来的“案发现场”就会保存在这:
1721055836151

打开Windbg,加载这个dump文件就可以还原当时的情况了!

1721055859016

美中不足的是,没有pdb,但是可以根据偏偏移,用IDA定位到具体是哪个函数,也算是一个解决方法吧。

双机调试应用层程序

https://bbs.kanxue.com/thread-246449.html