WinDbg教程-[1]、调试基础(用户模式)

在前面的文章中,介绍了如何使用WinDbg分析蓝屏原因

https://www.cnblogs.com/zhaotianff/p/15150244.html

 

不过那会都是在网上找的资料,东拼西凑出来,并没有系统的去学习WinDbg。

最近在学习内核开发这一块的内容,刚好要用到WinDbg,所以这里找资料进行系统的入门一下,做个总结。

 

本文内容是基于用户模式下的调试,后面的文章中,会介绍内核模式下的调试。

 

Windows调试工具(Debugging Tools for Windows)

Debugging Tools for Windows调试器工具以及软件包中调试器的相关文档组成。这个工具包可以作为Windows SDK或者WDK的一部分安装。

工具包中包括四个调试器:Cdb.exe、Ntsd.exe、Kd.exeWinDbg.exe

本文只详细介绍WinDbg,其它工具只做简单介绍。

 

Cdb.exe和Ntsd.exe

CdbNtsd用户模式的、基于控制台的调试器。它们能够被附加到进程上,就像别的用户模式调试器一样。这两者都有控制台用户界面一键入一条命令,得到一个回应,如此这般地重复。这两者之间唯一的区别在于,如果从控制台窗口启动,Cdb会直接用原来那个窗口,而Ntsd会打开一个新的窗口。其他方面它们都一样。

 

Kd.exe

Kd是具有控制台用户界面的内核调试器,能够被附加到本地内核或者另一台机器上。

 

WinDbg.exe

WinDbg是唯一一个具有图形用户界面的调试器。根据从菜单所做的选择,或者启动时指定的命令行参数,它能够进行用户模式和内核模式调试。

本文是基于用户模式的调试。

 

 

WinDbg简介

像平常在Visual Studio中调试时,大部分的功能是借助菜单或按钮实现,WinDbg建立在命令之上。

用户输入一个命令,调试器用文本描述命令执行的结果给出响应。

在GUI模式下,一些结果用专门的窗口进行显示,比如局部变量、栈、线程等。

正是因为这种调试模式,所以WinDbg学习的门槛就要相对高一些,得记这些命令。

 

WinDbg支持三类命令:

1、内部命令

这些命令内建于调试器之中,操作被调试的目标

2、元命令

这些命令以(.)开头,操作调试进程自身,不直接操作被调试的目标。

3、扩展命令

这些命令以(!)开头,为调试器提供了很多功能。所有扩展命令均在外部DLL中实现。 默认情况下,调试器加载一组预定义的扩展DLL,但是通过使用.load命令可以从Debugger目录或其他目录中加载更多DLL。

说明:WinDbg是大小写不敏感的,所以在输入命令时,大小写都可以。 

 

WinDbg基础调试(用户模式)

有两种方法可以初始化用户模式的调试

1、先打开WinDbg,再通过在“ 文件 ”菜单上,选择“ 启动可执行文件”。程序运行后,会自动附加到WinDbg

2、先运行程序,再打开WinDbg,然后通过“文件”菜单上,选择“附加到进程”,通过选择进程列表里的进程进行附加。

 

这里我们以notepad(Windows记事本)为例进行演示

 

1、打开WinDbg

2、启动notepad

在“ 文件 ”菜单上,选择“ 启动可执行文件”。 在“启动可执行文件”对话框中,输入C:\Windows\System32\notepad.exe。 (notepad.exe 文件通常位于 C:\Windows\System32.) 对于 “文件名”,请输入 notepad.exe。 选择“打开” 。进程创建后,会自动中断,WinDbg会自动添加一个断点。

说明:命令窗口是我们主要关注的窗口,它应该始终保持打开状态。这个窗口会显示各种命令的响应结果。

如果不小心关闭了,可以通过工具栏按钮再次打开

 

3、输入 .symfix命令,自动将符号路径设置为指向 Microsoft 符号存储

关于调试符号,可以参考https://www.cnblogs.com/zhaotianff/p/16931797.html

 

