使用内核调试会话也可以执行一些用户态调试任务,比如向位于用户态的模块设置断点。但这样做与使用用户态调试器有什么不同呢?我们就以向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 SymbolsLoading unloaded module list
........
2: kd> bl0 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: 0000DirBase: 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: 059cDirBase: 13076000  ObjectTable: e1a55640  HandleCount:  72.Image: notepad.exekd> .PROCESS 82748330
Implicit process is now 82748330
WARNING: .cache forcedecodeuser is not enabled1.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> gBreakpoint 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> bl0 e 77c20574     0001 (0001) ntdll!NtTerminateProcess2 e 81a1b043     0001 (0001) nt!NtTerminateProcess

这个显示表明内核调试会话已经落实了这个用户态的断点。

如果是在加载NTDLL模块后再执行bp命令,恢复执行后,KD会有一个提示告诉我们它成功的向断点位置写入了INT 3。

0: kd> bp ntdll!ZwTerminateProcess
0: kd> bl0 e 77c20574  0001 (0001) ntdll!NtTerminateProcess0: 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 900010c20: kd> !pte 77c20574    VA 77c20574
PDE at 00000000C0601DF0    PTE at 00000000C03BE100
contains 000000001C9AC867  contains 000000001DDDD025
pfn 1c9ac ---DA--UWEV    pfn 1dddd ----A--UREV0: 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(附注,执行这个命令前,需要切换windbg到目标进程)。因为内核断点已经取消了,这一定是用户态调试器写入的。

接下来的问题是,既然这里有0xCC,那么为什么不影响其它进程呢?注意这个线性地址与前面的一模一样。

其中的奥妙在于这个线性地址已经不再是前面那个物理地址了:

1: kd> !pte 77c20574VA 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写入的断点写在了公共的代码上,会影响到使用这个公共代码的所有进程。

用windbg内核模式调试用户态程序相关推荐

  1. windbg 如何再内核模式调试用户空间的程序

    1:使用!process 0 0 获取用户空间的所有的进程的信息 !process 0 0 **** NT ACTIVE PROCESS DUMP ****     PROCESS 80a02a60  ...

  2. 使用Windbg内核调试连接调试用户态程序的方法

    1. 中断目标计算机: 2. kd>!process 0 0 3. 找到要调试的程序对应进程: 4. kd>.process /i TARGETPROCESSADDRESS 5. kd&g ...

  3. cs寄存器 x86 特权模式_Windows操作系统管理进程和线程:内核模式和用户模式

    根据前面的介绍,NT内核会把操作系统的代码和数据映射到系统中所有进程的内核空间中.这样,每个进程内的应用程序代码便可以很方便地调用内核空间中的系统服务.这里的"很方便"有多层含义, ...

  4. 用户态程序调用系统态程序-快速系统调用

    在调试程序中,经常发现程序最后会调用到系统态的程序.这个过程是怎样的?用户空间的程序怎样进行系统调用,在此过程中是怎样进入和退出内核的. 根据运行状态和执行代码所在的内存空间的不同,CPU既可以运行于 ...

  5. 理解Windows内核模式与用户模式(新)

    版权声明:本文为博主原创文章,未经博主允许不得转载.  1.基础 运行 Windows 的计算机中的处理器有两个不同模式:"用户模式"和"内核模式".根据处 ...

  6. 理解Windows内核模式与用户模式

    内核层次架构 windows程序运行分为内核模式和用户模式,内核模式可以访问所有的内存地址空间, 并且可以访问所有的CPU指令.一般程序运行在用户模式, 通过系统调用切换到内核模式执行系统功能,Win ...

  7. windows用户态程序的Dump

    熟悉Linux的开发人员都知道,在Linux下开发程序,如果程序崩溃了,可以通过配置Core Dump,来让程序崩溃的瞬间产生一个Dump文件,然后通过dump文件来调试程序为什么崩溃.但是windo ...

  8. 用户模式 内核模式 linux,linux – “内核模式”和“用户模式”硬件...

    内核模式和用户模式是硬件功能,特别是处理器的功能.专为中高端系统(PC,功能手机,智能手机,除最简单的网络设备之外的所有系统--)设计的处理器都包含此功能.内核模式可以使用不同的名称:管理程序模式,特 ...

  9. LibcarePlus用户态程序热补丁

    LibcarePlus https://gitee.com/openeuler/docs/edit/stable2-20.03_LTS_SP1/docs/zh/docs/Virtualization/ ...

最新文章

  1. 程序员转正述职报告_程序员转正述职报告范文
  2. MVC Html.AntiForgeryToken() 防止CSRF攻击
  3. Nacos处理服务变更通知
  4. 3516a 自带的ive 算子的运行情况分析
  5. 抖音快闪PPT制作教程
  6. Eclipse_设置JSP模板
  7. 学校计算机房网络的拓扑结构一般采用,XX学校机房建设规划方案
  8. MySQL 线程池[2021-06-26]
  9. PDF怎么转换成JPG图片?教你如何快速转换
  10. 个人独资企业缴纳税种及税率
  11. 公众号配图在哪里找?快来看看这里
  12. DLM learning materials
  13. 数据结构与算法之美 | 别怕,有我!KMP 算法详解
  14. 解决active样式在ios手机上没有生效的问题
  15. 请教:关于爬取歌词的一点疑惑
  16. 爬虫漫游指南:HTTP/2 网站爬取
  17. 【59元 第二件1元】SIMEITOL/姿美堂玛卡片 蓝玛咖片MACA 60片
  18. Bruce Lee, My Hero!
  19. 投资 - 指标介绍:MA、WMA、EMA
  20. 鸿蒙不用百度网盘,百度网盘限速有救了!官方新出2种方法,不用开会员

热门文章

  1. SUBMAIL邮件平台API接口-Mail/xsend
  2. 《天龙八部》之《少年游》
  3. 图表嵌入到数据表格下方_如何在excel图表下方添加数据表 如何在excel图表中显示数值...
  4. 哈工大软件构造期末复习1
  5. 哈工大软件构造课程知识点总结(二)
  6. Unity3D插件 AnyPortrait 2D骨骼动画制作
  7. ad中按钮开关的符号_电工最常用电气元件实物图及对应符号
  8. 无法安装此计算机不存在英特尔,win10系统提示无法安装驱动程序,此计算机中没有Intel适配器怎么办...
  9. 时间管理的基础是精力管理
  10. STGCN的源码分析