[csaw CTF 2015] wyvern
1. 문제 풀이
(1) 실행결과
[1] 용의 비밀을 입력하라는 메세지가 나온다.
[2] 'test'를 입력 후 Enter를 누른 결과이다.
[3] 용의 힘과 스피드와 지능이 더 크기 때문에 실패했다는 내용이 담겨있다.
(2) 시작부분 정적분석
[1] IDA에 올려보면 처음 보이는 화면이다.
[2] 함수의 프롤로그 이후 빨간 네모를 보면 프로그램을 실행했을 때 출력되는 문구와 call하는 부분이 있다.
[3] call하는 부분은 난독화 되어 알 수는 없지만 느낌상 출력하는 함수를 호출하는 느낌이 든다.
[4] 이 프로그램은 C++로 작성되었으며 난독화가 되어있다.
(3) rename
[1] call하는 난독화된 함수명을 클릭한 후 n을 누르면 이름을 변경할 수 있다.
[2] C++의 대표적인 출력함수인 cout으로 수정하였다.
(4) rename 이후
[1] 난독화 된 함수의 이름이 cout으로 변경된 것을 확인할 수 있다.
[2] 모든 출력메세지가 출력된 이후 fgets로 입력받는 부분이 보인다.
[3] 실제로 실행했을 때와 동일한 느낌이 든다.
(5) 입력 후 결과
[1] 'test'를 입력 후 나온 메세지는 flag가 아니기 때문에 출력된 문구일 것이다.
[2] 그렇다면 정상적인 flag를 입력했다면 어떻게 출력이 되었을까?
(6) 문자열 검색
[1] [shift+F12] 키를 누르면 바이너리 내 모든 문자열을 확인할 수 있다.
[2] 맨 위에 보이는 부분이 정상적인 flag를 입력했을 때 출력되는 문자열로 보인다.
[3] 문자열을 더블클릭해서 문자열의 위치로 이동한다.
(7) 참조
[1] 이곳은 .data영역이다.
[2] 이 데이터영역을 참조한 곳으로 가기 위해 aAGreatSuccessH 변수를 클릭 후 'x'를 누른다.
(8) 이동
[1] 참조한 곳으로 이동한다.
(9) 참조하는 곳
[1] _Z15...(생략) 함수에서 참조하는 것을 알 수 있다.
[2] 그러나 이 함수는 main함수가 아니다.
[3] 그래서 이 함수를 호출한 곳으로 이동하기 위해 함수명을 클릭 후 'x'를 누른다.
(10) 호출한 곳
[1] 이 함수를 호출한 곳으로 이동한다.
(11) 분기문
[1] 위에서 비교후 분기문으로 나누어 지는 것을 알 수 있다.
[2] 아마도 flag가 일치하면 이 곳으로 오는 것으로 보여진다.
(12) 함수의 위치
[1] 호출하려는 _Z15..(생략) 함수를 우클릭 후 Xrefs graph to... 를 누른다.
(13) 함수의 위치
[1] _Z15..(생략) 함수는 main이 호출하는 것으로 보인다.
(14) 분기문 위에는?
[1] 보통 비교를 하려면 그 전에 키 값을 만들고 나서 비교를 한다.
[2] 갈라지는 분기문 위를 따라 올라가보니 _Z11..(생략) 라고 적힌 이상한 함수를 호출하는 것이 보인다.
[3] _Z11..(생략) 함수를 더블클릭해서 이동한다.
(15) Flag 알고리즘
[1] 아... 내 눈 ...
[2] 들어가보니 같은 일을 28번 하고 있다.
[3] 아마도 이 부분이 flag와 비교할 키를 만드는 것으로 보인다.
[4] 이 정도 정보를 알아냈으니 공격코드를 작성하러 이동한다.
(16) 공격코드 작성
#!/usr/bin/env python import angr import claripy import time def main(): # 실행파일을 로드한다. # 이 프로그램은 64비트 C++로 작성된 실행파일이고, 심하게 난독화되어 있다. p = angr.Project('wyvern') # IDA 프로그램으로 정적으로 분석을 하면 키가 28바이트인 것을 알 수 있다. # 마지막바이트에 '\n'을 추가해서 29바이트를 만드려고 한다. # 먼저 상태를 가지게 되었을 때 제약조건을 추가할 수 있는 심볼을 생성한다. # flag의 조합을 리스트로 생성한다. # flag_0 ~ flag27까지의 8비트짜리 비트벡터 symbol 28개를 담은 리스트를 생성한다. flag_chars = [claripy.BVS('flag_%d' % i, 8) for i in range(28)] # 생성한 심볼(비트벡터)에 '\n'을 추가해서 29바이트 flag 심볼리스트를 생성한다. flag = claripy.Concat(*flag_chars + [claripy.BVV(b'\n')]) # 분석을 하기위해 초기 프로그램 상태를 구성한다. # C++ 표준라이브러리를 다루어야 하기 때문에 모든 것을 초기화하는 작업이 필요하다. # 이 부분을 완벽히 하기 위해 유니콘 엔진을 사용하려고 한다. st = p.factory.full_init_state( args=['./wyvern'], add_options=angr.options.unicorn, stdin=flag, ) # 28개의 플래그 내에는 NULL값이나 Newline이 없도록 한다. for k in flag_chars: st.solver.add(k != 0) st.solver.add(k != 10) # 기호실행을 하기 위한 시뮬레이션 매니저를 구성한다. # deadended 상태가 될 때까지 진행한다. sm = p.factory.simulation_manager(st) sm.run() # systemcall의 끝에 도달한 모든 경로를 보여준다. # 플래그는 이 경로 중 하나일 것이다. # filter()는 두번째 인자의 내용을 첫번째 인자인 함수에 적용시켜서 # 두번째 인자 중 참인 값들로 리스트를 생성한다 # 리스트의 0번째 인덱스의 값만 반환한다. out = b'' for pp in sm.deadended: out = pp.posix.dumps(1) if b'flag{' in out: return next(filter(lambda s: b'flag{' in s, out.split())) if __name__ == "__main__": before = time.time() print(main()) after = time.time() print("Time elapsed: {}".format(after - before))
(17) 공격
[1] python3 solver.py
(18) 실행
'Try Attack > Reverse Engineering[basic]' 카테고리의 다른 글
MFC_reversing (0) | 2019.05.26 |
---|---|
[codegate_2017] angrybird (0) | 2019.04.08 |
BreakPoint (0) | 2018.12.28 |
Packing/Unpacking (0) | 2018.12.19 |
Code Caving (0) | 2018.12.18 |
댓글