리버싱 연습
lenas reversing 1
클리어 과정
Lenas reversing for newbies 라는
Tuts 4 You 홈페이지에서 Lena 라는 분이 올렸던 크랙미 파일들 중
첫번째에 해당하는 크랙미를 풀어보려고 한다.
실행시키자 마자 저런 메시지창을 띄우고 확인을 누르면 프로그램이 종료된다. 뭐지 싶었다.
올디로 열어보기로 했다.
아 그전에 어떤 언어로 이루어졌는지 ExeinfoPe로 한번만 살펴보겠다.
그냥 어셈블리어로만 작성되어 있는 것 같다.
올리디버거로 켜보았다.
첫 페이지부터 아까의 메시지창을 띄우는 함수호출 부분이 보일 정도로 가깝다.
아이콘을 로드하고, 커서를 로딩하는 함수를 사용하고, 그 다음에
CreateFileA라는 함수를 사용하는데, 이 함수는 winAPI에서 제공하는 함수인 것 같다.
함수를 호출하기 위해 집어넣는 파라메터들이 많은데,
>
이렇게 파라메터가 이루어져있다. 스택에 집어넣을 때 반대로 집어넣는 것을 유의하여 확인해보면 "Keyfile.dat", C0000000, 3, 0, 3, 0040216f에 있는 값, 0 를 스택에 넣는 모습이다.
일단 0040216f에 있는 값까지 알아보자.
push 하는 곳까지 실행시킨 뒤 0040216f을 덤프창에서 찾아보니 80 00 00 00이 나온다.
리틀엔디언을 빅 엔디언으로 바꾸면, 00000080(0x80)이 나온다.
각 파라메터의 의미는
파일 이름,
파일에 대한 권한 GENERIC_READ, GENERIC_WRITE,
파일에 대한 연속적 권한(다른 프로세스의 읽기/쓰기/열기),
자식 프로세스에 관한 권한 boolean(정확히 모르겠다),
파일의 존재 여부와 그에 대한 생성/삭제 여부,
파일에 대한 추가 속성 플래그,
GENERIC_READ가 설정되어 있을 때, 임시 파일 권한인지 여부(정확히 모르겠다)
정도로 보면 될 것 같다.
두 번째 파라메터는 GENERIC_READ, GENERIC_WRITE 이 두 가지가 있는데,
READ나 WRITE나 둘 다 c0000000값이 아닌 것으로 보인다. 마이크로소프트 홈페이지에서는 READ와 WRITE를 or 연산한 GENERIC_READ | GENERIC_WRITE 의 사용도 가능하다고 한다.
or 연산을 한 값을 사용한 것을 알 수 있다.
모든 파라메터의 값을 알았으니 함수 호출의 형태는 이러할 것이다.
각 파라메터를 헤더파일에 define으로 선언되어 있는 의미있는 단어들로 바꿨다. 각각의 뜻을 알고 싶다면 다시 한 번 이것을 보면 될 것 같다.
https://docs.microsoft.com/en-us/windows/desktop/api/fileapi/nf-fileapi-createfilea
이 함수의 리턴값은 파일을 다룰 수 있는 핸들러 포인터의 값을 반환시켜주는 것 같다.
CreateFileA함수를 호출해서 Keyfile.dat라는 이름을 가진 파일이 있으면 추가 다른 속성 없이 열고 없으면 에러를 발생시킨다.

