## CSAW CTF_2017(prophecy, rev)
[Summary]
1. LLVM으로 난독화된 문제이다.
2. 베이직 블록만 잘찾고 브포걸고 실행하면 된다.
3. 그래프 모드로 봤을 때 젤 아래에 있던 블럭들의 첫 번째만 브포걸고 실행하고
4. 베이직 블락이라고 생각되는 곳에서 디컴파일하면서 분석하면 된다.
5. 페이로드 중간에 비교하지 않는 부분은 \x00을 넣어서 뒤에 페이로드가 깨지지 않게 한다.
[Analysis]
이 문제는 간단하게 요약하면 LLVM으로 난독화되어 있는 아주 간단한 프로그램이다. 프로그램을 실행하여
전반적인 프로그램 흐름을 설명하면 우선 아래 [그림 1]과 같이 실행된다.
[그림 1] 프로그램 실행
위 그림을 보면 secret name을 받고 unlock할 key를 입력받는데 아무거나 입력했더니 그냥
프로그램이 종료가 된다. 그래서 IDA로 까보았다. 처음에는 LLVM 난독화는 리버싱해본적이 전혀 없어서
그냥 진자 IDA로 동적디버깅 F10계속 눌러가면서 내가 이해할 수 있는 코드 나오면 분석하고 다시 반복하는
형태로 진행했다. 그런데 이렇게 하다보니 정말 답도 없을거 같아서 LLVM 관련된 롸업을 쭉보다보니까
어떤 변수에 값을 집어넣고 계속 상태변화하는 것으로 다음 베이직 블럭을 가리킨다는 것을 알게되었고
그것을 알고나니까 쓸데없는건 다 생략하고 필요한 부분만 브포를 걸면 되겠구나라고 생각이 되었다.
우선 디컴파일을 한 소스코드를 보면 아래 [그림 2]와 같다.
[그림 2] 디컴파일 소스
[그림 2]를 보면 알겠지만 뭔가 의미 있는 코드 같으면서도 위에 if문 같은곳에서 v534랑 비교하고 점프하고
이런 루틴이 굉장히 많다. 진짜로 시간이 넘쳐나면 그냥 이런곳에다가 하나하나 다 브포 걸고 차례대로
F10만 연타하면 언젠가는 풀릴거지만 대회니까 좀 빠른 방법을 생각하던 도중 아래 [그림 3]의 그래프 뷰를
보게 되었다.
[그림 3] IDA 그래프 뷰
뭔가 젤 아래만 보면 전체 프로그램을 다 보는 것 같았다. 그래서 젤 아래 블럭들만 전부 브포를 걸었다.
그리고 IDA로 동적디버깅을 하니까 전부 basic블락은 아니었지만 대부분이 의미있는 코드 부분이었고 이제
이걸 기반으로 하나하나 분석해보도록 하겠다.
우선 첫 번재 루틴인 secret name을 입력받는 루틴은 [그림 4]와 같다.
[그림 4] 첫 번째 Basic Block
위 [그림 4]의 코드를 분석해보면 그냥 간단하게 출력할 것 출력하고 read()함수로 secret name을 입력 받는다.
이제 두 번재 루틴을 보면 아래 [그림 5]와 같다.
[그림 5] 두 번재 Basic Block
여기서는 secret name으로 입력 받은 것의 길이를 구한 후 -1부분에 \x00을 넣는다.
이제 세 번째 루틴을 보면 아래 [그림 6]과 같다.
[그림 6] 세 번째 Basic Block
여기서는 unlock할 key값을 입력하라고 하고 실제로 read()함수로 사용자 입력을 받는다. 그게 다다.
그럼 이제 네 번째 루틴을 볼 차례다. 아래 [그림 7]을 보자.
[그림 7] 네 번째 Basic Block
여기서는 unlock_key 값의 길이를 구한 다음 \x00을 마지막에 집어넣는다. [그림 5]와 비슷한 행위를 한다.
여기서 이 부분은 나중에 꽤 중요해지는데 이걸 제대로 몰랐다가 나중에 삽질을 엄청나게 했다.
이제 다섯 번째 루틴을 볼 텐데 코드는 아래 [그림 8]과 같다.
[그림 8] 다섯 번째 Basic Block
여기서는 secret name에 ".starcraft"라는 문자열이 있는지 검증하는 루틴이다. 여기서 만약
".starcraft"라는 문자열이 포함되어 있지 않으면 그냥 종료된다.(v33 != 0에서 확인)
이제 여섯 번째 루틴을 볼텐데 코드는 아래 [그림 9]와 같다.
[그림 9] 여섯 번째 Basic Block
여기서는 그냥 "[*] Interprting the secret...."이라는 문자열을 출력하고 끝이다.
일곱 번째 루틴을 보자. 코드는 아래 [그림 10]과 같다.
[그림 10] 일곱 번째 Basic Block
여기서는 그냥 /tmp경로를 얻어와서 "/tmp/" + [secret name]을 파일로 하나 만든 다음 key값을 그 파일에다가
쓰는 역할을 수행한다. 그리고 그 파일을 읽어들여와서 file handle을 저장하고 있다.
여덟 번째 루틴을 보자. 코드는 아래 [그림 11]과 같다.
[그림 11] 여덟 번째 Basic Block
여기서는 keyfile을 읽어들여와서 4바이트만큼 읽어들이고 0x17202508과 비교를 하는데 아마
의미적으로 생각하자면 20170825와 비교를 하는 것같다.(년월일)
여기서 비교 검증에 실패하면 프로그램은 또 종료되므로 key 값은 첫 4바이트는 "\x08\x25\x20\x17"
이어야 한다.
아홉 번재 루틴을 보자. 코드는 아래 [그림 12]와 같다.
[그림 12] 아홉 번째 Basic Block
여기서는 또 다시 키 파일에서 8바이트를 읽어들여와 저장하고 또 1바이트를 읽어들여와 저장한다.
이번 루틴에서는 비교하는 루틴은 없으므로 나중에 이를 다른 Basic Block에서 비교할 것이라고
생각할 수 있다. 우선은 8바이트 1바이트를 읽어들였다는 것만 기억하면 된다.
열 번째 루틴을 보자. 코드는 아래 [그림 13]과 같다.
[그림 13] 열 변째 Basic Block
여기서는 아까 [그림 12]에서 1바이트 읽어들여온 키값이랑 79라는 숫자랑 비교를 한다.
키값 비교가 있는 것이다. 여기서는 2가지 경우가 있는데 나는 79보다 작으면 왠지 프로그램 종료할 것 같았고
예상이 맞았다. 따라서 비교를 할때 79이상이어야 한다.
열한 번째 루틴을 보자. 코드는 아래 [그림 14]와 같다.
[그림 14] 열한 번째 Basic Block
여기서는 아까 [그림 13]에서 비교한 키 값과 이제 90과 비교를 한다. 아마 키 값의 조건은
79이상 90미만 이어야 하는 것 같다.
열두 번째 루틴을 보자. 코드는 아래 [그림 15]와 같다.
[그림 15] 열두 번째 Basic Block
여기서는 [그림 13, 14]에서 비교한 키 값이랑 또비교를 한다. 그런데 이번엔 부등호가 아니라
equal로 비교를 한다. 아마 키 값의 조건은 79(0x4f, 'O')인 것 같다. 따라서 두 번재로 비교하는 키 값은
0x4F이어야 한다.
열세 번째 루틴을 보자. 코드는 아래 [그림 16]과 같다.
[그림 16] 열세 번째 Basic Block
여기서는 그냥 이상한 문구를 출력하고 끝낸다. 아마 키 값 검증을 성공해서 다음 스테이지로 넘어가는 것 같다.
열네 번째 루틴을 보자. 코드는 아래 [그림 17]과 같다.
[그림 17] 열네 번째 Basic Block
여기서는 또 다시 키 파일에서 키 값을 1바이트 읽어들여온다. 아마 또 키검증을 하는 루틴이 있을 것 같다.
열다섯 번째 루틴을 보자. 코드는 아래 [그림 18]과 같다.
[그림 18] 열다섯 번째 Basic Block
여기서는 [그림 17]에서 읽어온 key값이랑 2랑 비교를 한다. 아마 아까와 비슷하게 2이상이어야 하는
조건인거 같다고 생각을 하면 된다.(물론 2가지경우가 있으므로 둘다 해봐야 한다.)
열여섯 번째 루틴을 보자. 코드는 아래 [그림 19]와 같다.
[그림 19] 열여섯 번째 Basic Block
여기서는 아까 그 키값과 3이랑 비교를 한다. 아마 2<= key <3이 이번 검증의 조건인 듯하다.
이 조건에 만족하는 건 2밖에 없으므로 세번째로 검증하는 key값의 조건은 0x02이다.
열일곱 번째 루틴을 보자. 코드는 아래 [그림 20]과 같다.
[그림 20] 열일곱 번째 Basic Block
여기서는 [그림 16]과 비슷하게 이상한 문구를 출력하는 역할만 한다. 아마 아까와 마찬가지로
키 값 검증이 끝났으니 다음 스테이지로 넘어간다는 뜻으로 이해하면 된다.
열여덟 번째 루틴을 보자. 코드는 아래 [그림 21]과 같다.
[그림 21] 열여덟 번째 Basic Block
여기서는 키 파일에서 4바이트만큼 key값을 읽어들여와서 813라인에서 0xE4EA93과 비교를 한다.
아마 네번째로 비교하는 키 값의 조건은 0xE4EA93인 것 같다.
열아홉 번째 루틴을 보자. 코드는 아래 [그림 22]와 같다.
[그림 22] 열아홉 번째 Basic Block
여기서는 여태까지의 키 값 검증이 끝난 후와 마찬가지로 이상한 문구를 출력하고 끝난다.
스물 번째 루틴을 보자. 코드는 아래 [그림 23]과 같다.
[그림 23] 스물 번째 Basic Block
여기서는 또 키 파일에서 7바이트만큼 읽어들여와 그 키 값과 0x4C55544152455A와 비교 검증을 한다.
아마 다섯 번째로 비교하는 key값의 조건은 "\x5A\x45\x52\x41\x54\x55\x4C"인 것 같다.
스물한 번째 루틴을 보자. 코드는 아래 [그림 24]와 같다.
[그림 24] 스물한 번째 Basic Block
후.. 끝이었다고 생각했는데 끝이 아니어따... F9누르면서 그냥 디버깅하면 빠르게 하니까 걱정하지 않아도된다...
여기서도 그냥 이상한 문구를 출력하고 끝이다.
스물두 번째 루틴을 보자. 코드는 아래 [그림 25]와 같다.
[그림 25] 스물두 번째 Basic Block
후.. 마지막 키값 검증이라고 생각하면서 침착하게 보면 fread()로 키 파일에서 6바이트만큼
키값을 읽어들여와 0x444556415300과 비교한다. 여섯 번째 키 값의조건은 "\x00\x53\x41\x56\x45\x44"이다.
스물세 번째 루틴을 보자. 코드는 아래 [그림 26]과 같다.
[그림 26] 스물세 번째 Basic Block
ㅎㅎㅎㅎㅎㅎㅎㅎㅎㅎ.... 끝이 아니었다. 키 값 검증이 아직 더 남았나보다.
스물네 번째 루틴을 보자. 코드는 아래 [그림 27]과 같다.
[그림 27] 스물네 번째 Basic Block
여기서 또 키 값 검증을 하는데 키 파일에서 4바이트만큼 읽어들여와서 0x4C4C4100과 비교를한다.
7번째 키값의 조건의 "\x00\x41\x4c\x4c"이다.
스물다섯 번째 루틴을 보자. 코드는 아래 [그림 28]과 같다.
[그림 28] 스물다섯 번째 Basic Block
ㅇㅇ. ㅇㅋ? ㅇㅋㅇㅋ. ㅇㅇㅇ. ㄱㄱ
스물여섯 번째 루틴을 보자. 코드는 아래 [그림 29]와 같다.
[그림 29] 스물여섯 번째 루틴
오오오오오오오오오오오오민ㅇ롬ㄴ아ㅓ로마더로미ㅏㅈㄷ로!!!!!!!!!
드디어..
system("cat flag");가 눈에 보였다!
이제 여기까지 분석한 걸 정리하면 아래 [그림 30]과 같다.
[그림 30] key 값 조건
????????는 8바이트고 검증을 안한다.
이제 이렇게 페이로드를 파이썬 코드를 짜서 보내면 되는데 여기서 나는 문제가 생겼었다. [그림 7]의 설명 중에
삽질을 했다라는 빨간 줄이 있는데 키 값을 읽어들여서 마지막 바이트에 "\x00"을 넣는데 이게
"\x08\x25\x20\x17" + "AAAAAAAA" + "\x4F" + "\x02" + "\x93\xEA\xE4\x00" + "\x5A~~~" + ~~
이렇게 페이로드를 보내면 100% 실패한다.
왜냐하면 strlen(key)-1부분에 "\x00"을 넣기 때문에 "\xE4"부분에 "\x00"이 채워져서 페이로드가 망가진다.
그러면 어떻게 해야하냐면 ????????부분에 그냥 중간에 "\x00"을 넣어주면 된다. 그럼 strlen()-1을 해도
????????부분에 "\x00"이 들어가고 ????????부분은 검증을 안하므로 페이로드도 안깨지고 정상적으로
플래그를 얻을 수 있다.
근데 이 글 적다보니 사망 플래그 각이나온다...
긴 글 읽어주셔서 감사합니다. ㅠㅠ
[Exploit Code] - prophecy_exploit.py
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 | from pwn import * #import hexdump context(arch='i386',os='linux') #local=True local=False if local: p = process("./prophecy") else: p = remote("reversing.chal.csaw.io", 7668) binary = ELF("./prophecy") raw_input() if __name__ == "__main__": print p.recvuntil(">>") p.send("asdfasdf.starcraft\n") key = "\x08\x25\x20\x17" + "AAAA" + "\x00"*4 + "\x4f" + "\x02" + "\x93\xea\xe4\x00" + "\x5a\x45\x52\x41\x54\x55\x4c" + "\x00\x53\x41\x56\x45\x44" + "\x00\x41\x4c\x4c" print p.recvuntil(">>") p.send(key+'\n') p.interactive() | cs |
[Get Flag~~!!!!]
[그림 31] flag
끝~!!!!
'CTF writeup' 카테고리의 다른 글
[34C3 CTF_2017] SimpleGC(pwnable) (0) | 2018.01.07 |
---|---|
[HITCON CTF_2017] start(pwnable) (0) | 2017.11.22 |
[HDCON_2017] Fabuary(reversing) (0) | 2017.09.21 |
[ASIS CTF_2017] mrs. hudson(pwnable) (0) | 2017.09.13 |
[Tokyo Westerns CTF_2017] swap(pwnable) (0) | 2017.09.13 |