我假设你已经掌握Ollydbg的使用,并且希望用WinDbg进行内核级的调试。这篇教程将会以Ollydbg为线索,帮助你尽快掌握WinDbg的使用,并简单介绍它的一些特性。我把这篇文章定位为Ollydbg到WinDbg内核调试之间的过渡。
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和0040c7c4范围内进行写操作则断
除了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
0040C7D6 8D4424 64 lea eax,dword ptr ss:[esp+64] EAX=初始栈+8
0040C7DA 50 push eax 此处是0040c7e2的参数
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 跳了