Windbg技巧: 利用C++类对象的虚拟函数表指针在内存中搜索对象

Windbg技巧一例:

利用C++类对象的虚拟函数表指针在内存中搜索对象 -- by: 张佩

 

Windbg是Windows系统下的调试利器,但即便有了windbg,也有力不从心的时候。我最近对release版本的驱动程序调试得较多,最大的困扰是虽然有私有符号,但在切换堆栈帧的时候,大多数情况下无法定位局部变量,从而使得信息有效性极大降低。编译器对Release版本进行了多种优化,这一次影响到的局部变量优化。局部变量优化则有两种情况,第一种是局部变量A直接保存在寄存器中,而不在栈上申请空间;第二种是在栈上申请空间,但当变量A不再被使用时,新的局部变量B将覆盖A的位置,这样变量A的有效信息将消失。

有时候我通过windbg的搜索命令(s),可以多少化解优化带来的困扰。

1.    内存搜索

关于内存搜索,一篇很有趣的文章是调试专家张银奎写的小文《从堆里寻找丢失的数据》。他记述了一次直接在IE浏览器中写文章而上传失败后,无法通过IE网页找回文章内容的经历。但作者最终又找回了自己的文章,方法则是手动在IE进程的堆内存中搜索关键字并定位内存块。读之趣味盎然。

内存搜索的原理很简单,计算机处理的所有数据,都应该保存在内存或页文件上。通过搜索系统或进程的内存地址空间,就可以找到所有可能存在的内存数据。由于内存空间是无比庞大的,在x86系统上用户和系统地址空间各为2G,x64平台上则更打得惊人。在庞大的数据海洋中,定位几个字节是一个极缓慢的过程。玩游戏的很多都试过游戏编辑器,它使用的也就是这个方法,通过关键字在内存中定位某个关键值并修改。

如果能够存在字符串是很幸运的事情,但在程序里面存在特征字符串的可能性很低。如果我要定位一个类对象,有什么好办法吗?

2.    虚拟函数表指针

C++类里面只有存在一个虚拟函数,编译器就会为这个类创建一张唯一的虚拟函数表,并把类的虚拟函数指针保存在表中。虚拟函数表是唯一的,存在于某个固定的地方。类对象为了能够使用它,需要有一个内部指针,这个指针即虚拟函数表指针(vftable)。

假设在程序里面有下面的两个最简单的类继承关系:

class baseClass
{
	bool m_bInit;
public:
	baseClass(){m_bInit = false;}
	virtual bool inti();
};

class impClass:public baseClass
{
public:
	impClass(){};
};

bool baseClass::inti()
{
	m_bInit = true;
	return true;
}

int _tmain(int argc, _TCHAR* argv[])
{
	impClass obj;
	return 0;
}
 因为基类baseClass中存在一个虚拟函数,所以baseClass类就有一张虚拟函数表。而impClass继承了baseClass,也就同时继承了它的虚拟函数,从而也就需要一张虚拟函数表。并且注意,虽然impClass没有定义新的虚拟函数,但它依旧有一张属于自己的虚拟函数表,而不是和基类共用同一张表。

编译器给每个虚拟函数表指定了唯一的名称,由于这是编译器指定的,所以我们往往不好说名称会是什么,并且不同的编译器一定有不同的命名策略,得到的名称可能就不同。这样,我们且用通配符*把所有的名称列出来看一看。使用下面的命令:

0:000:x86> x baseClass::*
00a714d0 baseClass::baseClass (void)
00a713f0 baseClass::inti (void)
00a7574c baseClass::`vftable'= <no type information>
很显然,我们找到了属于based类的名为 baseClass::`vftable'的虚拟函数表,使用dps命令列举函数表中的内容如下:
0:000:x86> dps 00a7574c
00a7574c  00a711f4win32!ILT+495(?intibaseClassUAEXXZ)
00a75750  00000000
同理再看看impClass类的情况:
0:000:x86> x win32!impClass::*
00a71c50 win32!impClass::impClass (void)
00a7574c win32!impClass::`vftable' = <no type information>
00a75740win32!impClass::`vftable' = <no type information>
 