4、输入 x notepad!* 命令查看notepad模块的符号

 1 0:000> x notepad!*
 2 00007ff6`40267460 notepad!<lambda_13f119b44549d7aec2177494846f39c3>::<lambda_invoker_cdecl> (void)
 3 00007ff6`4026eb70 notepad!wistd::__function::__func<<lambda_0186fc037f7c95b0bbb1a993b253897d>,long __cdecl(unsigned short *,unsigned __int64,unsigned __int64 *)>::operator() (void)
 4 00007ff6`402662d0 notepad!<lambda_694cf3250e255c878c64dabf1ae2e40c>::<lambda_invoker_cdecl> (void)
 5 00007ff6`40267bac notepad!ShowOpenSaveDialog (void)
 6 00007ff6`4027d75c notepad!StringLengthWorkerW (void)
 7 00007ff6`4027f5a0 notepad!`WaitForCompletion<Windows::Foundation::IAsyncOperationCompletedHandler<Windows::Security::EnterpriseData::FileProtectionInfo *>,Windows::Foundation::IAsyncOperation<Windows::Security::EnterpriseData::FileProtectionInfo *> >'::`2'::FTMEventDelegate::Invoke (void)
 8 00007ff6`40261380 notepad!wil::details::`dynamic initializer for 'g_header_init_InitializeStagingSRUMFeatureReporting'' (void)
 9 00007ff6`40267570 notepad!wistd::__function::__func<<lambda_0186fc037f7c95b0bbb1a993b253897d>,long __cdecl(unsigned short *,unsigned __int64,unsigned __int64 *)>::destroy (void)

这里的!后面的*通配符,代表显示全部符号。

如果我们想查找 notepadMain函数,可以输入 x notepad!*main*

1 0:012> x notepad!*main*
2 00007ff6`47a10118 notepad!__mainCRTStartup (void)
3 00007ff6`47a11213 notepad!__mainCRTStartup$filt$0 (void)
4 00007ff6`47a13000 notepad!_imp___getmainargs = <no type information>
5 00007ff6`479fa140 notepad!WinMain (WinMain)
6 00007ff6`47a10100 notepad!WinMainCRTStartup (WinMainCRTStartup)

 

5、使用 ~ 命令,显示被调试进程内部的所有线程信息

1 0:000> ~
2 .  0  Id: 5ba4.950 Suspend: 1 Teb: 00000034`cb6a0000 Unfrozen
3    1  Id: 5ba4.234c Suspend: 1 Teb: 00000034`cb6a2000 Unfrozen
4    2  Id: 5ba4.6834 Suspend: 1 Teb: 00000034`cb6a4000 Unfrozen
5    3  Id: 5ba4.52dc Suspend: 1 Teb: 00000034`cb6a6000 Unfrozen

线程基本信息如下所示(这里以列表里的第一项进行说明)

0
Id: 5ba4.950
Suspend: 1
Teb: 00000034`cb6a0000
Unfrozen
调试器线程索引 进程ID.线程ID 挂起计数(通常是1) 线程环境块(TEB) 是否冻结(从调试器的角度看,通常未冻结)

说明:WinDbg默认以十六进制显示,可以使用 ? 命令将数值转换为十进制,

如前面进程Id5ba4,输入 ? 5ba4,可以查看十进制 进程Id

1 0:002> ? 5ba4
2 Evaluate expression: 23460 = 00000000`00005ba4

可以用0n前缀代表十进制数字,使用这个命令可以反查十六进制数字

