pwnable.kr Toddler's Bottle_pt8 UAF

2016-12-27 10:21:49来源:作者:Blog of Mathias人点击

第七城市

这道题目是非常典型的user-after-free漏洞

同时也涉及到了关于fastbin的一些知识

首先我们来了解一下在linux的堆中,glib是如何来分配小于64kb的内存的。

在linux的堆中,内存是以chunk的形式进行分配的

而每个chunk的结构是这样表示的

其中chunk head中,prev_size是,如果前一个chunk处于空闲状态,则存放它的大小,而size的低三位bit为flag,其中最低位指示前一个chunk是否处于空闲状态。

如果本chunk正在被使用,那么后面的memory部分都是数据了。

如果本chunk处于空闲状态,那么memory的前8字节用于存放fd和bk指针,分别指向后一个未使用的chunk和前一个未使用的chunk

由于malloc需要负责回收已经free掉的chunk,它就需要进行chunk的合并,这里就就涉及到unlink的操作了,也是会引发漏洞的

当然,我们这次的主题不是它。

为了快速的维护较小内存块的分配,malloc会把这一系列较小的chunk通过他们的fd(指向后一个未使用的chunk)指针组成一个单链表,因为这里bk并没有使用,所以最后进入fastbin的chunk反而会被最先分配出去

这个单链表就被称为fastbin,它里面的内存块可以是8kb,16kb,24kb...80kb

当我们试图使用malloc去分配一块较小的内存(<=64 Bytes)时,它会首先检查对应大小的fastbin中是否包含未被使用的chunk,如果存在则直接将其从fastbin中移除并返回

其实是有一点像stack的,当然这个实际上就是一个单链表了

了解了fastbin的工作方式后,我们就来看看这道题目

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

很明显的,由于这些类的size都较小,通过了fastbin来进行分配对象的内存

这里如果我们先选择了3,去free掉这两个对象,而在通过1来调用的话,就形成了uaf的利用

但是这里我们想一想,怎样才能通过子类调用到父类的指针呢?

在C++里,对于存放类的虚表vtable,子类会继承父类的vtable,而如果需要调用虚函数

则这些调用语句是根据vtable的地址来进行相对偏移的。

然后子类根据自己是否重写或新增了某个方法,修改虚函数的指针地址,或者是新增一条虚函数指针

但是,父类的虚函数指针,即使子类没有使用,也仍然存在于它们的vtable里

而在对象内存的开头处就存放着vtable的地址

所以这下思路就很明显了,我们需要做这样的事情。

1.两个对象在被free后,以先man后women的顺序先后加入fastbin

2.在申请同样大小的内存(24)时,会从fastbin来进行内存的分配

所以,data = new char[len]语句得到了之前woman的内存所在

3.在read(open(argv2, O_RDONLY), data, len) 产生了堆溢出

4.由于case 1时,仍然可以使用被free掉的对象,产生了uaf漏洞

因此,通过这个分析,最关键的部分就在于我们需要通过这个堆溢出去覆盖掉vtable的地址

首先使用objdump -D uaf来分析这个文件

可以看到,子类man的虚表地址正是0x401570

发现这地址存放着第一条虚函数的指针,很明显的,它就是give_shell()原因如下

vtable指向的是虚表的开始指针。其实vtable是虚表的地址,虚表的第一项是第一个虚函数的指针。

由于不可能通过覆盖对象的内存直接去覆盖虚函数指针

因此,我们需要通过覆盖man的vtable,并让它减少一定量,使调用man的introduce方法时

调用到我们的give_shell()方法

这里每个指针的长度是8,所以我们要让man的vtable-8,来实现我们的目的

由于第一次after分配到的是woman的内存,因此我们需要两次after来得到man的内存

因此构造如下的payload

python -c "print '/x68/x15/x40/x00/x00/x00/x00/x00'" >> /tmp/mathias

./uaf 24 /tmp/mathias

然后选择 free-after-after-use

得到了flag yay_f1ag_aft3r_pwning

第七城市

最新文章

123

最新摄影

微信扫一扫

第七城市微信公众平台