0:000:x86> dps 00a75740
00a75740  00a711f4win32!ILT+495(?intibaseClassUAEXXZ)
00a75744  00000000
现在我们再看看如果有一个impClass类对象obj,它该是怎样的成员构成:
0:000:x86> dt /b obj
Local var @ 0x29f8b8 Type impClass
   +0x000 __VFN_table :0x00a75740
   +0x004 m_bInit          : 0

 0:000:x86> ?? sizeof(obj)
unsigned int 8

0:000:x86> dd 0x29f8b8 L2
0029f8b8  00cd5740 cccccc00
也就是说,对于一个impClass类型的对象而言,它内部仅有4个字节的内容,这个内容就是一个指向impClass虚拟函数表的指针。这样一来,我们就很有可以通过寻找这个指针而定位此对象。

3.    Windbg的内存搜索命令

确定了有可用的关键字后,我们开始下一步的工作。Windbg调试器提供了一个内存搜索命令:s(英文单词search的首字母)。这条命令的语法定义如下:

Syntax:

s [-[[Flags]]Type]Range Pattern
s -[[Flags]]vRange Object
s -[[Flags]]saRange
s -[[Flags]]suRange

参数Type是被搜索的关键字类型:

Type          Description
b                 Byte (8 bits)
w                WORD (16 bits)
d                 DWORD (32 bits)
q                 QWORD (64 bits)
a                 ASCII string(not necessarily a null-terminated string)
u                 Unicode string(not necessarily a null-terminated string)

参数Range是搜索范围。在32位系统的用户进程中,如果我想遍历整个进程用户地址空间,一般会使用下面的方式来定义我的搜索范围:

0x0 L?80000000(从用户空间起始地址,到结束地址)

如果嫌上面的范围太大,会拖慢搜索进度,则可以先搜索0-0x20000000,然后次第递增。如果在32位系统内核中调试,则应该把起始地址定为0x80000000。

参数Pattern即被搜索的关键字。关于S命令的更详细描述,可参考windbg帮助文档(本文未描述到的Flags参数,可被用来进行递增搜索,即可在前一次搜索的结果上进行再次搜索)

下面的示例是搜索含有本文中impClass::`vftable`地址的内存:

0:000:x86> s -d 0 L?80000000 0x00cd5740
0029f8b8  00cd5740 cccccc00cccccccc 0029f914  @W............).
00cd2130  00cd5740 5ff8458bc4815b5e 000000cc  @W...E._^[......
通过搜索整个用户内存,找到了两个可能结果,而为了判断搜索结果是否正确,再使用一些其他的特征值进行判断是必要的。在我们这里例子里,使用dt命令对可能的结果进行成员变量解析,结果如下: 

0:000:x86> dt 0029f8b8 impClass
win32!impClass
   +0x000 __VFN_table :0x00cd5740
   +0x004 m_bInit          : 0
 

0:000:x86> dt 00cd2130 impClass
win32!impClass
   +0x000 __VFN_table :0x00cd5740
   +0x004 m_bInit          : ffffffffffffff8b
从上面的结果来看,很明显第二个地址则是错误的,而第一个地址则极可能是正确的。

4.    小结

如果要在茫茫人海中找一个人,非得有面貌特征才行。内存搜索亦是如此。发现了虚拟函数表的这个价值令我激动不已。在一个已破损的调用栈里面寻找结构体或类对象指针是一件非常痛苦的事情,而本文所介绍的借助于虚拟函数表寻找类对象的方法,能够在一定程度上减轻这种痛苦。当然这种方法有很大局限性,首先它只能运用于含有虚拟函数的类对象身上,其次搜索过程可能很费时,而对于内存搜索的结果还需进一步的甄别才能得到有用信息。

多多少少,把这一技巧介绍给用得上的人。

参考文档:

张银奎 《从堆里寻找丢失的数据》http://advdbg.org/blogs/advdbg_system/articles/3413.aspx

 


  • 3
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值