在分析代码执行类漏洞时,需要搞定程序的控制流。我们知道,在C语言中,一般是通过覆盖函数返回地址,来劫持控制流实现跳转的。在C++中,除此之外,多个一个虚函数表的东西,通过覆盖对象的虚函数表也是可以劫持控制流的。作为基础积累,本篇简要概括下C++的内存布局,主要是自己学习总结,部分内容来自网上。
注:所有实验在Android平台实现。
定义一个Person类
class Person
{
public:
Person():mId(0), mAge(20){
for(int i=0;i<10;i++){
grade[i]='A'+i;
}
}
void print1()
{
cout << "id: " << mId
<< ", age: " << mAge << endl;
}
void print2()
{
cout << "i am print2 "<< endl;
}
public :
int mId;
int mAge;
char grade[10];
};
测试代码:
typedef void (Person::*mPrint)();
int main(){
Person p;
int* ptr= (int*) &p;
cout << "address of Person obj:"<cout << "size of Person obj:"<< sizeof(p)<cout<< "address of mTd:"<< &p.mId<<",value = "<< p.mId<cout<< "address of mAge:"<< &p.mAge<<",value = "<< p.mAge<cout<< "address of grade:"<< &p.grade<<",value = "<< p.grade[0]<printf("address of print1: %p\n",f1);
//cout<< "address of function print1:"<< f1 <
(p.*f1)();
mPrint f2= &Person::print2;
//cout<< "address of function print2:"<< f2 <
printf("address of print2: %p\n",f2);
(p.*f2)();
}
运行结果:
据此可以看出,非静态类成员是保存在对象结构中的,而非静态成员函数在成员结构之外。此时,对象的内存模型为:
static成员变量和函数不是和对象绑定的,所以不占对象的内存空间。
采用类似的测试:
有虚函数时,对象首先存储虚函数表,即对象的首地址就是虚函数表地址。
Person类:
class Person
{
public:
Person():mId(0), mAge(20){
for(int i=0;i<10;i++){
grade[i]='A'+i;
}
}
void print1()
{
cout << "id: " << mId
<< ", age: " << mAge << endl;
}
void print2()
{
cout << "i am print2 "<< endl;
}
static void getName(){
printf("my name is venscor!\n");
}
virtual void job1()
{
cout << "Person 1 !!!!" << endl;
}
virtual void job2()
{
cout << "Person 2 ..." << endl;
}
virtual void job3()
{
cout << "Person 3 &&&&" << endl;
}
virtual ~Person()
{
cout << "~~~~~Person" << endl;
}
public :
char grade[10];
int mId;
int mAge;
};
测试代码:
typedef void (Person::*mPrint)();
int main(){
Person p;
int* ptr= (int*) &p;
cout << "address of Person obj:"<"size of Person obj:"<< sizeof(p)<"address of mTd:"<< &p.mId<<",value = "<< p.mId<"address of mAge:"<< &p.mAge<<",value = "<< p.mAge<"address of grade:"<< &p.grade<<",value = "<< p.grade[0]<printf("address of print1: %p\n",f1);
//cout<< "address of function print1:"<< f1 <*f1)();
mPrint f2= &Person::print2;
//cout<< "address of function print2:"<< f2 <printf("address of print2: %p\n",f2);
(p.*f2)();
printf("----------test static---------\n");
typedef void (*GetName)();
GetName ff= &Person::getName;
//cout<< "address of function print2:"<< f2 <printf("address of print2: %p\n",ff);
ff();
printf("------------------------------------------------\n");
int ** vtabptr=(int **)*(int**)&p;
typedef void (*FuncPtr)();
for (int i = 0; i < 3 && *vtabptr != NULL; ++i)
{
FuncPtr func = (FuncPtr)*vtabptr;
printf("%p\n",func);
func();
++vtabptr;
}
//虚函数表里面其他内容
while (*vtabptr)
{
cout << "*vtbl == " << *vtabptr << endl;
++vtabptr;
}
}
运行结果:
增加了虚函数后,对象所占空间大小增加了4,并且成员变量地址也从对象地址+4开始,可以看出,对象地址的前四个字节是用来存放一个指针,此指针指向虚函数表。
从函数调用顺序也可以看出,虚函数表中存放的函数地址的位置也是和前面类中定义的顺序是一致的。
此部分内容基本来自 C++对象模型之详述C++对象的内存布局,只做一个简单记录。
类代码:
class Base
{
public:
Base()
{
mBase1 = 101;
mBase2 = 102;
}
virtual void func1()
{
cout << "Base::func1()" << endl;
}
virtual void func2()
{
cout << "Base::func2()" << endl;
}
private:
int mBase1;
int mBase2;
};
class Derived : public Base
{
public:
Derived():
Base()
{
mDerived1 = 1001;
mDerived2 = 1002;
}
virtual void func2()
{
cout << "Derived::func2()" << endl;
}
virtual void func3()
{
cout << "Derived::func3()" << endl;
}
private:
int mDerived1;
int mDerived2;
};
遍历虚函数表:
void visitVtbl(int **vtbl, int count)
{
cout << vtbl << endl;
cout << "\t[-1]: " << (long)vtbl[-1] << endl;
typedef void (*FuncPtr)();
for (int i = 0; vtbl[i] && i < count; ++i)
{
cout << "\t[" << i << "]: " << vtbl[i] << " -> ";
FuncPtr func = (FuncPtr)vtbl[i];
func();
}
}
测试代码:
int main()
{
Derived d;
char *p = (char*)&d;
visitVtbl((int**)*(int**)p, 3);
p += sizeof(int**);
cout << *(int*)p << endl;
p += sizeof(int);
cout << *(int*)p << endl;
p += sizeof(int);
cout << *(int*)p << endl;
p += sizeof(int);
cout << *(int*)p << endl;
return 0;
}
类代码:
class Base1
{
public:
Base1()
{
mBase1 = 101;
}
virtual void funcA()
{
cout << "Base1::funcA()" << endl;
}
virtual void funcB()
{
cout << "Base1::funcB()" << endl;
}
private:
int mBase1;
};
class Base2
{
public:
Base2()
{
mBase2 = 102;
}
virtual void funcA()
{
cout << "Base2::funcA()" << endl;
}
virtual void funcC()
{
cout << "Base2::funcC()" << endl;
}
private:
int mBase2;
};
class Derived : public Base1, public Base2
{
public:
Derived():
Base1(),
Base2()
{
mDerived = 1001;
}
virtual void funcD()
{
cout << "Derived::funcD()" << endl;
}
virtual void funcA()
{
cout << "Derived::funcA()" << endl;
}
private:
int mDerived;
};
测试代码:
int main()
{
Derived d;
char *p = (char*)&d;
visitVtbl((int**)*(int**)p, 3);
p += sizeof(int**);
cout << *(int*)p << endl;
p += sizeof(int);
visitVtbl((int**)*(int**)p, 3);
p += sizeof(int**);
cout << *(int*)p << endl;
p += sizeof(int);
cout << *(int*)p << endl;
return 0;
}
运行结果:
可以看出,多个继承时,对象结构先存放主继承(第一个)的虚函数表,接着存放主继承类的成员,然后存储次继承类的虚函数表和成员,最后才是子类的成员。
类代码:
class Base
{
public:
Base()
{
mBase = 11;
}
virtual void funcA()
{
cout << "Base::funcA()" << endl;
}
virtual void funcX()
{
cout << "Base::funcX()" << endl;
}
protected:
int mBase;
};
class Base1 : public Base
{
public:
Base1():
Base()
{
mBase1 = 101;
}
virtual void funcA()
{
cout << "Base1::funcA()" << endl;
}
virtual void funcB()
{
cout << "Base1::funcB()" << endl;
}
private:
int mBase1;
};
class Base2 : public Base
{
public:
Base2():
Base()
{
mBase2 = 102;
}
virtual void funcA()
{
cout << "Base2::funcA()" << endl;
}
virtual void funcC()
{
cout << "Base2::funcC()" << endl;
}
private:
int mBase2;
};
class Derived : public Base1, public Base2
{
public:
Derived():
Base1(),
Base2()
{
mDerived = 1001;
}
virtual void funcD()
{
cout << "Derived::funcD()" << endl;
}
virtual void funcA()
{
cout << "Derived::funcA()" << endl;
}
private:
int mDerived;
};
测试代码:
int main()
{
Derived d;
char *p = (char*)&d;
visitVtbl((int**)*(int**)p, 4);
p += sizeof(int**);
cout << *(int*)p << endl;
p += sizeof(int);
cout << *(int*)p << endl;
p += sizeof(int);
visitVtbl((int**)*(int**)p, 3);
p += sizeof(int**);
cout << *(int*)p << endl;
p += sizeof(int);
cout << *(int*)p << endl;
p += sizeof(int);
cout << *(int*)p << endl;
return 0;
}
这里测试代码时,发现运行时会出错,就是子类在覆盖了虚继承的父类方法时,在父类的虚函数表中取地址然后调用时会出错。代码这里就不在提出来了,直接用别人的结论好了。
class Base { ...... };
class Base1 : virtual public Base { ...... };
类结构:
class Base { ...... };
class Base1 : virtual public Base { ...... };
class Base2 : virtual public Base { ...... };
class Derived : public Base1, public Base2 { ...... };
使用虚继承后,在派生类的对象中只存在一份的Base子对象,从而避免了二义性。由于是多重继承,且有一个虚基类(Base),所以Derived类拥有三个虚函数表,其对象存在三个vptr。如上图所示,第一个虚函数表是由于多重继承而与第一基类(Base1)共享的主要实例,第二个虚函数表是与其他基类(Base2)有关的次要实例,第三个是虚基类的虚函数表。
类Derived的成员与Base1中的成员排列顺序相同,首先是以声明顺序排列其普通基类的成员,接着是派生类的成员,最后是虚基类的成员。
派生自虚基类的派生类的虚函数表中,也不含有虚基类中的virtual函数,派生类重写的virtual函数会在所有虚函数表中得到更新。
在类Derived的对象中,Base(虚基类)子对象部分为共享区域,而其他部分为不变区域。
我们知道,在对象的内存结构中,只保存有对象的非static成员变量和虚函数表。那么,程序在运行时,是如何通过对象找到对应的函数呢?其实,这些不在对象内存结构中的数据,在编译时就已经确定了,那些对象的非static函数,其实在编译的时候,已经确定了对应的内存位置,调用时直接换成对应的call 0xaddr就ok了,和普通c的函数已经没有什么区别。不过,对于对象的非static函数,和对象存在一定的绑定关系,会将函数的第一个参数变成this指针,用以明确是那个对象的方法。例如,Base::funcA()方法编译后就是funcA(this)的形式。这一点,和在做so逆向的时候是一致的。
对于static方法,已经inline函数,在编译后,在发生函数调用时,会直接把对应的汇编代码copy到调用处。