Skip to content

Pwnable.kr UAF

复制本地路径 | 在线编辑

C++ 虚函数

C++ 虚函数 vtable

知道对象在继承父类的时候,在子类的属性前面会有一个 vtable, 指向继承父类的值。

题目

#include <fcntl.h>
#include <iostream> 
#include <cstring>
#include <cstdlib>
#include <unistd.h>
using namespace std;

class Human{
private:
    virtual void give_shell(){
        system("/bin/sh");
    }
protected:
    int age;
    string name;
public:
    virtual void introduce(){
        cout << "My name is " << name << endl;
        cout << "I am " << age << " years old" << endl;
    }
};

class Man: public Human{
public:
    Man(string name, int age){
        this->name = name;
        this->age = age;
        }
        virtual void introduce(){
        Human::introduce();
                cout << "I am a nice guy!" << endl;
        }
};

class Woman: public Human{
public:
        Woman(string name, int age){
                this->name = name;
                this->age = age;
        }
        virtual void introduce(){
                Human::introduce();
                cout << "I am a cute girl!" << endl;
        }
};

int main(int argc, char* argv[]){
    Human* m = new Man("Jack", 25);
    Human* w = new Woman("Jill", 21);

    size_t len;
    char* data;
    unsigned int op;
    while(1){
        cout << "1. use\n2. after\n3. free\n";
        cin >> op;

        switch(op){
            case 1:
                m->introduce();
                w->introduce();
                break;
            case 2:
                len = atoi(argv[1]);
                data = new char[len];
                read(open(argv[2], O_RDONLY), data, len);
                cout << "your data is allocated" << endl;
                break;
            case 3:
                delete m;
                delete w;
                break;
            default:
                break;
        }
    }

    return 0;    
}

关键点:

可以看出 Man 和 Woman 都是继承了 Human 类,并且可以看出 Human 类的私有虚函数 give_shell 就是 system, 所以想办法执行这个函数。

程序给了我们 3 个选项:use 使用指针指向的函数; after 分配一段地址空间,我们可以用其将已经被 free 的内存,重新 allocate; free 将指针指向的内存释放。

利用思路

很明显,只有子类 introduce 会执行,所以就是把子类 introduce 改为父类 give_shell 的地址即可。

  • 假设 introduce 在子类的偏移量为 n, give_shell 在父类的偏移量为 m.
  • 假设 子类虚函数表 地址为 X, 父类虚函数表 为 Y.

做法:把 X 指向 Y+m-n 即可。因为要保证 X + n == Y + m.

里面的内容就是 p64(Y+m-n), 然后执行 3-2-2-1.

  • 第一步执行 3, 会把那两个指针执行的内存删除掉,可以调试发现大小是 24Bytes.
  • 第二步第三步执行 2, 会把之前删掉的内存块中添加 Y+m-n, 这样那两个指针开头为 Y+m-n.
  • 第四步执行 1, 让那两个指针指向的对象执行 introduce, 而找这个函数是通过对象开头的虚函数表,而这个表被我们改掉了,偏移 n 就是 Y+m, 所以成功执行了 give_shell.