Abex의 3번째 크랙미다. 이번 파일은 델파이로 작성되어 있으며 크랙미와 같은 디렉토리에 위치한 유효한 키파일을 찾아야 한다.
설명은 간단하다. 그럼 우선 프로그램을 실행시켜 보자.
시작하자마자 아무런 창도 없이 바로 메시지 박스가 호출되었다. 키파일을 확인하는 단계인 것 같다.
확인을 누르니 키파일을 찾을 수 없다는 메시지 박스가 호출된 후 프로그램이 종료되었다. 확인 -> 성공/실패로 이어지는 간단한 로직의 프로그램인 것 같은데, 일단 디버거로 열어보자.
예전 첫번째 크랙미처럼 스크롤 안에 다 들어오는 간단한 구조를 가지고 있다. 호출하는 함수명을 살펴보니 MessageBoxA(), CreateFileA() 등 윈도우 API를 사용하고 있는데 이 함수들의 동작은 MSDN 등 구글링을 통해 쉽게 찾을 수 있다.
int MessageBoxA(
HWND hWnd,
LPCSTR lpText,
LPCSTR lpCaption,
UINT uType
);
HANDLE CreateFileA(
LPCSTR lpFileName,
DWORD dwDesiredAccess,
DWORD dwShareMode,
LPSECURITY_ATTRIBUTES lpSecurityAttributes,
DWORD dwCreationDisposition,
DWORD dwFlagsAndAttributes,
HANDLE hTemplateFile
);
DWORD GetFileSize(
HANDLE hFile,
LPDWORD lpFileSizeHigh
);
프로그램 구조를 보면 파라미터를 스택에 넣고 함수를 호출한 후 반환값에 따라 분기하고 있는 것을 알 수 있다. 그렇다면 각 함수가 어떤 파라미터를 받고 어떤 값을 반환하는지 확인해야 할 필요가 있을 것이다.
먼저 첫번째 함수 호출인 MessageBoxA()의 경우 간단하게 uType, lpCaption, lpText, hWnd 네 가지 매개변수를 받는다. 이는 스택에 각각 0, abexcrackme3.402000, abexcrackme3.402012, 0 순서대로 들어가며 함수호출 규약에 따라 오른쪽 파라미터부터 왼쪽 파라미터로 적용된다. 그래서 lpCaption에 "abex' 3rd crackme"라는 문자열이, lpText에 "Click OK to check for the keyfile"라는 문자열이 적용된 메시지 박스가 호출되는 것이다.
함수호출 규약에 관해서는 링크를 참고하면 좋다. 중요한 것은 대부분의 호출 규약이 함수의 매개변수를 오른쪽에서 왼쪽으로 스택에 넣는다는 것이다. 그 외에는 함수 종료 후 정리를 피호출자가 하는지 호출자가 하는지 등의 차이가 있다.
어쨌든 이렇게 메시지 박스를 호출한 후에는 CreateFileA() 함수를 호출한다. 7개의 매개변수를 사용하는데 순서대로 abexcrackme3.4020B9, 80000000, 0, 0, 3, 80, 0을 사용하여 호출한다. 이 매개변수가 어떤 의미를 가지는지 확인하기 위해 문서를 확인해보면 다음과 같다.
-
lpFileName: 생성(create)되거나 오픈(open)될 파일 이름. abexcrackme3.4020B9에 위치한 "abex.l2c" 문자열이다.
-
dwDesiredAccess: 해당 파일에 대한 권한(읽기, 쓰기 등). 0x80000000은 GENERIC_READ, 즉 읽기 권한을 의미한다.
-
dwShareMode: 해당 파일의 공유 모드 설정. 파일을 열고 있을 때 다른 파일이 참조하는 것을 제한하며 0은 다른 프로세스가 해당 파일을 읽거나 쓰거나 삭제하는 것을 막는 옵션이다.
-
lpSecurityAttributes: SECURITY_ATTRIBUTES 구조체에 대한 포인터다. 필요없기 때문에 NULL 값이 주어졌다.
-
dwCreationDispotion: 존재하거나 존재하지 않는 파일, 디바이스에 대해 수행할 동작을 정의한다. 3 값은 OPEN_EXISTING이며 이는 해당 파일이 존재할때만 수행되며 존재하지 않는다면 ERROR_FILE_NOT_FOUND 에러를 발생시키고 함수를 종료한다.
-
dwFlagsAndAttributes: 파일이나 디바이스 속성을 정의한다. 0x80은 FILE_ATTRIBUTE_NORMAL로 아무런 속성을 가지지 않는다.
-
hTemplateFile: GENERIC_READ 권한으로 템플릿 파일을 참조하기 위한 핸들이다. 해당 함수가 이미 존재하는 파일을 열 때 이 옵션은 무시되며 NULL 값을 줄 수 있다. 그래서 0 값이 주어졌다.
이 매개변수로 볼 때 이 크랙미에서는 CreateFileA() 함수를 이용하여 "abex.l2c"란 파일을 열어서 읽고 파일이 없다면 실패하는 동작을 수행한다고 추측할 수 있다. 그렇다면 지금 크랙미 파일과 같은 폴더에 해당 파일이 없는데 이후 분기는 어떻게 진행될까?
함수 실행 결과값을 0xFFFFFFFF과 비교하여 일치한다면 00401075로 점프, 위에서 봤던 에러 메시지박스를 출력하게 된다. 0xFFFFFFFF는 -1을 나타내며 이는 CreateFileA()가 실패했을 때 반환하는 INVALID_HANDLE_VALUE값이다. 분기문이기 때문에 단순히 00401037의 je를 jne로 어셈블한다면 성공하지 않을까? 한번 jne로 어셈블한 후 실행해보면 이전과는 다른 메시지 박스가 출력된다.
자세히보니 성공 메시지 박스와 실패 메시지 박스 사이에 "The found file is not a valid keyfile!" 이라는 문구가 보인다. 즉 성공하려면 조건이 하나 더 필요한 것이다. 해당 구문으로 점프하는 분기를 살펴보기 전에 편의를 위해 "abex.l2c"란 파일을 하나 생성해주자. 빈 파일을 생성해주고 프로그램을 실행시켜보면 위의 메시지 박스가 다시 나타날 것이다.
CreateFileA() 이후 진행되는 블록을 살펴보면 매개변수 두 개를 스택에 넣고 GetFileSize() 함수를 호출하는 것을 알 수 있다. 해당 함수는 hFile, lpFileSizeHigh를 매개변수로 받아 실행되는데 이 의미는 문서를 살펴보면 다음과 같다.
-
hFile: 해당 파일에 대한 핸들. 핸들은 해당 파일을 열었을 때 유지되는 일종의 연결이라 할 수 있는 포인터다.
-
lpFileSizeHigh: 파일의 크기가 저장될 변수의 포인터. NULL 값을 사용할 수 있다.
두 번째 매개변수는 NULL 값이라치고 첫 번째 매개변수는 004020CA에 위치한 값을 전달하고 있다. ds:[4020CA]에는 무슨 값이 저장되어 있을까? 이는 직접 찾아가볼 필요 없이 바로 위쪽을 살펴보면 알 수 있다.
CreateFileA() 함수로 열어서 얻은 파일 핸들은 eax 레지스터에 반환되며 이를 004020CA에 저장하고 있다. 그리고 GetFileSize() 함수에서 다시 참조하여 사용하고 있다. 함수의 반환값은 eax 레지스터에 저장되어 값 12와 비교하고 있다. 즉 CreateFileA()로 열은 "abex.l2c" 파일의 크기를 구해서 0x12와 비교하여 최종적으로 성공으로 분기하는 것이다.
16진수로 12는 16+2=18이다. 보통 파일의 크기는 바이트 단위로 반환되므로 18바이트짜리 파일이 필요할 것이다. 확인을 위해 아까 만든 "abex.l2c" 파일에 18바이트 문자열을 저장한 후 프로그램을 다시 실행시켜보면 어떻게 될까?
크랙미가 성공한 것을 알 수 있다. 영문자, 숫자가 한 글자에 1바이트이므로 111122223333444455처럼 18글자를 쓰거나 한 글자에 3바이트씩 하는 한글을 사용해볼 수도 있을 것이다.
'챌린지 > CrackMe' 카테고리의 다른 글
Abex's 5th Crackme (0) | 2020.10.26 |
---|---|
Abex's 4th Crackme (0) | 2020.10.25 |
Abex's 2nd Crackme (0) | 2020.09.29 |
Abex's 1st Crackme (0) | 2020.08.29 |