C++
与C
语言最大的区别应该就是:C
语言是面向过程编程,而C++
是面向对象编程。
之前做逆向的时候大部分也只是C
逆向,然后部分的C++
逆向,然而当时的C++
逆向也大多没有涉及到面向对象(主要是当时我也不会面向对象,这个学期才学习的面向对象……最近在学PWN
的时候避免不了面向对象的C++
逆向,所以写篇博客来学习一下。
但比较尴尬的是……这个学期学的是java
和python
的面向对象,我的C++
还没有面向对象。所以正好边学习C++
的面向对象,边看它的逆向。
类的基础
基础内容
-
访问权限关键字:
public, private, protected
-
与类同名为构造函数
-
关键符号:
-
::
是作用域运算符,A::B
表示作用域A
中的名称B
,A
可以是名字空间、类、结构-
类作用域操作符
::
指明了成员函数所属的类。如:M::f(s)
就表示f(s)
是类M
的成员函数。(作用域,如果想在类的外部引用静态成员函数,或在类的外部定义成员函数都要用到。使用命名空间里的类型或函数也要用到(如:std::cout, std::cin, std::string
等等) -
表示引用成员函数及变量,作用域成员运算符 例:
System::Math::Sqrt()
相当于System.Math.Sqrt()
-
-
.
与->
.
:A.B
则A
为对象或者结构体,点号.
左边必须为实体->
:A->B
则A
为指针,->
是成员提取,A->B
是提取A
中的成员B
,A
只能是指向类、结构、联合的指针;->
左边必须为指针
-
// 区别.与->
class A
{
public:
int a = 0;
};
int main()
{
A b;
A *p = &b;
b.a; //类类型的对象访问类的成员
p->a; //类类型的指针访问类的成员
}
逆向分析
#include <bits/stdc++.h>
using namespace std;
class Person
{
public:
Person(char *name, int hei, int wei, char *dsp)
{
strcpy(this->name, name);
strcpy(this->dsp, dsp);
this->height = hei;
this->weight = wei;
}
void set_hobby(char *hobby);
char *show_hobby();
char name[20], dsp[20];
private:
int height, weight;
protected:
char hobby[20];
};
void Person::set_hobby(char *hobby)
{
strcpy(this->hobby, hobby);
}
char *Person::show_hobby()
{
return this->hobby;
}
int main()
{
Person hs("innerspace-hs", 190, 190, "most_powerful");
hs.set_hobby("Be stronger!");
cout << hs.name << " is " << hs.dsp << ". And " << hs.name << " likes to " << hs.show_hobby() << endl;
Person *bb_ptr;
bb_ptr = new Person("BlackBird", 177, 140, "most_vegetable!!!");
bb_ptr->set_hobby("Be more vegetable");
cout << bb_ptr->name << " is " << bb_ptr->dsp << ". And " << bb_ptr->name << " likes to " << bb_ptr->show_hobby();
}
我们看一下IDA
里面的内容,它并没有识别出class
的结构(和struct
一样), 但是识别出了所有的方法(但是这里的方法都换个名字变成了函数):
我们在structures里面还原出来这个class:
我们通过看大小不难发现,构建出来的class是不包含类方法的;同时,在IDA
里面看不出属性的性质(public、private、protected
)
在方法中,我们看到方法的隐藏参数this
指针,相当于python
里面的self
参数
类高级点的
继承
- 在继承的时候,继承的父类前面加的修饰词规定在子类中父类的属性与方法的属性……说的比较绕,来看段代码:
class Father {
private:
int age;
protected:
double a;
public:
[[nodiscard]] int get_age() { return this->age; }
}
class Son1 : Father {
private:
// father's
// private:
// int age;
// protected:
// double a;
// public:
// [[nodiscard]] int get_age() { return this->age; }
protected:
double b;
public:
...
}
class Son2 : public Father {
private:
...
protected:
...
public:
// father's
// private:
// int age;
// protected:
// double a;
// public:
// [[nodiscard]] int get_age() { return this->age; }
...
}
-
子类要使用父类的构造函数
class father{ public: father(……){ this->…… } …… privatr: …… protected: …… }; class son:farther{ public: using father::father }
逆向分析
#include <bits/stdc++.h>
using namespace std;
class Person
{
public:
Person(char *name, int hei, int wei, char *dsp)
{
strcpy(this->name, name);
strcpy(this->dsp, dsp);
this->height = hei;
this->weight = wei;
}
void set_hobby(char *hobby);
char *show_hobby();
char name[20], dsp[20];
private:
int height, weight;
protected:
char hobby[20];
};
void Person::set_hobby(char *hobby)
{
strcpy(this->hobby, hobby);
}
char *Person::show_hobby()
{
return this->hobby;
}
class Stu: public Person{
using Person::Person;
public:
double score;
char lev;
void get_sco(){
cout<<this->name<<" gets "<<this->score<<" and "<<this->lev<<"!"<<endl;
}
void set_sco(double sco, char *lev){
this->lev = *lev;
this->score = sco;
}
};
int main()
{
Stu *bb;
bb = new Stu("BlackBird", 177, 140, "most_vegetable!!!");
bb->set_hobby("Be more vegetable");
bb->set_sco(59.9, "D");
cout << bb->name << " is " << bb->dsp << " And " << bb->name << " likes to " << bb->show_hobby() << ".\n";
bb->get_sco();
}
我们的Stu
子类只是比Person
父类多了一个double
和一个char
,但是sizeof
却从0x44
增长到0x58
,应该只增长9,结果却增长了20。这里是因为在这个class
内部,编译器仍进行了堆栈对齐来提高效率,导致了部分空间的浪费。
我们可以看到,子类的构造函数调用了父类的构造函数
多态
#include<bits/stdc++.h>
using namespace std;
class Stu{
public:
char name[20];
Stu(char* name){
strcpy(this->name, name);
}
void show()
{
cout<<"I'm a student.\n";
}
};
class Dayi:public Stu{
public:
int grade = 1;
using Stu::Stu;
void show(){
cout<<"My name is "<<this->name<<" and I'm in Grade one\n";
}
};
class Daer : public Stu
{
public:
int grade=2;
using Stu::Stu;
void show()
{
cout << "My name is " << this->name << " and I'm in Grade two\n";
}
};
class Dasan : public Stu
{
public:
int grade=3;
using Stu::Stu;
void show()
{
cout << "My name is " << this->name << " and I'm in Grade three\n";
}
};
void ask(Stu* stu){
stu->show();
};
int main(){
Stu *huai=new Stu("Huai");
Dayi *bb=new Dayi("BlackBird");
Daer *rx=new Daer("Reverier");
Dasan *xq=new Dasan("Recucluer");
bb->show();
rx->show();
xq->show();
cout<<"-------------------------------\n";
ask(huai);
ask(bb);
ask(rx);
ask(xq);
return 0;
}
这个程序的输出是
My name is BlackBird and I'm in Grade one My name is Reverier and I'm in Grade two My name is Reculer and I'm in Grade three ------------------------------- I'm a student. I'm a student. I'm a student.
但是这明显不符合我们多态的预期,所以应该在父类的该方法下加上virtual
关键字:
class Stu{
public:
char name[20];
Stu(char* name){
strcpy(this->name, name);
}
virtual void show(){
cout<<"I'm a student.\n";
}
};
将该函数声明为虚函数,满足其动态性。
结果为:
My name is BlackBird and I'm in Grade one My name is Reverier and I'm in Grade two My name is Recucluer and I'm in Grade three ------------------------------- My name is BlackBird and I'm in Grade one My name is Reverier and I'm in Grade two My name is Reculer and I'm in Grade three
逆向分析
我们先看不加virtual
的,主要看一下ask
函数:
从这里可以看出来,他是已经绑定了的,就是已经确定执行的方法。
再看一下加了virtual
的,还是主要看一下ask
函数:
这里的函数就不是绑定了的,是一个动态的。由于多态的缘故,编译器还将相关多态的方法的函数指针存储在内存中:
其余的几个也是这样的。
从反汇编代码可以看出来,这里的第一个指针是虚函数表指针,这里的虚函数表(这里的函数表应该是该类所有涉及多态的方法表),虚函数表里面存储的才是类方法的指针。
放个图便于理解:
我们写一个涉及多个多态的程序:
#include<bits/stdc++.h>
using namespace std;
class Stu{
public:
char name[20];
Stu(char* name){
strcpy(this->name, name);
}
virtual void show()
{
cout << "I'm a student.\n\n";
}
virtual void tell(){
cout<<"You're a student.\n";
}
};
class Dayi:public Stu{
public:
int grade = 1;
using Stu::Stu;
void show(){
cout << "My name is " << this->name << " and I'm in Grade one\n\n";
}
void tell(){
cout<<"You're a student in grade one.\n";
}
};
class Daer : public Stu
{
public:
int grade=2;
using Stu::Stu;
void show()
{
cout << "My name is " << this->name << " and I'm in Grade two\n\n";
}
void tell()
{
cout << "You're a student in grade two.\n";
}
};
class Dasan : public Stu
{
public:
int grade=3;
using Stu::Stu;
void show()
{
cout << "My name is " << this->name << " and I'm in Grade three\n\n";
}
void tell()
{
cout << "You're a student in grade three.\n";
}
};
void ask(Stu* stu){
stu->show();
};
void tell(Stu* stu){
stu->tell();
}
int main(){
Stu *huai=new Stu("Huai");
Dayi *bb=new Dayi("BlackBird");
Daer *rx=new Daer("Reverier");
Dasan *xq=new Dasan("Recucluer");
tell(huai);
ask(huai);
tell(bb);
ask(bb);
tell(rx);
ask(rx);
tell(xq);
ask(xq);
return 0;
}
也就是说如果一个类涉及多态,那么在内存中,该类的第一个元素是一个指针指向该类的vtable
,然后vtable
里面存储着该类涉及多态的方法的函数指针。如果这个类不涉及多态的话,那么这个类是没有vtable的,也就是说没有第一个指向vtable的那个指针的。
貌似大部分的C++PWN
都是对这个vtable
进行攻击的……
参考资料:
C++逆向分析