在内核调试会话中设置用户态断点

本文介绍如何在内核调试模式下调试用户态模块,包括设置断点的区别、作用范围的不同及具体步骤。通过实例演示如何针对特定进程设置断点。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

转: http://advdbg.org/blogs/advdbg_system/articles/1492.aspx
   使用内核调试会话也可以执行一些用户态调试任务,比如向位于用户态的模块设置断点。但这样做与使用用户态调试器有什么不同呢?我们就以向NTDLL.dll模块的ZwTerminateProcess函数(Stub)为例谈谈二者的区别。
   区别一、在内核调试会话中设置这个断点的“难度”略大些。这是因为NTDLL不属于内核态的模块,所以内核会话通常不会加载这个模块(的符号),因此当执行bp命令时很可能被自动蜕化为bu命令。
   0: kd> bp ntdll!ZwTerminateProcess
   Bp expression 'ntdll!ZwTerminateProcess' could not be resolved, adding deferred bp
   恢复执行后,一般的操作也不会触发调试器来加载NTDLL模块和解决这个未决的断点。因此再中断下来,重新加载符号也可能没有用:
   2: kd> .reload
   Connected to Windows Vista 6000 x86 compatible target, ptr64 FALSE
   Loading Kernel Symbols
   ................................................................................   .............................................
   Loading User Symbols
   Loading unloaded module list
   ........
   2: kd> bl
   0 eu             0001 (0001) (ntdll!ZwTerminateProcess)
   使用.reload命令强制加载这个模块也不那么容易:
   0: kd> .reload /s /f ntdll.dll
   "ntdll.dll" was not found in the image list.
   Debugger will attempt to load "ntdll.dll" at given base 00000000.
   Please provide the full image name, including the extension (i.e. kernel32.dll)
   for more reliable results.Base address and size overrides can be given as
   .reload <image.ext>=<base>,<size>.
   Unable to add module at 00000000
   那么该如何设置呢?方法一需要以下几步:
   1.A 使用!process命令显示当前进程:
   kd> !process
   PROCESS 80af22a0  SessionId: none  Cid: 0000    Peb: 00000000  ParentCid: 0000
    DirBase: 00039000  ObjectTable: e1001e38  HandleCount: 240.
    Image: Idle
   如果像上面这样是IDLE进程或者是System这些没有用户态的进程,那么就需要执行下面一步,否则跳到1.C。
   1.B 使用!process 0 0命令列出所有进程,然后选一个普通的Windows进程,并切换到这个进程:
   kd> !process 0 0
   **** NT ACTIVE PROCESS DUMP ****
   ...
   PROCESS 82748330  SessionId: 0  Cid: 0110    Peb: 7ffde000  ParentCid: 059c
    DirBase: 13076000  ObjectTable: e1a55640  HandleCount:  72.
    Image: notepad.exe
kd> .PROCESS 82748330
Implicit process is now 82748330
WARNING: .cache forcedecodeuser is not enabled
   1.C 执行.reload或.reload /user重新加载符号:
kd> .reload
Connected to Windows XP 2600 x86 compatible target, ptr64 FALSE
Loading Kernel Symbols
................................................................................................
Loading User Symbols
...............................
Loading unloaded module list
..............................
kd> lm m ntdll
start    end        module name
7c800000 7c8c3000   ntdll      (pdb symbols)          d:/symbols/ntdll.pdb/9A2A73EBE8194059A14361915257B0B01/ntdll.pdb

   第二种看起来可能更费事的方法就是在系统服务的内核函数设置断点,断点命中后,执行栈回溯这样的命令,再执行.reload(加/user会省些时间,不是必须)。例如:
