분석 1
이번 프로그램은 딱히 뭔가가 없다.
File을 누르면 Exit 버튼이 전부이고
Help를 누르면 About 버튼이 전부인데 About 버튼을 누르면 위와 같이 뜬다.
분석 2
위의 사진은 EP 코드 부분인데, CreateFileA() 함수가 보인다.
// https://learn.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-createfilea
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
);
CreateFile() 함수의 정의는 위와 같고 새 파일을 만들거나 기존의 파일을 열 때 쓰는 함수이며, 인자 중 파일 이름 부분에는 CRACKME3.KEY 라는 파일 이름이 보인다.
그리고 401032 주소에서 함수의 반환값이 담긴 eax와 FFFFFFFF를 비교하는데 FFFFFFFF는 -1이다.
즉, CreateFile() 함수의 반환값이 -1로 파일 생성 혹은 불러오기에 실패했는지를 검사하는 것이다.
CreateFile 함수가 성공적이었다면 401043 주소로 점프한다.
CreateFile 함수의 반환값을 4020F5에 담고, eax에는 12h, ebx에는 402008 주소를 담는다.
그리고 ReadFile() 함수의 인자를 스택에 넣은 뒤 ReadFile() 함수를 호출한다.
그 후 401066 주소에서 12h와 4021A0 주소에 있는 4byte 데이터를 비교하는데
이 부분은 CRACKME3.KEY 라는 파일에서 읽은 데이터가 18글자인지 검사하는 부분이다.
(파일에서 읽은 데이터는 402008 주소에 담긴다.)
CRACKME3.KEY 파일 안의 데이터가 18글자라면 파일에서 읽어온 데이터를 암호화 하는 함수를 401074 주소에서 호출한다.
파일 안의 데이터를 가져와 암호화 하는 곳은 401311 주소의 함수이다.
해당 함수의 내부는 위와 같은데
- ecx와 eax를 0으로 초기화하고, esp+4(402008 == 파일에서 읽어온 18글자 데이터)를 esi에 넣고, bl 레지스터의 값을 41로 초기화한다.
- 그리고 esi 레지스터가 가리키는 데이터 중 1byte를 가져와 al에 담고, bl 레지스터의 값과 xor 연산한 뒤 다시 esi 레지스터가 가리키는 곳에 넣는다.
- esi를 증가함으로써 다음 글자를 가리키게 하고, bl도 증가시킨다.
- 그리고 1byte 데이터 즉, 글자와 bl의 값을 xor 연산한 값을 4020F9에 있는 데이터에 더하여 4020F9에 넣는다.
- xor 연산의 결과가 0이면 401335 주소로 점프하고, 연산 결과가 0이 아니면 ecx 레지스터의 cl 레지스터를 증가한 뒤 bl의 값이 4F인지 비교한다.
- bl의 값이 4F보다 작으면 40131B 주소로 점프하여 위의 2번부터 반복한다.
즉, 파일에서 가져온 문자열에서 문자 하나씩 가져와 bl의 값과 xor 연산한 뒤 해당 값을 가져온 문자 위치에 넣고, 402149 주소의 값에 계속 더하여 넣는다.
(단, bl의 값은 41부터 4F까지 계속 증가한다.)
그리고 xor 연산한 값이 0이 아니고 bl의 값이 4F가 아니면 cl 레지스터가 1씩 증가하는데 마지막으로 증가된 cl 레지스터의 값을 402149 주소에 넣은 뒤 함수를 종료한다.
그리고 나서 4020F9 주소의 4byte 값과 12345678을 xor 연산한 후 4020F9에 넣은 뒤
스택을 정리하고
402008(401074 주소에서 호출한 암호화하는 401311 주소의 함수를 호출한 뒤 암호화 된 데이터가 담긴 곳) 주소를 스택에 넣고 40108B 주소에서 40133C 주소의 함수를 호출한다.
40133C 주소의 함수 내부는 위와 같은데
402008 주소를 esi에 지정하고, esi 레지스터의 값에 E를 더함으로써 esi의 값은 402008에서 402016이 된다.
그리고 이 402016 주소의 데이터 4byte를 eax에 넣고 함수를 종료한다.
(즉, 파일에서 읽은 18글자 중 마지막 4글자에 해당하는 데이터를 eax에 넣는 것이다.)
그리고 나서 스택을 정리하고, eax의 값과 4020F9의 값을 비교한다.
sete 명령어는 Set if equal 라는 의미로 zero flag의 값이 1로 세팅되어 있으면 al 레지스터를 1로 설정하라는 것이다.
반대로 zero flag의 값이 0으로 세팅되어 있으면 al 레지스터를 0으로 설정한다.
eax의 값과 4020F9 주소의 값이 같아서 zero flag가 1로 세팅되었다면 al 레지스터를 1로 설정하고, 스택에 넣은 뒤 al의 값이 0인지 체크하여 0이라면 zero flag가 1로 세팅되므로 401037 주소로 점프하는데, al의 값이 1로 설정됐었기 때문에 zero flag가 0으로 세팅되고 40210E 주소를 스택에 넣은 뒤 4010A6 주소에서 401346 주소의 함수를 호출한다.
401346 주소의 함수 내부는 위와 같은데 Cracked!! 문자열이 완성되는 함수이다.
그리고 F8 키를 눌러 진행하다 보면 40118A 주소의 조건 분기가 나오는데 al과 1을 다르면 4011A3 주소로 점프하고 al의 값이 1과 같으면 40118C 주소로 이동하여 Cracked by : CodeEngn이 출력된다.
풀이
먼저 Cracked by : CodeEngn이 되기 위해서는 401074 주소에서 호출하는 401311 주소의 함수의 결과로 “CodeEngn” 문자열이 402008 주소에 담겨져야 한다.
그렇다면 순서가 파일에서 가져온 데이터 → 401311 주소의 함수에서 암호화 → “CodeEngn” 이다.
401311 주소의 함수에서는 파일에서 가져온 문자열의 각 문자를 41부터 4F까지의 값과 xor 연산한 결과를 402008 주소에 담는데, xor 연산의 특성 상 A xor B = C, B xor C = A 이므로 이 점을 이용하여 코드를 짠다.
#include <stdio.h>
#include <string.h>
int main(void)
{
char str[] = "CodeEngn";
for(unsigned int i = 0, al = 0, bl = 0x41; i < strlen(str) + 1; i++, bl++)
{
al = str[i] ^ bl;
printf("%02x ", al);
}
return 0;
}
result -> 02 2d 27 21 00 28 20 26 49
위의 코드를 돌리면 위와 같은 결과물이 나온다.
CRACKME3.KEY 파일을 만들어 대충 위와 같이 18글자를 적는다.
18글자여야 조건을 만족시킬 수 있다.
Hex Editor로 CRACKME3.KEY 파일을 열어 위와 같이 수정하고 저장한다.
그럼 위와 같이 401090 주소까지 실행한 후 402008 주소와 4020F9 주소의 값을 보면 바뀌어있다.
40108B 주소에서 호출하는 40133C 주소의 함수는 파일에서 읽어온 데이터 18글자 중 마지막 4글자에 해당하는 데이터를 eax에 담는다.
그리고 401093 주소에서 eax의 값과 4020F9의 값이 같아야 하므로 CRACKME3.KEY 파일을 hxd로 다시 한 번 열어 위와 같이 수정하고 저장한다.
그리고 20번 프로그램을 실행하면 위와 같이 뜬다.
인증
022D2721002820264900000000007B553412
'전쟁 > codeengn' 카테고리의 다른 글
[codeengn] Advance RCE 02 (0) | 2022.10.19 |
---|---|
[codeengn] Advance RCE 01 (0) | 2022.10.19 |
[codeengn] Basic RCE 19 (1) | 2022.10.14 |
[codeengn] Basic RCE 18 (0) | 2022.10.14 |
[codeengn] Basic RCE 17 (0) | 2022.10.12 |