【调试技术】.NET 调试

文章目录
  1. 1. 0x01 查看函数地址
    1. 1.1. !DumpHeap
  2. 2. 0x02 查看方法有哪些
  3. 3. 0x03 添加断点
  4. 4. 0x04 查看堆栈
  5. 5. 添加初始断点
  6. 6. 查找某个函数
  7. 7. 0x06 其他命令补充

概述:使用 windbg 调试 .NET 文件,虽然使用windbg是必须的技能,但是非必要情况下可以使用 dnSpy调试。

[toc]

以 C# 如下所示的代码为例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
namespace DnrspEngine.Hooks.System
{
class Win32Native
{
[StructLayout(LayoutKind.Sequential)]
internal class SECURITY_ATTRIBUTES
{
}

internal static SafeFileHandle MySafeCreateFile(string lpFileName, int dwDesiredAccess, FileShare dwShareMode, SECURITY_ATTRIBUTES securityAttrs, FileMode dwCreationDisposition, int dwFlagsAndAttributes, IntPtr hTemplateFile)
{
}

internal static SafeFileHandle PxySafeCreateFile(string lpFileName, int dwDesiredAccess, FileShare dwShareMode, SECURITY_ATTRIBUTES securityAttrs, FileMode dwCreationDisposition, int dwFlagsAndAttributes, IntPtr hTemplateFile)
{
}
}
}

命名空间 DnrspEngine.Hooks.System 有一个类 Win32Native。

调试 .Net 程序前,需要加载 SOS 扩展,SOS (Son of Strike) 是 .NET 调试的扩展,它提供了很多用于调试 .NET 应用程序的命令。首先,你需要加载 SOS 扩展:

1
.loadby sos clr

如果 clr 加载不成功,则需要尝试以下命令查看原因并解决(相关参考:SOS.DLL在windbg里加载错误 - 活着的虫子 - 博客园):

1
2
3
!sym noisy
.symfix 路径
.cordll -ve -u -l

或者在 clrjit 加载成功后再尝试加载 sos 和 clr,一般都是这个原因(相关参考:windbg的.loadby sos clr第一次总不成功,必须在加载clrjit以后才能运行,为何?-CSDN社区):

1
sxe ld:clrjit

0x01 查看函数地址

!name2ee 查看类信息,如果不知道有哪些类,可以使用 !DumpHeap 命令,如果要查看具体的对象具有的方法但是又没有源代码时,可以使用 dnSpy 这类的反编译工具。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# 查看类的属性
!name2ee mscorelib.dll!Microsoft.Win32.Win32Native
!name2ee System.dll!System.Diagnostics.Process

# 输出如下所示
0:026> !name2ee mscorlib.dll!Microsoft.Win32.Win32Native
Module: 70931000
Assembly: mscorlib.dll
Token: 0200000e
MethodTable: 709acdcc
EEClass: 70a67be8
Name: Microsoft.Win32.Win32Native

#查看 MethodTable
!DumpMT -md 709acdcc

#查看模块
!DumpModule /d 70931000
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# 查看具体的函数
!name2ee mscorlib.dll Win32Native.SafeCreateFile // 需要知道函数名
!name2ee system.dll System.Diagnostics.Process.Start
!name2ee system.dll System.Diagnostics.Process.StartWithCreateProcess
!name2ee system.dll System.Diagnostics.Process.StartWithShellExecuteEx
!name2ee mscorlib.dll System.AppDomain.CreateDomain

# 输出如下所示
0:026> !name2ee mscorlib.dll Win32Native.SafeCreateFile
Module: 70931000
Assembly: mscorlib.dll
Token: 06000042
MethodDesc: 70b9db00
Name: Microsoft.Win32.Win32Native.SafeCreateFile(System.String, Int32, System.IO.FileShare, SECURITY_ATTRIBUTES, System.IO.FileMode, Int32, IntPtr)
JITTED Code Address: 70d044a0

输出分析,以我当前要使用的字段为主:

Module: 模块句柄

MethodTable:方法表。给出了我们对应的类的方法表的地址

!DumpHeap