0: kd> bp nt!NtTerminateProcess
0: kd> g
Breakpoint 2 hit
nt!NtTerminateProcess:
81a1b043 8bff            mov     edi,edi
0: kd> kv
ChildEBP RetAddr  Args to Child              
9f272d54 8188c96a 00000000 00000000 0021f998 nt!NtTerminateProcess
9f272d54 77c20f34 00000000 00000000 0021f998 nt!KiFastCallEntry+0x12a (FPO: [0,3] TrapFrame @ 9f272d64)
WARNING: Frame IP not in any known module. Following frames may be wrong.
0021f998 7682d873 00000000 77e8f3b0 ffffffff 0x77c20f34
...
0: kd> .reload
Connected to Windows Vista 6000 x86 compatible target, ptr64 FALSE
Loading Kernel Symbols
..............................................................................................................................
Loading User Symbols
..................
再执行kv:
0: kd> kv
ChildEBP RetAddr  Args to Child              
9f272d54 8188c96a 00000000 00000000 0021f998 nt!NtTerminateProcess
9f272d54 77c20f34 00000000 00000000 0021f998 nt!KiFastCallEntry+0x12a (FPO: [0,3] TrapFrame @ 9f272d64)
0021f978 77c20580 77bfa35f 00000000 00000000 ntdll!KiFastSystemCallRet (FPO: [0,0,0])
0021f97c 77bfa35f 00000000 00000000 00af0e70 ntdll!NtTerminateProcess+0xc (FPO: [2,0,0])
0021f998 7682d872 00000000 77e8f3b0 ffffffff ntdll!RtlExitUserProcess+0x39 (FPO: [Non-Fpo])
此时可以确信内核调试会话已经加载NTDLL的符号了,再显示断点:
0: kd> bl
0 e 77c20574     0001 (0001) ntdll!NtTerminateProcess
2 e 81a1b043     0001 (0001) nt!NtTerminateProcess
这个显示表明内核调试会话已经落实了这个用户态的断点。
如果是在加载NTDLL模块后再执行bp命令,恢复执行后,KD会有一个提示告诉我们它成功的向断点位置写入了INT 3。
0: kd> bp ntdll!ZwTerminateProcess
0: kd> bl
0 e 77c20574  0001 (0001) ntdll!NtTerminateProcess
0: kd> g
KD: write to 77c20574  ok
相对而言,如果是在用户态调试会话中,因为NTDLL会被映射到所有用户态进程中,而且ZwTerminateProcess是导出的函数,所以bp ntdll!ZwTerminateProcess会非常顺利的执行。
区别二、断点的作用范围不同,在内核调试会话中设置的ntdll!NtTerminateProcess断点会影响所有进程(可能有特例),而在用户态调试中对这个位置设置的断点只对当前进程有效。举例来说,刚才在内核调试会话中设置bp断点时的当前进程是notepad,但是当我们关闭计算器进程时这个断点也会命中。甚至当我们新启动一个 WinMine程序,然后关闭它时,断点也会命中。
相对而言,如果是在调试notepad进程的用户态调试会话中对ntdll!NtTerminateProcess设置一个断点,那么这绝不会影响其它进程。
那么为什么有这个差异呢?
首先解释一下,为什么在内核调试会话中设置的断点会影响所有进程。还是通过试验来说明,我们先想办法观察到我们设置的断点所对应的INT 3指令。当KD落实我们的断点后,将目标再中断到调试器,这时无论是直接观察线性地址还是物理地址,都看不到INT 3:
1: kd> dd 77c20574
77c20574  000152cc 0300ba00 12ff7ffe 900008c2
77c20584  000153b8 0300ba00 12ff7ffe 900008c2
77c20594  000154b8 0300ba00 12ff7ffe 00498dc3
77c205a4  000155b8 0300ba00 12ff7ffe 00498dc3
77c205b4  000156b8 0300ba00 12ff7ffe 00498dc3
77c205c4  000157b8 0300ba00 12ff7ffe 900010c2
77c205d4  000158b8 0300ba00 12ff7ffe 900018c2
77c205e4  000159b8 0300ba00 12ff7ffe 900010c2
0: kd> !pte 77c20574    
               VA 77c20574
