[Reversing] crackme #15 (2024)

툴 - IDA Free

문제 소개

이번 문제는 exe 실행파일과 함께 keyfile(.dat)도 함께 주어진다.

[Reversing] crackme #15 (1)

우선 keyfile을 준비하지 않은 상태로 exe 파일을 실행하면 "Your time-trial has ended... Please register and copy the keyfile sent to you to this directory!"이라며 keyfile을 exe 파일과 같은 디렉터리에 넣으라하고, 두 파일을 같은 디렉터리 내에 넣은 상태에서 실행하자 이번에는 "Your current keyfile is invalid... Please obbtain a valid one from the software author!"라며 값이 잘못되었단 실패 메시지가 뜬다.

아마 올바른 값을 넣어서 keyfile을 수정하고 exe 파일을 실행하면 자동으로 정답 메시지가 뜨면서 문제가 해결될 것 같다.

코드 분석

[Reversing] crackme #15 (2)

먼저 "due-cm2.dat" 파일을 읽어오는 loc_40109A으로 가보니 ReadFile이라는 함수가 보인다.

🔗 참고
https://learn.microsoft.com/ko-kr/windows/win32/api/fileapi/nf-fileapi-readfile

✏️ 매개변수

BOOL ReadFile( [in] HANDLE hFile, [out] LPVOID lpBuffer, [in] DWORD nNumberOfBytesToRead, [out, optional] LPDWORD lpNumberOfBytesRead, [in, out, optional] LPOVERLAPPED lpOverlapped);

매개변수는 순서대로 읽고자 하는 파일의 핸들, 데이터를 저장한 버퍼의 시작 주소, 읽고자 하는 데이터의 크기(byte), 읽은 바이트 수를 저장하는 변수, 비동기적인 데이터 전송을 나타낸다.
이름에서 알 수 있겠지만 지정된 파일 또는 I/O(입출력) 디바이스에서 데이터를 읽어오는 함수이며, 파일을 제대로 읽어왔다면 0이 아닌 값, 함수가 실패하거나 비동기적으로 완료되는 경우에는 0을 반환한다.

dat 파일에 "hello world!"란 값을 입력해두고, 실행시켜보면 위 함수가 실행되면서 데이터를 읽어와 byte_40211A에 저장한다. 실제 그 주소로 가보면 데이터가 담겨있는 것을 알 수가 있다. (Hex View 참고)

정상적으로 파일의 내용이 조건에 맞는지 확인해보기 위해선 loc_4010B4 부분으로 이동해야 한다. jnz 조건에 맞아야 하므로 test 명령어 실행 결과가 1을 반환하여 ZF가 0이 되어야 하기 때문에 keyfile을 같은 디렉터리 내에 존재하도록만 하면 될 것이다. 0을 반환할 경우 ZF가 1이 되어 실패 메시지를 출력하는 loc_4010F7로 점프한다.

지금까진 그저 keyfile의 존재 여부를 확인하고 저장된 데이터를 읽어와 버퍼에 저장하는 과정이었다. 올바른 데이터를 입력하여 성공 메시지를 출력하기 위해선 몇 가지 조건에 맞도록 keyfile 값을 수정해야 한다.

[조건1]loc_4010B4:xor ebx, ebx xor esi, esi cmp ds:NumberOfBytesRead, 12h jl short loc_4010F7

첫 번째로 파일에 읽어온 데이터의 크기가 12h보다 커야 한다. 여기서 12h는 16진수이므로 변환하면 18 byte가 된다.

12h(18)보다 작은 경우에는 jl 명령어 실행 조건에 해당되므로 실패 메시지를 출력하는 loc_4010F7로 점프한다.

이 부분은 데이터를 18자 이상으로 입력해주면 통과된다.

[조건2]loc_4010C1:mov al, ds:byte_40211A[ebx] cmp al, 0 jz short loc_4010D3 cmp al, 1 jnz short loc_4010D0 inc esiloc_4010D0:inc ebx jmp short loc_4010C1loc_4010D3:cmp esi, 2 jl short loc_4010F7 xor esi, esi xor ebx, ebxloc_4010DC: [조건3]에 작성