1 0:002> ? 0n23460
2 Evaluate expression: 23460 = 00000000`00005ba4

 

6、输入 lm 命令,查看已经加载的模块

 1 0:000> lm
 2 start             end                 module name
 3 00007ff6`40260000 00007ff6`40292000   notepad    (pdb symbols)          C:\ProgramData\dbg\sym\notepad.pdb\48F76637AE64DAE8764C8F9F4B27AEA51\notepad.pdb
 4 00007ffb`e0710000 00007ffb`e0995000   COMCTL32   (deferred)             
 5 00007ffb`f29d0000 00007ffb`f29f1000   win32u     (deferred)             
 6 00007ffb`f2b40000 00007ffb`f2bc0000   bcryptPrimitives   (deferred)             
 7 00007ffb`f2bc0000 00007ffb`f2cba000   ucrtbase   (deferred)             
 8 00007ffb`f3440000 00007ffb`f34de000   msvcp_win   (deferred)             
 9 00007ffb`f3550000 00007ffb`f37f3000   KERNELBASE   (deferred)             
10 00007ffb`f3950000 00007ffb`f3ae4000   gdi32full   (deferred)             
11 00007ffb`f3af0000 00007ffb`f3b99000   shcore     (deferred)             
12 00007ffb`f3ba0000 00007ffb`f3d33000   USER32     (deferred)             
13 00007ffb`f3d40000 00007ffb`f3de3000   advapi32   (deferred)             
14 00007ffb`f3f90000 00007ffb`f4042000   KERNEL32   (deferred)             
15 00007ffb`f49a0000 00007ffb`f4a3e000   msvcrt     (deferred)             
16 00007ffb`f4a50000 00007ffb`f4ae7000   sechost    (deferred)             
17 00007ffb`f4d70000 00007ffb`f4e90000   RPCRT4     (deferred)             
18 00007ffb`f4f00000 00007ffb`f4f26000   GDI32      (deferred)             
19 00007ffb`f55f0000 00007ffb`f5926000   combase    (deferred)             
20 00007ffb`f5a40000 00007ffb`f5c30000   ntdll      (pdb symbols)          C:\ProgramData\dbg\sym\ntdll.pdb\CFD10E5F223FEE5F26227CB82510FEDC1\ntdll.pdb

模块列表显示了当前被调试进程(notepad.exe)的所有模块(DLL和EXE)。你能够从中看到已加载的模块的起始和终止的虚拟地址。

在模块名称后面可以看到这个模块的符号的状态(在括号里)。可能的值有这些:

deferred(推迟)

在当前调试会话中还没用到,因此现在还没有被加载。这些符号会在载入时被载入。

 

pdb symbols(pdb符号)

正确的公开符号已经被载入(来自微软公开的符号服务器),后面会显示PDF文件路径

 

export symbol(输出符号)

这个DLL只有输出符号可用,这一般意味着该模块没有符号,或者没有找到相应的符号

 

no symbol(没有符号)

试图去找本模块的符号,但是什么都没发现,连输出符号都没有(这种模块没有输出符号,比如可执行文件和驱动程序文件)。

 

可以通过.reload /f modulename.dll强制加载模块的符号

如这里我执行 .reload /f gdi32.dll

 1 0:000> .reload /f gdi32.dll
 2 0:000> lm
 3 start             end                 module name
 4 00007ff6`40260000 00007ff6`40292000   notepad    (pdb symbols)          c:\symbols\notepad.pdb\48F76637AE64DAE8764C8F9F4B27AEA51\notepad.pdb
 5 00007ffb`e0710000 00007ffb`e0995000   COMCTL32   (deferred)             
 6 00007ffb`f29d0000 00007ffb`f29f1000   win32u     (deferred)             
 7 00007ffb`f2b40000 00007ffb`f2bc0000   bcryptPrimitives   (deferred)             
 8 00007ffb`f2bc0000 00007ffb`f2cba000   ucrtbase   (deferred)             
 9 00007ffb`f3440000 00007ffb`f34de000   msvcp_win   (deferred)             
10 00007ffb`f3550000 00007ffb`f37f3000   KERNELBASE   (deferred)             
11 00007ffb`f3950000 00007ffb`f3ae4000   gdi32full   (deferred)             
12 00007ffb`f3af0000 00007ffb`f3b99000   shcore     (deferred)             
13 00007ffb`f3ba0000 00007ffb`f3d33000   USER32     (deferred)             
14 00007ffb`f3d40000 00007ffb`f3de3000   advapi32   (deferred)             
15 00007ffb`f3f90000 00007ffb`f4042000   KERNEL32   (deferred)             
16 00007ffb`f49a0000 00007ffb`f4a3e000   msvcrt     (deferred)             
17 00007ffb`f4a50000 00007ffb`f4ae7000   sechost    (deferred)             
18 00007ffb`f4d70000 00007ffb`f4e90000   RPCRT4     (deferred)             
19 00007ffb`f4f00000 00007ffb`f4f26000   GDI32      (pdb symbols)          c:\symbols\gdi32.pdb\209AD405837D061EF9D34CBDC009D7711\gdi32.pdb
20 00007ffb`f55f0000 00007ffb`f5926000   combase    (deferred)             
21 00007ffb`f5a40000 00007ffb`f5c30000   ntdll      (pdb symbols)          c:\symbols\ntdll.pdb\CFD10E5F223FEE5F26227CB82510FEDC1\ntdll.pdb

 

7 当前线程指示

在前面输入 ~ 查看线程时,有一个线程数据前面带有一个点,这个线程就是当前线程。

1 0:005> ~
2    0  Id: 8824.5a80 Suspend: 1 Teb: 00000096`de0af000 Unfrozen
3    1  Id: 8824.48bc Suspend: 1 Teb: 00000096`de0bd000 Unfrozen
4    2  Id: 8824.521c Suspend: 1 Teb: 00000096`de0bf000 Unfrozen
5    3  Id: 8824.460 Suspend: 1 Teb: 00000096`de0c3000 Unfrozen
6    4  Id: 8824.5a30 Suspend: 1 Teb: 00000096`de107000 Unfrozen
7 .  5  Id: 8824.9a8 Suspend: 1 Teb: 00000096`de109000 Unfrozen

如果没有指定哪个线程的话,任何线程命令都会作用到这个线程上。

 

8 输入命令 k ,显示当前线程的调用堆栈

1 0:005> k
2  # Child-SP          RetAddr               Call Site
3 00 00000096`ddfafc78 00007ffb`f5b0d4db     ntdll!DbgBreakPoint
4 01 00000096`ddfafc80 00007ffb`f3fa7bd4     ntdll!DbgUiRemoteBreakin+0x4b
5 02 00000096`ddfafcb0 00007ffb`f5aace71     KERNEL32!BaseThreadInitThunk+0x14
6 03 00000096`ddfafce0 00000000`00000000     ntdll!RtlUserThreadStart+0x21

可以看到这个线程的调用列表(当然,只有用户模式的)。上面输出中的栈顶部是函数DbgBreakPoint,它位于模块ntdll.dll中。

通用的带有符号的地址格式是: modulename !functionname + offset。如果正好位于函数的开头,那么offset是可选的,也可能为零。

另外要注意模块名称里不带扩展名。在上面的输出中, DbgBreakPointDbgUiRemoteBreakin调用,而后者又被BaseThreadInitThunk调用,依此类推。

另外说明一下:该线程是被WinDbg注入的,而非进程的实际线程,以便强行进入目标进程。

 

9 输入命令 ~ns 切换线程 , n是线程的索引值。

如切换到线程0就是 ~0s

再执行 k,显示调用堆栈,输出如下:

 1 0:005> ~0s
 2 win32u!NtUserGetMessage+0x14:
 3 00007ffb`f29d1164 c3              ret
 4 0:000> k
 5  # Child-SP          RetAddr               Call Site
 6 00 00000096`ddeafd28 00007ffb`f3bc477d     win32u!NtUserGetMessage+0x14
 7 01 00000096`ddeafd30 00007ff6`4026a3d3     USER32!GetMessageW+0x2d
 8 02 00000096`ddeafd90 00007ff6`402802b7     Notepad!WinMain+0x293
 9 03 00000096`ddeafe60 00007ffb`f3fa7bd4     Notepad!__mainCRTStartup+0x19f
