C++虚函数:虚指针、虚表、虚函数入口地址

测试程序:

//test.c
#include"stdio.h"
#include"string.h"
class GSVirtual
{
public:
	void gsv(char *src)
	{
		char buf[200];
		strcpy(buf,src);
		vir2();
	}
	virtual void vir1()
	{
		printf("vir1");
	}
	virtual void vir2()
	{
		printf("vir2");
	}
};
int main(int argc,char *argv[])
{
	GSVirtual test;
	test.gsv(argv[1]);
	return 0;
}

在linux下编译:

$g++ -o vtabletest ./vtabletest.c

在ida下查看vtabletest的反汇编,找到关键函数点 gsv(char *src):

该函数中调用了虚函数vir2(),虽然gsv显示只有一个参数,但是实际上默认还有另一个参数:虚指针,查看调用gsv时,参数入栈可发现,有两个参数入栈:

然后,我们回到gsv函数中,其中首先分配了当前栈帧空间:

.text:08048538 55                                               push    ebp

.text:08048539 89 E5                                         mov     ebp, esp

.text:0804853B 81 EC F8 00 00 00                  sub     esp, 0F8h

然后,将第一个参数复制到当前栈帧中:注意这个参数就是虚指针的地址(一般在上一个函数的栈帧中)

.text:08048541 8B 45 08                                      mov     eax, [ebp+arg_0]      //arg0=8

.text:08048544 89 85 24 FF FF FF                      mov     [ebp+var_DC], eax   //var_DC=-0XDC

后面,函数会分配函数的局部变量的栈帧中的位置,然后执行strcpy(buf,src)

然后调用虚函数vir2(),我们主要来分析这一段代码:

.text:08048576 8B 85 24 FF FF FF                             mov     eax, [ebp+var_DC]    //将虚指针地址赋值给eax

.text:0804857C 8B 00                                         mov     eax, [eax]         //提取虚指针内存地址中的虚表入口地址,一般在.rodata中

.text:0804857E 83 C0 04                                      add     eax, 4             //由于调用的是vir2(),因此,该虚函数地址在虚表中的位置偏移4*1 bytes

.text:08048581 8B 10                                         mov     edx, [eax]        //提取vir2()虚函数的入口地址

.text:08048583 8B 85 24 FF FF FF                             mov     eax, [ebp+var_DC]

.text:08048589 89 04 24                                      mov     [esp], eax                //虚指针继续入栈,可视为为下一个函数调用的参数

.text:0804858C FF D2                                         call    edx            //调用vir2()

在linux中,用gdb调试:

在0x8048576处下断点后,查看相关内存信息:

可对照ida下的汇编代码看,ebp+8和ebp-0xdc 存储的都是虚指针地址0xbffff2dc 在上一个函数的栈帧中

虚指针指向地址0x80486c8,即虚表地址

虚表中按顺序存储了每个虚函数的入口地址。

0x80485a2 和0x80485b6分别就是虚函数vir1()和vir2()的地址:

最后,测试程序存在缓冲区溢出漏洞,在gs保护下,则可通过覆盖虚表指针来劫持控制流。

时间: 2024-11-07 17:13:42

C++虚函数:虚指针、虚表、虚函数入口地址的相关文章

C++内存分布 虚表 虚指针(非常重要)

C++内存分布 虚表 虚指针: class Base { public: int m_base; }; class DerivedA: public Base { public: int m_derivedA; }; class DerivedB: public Base { public: int m_derivedB; }; class DerivedC: public DerivedA, public DerivedB { public: int m_derivedC; }; 类结构图:

c/c++: c++继承 内存分布 虚表 虚指针 (转)

http://www.cnblogs.com/DylanWind/archive/2009/01/12/1373919.html 前部分原创,转载请注明出处,谢谢! class Base { public:  int m_base; }; class DerivedA: public Base { public:  int m_derivedA; }; class DerivedB: public Base { public:  int m_derivedB; }; class DerivedC

类的成员函数的指针

前面一篇文章<函数的指针 >介绍了全局函数的指针,现在我们再来介绍一下成员函数的指针. 成员函数指针的定义: 一般形式 Return_Type (Class_Name::* pointer_name) (Argument_List); 用typedef简化的形式 Typedef Return_Type (Class_Name::* FuncPtr_Type) (Argument_List); FuncPtr_Type pFunc = NULL; //pFunc为成员函数指针的指针变量 成员函数

