본문으로 바로가기

파일의 IT 블로그

  1. Home
  2. 보안 강좌/CrackMe
  3. [Solution] Abex' CrackMe #5 풀이 [완결]

[Solution] Abex' CrackMe #5 풀이 [완결]

· 댓글개 · KRFile

안녕하세요 파일입니다~ CrackMe 푸는게 너무 재밌어서 ㅎㅎ 계속 폭주해서 글을 작성하다보니 CrackMe 글만 몇일만에 벌써 4개를 쓰게 되었네요. 오늘은 대방의 Abex CrackMe #5번 마지막 문제입니다. 이번 문제도 사실은 그렇게 난이도가 어렵지 않습니다. 또 문자열 위주로만 볼것이기 때문에 올리디버거만 사용하도록 하겠습니다.

 

 

abex' crackme5.exe
0.01MB

다운로드는 여기서 하시면 됩니다~

 

실행

우선 이번 크랙미 #5번의 경우엔 델파이로 만들어져 있습니다. 마지막 되서 잡소리로 작성하자면.. 델파이 역시 VB6.0 처럼 옛날에 프로그래밍 할 때 많이 사용하던 추억의 언어입니다. 요새는 사용되는걸 거의 못 본 거 같네요 ㅎㅎ 예전에 어둠의 공간에서 게임핵 만들때 많이 사용된 언어였던거 같은데..

 

이미지 출처 : https://www.flaticon.com/kr/free-icon/delphi_5968252

참고로 델파이 공식 아이콘은 이렇게 생겼습니다. 바이너리 파일엔 이 아이콘이 꼭 포함되어 있진 않았던 거 같은데.. 제가 델파이로 프로그램 만들어 본게 7년전에 딱 3번 정도라 잘 기억이 안나네요 죄송합니다 ㅎㅎ

어쨌던 연식이 오래된 녀석이지만 아직도 델파이 구버전으로 프로그램을 만드는 분이 있을정도로 나름 잘 만들어진(?) 언어라고 생각합니다.

 

실행하면 또 제품키 맞추는 창이 뜹니다. 

 

실패하면 에러 메세지가 뜨는데요. 이번건 실패하면 프로그램을 자동 종료시켜 버립니다. 화나네요.

이번 풀이도 저번 크랙미 #4번 하고 비슷할 거 같은데요. 아마 시리얼 확인시 문자열 비교 함수를 찾아서 BP를 걸고 그때 참조되는 문자열을 훔쳐보면 끝나지 않을까 싶습니다.

 

맨날 하던대로 올리디버거로 열고, 참조된 문자열을 찾아봅시다.

 

이번엔 찾아지는 문자열이 거의 없군요. 또 4562-ABEXL2C-5781 같은 시리얼로 추정되는 텍스트가 보입니다. 더블클릭해서 문자열이 참조되고 있는 코드 라인으로 이동합니다.

 

4562-ABEX와 L2C-5781이 사용되는 위치로 이동했습니다.

여기서 감을 잘 못찾겠으면 Intermodular calls 를 통해 문자열 비교 함수를 찾아봐서 BP를 걸어주셔도 됩니다만. 여기서 코드를 아래로 내려보면 수상한 함수 이름을 찾아보실 수 있을겁니다.

 

코드를 조금 내려보면 또 lstrcmpiA 라는 문자열 비교 함수를 호출하고 있습니다.

사실 이번건 비주얼 베이직 프로그램 처럼 추정할 필요도 없는게 윈도우32 API 함수입니다.

비주얼 베이직에서 호출하는 vba로 시작하는 함수들은 인터넷에 찾아도 정보가 거의 안나오거든요 -.-

 

lstrcmpiA : https://learn.microsoft.com/ko-kr/windows/win32/api/winbase/nf-winbase-lstrcmpia

함수 설명 : 두 문자열을 비교합니다. 대/소문자를 구분하지 않고 비교합니다.

대/소문자를 구분하는 비교를 수행하려면 lstrcmp 함수를 사용합니다.

 

어쨌던 저게 문자열 비교 함수인가보네요. 또 밑으로 내리면 에러, 성공 메세지가 있는걸로 보아서 여기 위쪽에서 strcmp로 시리얼이 맞는지 비교하고, 이 값을 활용해 성공 또는 실패 메세지로 분기하는걸로 추정됩니다.