10 04 00000096`ddeaff20 00007ffb`f5aace71     KERNEL32!BaseThreadInitThunk+0x14
11 05 00000096`ddeaff50 00000000`00000000     ntdll!RtlUserThreadStart+0x21

这是Notepad的主线程(第一个线程)。栈的顶部显示了线程正在等待用户界面消息。

 

10 输入 ~nk 在不切换线程的情况下显示指定线程的调用堆栈

如输入 ~0k,可以显示线程0的调用堆栈

1 0:002> ~0k
2  # Child-SP          RetAddr               Call Site
3 00 00000096`ddeafd28 00007ffb`f3bc477d     win32u!NtUserGetMessage+0x14
4 01 00000096`ddeafd30 00007ff6`4026a3d3     USER32!GetMessageW+0x2d
5 02 00000096`ddeafd90 00007ff6`402802b7     Notepad!WinMain+0x293
6 03 00000096`ddeafe60 00007ffb`f3fa7bd4     Notepad!__mainCRTStartup+0x19f
7 04 00000096`ddeaff20 00007ffb`f5aace71     KERNEL32!BaseThreadInitThunk+0x14
8 05 00000096`ddeaff50 00000000`00000000     ntdll!RtlUserThreadStart+0x21

此时我们再调用 ~ 查看线程列表,发现 那个点 已经移到线程2。在线程5上还显示了一个#。

1 0:002> ~
2    0  Id: 8824.5a80 Suspend: 1 Teb: 00000096`de0af000 Unfrozen
3    1  Id: 8824.48bc Suspend: 1 Teb: 00000096`de0bd000 Unfrozen
4 .  2  Id: 8824.521c Suspend: 1 Teb: 00000096`de0bf000 Unfrozen
5    3  Id: 8824.460 Suspend: 1 Teb: 00000096`de0c3000 Unfrozen
6    4  Id: 8824.5a30 Suspend: 1 Teb: 00000096`de107000 Unfrozen
7 #  5  Id: 8824.9a8 Suspend: 1 Teb: 00000096`de109000 Unfrozen

带有#标识的线程是引起最后一个断点的线程(在当前演示中是因为我们初始附加调试器的操作)

 

11 输入 !teb 命令可以查看当前线程的TEB

 1 0:002> !teb
 2 TEB at 00000096de0bf000
 3     ExceptionList:        0000000000000000
 4     StackBase:            00000096de500000
 5     StackLimit:           00000096de4ef000
 6     SubSystemTib:         0000000000000000
 7     FiberData:            0000000000001e00
 8     ArbitraryUserPointer: 0000000000000000
 9     Self:                 00000096de0bf000
10     EnvironmentPointer:   0000000000000000
11     ClientId:             0000000000008824 . 000000000000521c
12     RpcHandle:            0000000000000000
13     Tls Storage:          0000026f1b184d80
14     PEB Address:          00000096de0ae000
15     LastErrorValue:       0
16     LastStatusValue:      c000000d
17     Count Owned Locks:    0
18     HardErrorMode:        0

