Codegate2019 - A Mola

By BPSEC | May 2, 2019

2019 코드게이트 Final - A Mola



안녕하세요.

2019 코드게이트 본선 문제 “A Mola” 풀이입니다.

문제의 난이도가 어려운편은 아니지만 분석 포인트가 몇 가지 있는 편이라 핵심 위주로 작성하겠습니다.

요약하면 아래와 같습니다.


  1. 프로그램의 입력 처리 과정
  2. 2개의 배열 할당 방식
  3. flag 출력 방식




1. 프로그램의 입력 처리 과정






해당 문제는 실행 시 사용자 입력을 기다립니다.

이 때 숫자 10~15 외의 값이 입력될 경우 프로그램을 종료합니다.

각 항목별로 입력 받는 횟수가 다른데 “15 : Confident” 메뉴를 활성화하기 위해서는 최소 1회 10~14번 항목 중 하나를 선행해야 합니다.

입력 처리 루틴을 ida를 통해 확인해보겠습니다.





사용자로부터 받은 입력 값을 기반으로 10~15까지의 case 분기를 수행합니다.

이 때 각 입력 값(10~15) 별로 변수에 0xA0000000 ~ 0xF0000000까지의 값을 대입한 후 동일 함수에 인자를 변경하여 재귀적으로 다시 호출합니다.

재귀적으로 호출한 결과는 메뉴 선택 시 하위 메뉴의 호출로 볼 수 있습니다.

간단히 정리하면 아래와 같습니다.


- sub_30B8(0) : 프로그램 종료
- sub_30B8(1) : 메인 메뉴
- sub_30B8(2) : Eat 메뉴의 첫 번째 하위 항목
- sub_30B8(12) : Play의 첫 번째 하위 항목


이 때 사용자가 입력하는 값은 모든 메뉴별로 10~15의 숫자만 입력 가능하며, 해당 입력 값은 단순히 분기를 위해서만 사용됩니다.

다만 각 메뉴의 최하위 항목까지 진행되는 동안 다음 규칙에 의해 값을 더하게 됩니다.


- 1 번째 메뉴 : 0xA0000000 ~ 0xF0000000
- 2 번째 메뉴 : 0x0A000000 ~ 0x0F000000
- 3 번째 메뉴 : 0x00A00000 ~ 0x00F00000


예를 들어 아래 순서로 입력했다고 가정할 경우 최종적으로 생성되는 값을 계산해 보겠습니다.





이렇게 생성된 값은 각 입력의 최종 항목에서 배열에 순차적으로 최종 합을 대입합니다.

그리고 사용자가 입력한 만큼의 합이 저장된 배열의 값을 순차적으로 비트 연산을 통해 새로운 배열에 할당합니다.

위에서 생성된 0xAAAAAAA0이라는 값을 새로운 배열에 넣을 때는 16진수 표기 기준 각 자릿수 별로 다음과 같은 비트 연산을 수행합니다.





결과적으로 아래 표와 같이 입력 시 더해진 값들로 다시 나누는 과정을 수행합니다.





2. 배열의 값 할당 방식



문제에서는 총 3개의 배열을 사용합니다.

각 배열의 용도는 다음과 같이 정의할 수 있습니다.



배열의 생성 횟수는 초기 프로그램이 실행될 때 임의의 값에 의한 반복으로 정해집니다.

해당 코드는 다음과 같습니다.





그리고 임의 값으로 생성된 횟수만큼 사용자 입력을 완료한 후 “배열 3”의 마지막 배열은 사용자 입력과 관계 없이 0xFFFFFFFF로 할당됩니다.



3. 2개의 배열 할당 방식



문제를 해결하기 위한 가장 핵심 방식은 다음 2가지 요소를 파악하는 것입니다.


  1. 특정 메뉴 선택 시 16진수 기준 8번째 자리에 값(0x00DEAD00) 변조
  2. 8번째 자리 값이 “배열 3”의 마지막에 위치할 경우 프로그램 종료 시 출력하는 값의 offset 변조 가능


기본적으로 사용자로부터 입력받는 “배열 1”과 “배열 2”는 프로그램 생성 시 결정되는 임의의 값에 의해 반복되므로 이 값을 알아내지 않는 이상 “배열 3”의 마지막 값인 0xFFFFFFFF을 변조할 수 없습니다.

하지만 “배열 3”이 만들어질 때 총 반복 횟수를 공유하여 “배열 1” 이후 “배열 2”의 값이 저장되기 때문에 “배열 3”의 마지막 값을 변조할 수 있습니다.

제공되는 바이너리 파일 만으로 이를 알아낼 수 있는 방법이 없으므로 배열의 마지막에 특정 메뉴가 위치하는 것이 풀이의 핵심이라 할 수 있습니다.

“배열 3”의 마지막 값이 0xFFFFFFFF일 경우 DyingMessage 파일의 다음 값을 읽어옵니다.





하지만 해당 값은 flag가 아니며, 변조 후 읽어야 하는 데이터는 다음 값입니다.





“배열 3”의 마지막 값을 0x00DEAD00로 변경할 경우 다음 처리 과정에 의해 출력하는 값이 다르게 처리됩니다.





즉 fseek()의 위치를 0x00DEAD00로 변경하면 flag를 출력하게 됩니다.

실제 해당 값을 변조하기 위한 방법은 아래와 같습니다.


  1. 최소 1번의 10~14 메뉴(배열 1) 실행으로 15번(배열 2) 메뉴 활성화
  2. “배열 2”의 8번 째 자릿수 쓰기 메뉴 선택
  3. 총 반복 횟수만큼 “배열 1” 작성


“배열 1”과 “배열 2”가 동일한 반복 횟수 하에 입력을 받기 때문에 일반적으로 “배열 3”의 마지막 값은 항상


배열 3의 마지막 2개 요소 : [“배열 2”의 마지막 값 ] [0xFFFFFFFF]


과 같이 구성되며, 위 offset(0xFFFFFFFF)에 위치한 GaeBokchiIsNeverDie을 출력합니다.

하지만 입력 중 “배열 2”의 마지막 값을 다음 순서로 입력한다면 “배열 3”의 마지막 값을 조작할 수 있습니다.



배열 3의 마지막 2개 요소

[“배열 2”의 마지막 값 ] [0x00DEAD00]





이를 기반으로 공격 코드를 작성하여 실행하면 위와 같은 flag를 획득할 수 있습니다

comments powered by Disqus