第一部分 事件处理
调试事件与异常
windows定义了九种调试事件,其中异常是其中的一种调试事件(EXCEPTION_DEBUG_EVENT)。异常也存在包括win32异常,调试器异常,编译器异常(VC异常),托管异常(.net异常),其他异常(主动调用RaiseException抛出异常)。
二次机会
对于每个异常,windows异常处理模块给与两次处理异常的机会,也就是说程序拥有两次处理机会,处理后调试器会向系统返回一直处理结果。如果最后一次无法处理完异常,windows本身会调用默认的异常处理(用户态下抛出默认提示框,内核下蓝屏)。
对于第一次处理异常机会,调试器一般会放弃处理,让系统继续分发,交给自身的异常处理程序处理,对于第二次处理机会,调试器一般会断在产生异常的地方。(与原文表述不同)
GH和GN命令
在调试器中断异常后,可以使用G命令(go)恢复程序运行,我们可以指定程序关于异常处理的结果,也就是如果GH(go handle)表示此异常得到处理,如果是GN(go not handle)表示异常为处理.
第二部分 控制调试目标
初始断点
windows进程加载器在完成了进程在用户态下最基本的初始化后,系统的进程初始化函数主动触发中断指令(int 3),这种断点称之为初始断点。
我们使用kn命令来查看调用栈的情况,可以看到这个断点是调试器创建的。
在调试选项的时候加入-g选项,以忽略初始断点,也就是不在将程序中断给用户。如果需要分析程序的入口函数,初始断点是非常有效的。
单步执行
windbg提供两种单步执行的模式,一种是源码级单步和汇编级单步。
和ollydbg一样,windbg也提供两种单步的方式,一种是单步步入,另外一种是单步步入。区别和ollydbg是一致的。前者windbg称为单步(step),后者称之为跟踪(trace)。
- p:单步步入(进入函数)
- t:单步步过(不进入函数,只是执行完毕)
- 完整用法:p|t [r:是否显示寄存器信息] [=startaddress] [count] [“command”]
关于步入操作,在CPU上都是通过置标志寄存器TF位来实现的,每当执行完一条程序(执行完TF为0),置TF位为1,在下条指令执行前检查TF情况,以此判断是否需要单步。
单步执行到指定位置
pa或者ta用来执行到指定代码。
- 完整用法:pa|ta [r] [=startaddress] [endaddress]
执行到下一个函数调用
pc或者tc命令用来执行到下一个函数调用,count指定执行的函数的个数,这也就是为什么可以选择pc或者tc的原因(如果只是在下一个函数调用前中断,t命令或者p命令是相同的功能)。而count数可以指定执行的函数个数,这就可以判断在count个函数调用前的count个函数的执行方式。
- 完整用法:pc|tc [r] [=startaddress] [count]
单步执行到下一个分支
tb命令是单步执行到下一个分支,但是对于x86的机器只能用于内核调试,对于x64的可以用于内核和用户调试。
- 完整命令:tb [r] [=startaddress] [count]
- x86无法使用用户调试
- x64可以使用
- x86无法使用用户调试
继续运行
g命令是最常用的恢复运行的命令。其中a是硬件断点,不含a为软件断点。如果没有任何参数则执行到已存在断点处。
- g[a] [=startaddress] [Breakaddress] [“breakcommand”]
- gh:处理异常
- gn:报告未处理异常
- gu:返回上一个函数
- gc:使用条件断点
跟踪监视
跟踪是调试器的灵魂,wt命令是windbg跟踪的命令
总结
总结如下:
第三部分 使用断点
软件断点
windbg一共有三种设置软件断点的命令:bp,bm,bu,最常见的就是bp命令
bp命令的格式如下:
- bp [ID] [Options] [Address[Passes]] [“Command”]
- ID:设置断点号,没有什么重要作用
- Options:选项
- /1:一次性断点,用完就从断点表中删去
- /p或者/t:只在当前进程或者线程中才触发此断点
- /c或者/C:中断给用户最大调用深度或者最小调用深度!!
- Address:断点地址//可以是内存地址,也可以是符号函数地址(printf)
- Passes:限制中断次数,如果Passes==0,则放行程序,不中断!
bu命令:设置延迟断点,也就是说windbg可以允许当程序模块没有加载,便可以提前设置断点,当模块加载执行的时候,断在目标断点处。
bm命令:批量设置断点,通常是设置模糊匹配断点或者成员函数断点,把MyClass 所有的成员函数都下断点: bu MyApp!MyClass:: ,或者把所有以CreateWindow开头的函数都下断点: bu user32!CreateWindow 。但是在设置批量断点的时候,调试器需要确认匹配的符号是代码还是数据,也就是说bm命令执行的前提是需要知道一个符号的类型。这就可能造成以下的问题。
此问题的最佳解决方案是:首先设置一个空的符号目录(.sympath .),然后.reload,然后执行bm命令。
硬件断点
ba命令用于设置硬件断点。
- ba Access Size [ID] [Options] [Address[Passes]] [“Command”]
- Access:表示设置硬件断点的类型
- e:表示对指令进行读写时,触发断点,和软件中断类型,只是不修改指令
- w:对指定地址进行写入操作触发断点
- r:对指定地址进行读取操作触发断点
- i:对指定的地址执行IO操作触发断点
- Access:表示设置硬件断点的类型
条件断点
条件断点利用之前的断点命令加上调价判断合并而来,语法如下:
bp|bm|ba|bu address ".if(Condition){Command} .else{gc}"
地址表达式
- 模块名!函数符号:dbgee!WinMain
- 内存地址:00401000:
- 源代码调试:
module!SourceCodeFile:Line
- C++类方法:Class::ClassMethod
针对线程设置断点
- ~ThreadId bp|bm|ba
管理断点
- bl:列举所有断点
- bc: 删除断点
- bd:禁止断点
- be:启用断点
第四部分 观察栈
显示栈回溯
- k命令:基本的栈回溯
- 显示结果的第二列是:是函数的返回地址,这是在父函数中的指令地址(也就是说调用栈上面函数的指令的下一条指令)。
- 显示结果的第二列是:是函数的返回地址,这是在父函数中的指令地址(也就是说调用栈上面函数的指令的下一条指令)。
- kb命令:显示存放在栈上面的前三个参数
- 需要注意的是这三个参数不一定是函数的参数,只是存放在栈上面三个值。
- kb命令仅仅显示三个参数,而且顺序固定 ebp+8,ebp+c ebp+10,如果需要查看第四个,可以使用dd ebp+14。
- kp命令:显示堆栈函数原型
- 前提条件是:需要得到程序的私有符号,如果没有调试对象的私有符号,windbg不显示参数。
- 前提条件是:需要得到程序的私有符号,如果没有调试对象的私有符号,windbg不显示参数。
- kv命令:显示栈指针省略(FPO)和函数调用约定
- 和kb前面保持一致,增加了上述的新内容
- 和kb前面保持一致,增加了上述的新内容
- kn:显示序号
观察栈变量
- dv命令:观察栈变量:dv /i/t/V
- 前提:拥有私有符号可以有效使用。
- 前提:拥有私有符号可以有效使用。
【重点】如果没有私有符号,可以有两种方法获取栈变量,第一种方法是直接观察内存窗口,以及使用内存显示页面,根据EBP的布局来显示数据。第二种方法是更具汇编指令提供的内存指令,获取对内存的引用。借此获取栈变量信息。
- .frame [栈帧号]:切换变量上下文
- !for_each_local:循环遍历所有的变量
- !for_each_frame:遍历所有栈帧
手工回溯栈
由于一些原因,比如说溢出,异常,导致windbg对栈的判断是不准确的,所以此时不建议使用k命令来回溯栈,我们采用手工方式进行栈回溯。一般存在两种情况,第一种:EBP寄存器的数值是正确的,此时回溯栈相对容易,第二种:如果EBP的数值不可靠,我们使用!tab查看线程控制块和dds来分析栈。
EBP和ESP可靠
第1步:确定当前函数名:
ln eip
120:000> ln eip(76f5d470) ntdll!LdrInitShimEngineDynamic+0x6af | (76f5ed00) ntdll!NtCurrentTeb第2歩:如果ESP和EBP没有异常的情况下,EBP就是就是当前函数的ChildEbp(子帧栈的基址寄存器),EBP+4就是当前函数的返回地址(在父函数内部)。,
|
|
- 第3步:得到一个函数信息:
0060fa30 76f58c37 ntdll!LdrInitShimEngineDynamic+0x6af
第4步:接下来利用第2步获得的返回地址,得到调用函数的函数名,
ln 76f58c37
12ln 76f58c37(76f3a1a0) ntdll!RtlCaptureStackContext+0x1ea97 | (76f58f20) ntdll!LdrAppxHandleIntegrityFailure第5步:根据当前的EBP在内存里面的数值就是父函数的EBP,EBP+4是函数的返回地址,使用
dd ebp
来获取父函数的ChildPEB,使用dd xxxx+4
获取父函数的返回地址(在外公函数里面)12340:000> dd 0019fc90 l1 //这一步使用dd ebp是一样的,下一步却不行0019fc90 0019fcf00:000> dd 0019fcf0+4 l1 //函数返回地址0019fcf4 76f137be第6步:利用返回地址得到外公函数符号
120:000> ln 76f137be(76f13750) ntdll!LdrInitializeThunk+0x6e | (76f13ca0) ntdll!RtlCharToInteger第7步:重复4-6:直到堆栈数据为0,达到边界。
12340:000> dd 0019fd00 l10019fd00 0019fd100:000> dd 0019fd14 l10019fd14 00000000
EBP和ESP不可靠
第1步:使用!tab获取线程栈空间数据
12345670:000> !tebTEB at 0033a000ExceptionList: 0019fa20StackBase: 001a0000 //栈基址StackLimit: 0019d000 //栈顶SubSystemTib: 00000000FiberData: 00001e00第2步:使用dds命令
1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465660:000> dds esp //查看栈顶附近内存0019fa04 f8f3a4be0019fa08 76eb69c4 ntdll!`string'0019fa0c 003370000019fa10 000000100019fa14 00f3a4960019fa18 0019fa040019fa1c 76f001cf ntdll!LdrpPrepareModuleForExecution+0x610019fa20 0019fc800019fa24 76f26b20 ntdll!_except_handler40019fa28 8e115e060019fa2c 000000000019fa30 0019fc900019fa34 76f58c37 ntdll!LdrpInitializeProcess+0x1b420019fa38 f8f3a21e0019fa3c 000000000019fa40 000000000019fa44 ffffffff0019fa48 005c005a0019fa4c 02571e7c0019fa50 0019fbb00019fa54 000000000019fa58 000002010019fa5c 000000000019fa60 0019fbac0019fa64 000000000019fa68 025730400019fa6c 76fc7ba0 ntdll!LdrpWorkQueue0019fa70 76fc7ba0 ntdll!LdrpWorkQueue0019fa74 000000000019fa78 025777a00019fa7c 000000080019fa80 000000000:000> dds ebp //查看栈低附近的内存0019fa30 0019fc900019fa34 76f58c37 ntdll!LdrpInitializeProcess+0x1b420019fa38 f8f3a21e0019fa3c 000000000019fa40 000000000019fa44 ffffffff0019fa48 005c005a0019fa4c 02571e7c0019fa50 0019fbb00019fa54 000000000019fa58 000002010019fa5c 000000000019fa60 0019fbac0019fa64 000000000019fa68 025730400019fa6c 76fc7ba0 ntdll!LdrpWorkQueue0019fa70 76fc7ba0 ntdll!LdrpWorkQueue0019fa74 000000000019fa78 025777a00019fa7c 000000080019fa80 000000000019fa84 00403000 base32+0x30000019fa88 000010000019fa8c 000000080019fa90 000000000019fa94 00403af4 base32+0x3af40019fa98 000000020019fa9c 000000000019faa0 000000000019faa4 000000000019faa8 000000000019faac 00000000
第五部分 分析内存
显示内存
- d{a|b|c|d|D} [Options] [Range]
大括号里面代表的是显示类型。
da:显示ASCII码,du:显示Unicode
120:000> da0019fc84 " k.v..........."db:显示字节和ASCII //最常用
1234567890:000> db0019fc94 da 38 f1 76 7e a2 f3 f8-24 fd 19 00 00 00 eb 76 .8.v~...$......v0019fca4 00 70 33 00 00 00 00 00-aa 24 f0 56 00 b0 fc 76 .p3......$.V...v0019fcb4 00 00 03 00 28 70 33 00-01 00 00 00 00 00 eb 76 ....(p3........v0019fcc4 00 00 00 00 24 fd 19 00-00 70 33 00 00 00 00 00 ....$....p3.....0019fcd4 00 a0 33 00 98 fc 19 00-00 00 00 00 ff ff ff ff ..3.............0019fce4 20 6b f2 76 8e a5 10 8e-00 00 00 00 00 fd 19 00 k.v............0019fcf4 be 37 f1 76 00 00 00 00-00 00 00 00 10 fd 19 00 .7.v............0019fd04 6c 37 f1 76 00 00 00 00-5e 1b 59 bb 00 00 00 00 l7.v....^.Y.....dc:显示dword和ASCII编码
1234567890:000> dc0019fd14 00000000 0019fd24 76eb0000 00000000 ....$......v....0019fd24 0001003f 00000000 00000000 00000000 ?...............0019fd34 00000000 00000000 00000000 0000027f ................0019fd44 00000000 0000ffff 00000000 00000000 ................0019fd54 00000000 00000000 00000000 00000000 ................0019fd64 00000000 00000000 00000000 00000000 ................0019fd74 00000000 00000000 00000000 00000000 ................0019fd84 00000000 00000000 00000000 00000000 ................dd:显示dword
1234567890:000> dd0019fd94 00000000 00000000 00000000 000000000019fda4 00000000 00000000 00000000 0000002b0019fdb4 00000053 0000002b 0000002b 000000000019fdc4 00000000 00337000 00000000 000000000019fdd4 00401df5 00000000 76f20d90 000000230019fde4 00000202 0019fff0 0000002b 0000027f0019fdf4 00000000 00000000 00000000 000000000019fe04 00000000 00001f80 0000ffff 00000000dp:显示指针
1234567890:000> dp0019fe14 00000000 00000000 00000000 000000000019fe24 00000000 00000000 00000000 000000000019fe34 00000000 00000000 00000000 000000000019fe44 00000000 00000000 00000000 000000000019fe54 00000000 00000000 00000000 000000000019fe64 00000000 00000000 00000000 000000000019fe74 00000000 00000000 00000000 000000000019fe84 00000000 00000000 00000000 00000000ds:显示String类型,dS:显示Unicode_String类型
Range:表示需要显示内存的范围dd startaddress endaddress(两个内存地址)
1234567890:000> dc 0019fd94 0019fe040019fd94 00000000 00000000 00000000 00000000 ................0019fda4 00000000 00000000 00000000 0000002b ............+...0019fdb4 00000053 0000002b 0000002b 00000000 S...+...+.......0019fdc4 00000000 00337000 00000000 00000000 .....p3.........0019fdd4 00401df5 00000000 76f20d90 00000023 ..@........v#...0019fde4 00000202 0019fff0 0000002b 0000027f ........+.......0019fdf4 00000000 00000000 00000000 00000000 ................0019fe04 00000000 ....dd startaddress L(内存对象个数16进制)
1234567890:000> dc 0019fd94 L1D0019fd94 00000000 00000000 00000000 00000000 ................0019fda4 00000000 00000000 00000000 0000002b ............+...0019fdb4 00000053 0000002b 0000002b 00000000 S...+...+.......0019fdc4 00000000 00337000 00000000 00000000 .....p3.........0019fdd4 00401df5 00000000 76f20d90 00000023 ..@........v#...0019fde4 00000202 0019fff0 0000002b 0000027f ........+.......0019fdf4 00000000 00000000 00000000 00000000 ................0019fe04 00000000dd endaddress L-(内存对象个数16进制)
1234567890:000> dc 0019fe04 L-1D0019fd90 00000000 00000000 00000000 00000000 ................0019fda0 00000000 00000000 00000000 00000000 ................0019fdb0 0000002b 00000053 0000002b 0000002b +...S...+...+...0019fdc0 00000000 00000000 00337000 00000000 .........p3.....0019fdd0 00000000 00401df5 00000000 76f20d90 ......@........v0019fde0 00000023 00000202 0019fff0 0000002b #...........+...0019fdf0 0000027f 00000000 00000000 00000000 ................0019fe00 00000000
显示数据类型
dt命令常用与显示数据类型,其第一种方法是显示一个数据结构:dt module!Typename,选项-b:显示所有子类型,-r0,不显示子类型,-r1:显示一层子类型
|
|
|
|
第二种方法是指定特定内存地址上的数据结构,其他和第一种用法一致.
第三种方法是显示类型的实例,包括变量和函数。
搜索内存
windbg使用s命令搜索内存。一共三个试用方式。
第一种方法,搜索内存中指定的字符串。
- s [-Flags(b|w|d|q|a|u)] [Range] [Pattern]
![](https://i.imgur.com/4VvAUdr.png)
第二种方法:在指定的内存空间中搜索与指定对象相同类型的对象。
- s [-v] Range ObjectAddress123450:011> s -v 0x12fc30 L1000 0x12fe4c //0x12fe4c是类的地址,*** ERROR: Symbol file could not be found. Defaulted to export symbols for IEXPLORE.EXE -*** ERROR: Symbol file could not be found. Defaulted to export symbols for odbc32.dll -*** ERROR: Module load completed but symbols could not be loaded for odbcint.dll*** ERROR: Symbol file could not be found. Defaulted to export symbols for
修改内存数据
使用e命令来修改内存数据,可以修改字符型,也可以修改数据类型。
具体用法如下:a:b表示以0结尾的ASCII,za表示不是以0结尾的ASCII,zu或者au保证在末尾加0,而a或者u不会。u,zu同理。
- e{a|u|za|zu}address “String”123456789101112131415161718190:000> db76f5db9f eb 76 c7 44 24 68 00 00-00 01 66 89 44 24 70 85 .v.D$h....f.D$p.76f5dbaf f6 74 29 8b 15 30 03 fe-7f 8b c2 6a 20 83 e0 1f .t)..0.....j ...76f5dbbf 59 2b c8 d3 ce 33 f2 89-37 74 07 33 f6 e9 1c 01 Y+...3..7t.3....76f5dbcf 00 00 be 01 00 00 c0 e9-12 01 00 00 8d 54 24 10 .............T$.76f5dbdf 8d 4c 24 68 e8 ed 5d f7-ff 8b f0 85 f6 0f 88 e8 .L$h..].........76f5dbef 00 00 00 8d 44 24 18 ba-01 40 00 00 50 33 c9 e8 ....D$...@..P3..76f5dbff 59 ae f8 ff 8d 44 24 0c-50 6a 01 6a 00 8d 54 24 Y....D$.Pj.j..T$76f5dc0f 24 8d 4c 24 74 e8 a3 d9-f9 ff 80 7c 24 64 00 8b $.L$t......|$d..0:000> ea 76f5db9f "strui"0:000> db 76f5db9f76f5db9f 73 74 72 75 69 68 00 00-00 01 66 89 44 24 70 85 struih....f.D$p.76f5dbaf f6 74 29 8b 15 30 03 fe-7f 8b c2 6a 20 83 e0 1f .t)..0.....j ...76f5dbbf 59 2b c8 d3 ce 33 f2 89-37 74 07 33 f6 e9 1c 01 Y+...3..7t.3....76f5dbcf 00 00 be 01 00 00 c0 e9-12 01 00 00 8d 54 24 10 .............T$.76f5dbdf 8d 4c 24 68 e8 ed 5d f7-ff 8b f0 85 f6 0f 88 e8 .L$h..].........76f5dbef 00 00 00 8d 44 24 18 ba-01 40 00 00 50 33 c9 e8 ....D$...@..P3..76f5dbff 59 ae f8 ff 8d 44 24 0c-50 6a 01 6a 00 8d 54 24 Y....D$.Pj.j..T$76f5dc0f 24 8d 4c 24 74 e8 a3 d9-f9 ff 80 7c 24 64 00 8b $.L$t......|$d..
也可以修改数值类型的内存
- e{b|d|D|q|w} address “Value”123456789100:000> ew 76f5dc1f 41 41 41 410:000> db 76f5dc1f76f5dc1f 41 00 41 00 41 00 41 00-a5 90 f9 ff 85 f6 79 39 A.A.A.A.......y976f5dc2f a1 90 57 fc 76 a8 03 74-29 56 8d 44 24 6c 50 68 ..W.v..t)V.D$lPh76f5dc3f 44 f6 eb 76 6a 00 68 84-f6 eb 76 68 ee 0a 00 00 D..vj.h...vh....76f5dc4f 68 b0 68 eb 76 e8 f4 d1-ff ff 83 c4 1c a1 90 57 h.h.v..........W76f5dc5f fc 76 a8 10 74 75 cc eb-72 8b 44 24 0c 81 48 34 .v..tu..r.D$..H476f5dc6f 00 01 00 00 8b 44 24 0c-8b 40 18 a3 b8 7b fc 76 .....D$..@...{.v76f5dc7f e8 71 5a f7 ff 8b f0 85-f6 79 2a a1 90 57 fc 76 .qZ......y*..W.v76f5dc8f a8 03 74 ce 56 68 6c f5-eb 76 6a 00 68 84 f6 eb ..t.Vhl..vj.h...
观察内存属性
使用拓展命令!address来观察内存属性,如果指定内存地址,显示指定内存地址所在的块的信息,如果不指定,则显示所有的内存块的信息。
!pte命令显示指定地址的页表表项和页目录表项。
显示链表
dt遍历链表(数据结构)和dl遍历链表(专门遍历链表)。
- dl[b] startaddress counter size
拓展命令!list也可以遍历链表,但是这个较为复杂不建议使用