!teb 后面带其它线程TEB的地址,可以输出其它线程的TEB

 1 0:002> !teb 00000096`de0c3000
 2 TEB at 00000096de0c3000
 3     ExceptionList:        0000000000000000
 4     StackBase:            00000096de600000
 5     StackLimit:           00000096de5ef000
 6     SubSystemTib:         0000000000000000
 7     FiberData:            0000000000001e00
 8     ArbitraryUserPointer: 0000000000000000
 9     Self:                 00000096de0c3000
10     EnvironmentPointer:   0000000000000000
11     ClientId:             0000000000008824 . 0000000000000460
12     RpcHandle:            0000000000000000
13     Tls Storage:          0000026f1b184ba0
14     PEB Address:          00000096de0ae000
15     LastErrorValue:       0
16     LastStatusValue:      c000000d
17     Count Owned Locks:    0
18     HardErrorMode:        0

!teb命令显示的数据含义

StackBase和StackLimit:当前线程的用户模式栈基址和限制。
ClientId:进程和线程ID。
LastErrorValue :上一个Win32错误代码( GetLastError )。
Tls Storage:此线程的线程局部存储(TLS )数组。这里不做详细介绍,可以参考以下链接:https://learn.microsoft.com/zh-cn/windows/win32/procthread/using-thread-local-storage
PEB Address:进程环境块(PEB)的地址,可以通过!peb来查看PEB的内容

_teb命令显示的是其背后真正的结构中的部分内容,这里的结构是_TEB,它在ntdll中定义。

可以使用dt(display type) _teb命令查看真正的结构:

如果知道结构在哪里定义的,可以在结构名称前带上模块名称,如这里带上ntdll

1 dt ntdll!_teb

在前面的命令中加上一个地址,就可以得到这个结构数据成员的实际值

0:002> dt _teb 00000096de0bf000
ntdll!_TEB
   +0x000 NtTib            : _NT_TIB
   +0x038 EnvironmentPointer : (null) 
   +0x040 ClientId         : _CLIENT_ID
   +0x050 ActiveRpcHandle  : (null) 
   +0x058 ThreadLocalStoragePointer : 0x0000026f`1b184d80 Void
   +0x060 ProcessEnvironmentBlock : 0x00000096`de0ae000 _PEB
   +0x068 LastErrorValue   : 0
   +0x06c CountOfOwnedCriticalSections : 0
   +0x070 CsrClientThread  : (null) 
   +0x078 Win32ThreadInfo  : 0x00000000`0000521c Void
   +0x080 User32Reserved   : [26] 0
   +0x0e8 UserReserved     : [5] 0
   +0x100 WOW32Reserved    : (null) 
   +0x108 CurrentLocale    : 0x804
   +0x10c FpSoftwareStatusRegister : 0
   +0x110 ReservedForDebuggerInstrumentation : [16] (null) 
   +0x190 SystemReserved1  : [30] (null) 
   +0x280 PlaceholderCompatibilityMode : 0 ''
   +0x281 PlaceholderHydrationAlwaysExplicit : 0 ''
   +0x282 PlaceholderReserved : [10]  ""
   +0x28c ProxiedProcessId : 0
   +0x290 _ActivationStack : _ACTIVATION_CONTEXT_STACK
   +0x2b8 WorkingOnBehalfTicket : [8]  ""
   +0x2c0 ExceptionCode    : 0n0
   +0x2c4 Padding0         : [4]  ""
   +0x2c8 ActivationContextStackPointer : 0x00000096`de0bf290 _ACTIVATION_CONTEXT_STACK
   +0x2d0 InstrumentationCallbackSp : 0

每个成员都会显示出相对于结构起始处的偏移量、成员的名称和值。简单的值直接显示,而结构体的值(例如上面的ClientID )通常会显示成一个超链接。

单击这个超链接会显示出该结构的详情。调试器会使用一个新的dx命令来查看数据

1 0:002> dx -r1 (*((ntdll!_CLIENT_ID *)0x96de0bf040))
2 (*((ntdll!_CLIENT_ID *)0x96de0bf040))                 [Type: _CLIENT_ID]
3     [+0x000] UniqueProcess    : 0x8824 [Type: void *]
4     [+0x008] UniqueThread     : 0x521c [Type: void *]

 

说明:TEB全程Thread Environment Block(线程环境块),它包含了线程的上下文信息(The Thread Environment Block holds context information for a thread.)

TEB在线程数据结构中的位置如下:

 关于TEB,如果想了解更多信息,可以查看《Windows Internals Seventh Edition Part 1》的第4章Thread

 

12 使用 bp / bu 命令添加断点

如我们要在CreateFile函数处设置一个断点,可以输入 bp kernel32!CreateFileW 命令

1 0:012> bp kernel32!CreateFileW
2 0:012> bl
3      0 e Disable Clear  00007ffb`f3fb2090     0001 (0001)  0:**** KERNEL32!CreateFileW

 

如我们要在 notepad.exe的Main函数放置断点,可以输入 bp notepad!WinMain 命令

