Try Attack/Reverse Engineering[basic]

[csaw CTF 2015] wyvern

D4tai1 2019. 4. 8.

 

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

댓글