!DumpHeap 命令用于查看当前调试的进程的堆上的对象,略微局限

  1. -stat:只输出堆上所有类型对象的统计摘要,包括它们的计数、大小和类型名称。这个选项非常有用,可以快速了解堆上对象的分布情况。
  2. -nostrings:排除字符串的输出。当与 -stat 一起使用时,这个选项可以使得输出更加简洁,专注于非字符串类型的对象。
  3. -gen X:仅输出属于指定代的对象。在 .NET 中,对象根据它们的生命周期被分配到不同的代(Generation),这个选项允许你查看特定代的对象。
  4. -min X 和 -max X:忽略小于或大于指定字节大小的对象。这两个选项可以帮助你过滤掉不感兴趣的小对象或大对象。
  5. -mt MethodTable:仅列出具有指定 MethodTable 的对象。MethodTable 是 .NET 中用于描述类型的内部结构。
  6. -type type:仅列出类型名为指定子字符串的对象。这个选项允许你按类型名称过滤输出。
  7. -live:仅输出仍然存活的对象,即那些还没有被垃圾回收器标记为可回收的对象。
  8. -dead:仅输出已死亡的对象,这些对象将在下一次完全垃圾回收(Full GC)中被回收。
  9. -cache:将对象保存在内部缓存中以供以后使用,这有助于加快后续扫描堆的速度。
  10. -lx:只打印每个堆中的指定数量的项,而不是所有对象。这可以帮助你限制输出的数量。
  11. -short:仅打印出对象的地址。这个选项通常与其他命令(如 .foreach)组合使用,用于自动化处理或进一步分析。

0x02 查看方法有哪些

!dumpmt 查看方法有哪些

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
!dumpmt -md %MethodTable% # 查看方法有哪些
---------------------------------------
!dumpmt -md 00007ffd79054540

# 输出如下所示:
0:024> !dumpmt -md 00007ffd79054540
EEClass: 00007ffd792da338
Module: 00007ffd79051810
Name: DnrspEngine.Hooks.System.Win32Native
mdToken: 000000000200000b
File: DnrspEngine, Version=1.0.0.1031, Culture=neutral, PublicKeyToken=null
BaseSize: 0x18
ComponentSize: 0x0
Slots in VTable: 7
Number of IFaces in IFaceMap: 0
--------------------------------------
MethodDesc Table
Entry MethodDesc JIT Name
00007ffdd5c957a0 00007ffdd579c7a0 PreJIT System.Object.ToString()
00007ffdd5d0d2b0 00007ffdd5935560 PreJIT System.Object.Equals(System.Object)
00007ffdd5cfaba0 00007ffdd5935588 PreJIT System.Object.GetHashCode()
00007ffdd5d0aac0 00007ffdd5935590 PreJIT System.Object.Finalize()
00007ffd79159448 00007ffd79054538 NONE DnrspEngine.Hooks.System.Win32Native..ctor()
00007ffd792e1440 00007ffd79054518 JIT DnrspEngine.Hooks.System.Win32Native.MySafeCreateFile(System.String, Int32, System.IO.FileShare, SECURITY_ATTRIBUTES, System.IO.FileMode, Int32, IntPtr)
00007ffd792e1810 00007ffd79054528 JIT DnrspEngine.Hooks.System.Win32Native.PxySafeCreateFile(System.String, Int32, System.IO.FileShare, SECURITY_ATTRIBUTES, System.IO.FileMode, Int32, IntPtr)

输出分析:

可以看到输出内容是比较多的,并且输出了方法表示以及各个方法对应的地址。这里就可以对函数下断点了,以 DnrspEngine.Hooks.System.Win32Native.MySafeCreateFile 为例 (注意只有 JIT 的函数才可以下断点)

0x03 添加断点

1
2
3
4
5
6
!bpmd -md 00007ffd79054518

# 输出如下所示:
0:024> !bpmd -md 00007ffd79054518
MethodDesc = 00007ffd79054518
Setting breakpoint: bp 00007FFD792E1440 [DnrspEngine.Hooks.System.Win32Native.MySafeCreateFile(System.String, Int32, System.IO.FileShare, SECURITY_ATTRIBUTES, System.IO.FileMode, Int32, IntPtr)]

分析:

可以看到断点设置信息,已经函数参数等。等待函数执行就可以触发断点。

0x04 查看堆栈

1
2
3
4
5
6
7
!clrstack 

# 查看所有线程的堆栈
!dumpstack
#或者
~*e!dumpstack

添加初始断点

1
2
3
4
5
0:000> sxe ld:clrjit

0:000> .load C:\Windows\Microsoft.NET\Framework\v4.0.30319\sos.dll
0:000> .load C:\Windows\Microsoft.NET\Framework\v4.0.30319\clr.dll
0:000> .loadby sos clr (等同于上面两条命令)

查找某个函数

1
2
# 如下所示,查找AppDomain.CreateDomain
!name2ee *!AppDoamin.CreateDomain

0x06 其他命令补充

  1. 帮助命令

    1
    !help
  2. 清屏

    1
    !cls
  3. 查看当前托管线程已执行时间

    1
    !runaway
  4. 查看当前线程池情况

    1
    !threadpool