| 일 | 월 | 화 | 수 | 목 | 금 | 토 |
|---|---|---|---|---|---|---|
| 1 | 2 | 3 | 4 | |||
| 5 | 6 | 7 | 8 | 9 | 10 | 11 |
| 12 | 13 | 14 | 15 | 16 | 17 | 18 |
| 19 | 20 | 21 | 22 | 23 | 24 | 25 |
| 26 | 27 | 28 | 29 | 30 |
- __vectorcall
- 32bit
- __fastcall
- crackme
- 코드엔진
- 프로그래머스
- 크랙미
- x32
- Calling Convention
- Programmers
- __stdcall
- Python
- 함수 호출 규약
- Reversing
- 실행파일
- 리버싱
- 리치헤더
- CodeEngn
- __cdecl
- Image dos header
- RVA
- Rich Header
- stack frame
- x64
- rev
- image section header
- 파이썬
- Dos Stub
- ABI
- pe format
- Today
- Total
kj0on
abex' crackme 3 상세분석 본문
0. 실행환경
0-1. 운영체제 (1)

0-2. 툴 (2)


1. 파일

2. 프로그램 동작

프로그램 실행 시 처음 나타나는 메시지는 위와 같다. OK버튼을 클릭 시 keyfile을 검사한다는 문구가 출력된다.

file을 찾을 수 없다는 메시지가 출력된다. 따라서 해당 프로그램은 키파일을 검사한 뒤 특정 조건에 따라 조건부 분기가 이루어 진다는 사실을 추측할 수 있다.
3. Detect It Easy

32비트 실행파일, 델파이로 작성된 프로그램인 것을 알 수 있다.
4. 분석
4-1. EntryPoint

x32dbg에서 실행한 뒤 EntryPoint는 위와 같다. abex' crackme 1과 비슷하게 코드의 길이가 매우 짧다는 것을 알 수 있다.
4-2. MessageBoxA