1 0:012> bp notepad!WinMain
2 0:012> bl
3      0 e Disable Clear  00007ff6`479fa140     0001 (0001)  0:**** notepad!WinMain

 

13、使用 bl 命令查看断点列表

1 0:012> bl
2      0 e Disable Clear  00007ffb`f3fb2090     0001 (0001)  0:**** KERNEL32!CreateFileW

可以看到断点的索引值(0)是被允许了还是被禁止了( e=被允许,d=被禁止)

并且得到用来禁止( bd命令)/ 启用(be命令)和删除( bc命令)该断点的超链接。单击链接可以禁止和删除断点

 

bd/be 后面带*,可以禁用或启用全部断点

bd/be 后面带数字,可以禁用或启用对应的断点 

 

14、输入 g 命令、按下工具栏上的Go按钮或者按F5键,会继续执行进程。

调试器会显示正在忙碌,这也就意味着直到下次中断才能输入命令。

 

此时我们回到记事本,用文件菜单打开一个文件,打开文件时会调用CreateFileW函数,调试器会触发断点,然后中断。

此时我们输入 k 查看调用堆栈(如果调试器需要从微软的符号服务器下载符号的话,这里的加载时间会长一点,请耐心等待)

 1 0:004> ~
 2    0  Id: 9b34.26b0 Suspend: 1 Teb: 00000005`79b86000 Unfrozen
 3    1  Id: 9b34.59a8 Suspend: 1 Teb: 00000005`79b88000 Unfrozen
 4    2  Id: 9b34.3be8 Suspend: 1 Teb: 00000005`79b8a000 Unfrozen
 5    3  Id: 9b34.2364 Suspend: 1 Teb: 00000005`79b8c000 Unfrozen
 6 .  4  Id: 9b34.40ac Suspend: 1 Teb: 00000005`79b8e000 Unfrozen
 7    5  Id: 9b34.57f4 Suspend: 1 Teb: 00000005`79ba4000 Unfrozen
 8    6  Id: 9b34.5b6c Suspend: 1 Teb: 00000005`79b92000 Unfrozen
 9    7  Id: 9b34.2f90 Suspend: 1 Teb: 00000005`79b94000 Unfrozen
