uaf的原理

SLUB机制:对对象类型没有限制,两个对象只要大小差不多就可以重用同一块内存,因为系统分配的内存释放后,如果内存较小,并不会马上将其合并回收,会将其链接在fastbins下。这里如果我们对这个没有回收的内存进行读写就可以达到攻击的目的。(指向这块内存的指针叫做恶性迷途指针)

uaf源代码

#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的内存,在第二歩重新申请差不多大小的内存,就可以申请到这块内存,然后对刚刚释放的man和woman的内存进行读写。我们要对man的内存进行改写,因为释放顺序是man,woman,那么申请顺序就是woman,man。所以我们要申请两次。
其次我们要知道C++中虚函数反汇编后的结构。 当类中定义有虚函数时,编译器会把该类中所有虚函数的首地址保存在一张地址表中,即虚函数表中,如下图(网上的图来说明一下):

可以看出第一个虚函数的地址就是虚函数表的地址。所以该程序中give_shell虚函数地址就是该类虚函数表的地址。该地址加8(该程序是64位的)就是第二个虚函数地址,即introduce虚函数。所以要想得到shell,就要完成两个步骤:

  • 获得man对象申请的内存大小
  • man对象的首地址,其实就是man对象中give_shell的地址

用IDA反汇编就可以知道man对象申请内存的大小为24:

v13返回的是man对象的地址

v13+8就是introduce函数的地址:

我们定位到Human* m = new Man(“Jack”, 25);的汇编代码中,使用步入操作,可以在调用Human后返回man对象的地址。

我们已经得到了man对象的内存大小和首地址,我们只需要申请24字节大小的内存两次就可以申请到man对象的内存,要写入什么数据呢?我们需要调用introduce函数时去调用give_shell,那么就需要改变v13的值,我们将v13-8的值赋给v13,那么调用introduce时就是调用原来v13的值,即give_shell。0x401570-8=0x401568
exploit如下:

flag: yay_f1ag_aft3r_pwning