본문 바로가기

Rerversing/Exercise reversing

[Reversing.kr] - Easy CrackMe 문제 풀이

본격적인 글?을 처음으로 작성...

이미 아주 옛날부터 이쪽에 관심있는 사람들이라면 다 했던 것을 난 이제서야 해본다.


뭐! 목적은 내가 한 것을 남겨보고자 하는 것과 이렇게 남기면서 내 머릿속에 한 번 더 각인시키고자 하는 것이 목적인 만큼

활발히는 아닐지라도 애정을 갖고 한 번 진행해보는 것도 나쁘지 않을 것 같다.


지인의 추천으로 알게 된 Reversing.kr 사이트에서 제공된 첫 번째 예제 문제이다.

자~~ GoGo!~






[그림. 1]  Easy Crack Me 실행 화면


처음 실행 화면은 [그림. 1]과 같다.

심플하게 다이얼로그 박스 하나만 있는데 뭔가 입력하고 확인 버튼을 누르면 어떤 반응이 있겠지.

(쌩뚱맞게 이게 만약 악성코드 였다면 VM에서 돌렸을텐데... 라는 생각을 했지만, 뭐 믿고 그냥 리얼 환경에서 바로 실행 -_-)






[그림. 2]  Incorrect Password


시험 삼아서 개인 ID로 사용하는 slays 를 입력하고 확인 버튼을 누르면 부정확한 패스워드라는 메시지가

위의 [그림. 2] 처럼 발생한다.

(아.. 그냥 12345 수준으로 입력해서 풀이를 할 걸 굳이 ID를 쓰니 풀이 과정을 보기가 약간 불편해질 망조가.. -_-)


실행 결과만 보면 제대로 된 패스워드를 넣지 않았다는 건데, 그렇다면 본 예제 파일이 원하는 패스워드가 무엇인지를

알아내는 것이 이번 문제의 핵심일 터... 디버거로 한 번 까보도록 하자.






[그림. 3]  MessageBoxA 함수 사용 주소


디버거로 확인하기 전에 [그림. 2]에서 추정할 수 있는 힌트는 "Incorrect Password" 라는 메시지가 발생했다는 것이다.

일반적으로 윈도우 환경에서 메시지 박스를 출력할 때 쓰이는 함수는 MessageBox() 라는 API 함수이다.

해당 함수가 쓰이는지를 확인하기 위해서 본 예제의 코드 영역인 Entry Point 부분으로 접근 한 후,

Search for -> all intermodular calls 기능으로 코드에서 사용하는 함수 리스트를 조회하면 [그림 3] 처럼

MessageBoxA 함수를 사용하는 영역이 두 곳이 있음을 알 수 있다.






[그림. 4]  MessageBoxA 함수 영역


두 곳의 주소로 이동해보면 성공 시의 메시지를 출력해주는 함수와 실패 시의 메시지를 출력해주는 함수가 보이는데,

두 개의 메시지 함수가 각각 "Congratulation" 과 "Incorrect Password" 라는 메시지를 출력해준다.

실패 메시지 출력 코드 영역을 자세히 보면 00401142 주소로 점프시켜주는 구간이 총 4개가 된다는 것을 알 수 있다.

아마 그 주소들이 패스워드를 비교하는 영역에서 패스워드가 틀리면 이 쪽으로 점프시켜주는 주소들이 될 것이다.






[그림. 5]  GetDigitemTextA 함수


우선 결과 메시지 출력 이전에 사용자가 입력한 패스워드 값을 비교하는 곳이 있을테니 그 부분을 확인해야 할 것이다.

슬라이드 바를 이용해 메세지 박스 출력 코드의 위 쪽으로 화면을 위로 올리다 보면은 [그림. 5]와 같이 GetDigitemTextA

API 함수가 확인이 되는데, 본 함수는 다이얼로그 박스에 입력된 텍스트 값을 가져올 때 사용되는 함수이다.

이 함수가 존재하는 영역이 Easy Crack Me 의 [확인] 버튼을 눌렀을 때의 이벤트 헨들러 영역에 해당될 것이다.

예제 파일 실행 뒤, GetDigitemTextA 함수에 BP를 걸고 아무 패스워드 값이나 입력해서 확인 버튼을 누르면 해당 함수에서 멈추게 된다.

스텍 영역을 보면 함수의 인자값들이 보이는데 3번째 인자 값이 패스워드 값을 저장할 포인터를 가리키고 있다.






