crackme1을 실행시키면 이전 hello world! 보다 코드수가 적은 것을 알 수 있습니다.
왜냐하면 crackme는 어셈블리어로 만들어졌기 때문에 이전과 다르게 복잡하지 않습니다.
우선 프로그램을 실행시켜보면서 무엇을 하는지 확인하겠습니다.
[F9]을 통해 프로그램을 수행하면 "Make me think your HD is a CD-ROM"이 적힌 MessageBox가 출력됩니다.
우선 [확인]을 누르고 계속 진행하면
에러 메시지 박스가 출력되며 프로그램이 끝나게 됩니다.
crackme1은 HD를 CD-ROM으로 인식해야 합니다.
저도 쓰고도 무슨 말인지 잘 모르겠습니다. 하나 정확한 것은
crackme1 어셈블리 코드를 보았을 때 Error 메시지 박스 대신 YEAH! 성공(?)을 의미하는 메시지 박스가 출력되도록 하는 것이 목표라고 생각하면 될 것 같습니다.
이번 crackme1에서는 코드 수가 적기 때문에 하나하나 분석하면서 진행하겠습니다.
다시 시작점으로 돌아와서 시작점인 00401000 주소부터 보면 함수( Win32 api)가 있습니다.
MessageBox() 함수를 이전에 몇 번 등장했기 때문에 꽤나 친숙하지만 아래 처음 보는 GetDriveTypeA() 함수가 있습니다.
GetDriveType() 함수
UINT GetDriveTypeA(
LPCSTR lpRootPathName
);
이 함수는 지정된 디스크가 어떤 형태인지 구분하여 그에 해당되는 반환 값을 반환합니다.
DRIVE_UNKNOWN 0 |
드라이브 유형을 확인할 수 없습니다. |
DRIVE_NO_ROOT_DIR 1 |
루트 경로가 잘못되었습니다. 예를 들어, 지정된 경로에 마운트 된 볼륨이 없습니다. |
DRIVE_REMOVABLE 2 |
드라이브에 이동식 미디어가 있습니다. 예 : 플로피 드라이브, 썸 드라이브 또는 플래시 카드 리더. |
DRIVE_FIXED 3 |
드라이브에 고정 미디어가 있습니다. 예를 들어, 하드 디스크 드라이브 또는 플래시 드라이브. |
DRIVE_REMOTE 4 |
드라이브가 원격 (네트워크) 드라이브입니다. |
DRIVE_CDROM 5 |
드라이브가 CD-ROM 드라이브입니다. |
DRIVE_RAMDISK 6 |
드라이브는 RAM 디스크입니다. |
위 표에 왼쪽은 반환 코드와 반환 값 오른쪽은 반환 코드/값에 대한 설명입니다.
다시 코드로 돌아와서 GetDriveTypeA함수에 지정된 디스크는 C:\으로 하드 디스크 드라이브이기 때문에 3이 반환 값으로 반환될 것입니다.
올리 디버거를 통해 GetDriveTypeA함수를 수행한 후 함수 리턴 값을 저장하는 EAX 레지스터를 보면 3이 저장돼있는 것을 확인할 수 있습니다.
이후 코드를 차례대로 살펴보면
ESI 값을 1 증가
EAX 값을 1 감소 // eax 값은 2
JMP 00401021 바로 아래로 점프한다 사실상 쓸데없는 코드이기에 그냥 넘어가면 됩니다.
ESI 값을 1 증가
ESI 값을 1 증가
EAX값을 1 감소 // eax 값은 1
위 여러 연산(?)을 수행한 이후 코드입니다.
00401024 주소에 CMP가 있는 것을 보아 이제 비교를 통해서 에러 메시지를 출력할 것인지 아니면 성공메시지를 출력할 것인지 결정하는 것 같습니다.
차례대로 코드를 보게 되면 EAX와 ESI값을 비교하고 다음 명령어는 EAX와 ESI값이 같게 되면 0040103D주소 즉 에러 메시지 대신 YEAH! 메시지가 출력되게 됩니다.
이후 주소 00401028 ~ 00401036까지 MessageBox() 함수가 있고 주소 0040103B는 프로그램을 종료하는 ExitProcess로 점프하며 프로그램을 종료합니다.
이후 0040103D ~ 0040104B까지 Yeah! 메시지 박스 함수가 있습니다.
이제 코드가 어떻게 작동하는지 알았으니 코드를 패치하여 크랙을 하겠습니다.
코드를 패치하여서 성공 메시지 박스를 출력하는 방법에는 여러 방법이 있습니다.
리버싱 핵심원리 책에서는 0040103D주소에 JE 명령어를 JMP로 바꾸어 무조건 점프하도록 하여서 성공 메시지가 출력되게 하였습니다.
제가 한 방법에 대해 설명하면서 JE명령어는 CMP명령어에서 2개의 값이 같은지 다른지 판별할 수 있는지에 대해 설명하겠습니다.
CMP 명령어는 비교를 할 때 비교할 두 값을 빼서 같은지 다른지를 검사합니다.
그럼 두 값을 뺏을 때 결과가 0이면 같은 값이 될 것이고 0이 아니면 서로 다른 값이 될 것입니다.
그리고 이 CMP명령어의 결과를 JE명령어가 알게 하기 위해서는 제로 플래그라는 Z FLAG를 참조해야 합니다.
Z FLAG는 CMP명령어의 결과를 저장하게 됩니다.
CMP명령어에서 두 값이 같으면 결과가 0이기 때문에 Z Flag는 1이 됩니다.
왜 결과는 0인데 1이지 생각할 수 있지만 제로 플래그는 연산 결과가 0일 때 참인 1이 된다 생각하시면 됩니다.
이후 JE명령어는 Z FLAG를 보고 참(1)이면 점프를 하고 거짓(0)이면 점프를 수행하지 않습니다.
여기서 점프를 해야지 에러 메시지 박스를 피하고 성공 메시지 박스로 이동할 수 있기 때문에 JE명령어의 수행을 결정하는 Z FLAG를 참(1)으로 전환시켜 원하는 결과를 얻도록 하였습니다.
이렇게 여러 방법으로 코드를 패치한 뒤 프로그램을 실행하면
에러 메시지 박스 대신 성공(?) 메시지 박스가 출력됩니다.
코드가 짧기 때문에 하나하나 다 분석하다 보니 많이 길어진 것 같습니다.
다음장은 기존의 스택 프레임은 생략하고 abex crackme2 분석으로 하겠습니다.
'리버싱' 카테고리의 다른 글
리버싱 #04 abex crackme 2 분석 (0) | 2021.01.24 |
---|---|
리버싱 #02 Hello world 디버깅 (0) | 2021.01.19 |
리버싱 #01 리버싱 스토리 (3) | 2020.12.28 |
리버싱 #00 intro (2) | 2020.12.27 |