본문 바로가기

Wargame/pwnable.kr

[Grotesque] maze

 

 

 

 

문제 난이도가 생각보다 어렵지 않고, 재밌다.

문제풀면서 뭔가.. 치트키를 입력해서 숨겨진 장소에 들어가는 기분이 들기도 하고.. 여튼 재밌다. 무슨 문제 풀지 고민되시는 분들은 이걸 꼭 풀어보길 바란다.

 

 - 400명 이상 푼 문제는 PoC 코드를 공개하겠다.

 - 이 글에는 문제 풀이, 힌트 등이 있으니 실력 증진을 원하는 분들은 충분히 삽질한 후 읽어주시길 바란다.

 

1. 코드흐름

====================================================

이 문제는 어셈으로만 보기에는 시간이 너무 오래걸릴 것 같아서 IDA Pro의 힘을 빌렸다.

 

 

 

main() 함수

 

 

main 함수이다. read_mapfile(), maze_process(), finish_maze() 함수, clear_count 등의 변수는 내가 알아보기 쉽게 이름을 바꿔놓은 것이다.

sub_400xxx 같은 것만 보면 너무 복잡하기 때문에.. ㅋㅋ 바꾸는 작업이 오래 걸리지 않으니, 본인이 알아보기 쉽게 바꾼후 분석하자.

 

main에서는 clear_count가 20이하일 경우 read_mapfile, maze_process를 계속 반복하여 실행한다.

read_mapfile은 map.txt에서 데이터를 읽어와 maze를 구축하고  글쓰다보니 알았는데, read_mapfile이 아니라 readLocation_player_guard가 정확한 함수명이 될 것 같다. 이 함수는 map.txt에서 플레이어, guard의 좌표값을 받아온다.

 

maze_process는 구축된 미로에서 우리가 실제로 미로를 빠져나가는 게임을 할 수 있다.

미로를 클리어할 때마다 clear_count가 1씩 증가하며, 만약 clear_count가 20이 넘을 경우, 반복문을 종료하고

finish_maze를 실행하여 record 파일에 우리가 입력한 값을 기록한다.

 

 

 

read_mapfile() 함수

 

 

보시다시피 read_mapfile() readLocation_player_guard()에서는 player, guard의 좌표값을 받아와 관련 변수를 초기화하고 있다.

 

 

 

maze_process()_1

 

 

maze_process에서는 

 1. guard_setting 함수를 이용해 guard_number(가드(guard)의 총 숫자)만큼 가드가 다음에 이동할 좌표를 설정하고

 2. setting_maps 함수에서 map.txt로 읽어온 값들을 이용해 maze를 구축한다.

 3. player_hit_a_guard() 함수를 통해 플레이어('S')와 가드('G')의 좌표가 나란히 붙어있는지 검사하고, 만약 붙어있다면 프로그램을 종료한다.

 4. 그게 아니라면 move_player() 함수를 이용해 플레이어('S')가 입력된 값(w, a, s, d)을 통해 다음 지점으로 넘어갈 수 있는지 체크하고

 5. 값(w, a, s, d)에 따라 player_x 좌표, player_y좌표 값을 증가시키거나 감소시킨다.

 6. read_map_txt[16 * player_y + player_x] = '0'을 이용해 플레이어('S')가 이전에 움직인 좌표는 '0'으로 채운다.

 

 

 

 

 

 7. 그리고 다중 if문을 이용해 global_count, byte_602218변수의 값을 조정한다.

   - 딱봐도 global_count != 10 || player_y != 14 || player_x != 8 || clear_count <= 4 부분이 수상해보이지 않나?? 여기가 힌트에요!!!! 라고 말하는 것 같다.

 

 

 

finish_maze()

 

 

스테이지를 20까지 클리어하면 finish_maze()가 실행되는데, record 파일을 열어서(없다면 생성한다) 플레이어의 이름을 기록한다.

근데 gets로 받는걸 보니 여기서 오버플로우가 터질 것 같다.

 

 

 

system()

 

 

이 문제는 친절하게도 system("/bin/sh") 함수를 사용할 수 있게 해놨다. maze 바이너리에서는 해당 함수가 사용되지 않지만 문제 풀기 쉬우라고 만들어놨나보다.

 

2. 고려사항

====================================================

 1. 미로를 직접 손으로 다 풀라고 만든 문제가 아니다.

 2. 다들 아시겠지만, pwnable.kr/bin/maze_map.txt 여기서 받은 maze_map.txt를 map.txt로 바꿔야 maze 바이너리가 제대로 실행된다.

 3. maze 바이너리 실행 후 'w', 'a', 's', 'd'를 입력할 때, 과연 저것들만 입력할 수 있을까?? 

 

3. 필요 지식

====================================================

 1. IDA Pro를 가지고 있으면 분석하기 쉽다.

 관련 주소: www.hex-rays.com/products/ida/

 2. 리버싱을 간단하게라도 알아야 한다.

  - 여기서 말하는 리버싱은 reversing.kr같은 워게임 사이트에서 익힌 기술을 말하는 게 아니라, 말그대로 프로그램을 역분석하는 능력을 말한다.

 

4. 문제 풀이

====================================================

 

 

 

 

문제의 목표를 읽어보자면, 일단 우리가 20레벨을 달성해야하나보다.

문제 목표를 알았으니, 코드를 보자.

 

1. 코드 흐름에서 본 clear_count는 우리가 달성한 레벨을 담아놓는 변수인데, 저 변수가 20이 넘을 경우 스테이지를 모두 클리어했다고 판단하고 finish_maze()를 실행한다.

 

clear_count 변수의 주소는 0x602244이다. 혹시 저기를 어떻게든 조작할 수 있을지 모르니 기억해두자.

 

2. finish_maze()에서 뭘하나 했더니, 'record' 파일에 gets()를 이용해 문자열을 입력받는다. gets()는 문자열 길이 제한없이 입력받으므로, 여기서 오버플로우가 일어날거라 짐작할 수 있다. 근데 오버플로우를 하려면 스테이지 20을 가야하지 않나?? 그럼 20을 가기 위해 뭘해야할까..

 

3. maze_process()에서 다중if문을 봤을텐데, 일단 수상해보이는 곳을 먼저 확인해보자. 다중if문의 조건을 만족하면 byte_602218위치를 '0'으로 채운다는데.. 도대체 뭐가 어떻게 바뀐다는거지?? 0x602218은 clear_count의 근처에 위치한 주소이지만 clear_count를 바꿀 수 있을 것 같지는 않고.

 궁금하면 gdb에서 set 명령어를 이용해 해당 주소의 값을 '0'으로 바꿔보자

 

4. maze의 가드(guard)들이 랜덤하게 움직이는 것 같지만 사실 완벽한 랜덤은 아니다. urandom을 이용하지 않았기 때문에 maze를 실행할때마다 가드들이 항상 똑같이 움직인다. 자기만의 루트를 찾아서 스테이지를 클리어하자.

 

5. maze_process()의 다중 if문에서 요구한 player_x = 8, player_y = 14인 위치는 아래 그림과 같다.

 

 

 

 

 

문제가 너무 쉽다보니까 설명을 조금만 더 자세하게 하면 쉽게 풀리기 때문에, 이번 글에서는 힌트만 많이 적어놨다.

 - 끗 -

 

 

 

 

 

'Wargame > pwnable.kr' 카테고리의 다른 글

[Grotesque] Starcraft  (2) 2022.02.20
[Grotesque] lokihardt  (2) 2021.04.01
[Grotesque] aeg  (2) 2020.10.03
[Rookies] crypto1  (0) 2020.09.14
[Rookies] note  (0) 2020.09.10