windbg高级条件断点初探

背景介绍:

我对windbg高级条件断点的探究起源于最近遇到一个问题:一个很久没有修改过的模块(rtutils.dll)在用户机器上出现了崩溃,由于模块太旧,且维护人员已经离职,无从知晓到该模块是何时,由谁加载的。为了弄清楚这个问题,于是想到用windbg的条件断点应该能定位问题,并进行了一番初步探究。

分析:

如何才能够定位到是谁加载的rtutils.dll呢?我首先想到的是系统API里的LoadLibrary,毕竟dll加载都得使用这个API,而且LoadLibrary有一个参数,表示要加载的文件路径。接下来要解决的就是如何对LoadLibrary下断点了,由于工程很庞大,加载dll有上百个,如果每个LoadLibrary都断下来,然后通过看参数内容是不是rtutils.dll来定位,那估计眼睛都会看瞎的。怎么办?这时强大的windbg条件断点语句就该出场了,让他帮我完成一个当LoadLibrary的参数中包含“rtutils.dll”字符串时就break的断点,然后就能用kb看堆栈,就能找到加载dll的源头了。


编写条件断点语句:

这里将要用到windbg脚本的相关语法,这里只对用到的语法和命令作简单解释,有兴趣的可以参考入门教程:windbg 脚本简单入门,如果想获取更加详尽的语法知识,可以查看windbg的帮助文档。

为了能让复杂的条件语句看起来比较清晰直观,我们需要创建一个脚本文件,姑且命名为:bpstrcmp.txt,将条件语句写在其中。

首先我们需要获取LoadLibrary的参数内容,我们知道LoadLibrary的参数是一个const wchar*类型的指针(姑且给他取名:pFileName,下文都用此名称),那么pFileName存在哪里呢?了解函数调用栈同学应该知道,pFileName存储在esp+4指向的栈地址空间里(esp指向栈顶,函数调用时参数会以从右到左顺序入栈(cdecl调用约),接下来call指令执行时,会将函数的返回地址,也就是call的下一条指令地址,压入栈中,此时esp会减4,所以esp+4为第一个参数地址)。接下来就是要获取pFileName,这里需要使用poi运算符,poi运算符的作用是从一个指定地址获取一个指针大小内容,我们的程序是32位的,所以取出的数据大小是4字节,如果是64位程序,那大小就是8字节。太好了,于是乎我们可以用poi(esp+4)获取到pFileName了。

有了pFileName,接下来我们还需要字符串查找函数和条件语句,和C/C++语言一样,windbg脚本语言也提供了丰富流程控制语句,如下:

.if
.else
.elif
.for
.while
.break
.continue
.do

这里我们使用.if和.else就够了,用法和C/C++类似。同样,windbg脚本提供了字符串比较运算符:

//和C语言的strcmp类似,值为-1或0或1
$scmp("String1", "String2")

//同上,只是大小写不敏感
$sicmp("String1", "String2")

//从String中查找是否包含Pattern,如果包含则值为:1,否则为:0;最变态的是它支持字符串通配符(如果想了解它的字符串通配符语法可以打开windbg,按F1打开帮助文档,然后搜索String Wildcard Syntax)
$spat("String", "Pattern")
有了这些,我们的脚本就可以顺利完成了,内容如下:

as /mu ${/v:ModuleName} poi(esp+4)
.if ($spat(@"${ModuleName}", "*rtutils.dll")) { .echo ${ModuleName}; kb; } .else { .echo not hit; gc; }

额? as /mu ${/v:ModuleName} poi(esp+4) 又是什么鬼?下面让我来一一介绍:

as命令用来定义一个别名,类似于C/C++的#define。

/mu参数:将别名设置为以null结尾的Unicode字符串。

${/v:ModuleName}:其中ModuleName即为我们定义的别名;${ } 为别名解释器,将别名扩展成它真正对应的内容;/v表示不管别名是否有定义,都保持别名字面意思不变,也就是不扩展成它真实的内容,即保持为ModuleName。

所以as /mu ${/v:ModuleName}  poi(esp+4) 的代表的含义就是:定义一个叫作ModuleName的别名,该别名等价于一个位于poi(esp+4)地址上以null结尾的Unicode字符串,在这里即为LoadLibrary的参数pFileName。

接下来我们看看$spat(@"${ModuleName}", "*rtutils.dll")语句中的内容:

${ModuleName}:其中${ }为解释器,ModuleName为别名,windbg会在执行脚本会将${ModuleName}替换为其真身(可以理解为C语言的宏扩展),在本例中即为LoadLibrary的参数pFileName。

