抱歉,您的浏览器无法访问本站

本页面需要浏览器支持(启用)JavaScript


了解详情 >

BlackBird的博客

这世界上所有的不利状况,都是当事者能力不足导致的

C++C语言最大的区别应该就是:C语言是面向过程编程,而C++是面向对象编程。

之前做逆向的时候大部分也只是C逆向,然后部分的C++逆向,然而当时的C++逆向也大多没有涉及到面向对象(主要是当时我也不会面向对象,这个学期才学习的面向对象……最近在学PWN的时候避免不了面向对象的C++逆向,所以写篇博客来学习一下。

但比较尴尬的是……这个学期学的是javapython的面向对象,我的C++还没有面向对象。所以正好边学习C++的面向对象,边看它的逆向。

类的基础

基础内容

  • 访问权限关键字: public, private, protected

  • 与类同名为构造函数

  • 关键符号

    • ::是作用域运算符,A::B表示作用域A中的名称BA可以是名字空间、类、结构

      • 类作用域操作符

        ::指明了成员函数所属的类。如:M::f(s)就表示f(s)是类M的成员函数。(作用域,如果想在类的外部引用静态成员函数,或在类的外部定义成员函数都要用到。使用命名空间里的类型或函数也要用到(如:std::cout, std::cin, std::string 等等)

      • 表示引用成员函数及变量,作用域成员运算符 例:System::Math::Sqrt()相当于System.Math.Sqrt()

    • .->

      • .A.BA为对象或者结构体,点号.左边必须为实体
      • ->A->BA为指针,->是成员提取,A->B是提取A中的成员BA只能是指向类、结构、联合的指针;->左边必须为指针
// 区别.与->
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一样), 但是识别出了所有的方法(但是这里的方法都换个名字变成了函数):

image-20210727154739738

我们在structures里面还原出来这个class:

image-20210727161210285

我们通过看大小不难发现,构建出来的class是不包含类方法的;同时,在IDA里面看不出属性的性质(public、private、protected

image-20210727220429306

在方法中,我们看到方法的隐藏参数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();
}

image-20210728162747433

我们的Stu子类只是比Person父类多了一个double和一个char,但是sizeof却从0x44增长到0x58,应该只增长9,结果却增长了20。这里是因为在这个class内部,编译器仍进行了堆栈对齐来提高效率,导致了部分空间的浪费。

image-20210728162820007

我们可以看到,子类的构造函数调用了父类的构造函数

多态

#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函数:

image-20210728171116103

从这里可以看出来,他是已经绑定了的,就是已经确定执行的方法。

再看一下加了virtual的,还是主要看一下ask函数:

image-20210728172120162

这里的函数就不是绑定了的,是一个动态的。由于多态的缘故,编译器还将相关多态的方法的函数指针存储在内存中:

image-20210728172411041

image-20210728172425928

其余的几个也是这样的。

从反汇编代码可以看出来,这里的第一个指针是虚函数表指针,这里的虚函数表(这里的函数表应该是该类所有涉及多态的方法表),虚函数表里面存储的才是类方法的指针。

放个图便于理解:

image-20210728184118818

我们写一个涉及多个多态的程序:

#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;
}

image-20210728180130093

image-20210728180140961

image-20210728180204158

也就是说如果一个类涉及多态,那么在内存中,该类的第一个元素是一个指针指向该类的vtable,然后vtable里面存储着该类涉及多态的方法的函数指针。如果这个类不涉及多态的话,那么这个类是没有vtable的,也就是说没有第一个指向vtable的那个指针的。

貌似大部分的C++PWN都是对这个vtable进行攻击的……

参考资料:

C++中::和:, .和->的作用和区别?

C++面向对象程序设计

C++逆向分析

评论