pwnable.kr之uaf

uaf

终于有时间写一下了,这个题目的答案百度了一下,貌似没有什么特别详细的解答,都是大神们的writeup,可能觉得太简单了,几句话就完事了,而我这种渣渣,只能从头学习,那里不会学哪里。新手可以一起学习一下,如果有兴趣一起学习pwn的可以留言,一起进步, 不扯了,开始正事!

 

uaf漏洞分析基础知识补充:

1

UAF:引用一段被释放的内存可导致程序崩溃,或处理非预期数值,或执行无干指令。使用被释放的内存可带来诸多不利后果,根据具体实例和缺陷发生时机,轻则导致程序合法数据被破坏,重则可执行任意指令。


2

UAF错误的原因:

(1)导致程序出错和发生异常的各种条件

(2)程序负责释放内存的指令发生混乱

其实简单来说就是因为分配的内存释放后,指针没有因为内存释放而变为NULL,而是继续指向已经释放的内存。攻击者可以利用这个指针对内存进行读写。(这个指针可以称为恶性迷途指针)


3

UAF漏洞的利用:

(1)先搞出来一个迷途指针

(2)精心构造数据填充被释放的内存区域

(3)再次使用该指针,让填充的数据使eip发生跳转。


4

在填充的阶段要考虑系统的内存分配机制,这里介绍一下SLUB

SLUB

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

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

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

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

如果碰巧这块问题内存新分配的数据是比如C++中的类,那这块内存堆对上可能散落着各种函数指针,只要用shellcode的地址覆盖其中一个函数指针,就能够达成执行任意指令。

 

5

malloc函数做了那些事情。

大于512字节的请求,是纯粹的最佳分配,通常取决于FIFO,就是最近使用过的。

小于64字节的请求,这是一个缓存分配器,保持一个快速的再生池块。

在这个两者之间的,对于大的和小的请求的组合,做的最好的是通过尝试,找到满足两个目标的最好的。

对于特别大的字节,大于128KB,如果支持的话,依赖于系统内存映射设备。

 

6

虚函数,一旦一个类有虚函数,编译器会为这个类建立一张vtable。子类继承父类vtable中所有项,当子类有同名函数时,修改vtable同名函数地址,改为指向子类的函数地址,子类有新的虚函数时,在vtable中添加。记住,私有函数无法继承,但如果私有函数是虚函数,vtable中会有相应的函数地址,所有子类可以通过手段得到父类的虚私有函数。

 

7

vptr每个对象都会有一个,而vptable是每个类有一个

vptr指向vtable

一个类中就算有多个虚函数,也只有一个vptr

做多重继承的时候,继承了多个父类,就会有多个vptr

 

8

虚函数表的结构:它是一个函数指针表,每一个表项都指向一个函数。任何一个包含至少一个虚函数的类都会有这样一张表。需要注意的是vtable只包含虚函数的指针,没有函数体。实现上是一个函数指针的数组。虚函数表既有继承性又有多态性。每个派生类的vtable继承了它各个基类的vtable,如果基类vtable中包含某一项,则其派生类的vtable中也将包含同样的一项,但是两项的值可能不同。如果派生类覆写(override)了该项对应的虚函数,则派生类vtable的该项指向覆写后的虚函数,没有覆写的话,则沿用基类的值。

每一个类只有唯一的一个vtable,不是每个对象都有一个vtable,恰恰是每个同一个类的对象都有一个指针,这个指针指向该类的vtable(当然,前提是这个类包含虚函数)。那么,每个对象只额外增加了一个指针的大小,一般说来是4字节。

在类对象的内存布局中,首先是该类的vtable指针,然后才是对象数据。

在通过对象指针调用一个虚函数时,编译器生成的代码将先获取对象类的vtable指针,然后调用vtable中对应的项。对于通过对象指针调用的情况,在编译期间无法确定指针指向的是基类对象还是派生类对象,或者是哪个派生类的对象。但是在运行期间执行到调用语句时,这一点已经确定,编译后的调用代码能够根据具体对象获取正确的vtable,调用正确的虚函数,从而实现多态性。

 

 