【转载】C/C++杂记:深入理解数据成员指针、函数成员指针

原文:C/C++杂记:深入理解数据成员指针.函数成员指针 1. 数据成员指针 对于普通指针变量来说,其值是它所指向的地址,0表示空指针.而对于数据成员指针变量来说,其值是数据成员所在地址相对于对象起始地址的偏移值,空指针用-1表示.例: 代码示例:   2. 函数成员指针 函数成员指针与普通函数指针相比,其size为普通函数指针的两倍(x64下为16字节),分为:ptr和adj两部分. (1) 非虚函数成员指针 ptr部分内容为函数指针(指向一个全局函数,该函数的第一个参数为this指针),ad

【C语言】函数指针与回调函数

在C语言中:指针是C语言的特色,有着各种各样的指针,普通的变量指针,常量指针,数组指针,指针数组,函数指针,指针函数.我们就讲一下函数指针与回调函数吧 首先关于函数指针,其实很简单. 对于一个函数指针来说,顾名思义,就是一个指向函数的指针,需要知道的是,对于指针而言,他总是存储一块地址,地址里面有着一个,一组,或者一块数据,在函数中,函数的存储是放在代码段的,每个函数都有着一个函数首地址,调用了这个地址相当于调用的这个函数. 具体的可以观看我的这篇博客,其中就通过在内存阶段改变栈帧返回值,成功的

C/C++杂记:深入理解数据成员指针、函数成员指针

1. 数据成员指针 对于普通指针变量来说,其值是它所指向的地址,0表示空指针. 而对于数据成员指针变量来说,其值是数据成员所在地址相对于对象起始地址的偏移值,空指针用-1表示.例: 代码示例: struct X { int a; int b; }; #define VALUE_OF_PTR(p) (*(long*)&p) int main() { int X::*p = 0; // VALUE_OF_PTR(p) == -1 p = &X::a; // VALUE_OF_PTR(p) ==

数组指针、指针数组、函数指针、指针函数 -----笔记

1.数组的四种访问方式 定义数组 a[]; 指针 *p 指向数组a; (1) 利用数组的下表进行访问 a[i]; (2) 数组名+地址的偏移量i *(a+i) (3) 利用指针 p 进行下表访问 p[i] (4) 指针p + 地址的偏移量i *(p+i) 一维数组数组名:相当于一个单指针 2. 数组指针(指针)     指针数组(数组) 函数指针(指针)     指针函数(函数)    -------->只看后边两个字就能够区分是指针还是数组或函数 _______________________

函数和指针 C++

一.用函数指针变量调用函数. 指针变量也可以指向一个函数,一个函数在编译时被分配给一个入口地址.这个函数入口地址就称为函数的指针.可以用一个指针变量指向函数,然后通过该指针变量调用此函数. 定义指向函数的方法,格式是: int (*p)(int,int);     函数名代表函数入口地址,而max(a,b)则是函数调用. 二.返回指针值的函数 一个函数可以带回一个整数值.字符值.实型值等,也可以带回指针型的数据,即地址.其概念和以前类似,只是带回的值是指针类型而已.返回指针值的函数简称为指针函数

iOS指向函数的指针和block

  一:block基础知识 block基础知识 基本概念:block是用来保存一段代码的:^:是block得标志  好比*:是指针的标志 特点:1:保存一段代码: 2:可以有参数和返回值: 3:可以作为函数的参数传递: 与代码块的区别,代码块里的代码会自动执行,block中代码要手动调用: 二:普通数据类型.指向函数的指针.block的定义的类比 1:基本数据类型: 例如:int a = 10; 格式:数据类型  变量名 = 值: 2:指向函数的指针:可以仿照上边基本数据类型的定义 例如:voi

函数与指针

1.C语言中函数有自己特定的类型 2.函数的类型由返回值,参数类型和参数个数共同决定 3.C语言中通过typedef为函数类型重命名 4.函数指针用于指向一个函数 5.函数名是执行函数体的入口地址 6.可通过函数类型定义函数指针:Func Type* pointer 7.也可以直接定义:type (*pointer)(parameter list) pointer为函数指针变量名,type为指向函数的返回值类型,paramter list为指向函数的参数类型列表 #include <stdio.