저 lstrcmpiA 호출 부분에 F2를 눌러서 Break Point를 걸어주겠습니다.

 

 

 

디버깅을 켜서 프로그램을 실행해주고.. Check를 눌러서 시리얼을 체킹하는 코드를 실행되도록 해봅시다.

 

제가 BP를 걸어놓은 부분에 실행이 딱 멈췄고, 이제 lstrcmpiA 가 비교하는 저 00402324 와 00402000 주소에는 우리가 입력값으로 제공한 Enter your serial 과 실제 시리얼로 추정되는 문자 L2C-5781JFF"500G4562-ABEX 를 비교하고 있습니다. 뭐 지금까지 했다면 알겠지만 L2C-5781JFF"500G4562-ABEX 이게 시리얼 키겠죠?

 

이 값은 어떻게 만들어졌나 보니 위에 lstrcatA이라는 윈도우 API를 호출해서 L2C-5781 하고 뒤에 정체 불명의 문자열 JFF"500... 을 합쳐서 만들어 졌군요. L2C-5781 은 그냥 이 프로그램 만드신 분이 db 명령어 같은걸로 이미 값을 메모리에 작성하고 쓴 것인데, 뒤에 JFF"500 이라는 문자열은 이 프로그램이 어떤 알고리즘(규칙)에 의해 생성한 것입니다.

 

일단 디버깅을 멈추고 값을 입력해보니깐 맞았다고 나오네요. 물론 이 프로그램은 항상 시리얼 키가 동일한게 아니고, 어떤 규칙에 의해 시리얼 키를 생성합니다. crackme #2번 처럼요. 이렇게 끝내긴 아쉬우니깐 이 프로그램이 어떻게 시리얼 키를 만들고 있는지 확인해 볼겁니다.

 

코드를 위로 좀 올려보면 GetDlgItemTextA라는 윈도우 API를 호출하고 있는데요. 이게 우리가 입력한 시리얼 값 Enter your serial 이라는 문자열을 얻어내는 코드입니다. 저곳에 BP를 걸고 한 줄씩 실행해보면서 시리얼 키를 어떻게 만드는지 문자열을 변화를 살펴보면 됩니다.

 

이전에 설명드렸듯이 올리디버거가 어떤 주소가 문자열을 가리키고 있으면 오른쪽에 ASCII "문자열" 이라는 형태로 문자열 값을 같이 보여주기 때문에 어렵지 않게 프로그램이 어떤 문자열을 만들고 있는지 추적할 수 있습니다.

F8 (Step Over) 을 눌러주면서 오른쪽에 문자열이 어떻게 변해가는지 확인해보겠습니다.

 

F8로 계속 실행하다가, GetVolumeInformationA 함수를 호출한 순간 램의 0040225C 주소 위치에는 "HDD 500G" 라는 문자열이 저장 됩니다. (004023F3 에는 이미 4562-ABEX 라는 값이 세팅되어 있었습니다.)

 

저 HDD 500G 라는 이름은 제가 따로 설정해둔 F드라이브의 디스크 이름입니다.

왜 C도 아니고 D도 아니고 굳이 F냐 하면 제가 CrackMe 5번을 F 디스크에서 실행하고 있기 때문입니다.

 

결론적으로 GetVolumeInformationA 를 활용하며 자신이 실행되고 있는 디스크의 디스크 이름을 가져와서 0040225C 에 저장하고 있습니다.

 

그리고 lstrcatA를 호출하면 HDD 500G 문자열 뒤에 미리 저장해뒀던 4562-ABEX 문자열을 뒤에 이어다 붙입니다.

결론적으로 40225C 주소에는 HDD500G4562-ABEX 라는 문자열이 세팅됩니다.

 

이제 다음으로 MOV DL, 2를 이용해 EDX 레지스터 안에 DL 위치[하위 1바이트]에 16진수 2를 쓸 차례인데요. 아직 MOV DL,2를 실행하진 않았지만 현재 EDX 값이 000...A 이기 때문에 뒤에 0A에 02를 쓰면 결론적으로 EDX의 값은 000..2 , 즉 16진수 2가 저장될 겁니다.

 

사실 EDX에 그냥 2써도 되는데 통쨰로 쓰는거 보단 DL에 쓰는게 더 효율적이라 이렇게 어셈블리 코드가 생성되지 않았나 싶네요.

 