[그림. 6]  GetDigitemTextA 함수 결과 및 비교 구문


[그림. 6]을 보면 비어있던 0018F958 스텍 영역에 패스워드 값으로 입력한 slays 라는 단어가 저장된 것이 확인된다.

함수 호출 코드 다음에 004010B0에서 CMP 코드가 등장하고 그 밑의 코드는 JNZ 분기점이 보인다.

CMP로 [ESP+5] 영역의 BYTE 값을 0x61과 비교하는 것을 볼 수 있는데 스택 값을 보면 ESP+5의 BYTE값은

알파벳 l 인 것을 알 수 있다.

입력한 패스워드 값은 slays 인데 비교의 시작이 s가 아닌 l 부터 하는 게 의아하겠지만 그냥 뒤로 하고 보이는데로

분석을 속행하자.

CMP 명령어는 비교하는 두 개의 값이 같은지 아닌지 비교할 때 자주 쓰이는 어셈 명령어인데 둘이 다르면 JNZ 코드에 의해

실패 메시지 함수 출력 부분으로 점프를 하게 된다.

알파벳 l 은 16진수 값으로 하면 0x6C이니깐 당연히 실패하는 쪽으로 빠지겠지.. 

즉 여기서 입력된 패스워드 값 중 두 번째 값은 0x61에 해당되는 문자열이 입력되었어야 한다는 것을 알 수 있다.

실패하지 않으려면 저 분기문을 넘어가야 하니 CMP 실행 전에 l을 소문자 a로 미리 변경하여 우회를 시키자.

(0x61은 소문자 'a' 에 해당되는 값이다.)






[그림. 7]  문자열 비교 함수


00401089의 코드를 보면 [ESP+A] 해당되는 스텍 주소를 ECX 에 넣고 "5y" 라는 아스키 값과 ECX를 PUSH 한 뒤 특정 함수를 호출하는데, [ESP+A]가 가리키는 주소에는 slays 중 ays 가 존재한다. 이 전에 알파벳 l 을 비교한 후에 그 다음 문자열을 비교할 것으로 보인다.

참고로 004010B7 주소의 PUSH 2 라는 코드 역시 해당 함수에서 인자값으로 사용이 되는데 이건 함수 내에서 카운터 수치로 쓰이게 된다.

해당 함수로 들어가보자






[그림. 8]  3, 4번째 문자열 비교 수행


인자값으로 들어온 2는 ECX 레지스터에 저장하여 문자열 비교 횟수의 카운터 역할을 시키고

인자값으로 들어왔던 사용자의 입력 암호 나머지 부분과 "5y"라는 문자열을 비교한다.

솔직히 이 부분만 보더라도 5y를 비교한다는 것은 암호의 3번째, 4번째 자리가 5y여야 한다는 것을 짐작할 수 있다.

카운터도 친절하게 2 가 입력되는 걸 보면 두 자리의 비교라고 짐작할 수 있다.

[그림. 8]을 보면 함수 내부에서 00401164 주소의 REPNE 명령어와 0040116F 주소의 REPE 명령어를 이용하는데


두 명령어를 이용해서 특정한 두 개의 값(여기서는 뭐 당근 문자열 비교겠지)을 비교하는 것인데,

각 명령어의 역할은 다음과 같다.

(참고로 REP 류의 명령어는 단독으로 쓰이지 못하고 꼭 뒤에 추가로 명령어가 붙어야 함)


REPE -> ECX 값이 0보다 크고 ZF 레지스터 값이 1일 경우에 그 뒤의 명령어들을 반복시킴

REPNE -> ECX 값이 0보다 크고 ZF 레지스터 값이 0일 경우에 그 뒤의 명령어들을 반복시킴

(위 명령어는 두 가지의 조건 중 하나라도 어긋나면 코드를 빠져나오게 된다.)


EAX 를 0으로 먼저 초기화를 시킨 뒤 00401164 코드에서 REPNE 명령어와 SCAS 명령어를 이용하는데 이는 사용자 입력 암호 중 3, 4 번째 자리가 공란인지 아닌지를 확인하는 구간이다.

0040116F 코드에서 REPE CMPS 명령어는 사용자 암호 3,4 번째 부분과 "5y" 코드를 직접 비교하는 것이고,