10    8  Id: 9b34.4844 Suspend: 1 Teb: 00000005`79b96000 Unfrozen
11    9  Id: 9b34.8ab8 Suspend: 1 Teb: 00000005`79baa000 Unfrozen
12   10  Id: 9b34.5e2c Suspend: 1 Teb: 00000005`79b9a000 Unfrozen
13   11  Id: 9b34.9288 Suspend: 1 Teb: 00000005`79ba8000 Unfrozen
14   12  Id: 9b34.7bfc Suspend: 1 Teb: 00000005`79ba0000 Unfrozen
15   13  Id: 9b34.7188 Suspend: 1 Teb: 00000005`79bac000 Unfrozen
16   14  Id: 9b34.1a2c Suspend: 1 Teb: 00000005`79bae000 Unfrozen
17   15  Id: 9b34.44e4 Suspend: 1 Teb: 00000005`79bb0000 Unfrozen
18   16  Id: 9b34.5418 Suspend: 1 Teb: 00000005`79bb2000 Unfrozen
19 0:004> k
20  # Child-SP          RetAddr               Call Site
21 00 00000005`79d7e9f8 00007ffb`c01785d6     KERNEL32!CreateFileW
22 01 00000005`79d7ea00 00007ffb`c01786d2     TortoiseSVN+0x385d6
23 02 00000005`79d7ea60 00007ffb`c0178913     TortoiseSVN+0x386d2
24 03 00000005`79d7eaa0 00007ffb`c01778cc     TortoiseSVN+0x38913
25 04 00000005`79d7ed50 00000000`60bb1706     TortoiseSVN+0x378cc
26 05 00000005`79d7f030 00007ffb`f4334625     TortoiseOverlays+0x1706
27 06 00000005`79d7f060 00007ffb`f43344f6     SHELL32!CFSIconOverlayManager::_GetFileOverlayInfo+0x111
28 07 00000005`79d7f140 00007ffb`f2df1331     SHELL32!CFSIconOverlayManager::GetFileOverlayInfo+0x46
29 08 00000005`79d7f180 00007ffb`f2e5936b     windows_storage!CFSFolder::_GetOverlayInfo+0x179
30 09 00000005`79d7f240 00007ffb`f2e59267     windows_storage!CRegFolder::_GetOverlayInfo+0xbf
31 0a 00000005`79d7f310 00007ffb`f2d53de6     windows_storage!CRegFolder::GetOverlayIndex+0x47
32 0b 00000005`79d7f340 00007ffb`f2e5936b     windows_storage!CAutoDestItemsFolder::GetOverlayIndex+0xb6
33 0c 00000005`79d7f3c0 00007ffb`f2e59267     windows_storage!CRegFolder::_GetOverlayInfo+0xbf
34 0d 00000005`79d7f490 00007ffb`caa1b191     windows_storage!CRegFolder::GetOverlayIndex+0x47
35 0e 00000005`79d7f4c0 00007ffb`caa42a95     explorerframe!CNscOverlayTask::_Extract+0x51
36 0f 00000005`79d7f510 00007ffb`caa16362     explorerframe!CNscOverlayTask::InternalResumeRT+0x45
37 10 00000005`79d7f540 00007ffb`f2e39be4     explorerframe!CRunnableTask::Run+0xb2
38 11 00000005`79d7f580 00007ffb`f2e39825     windows_storage!CShellTask::TT_Run+0x3c
39 12 00000005`79d7f5b0 00007ffb`f2e39705     windows_storage!CShellTaskThread::ThreadProc+0xdd
40 13 00000005`79d7f660 00007ffb`f3b226f6     windows_storage!CShellTaskThread::s_ThreadProc+0x35
41 14 00000005`79d7f690 00007ffb`f5a6f665     shcore!ExecuteWorkItemThreadProc+0x16
42 15 00000005`79d7f6c0 00007ffb`f5a745c4     ntdll!RtlpTpWorkCallback+0x165
43 16 00000005`79d7f7a0 00007ffb`f3fa7bd4     ntdll!TppWorkerThread+0x8d4
44 17 00000005`79d7fb60 00007ffb`f5aace71     KERNEL32!BaseThreadInitThunk+0x14
45 18 00000005`79d7fb90 00000000`00000000     ntdll!RtlUserThreadStart+0x21

 

15、 查看内存数据

当调试器在CreateFIleW函数中断时,我们能做些什么?

可能想知道现在正在打开什么文件,我们能够根据CreateFilew函数的调用惯例来得到这个信息。由于这是一个64位进程(并且处理器是Intel ),调用惯例中提到了第一个整数或者指针参数通过RCX、RDX、R8和R9寄存器进行传递。因为文件名是CreateFilew的第一个参数,所以相应的寄存器是RCX

r 命令 显示 RCX 寄存器的值

1 0:004> r rcx
2 rcx=00000163401b4bc8

 

db 命令以字节方式显示内存,右边是相应的ASCII字符。

db 后面是内存的地址,也就是上面rcx=后面的值。

1 0:004> db 00000163401b4bc8
2 00000163`401b4bc8  5c 00 5c 00 2e 00 5c 00-70 00 69 00 70 00 65 00  \.\...\.p.i.p.e.
3 00000163`401b4bd8  5c 00 54 00 53 00 56 00-4e 00 43 00 61 00 63 00  \.T.S.V.N.C.a.c.
4 00000163`401b4be8  68 00 65 00 2d 00 30 00-30 00 30 00 30 00 30 00  h.e.-.0.0.0.0.0.
5 00000163`401b4bf8  30 00 30 00 30 00 34 00-31 00 31 00 30 00 35 00  0.0.0.4.1.1.0.5.
6 00000163`401b4c08  61 00 35 00 35 00 00 00-00 00 00 00 00 00 00 00  a.5.5...........
7 00000163`401b4c18  01 ba 67 bb 45 02 00 80-43 00 3a 00 5c 00 50 00  ..g.E...C.:.\.P.
8 00000163`401b4c28  72 00 6f 00 67 00 72 00-61 00 6d 00 20 00 46 00  r.o.g.r.a.m. .F.
9 00000163`401b4c38  69 00 6c 00 65 00 73 00-5c 00 54 00 6f 00 72 00  i.l.e.s.\.T.o.r.

 

由于这个字符串是Unicode的,所以使用db命令看起来不是非常方便。

使用 du 命令可以更加方便地查看Unicode字符串

1 0:004> du 00000163401b4bc8
2 00000163`401b4bc8  "\\.\pipe\TSVNCache-0000000041105"
3 00000163`401b4c08  "a55"

 

可以通过给寄存器名字前加@前缀来直接使用寄存器的值

1 0:004> du @rcx
2 00000163`401b4bc8  "\\.\pipe\TSVNCache-0000000041105"
3 00000163`401b4c08  "a55"

 

16、 使用 u 命令查看反汇编指令

我们增加一个新的断点,断点在NtCreateFile函数,这个API会被CreateFileW调用

1 0:014> bp ntdll!ntcreatefile
2 0:014> bl
3      0 e Disable Clear  00007ffb`f3fb2090     0001 (0001)  0:**** KERNEL32!CreateFileW
4      1 e Disable Clear  00007ffb`f5adcaf0     0001 (0001)  0:**** ntdll!NtCreateFile

 

g 命令继续执行,调试器应该会中断

1 0:014> g
2 Breakpoint 1 hit
3 ntdll!NtCreateFile:
4 00007ffb`f5adcaf0 4c8bd1          mov     r10,rcx

 