*号:即为通配符,表示rtutils.dll前有0个或多个字符。

@"string"又是啥玩意?该语法是用来禁用掉转义符,比如字符串中包含"\r",会被当做'\'和'r'单独处理。如果不使用@"string"语法会有什么问题呢?举个栗子:

表达式$spat("C:\rtutils.dll", "*rtutils.dll")的值是0,因为'\r'被当做转义符,所以无法从C:\rtutils.dll找到字符'r',从而导致匹配不到*rtutils.dll,与预期不符

至此,脚本已经写的妥妥,那么怎么执行脚本呢?

windbg提供了$$<FilePath命令来执行脚本,于是我们的条件断点语句可以写出来了:

bp Kernel32!LoadLibraryW "$$<D:\\tool\\help\\bpstrcmp.txt"

LoadLibraryW所在的dll是Kernel32.dll,要查找API所在模块,可以msdn上搜索API名称,一般在页面下面会写明其所在模块;我们的程序是Unicode版本,所有此处应该对LoadLibraryW函数下断点,如果是多字符集版本的程序,应该对LoadLibraryA下断点


验证效果:

测试程序代码如下:

// testbp.cpp : 定义控制台应用程序的入口点。
//

#include "stdafx.h"
#include <Windows.h>

void loadfunc()
{
	LoadLibrary(L"c:\\temp\\rnr20.dll");
	LoadLibrary(L"c:\\temp\\RotMgr.dll");
	LoadLibrary(L"c:\\temp\\rtmcodecs.dll");
	LoadLibrary(L"c:\\temp\\rtutils.dll");
	LoadLibrary(L"c:\\temp\\RzChromaSDK64.dll");
}

int _tmain(int argc, _TCHAR* argv[])
{
	loadfunc();

	return 0;
}
首先运行windbg,配置好微软符号路径(srv*d:/Symbols*http://msdl.microsoft.com/download/symbols),然后Ctrl+E让windbg运行我们的测试程序testbp.exe,设置好条件断点,然后执行g命令,我们将看到windbg上有如下内容:
c:\temp\rtutils.dll
ChildEBP RetAddr  Args to Child              
WARNING: Stack unwind information not available. Following frames may be wrong.
0019fd7c 00411407 0041577c 0019ff28 00411078 KERNEL32!LoadLibraryW
0019fe54 00411a93 00411078 00411078 0022f000 testbp!loadfunc+0x67 [d:\projects\testbp\testbp.cpp @ 12]
0019ff28 004119c6 00000001 00a20f68 00a20ff8 testbp!wmain+0x23 [d:\projects\testbp\testbp.cpp @ 20]
0019ff78 0041180d 0019ff94 741862c4 0022f000 testbp!__tmainCRTStartup+0x1a6 [f:\sp\vctools\crt_bld\self_x86\crt\src\crtexe.c @ 594]
0019ff80 741862c4 0022f000 741862a0 f2b86886 testbp!wmainCRTStartup+0xd [f:\sp\vctools\crt_bld\self_x86\crt\src\crtexe.c @ 414]
0019ff94 77120fd9 0022f000 9f3c3364 00000000 KERNEL32!BaseThreadInitThunk+0x24
0019ffdc 77120fa4 ffffffff 77142f0c 00000000 ntdll!__RtlUserThreadStart+0x2f
0019ffec 00000000 00411078 0022f000 00000000 ntdll!_RtlUserThreadStart+0x1b
eax=00000000 ebx=0022f000 ecx=9f25ccb8 edx=00000000 esi=0019fd88 edi=0019fe54
eip=7418dc30 esp=0019fd80 ebp=0019fe54 iopl=0         nv up ei pl zr na pe nc
cs=0023  ss=002b  ds=002b  es=002b  fs=0053  gs=002b             efl=00000246
KERNEL32!LoadLibraryW:
7418dc30 8bff            mov     edi,edi
可以看到程序被windbg断了下来,打印出了加载的dll路径和当前堆栈;当前堆栈是否就是我们要找的加载rtutils.dll的地方呢?下面我们通过查看参数内容验证一下:
0:000> du 0041577c 
0041577c  "c:\temp\rtutils.dll"
0:000> al
  Alias            Value  
 -------          ------- 
 ModuleName       c:\temp\rtutils.dll 

du 0041577c:显示0041577c地址上Unicode字符串(0041577c即为LoadLibraryW的参数地址)

al:列出当前定义的别名和它对应的真实值

从打印出的结果我们可以看到当前正在加载rtutils.dll堆栈,至此我们的条件断点成功完成它的任务。

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值