下面开始分析pwnable.kr中的uaf

 

 

第一个类Human中有虚函数,那么类Human具有一个vtable,这个vtable中记录了类中所有虚函数的函数指针,即包括give_shell和introduce两个函数的函数指针。在vtable后面是类的数据部分。


紧接着是两个类,这两个类继承了类Human。可以看到他们各自实现了introduce,根据前面基础部分说的,这两个类都会继承父类的vtable,vatble中introduce的函数指针被替换成了他们自己的函数地址。

紧接着看Main



程序流程是根据输入数字跳转到不同的地方执行

1 调用两个类的函数

2 分配data空间,注意用的时New,从文件名为argv[2]中读取长度为argv[1]的字符到data部分。

3 释放对象

 

这里如果是先执行3再执行2,那么把对象空间释放并且把指针置NULL却又去引用了,就触发了UAF漏洞。那么如何操纵被释放的空间呢?可以看到在case2中,是从文件名为argv[2]中读取长度为argv[1]的字符到data部分。利用前面所述UAF漏洞,data在分配空间的时候就分配到了case3中被释放的空间。如果我们能够把introduce函数的指针覆盖为give_shell的指针,那么就可以在接着执行1,调用shell了。

 

具体如何利用呢?

IDA分析一下:



可以看到,程序给每个对象分配了0x18即24个字节。


后面发现调用了Man的构造函数。


跟进去


跟到401284后发现giveshell的地址为0x40117a



在IDA中看一下


发现了Man的vtable

点进去看一下两个函数的地址:

give_shell


introduce:


感兴趣童鞋可以看一下,Human中的giveshell和Man中地址是一样的,而introduce是不一样的,原因前面说过了。

看一下这里的数据,同样是查看十六进制数据,x/a就可以看到函数名字。。。get !!!


这里也证实了IDA中的地址是对的。

在IDA中分析清楚程序流程后,看到在选择1的时候时调用Introduce函数的。


前面还有


所以v13基本可以猜出来是vptr,然后转换为指针,取其中的数据是vtable的第一个值,再加8 是第二个值,正好是introduce的函数指针,然后经过前面的调用,就是调用了Introduce函数了。

前面漏洞利用的思路是让调用introduce的时候,调用成getshell。

类在内存中的相对位置如下,那么如果让vtable减去8,那么再调用Introduce的时候,Introduce的函数指针就指向了原来的getshell。(可以理解类vtable向右平移了8个字节。。。)

如图:


蓝色箭头是原来的getshell函数,+8之后指向了红色的位置,调用introduce。如果让vtable指向get_shell那么就要让v13+8之后等于蓝色的位置,因此只要给vtable-8就可以了。

那么输入的数据如何覆盖呢?这又要考虑在new的时候,堆的结构以及如何分配的。

通过学习http://www.cnblogs.com/bizhu/archive/2012/09/25/2701691.html

发现这样一句话:

“在C++中,如果类中有虚函数,那么它就会有一个虚函数表的指针__vfptr,在类对象最开始的内存数据中。之后是类中的成员变量的内存数据。”

 

那么根据这句话所说,这个程序在case2中读取数据的填充到data空间的时候,开始的八字节就是vtable。之后是类的数据。

http://blog.csdn.net/zhangliang_218/article/details/5544802这篇博客中简单地谈到了。vtable指向的是虚表的开始指针。其实vtable是虚表的地址,虚表的第一项是第一个虚函数的指针。(要是错了,大神要指出来哦)


 


那么我们可以把vtable的地址减小8,那么程序在后面用rax+8调用introduce函数时候,不就调用成get_shell了吗!!!

利用过程:

401570-8=401568


flag:yay_f1ag_aft3r_pwning

这里需要注意下free的顺序是先free的m,后free的w,因此在分配内存的时候优先分配到后释放的w,因此需要先申请一次空间,将w分配出去,再开一次,就能分配到m了。


 

阅读更多
版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/qq_20307987/article/details/51511230
文章标签: pwnable.kr uaf
个人分类: pwn学习
想对作者说点什么? 我来说一句

没有更多推荐了,返回首页

关闭
关闭
关闭