二进制学习系列-堆溢出

Pwnable-UAF

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

所需知识:

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

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

单继承,无虚函数重载:

单继承,有虚函数重载:

总结

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

详情知识请移步:https://bbs.pediy.com/thread-224651.htm

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详解:

源代码:

快速浏览一遍过后我们可以观察到主要的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虚表地址

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

2.vtable地址

找到man的构造函数

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

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

3.重写覆盖

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

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

4.allocate

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

利用:

由于先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

原文发布于微信公众号 - 安恒网络空间安全讲武堂(cyberslab)

原文发表时间:2018-09-06

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏java一日一条

Java中关于String类型的10个问题

简单来说,“==”是用来检测俩引用是不是指向内存中的同一个对象,而equals()方法则检测的是两个对象的值是否相等。只要你想检测俩字符串是不是相等的,你就必须...

8410
来自专栏進无尽的文章

基础篇-ObjectC继承、类别、属性

    在实际的开发过程中,继承和类别都会得到很多用处。对于界面相似度很高的情况下,使用继承可以节省很多代码和设置,只需要在子类中重写父类中的方法,或者增加新的...

8910
来自专栏AI研习社

正则表达式教程:实例速查

正则表达式(regex 或 regexp)在文本信息提取方面是非常有用的工具,通过查询一个或多个特定搜索模式的匹配实现(例如,特定的ASCII或unicode字...

11530
来自专栏java一日一条

深入浅出JavaScript之闭包(Closure)

闭包(closure)是掌握Javascript从人门到深入一个非常重要的门槛,它是Javascript语言的一个难点,也是它的特色,很多高级应用都要依靠闭包实...

10620
来自专栏深度学习之tensorflow实战篇

python yield函数深入浅出理解

首先关于生成器的那些事: 1.通常的for…in…循环中,in后面是一个数组,这个数组就是一个可迭代对象,类似的还有链表,字符串,文件。它的缺陷是所有数据都...

38840
来自专栏前端进阶之路

JS学习系列 06 - 变量对象

上一节我们讨论了执行上下文,那么在上下文中到底有什么内容,为什么它会和作用域链扯上关系,JS 解释器又是怎么找到我们声明的函数和变量,看完这一节,相信大家就不会...

12820
来自专栏Python自动化测试

python的set集合学习(七)

在python中,set集合是一个无序并且不重复的元素集合,它的关键字是set,依然按照之前的思维方式,我们定义一个set集合,来看它的类所具备的功...

14430
来自专栏小樱的经验随笔

C/C++中inline用法详解

(一)inline函数(摘自C++ Primer的第三版) 在函数声明或定义中函数返回类型前加上关键字inline即把min()指定为内联。       in...

29130
来自专栏zaking's

用js来实现那些数据结构02(数组篇02-数组方法)

    上一篇文章简单的介绍了一下js的类型,以及数组的增删方法。这一篇文章,我们一起来看看数组还有哪些用法,以及在实际工作中我们可以用这些方法来做些什么。由于...

441110
来自专栏编程

10个Python面试常问的问题

概述 Python是个非常受欢迎的编程语言,随着近些年机器学习、云计算等技术的发展,Python的职位需求越来越高。下面我收集了10个Python面试官经常问的...

24170

扫码关注云+社区

领取腾讯云代金券