crackme#20 解析手引き

今回のcrackmeは異色で、今までと違いグラフィックを使用しています。
まず、始めにUPXで、crkme20.exeを解凍します。
これは、もう終ってますね?
解答方法は#10を参照して下さい。

とあえず、crkme20.exeを起動させて見ましょう。
'A'~'Z'をクリックしPASSWORD入力して、REGISTクリックで登録ですね。
となると、REGISTクリック・イベントの処理部分を探してみましょう。

リソース・エディタで、crkme20.exeのリソース見ても、crackme #20ウィンドウの殆どは、
グラフィック描画の為、主立ったリソース情報が得られません。
OllyDBGで解析して行きましょう。
OllyDBGを、起動して「crkme20.exe」を読込んでください。

右クリック→「検索」→「ラベル一覧」(「Search for」→「Name(Label)」)で、ラベル一覧の一覧を表示します。

Names in crkme20
Address Section Type ( Name Comment
00401000 .text Export <ModuleEntryPoint>
00402000 .rdata Import ( GDI32.CreateCompatibleDC
00402004 .rdata Import ( GDI32.SelectObject
00402008 .rdata Import ( GDI32.DeleteDC
0040200C .rdata Import ( GDI32.CreateBrushIndirect
00402010 .rdata Import ( GDI32.BitBlt
00402018 .rdata Import ( KERNEL32.ExitProcess
0040201C .rdata Import ( KERNEL32.GetModuleHandleA
00402024 .rdata Import ( USER32.ReleaseDC
00402028 .rdata Import ( USER32.GetDC
0040202C .rdata Import ( USER32.LoadIconA
00402030 .rdata Import ( USER32.LoadBitmapA
00402034 .rdata Import ( USER32.GetClientRect
00402038 .rdata Import ( USER32.FillRect
0040203C .rdata Import ( USER32.EndDialog
00402040 .rdata Import ( USER32.EndPaint
00402044 .rdata Import ( USER32.DialogBoxParamA
00402048 .rdata Import ( USER32.SendMessageA
0040204C .rdata Import ( USER32.ShowWindow
00402050 .rdata Import ( USER32.BeginPaint
00402054 .rdata Import ( USER32.GetWindowRect
00402058 .rdata Import ( USER32.GetWindowLongA
0040205C .rdata Import ( USER32.CreateWindowExA
00402060 .rdata Import ( USER32.DefWindowProcA
00402064 .rdata Import ( USER32.GetDlgCtrlID
00402068 .rdata Import ( USER32.GetParent
0040206C .rdata Import ( USER32.SetWindowLongA
00402070 .rdata Import ( USER32.SetWindowPos
00402074 .rdata Import ( USER32.LoadCursorA
00402078 .rdata Import ( USER32.RegisterClassExA
0040207C .rdata Import ( USER32.ReleaseCapture
00402080 .rdata Import ( USER32.SetCapture
00402088 .rdata Import WINMM.PlaySoundA

「00402064 .rdata Import ( USER32.GetDlgCtrlID 」を選択して、「Enter」キーを押してください。
「References in crkme20:.text to USER32.GetDlgCtrlID」というウィンドウが表示されます。

References in crkme20:.text to USER32.GetDlgCtrlID
Address Disassembly Comment
004019EB CALL <JMP.&USER32.GetDlgCtrlID>
00401A32 JMP DWORD PTR DS:[<&USER32.GetDlgCtrlID> USER32.GetDlgCtrlID

「004019EB CALL <JMP.&USER32.GetDlgCtrlID>」にブレーク・ポイント仕掛けます。

「F9」キーで実行させます。
すると、crackme #20のウィンドウが表示されます。
何も入力せずに「REGIST」をクリックしてみましょう。
先程仕掛けたブレークポイントで止まります。

004019E8 . FF75 08 PUSH DWORD PTR SS:[EBP+8] ; /hWnd
004019EB . E8 42000000 CALL <JMP.&USER32.GetDlgCtrlID> ; \GetDlgCtrlID
004019F0 . 8945 EC MOV DWORD PTR SS:[EBP-14],EAX

「F8」キー押して、CALLを実行させます。
EAXレジスタに、REGISTボタンのコントロールIDが入っています。
0x3E8(1000)ですね。

その下には、

004019F3 . FF75 08 PUSH DWORD PTR SS:[EBP+8] ; /lParam
004019F6 . FF75 EC PUSH DWORD PTR SS:[EBP-14] ; |wParam
004019F9 . 68 11010000 PUSH 111 ; |Message = WM_COMMAND
004019FE . FF75 F0 PUSH DWORD PTR SS:[EBP-10] ; |hWnd
00401A01 . E8 2AFDFFFF CALL <JMP.&USER32.SendMessageA> ; \SendMessageA

と、WM_COMMAND でコントロールIDを送ってます。
それでは、「Ctrl」+「F2」で再起動します。


再起動後、上からずっと下の方へ見ていくと、

0040100C . 6A 00 PUSH 0 ; /lParam = NULL
0040100E . 68 56124000 PUSH crkme20.00401256 ; |DlgProc = crkme20.00401256
00401013 . 6A 00 PUSH 0 ; |hOwner = NULL
00401015 . 6A 01 PUSH 1 ; |pTemplate = 1
00401017 . FF35 00304000 PUSH DWORD PTR DS:[403000] ; |hInst = NULL
0040101D . E8 D8060000 CALL <JMP.&USER32.DialogBoxParamA> ; \DialogBoxParamA

とあります。
ここは、ダイアログ・ウィンドウを生成するAPIコールです。
重要な部分は、

0040100E . 68 56124000 PUSH crkme20.00401256 ; |DlgProc = crkme20.00401256

です。
このcrkme20の親ウィンドウは、ダイアログ・ウィンドウで構成され、イベント処理は、crkme20.00401256 から始まっています。
では、crkme20.00401256 にジャンプしましょう。
「Ctrl」+「G」で表示されるダイアログに、00401256っと入力して、「OK」ボタンをクリックして下さい。

00401256 . 55 PUSH EBP
00401257 . 8BEC MOV EBP,ESP
00401259 . 83C4 A0 ADD ESP,-60
0040125C . 53 PUSH EBX
0040125D . 56 PUSH ESI
0040125E . 57 PUSH EDI
0040125F . 817D 0C 100100>CMP DWORD PTR SS:[EBP+C],110
00401266 . 0F85 B7000000 JNZ crkme20.00401323
0040126C . 8B45 08 MOV EAX,DWORD PTR SS:[EBP+8]
0040126F . EB 0C JMP SHORT crkme20.0040127D
00401271 . 63 72 61 63 6B>ASCII "crackme #20",0


6行ほど下に、

0040125F . 817D 0C 100100>CMP DWORD PTR SS:[EBP+C],110

とあります。
ウィンドウ・メッセージIDの比較です。
WM_COMMAND (0x111) の部分を探しましょう。
比較後のジャンプ先を見ていけば、簡単に見つかりますね。


004013C8 > 817D 0C 110100>CMP DWORD PTR SS:[EBP+C],111   ←ここですね
004013CF . 0F85 F5010000 JNZ crkme20.004015CA
004013D5 . 66:817D 10 102>CMP WORD PTR SS:[EBP+10],2710
004013DB . 72 48 JB SHORT crkme20.00401425
004013DD . 66:817D 10 292>CMP WORD PTR SS:[EBP+10],2729
004013E3 . 77 40 JA SHORT crkme20.00401425
004013E5 . 8B45 10 MOV EAX,DWORD PTR SS:[EBP+10]
004013E8 . 25 FFFF0000 AND EAX,0FFFF
004013ED . 2D CF260000 SUB EAX,26CF
004013F2 . 8B15 DD324000 MOV EDX,DWORD PTR DS:[4032DD]
004013F8 . 8882 D0324000 MOV BYTE PTR DS:[EDX+4032D0],AL
004013FE . 833D DD324000 >CMP DWORD PTR DS:[4032DD],0B
00401405 . 73 06 JNB SHORT crkme20.0040140D
00401407 . FF05 DD324000 INC DWORD PTR DS:[4032DD]
0040140D > 6A 64 PUSH 64
0040140F . E8 FD010000 CALL crkme20.00401611


次に、2行下の比較です。

004013D5 . 66:817D 10 102>CMP WORD PTR SS:[EBP+10],2710

ここでは、WM_COMMANDと一緒に送られた、値の比較です。
今は、先程取得した、コントロールID 0x3E8(1000) の比較部分を探します。

004014FB > 66:817D 10 E80>CMP WORD PTR SS:[EBP+10],3E8  ← ここですね
00401501 . 0F85 01010000 JNZ crkme20.00401608
00401507 . 8D35 D0324000 LEA ESI,DWORD PTR DS:[4032D0]
0040150D . 8B06 MOV EAX,DWORD PTR DS:[ESI]
0040150F . 25 FFFFFF00 AND EAX,0FFFFFF
00401514 . 3D 494A4C00 CMP EAX,4C4A49
00401519 . 0F85 93000000 JNZ crkme20.004015B2
0040151F . 83C6 03 ADD ESI,3
00401522 . 33C0 XOR EAX,EAX
00401524 . 33C9 XOR ECX,ECX
00401526 . EB 2E JMP SHORT crkme20.00401556


では、

00401507 . 8D35 D0324000 LEA ESI,DWORD PTR DS:[4032D0]

ここにブレークポイント設定して、「F9」で動かしましょう。
crackme #20のウィンドウが表示されます。
フェーク・パスとして、"ABC"と入力して「REGIST」ボタンをクリックします。
"A"を、クリックした時に、Address 004019EB call <jmp.&USER32.GetDlgCtrlID> でブレークするので、
ついでにブレークポイントを解除します。

「REGIST」ボタンをクリック後は、

00401507 . 8D35 D0324000 LEA ESI,DWORD PTR DS:[4032D0]

ブレークポイントのところでブレークします。
ここで、入力パス域のアドレスを、ESIに代入ですね。(0x170146)
その下で、上位4文字をEAXレジスタに読込んでます。

0040150D . 8B06 MOV EAX,DWORD PTR DS:[ESI]  ← これです。

で、レジスタ値の上位1バイトクリア (AND EAX,0FFFFFF)

00401514 . 3D 494A4C00 CMP EAX,4C4A49 

ここでパス比較してますね。(3文字)
0x4C4A49 を,ASCII文字に変換すると、"LJI"となります。
これは、レジスタ値なので、パスの上位3文字は、"IJL"となってるようです。

ここで、フェクパスを変更します。
現在の行を右クリック→Follow in Dump→Memory Address で、入力パス域をダンプ表示します。
そして、3バイト選択して「Ctrl」+「e」で、ASCIIの部分に"IJL"と入力します。


「F7」キーを数回押して、
Address 0040151F にカーソルがくるまで実行させます。

0040151F . 83C6 03 ADD ESI,3

ここで、ESIレジスタ+3 パス4文字目を、esiレジスタは示します。
ESIレジスタの指す所は 004032D3 です。
3行下に、

00401526 . EB 2E JMP SHORT crkme20.00401556

の記述があります。
無条件ジャンプですね。 ここからは、何故か無条件ジャンプが数回でてきます。
無条件ジャンプ命令を除いても複雑ですが、簡単に説明をします。


+→00401556 > BA 1A000000 MOV EDX,1A
| 0040155B . F7E2 MUL EDX
| 0040155D . 0FBE1C31 MOVSX EBX,BYTE PTR DS:[ECX+ESI] パス4文字目以降ecxレジスタ値の示す1バイトを読込み
| 00401561 . 85DB TEST EBX,EBX           未入力かのチェック
| 00401563 . 74 4D JE SHORT crkme20.004015B2    ジャンプするとPASSWORDエラー
| 00401565 . 83EB 41 SUB EBX,41
| 00401568 . 03C3 ADD EAX,EBX
| 0040156A . 41 INC ECX
| 0040156B . 83F9 06 CMP ECX,6             ecxが6より小さい時ループ (パス4文字目から9文字を処理する。)
+←0040156E .^72 E6 JB SHORT crkme20.00401556
  00401570 . 50 PUSH EAX
  00401571 . 33C9 XOR ECX,ECX
  00401573 .^EB B3 JMP SHORT crkme20.00401528


このループで、英数を0-25に直し、((((((a*26)+b)*26+c)*26+d)*26+e)*26+f)*26が一つ目になります。
(abcdefは、4文字から9文字のこと)


+→00401528 > 8BD8 MOV EBX,EAX
| 0040152A . 25 FFFF0000 AND EAX,0FFFF
| 0040152F . C1EB 10 SHR EBX,10
| 00401532 . F7E0 MUL EAX
| 00401534 . F7E0 MUL EAX
| 00401536 . 35 02B43F24 XOR EAX,243FB402
| 0040153B . 03C3 ADD EAX,EBX
| 0040153D . 35 69234D8A XOR EAX,8A4D2369
| 00401542 . 41 INC ECX
| 00401543 . 83F9 14 CMP ECX,14             ECXが14より小さい時ループ 
+←00401546 .^72 E0 JB SHORT crkme20.00401528
  00401548 . 5A POP EDX
  00401549 . 3D 8139EEC9 CMP EAX,C9EE3981
  0040154E . 75 62 JNZ SHORT crkme20.004015B2    ジャンプするとPASSWORDエラー
  00401550 . 33C0 XOR EAX,EAX
  00401552 . 33C9 XOR ECX,ECX
  00401554 . EB 45 JMP SHORT crkme20.0040159B


2つ目のループが、パスチェックになります。
14回ループするのが分かりますね。
詳しく説明が出来ないので次へ・・・。

+→0040159B > 51 PUSH ECX
| 0040159C . 8BCA MOV ECX,EDX
| 0040159E . 83E1 01 AND ECX,1
| 004015A1 . 33C1 XOR EAX,ECX
| 004015A3 . D1EA SHR EDX,1
| 004015A5 . 59 POP ECX
| 004015A6 . 41 INC ECX
| 004015A7 . 83F9 20 CMP ECX,20             ecxが20より小さい時ループ 
+←004015AA .^72 EF JB SHORT crkme20.0040159B
  004015AC . 85C0 TEST EAX,EAX
  004015AE . 75 02 JNZ SHORT crkme20.004015B2    ジャンプするとPASSWORDエラー
  004015B0 .^EB C3 JMP SHORT crkme20.00401575


3つ目のループは、ECXが0になるまで続いています。
このときに使われる値は、はじめのループで出した値のようです。
ECXの値は、パスチェック終了まで保存されているので続きになります。
つまり、最初の6回後、そのまま20(32)になるまでずっと続いているようです。

<注意>
この#20は、前半で出した数値は後半では無くなってしまうので、逆算はできません。


0040153B . 03C3 ADD EAX,EBX

がそれにあたります。
つまり、途中6文字は総当たりで計算する事になります。
ループ回数は、26^6*32 回です。


これら一連のループによる処理文字数は6文字で、ごにょごにょ計算して、正しいパスの4文字目から9文字が正しい時は、

004015B0 .^EB C3 JMP SHORT crkme20.00401575

へ、無情にも無条件ジャンプします。
ジャンプ後、

00401575 > 8B46 05 MOV EAX,DWORD PTR DS:[ESI+5]

で、パス文字の9文字目から4バイトをEAXレジスタに読込みます。
次に

00401578 . C1E8 08 SHR EAX,8          右に8bitシフト
0040157B . 66:3D 4557 CMP AX,5745         これも、固定値の比較のようですね
0040157F . 75 31 JNZ SHORT crkme20.004015B2 ジャンプするとPASSWORDエラー
00401581 . C1E8 10 SHR EAX,10          右に16bitシフト
00401584 . 85C0 TEST EAX,EAX
00401586 . 75 2A JNZ SHORT crkme20.004015B2 ジャンプするとPASSWORDエラー
00401588 . 6A 65 PUSH 65
0040158A . E8 82000000 CALL crkme20.00401611
0040158F . C705 CC324000 >MOV DWORD PTR DS:[4032CC],1
00401599 . EB 28 JMP SHORT crkme20.004015C3


Address 0040157B の 0x5745を、ASCII文字にすると、"WE"で、
逆にして、"EW"が、正解パスの1部です。(10文字と11文字)
ここで、EAXレジスタが0以外か判定してます。 (0以外の時は、パス・エラーとなります。)
よって、正解パスは、"IJL******EW"の11文字の様です。
*の部分は、先の総当たりループ内で計算している部分です。

Keygenによって導き出される解答は、「IJLINNHQBEW」になります。
下に東洋人さんのKeygenソース(C言語)を掲載しましたので、参考にして下さい。
また、別室「wipipiさんのお部屋」にて#20のKG講座が始まる予定なので、そちらも参考にして下さい。



東洋人さん作 「#20 KG ソース」

// TODO: この位置にコントロール通知ハンドラ用のコードを追加してください
char buf[10];
unsigned int a = 0xC9EE3981;
unsigned int abc = 0x00000000;
unsigned int bbb = 0x00000000;
unsigned int c = 0x0FFFF;
unsigned int d = 0x243FB402;
unsigned int e = 0x8A4D2369;
unsigned int ddd = 0x00000000;
char Table1[] = "IJL000000EW";
 while(a != abc){
unsigned int ab1 = 0x00000000;
unsigned int ab2 = 0x00000000;
unsigned int ab3 = 0x00000000;
  unsigned int ab4 = 0x00000000;
// 0123456789ABCDEF10111213141516171819
// ABCDEFGHIJKLMNOP Q R S T U V W S Y Z
char Table[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
int count;
for(int j = 0; j < 6; j++){
ab2=0x1A;
ab1 = ab1 * ab2;
count = rand()%0x16;
ab3 = Table[count];
ab3 = ab3 - 0x41;
ab1 = ab1 + ab3;
Table1[3+j] = Table[count];
}
abc = ab1;

////////////////////////////////////////////////////////////
for(int i = 0x0; i<0x14; i++){
bbb= abc;
abc &= c;
bbb = bbb >> 0x10;
abc = abc * abc;
abc = abc * abc;
abc = abc ^ d;
abc = abc + bbb;
abc = abc ^ e;
}
}
wsprintf(buf,"%X",abc);

CEdit* myED1=(CEdit*)GetDlgItem(IDC_EDIT1);
myED1->SetWindowText(buf);
wsprintf(buf,"%X",ddd);
CEdit* myED2=(CEdit*)GetDlgItem(IDC_EDIT3);
myED2->SetWindowText(Table1);
}





これで、eagle0wlさん謹製 crackmeシリーズ を終わります。
ご購読ありがとうございました。