[crackme5] abex' 5th crackme 풀이 및 복원
1. [abex' 5th crackme] 풀이시연
1) 실행파일 실행
[1] 파일 열기
▶ 프로그램을 실행시키자 Serial 값을 입력하고 Check를 클릭할 수 있도록 만들어져 있다.
▶ Check를 클릭하자 입력한 시리얼 값이 올바르지 않다는 메세지를 출력하고 종료한다.
2) 개발언어 및 링커 확인
[1] Pascal계열의 Delphi로 작성됨을 알 수 있다.
[2] 32비트 실행파일임을 알 수 있다.
[3] Terbo 링커를 사용하여 링킹한 것을 알 수 있다.
[4] 헤더타입이 PE라고 적힌 것이 확인되어 Windows용 실행파일임을 알 수 있다.
[5] ImageBase(기준주소)가 0x400000임을 알 수 있다
[6] EntryPoint(시작주소)가 기준주소로부터 0x1000 만큼 떨어진 곳임을 알 수 있다.
[7] 기타 시그니쳐 및 옵션 세부사항은 직접 하나씩 확인해보면 된다.
[8] Detect It Easy 1.01사용하였다.
3) 상상
[1] 1)에서 실행해본 결과 특정파일 이름이 있는지 확인하는 조건문이 있을 것으로 추측된다.
[2] 의사코드
시리얼 값 입력..
if (입력한 시리얼 값이 정상일 경우) {
//참인 경우? 실행할 내용
} else {
MessageBox(핸들번호, "The serial you entered is not correct!.", "Error!", MB_OK);
}
종료..
4) 풀이
[1] Main함수 실행
▶ 먼저 시리얼 값이 실행파일 내 존재하는지 확인하기 위해 [search for - All referenced text strings]를 눌러 확인한다.
[2] 문자열 검색
▶ [4562-ABEX, L2C-5781] 등의 값이 보이지만 분석하는 것이 목적이므로 "Yep, you entered a correct serial!" 주소로 이동해 보았다.
[3] 분기문
▶ 위에서 조건문을 통해 이 메세지 박스 영역으로 온 것으로 보인다.
▶ [0x4010FC]의 compare문을 통해 EAX가 0이면 [0x401117]로 점프하는 것이 보인다.
[4] 분기문 전
▶ [0x4010FC]주소 위에를 더 보면 lstrcmpiA( )함수와 lstrcatA( )함수가 보인다.
[5] lstrcmpiA( ) 함수
lstrcmpiA( ) 함수의 원형
int lstrcmpiA(
LPCSTR lpString1,
LPCSTR lpString2
);
▶ lstrcmpiA( )함수는 lpString1과 lpString2를 비교하여 같으면 0을 반환한다.
[6] lstrcatA( ) 함수
LPSTR lstrcatA( ) 함수의 원형
LPSTR lstrcatA(
LPSTR lpString1,
LPCSTR lpString2
);
▶ lstrcatA( )함수는 lpString1문자열에 lpString2문자열을 추가해서 문자열의 주소를 반환한다.
▶ lstrcatA( )함수 위로 올라가보면 jmp문은 보이지 않고 GetDlgItemText( ) 함수와 GetVolumnInformation( ) 함수가 보인다.
[7] GetDlgItemText( ) 함수
UINT GetDlgItemTextA( ) 함수의 원형
UINT GetDlgItemText (
HWND hDlg,
int nIDDlgItem,
LPSTR lpString,
int nMaxCount
);
▶ GetDlgItemTextA( )함수는 CHAR형 문자열을 복사하며 마지막 NULL을 빼고 복사한 문자열의 길이를 반환한다.
▶ 파라미터로는 핸들번호, 복사할 문자열의 ID, 복사 후 저장할 문자열의 주소, 복사할 최대 문자 수가 들어간다.
▶ 즉, 처음에 시리얼 값 입력한 것을 0x402324의 주소에 복사해 놓는다
[8] GetVolumnInformation( ) 함수
BOOL GetVolumeInformationA( ) 함수의 원형
BOOL GetVolumeInformationA(
LPCSTR lpRootPathName,
LPSTR lpVolumeNameBuffer,
DWORD nVolumeNameSize,
LPDWORD lpVolumeSerialNumber,
LPDWORD lpMaximumComponentLength,
LPDWORD lpFileSystemFlags,
LPSTR lpFileSystemNameBuffer,
DWORD nFileSystemNameSize
);
▶ GetVolumnInformation( ) 함수는 파라미터에 주소를 넣고 그 곳에 볼륨에 대한 정보를 얻어오는 함수이다.
▶ LPCSTR lpRootPathName은 드라이브명에 대한 문자열 포인터를 의미한다.
+ lpRootPathName의 값이 NULL(0)일 경우 현재디렉터리의 최상위 디렉터리를 의미한다.
▶ LPSTR lpVolumeNameBuffer는 볼륨명에 대한 문자열 포인터를 의미한다.
▶ DWORD nVolumeNameSize는 볼륨명의 최대 길이를 의미한다.
▶ LPDWORD lpVolumeSerialNumber는 볼륨의 일련번호를 수신하는 변수의 포인터를 의미한다.
+ 볼륨의 일련번호는 cmd창에서 [vol c:]의 방식으로 입력하면 확인되는 것과 같다.
▶ LPDWORD lpMaximumComponentLength는 파일시스템이 지원하는 파일이름의 최대길이를 받는 변수의 포인터를 의미한다.
▶ LPDWORD lpFileSystemFlags는 파일시스템과 관련된 플래그를 수신하는 변수의 포인터를 의미한다.
▶ LPSTR lpFileSystemNameBuffer는 파일시스템의 이름을 수신하는 문자열에 대한 포인터를 의미한다.
▶ DWORD nFileSystemNameSize는 파일시스템이름의 최대길이를 의미한다.
▶ 0을 반환했다면 요청한 정보가 모두 검색되지 않았다는 의미이다.
▶ 즉, 현재 abex' 5th crackme가 있는 최상위 디렉토리의 볼륨명을 0x40225C에 저장하고, 볼륨의 일련번호를 0x402194에 저장한다.
▶ 파일시스템 이름을 저장할 문자열 포인터는 NULL로 부여했기 때문에 저장하지 않는다.
[9] DialogBoxParamA( ) 함수
INT_PTR DialogBoxParamA( ) 함수의 원형
INT_PTR DialogBoxParamA(
HINSTANCE hInstance,
LPCSTR lpTemplateName,
HWND hWndParent,
DLGPROC lpDialogFunc,
LPARAM dwInitParam
);
▶ DialogBoxParamA( ) 함수는 다이얼로그 박스를 실행시키는 함수이다.
▶ HINSTANCE hInstance는 커널이 WinMain( )함수에 부여한 값으로 응용프로그램의 인스턴스 값을 말한다.
+ 매개변수가 NULL일 경우 현재 실행파일의 인스턴스 값을 사용한다.
▶ LPCSTR lpTemplateName은 생성할 대화상자의 ID 값을 말한다.
▶ HWND hWndParent는 창의 핸들번호이다.
▶ DLGPROC lpDialogFunc은 대화상자 함수에 대한 포인터로 대화상자에서 발생한 메세지를 처리하는 메세지처리함수이다.
▶ LPARAM dwInitParam은 대화상자의 초기 모양을 설정하기 위한 값을 전달한다.
▶ 즉, 현재 응용프로그램의 인스턴스를 사용하고 ID는 1번을 사용하며, 메세지 발생시 0x401029로 이동한다.
▶ 초기화속성은 없다.
[10] EndDialog( ) 함수
BOOL EndDialog(
HWND hDlg,
INT_PTR nResult
);
▶ hDlg는 사용한 대화상자의 핸들값, nResult는 대화상자 내 함수에서 응용프로그램에 반환할 값을 말한다.
▶ 사용한 대화상자를 반환(종료)한다. (두 번째 파라미터의 0은 대화상자를 끝낸다는 말이다.)
[11] GetModuleHandleA()
HMODULE GetModuleHandleA( ) 함수의 원형
HMODULE GetModuleHandleA(
LPCSTR lpModuleName
);
▶ exe나 dll, 즉 실행파일의 모듈 값을 가져오는 함수이다.
▶ lpModuleName 파라미터에 NULL을 넣으면 소스를 작성하고 있는 영역의 모듈 값을 가져온다.
▶ 함수 실행 후 eax에 모듈 값(0x400000[Image Base] = 현재 함수가 있는 시작영역)이 반환된다.
5) 풀이
[1] 시작
▶ 먼저 첫 번째 파라미터에 NULL을 넣고 GetModuleHandleA( ) 함수를 실행하면 EAX에 현재 영역의 시작주소인 0x400000이 반환된다.
[2] 대화상자 실행
▶ [1]에서 반환받은 0x400000(현재 영역의 시작주소)를 0x4023EC에 저장 후 첫 번째 파라미터에 넣는다.
▶ 두 번째 파라미터는 다이얼로그 박스의 식별번호를 1번으로 지정하기 위해 1을 넣는다.
▶ 세 번째 파라미터의 핸들번호는 NULL을 넣는다.
▶ 네 번째 파라미터의 0x401029는 대화상자에서 입력받은 메세지를 처리하는 곳의 주소를 넣는다.
▶ 다섯 번째 파라미터는 특별한 디자인을 설정하지 않도록 NULL을 넣는다.
[3] 대화상자 입력
▶ 123456의 값을 넣고 체크를 누른다.
[4] GetDlgItemTextA( ) 함수처리
▶ 첫 번째 파라미터로 [EBP+8]에 있는 Creakme5의 핸들번호를 넣는다.
▶ 두 번째 파라미터는 복사할 문자열의 식별번호인 0x68을 넣는다.
▶ 세 번째 파라미터는 다이얼로그에서 입력한 문자열을 저장할 주소를 넣는다.
▶ 네 번째 파라미터는 복사할 문자열의 최대 길이인 0x25를 넣는다.
▶ 이 후 다이얼로그에서 입력한 문자를 0x402324의 주소에 저장 후 입력한 문자열의 길이를 반환한다.
[5] 문자열의 길이 반환
▶ 대화상자에 입력한 123456의 길이인 6이 EAX에 반환된 것을 확인한다.
[7] GetVolumeInformationA( ) 함수
▶ 첫 번째 파라미터는 현재 파일이 있는 곳의 최상위 디렉터리를 의미하는 NULL을 넣는다.
▶ 두 번째 파라미터는 볼륨명을 저장하기 위한 주소로 0x40225C를 넣는다.
▶ 세 번째 파라미터는 볼륨명의 최대 길이를 의미하며 0x32를 넣는다.
▶ 네 번째 파라미터는 볼륨의 일련번호를 저장하기 위한 주소로 0x402194를 넣는다.
▶ 다섯 번째 파라미터는 파일명의 최대 길이를 가지고 있는 주소로 0x402190을 넣으며 0x402190에는 255(0xFF)가 들어있다.
▶ 여섯 번째 파라미터는 플래그를 저장하는 주소로 0x4020C8을 넣는다
▶ 일곱 번째 파라미터는 파일시스템의 이름을 저장하는 주소를 넣지만 NULL을 넣는다.
▶ 여덟 번째 파라미터는 파일시스템이름의 최대사이즈를 넣지만 NULL을 넣는다.
... 기나긴 파라미터 push과정이 끝났다.
[8] 문자열 합치기
▶ 첫 번째 파라미터에 0x40225C를 넣었고, 0x40225C는 [7]에서 볼륨명을 저장한 주소라고 확인했다.
▶ 두 번째 파라미터는 0x4023F3을 넣었고, 0x4023F3 주소에는 "4562-ABEX" 값이 들어있다.
[9] [8]의 실행 결과
▶ 첫 번째 파라미터 주소의 문자열과 두 번째 파라미터 주소의 문자열을 붙여서 첫 번째 파라미터의 주소에 저장되었다.
▶ 여기서 드는 의문은 볼륨명에 아무런 값이 저장되지 않았을까..?
[10] 연산
▶ 연산방법은 [9]에서 합한 문자열의 시작주소부터 4바이트의 값을 1씩 증가시킨다.
▶ 위 연산을 2번 반복한다.
▶ 결과적으로 "4562-ABEX"의 문자열이 "6784-ABEX"로 변경되었다.
[11] 문자열 합치기
▶ 첫 번째 파라미터에 0x402000을 넣었고, 0x402000 주소에는 0으로 채워져 있다.
▶ 두 번째 파라미터는 0x4023FD를 넣었고, 0x4023FD 주소에는 "4562-ABEX" 값이 들어있다.
[12] [11]의 실행 결과
▶ 첫 번째 파라미터 주소의 문자열과 두 번째 파라미터 주소의 문자열을 붙여서 첫 번째 파라미터의 주소에 저장되었다.
[13] 문자열 합치기
▶ [8]에서 합치고 [10]에서 연산한 문자열과 [11]에서 만든 합해서 만든 문자열을 합한다.
[14] [13]의 결과
▶ 첫 번째 파라미터 주소의 문자열과 두 번째 파라미터 주소의 문자열을 붙여서 첫 번째 파라미터의 주소에 저장되었다.
[15] 문자열 비교
▶ [4]에서 0x402324의 주소에 대화상자에서 입력한 문자열을 저장했었다.
▶ 첫 번째 파라미터로 [13]에서 합해서 만든 문자열의 주소인 0x402000을 넣는다.
▶ 두 번째 파라미터로 [4]에서 입력한 문자의 주소인 0x402324를 넣는다.
[16] 문자열 비교 결과
▶ EAX에 1이 반환된 것으로 보아 입력한 시리얼 값과 "L2C-57816784-ABEX"는 같지 않다는 것을 알 수 있다.
[17] 비교에 따른 결과
▶ EAX가 0이 아니므로 0x401117로 이동하지 못하고 0x401101부터 시작하는 메세지 박스 출력 후 0x40112D부터 시작하는 EndDialog( ) 함수를 호출하고 대화상자를 닫고 종료한다.
[18] 의문점
▶ [9]에서 볼륨명이 왜 반환되지 않았을까?
▶ 직접 하드디스크 볼륨명을 확인해본 결과 "로컬 디스크"라고 적혀 있었다.
▶ 그러나 이 볼륨명은 볼륨명이 없을 때 표시되는 글자이다.
▶ 그래서 F2를 눌러 "test"라는 볼륨명을 직접 지정해 주었다.
[19] 볼륨명 저장확인
▶ GetVolumeInformationA( ) 함수에 갔다오니 0x40225C의 주소에 "test"라는 볼륨명이 반환되었다.
▶ 그렇다면 시리얼 값은 고정적이지 않고 자신의 하드디스크의 볼륨명에 따라 변하는 것으로 보인다.
[20] 문자열 합치기
▶ [19]에서 문자열을 합친 결과 0x40225C의 주소에는 "test4562-ABEX" 문자열이 저장되었다.
[21] 연산
▶ 0x40225C에 있는 문자열 "test4562-ABEX"의 앞 4바이트의 값을 1씩 증가시킨다.
▶ 위 연산을 2번 반복한다.
▶ 0x40225C에 있는 문자열은 앞 4바이트인 "test"가 각각 2씩 더해져서 "vguv4562-ABEX"로 변경되었다.
[22] 문자열 합하기
▶ 이미 instruction을 실행해서 주소의 값이 동일하게 변경되어 있어서 다음 줄에서 확인한다.
▶ 0x4010D9의 lstrcatA( )함수에서 0x402000주소의 값은 "L2C-5781"이다.
▶ 0x4010E8의 lstrcatA( )함수에서 0x402000주소의 값은 "L2C-5781vguv4562-ABEX"이다.
▶ 이유는 "L2C-5781" + "L2C-5781vguv4562-ABEX"를 했기 때문이다.
[23] 확인한 시리얼 값 입력
▶ [22]의 결과로 얻은 시리얼 값을 넣는다.
[24] 결과 확인
▶ 정상적인 시리얼 값이 입력되었음을 확인할 수 있다.
▶ 시리얼키 구하는 알고리즘은 아래와 같이 구할 수 있다.
+ ① [현재 파일이 위치하는 최상위디렉터리의 볼륨명] + "4562-ABEX"
+ ② [위에서 연산한 문자열]의 1~4번째 인덱스의 문자는 2씩 더해준다.
+ ③ "L2C-5781" + [위에서 연산한 문자열]
6) 원본소스 가복원
<<Event_start>>
eax =KERNEL32.GetModuleHandleA(0);
USER32.DialogBoxParamA(eax, 1, 0, 0x401029, 0);
<<Event_check>>
eax = USER32.GetDlgItemTextA(hWnd, 0x68, 0x402324, 0x25);
KERNEL32.GetVolumnInformationA(NULL, 0x40225C, 0x32, 0x402194, 0x402190, 0x4020C8, NULL, NULL);
KERNEL32.lstrcatA(0x40225C, 0x4023F3);
for (int i = 2; i >0; i--) {
[0x40225C]++;
[0x40225D]++;
[0x40225E]++;
[0x40225F]++;
}
KERNEL32.lstrcatA(0x402000, 0x4023FD);
KERNEL32.lstrcatA(0x402000, 0x40225C);
eax = KERNEL32.lstrcmpiA(0x402000, 0x402324);
if (eax == 0) {
USER32.MessageBoxA(hWnd, "Yep, you entered a correct serial!", "Well Done!", MB_OK);
}
else {
USER32.MessageBoxA(hWnd, "The serial you entered is not correct!", "Error!", MB_OK);
}
<<Event_OK>>
USER32.EndDialog(hWnd, 0);
KERNEL32.ExitProcess(0);
7) C언어 코드로 복구
[1] dialog.rc
[2] resource.h
#define IDD_DIALOG1 101 #define IDC_EDIT1 1001
[3] C소스파일.c
#include<stdio.h> #include<Windows.h> #include "resource.h" char buffer[100]; BOOL CALLBACK dlg_proc(HWND hdlg, UINT msg, WPARAM wParam, LPARAM lParam) { int eax; switch (msg) { case WM_COMMAND: switch (LOWORD(wParam)) { case IDOK: //IDOK는 확인을 눌렀을 때 eax = GetDlgItemTextA(hdlg, IDC_EDIT1, buffer, 0x25); //IDC_EDIT1은 시리얼 키를 입력한 공간의 식별번호이다. printf("입력한 문자열 길이 : %d \n", eax); EndDialog(hdlg, 0); break; } break; } } int main() { int eax; char first_buffer[] = "4562-ABEX"; char second_buffer[] = "L2C-5781"; char save[100]; save[0] = NULL; char VolumeNameBuffer[100]; char pVolumeSerialNumber[100]; int pMaxFilenameLength; int pFileSystemFlags; eax = GetModuleHandleA(NULL); DialogBoxParamA(eax, MAKEINTRESOURCE(IDD_DIALOG1), 0, dlg_proc, 0); //MAKEINTRESOURCE(IDD_DIALOG1)는 불러올 대화상자의 식별번호이다. GetVolumeInformationA(NULL, VolumeNameBuffer, 0x32, pVolumeSerialNumber, &pMaxFilenameLength, &pFileSystemFlags, NULL, NULL); lstrcatA(VolumeNameBuffer, first_buffer); printf("문자열 더한 결과 1 : %s \n", first_buffer); for (int i = 2; i > 0; i--) { (*(first_buffer))++; (*(first_buffer + 1))++; (*(first_buffer + 2))++; (*(first_buffer + 3))++; } printf("반복문 이후 결과 : %s \n", first_buffer); lstrcatA(save, second_buffer); printf("문자열 더한 결과 2 : %s \n", save); lstrcatA(save, first_buffer); printf("문자열 더한 최종결과 : %s \n", save); eax = lstrcmpiA(save, buffer); printf("최종 결과와 비교할 대화상자에서 입력한 문자열 : %s \n", buffer); if (eax == 0) { MessageBoxA(NULL, "Yep, you entered a correct serial!", "Well Done!", MB_OK); } else { MessageBoxA(NULL, "The serial you entered is not correct!", "Error!", MB_OK); } return 0; }
[4] 복구한 C언어 코드로 실행
▶ 대화상자에 시리얼 키 입력
▶ 일치할 경우 위와 같이 출력되며 소스복원에 어느정도 성공했음을 알 수 있다.
▶ printf문은 시각적으로 보여주기 위해 추가하였으므로 주석처리해도 무방하다.
'Try Attack > Reverse Engineering[basic]' 카테고리의 다른 글
Packing/Unpacking (0) | 2018.12.19 |
---|---|
Code Caving (0) | 2018.12.18 |
[crackme3] abex' 3rd crackme 풀이 및 복원 (0) | 2018.11.08 |
[crackme2] abex' 2nd crackme 풀이 및 복원 (0) | 2018.11.07 |
[crackme1] abex' 1th crackme 풀이 및 복원 (0) | 2018.11.07 |
댓글