PDE at 00000000C0601DF0    PTE at 00000000C03BE100
contains 000000001C9AC867  contains 000000001DDDD025
pfn 1c9ac ---DA--UWEV    pfn 1dddd ----A--UREV
0: kd> !dd 1dddd574
#1dddd574 000152b8 0300ba00 12ff7ffe 900008c2
#1dddd584 000153b8 0300ba00 12ff7ffe 900008c2
#1dddd594 000154b8 0300ba00 12ff7ffe 00498dc3
#1dddd5a4 000155b8 0300ba00 12ff7ffe 00498dc3
#1dddd5b4 000156b8 0300ba00 12ff7ffe 00498dc3
#1dddd5c4 000157b8 0300ba00 12ff7ffe 900010c2
#1dddd5d4 000158b8 0300ba00 12ff7ffe 900018c2
#1dddd5e4 000159b8 0300ba00 12ff7ffe 900010c2
这是因为调试器在将目标中断到调试器之前会恢复已经设置的断点,按Ctrl+Alt+D启用WinDBG与KD的通信过程后就可以看到这样的信息:
DbgKdRestoreBreakPoint(1) returns 00000000
当恢复执行时,WinDBG会重新把断点写入:
DbgKdWriteBreakPoint(77c20574) returns 00000000, 1

那么如何观察到写入的INT 3呢?一种很惬意的方法就是使用ITP这样的硬件调试器,用了ITP,对付这样的任务真是手到擒来(图1)。

图1 使用硬件调试器观察断点指令(0xCC)
因为NTDLL是映射到所有进程中的,所以每个进程执行NtTerminateProcess函数时都会撞见这个0xCC,于是乎这个断点对所有进程都起作用也就在情理之中了。
下面再说说另一种情况,也就是在用户态调试器中对ntdll!NtTerminateProcess设置断点,难道这时就没有把0xCC写在大家都会“撞见”的地方么?的确如此。
我们在内核调试会话中使用bc *命令清除所有断点,并恢复执行一次,而且通过ITP观察确保刚才的0xcc已经不在。然后在目标系统中启动系统中自带的NTSD来调试计算器程序,并使用bp ntdll!NtTerminateProcess设置一个断点。恢复执行一次,以便让调试器写入这个断点。然后退出计算器程序,这时计算器程序会中断到 NTSD,NTSD中不做分析,直接用g命令恢复执行,这下,我们前面设置的nt!NtTerminateProcess断点会命中,也就是中断到内核调试器中。
在内核调试器中,观察nt!NtTerminateProcess所对应的线性地址:
1: kd> dd 77c20574
77c20574  000152cc 0300ba00 12ff7ffe 900008c2
77c20584  000153b8 0300ba00 12ff7ffe 900008c2
77c20594  000154b8 0300ba00 12ff7ffe 00498dc3
77c205a4  000155b8 0300ba00 12ff7ffe 00498dc3
77c205b4  000156b8 0300ba00 12ff7ffe 00498dc3
77c205c4  000157b8 0300ba00 12ff7ffe 900010c2
77c205d4  000158b8 0300ba00 12ff7ffe 900018c2
77c205e4  000159b8 0300ba00 12ff7ffe 900010c2
睁大眼睛看那个0xCC,对的,这里的确有0xCC。因为内核断点已经取消了,这一定是用户态调试器写入的。
接下来的问题是,既然这里有0xCC,那么为什么不影响其它进程呢?注意这个线性地址与前面的一模一样。
其中的奥妙在于这个线性地址已经不再是前面那个物理地址了:
1: kd> !pte 77c20574
               VA 77c20574
