盒子
盒子
文章目录
  1. Pwnable-UAF.学习笔记
    1. 1.虚函数的内存地址空间:
    2. 总结
    3. 2.Use-After-Free
    4. Dangling pointer:
    5. UAF:
    6. 3.SLUB:
  2. Pwnable-UAF详解:
    1. 利用:
    2. getshell

Pwnable-UAF

Pwnable-UAF.学习笔记

这道题主要考察的是虚函数的内存地址空间以及UAF的使用

所需知识:

1.虚函数的内存地址空间:

在C++中,如果类中有虚函数,那么它就会有一个虚函数表的指针__vfptr,在类对象最开始的内存数据中。之后是类中的成员变量的内存数据。
对于子类,最开始的内存数据记录着父类对象的拷贝(包括父类虚函数表指针和成员变量)。 之后是子类自己的成员变量数据。

1

单继承,无虚函数重载:

2

单继承,有虚函数重载:

3

总结

  • 如果一个类中有虚函数,那么就会建立一张虚函数表vtable,子类继承父类vtable,若,父类的vtable中私有(private)虚函数,则子类vtable中同样有该私有(private)虚函数的地址。注意这并不是直接继承了私有(private)虚函数
  • 当子类重载父类虚函数时,修改vtable同名函数地址,改为指向子类的函数地址,若子类中有新的虚函数,在vtable尾部添加。
  • vptr每个对象都会有一个,而vptable是每个类有一个,vptr指向vtable,一个类中就算有多个虚函数,也只有一个vptr;做多重继承的时候,继承了多个父类,就会有多个vptr

详情知识请移步此处

2.Use-After-Free

Dangling pointer:

指向被释放的内存的指针。

成因:释放掉后没有将指针重置为NULL.简单来说就是因为分配的内存释放后,指针没有因为内存释放而变为NULL,而是继续指向已经释放的内存.

UAF:

对上面所说的指针进行利用,引用到自己想引用的函数上等等。

3.SLUB:

SLUB:系统内存分配机制。

对对象类型没有限制,两个对象只要大小差不多就可以重用同一块内存,而不在乎类型是否相同样的话,同一个笼子既可以放鸡,又可以放鸭。也就是说我们释放掉sock对象A以后马上再创建对象B,只要A和B大小相同(不在乎B的类型),那么B就极有可能重用A的内存。SLAB差不多,只不过要求类型也要相同。

既然B可以为任意对象类型,那我们当然希望选择一个用起来顺手的对象类型。至少要符合以下2个条件:

1.用户可以控制该对象的大小

2.用户空间可以对该对象写入数据

Pwnable-UAF详解:

源代码:

4

5

快速浏览一遍过后我们可以观察到主要的Human和Man、Woman三个类,Human是父类,其余是继承了的子类,并且两个子类都重写了父类中的introduce函数,我们还注意到了父类中的getshell私有函数,所以我们之后肯定会用到它。由前者的知识点我们可以明白,三个类中都有虚函数,所以每个类都有一个vtable表来存储虚函数,并且两个子类都继承了父类的vtable表,并且也有父类私有虚函数的getshell虚函数。

所以我们可以想到利用子类的构造函数,来跟随找出vtable,再利用getshell虚函数地址来继续。

main函数中after那一段的作用是分配一段地址空间,我们可以利用已经被free的内存重新allocate一个可控的地址空间。

所以我们的思路是:

1.找到getshell虚表的地址

2.找到vtable的地址

3.重写覆盖vptr指针指向地址

4.free后再allocate得到可控地址

1.getshell虚表地址

6

由于我是本地自己重新把平台上的cpp文件编译了一遍,所以地址和平台上环境地址会不一样。(后来我才明白是因为自己编译cpp文件的时候所使用的参数不同的原因,比如gcc -g uaf.cpp -o uaf和不加-g是有区别的) 以上可以看见getshell虚函数在vtable中的地址为0x4012ea,也可以在gdb中调试,来查看getshell地址。

7

2.vtable地址

找到man的构造函数8

在0x401084处下断点,用gdb调试9

p /x $ebx的作用是打印出实例化man对象的地址,而后查看man对象的内存地址空间,因为虚表指针在首部,所以我们找到了虚表的地址是0x401668

3.重写覆盖

我们首先得需要找到虚表指针引用introduce函数时候的偏移量:

10

我们可以大致推测出v12和v13是同一个vptr指针,偏移+8后刚好是getshell地址+8后的introduce函数地址,所以我们可以开始利用,把vtable表的地址-8,即把vptr指针指向的地址-8时,就可以在程序运行use段时引用introduce函数的时候实则引用的是getshell函数。

4.allocate

11

可以看到原来man对象分配到的空间是0x30,即48字节,所以当我们再次分配的时候也要分配48字节,保证自己拿到的是原先被free掉的地址空间。

利用:

12

由于先free掉的是m,所以当我们分配第一次的时候得到的是w所指向的空间,所以我们需要分配两次得到m所指向的空间再来利用。 因为这题是从文件中读出内容覆盖,所以我们可以使用python -c来写入转变成不可见字符(由于我试过直接在文档里面写十六进制的地址没法被读取,所以才明白要转变成不可见的字符)。

程序在case2中读取数据的填充到data空间的时候,开始的八字节就是vtable。之后是类的数据。(因为geshell表+8字节后就是introduce表,所以推测读取的地址为8字节一个段)

0x401668-0x8=0x401660

python -c ‘ print “\x60\x16\x40\x00\x00\x00\x00\x00” ‘ > /tmp/exp

./uaf 48 /tmp/exp

getshell

13

支持一下
扫一扫,支持v1nke
  • 微信扫一扫
  • 支付宝扫一扫