올리디버거에서 실행시키는 위치에 Keyfile.dat 파일을 대충 만들고,
저 실행문까지 실행시켰다.
INVALID_HANDLE_VALUE를 의미하는 -1과 리턴값이 저장된 EAX를 비교해서 점프하게 되는데, 점프하지 않게 되면 아까의 purchase 메시지가 뜨고 프로그램이 종료하게 된다.
리턴된 값은 -1이 아니므로 점프를 할 것이다.
점프 후에 바로 인자 몇 개를 넣고 ReadFile이라는 함수를 호출한다.
말 그대로 파일을 읽는 함수같고, CreateFileA 함수에서 리턴된 hFile 값(파일의 핸들러 주소 또는 값)을 인자로 넣는 것을 보아 "Keyfile.dat" 파일을 읽으려고 하나보다.
그 후, 점프와 반복문이 보인다.
Test EAX, EAX연산을 통해 연산의 결과가 0이면 점프하지 않게 되어
그 다음 점프문으로 가서
Tuts 4 You 홈페이지에서 Lena 라는 분이 올렸던 크랙미 파일들 중
첫번째에 해당하는 크랙미를 풀어보려고 한다.
올디로 열어보기로 했다.
아 그전에 어떤 언어로 이루어졌는지 ExeinfoPe로 한번만 살펴보겠다.
그냥 어셈블리어로만 작성되어 있는 것 같다.
올리디버거로 켜보았다.
첫 페이지부터 아까의 메시지창을 띄우는 함수호출 부분이 보일 정도로 가깝다.
아이콘을 로드하고, 커서를 로딩하는 함수를 사용하고, 그 다음에
CreateFileA라는 함수를 사용하는데, 이 함수는 winAPI에서 제공하는 함수인 것 같다.
함수를 호출하기 위해 집어넣는 파라메터들이 많은데,
<
lpFileName, dwDesiredAccess, dwShareMode, lpSecurityAttributes, dwCreationDisposition, dwFlagsAndAttributes, hTemplateFile>
이렇게 파라메터가 이루어져있다. 스택에 집어넣을 때 반대로 집어넣는 것을 유의하여 확인해보면 "Keyfile.dat", C0000000, 3, 0, 3, 0040216f에 있는 값, 0 를 스택에 넣는 모습이다.
일단 0040216f에 있는 값까지 알아보자.
push 하는 곳까지 실행시킨 뒤 0040216f을 덤프창에서 찾아보니 80 00 00 00이 나온다.
리틀엔디언을 빅 엔디언으로 바꾸면, 00000080(0x80)이 나온다.
각 파라메터의 의미는
파일 이름,
파일에 대한 권한 GENERIC_READ, GENERIC_WRITE,
파일에 대한 연속적 권한(다른 프로세스의 읽기/쓰기/열기),
자식 프로세스에 관한 권한 boolean(정확히 모르겠다),
파일의 존재 여부와 그에 대한 생성/삭제 여부,
파일에 대한 추가 속성 플래그,
GENERIC_READ가 설정되어 있을 때, 임시 파일 권한인지 여부(정확히 모르겠다)
정도로 보면 될 것 같다.
두 번째 파라메터는 GENERIC_READ, GENERIC_WRITE 이 두 가지가 있는데,
READ나 WRITE나 둘 다 c0000000값이 아닌 것으로 보인다. 마이크로소프트 홈페이지에서는 READ와 WRITE를 or 연산한 GENERIC_READ | GENERIC_WRITE 의 사용도 가능하다고 한다.
모든 파라메터의 값을 알았으니 함수 호출의 형태는 이러할 것이다.
각 파라메터를 헤더파일에 define으로 선언되어 있는 의미있는 단어들로 바꿨다. 각각의 뜻을 알고 싶다면 다시 한 번 이것을 보면 될 것 같다.
https://docs.microsoft.com/en-us/windows/desktop/api/fileapi/nf-fileapi-createfilea
이 함수의 리턴값은 파일을 다룰 수 있는 핸들러 포인터의 값을 반환시켜주는 것 같다.
CreateFileA함수를 호출해서 Keyfile.dat라는 이름을 가진 파일이 있으면 추가 다른 속성 없이 열고 없으면 에러를 발생시킨다.
올리디버거에서 실행시키는 위치에 Keyfile.dat 파일을 대충 만들고,
저 실행문까지 실행시켰다.
INVALID_HANDLE_VALUE를 의미하는 -1과 리턴값이 저장된 EAX를 비교해서 점프하게 되는데, 점프하지 않게 되면 아까의 purchase 메시지가 뜨고 프로그램이 종료하게 된다.
리턴된 값은 -1이 아니므로 점프를 할 것이다.
점프 후에 바로 인자 몇 개를 넣고 ReadFile이라는 함수를 호출한다.
말 그대로 파일을 읽는 함수같고, CreateFileA 함수에서 리턴된 hFile 값(파일의 핸들러 주소 또는 값)을 인자로 넣는 것을 보아 "Keyfile.dat" 파일을 읽으려고 하나보다.
Test EAX, EAX연산을 통해 연산의 결과가 0이면 점프하지 않게 되어
그 다음 점프문으로 가서
이런 메시지창을 띄우는 실행문이 이루어진 뒤 프로그램이 끝나게 된다.
키파일이 유효하지 않다고 나온다.
위에선 ReadFile 함수를 호출하고 난 뒤 아래로 내려오는 점프문을 실행하지 않고 내려오게 된다.
그 후에 EBX와 ESI 레지스터를 각각 xor연산을 한 실행문으로 각각 0을 갖게 만든다.
그 다음 실행문으로 비교가 나오는데, 402173에 있는 값과 0x10을 비교한다. 그런데 저 주소는 무슨 값을 가지는지, 무슨 값의 의미인지.
위에서 ReadFile을 호출하기 전 인자를 넣을 때, 저 주소를 넣는 모습이 보이는데
코멘트에 의하면 pBytesRead 라는 뜻을 가진 인자로 들어가는 것 같다.
마이크로소프트 홈페이지에서 ReadFile 함수의 파라메터들을 살펴보면,
파일 핸들러,
읽은 문자열을 저장할 버퍼,
최대 읽을 문자열 길이,
실제로 읽어진 길이를 저장할 주소,
FILE_FLAG_OVERLAPPED 플래그 속성이 주어져 있을 때 덮어씌워도 되는지? 여부
로 이루어져 있고, 프로그램에서 인자를 넣은 것을 보면
Keyfile.dat의 핸들러 주소 또는 값, 문자열을 저장할 버퍼(길이가 31로 되어 있음), 최대 읽을 문자열 갯수 70, 읽은 문자열의 길이를 저장할 주소인 402173, NULL 을 넣는 것으로 해석하면 될 것 같다.
402173 주소에는 ReadFile 함수를 호출한 뒤 Keyfile.dat에서 읽어진 문자열의 길이를 가지고 있는 것이다.
Keyfile.dat를 만들었을 때 abcde를 넣었고, 그 문자열의 길이인 5가 저장되어 있는 것이 보인다.
분기문에서 문자열의 길이가 0x10보다 작으면 점프하게 되는데, 점프 후엔
다시 이 메시지창이 뜨면서 프로그램이 끝나는 것을 볼 수 있다.
키 파일에는 최소 16(0x10)자 이상 들어가야 하는 것으로 보인다.
키파일에 123456789ABCDEFG 를 넣고 다시 실행시켰다.
문자열의 길이가 16 이상이므로 점프하지 않고 내려오게 된다.
반복문이 시작되는데, xor연산하여 0이 되어있는 EBX에 문자열이 저장되어있는 40211a를 더한 주소의 값을 AL에 복사한다.
반복되어가면서 EBX를 증가시켜서 40211a + EBX 의 값을 참조해 가며 문자열의 문자를 하나씩 읽게 하는 실행문이다.
AL과 0을 비교해서 만약 0이 나오면(문자열의 끝이면) 점프로 반복문을 나가고, 아니면 0x47과 비교한다.
0x47은 아스키코드로 'G'인데, 만약 문자열의 N번째 문자가 G가 아니면 점프를 통해 EBX만 증가하고, G이면 ESI를 1 증가시킨 뒤 EBX도 증가하게 된다.
문자열을 담은 주소의 최대 크기는 31이지만 문자열의 현재 길이는 16이므로 16번 반복한 뒤 AL에 0의 값을 복사하게 되어 반복문을 나가게 될 것이다.
반복문을 나가게 되면 ESI와 8을 비교해서 8보다 적으면 점프하는데,
점프하게 되면 아까와 같은 메시지가 뜨고 종료된다.
위의 반복문에서 ESI를 증가시키는 조건은 문자열에서 N번째의 문자가
'G' 일 경우 1 증가였다.
Keyfile.dat에서 읽은 문자열에서 문자열의 길이가 16 이상이면서 문자 'G'가 8개 이상 있으면 조건을 만족하게 되는 것 같다.
최종적으로 8보다 적을 때 점프하지 않고 그 아래에 점프문을 통해서 온 곳은 프로그램이 만족하는 메시지가 나오게 호출하는 곳이 나온다.
Keyfile.dat에 작성된 내용을 수정하고 다시 실행시켰을 때 클리어를 할 수 있었다.
프로그램의 실행 순서 :
프로그램이 실행되는 곳에 Keyfile.dat 파일이 있으면 열고, 없으면 INVALID_HANDLE_VALUE를 리턴해 프로그램이 끝난다.
Keyfile.dat에서 파일을 읽어 최대 31개의 문자열이 들어가는 주소에 최대 70개의 문자열을 읽어 저장한다. 이 때 문자열의 길이가 반환된 주소는 402173 이다.
Keyfile.dat에서 읽은 문자열이 없으면 TEST EAX, EAX 연산으로 점프되어 프로그램이 종료된다.
Keyfile.dat에서 읽은 문자열의 길이와 0x10(16)을 비교해 16보다 적으면 점프되어 프로그램이 종료된다.
반복문을 통해 문자열의 주소에서 문자를 한 개씩 AL에 복사해서 읽어가며 AL이 0일 때 점프문을 나간다.
AL의 값과 0x47('G')를 비교해 문자가 'G'일 경우 ESI를 증가시킨다.
반복문을 끝내고 나와서 ESI에 저장된 값과 8을 비교해 8보다 적으면(문자열에서 'G'가 최소 8개) 점프되어 프로그램이 끝난다.
문자열에서 'G'가 최소 8개 이상 있으면 프로그램에서 원하는 출력이 나오고 프로그램이 종료된다.(클리어)
코드로 대충 적어보면 이런 식으로 생각하면 될 것 같다.
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
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
|
#include <Windows.h>
#include <stdio.h>
void Success();
void FailRead();
void FailKey();
int main()
{
char str[31];
int len;
char c = -1;
int i = 0;
int comp = 0;
HANDLE keyHandle = CreateFileA("Keyfile.dat",
(GENERIC_READ | GENERIC_WRITE),
3,
NULL,
OPEN_EXISTING,
FILE_ATTRIBUTE_NORMAL,
NULL);
if (keyHandle == -1)
{
FailRead();
return 0;
}
ReadFile(keyHandle, str, 70, len, NULL);
if (len < 16)
{
FailKey();
return 0;
}
while (c != 0)
{
c = str[i];
if (c == 'G') comp++;
i++;
}
if (comp < 8)
{
FailKey();
return 0;
}
Success();
return 0;
}
void Success()
{
MessageBoxW(NULL, L"Congratuation!!!", L"Yes", MB_OK);
}
void FailRead()
{
MessageBoxW(NULL, L"Purchase new license.", L"NO", MB_OK);
}
void FailKey()
{
MessageBoxW(NULL, L"Key is not Valid.", L"NO", MB_OK);
}
| cs |
댓글
댓글 쓰기