PDE at 00000000C0601DF0    PTE at 00000000C03BE100
contains 00000000012E0867  contains 00000000049BD025
pfn 12e0 ---DA--UWEV    pfn 49bd ----A--UREV
虽然还同是一个线性地址,但是它现在对应的物理地址变成了49bd574。观察这个物理地址,其内容与刚才使用线性地址的得到的结果是一样的:
1: kd> !dd 49bd574
# 49bd574 000152cc 0300ba00 12ff7ffe 900008c2
# 49bd584 000153b8 0300ba00 12ff7ffe 900008c2
# 49bd594 000154b8 0300ba00 12ff7ffe 00498dc3
# 49bd5a4 000155b8 0300ba00 12ff7ffe 00498dc3
# 49bd5b4 000156b8 0300ba00 12ff7ffe 00498dc3
# 49bd5c4 000157b8 0300ba00 12ff7ffe 900010c2
# 49bd5d4 000158b8 0300ba00 12ff7ffe 900018c2
# 49bd5e4 000159b8 0300ba00 12ff7ffe 900010c2
而此时,物理地址1dddd574那里根本没有0xCC。
说到这里,谜团基本揭开了。事实上, 对于一个普通的进程,系统会把NTDLL的代码映射给它,如果这个进程始终很普通,那么它便会永远使用这份映射过来的代码。但是当它要修改代码时,系统会执行所谓的 Copy on Write动作,为其复制一份,让它来写。结合我们的情况,当在用户态调试会话中向NTDLL中设置断点时,系统为其复制了一份代码,让它去写,因此它写入的断点只有它自己“撞的到”,不会影响其它进程。但是当在内核会话中写入断点时,因为是内核调试引擎执行的写动作,所以没有触发Copy on Write,因此KD写入的断点写在了公共的代码上,会影响到使用这个公共代码的所有进程。




  怎么在内核调试模式调应用程序(驱动程序,应用程序一锅汇了)
转: http://bbs.driverdevelop.com/htm_data/125/0901/115354.html
先看一下上面的张银奎大侠的文章,然后我们再分析一下怎么做。
看了上面的文章,也许你已经成功了,那就没必要看下面的文字 ,如果你还没有明白,我们就实操一下:

环境:
双机内核模式调试状态,即通过windbg连接另一台机器调试驱动,不管对方是真机还是虚拟机。此时,我们可以调试驱动程序(我假设你已经会调驱动,并且环境准备好了,想调的应用程序的pdb文件也准备好了).

通常,我们可能会在目标机(被调试的机器上)再运行一个windbg或者用vc的远程调式工具进行调试。但是我现在的所有工程,包括测试程序都是用winddk编译的,改用vc调试比较麻烦,现在就是想在调驱动的同时继续调试我的测试程序,这样方便又直接。


现在处于kd> 状态 ,可以直接设置驱动程序的断点,但是你要是设置应用程序的断点,会收到错误消息,说找不到这个模块。好,我们就来记它能找到:

!process 0 0
会出列进程相关信息,如
PROCESS 820a36d0  SessionId: 0  Cid: 07cc    Peb: 7ffd9000  ParentCid: 01d4
    DirBase: 09b00220  ObjectTable: e18ecc48  HandleCount: 104.
    Image: alg.exe

PROCESS 820d2020  SessionId: 0  Cid: 0118    Peb: 7ffdf000  ParentCid: 00c4
    DirBase: 09b00260  ObjectTable: e19252b0  HandleCount: 470.
    Image: explorer.exe

PROCESS 82232b58  SessionId: 0  Cid: 016c    Peb: 7ffdf000  ParentCid: 0118
    DirBase: 09b002c0  ObjectTable: e188c9d8  HandleCount:  76.
    Image: epsng_certd.exe

PROCESS 82170020  SessionId: 0  Cid: 00d8    Peb: 7ffde000  ParentCid: 0118
    DirBase: 09b002a0  ObjectTable: e1916e98  HandleCount:  36.
    Image: VMwareTray.exe

PROCESS 821353d8  SessionId: 0  Cid: 0170    Peb: 7ffdc000  ParentCid: 0118
    DirBase: 09b002e0  ObjectTable: e1c8e138  HandleCount: 128.
    Image: VMwareUser.exe

找到你关心的进程 ,如 explorer.exe ,它的进程 PROCESS 值,其实就是EPROCESS, 为 820d2020  
然后,你要切换进程上下文

.Process  820d2020   注意:这里的Process 前面有一个点,不要看成你的显示器上的灰尘

这样,就处于这个进程上下文了。

.reload /user  加载用户模式符号文件  (保证你的符号文件处于正确的目录中)

然后就可以直接 下断点了,比如

bp explorer!xxxxfunc

其它的没啥区别了
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值