===============================
Crackme #1
===============================

딱 보니깐 간단하게 생겼죠?
패스워드 보일락 말락~ 말락 -_-..
Olly로 잠시 코드를 살펴 보죠.
004010FD . |68 F7204000 PUSH due-cm1.004020F7 ; |Buffer = due-cm1.004020F7
00401102 . |6A 01 PUSH 1 ; |ControlID = 1
00401104 . |FF75 08 PUSH DWORD PTR SS:[EBP+8] ; |hWnd
00401107 . |E8 55020000 CALL <JMP.&USER32.GetDlgItemTextA> ; \GetDlgItemTextA
0040110C . |33C0 XOR EAX,EAX
0040110E > |80B8 F7204000>CMP BYTE PTR DS:[EAX+4020F7],0
00401115 . |74 18 JE SHORT due-cm1.0040112F
00401117 . |80B0 F7204000>XOR BYTE PTR DS:[EAX+4020F7],43
0040111E . |80B0 F7204000>XOR BYTE PTR DS:[EAX+4020F7],1E
00401125 . |80B0 F7204000>XOR BYTE PTR DS:[EAX+4020F7],55
0040112C . |40 INC EAX
0040112D .^|E2 DF LOOPD SHORT due-cm1.0040110E
0040112F > |83F8 00 CMP EAX,0
00401132 . |75 18 JNZ SHORT due-cm1.0040114C
00401134 . |68 00200000 PUSH 2000 ; /Style = MB_OK|MB_TASKMODAL
00401139 . |68 01204000 PUSH due-cm1.00402001 ; |Title = "Duelist's Crackme #1"
0040113E . |68 9D204000 PUSH due-cm1.0040209D ; |Text = "Couldn't validate code, because it wasn't entered..."
00401143 . |6A 00 PUSH 0 ; |hOwner = NULL
00401145 . |E8 A5010000 CALL <JMP.&USER32.MessageBoxA> ; \MessageBoxA
0040114A .^|EB A8 JMP SHORT due-cm1.004010F4
0040114C > |6A 24 PUSH 24 ; /Arg3 = 00000024
0040114E . |68 D3204000 PUSH due-cm1.004020D3 ; |Arg2 = 004020D3
00401153 . |68 F7204000 PUSH due-cm1.004020F7 ; |Arg1 = 004020F7
00401158 . |E8 64000000 CALL due-cm1.004011C1 ; \due-cm1.004011C1
0040115D . |83F8 00 CMP EAX,0
00401160 . |74 1B JE SHORT due-cm1.0040117D
00401162 . |68 00200000 PUSH 2000 ; /Style = MB_OK|MB_TASKMODAL
00401167 . |68 01204000 PUSH due-cm1.00402001 ; |Title = "Duelist's Crackme #1"
0040116C . |68 17204000 PUSH due-cm1.00402017 ; |Text = "Congratulations! Please send your name/email/solution to duelist@beer.com!"
00401171 . |6A 00 PUSH 0 ; |hOwner = NULL
00401173 . |E8 77010000 CALL <JMP.&USER32.MessageBoxA> ; \MessageBoxA
쉽네요. 그냥 xor로 돌면서 암호화 시키는거 였습니다.
암호화된 결과가 {aexdm&kzikcem&<&fmjam{&jq&l}mda{| 가 되어야 하는군요.
간단한 코드를 작성하여 보았습니다.
#include <string.h>
{
char corret[] = "{aexdm&kzikcem&<&fmjam{&jq&l}mda{|";
char tmp;
{
for(char chr = 0x20; chr <= 'z'; chr++)
{
tmp = chr ^ 0x43;
tmp ^= 0x1E;
tmp ^= 0x55;
if(tmp == corret[i])
printf("%c",chr);
}
}
printf("\n");
return 0;
}
실행 시켜보면..

===============================
Crackme #2
===============================
이번에는 Keyfile을 찾는 문제라는군요.
처음 실행시키면 다음과 같이 keyfile 어쩌고 나오네요.

Olly로 Keyfile이 뭔지 살펴 보면..
0040105E . 68 6F214000 PUSH due-cm2.0040216F ; ||Attributes = READONLY|HIDDEN|SYSTEM|ARCHIVE|TEMPORARY|402048
00401063 . 6A 03 PUSH 3 ; ||Mode = OPEN_EXISTING
00401065 . 6A 00 PUSH 0 ; ||pSecurity = NULL
00401067 . 6A 03 PUSH 3 ; ||ShareMode = FILE_SHARE_READ|FILE_SHARE_WRITE
00401069 . 68 000000C0 PUSH C0000000 ; ||Access = GENERIC_READ|GENERIC_WRITE
0040106E . 68 79204000 PUSH due-cm2.00402079
; ||FileName = "due-cm2.dat"
00401073 . E8 0B020000 CALL <JMP.&KERNEL32.CreateFileA> ; |\CreateFileA
due-cm2.dat 라는 파일이군요.
요구조건을 살펴보죠.
004010C9 . 74 08 JE SHORT due-cm2.004010D3
004010CB . 3C 01 CMP AL,1
004010CD . 75 01 JNZ SHORT due-cm2.004010D0
004010CF . 46 INC ESI
004010D0 > 43 INC EBX
004010D1 .^ EB EE JMP SHORT due-cm2.004010C1
004010D3 > 83FE 02 CMP ESI,2
004010D6 . 7C 1F JL SHORT due-cm2.004010F7
004010D8 . 33F6 XOR ESI,ESI
004010DA . 33DB XOR EBX,EBX
004010DC > 8A83 1A214000 MOV AL,BYTE PTR DS:[EBX+40211A]
004010E2 . 3C 00 CMP AL,0
004010E4 . 74 09 JE SHORT due-cm2.004010EF
004010E6 . 3C 01 CMP AL,1
004010E8 . 74 05 JE SHORT due-cm2.004010EF
004010EA . 03F0 ADD ESI,EAX
004010EC . 43 INC EBX
004010ED .^ EB ED JMP SHORT due-cm2.004010DC
004010EF > 81FE D5010000 CMP ESI,1D5
004010F5 . 74 1D JE SHORT due-cm2.00401114
004010F7 > 6A 00 PUSH 0 ; |/Style = MB_OK|MB_APPLMODAL
004010F9 . |68 01204000 PUSH due-cm2.00402001 ; ||Title = "Duelist's Crackme #2"
004010FE . |68 86204000 PUSH due-cm2.00402086 ; ||Text = "Your current keyfile is invalid... Please obtain a valid one from the software author!"
00401103 . |6A 00 PUSH 0 ; ||hOwner = NULL
00401105 . |E8 5D020000 CALL <JMP.&USER32.MessageBoxA> ; |\MessageBoxA
0040110A . |E8 AA010000 CALL <JMP.&KERNEL32.ExitProcess> ; \ExitProcess
00이 나올떄 까지 루프를 도는데, 이때 01의 갯수가 2가 아니면 올바르지 않는
Keyfile로 인식합니다. 그러니까 00이 나오기 전에 반드시 01이 두개 있어야 겠죠.
그런데, 0x4010dc에 보면 파일의 첫번쨰 바이트를 al에 넣고 0이나 1이 아닌 동안,
esi에 계속 더하는 것을 볼 수 있고 그 더한 값이 0x1d5가 되어야 한다고 합니다.
0x1d5는 10진수로 469 이고 딱 떨어지게 나누려면 7으로 나누면 67(0x43)이 되겠죠.
0x43으로 7bytes를 넣어주고, 01 01을 넣어주면 이 루프는 통과하게 되겠네요.
0040111D . 3C 00 CMP AL,0
0040111F . 74 18 JE SHORT due-cm2.00401139
00401121 . 3C 01 CMP AL,1
00401123 . 74 14 JE SHORT due-cm2.00401139
00401125 . 83FE 0F CMP ESI,0F
00401128 . 73 0F JNB SHORT due-cm2.00401139
0040112A . 3286 1A214000 XOR AL,BYTE PTR DS:[ESI+40211A]
00401130 . 8986 60214000 MOV DWORD PTR DS:[ESI+402160],EAX
00401136 . 46 INC ESI
00401137 .^ EB DD JMP SHORT due-cm2.00401116
00401139 > 43 INC EBX
0040113A . 33F6 XOR ESI,ESI
0040113C > 8A83 1A214000 MOV AL,BYTE PTR DS:[EBX+40211A]
00401142 . 3C 00 CMP AL,0
00401144 . 74 09 JE SHORT due-cm2.0040114F
00401146 . 3C 01 CMP AL,1
00401148 .^ 74 F2 JE SHORT due-cm2.0040113C
0040114A . 03F0 ADD ESI,EAX
0040114C . 43 INC EBX
0040114D .^ EB ED JMP SHORT due-cm2.0040113C
0040114F > 81FE B2010000 CMP ESI,1B2
00401155 .^ 75 A0 JNZ SHORT due-cm2.004010F7
00401157 . 6A 00 PUSH 0 ; /lParam = NULL
00401159 . 68 C9114000 PUSH due-cm2.004011C9 ; |DlgProc = due-cm2.004011C9
0040115E . 6A 00 PUSH 0 ; |hOwner = NULL
00401160 . 6A 05 PUSH 5 ; |pTemplate = 5
00401162 . FF35 77214000 PUSH DWORD PTR DS:[402177] ; |hInst = NULL
00401168 . E8 42020000 CALL <JMP.&USER32.DialogBoxParamA>
; \DialogBoxParamA
01 01 다음의 byte를 0이 나올떄 까지 계속 더하네요.
0x1B2는 10진수로 434죠. 434 역시 7로 나누어 떨어집니다.
몫은 0x3E(62)가 되네요. 0x3E로 7개를 채워주고 그다음엔 00을 넣어주면 되겠네요.
그리고 한바이트 더 01을 넣어주면 끝입니다.
자 이렇게 해서 나온 keyfile을 같은 폴더에 넣고 대상을 실행시켜 보면..

===============================
Crackme #3
===============================
먼저 대상을 실행시킨,
Tip으로 리소스 에디터를 이용하는게 좋다네요.
그래서 리소스해커로 대상프로그램을 열어서 내용을 봤습니다.
CONTROL "Check", 1, BUTTON, BS_DEFPUSHBUTTON | WS_CHILD | WS_VISIBLE | WS_TABSTOP, 27, 32, 29, 14
CONTROL "", 97, BUTTON, BS_AUTOCHECKBOX | WS_CHILD | WS_VISIBLE | WS_TABSTOP, 5, 5, 8, 8
CONTROL "732", 73, BUTTON, BS_AUTOCHECKBOX | WS_CHILD | WS_VISIBLE | WS_TABSTOP, 17, 5, 8, 8
CONTROL "", 94, BUTTON, BS_AUTOCHECKBOX | WS_CHILD | WS_VISIBLE | WS_TABSTOP, 29, 5, 8, 8
CONTROL "", 22, BUTTON, BS_AUTOCHECKBOX | WS_CHILD | WS_VISIBLE | WS_TABSTOP, 41, 5, 8, 8
CONTROL "", 37, BUTTON, BS_AUTOCHECKBOX | WS_CHILD | WS_VISIBLE | WS_TABSTOP, 53, 5, 8, 8
CONTROL "", 38, BUTTON, BS_AUTOCHECKBOX | WS_CHILD | WS_VISIBLE | WS_TABSTOP, 65, 5, 8, 8
CONTROL "", 89, BUTTON, BS_AUTOCHECKBOX | WS_CHILD | WS_VISIBLE | WS_TABSTOP, 89, 5, 8, 8
CONTROL "", 33, BUTTON, BS_AUTOCHECKBOX | WS_CHILD | WS_VISIBLE | WS_TABSTOP, 77, 5, 8, 8
CONTROL "", 83, BUTTON, BS_AUTOCHECKBOX | WS_CHILD | WS_VISIBLE | WS_TABSTOP, 101, 5, 8, 8
CONTROL "", 21, BUTTON, BS_AUTOCHECKBOX | WS_CHILD | WS_VISIBLE | WS_TABSTOP, 5, 18, 8, 8
CONTROL "", 55, BUTTON, BS_AUTOCHECKBOX | WS_CHILD | WS_VISIBLE | WS_TABSTOP, 17, 18, 8, 8
CONTROL "", 49, BUTTON, BS_AUTOCHECKBOX | WS_CHILD | WS_VISIBLE | WS_TABSTOP, 29, 18, 8, 8
CONTROL "", 72, BUTTON, BS_AUTOCHECKBOX | WS_CHILD | WS_VISIBLE | WS_TABSTOP, 41, 18, 8, 8
CONTROL "", 93, BUTTON, BS_AUTOCHECKBOX | WS_CHILD | WS_VISIBLE | WS_TABSTOP, 53, 18, 8, 8
CONTROL "", 12, BUTTON, BS_AUTOCHECKBOX | WS_CHILD | WS_VISIBLE | WS_TABSTOP, 65, 18, 8, 8
CONTROL "", 39, BUTTON, BS_AUTOCHECKBOX | WS_CHILD | WS_VISIBLE | WS_TABSTOP, 89, 18, 8, 8
CONTROL "", 82, BUTTON, BS_AUTOCHECKBOX | WS_CHILD | WS_VISIBLE | WS_TABSTOP, 77, 18, 8, 8
CONTROL "", 29, BUTTON, BS_AUTOCHECKBOX | WS_CHILD | WS_VISIBLE | WS_TABSTOP, 101, 18, 8, 8
이를 통해 각 체크 박스의 번호를 알수 있고,
체크버튼은 1번이고, 종료 버튼은 2번이네요.
Olly에서 대상 프로그램을 열고 Check 버튼을 누를떄의 코드를 살펴보면,
00401121 . 8935 62214000 MOV DWORD PTR DS:[402162],ESI
00401127 > /0FBE8E FE2040>MOVSX ECX,BYTE PTR DS:[ESI+4020FE]
0040112E . |83F9 4D CMP ECX,4D
00401131 . |74 2F JE SHORT due-cm3.00401162
00401133 . |890D 5E214000 MOV DWORD PTR DS:[40215E],ECX
00401139 . |51 PUSH ECX ; /ButtonID
0040113A . |FF75 08 PUSH DWORD PTR SS:[EBP+8] ; |hWnd
0040113D . |E8 D0010000 CALL <JMP.&USER32.IsDlgButtonChecked> ; \IsDlgButtonChecked
00401142 . |46 INC ESI
00401143 . |83F8 00 CMP EAX,0
00401146 .^ 74 DF JE SHORT due-cm3.00401127
00401148 . |A1 5E214000 MOV EAX,DWORD PTR DS:[40215E]
0040114D . |0FBE8E FE2040>MOVSX ECX,BYTE PTR DS:[ESI+4020FE]
00401154 . |0FAFC1 IMUL EAX,ECX
00401157 . |0FAFC6 IMUL EAX,ESI
0040115A . |0105 62214000 ADD DWORD PTR DS:[402162],EAX
00401160 .^\EB C5 JMP SHORT due-cm3.00401127
00401162 > A1 62214000 MOV EAX,DWORD PTR DS:[402162]
00401167 . 6BC0 4D IMUL EAX,EAX,4D
0040116A . 3D 6654F300 CMP EAX,0F35466
0040116F . 75 20 JNZ SHORT due-cm3.00401191
0x4020fe에서 한 바이트씩 가져와서 그를 ButtonID로 사용해서 Check되었는지
유무를 조사한 후, 체크되지 않았다면 그 다음 버튼을 조사하고 체크 되었다면
어떠한 처리를 하네요.
우선 0x4020fe에 가서 ButtonID의 순서를 가져와 보면,
0040210E 61 52 aR
네요.
만약 16이라는 아이디를 가진 버튼이 체크되어 있다면,
다음의 곱셈 처리를 하네요.
00401157 . |0FAFC6 IMUL EAX,ESI
0040115A . |0105 62214000 ADD DWORD PTR DS:[402162],EAX
00401160 .^\EB C5 JMP SHORT due-cm3.00401127
00401162 > A1 62214000 MOV EAX,DWORD PTR DS:[402162]
00401167 . 6BC0 4D IMUL EAX,EAX,4D
(현재 버튼의 ID * 다음 버튼의 ID * 현재 순서) * 4d 로 표시해 볼 수 있겠네요.
만약 0x16이라는 아이디를 가진 버튼이 눌렸다면 (0x16 * 0x49 * 1) * 0x4d 가 되겠네요.
이렇게 구해진 값들을 모두 더해 0xF35466이 되면 된다고 합니다.
이는 잠시 후 프로그램 코드를 통해 해결하면 되는 문제인데,
문제가 되는건 ButtonID의 순서와 현재 창에 표시되는 번호 순서가 다르다는 것입니다.
귀찮지만 직접 순서를 부여해주면,

다음과 같이 0xF35466의 결과가 되는 조합을 찾아내느 코드를 작성해 보았습니다.
{
char corret[18] = {0x16,0x49,0x5E,0x15,0x27,0x26,0x21,0x25,0x1D,
0x59,0x53,0x37,0x31,0x48,0x5D,0x0C,0x61,0x52};
{
sum = 0;
for(int j = 0; j < 18; j++)
{
if(i & (1 << j))
{
sum += (corret[j] * corret[j+1] * (j+1)) * 0x4d;
}
}
if(sum == 0xF35466)
{
for(j =0; j < 18; j++)
{
printf("%d - ",j+1);
if(i & (1 << j))
{
printf("O");
}
else
{
printf("X");
}
printf("\n");
}
}
}
return 0;
}
실행 시켜 보니, 조합이 1개가 아니네요.

이를 순서대로 대입해 보면,

===============================
Crackme #4
===============================
이번엔 name에 맞는 serial을 구하는 문제입니다.
OllyDbg로 코드를 조금 살펴보면,
00401129 . |6A 00 PUSH 0 ; |wParam = 0
0040112B . |6A 0E PUSH 0E ; |Message = WM_GETTEXTLENGTH
0040112D . |6A 03 PUSH 3 ; |ControlID = 3
0040112F . |FF75 08 PUSH DWORD PTR SS:[EBP+8] ; |hWnd
00401132 . |E8 41020000 CALL <JMP.&USER32.SendDlgItemMessageA> ; \SendDlgItemMessageA
00401137 . |A3 AF214000 MOV DWORD PTR DS:[4021AF],EAX
0040113C . |83F8 00 CMP EAX,0
0040113F . |0F84 D5000000 JE due-cm4.0040121A
00401145 . |83F8 08 CMP EAX,8
00401148 . |0F8F CC000000 JG due-cm4.0040121A
Name의 글자수가 0이어도 안되고 8자를 넘어서도 안되네요.
00401152 . 6A 00 PUSH 0 ; |wParam = 0
00401154 . 6A 0E PUSH 0E ; |Message = WM_GETTEXTLENGTH
00401156 . 6A 04 PUSH 4 ; |ControlID = 4
00401158 . FF75 08 PUSH DWORD PTR SS:[EBP+8] ; |hWnd
0040115B . E8 18020000 CALL <JMP.&USER32.SendDlgItemMessageA> ; \SendDlgItemMessageA
00401160 . 83F8 00 CMP EAX,0
00401163 . 0F84 B1000000 JE due-cm4.0040121A
00401169 . 3BF0 CMP ESI,EAX
0040116B . 0F85 A9000000 JNZ due-cm4.0040121A
등록코드의 경우는 길이가 0이어서는 안되고, 유저이름의 길이와 다르면 안되네요.
00401176 . 6A 08 PUSH 8 ; |wParam = 8
00401178 . 6A 0D PUSH 0D ; |Message = WM_GETTEXT
0040117A . 6A 03 PUSH 3 ; |ControlID = 3
0040117C . FF75 08 PUSH DWORD PTR SS:[EBP+8] ; |hWnd
0040117F . E8 F4010000 CALL <JMP.&USER32.SendDlgItemMessageA> ; \SendDlgItemMessageA
00401184 . 68 79214000 PUSH due-cm4.00402179 ; /lParam = 402179
00401189 . 6A 10 PUSH 10 ; |wParam = 10
0040118B . 6A 0D PUSH 0D ; |Message = WM_GETTEXT
0040118D . 6A 04 PUSH 4 ; |ControlID = 4
0040118F . FF75 08 PUSH DWORD PTR SS:[EBP+8] ; |hWnd
00401192 . E8 E1010000 CALL <JMP.&USER32.SendDlgItemMessageA> ; \SendDlgItemMessageA
이제 유저이름과 등록코드를 가져오는 것을 볼 수 있습니다.
0040119D . |0FBE81 602140>MOVSX EAX,BYTE PTR DS:[ECX+402160]
004011A4 . |83F8 00 CMP EAX,0 ; Switch (cases 0..7A)
004011A7 . |74 32 JE SHORT due-cm4.004011DB
004011A9 . |BE FFFFFFFF MOV ESI,-1
004011AE . |83F8 41 CMP EAX,41
004011B1 . |7C 67 JL SHORT due-cm4.0040121A
004011B3 . |83F8 7A CMP EAX,7A
004011B6 . |77 62 JA SHORT due-cm4.0040121A
004011B8 . |83F8 5A CMP EAX,5A
004011BB . |7C 03 JL SHORT due-cm4.004011C0
004011BD . |83E8 20 SUB EAX,20 ; Cases 5A ('Z'),5B ('['),5C ('\'),5D (']'),5E ('^'),5F ('_'),60 ('`'),61 ('a'),62 ('b'),63 ('c'),64 ('d'),65 ('e'),66 ('f'),67 ('g'),68 ('h'),69 ('i'),6A ('j'),6B ('k'),6C ('l'),6D ('m')... of switch 004011A4
004011C0 > |46 INC ESI ; Cases 41 ('A'),42 ('B'),43 ('C'),44 ('D'),45 ('E'),46 ('F'),47 ('G'),48 ('H'),49 ('I'),4A ('J'),4B ('K'),4C ('L'),4D ('M'),4E ('N'),4F ('O'),50 ('P'),51 ('Q'),52 ('R'),53 ('S'),54 ('T')... of switch 004011A4
004011C1 . |0FBE96 172040>MOVSX EDX,BYTE PTR DS:[ESI+402017]
004011C8 . |3BC2 CMP EAX,EDX
004011CA .^|75 F4 JNZ SHORT due-cm4.004011C0
004011CC . |0FBE86 3C2040>MOVSX EAX,BYTE PTR DS:[ESI+40203C]
004011D3 . |8981 94214000 MOV DWORD PTR DS:[ECX+402194],EAX
004011D9 .^\EB C1 JMP SHORT due-cm4.0040119C
글자는 A보다는 커야하고, z 보다는 작아야 하고, z 보다는 커야 하네요.
00402010 41 31 4C 53 4B 32 44 4A 46 A1LSK2DJF
00402020 34 48 47 50 33 51 57 4F 35 45 49 52 36 55 54 59 4HGP3QWO5EIR6UTY
00402030 5A 38 4D 58 4E 37 43 42 56 39 Z8MXN7CBV9
에 있는 글자와 사용자가 입력한 이름의 글자가 같아지는 순간의 ESI를 인덱스로
00402030 20 53 55 37 43 SU7C
00402040 53 4A 4B 46 30 39 4E 43 53 44 4F 39 53 44 46 30 SJKF09NCSDO9SDF0
00402050 39 53 44 52 4C 56 4B 37 38 30 39 53 34 4E 46 9SDRLVK7809S4NF
에 있는 글자를 차례로 하면 시리얼이 되는 것입니다.
아 그리고 소문자일 경우는 0x20을 뺴줘서 대문자로 만들어 주고 있네요.
이런 정보를 가지고 keygen을 한번 작성해 보았습니다.
{
char name[8];
char order[] = "A1LSK2DJF4HGP3QWO5EIR6UTYZ8MXN7CBV9";
char key[] = "SU7CSJKF09NCSDO9SDF09SDRLVK7809S4NF";
printf("Enter your Name : ");
scanf("%s",name);
if(strlen(name) > 8)
{
printf("Name must shorter than 8 bytes\n");
exit(-1);
}
{
for(int j = 0; j < strlen(order); j++)
{
if(name[i] == order[j])
{
printf("%c",key[j]);
}
}
}
printf("\n");
}
실행 시켜보면,

유저이름 Dual에 대한 Serial은 KDS7이라는 군요.
실제로 한번 대입해 보죠.

Posted by Dual


