2
.一般调试流程
2
.
1
调试目标
CTRL+E
相当于
Ollydbg
的菜单
“
文件
=>
打开
”
,
WinDbg
除了能设启动参数之外,还能设起始文件夹,还有一个调试子进程的额外选项。
F6
相当于
Ollydbg
的
“
文件
=>
附加
”
CTRL+K
内核调试模式。分
4
种模式,分别是
COM
,
1394
,
USB2.0
和本地调试,头
3
种都是双机调试,最后一种调试需要
XP
以上版本。
Ollydbg
有
3
个入口点可以选择,而
WinDbg
只能停在系统断点。要使
WinDbg
停在
EP
,可以使用
g @$exentry
。
2
.
2
调试功能
2
.
2
.
1
基本调试功能
F11
或者
F8
步入
F10
步过
SHIFT+F11
步出(注:这个功能最好少用,尤其在
TLSCALLBACK
中,
100%
出错)
F5
运行
CTRL+SHIFT+F5
重新运行
SHIFT+F5
关闭
CTRL+BREAK
停止
Ollydbg
的两个功能,执行到返回和执行到用户代码,
WinDbg
没有。我用两个
SCRIPT
来实现它,你将能在附件中找到,分别为
gr
和
guc
,把他们复制到
WinDbg
的安装目录下,可以通过输入
“$$><gr”
来使用执行到返回功能,输入
“$$><guc”
则对应执行到用户代码。假如你把文件复制到别的地方,那么你需要输入完整的文件路径。
由于执行到返回的
SCRIPT
已经在我的另一篇教程中提到,现在我们来看看
guc
的代码,简单了解一下
WinDbg
的
SCRIPT
功能。
r @$t0=@$peb+8
r @$t0=poi(@$t0) ;$$
通过
PEB
取得
MZ
头,程序上界
r @$t1=@$t0+3c
r @$t1=poi(@$t1)+@$t0 ;$$
取得
PE
头
r @$t1=@$t1+50
r @$t1=poi(@$t1)+@$t0 ;$$
取得程序在内存中的下界
.while(@$ip>@$t1 or @$ip<@$t0){ ;$$
超过下界或者超过上界进入循环
.if(@$ip<@$t1 and @$ip>@$t0){ ;$$
在程序范围之内则跳出循环
.break
}
p ;$$
单步步过
}
注意:这个
SCRIPT
并没有判断是否处于系统断点,假如你在进入程序区域之前使用这个
SCRIPT
,效果则等同运行。如果永远都不会进入用户区域,例如你在调试
ExitProcess
,那么将会导致死循环。
2
.
2
.
2
断点功能
软中断,即
INT3
:
[~Thread] bp[ID] [Options] [Address [Passes]] ["CommandString"]
~Thread
指定线程,可缺省
ID
指定断点
ID
,可缺省。内核调试限
32
个断点,用户模式不限
Options
可缺省
/1
中断后自动删除该断点。
/c
指定最大调用深度,大于这个深度则断点不工作。
/C
指定最小调用深度,小于这个深度则断点不工作。
/C
和
/c
不能同时使用。
Address
地址或符号。例如
MessageBoxW.
Passes
忽略中断的次数,可缺省。例:
bp messageboxw 1
则程序会忽略第一次调用
messageboxw
产生的中断,其后激活断点。
CommandString
可缺省。每次中断后都会运行该命令行。一般用于设置条件断点,也用于
HOOK
某些用于
ANTI DEBUG
的
API
硬件断点:
[~Thread] ba[ID] Access Size [Options] [Address [Passes]] ["CommandString"]
Access r
读写;
w
写;
e
运行;
I I/O
操作断点,只限
XP
或以后版本,内核调试,
X86
系列。
Size
大小,只能是
1
,
2
,
4
,
8
。如果
Access
为
e
,则只能是
1
。
例:
Ba e1 0040c7c0; $$
运行断点
Ba w4 0040c7c0; $$0040c7c0
和
0040c
7c
4
范围内进行写操作则断
除了
ba
和
bp
,还有以下两个断点相关指令
bl
显示当前断点状态及
ID
bc ID
删除断点
,
例如
bc *; $$
删除所有断点。
异常断点:
菜单
“debug=>event filters”
,在这里你能设置包括断点异常在内的所有异常的处理方式。一般我们不关心那个。
Ollydbg
提供了
SHIFT+F7/F8/F9
,而
WinDbg
就只提供了一个指令
gN
。注意
WinDbg
某些指令是区分大小写的。把这个放在这里说是因为你也可以用该命令下断,下面让我们来看看
gN
的用法
[~Thread] gN[a] [= StartAddress] [BreakAddress ... [; BreakCommands]]
a
如果你使用了
gNa
那么将会以内存断点的方式中断,缺省则以软中断的方式
BreakCommands
中断产生之后便会运行该命令
这相当于
Ollydbg
中的
SHIFT+F9
消息断点:
WinDbg
并没有提供这个功能,不过你可以写一个
SCRIPT
对
RegisterClass
或
RegisterClassEx
设断,取得
WNDPROC
的首址,并设条件断点。由于我对消息机制了解不多,这里就不给出
SCRIPT
了。
2.2.3
自动跟踪
[~Thread] t [r] [= StartAddress] [Count] ["Command"]
[~Thread]
设置其影响的线程。可缺省
[= StartAddress]
设置起始地址。要注意在地址前面加上
=
,否则会被当作
COUNT
参数。此外还会以当前环境(堆栈和寄存器),直接跳到该地址执行跟踪。可缺省
[Count]
跟踪步数,可缺省
["Command"]
跟踪完毕之后会在结果显示之前执行的命令,可缺省
这里并不会象
Ollydbg
那样统计次数。一般这个指令用于单步步入,类似的还有
P
,单步步过。较有特色的命令是
PC
和
TC
,到
CALL
就自动暂停。当然
WinDbg
也有能统计的命令。
wt [WatchOptions] [= StartAddress] [EndAddress]
跟踪,并显示统计结果。
WatchOptions
可缺省,可选参数如下
-l num
跟踪深度限制
,
例如限制跟踪深度为
10
则
wt -l10 00402312
-nc
不显示单独
CALL
的信息
-ns
不显示累计信息
-nw
不显示在跟踪过程中的警告信息
EndAddress
终点地址。当
wt
在模块或者函数入口点,此项可缺省。
现在我们来看看它的显示效果(从帮助文件中复制过来的
:P
)
0:000> wt
函数的开始,现在使用
"wt"
Tracing MyModule!myFunction to return address 00401137
141 [ 0] MyModule!myFunction
20 [ 1] MyModule!anotherFunction
5 [ 2] MyModule!deeperFunction
10 [ 1] MyModule!anotherFunction
3 [ 2] MyModule!deeperFunction
30 [ 1] MyModule!anotherFunction
4 [ 0] MyModule!myFunction
147 Instructions were executed 146(0 from other threads) traces 147 sums
Function Name Invocations MinInstr MaxInstr AvgInstr
MyModule!deeperFunction 2 3 5 4
MyModule!anotherFunction 1 68 68 68
MyModule!myFunction 1 213 213 213
0 system calls were executed
System Call:
3
.
调试示例
首先要说明的是
WinDbg
中的某些指令是区分大小写的。最后我将以实际调试过程来说明
Ollydbg
与
WinDbg
的差别。先介绍一下目标软件,
black light beta
版。自动查
ROOTKIT
,使用修改文件名并重新启动系统的方法清除
ROOTKIT
。国庆节到期,爆破时间限制不难,设断改跳转就可以了,如果写这个,就没什么好写了。用
PEID
查了一下,没有壳,有
TLS
表。这个才是这次调试的主角,我希望通过调试来增强对
TLSCALLBACK
的了解。
先使用
Ollydbg
载入目标程序,如果你的设置是首次暂停在
WINMAIN
,并且没有使用任何关于
TLS
的增强插件,那么你会看到窗口出来了,但是
Ollydbg
却提示
“
进程已经终止,退出代码
0”
。
现在用
WinDbg
载入程序,选中
“debug child processes also”
。程序一开始停在系统断点。输入
g $exentry.
没断成功,落在子进程的系统断点。再次输入
g $exentry
,产生了一个非法访问的错误。
gN $exentry
,终于能断在
EP
了。好了,如果你喜欢用这个软件,你可以爆破它的时间限制了。
Ollydbg
与
WinDbg
的第一次较量,在没有任何插件的情况,
WinDbg
由于支持子进程调试,使得它在这个特例里要简单一些。
现在我们可以知道,程序在入口点之前就已经被运行,很可能是
TLSCALLBACK
,并在建立新进程后退出。
-------------------------------------------------------
关于多进程调试
-------------------------------------------------------------
0:000> | ;$$
查询当前进程数
,
注意
0:000
表示当前调试的进程的识别号是
0
. 0 id: 46c create name: beta.exe
# 1 id: 410 child name: beta.exe
0:000> | 1 s ;$$
切换到子进程
1
。
S
是切换参数,当前状态是默认显示的。
eax=00000000 ebx=7ffdf000 ecx=00010101 edx=ffffffff esi=00000000 edi=00000000
eip=0041e3ec esp=0012ffc4 ebp=0012fff0 iopl=0 nv up ei pl zr na pe nc
cs=001b ss=0023 ds=0023 es=0023 fs=0038 gs=0000 efl=00000246
beta+0x1e3ec:
0041e3ec 6a60 push 60h
1:001> ;$$
切换之后命令提示符变成
1:001
了。
--------------------------------------------------------------
回到正题
------------------------------------------------------------
现在我们需要调试
TLS
处的代码。回到
Ollydbg
,我用的插件是
shoooo
大大的
WMOS
,能显示
TLSCALLBACK
的首址。为了方便每次都停在
TLSCALLBACK
,我在入口处设了硬断,重新运行之后,我们可以开始调试了。
0040C7C0 8B4424 08 mov eax,dword ptr ss:[esp+8]
一开始便从初试栈中读取数据
0040C7C4 83EC 54 sub esp,54
0040C7C7 56 push esi
初始栈
-58
0040C7C8 BE 01000000 mov esi,1
0040C7CD 3BC6 cmp eax,esi
比较初始栈
+8
是否为
1
0040C7CF 0F85 E4010000 jnz blbeta.0040C9B9
没有跳转
0040C7D5 53 push ebx
初始栈
-5c
0040C
7D6 8D4424 64 lea eax,dword ptr ss:[esp+64] EAX=
初始栈
+8
0040C7DA 50 push eax
此处是
0040c
7e2
的参数
0040C7DB FF15 ACA34300 call dword ptr ds:[<&KERNEL32.GetCommandLineW>;
0040C7E1 50 push eax
0040C7E2 FF15 60A44300 call dword ptr ds:[<&SHELL32.CommandLineToArg>;
0040C7E8 8BD8 mov ebx,eax EBX=
参数首址
0040C7EA 397424 64 cmp dword ptr ss:[esp+64],esi
参数长度与
1
比较
0040C7EE 76 19 jbe short blbeta.0040C809
跳了
----------------------------------------------------------
如果没有跳会怎么样?
----------------------------------------------------
0040C7F0 8B4B 04 mov ecx,dword ptr ds:[ebx+4]
0040C7F3 68 74364400 push blbeta.00443674 ; UNICODE "/q"
0040C7F8 51 push ecx
0040C7F9 E8 B1290100 call blbeta.0041F1AF
从传入参数看,此
CALL
判断运行参数是否为
/q
0040C7FE 83C4 08 add esp,8
0040C801 85C0 test eax,eax
0040C803 0F84 AF010000 je blbeta.0040C9B8
可能是参数
/q
对应的功能模块
Ollydbg
的参考字符功能,基本可以猜到该程序能以参数启动,格式为
“/
参数
”
,长度不等于
1
--------------------------------------------------------------
回到正题
------------------------------------------------------------
0040C809 33D2 xor edx,edx
0040C80B 55 push ebp
初始栈
-60
0040C80C 57 push edi
初始栈
-64
0040C80D 33C0 xor eax,eax
0040C80F 895424 10 mov dword ptr ss:[esp+10],edx
0040C813 B9 11000000 mov ecx,11
0040C818 8D7C24 20 lea edi,dword ptr ss:[esp+20]
0040C81C F3:AB rep stos dword ptr es:[edi]
0040C81E 397424 6C cmp dword ptr ss:[esp+6C],esi
仍然是跟初始栈
+8
比较
0040C822 895424 14 mov dword ptr ss:[esp+14],edx
0040C826 895424 18 mov dword ptr ss:[esp+18],edx
0040C82A C74424 20 44000>mov dword ptr ss:[esp+20],44
0040C832 895424 1C mov dword ptr ss:[esp+1C],edx
0040C836 76 70 jbe short blbeta.0040C8A8 ESI
没有改变过,此处相当于
JMP
-----------------------------------------------junk code
如何引诱你改变
EIP
跟进去?
------------------------------------------------
0040C838 8B43 04 mov eax,dword ptr ds:[ebx+4]
0040C83B 68 64364400 push blbeta.00443664 ; UNICODE "/expert"
0040C840 50 push eax
0040C841 E8 69290100 call blbeta.0041F1AF
0040C846 83C4 08 add esp,8
0040C849 85C0 test eax,eax
0040C84B 75 5B jnz short blbeta.0040C8A8
首先是一个非常引诱的字符参考
“/expert”
,跟进去仿佛能开启高级功能。然后是
0040C
8A
8
由于跟上面跳转一样,或者会让你觉得不跳转,继续跟下去便是开启高级功能的关键。攻城为下攻心为上,如果我在这里安排一些破坏性的代码。。。。。。
--------------------------------------------------------------
回到正题
------------------------------------------------------------
虽然跳过了一大堆
junk code
不过我猜肯定不止那么少。
0040C8A8 8B0B mov ecx,dword ptr ds:[ebx]
0040C8AA 51 push ecx
0040C8AB E8 21200100 call blbeta.0041E8D1
0040C8B0 68 74364400 push blbeta.00443674 ; UNICODE "/q"
0040C8B5 8BF0 mov esi,eax
0040C8B7 E8 15200100 call blbeta.0041E8D1
前面已经有处理
/q
的例程了,这里还处理?忽略
0040C8BC 83C4 08 add esp,8
0040C8BF 6A 04 push 4
0040C8C1 8D6C06 04 lea ebp,dword ptr ds:[esi+eax+4]
0040C8C5 68 00100000 push 1000
0040C8CA 8D7C2D 00 lea edi,dword ptr ss:[ebp+ebp]
0040C8CE 57 push edi
0040C8CF 6A 00 push 0
0040C8D1 FF15 B0A34300 call dword ptr ds:[<&KERNEL32.VirtualAlloc>] ; KERNEL32.VirtualAlloc
0040C8D7 8BCF mov ecx,edi
这条指令跟下一条指令相当于
mov edx,edi
0040C8D9 8BD1 mov edx,ecx
到这里还有
junk code
0040C8DB 8BF0 mov esi,eax
又是一个
junk code
0040C8DD C1E9 02 shr ecx,2
0040C8E0 33C0 xor eax,eax
0040C8E2 8BFE mov edi,esi
相当于
mov edi,eax
0040C8E4 F3:AB rep stos dword ptr es:[edi]
代码分析
=
体力活,本文重点介绍工具使用,而不是代码还原。现在忽略细节,直接看它用了什么
API
。
CALL KERNEL32.VirtualAlloc
Call KERNEL32.GetModuleFileNameW
call KERNEL32.CreateEventW
call KERNEL32.CreateProcessW
其中
|CommandLine = ""D:/blbeta.exe" /q"
call KERNEL32.WaitForSingleObject
call KERNEL32.CloseHandle
call KERNEL32.VirtualFree
call KERNEL32.ExitProcess
ExitProcess
之后又来到一开始的地方了。
0040C7C0 8B4424 08 mov eax,dword ptr ss:[esp+8]
0040C7C4 83EC 54 sub esp,54
0040C7C7 56 push esi
0040C7C8 BE 01000000 mov esi,1
0040C7CD 3BC6 cmp eax,esi
0040C7CF 0F85 E4010000 jnz blbeta.0040C9B9
这次跳转成功了
0040C9B9 5E pop esi
0040C9BA 83C4 54 add esp,54
0040C9BD C2 0C00 retn 0C retn
后
ESP-0c
关于
TLS
:现在我们知道
TLSCALLBACK
通过初始栈
+8
的数据来判断是什么情况下被调用。当它的值为
1
时,代表
TLS
被首次运行。假如只是简单判断是否第一次被运行,只要判断是否为
1
就够了。为什么结束时是
retn 0c?
现在让我们单步进入系统区域看看:
77F86209 FF75 14 push dword ptr ss:[ebp+14]
77F8620C FF75 10 push dword ptr ss:[ebp+10]
77F8620F FF75 0C push dword ptr ss:[ebp+C]
77F86212 FF55 08 call dword ptr ss:[ebp+8]
77F86215 8BE6 mov esp,esi
现在可以知道
call dword ptr ss:[ebp+8]
的最后是调用
TLSCALLBACK
,所以结束时必须代替系统释放堆栈,通过这次调试,我们可以用汇编写
TLSCALLBACK
,并把
ANTI DEBUG
代码放在这里欺负新人了
: P
我们来看看,如果在
WinDbg
中,应该怎么做。为了让
WinDbg
看起来容易用一点,我又写了个
SCRIPT
,它在附件里的名字是
gs
。复制到程序安装目录,输入
$$><gs
,然后按
ENTER
。如果程序有
TLSCALLBACK
,将会断在那里
(
用了
bp
设断,然后
g
的方法,以后也会自动断在那里
)
,没有就断在
EP
。
--------------------------------------------------------------gs
的注释
------------------------------------------------------------
r @$t0=@$peb+8
r @$t0=poi(@$t0) ;$$
通过
peb
获得基址
r @$t1=@$t0+3c
r @$t1=poi(@$t1)+@$t0
r @$t1=@$t1+c0
r @$t1=poi(@$t1) ;$$
取得
TLS
表基址
.if (@$t1!=0){ ;$$
判断
TLS
表是否存在
r @$t1=@$t1+@$t0
r @$t1=@$t1+c
r @$t1=poi(@$t1)
r @$t1=poi(@$t1)
.if(@$t1!=0){ ;$$
判断
TLSCALLBACK
是否存在
r @$t0=0 ;$$
是否有
TLSCALLBACK
的标记
bp @$t1
g
}
}
.if (@$t0!=0){ ;$$
没有
TLSCALLBACK
就
GO EP
g $exentry
}
-------------------------------------------------------------
注释结束
------------------------------------------------------------
现在程序停在入口点了。我用
OD
的时候习惯静态分析,然后分析到我感兴趣的地方,设断,
F9
,看寄存器看堆栈。这在
WinDbg
中不可能,它的反汇编功能真的不怎么样。例如我单步到这里:
call dword ptr [beta+0x3a3ac (0043a3ac)] ds:0023:0043a3ac={KERNEL32!GetCommandLineW (77e7386f)}
才会显示这个是
API
调用,在反汇编窗口里面只是单纯显示
call dword ptr [beta+0x3a3ac (0043a3ac)]
。你必须跟进那个
call
,调用栈才有反应,还不如直接用
db esp
查看堆栈。而且进去之后也不会提示那些是什么参数。所以我一般在使用的时候,同时打开
MSDN
的搜索页面,当然还有
IDA
。
0040c7cf 0f85e4010000 jne beta+0xc9b9 (0040c9b9) [br=0]
后面
br=0
代表没有跳转成功,如果是
1
,那么就是成功跳转。当然
WinDbg
也有方便的地方。在
Ollydbg
的例子中,我是下拉反汇编窗口直接看后面用什么
API
。而在
WinDbg
中,你可以使用
pc
命令,现在直接自动单步到
CALL
,然后停下来,而不需要关心在这中间遇到多少个条件跳转。
第二次较量,在进行代码分析时
WinDbg
要逊色一点,它的
SCRIPT
功能倒是不错,可以弥补一些差距。
关于目标程序:现在我们知道程序通过参数
/q
启动一个新的进程。用
Ollydbg
和
WinDbg
带参数
/q
重新载入程序。开始的时候,我们曾经用
WinDbg
双进程调试过,当时新进程产生了一个错误,忽略错误运行之后,我们能停在
EP
。我希望知道
系统是怎么处理这个错误的。
Ollydbg
中按
F9
到达错误处,然后按
SHIFT+F7
进入
SEH
链中的第一个例程
WinDbg
中按
F5
到达错误处,然后还是
SCRIPT
,对应附件中的
lseh
和
disasm
这两个文件。一定要复制到安装目录下。
$$><lseh
使用
SCRIPT
,效果如图:
这个
SCRIPT
的功能是列举所有
SEH
例程,可以下断,也可以反汇编该例程的前
10
条命令。我用
DML
做了一个简单的可视化界面。显然可以做一个界面,通过点鼠标来单步之类的,这样
HOOK
快捷键
ANTI DEBUG
就不可行了。如果你点一下反汇编,你会发现系统
DLL
里面有很多符号标记,相反
Ollydbg
却认不出来。也许
Micro$oft
开发
WinDbg
就是鼓励别人调试它的产品。
-----------------------------------------------------------SCRIPT
注释
------------------------------------------------------------
r @$t0=poi(@$teb) ;$$
取得
SEH
链表的第一个元素
.for (r $t3 = 0; @$t0 !=-1; r $t3 = @$t3 + 1){ ;$$
到达终点则停止循环
r @$t1=poi(@$t0+4)
.printf /D "[%x] %X <link cmd=/"bp /1 %x/">
下断
</link> <link cmd=/"r @$t2=%x;$$><disasm /">
反汇编
</link>/n",@$t3,@$t1,@$t1,@$t1 ;$$dml
的标记跟
HTML
很象。在
””
中使用
”
你需要在
”
前面加
/
r @$t0=poi(@$t0) ;$$
即
/”/”=””
。
.printf
的用法跟
C
语言中的一样,我希望你懂
C
语言
}
.if (@$t0=-1){
.echo "
已达
SEH
链结尾
"
}
反汇编的链接命令是使用
disasm
这个
SCRIPT
,并以
@$t2
来传递参数,下面来看看它的代码:
u @$t2 l10 ;$$
反汇编
@$t2
指向的地址
.printf /D "<link cmd=/"$$><Lseh/">
返回
</link>/n" ;$$
使用
lseh
--------------------------------------------------------------
结束
----------------------------------------------------------------
这有点花俏,我写了另外一个
SCRIPT
,对应附件中的
SEH
。它的功能是在第一个
SEH
例程下断了。
gN
,断下来。
我们看到处理错误的函数在系统区域,由于忽略错误能回到
EP
,也就是说该系统
SEH
例程可以处理错误,然后返回到某处用户代码,于是我在
WinDbg
中使用
guc
这个
SCRIPT
,运行到用户代码为止,结果窗体出来了还没停下来。现在换
Ollydbg
取消了入口处的断点之后,使用执行到用户代码,效果跟
WinDbg
一样。重新开始,经过层层跟踪之后发现出问题的地方是
INT 2E
,单步过去就变成运行。看来
R3
调试器跟我一样,不认识那东西。也就是说,可以用
INT 2E
等调用来对抗模拟跟踪,当然这需要你掌握它的使用。至于
TLSCALLBACK
,或者我们可以假定它出错,系统就会把控制权交回
EP
,有待实践证明或高手调试证明了。
第三次较量,算是平手了。
最后简单的总结一下
WinDbg
在
R3
下的表现:
1
.
强大的
script
功能,适用于自动化完成一些任务。
2
.
DML
,提供一个简陋的可视化界面与用户进行简单的互动。
3
.
HTML
文件输出支持,这是我在学习
DML
的过程中发现的。
“.logopen”
建立
HTML
文件,
“.printf”
输出
HTML
标记,
“.logclose”
关闭文件。
4
.
适合调试系统内核,它对内核的符号标记比
Ollydbg
要多。为了获得更多
WINDOWS
内核的符号支持,你需要通过菜单
"File=>Symbol File Path"
然后输入
“srv*%SystemRoot%/symbols*http://msdl.microsoft.com/download/symbols”
注意:此命令行使
WinDbg
到
micro$oft
的网站下载符号文件,由于网速以及符号文件库比较大的关系,会导致初次使用时较费时(我这里校园网,等了几分钟)。此外假如程序也加载了一些非
micro$oft
的模块,
WinDbg
也会尝试在该网站查找,因此假如你对内核不感兴趣,请把路径设为空格。
这里列出一些资源方便大家学习
关于SCRIPT
http://bbs.pediy.com/showthread.php?s=&threadid=33663
WINDBG Script简易教程
关于DML
http://bbs.pediy.com/showthread.php?s=&threadid=34145
这个是DML帮助文档的翻译
http://bbs.pediy.com/showthread.php?s=&threadid=34018
一个DML的例子“写了个小的 WinDbg 脚本,可以显示 SSDT”
关于WinDbg的插件制作方法
请看Debugging Help中的Debuggers/Debuggers Operation/Debugger Extensions
关于内核调试
在安装目录下kernel_debugging_tutorial.doc
关于WinDbg的最大特点
Micro$oft设计这个东西是用于排错,有大量针对排错优化的指令。尤其是在拥有源代码的情况下,有符号文件的情况,它的易用性不比OD差,如果你对这方面有兴趣,或者想体验一下它在该方面的功能表现,我推荐你看
《Windows用户态程序高效排错》
你将能从作者的BLOG下载到:http://www.cnblogs.com/lixiong
WinDbg的下载链接
http://www.microsoft.com/whdc/devtools/debugging/installx86.mspx