jl 명령어로 점프되지 않을 경우, 바로 이 부분으로 내려온다.
데이터가 저장된 ds:byte_40211A에 []가 붙어있으므로 아마 1 byte만 불러와 al에 넣는 것으로 생각된다.
그 1 Byte를 cmp 명령어로 비교하여 0과 같을 경우에는 ZF가 1이 되어 loc_4010D3로 점프한다. 이동한 경우 다시 cmp 명령어를 사용하여 esi를 2와 비교하고 2보다 작은 경우에는 실패 메시지를 출력하는 loc_4010F7로 점프한다.

al이 0과 다를 경우는 아래로 내려가 이번엔 1과 비교한다. cmp al, 1에서 al의 값이 1이 아닐 경우 ZF가 0이 되면서 loc_4010D0로 점프한다. 해당 위치에서는 ebx 값을 +1 해주고 loc_4010C1로 점프하는 것으로 보아 데이터의 다음 Byte 값을 al에 담아서 위의 과정을 반복하는 것으로 생각된다.

두 번의 cmp 이후에는 esi를 +1 해주는 코드가 나오고, 아래로 내려가 loc_4010D0 부분을 실행한다.

코드를 위에서부터 설명해서 순서가 좀 뒤죽박죽이지만 [조건2]는 하나의 loop 같다.
데이터의 1 Byte씩 al에 넣어서 값을 비교하며 esi < 2 && byte_40211A[ebx] == 0 이라면 에러 메시지 출력, esi > 2이면 loop를 끝내고 loc_4010DC로 간다.
알 수 있는 사실은 esi가 2보다 작은 상태일 때 al이 0이면 jz short loc_4010D3 부분으로 인해 실패 메시지가 있는 곳으로 점프되므로 데이터의 처음 두 값은 0이면 안된다.

두 번째 조건을 알았으므로 이제 다음 조건을 보겠다.

[조건3]loc_4010DC:mov al, ds:byte_40211A[ebx] cmp al, 0 jz short loc_4010EF cmp al, 1 jnz short loc_4010EF add esi, eax inc ebx jmp short loc_4010DCloc_4010EF: cmp esi, 1D5h jz short loc_401114loc_4010F7:실패 메시지 출력 ...

이번에도 데이터 값을 1 Byte씩 불러와 al에 넣는다. loc_4010C1와 비슷하게 cmp 명령어로 al과 0, 1을 비교하는데 여기서 0과 같거나(ZF = 1) 1과 다르면 (ZF = 0) loc_4010EF로 점프한다.

만약 점프하지 않을 경우에는 eax 값을 esi에 더하고, ebx에 +1을 해주어 다음 데이터의 1 Byte 값을 al에 넣어서 위 과정을 다시 반복해준다.

여기서 al은 eax 레지스터의 하위 8비트를 나타내는 부분이므로, eax는 al 앞에 상위 24비트가 붙은 형태가 된다. 만약 현재 al 값이 ‘E’라면, 아스키코드 값은 ‘0x45’이므로, eax는 0x00000045가 되어 esi에 들어가는 셈이다.

loc_4010EF에서는 esi와 1D5h를 비교하는데 두 값이 다르다면(ZF = 0) jz가 실행되지 않고 아래의 loc_4010F7로 내려가 실패 메시지가 출력되므로 두 값을 같도록하여 다음 조건 코드가 있는 loc_401114로 점프해야 한다.

정리하자면 al를 총 더한 값이 1D5h(469)가 되기 전까지 loop를 돌며 계속 esi에 더해주면 된다. 0x00이나 0x01를 제외하고 총합이 469만 되면 통과된다.

[조건4]loc_401114:xor esi, esiloc_401116:inc ebxmov al, ds:byte_40211A[ebx] cmp al, 0 jz short loc_401139 cmp al, 1 jz short loc_401139 cmp esi, 0Fh jnb short loc_401139 xor al, ds:byte_40211A[esi] mov ds:byte_40211A[esi], eax inc esi jmp short loc_401116loc_401139:inc ebx xor esi, esiloc_40113C: [조건5]에 작성