u 命令列出接下来要执行的8条指令

 1 0:014> u
 2 ntdll!NtCreateFile:
 3 00007ffb`f5adcaf0 4c8bd1          mov     r10,rcx
 4 00007ffb`f5adcaf3 b855000000      mov     eax,55h
 5 00007ffb`f5adcaf8 f604250803fe7f01 test    byte ptr [SharedUserData+0x308 (00000000`7ffe0308)],1
 6 00007ffb`f5adcb00 7503            jne     ntdll!NtCreateFile+0x15 (00007ffb`f5adcb05)
 7 00007ffb`f5adcb02 0f05            syscall
 8 00007ffb`f5adcb04 c3              ret
 9 00007ffb`f5adcb05 cd2e            int     2Eh
10 00007ffb`f5adcb07 c3              ret

值0x55被复制到了EAX寄存器。这是NtCreateFile的系统服务号。列表中显示的syscall指令用来转换到内核模式,然后执行NtCreateFile系统服务。

 

说明:CreateFileW函数在kernel32.dll中实现,这里kernel32.dll是Windows子系统的一个DLL。CreateFileW函数在用户模式运行,因此无法直接打开文件。在进行了一些错误检查之后,它调用了NtCreateFile。这是一个在NTDLL.dll中实现的函数,而NTDLL.dll是—个基础的DLL,它实现了“原生API(Native API)“,并且它实际上是位于用户模式的底层代码。NtCreateFile是一个执行到内核模式的转换API。在进行实际的转换之前,它先把一个叫作系统服务号的数字(NtCreateFile是0x55)放到CPU的寄存器里(Intel/AMD体系结构上是EAX)。然后它会执行一个特殊的CPU指令(在x64系统里是syscall,在x86系统里是sysenter)来实际转换到内核模式,并跳转到一个预定义的被称为系统服务分发器( system service dispatcher )的例程。

系统服务分发器继而使用EAX寄存器中的值作为系统服务分发表( System Service Dispatch Table,SSDT)的入口索引,代码跳转至相应的系统服务中。对上述的记事本例子来说,SSDT中相应的入口会指向IO管理器(I/O Manager )的NtCreateFile函数。请注意,这个函数与NTDLL.dll里的函数有相同的名称,而且还有一样的参数。当系统服务执行完毕后,线程会返回到用户模式,执行紧接着sysenter/syscall的指令。这些事件的顺序如下所示。

 

17、使用 p 命令能够以跳过函数的方式(逐过程)单步执行下一条指令(按F10键也可以)

说明: t 命令以进入函数的方式(逐语句)

 1 0:014> p
 2 ntdll!NtCreateFile+0x3:
 3 00007ffb`f5adcaf3 b855000000      mov     eax,55h
 4 0:014> p
 5 ntdll!NtCreateFile+0x8:
 6 00007ffb`f5adcaf8 f604250803fe7f01 test    byte ptr [SharedUserData+0x308 (00000000`7ffe0308)],1 ds:00000000`7ffe0308=00
 7 0:014> p
 8 ntdll!NtCreateFile+0x10:
 9 00007ffb`f5adcb00 7503            jne     ntdll!NtCreateFile+0x15 (00007ffb`f5adcb05) [br=0]
10 0:014> p
11 ntdll!NtCreateFile+0x12:
12 00007ffb`f5adcb02 0f05            syscall
13 0:014> p
14 ntdll!NtCreateFile+0x14:
15 00007ffb`f5adcb04 c3              ret

由于当前是用户模式下的调试,所以单步跟踪进入syscall指令是不可能的。不管是逐过程还是逐语句,都会执行完该指令并返回结果。

在x64调用惯例下,函数的返回值保存在EAX或者RAX里。对系统调用来说,它是一个NTSTATUS值,因此EAX中包含返回状态:

1 0:014> r eax
2 eax=0

 

18、单击工具栏上的 ”Break“按钮 或按Ctrl + Break键可以强制中断

禁用所有断点,并让notepad继续运行

1 0:014> bd *
2 0:014> g

现在没有断点了,可以点击 Break按钮,再次中断。

 

19、输入 qd 命令结束调试会话,并使任何用户模式目标应用程序保持运行状态。 

这个命令实际上是 .detach命令(结束调试会话,但使任何用户模式目标应用程序保持运行状态)和q命令(结束调试会话)的结合

 

通过上面的命令,目前已经对WinDbg用户模式下的调试有了初步的认识,后续 我还会补充一些文章,对用户模式下的调试做深入介绍。

posted @ 2024-01-10 11:48  zhaotianff  阅读(1054)  评论(1编辑  收藏  举报