첫번째 팝업을 띄워주는 부분이다. MessageBoxA 함수를 호출하기 전 4개의 인자를 스택에 push해주는 걸 알 수 있다.
int MessageBoxA(
[in, optional] HWND hWnd,
[in, optional] LPCSTR lpText,
[in, optional] LPCSTR lpCaption,
[in] UINT uType
);
함수의 동작을 파악하기 위해 MSDN에서 MessageBoxA 함수를 확인했다(https://learn.microsoft.com/ko-kr/windows/win32/api/winuser/nf-winuser-messageboxa).

매개변수의 의미는 위와 같다.
| 매개변수 | 값 | 의미 |
| hWnd | 0x0 | 메시지 상자에 소유자 창이 없음 |
| IpText | 0x402012 | 표시할 메시지의 텍스트 데이터 주소는 0x402012 |
| IpCaption | 0x402000 | 대화 상자 제목의 텍스트 데이터 주소는 0x402000 |
| uType | 0x0 | 메시지 상자의 표시 단추의 플래그는 0x0 |
abex' crackme1.exe에서 MessageBoxA에 사용되는 인자의 값과 의미를 매칭하면 위와 같다.

hWnd가 0x0이기 때문에 메시지 상자에 소유자 창이 없다. 이는 부모 창의 핸들값이 없다는 것을 의미하고 첫 실행동작에서 알 수 있듯이 해당 메시지 창이 독립적으로 띄워진다는 의미다.

표시할 메시지의 텍스트 데이터는 0x402012에 있다. 해당 위치로 가보면 메시지 창의 메시지를 알 수 있다.

대화 상자 제목의 텍스트 데이터는 0x402000에 있다. 해당 위치로 가보면 대화 상자 제목의 메시지를 알 수 있다.

uType값이 0x0이기 때문에 표시 단추 플래그는 위와 같다. 해당 값으로 인해 확인 버튼 하나만 나타난다.

반환값은 위와 같다. 확인버튼 하나만 있기 때문에 버튼을 누를 시 1이 반환된다.
4-3. CreateFileA

메시지를 띄우고 난 뒤 CreateFileA 함수가 호출된다. 함수를 호출하기 전 7개의 인자를 스택에 push해주는 걸 알 수 있다..
HANDLE CreateFileA(
[in] LPCSTR lpFileName,
[in] DWORD dwDesiredAccess,
[in] DWORD dwShareMode,
[in, optional] LPSECURITY_ATTRIBUTES lpSecurityAttributes,
[in] DWORD dwCreationDisposition,
[in] DWORD dwFlagsAndAttributes,
[in, optional] HANDLE hTemplateFile
);
함수의 동작을 파악하기 위해 MSDN에서 CreateFileA 함수를 확인했다(https://learn.microsoft.com/ko-kr/windows/win32/api/fileapi/nf-fileapi-createfilea). 매개변수에 대한 설명이 매우 길기 때문에 인자에 대한 자세한 설명은 생략했다.
| 매개변수 | 값 | 의미 |
| lpFileName | "abex.l2c" | 만들거나 열 파일 또는 디바이스의 이름은 "abex.l2c"이다. |
| dwDesiredAccess | 0x80000000 | GENERIC_READ(읽기 권한)을 뜻한다. |
| dwShareMode | 0x00 | 함수 호출이 성공하면 파일 또는 디바이스를 공유할 수 없으며 파일 또는 디바이스에 대한 핸들이 닫혀 있을 때까지 다시 열 수 없다. |
| lpSecurityAttributes | 0x00 | 반환된 핸들은 애플리케이션이 만들 수 있는 자식 프로세스에서 상속할 수 없으며 반환된 핸들과 연결된 파일 또는 디바이스는 기본 보안 설명자를 가져온다. |
| dwCreationDisposition | 0x03 | 파일이 있는 경우에만 파일 또는 디바이스를 연다. 지정된 파일 또는 디바이스가 없으면 함수가 실패하고 마지막 오류 코드가 ERROR_FILE_NOT_FOUND(2)로 설정된다. |
| dwFlagsAndAttributes | 0x80 | 파일에 다른 특성 집합이 없다. 이 특성은 단독으로 사용하는 경우에만 유효하다. |
| hTemplateFile | 0x00 | 템플릿 파일에 대한 유효한 핸들이다. 템플릿 파일은 생성되는 파일에 대한 파일 특성 및 확장 특성을 제공한다. 기존 파일을 열 때 이 매개 변수를 무시합니다. 암호화된 새 파일을 열 때 파일은 부모 디렉터리에서 임의 액세스 제어 목록을 상속한다. |
abex' crackme2.exe에서 CreateFileA에 사용되는 인자의 값과 의미를 매칭하면 위와 같다. 한줄로 요약하면 "파일 또는 디바이스가 있는 경우 읽기 권한을 부여하여 "abex.l2c"을 연다. 공유, 상속을 할 수 없으며 특성 집합이 없다."가 된다.

함수 호출이 완료되면 핸들 또는 에러코드를 반환한다.
4-4. 에러처리

반환 값을 0x4020CA에 담는다. 이후 반환값을 비교하는데 INVALID_HANDLE_VALUE(0xFFFFFFFF)일 경우 에러메시지를 출력하는 코드로 조건부 분기가 이루어 진다.

INVALID_HANDLE_VALUE(0xFFFFFFFF)일 경우 해당 코드로 분기한다.
4-5. GetFileSize

핸들 값이 정상이라면 GetFileSize함수가 호출된다.
DWORD GetFileSize(
[in] HANDLE hFile,
[out, optional] LPDWORD lpFileSizeHigh
);
함수 원형은 위와 같다(https://learn.microsoft.com/ko-kr/windows/win32/api/fileapi/nf-fileapi-getfilesize#syntax).
| 인자 | 값 | 의미 |
| hFile | 0x4020CA | 파일에 대한 핸들 값으로 해당 주소는 CreateFileA의 반환 값이다. |
| lpFileSizeHigh | 0x00 | 파일 크기의 상위 doubleword가 반환되는 변수에 대한 포인터입니다. 애플리케이션에 상위 이중 단어가 필요하지 않은 경우 NULL이다. |
abex' crackme2.exe에서 GetFileSize에 사용되는 인자의 값과 의미를 매칭하면 위와 같다.

파일 크기를 반환한다. lpFileSizeHigh가 0x00이므로 하위 DWORD를 반환한다.
4-6. 조건부 분기

이후 GetFileSize의 반환 값(파일사이즈)와 0x12를 비교한다. 이후 ZF에 따라 조건부 분기가 이루어 진다.

ZF가 1일 경우 분기가 발생하지 않으면서 성공 메시지를 출력한다.

ZF가 0일 경우 분기가 발생하면서 실패 메시지를 출력한다.
4-7. ExitProcess

두 케이스 모두 메시지 출력 이후 jmp로 무조건 분기가 이루어 지면서 ExitProcess가 호출된다. 해당 함수로 프로세스를 종료한다.
5. 풀이
5-1. ZF 수정

조건부 분기가 발생하는 시점에서 ZF를 수정한다. "Yep, key file found!"의 메시지를 출력하도록 분기를 수정한다.
5-2. abex.l2c 파일 생성

CreateFileA에서의 인자값을 통해 "abex.l2c"의 파일이 존재할 때, 해당 파일을 열어서 핸들 값을 반환한다. 따라서 같은 위치에서 "abex.l2c" 파일을 생성해준다.

CreateFileA 호출 이후 핸들 값이 나타나는 것을 알 수 있다.

이후 GetFileSize를 호출한 뒤 레지스터 값을 보면 0x00이 반환된 것을 확인할 수 있다.

이유는 파일 사이즈가 0바이트 이기 때문이다.

파일 사이즈와 0x12(18)을 비교하기 때문에 "abex.l2c" 파일을 열고 18byte를 채워준다.

파일 저장 후 abex' crackme 3을 실행해 보면 성공 메시지가 출력된다.
5-3. 레지스터 값 수정

CreateFileA의 값을 0x4020CA에 mov하는 과정에서 EAX의 값을 보면 INVALID_HANDLE_VALUE인 것을 알 수있다. 해당 값을 INVALID_HANDLE_VALUE 아닌 값으로 수정한다(에러 처리 분기가 발생하지 않음).

GetFileSize 호출 이후 EAX의 값을 보면 0xFFFFFFFF가 나타난다. 그 이유는 핸들 인자로 아무 값을 넣었기 때문에 GetFileSize 호출 후 INVALID_HANDLE_VALUE가 반환되기 때문이다. cmp를 실행하기 전 EAX값을 0x12로 수정해준다(실패 메시지로 분기가 발생하지 않음).

성공 메시지가 출력된다.
5-4. nop 패치

조건부 분기가 발생하는 코드를 nop로 패치해준다.

이후 분기가 발생하지 않으면서 성공 메시지를 출력한다.
'Reversing > abex' crackme' 카테고리의 다른 글
| abex' crackme 5 상세분석 (3) | 2025.06.23 |
|---|---|
| abex' crackme 4 상세분석 (0) | 2025.06.22 |
| abex' crackme 2 상세분석 (0) | 2025.06.17 |
| abex' crackme 1 상세분석 (1) | 2025.06.17 |