이 부분은 al을 0, 1과 비교만 할 뿐 위의 조건들처럼 메시지를 출력하는 곳으로 점프가 된다거나 하는 부분은 없다.
마찬가지로 데이터 값을 1 Byte씩 불러와 al에 넣고, cmp 명령어로 비교한다. 만약 al이 0, 1, 0Fh(15) 이상인 경우에는 loc_401139로 점프하여 다음 조건으로 넘어간다.
만약 모두 해당되지 않아 점프하지 못한 경우 al과 ds:byte_40211A[esi]를 xor한 후, 그 결과를 byte_40211A의 esi번째 위치에 넣고 esi에 +1하여 위 과정을 다시 반복한다.

[조건5]loc_40113C:mov al, ds:byte_40211A[ebx] cmp al, 0 jz short loc_40114F cmp al, 1 jnz short loc_40113C add esi, eax inc ebx jmp short loc_40113Cloc_40114F:cmp esi, 1B2h jnz short loc_4010F7

이제 마지막 조건이다. 마찬가지로 데이터 값을 1 Byte씩 불러와 al에 넣고, cmp 명령어로 al 값을 0, 1과 비교한다.

al이 0인 경우에는 loc_40114F로 점프하여 다시 한 번 esi를 1B2h(434)와 비교한다. 비교 결과 같지 않은 경우는 실패 메시지를 출력하는 loc_4010F7로 점프하기 때문에 esi값이 1B2h(434)가 되어야 한다.

al이 1이 아닌 경우 다시 loc_40113C 부분을 처음부터 실행하고, 1인 경우는 아까 [조건3]에서 했던 것처럼 eax 값을 esi에 더하고, ebx를 +1하여 다음 데이터의 1 Byte 값을 al에 넣어 위 과정을 다시 반복하여 수행한다.

정리하자면 마지막 조건은 esi 값이 정확히 1B2h(434)이 되도록 만들어주면 된다.

문제 해결

모든 조건을 정리하여 적어보면 18 Byte 이상의 값을 적어야 하며, 처음 2 Byte는 0x00, 0x01이 아닌 값이 들어있어야 한다. 그리고 첫 번째 0x01 이전 데이터의 합은 1D5h(469), 두 번째 0x01부터 파일이 끝날 때까지의 데이터 합이 1B2h(434)가 되도록 해주면 된다.

[Reversing] crackme #15 (3)

keyfile에 올바른 값을 넣고 exe 파일 실행 시, 이런 창이 뜨면 성공이다.

느낀 점

조건 하나만 틀려도 계속 실패 메시지가 나와서 구하는데 좀 많은 시간이 걸린 탓에 어제 못 올리고 오늘 올리게 되었다.
개인적으로 푸는데 가장 힘들었던 문제 였던 것 같다.. 마지막에 창이 뜰 때 입력란에 문장이 나오는 사람들도 있던데 난 왜 안되는지 잘 모르겠다.. 어거지로 푸는데 성공하긴 했지만 사실 아직 몇몇 부분은 잘 이해가 안 가서 한 번 더 분석해봐야 할 것 같다.

[Reversing] crackme #15 (2024)
Top Articles
Latest Posts
Recommended Articles
Article information

Author: Kerri Lueilwitz

Last Updated:

Views: 6199

Rating: 4.7 / 5 (47 voted)

Reviews: 86% of readers found this page helpful

Author information

Name: Kerri Lueilwitz

Birthday: 1992-10-31

Address: Suite 878 3699 Chantelle Roads, Colebury, NC 68599

Phone: +6111989609516

Job: Chief Farming Manager

Hobby: Mycology, Stone skipping, Dowsing, Whittling, Taxidermy, Sand art, Roller skating

Introduction: My name is Kerri Lueilwitz, I am a courageous, gentle, quaint, thankful, outstanding, brave, vast person who loves writing and wants to share my knowledge and understanding with you.