프로그램을 실행하면 다음과 같은 화면을 볼 수 있습니다.
프로그램을 대충 분석해보면, 다음 창에 사용자가 그림을 그리게하고, Check 버튼을 눌러 그림을 검증하는 루틴을 띄고 있는 듯 합니다.
아무 그림이나 입력한 결과, 틀렸다는 창을 볼 수 있었습니다.
그렇다면, 우선 일차적인 목표를 Wrong을 출력하지 않는 것으로 잡아봅시다. 그러기 위해서 일단 Wrong 창을 뜨는 부분과 굉장히 유력하게 관련이 있는 찾아 브레이크 포인트를 걸어 확인해봅시다. 우선 0x00413CD 영역에 브레이크 포인트를 걸었습니다.
디버깅을 해본 결과, 다행히 0x004013CD 영역(제가 Wrong으로 표시한 영역)은 Wrong 창과 관련이 있었습니다. 우리의 목표는 Wrong이 아닌 다른 창 예를 들면 Correct 라던가 Congratulation 같은 창을 보는 것이 때문에, 그 다음은 Wrong 창을 뜨지 않도록 하는 경로를 찾아보도록 하겠습니다.
Jump를 하는 경로(왼쪽 화면 화살표)를 보면, 0x004013A8 주소에서 dl 값과 bl 값을 비교하여 같지 않으면, Wrong창으로 Jump를 하는 것을 확인할 수 있습니다. 근데 또 자세히 보시면, 이 영역은 반복적으로 이루어지는 것을 알 수 있습니다. 구체적으로 설명드리면, 0x004013AE 에서 edi 와 0x159F0( 십진수로 90000)을 비교하여, edi가 작을경우 0x004013A3으로 Jump 합니다. 물론 그 전에 edi에 값을 1 증가시키는 작업을 수행하고요. 그렇다면 여기서 edi 의 초기 값이 얼만지가 중요하겠지요? edi 의 초기 값은 위에 보이는 영역 바로 위에서(0x0040139D 영역) 다음 그림과 같이 확인할 수 있습니다.
위의 그림을 보시면, xor edi, edi 를 하는 것을 보실 수 있습니다. 같은 값을 xor 연산하기 때문에 edi 값은 결국 0이 되겠네요! 여기서 edi 의 초기 값은 0이라는 것을 알 수 있습니다.
아무튼 결론은 0x004013A3 영역은 비교를 0x159F0 번 반복하는 반복문이라는 이야기를 하고 싶었습니다! (그래서 Compare 라고 이름 붙여주었습니다:) )
반복문을 설명하느라 약간 말이 새었는데, 아까 반복문 안의 0x004013A8 영역에서 비교를 해서 Wrong으로 Jump를 했지요? 그렇다면 Jump를 안하면 Wrong을 보지 않는 우리의 목표를 실현할 수 있다는 겁니다!
이제 우리가 신경써야 할 곳은 바로 0x004013A8의 비교문입니다. bl 과 dl 을 비교하기 전에, 우선 이 값들의 출처를 확인해보도록 합시다. ecx와 ecx+eax의 값의 영역에서 값을 불러오는데, 반복문을 들어가기 직전인 0x004013A3에 브레이크 포인트를 걸고, 디버깅을 수행하여 두 영역에 뭐가 들어있는지 확인해봅시다.
EIP 가 0x004013A3을 가리키고 있는 상황에서 레지스터의 상태는 다음과 같습니다.
아까 예상했던대로, EDI는 0으로 잘 초기화가 되었습니다. ECX는 0x06BC0048을 값으로 가지고 있고, ECX + EAX 는 0xF98BE018 + 0x06BC0048 = 0x0047E060 입니다.
Hex view를 한 번 봐보면 0x47E060 ~ 0x47E060 + 0x159F0 - 1 영역과 0x6BC0040 ~ 0x6BC0048 + 0x159F0 - 1 영역은 다음과 같이 FF 또는 0 의 값들로 덮여있는 것을 확인할 수 있습니다.
반복문에서는 inc ecx 를 이용하여 이 모든 영역들을 1 바이트씩 비교를 합니다. Wrong으로 뛰지 않으려면 이 모든 영역들의 값이 같아야겠지요.
여기서 추론할 수 있는 점은, 거의 확실하게 이 두 영역들은 24비트로 색상을 구분하는 비트맵 이미지의 데이터 영역들이고(다음 그림에서 볼 수 있는 것처럼 사용되는 API를 보면 Bitmap과 관련이 있어 보입니다. 해당 함수를 봤을 때, 사이즈가 200 X 150 인 것을 알 수 있습니다.),
(+추가)
몰랐는데, biBitCount 는 픽셀 당 비트 수라고 합니다. 여기서 24비트 비트맵이라는 것을 알 수 있습니다.
두 영역들 중 한 군데는 사용자의 입력을 받는 부분이겠거니와, 다른 한 군데는 기존에 존재하여 사용자의 입력 값과 비교를 당하는 부분이라는 것입니다. 그렇다면 중요한 것은 어느 영역이 기존에 존재하던 영역이라는 것인데, 사실 두 영역을 구분하는 것은 디버깅을 여러 번 해보아도 알 수 있습니다. 기존의 존재하던 영역은 파일 상에서도 계속 존재할 것이기 때문에 메모리 상에서의 주소(RVA)가 변하지 않겠지만, 사용자의 입력을 받는 부분의 주소는 매번 변할 것이기 때문에, 그냥 디버깅을 한 두 번만 해보아도 알 수 있을 겁니다. 위의 경우에는 0x0047E060이 기존 이미지의 데이터가 있는 주소입니다.
심심하지는 않지만, 배웠던 것을 복습하기 위해 한 번 PE구조를 보며 0x0047E060에 있는 것이 기존에 존재하던 영역인지 확인해봅시다. (무의미한 과정입니다.)
PE view로 ImagePrc.exe 를 까봅시다. 다음과 같은 화면을 확인할 수 있습니다.
이미지 파일은 리소스에 해당하기 때문에, 아마 리소스 섹션에 이미지 데이터가 있을 것이라고 생각되어 확인한 결과, 역시 리소스 섹션에 이미지에 관련된 데이터들이 있었습니다. 여기서 부가적인 여러 메타데이터들을 제외하고 순수하게 값 데이터만 본다면 File Offset이 0x00009060 부터 데이터가 시작되는 것을 알 수 있습니다. 자, 이제부터 이 Raw 값을 RVA 값으로 바꿔보도록 하겠습니다.
이미지가 리소스 섹션에 속하므로 리소스 섹션 헤더를 참조하여 RVA 값과 Pointer To Raw Data 값을 확인해보면 각각의 값은 0x7E000 과 0x9000입니다. Optional Header의 ImageBase 값도 찾아보면 0x400000입니다. 이 값들을 이용하여 0x9060 이라는 Raw 값을 RVA 값으로 변환시켜 본다면, RVA = 0x400000 + 0x7E000 - 0x9000 + 0x9060 = 0x47E060 입니다. 기존에 확인했었던 0x47E060은 파일 오프셋이 0x9060인 데이터라는 것을 확인하였습니다.
아무튼 메모리 상에서 0x47E060 ~ 0x47E060 + 0x159F0 - 1 영역과 0x6BC0040 ~ 0x6BC0048 + 0x159F0 - 1을 일치시키면 Wrong 창을 보지 않을 수 있다는 것과, 메모리 상에서 0x47E060 영역 혹은 파일 오프셋으로 0x9060 영역에서 기존 프로그램에 존재하던 어떤 그림이 있다는 것을 알게되었습니다.
그림의 값 데이터는 이미 알아내었기 때문에, 헤더만 붙여주면 bmp 파일 형태로 볼 수 있을 겁니다. 아까 구한 사이즈 (200 X 150)으로 아무 그림 파일이나 만든 다음에, 그 안의 데이터를 덮어씌우는 방식을 사용했습니다.
비트맵 파일의 포멧구조는 위와 같기 때문에, 오프셋 0x36 부터 RVA로 0x47E060 또는 Raw로 0x9060 에 있는 hex 값 데이터를 덮어씌워주면 됩니다.
파일 수정을 완료한 뒤, 파일을 열어보면, 답을 확인할 수 있습니다.
후기)
몇 비트 비트맵을 사용하는지 찾는 데에도 꽤 애먹었슴미다;
'Write-up' 카테고리의 다른 글
[angr tutorial] ais3_crackme writeup (0) | 2018.01.14 |
---|---|
[SECCON 2017] Powerful_Shell writeup (0) | 2018.01.14 |
[reversing.kr] Easy_CrackMe writeup (0) | 2018.01.08 |
[SECCON 2017] Vigenere3d writeup (0) | 2018.01.01 |
[CSAW 2017] SCV writeup (0) | 2017.12.27 |