그 밑의 점프 코드는 비교 결과에 대하여 암호가 작거나 클 경우의 동작을 위한 점프문으로 구성되어진다.

암호가 공란이거나 또는 5y 와 같지 않을 경우 점프 문을 통해 ECX 레지스트를 이용한 연산이 수행되며

MOV EAX, ECX 명령을 통해 EAX에 값을 세팅한다.

암호가 일치하지 않을 경우에는 JA, JE 점프문에서 걸려저서 ECX 레지스터에 0이 아닌 다른 값으로

세팅이 되고 그 값이 EAX에 저장이 된다.

암호가 제대로 됐다면 EAX에는 0이 세팅이 되며 함수를 빠져나가게 된다.

(EAX는 함수 완료 후 다음 분기문의 참조로서 사용이 된다.)






[그림. 9]  두 번째 암호 비교 성공


3, 4번째 항목을 5y로 맞춰주면 EAX는 0이 되며 함수를 빠져나온다.

004010CB의 TEST EAX, EAX 코드는 EAX가 0인지를 체크하는 코드인데, EAX 값에 0이 설정되었다면 패스워드 비교가 서로 맞았다는 뜻이고 0이 아니라면 패스워드 값이 서로 달랐다는 것을 의미한다.

만약 0이 아니라면 004010CD의 JNZ 코드로 인해서 실패 메시지 함수로 빠잉~ 하게 된다.

뭐~ 5y라는 비번까지는 맞춰줬으니 다행히 분기문은 그냥 지나쳐가겠지.






[그림. 10]  세 번재 암호 비교


이어지는 코드에서 갑자기 등장한 "R3versing" 아스키 코드....

그리고 [ESP + 10] 스텍 주소를 EAX에 셋팅하는데, 해당 위치를 가보면 입력했던 사용자 값의 5번째 위치를 가리키는 것이 확인된다.

slay 까지 왔으니 끝에 남은 s 의 위치를 EAX가 가리키게 된다.

(역시, 그냥 숫자로 예제 풀이할 걸... 하고 급 후회가 몰려오는 중 -_-)


그렇게 세팅을 하고 [그림 10]에서 처럼 문자열 비교 반복문들을 엄청 돌려댄다. 만약 비교 값이 다를 경우 JNZ 코드들로 인하여 00401102 로 빠지며 EAX가 -1로 세트되고 그림엔 안 보이지만 밑에 나올 코드에서 또 다시 JNZ 점프문으로 인해 실패 메시지 함수 출력으로 빠잉~ 하게 된다.






[그림. 11]  마지막 암호 비교


연산을 설명하긴 귀찮으니 자세한 설명은 생략하고...

앞서 얻은 힌트 "서로 값이 다르면 점프문 타고 빠이~" 한다는 공통적인 점들과 그리고 친절하게도

R3versing 이라는 아스키 코드가 평문으로 나타나주셨으니 최소한 현재까지 알려진 암호의 조합은 "a5yR3versing" 일 것이라 추정되겠다.

(참고로 맨 앞에 한 자리를 아직도 비교하지 않았으므로 그 부분이 빠졌다는 것을 염두하자)


해당 암호의 앞에 한 자리가 비었다는 것은 위의 분석 결과로 알 수 있지만 뒷 부분에도 뭔가 더 붙을지는 계속 분석해봐야

알 수 있겠지.

세 번째 암호 비교문을 탈출하면 [그림. 11] 의 CMP 코드를 보게 되는데, 자세히 보면 [ESP+4] 스텍 영역의 값과 0x45 값을 비교하고 있다. 그리고 그 다음 코드인 JNZ 코드가 있는데 CMP 코드 수행 결과, 두 비교 값이 다르다면 실패 메시지 박스를 출력하는 코드로 점프를 하고, 비교 값이 똑같다면 JNZ 분기문은 그냥 지나쳐 버리고 그 밑의 Congratulation 메시지를 출력하는 메시지 박스에 도달할 수 있다.

[ESP +5]의 영역을 확인해보면 암호의 맨 앞자를 가리키는 것을 알 수 있다. (처음에 비교하지 않고 지나쳤던 바로 그 부분...)

0x45에 해당되는 텍스트는 대문자 알파벳 E 이며 이 모든 것을 조합하면 암호는 "Ea5yR3versing" 으로 조합이 된다.






[그림. 12]  풀이 완료


Ea5yR3versing 을 입력한 결과, 풀이 완료~~~