Reversing.Kr - ImagePrc (Reversing, 120pts)
이번 문제의 파일(ImagePrc.exe)을 열면 아래 [그림 1]과 같은 화면이 나온다.
보다시피 하얀 바탕으로 칠해진 작은 박스 안에 Check 버튼이 있는 것이 전부다. 여기서 Check 버튼을 누르면 ‘Wrong’이라는 메시지 박스가 출력된다.
조금 더 살펴보니, 하얀 바탕에서는 텍스트 같은 것을 입력하는 것이 아니라 마우스로 그림을 그릴 수 있었다. 아무래도 문제 제목에도 ‘Image’라는 말이 들어간 것으로 보아 특정 그림을 그려야 Correct 메시지를 띄워주나 보다.
그런데 생각해보면, 특정 그림을 똑같이 그려낸다는 것은 사실상 불가능에 가깝다. 검증 루틴을 확인해봐야 알겠지만, 만약 픽셀 하나하나까지 완벽하게 일치하게 그려야한다면.. 이 문제는 아무도 풀 수 없는 문제가 될 것이다. 심지어 어떤 그림을 그려야 하는 지도 전혀 알 수 없는 상태이다. 그림을 그려서 풀 문제는 아니다.
그렇다면… 리버싱을 통해서 이 프로그램이 요구하는 정답이 무엇인지 알아내는 것이 이 문제의 목표일 것이다. 요구하는 정답이 이미지라면 그 이미지 속에 플래그가 있거나 플래그를 얻을 수 있는 힌트라도 있을 것이다.
우선 기드라를 통해 이 파일을 분석해보았다. 이번에도 Defined Strings를 통해 ‘Wrong’ 문자열을 참조한 함수를 먼저 찾았다. 해당 함수의 구문 중 일부는 아래 [그림 4]와 같다.
예상대로 ‘Wrong’ 문자열은 이미지 검증 루틴에 같이 붙어있었다. [그림 4]에서 우선 주목해야 할 부분은 do ~ while 반복문이다. 이 반복문은 무려 90000번을 도는데 90000번 내내 반복문 안에 있는 if문을 만족하지 않아야 Wrong 메시지를 띄우지 않는다. if 문이 기드라로 보면 뭔가 복잡해보이는데 어셈블리로 보면 세상 간단한 형태로 볼 수 있다. (디컴파일링이 잘 되지 않았던 모양이다)
보다시피 ECX값이 주소인 곳에서 1바이트를 가져오고, EAX + ECX값이 주소인 곳에서 1바이트를 가져와 서로 비교한다. 이 때 서로 값이 다르면 곧바로 반복문을 벗어나 ‘Wrong’ 메시지를 출력하는 형태임을 볼 수 있다. 그렇다면 중요한 것은 ECX와 EAX + ECX가 어디를 가리키고 있느냐 일 것이다. 레지스터 값을 기드라로만 계산하기는 쉽지 않다. 디버거를 사용해야 할 때다.
디버거로 ImagePrc.exe를 열고, [그림 5]의 반복문이 시작되는 주소인 0x4013A3에 BP를 걸고 실행하였다. BP에 걸리기 전 내 입력을 먼저 받지만 굳이 그림을 그릴 필요 없이 Check를 누르면 된다.
4013A3에 멈췄을 때 EAX와 ECX의 값은 위 [그림 6]과 같았다. 그래서 우선 ECX값을 주소로 하는 곳을 확인해보았는데, 아래 [그림 7]과 이후 90000바이트가 모두 0xFF로 도배된 모습이었다. 다만 ECX+EAX를 주소로 하는 곳은 쭉 내리다보니 아래 [그림 8]과 같이 중간에 0xFF가 아니라 0x00이 상당수 있는 것이 보였다.
이 반복문이 이미지의 일치 여부를 검증하는 것이라면, 반복문에서 비교하는 1바이트 데이터는 해당 이미지의 픽셀 데이터일 가능성이 높다. 즉, 0xFF가 아무 것도 그려지지 않은 하얀 바탕을 의미한다면 0x00은 그 위에 무언가 그려진 것을 의미한다는 것이다. 내가 입력한 그림은 아무 것도 그리지 않은 상태였으니 0xFF로만 구성되었을 것이다. 따라서 ECX값을 주소로 하는 부분이 내가 그린 그림이다. 그렇다면 자연스럽게 내가 그린 그림과의 비교대상인 데이터가 바로 내가 찾던 그 이미지라는 결론이 나온다.
거의 다 오긴 했는데, 해당 데이터를 가지고 어떻게 이미지로 만들 수 있을까? 혹시나 데이터들 사이에 플래그 비스무리한거라도 있을까 싶었으나 그런건 없었다. 우선 해당 데이터만 추출해야 하는데, 어떻게 추출할까 잠시 생각하던 중 반복문 전에 FindResourceA(), LoadResource(), LockResoure() 함수를 호출하는 것이 눈에 띄었다.
저 함수들을 보니 문득 파일 내에 관련 리소스가 있겠다는 생각이 들었다. 홀린듯이 PEview로 열어보았다.
[그림 9]에서 FindResourceA()를 호출하며 ResourceName을 ‘65’로 뒀는데 정말로 이름이 ‘65’인 녀석이 있었다. 해당 부분이 바로 앞서 보았던 [그림 8]의 내용과 일치한다. 이제 파일 내 해당 부분이 어디 있는지 알았으니 헥스 에디터로 간단하게 추출할 수 있다. ImagePrc.exe를 헥스 에디터로 열고, 0x9060부터 0x1EFEF까지 추출하면 된다.
이제 저장을 하고 열어보면 되는데… 당연하게도 열리지 않는다. 생각해보니 아직 이 파일이 어떤 종류의 이미지 파일인지도 모르는 상태였다. 그래서 다시 기드라로 돌아와 살펴보니 CreateCompatibleBitmap() 함수가 눈에 띄었다. 해당 함수는 그림을 입력받는 창을 만들 때 호출되었는데, 그렇다면 입력값은 Bitmap 이미지로 만들어졌을 것이다. 따라서 입력값과 비교하는 이미지 또한 Bitmap일 가능성이 높다.
그렇다면 Bitmap 이미지 파일의 헤더를 적절하게 붙여넣으면 온전한 이미지 파일로 복구될 것이다. Bitmap 헤더를 기존에 갖고 있는 Bitmap 파일에서 가져오든, 아니면 구글링을 해서 가져올 수도 있겠지만 내 PC에는 Bitmap 파일이 없는 것 같아 그냥 그림판으로 Bitmap 파일을 만들어서 헤더만 추출하기로 했다.
대충 하얀 바탕만 있는 이미지를 저장하면 되는데, 이 때 이미지의 크기를 200 * 150으로 맞춰주었다. 헤더에 이미지의 크기를 표시하는 부분이 있을 것이므로, 사전에 미리 맞춰주는 것이다. 저장을 하려고 보니, 비트맵도 여러 종류가 있었다. 단색, 16색, 256색, 24비트… 이중에 정확히 뭐가 맞을진 모르겠지만 일단 24비트로 해보기로 했다.
그렇게 저장을 한 이미지를 헥스 에디터로 열어서 헤더를 추출하여 플래그가 있을 이미지 파일에 붙여넣으면 된다.
이렇게까지 하면 마침내 온전한 이미지 파일이 완성되고, 그 이미지 내 있는 정보도 확인할 수 있다. 해당 이미지에 있는 단어가 곧 플래그이므로, 해당 값을 입력하면 120점을 얻을 수 있다.