왜 EDX에 2를 세팅하냐 하면 for문의 loop 카운트로 이용하기 위해 세팅한다고 생각해주시면 됩니다. 보시면 DL이 0이 될때까지 JMP를 하면서 반복을 하는데요. 총 2회 반복을 하게 됩니다. for(int i = 0; i < 2; i++) 에서 이 2가 EDX값이라고 생각하면 됩니다.

 

"HDD 500G4562-ABEX" 라는 문자열이 저장되어 있는 40225C 위치에 1을 더하고, 그 다음 글자 주소인 40225D 위치에 다시 1을 더하고.. 결론적으론 아까 만들어둔 "HDD500G4562-ABEX" 의 앞 4글자에 ASCII 코드를 1씩 더해서 다음 문자로 이동시키는 행위를 2번 한다고 보면 되겠습니다.

 

그러니깐 결론적으론 "HDD " 라는 4글자(공백 포함)에 각 자리수에 2를 더함으로써 ASCII 코드를 증가시키고 있는게 되겠군요. 아스키코드는 기본적으로 알파벳 순서기 때문에 H에 2를 더하면 알파벳 순으로 H 문자의 2번째 뒤 H->I->J 즉, J가 되겠고, D는 D->E->F 가 되겠고.. 이런식으로 앞 4글자가 변하게 되겠습니다.

 

LOOP가 끝나면 0040225C 위치의 문자열은 JFF"500G4562-ABEX가 됩니다. 재미있죠?

 

최종적으론 아까 세팅해둔 L2C-5781을 JFF"500G4562-ABEX와 결합시키면서 완성된 시리얼이 바로 L2C-5781JFF"500G4562-ABEX 였습니다. 재미있죠 ㅎㅎ 아니 재미없어

 

그러니깐 시리얼키 알고리즘의 결론은?

1. 현재 실행중인 드라이브 이름 문자열을 저장한다.

2. 문자열의 앞 4글자의 ASCII 코드를 각 2씩 증가시킨다.

3. 앞 뒤로 L2C-5781 와 4562-ABEX 를 붙여준다.

 

가 됩니다.

프로그램의 비밀을 완벽히 알았군요 

GOOD~

=> 참고로 드라이브 이름이 따로 설정되지 않은 경우 빈 문자열이 GetVolumeInformationA에서 리턴되기 때문에 0040225C 에는 lstrcatA에 의해 빈 문자열 + 4562-ABEX 문자열이 들어가 4562-ABEX 가 들어가게 되고 여기서 ASCII 코드가 2씩 증가하게 됩니다. 

위 설명의 경우는 드라이브 이름이 설정되었다는걸 가정으로 설명한 규칙입니다.

 

C언어로 제작한 시리얼 키 생성

#include <stdio.h>
#include <windows.h>

char *get_serial(char *drive)
{
    char front[] = "L2C-5781";
    char back[] = "4562-ABEX";

    static char result[50] = {
        0,
    }; // must be static or global (not local)

    char volume_name_buf[50] = {
        0,
    };

    GetVolumeInformationA(drive, volume_name_buf, 0x32u, 0, 0, 0, 0, 0);
    strcat(volume_name_buf, back);

    volume_name_buf[0] += 2;
    volume_name_buf[1] += 2;
    volume_name_buf[2] += 2;
    volume_name_buf[3] += 2;

    strcat(result, front);
    strcat(result, volume_name_buf);

    return result;
}

int main()
{
    printf("Serial: %s", get_serial("F:\\"));
    return 0;
}

>>> L2C-5781JFF"500G4562-ABEX

CrackMe 5번 시리얼키 생성 규칙대로 시리얼키를 자동 생성해주는 프로그램을 C언어로 작성해봤습니다.

어쨌던 Abex CrackMe 시리즈는 여기가 끝입니다~ 나중에 다른 문제 풀면 풀이 올려보도록 하겠습니다.

 

그리고 또 중간 중간 술마시면서 글을 재밌어서 급하게 작성하다보니 오타나 설명에 오류가 있을 수 있는데 댓글로 피드백 주시면 감사하겠습니다~ 도움이 되셨길 바랍니다 ^^

SNS 공유하기
💬 댓글 개
이모티콘창 닫기
울음
안녕
감사해요
당황
피폐

이모티콘을 클릭하면 댓글창에 입력됩니다.