crackme#02a 解析手引き

この解析環境はWindows98 OllyDBG1.07(日本語化)で行っています。


OllyDBGを、起動して「crkme02a.exe」を読込んでください。
前回と同様に、「検索」→「ラベル一覧」(「Search for」→「Name(Label)」)で、ラベル一覧の一覧を、表示します。

Names in crkme02a
Address Section Type ( Name Comment
00401000 .text Export
00402000 .rdata Import ( KERNEL32.GetModuleHandleA
00402004 .rdata Import ( KERNEL32.ExitProcess
0040200C .rdata Import ( USER32.GetWindowTextA   ←ココ
00402010 .rdata Import ( USER32.LoadIconA
00402014 .rdata Import ( USER32.GetDlgItem
00402018 .rdata Import ( USER32.DialogBoxParamA
0040201C .rdata Import ( USER32.EndDialog
00402020 .rdata Import ( USER32.SetWindowLongA
00402024 .rdata Import ( USER32.ShowWindow
00402028 .rdata Import ( USER32.CallWindowProcA
0040202C .rdata Import ( USER32.MessageBoxA
00402030 .rdata Import ( USER32.SendMessageA

「USER32.GetWindowTextA」を選択して、「Enter」キーを押してください。
これは前回も説明されてますが、画面上のテキストボックスから文字列を取得するAPIのひとつです。
「References in crkme02a:.text to user32.USER32.GetWindowTextA」というウィンドウが表示されます。

References in crkme02a:.text to USER32.GetWindowTextA
Address Disassembly Comment
00401240 CALL <JMP.&USER32.GetWindowTextA>
0040132C JMP DWORD PTR DS:[<&USER32.GetWindowText USER32.GetWindowTextA

Address 00401240 call <jmp.&USER32.GetWindowTextA>を選択して、「F2」キーでブレークポイントを設定してください。
そして、「Enter」キーを押すと、CPUウィンドウが表示されます。

00401220 . 6A 00 PUSH 0 ; /Style = MB_OK|MB_APPLMODAL
00401222 . 6A 00 PUSH 0 ; |Title = NULL
00401224 . 68 28314000 PUSH crkme02a.00403128 ; |Text = "・・・・・・・・"
00401229 . FF75 08 PUSH DWORD PTR SS:[EBP+8] ; |hOwner
0040122C . E8 07010000 CALL <JMP.&USER32.MessageBoxA> ; \MessageBoxA
00401231 .^EB A7 JMP SHORT crkme02a.004011DA
00401233 $ 6A 7E PUSH 7E ; /Count = 7E (126.)
00401235 . 68 2D304000 PUSH crkme02a.0040302D ; |Buffer = crkme02a.0040302D
0040123A . FF35 04304000 PUSH DWORD PTR DS:[403004] ; |hWnd = NULL
00401240 . E8 E7000000 CALL <JMP.&USER32.GetWindowTextA> ; \GetWindowTextA         ←ココから
00401245 . E8 36000000 CALL crkme02a.00401280
0040124A . 85C0 TEST EAX,EAX
0040124C . 74 19 JE SHORT crkme02a.00401267
0040124E . 6A 00 PUSH 0 ; /Style = MB_OK|MB_APPLMODAL
00401250 . 68 07314000 PUSH crkme02a.00403107 ; |Title = "登録情報"0403
00401255 . 68 1D314000 PUSH crkme02a.0040311D ; |Text = "正解です!"04031
0040125A . FF75 08 PUSH DWORD PTR SS:[EBP+8] ; |hOwner
0040125D . E8 D6000000 CALL <JMP.&USER32.MessageBoxA> ; \MessageBoxA
00401262 .^E9 37FEFFFF JMP crkme02a.0040109E
00401267 > 6A 00 PUSH 0 ; /Style = MB_OK|MB_APPLMODAL
00401269 . 68 07314000 PUSH crkme02a.00403107 ; |Title = "登録情報"0403
0040126E . 68 10314000 PUSH crkme02a.00403110 ; |Text = "不正解です。"403110
00401273 . FF75 08 PUSH DWORD PTR SS:[EBP+8] ; |hOwner
00401276 . E8 BD000000 CALL <JMP.&USER32.MessageBoxA> ; \MessageBoxA

CPUウィンドウで、00401240の行から10行位ほど上も表示されるように、スクロールしてください。

Address 00401224に、TEXT = "・・・・・"文字化けしてますが、表示されてます。
その行に注目して、「F9」キーで動かしてみましょう。
crackme #02aのウィンドウが、表示されました。
フェークパス「12345678」と入力して、「登録」ボタンクリックしてください。


「そういう所にBP仕掛けますか(笑)」と表示されました。(作者は、ここのBPを仕掛けると予想していたようです。)
OKボタンをクリックしてください。
crkme02a.exeは、終了しましたが、OllyDBGで、先程の注目点(Address 00401224)に、エラーのメッセージが表示されてます。

00401220 . 6A 00 PUSH 0 ; /Style = MB_OK|MB_APPLMODAL
00401222 . 6A 00 PUSH 0 ; |Title = NULL
00401224 . 68 28314000 PUSH CRKME02A.00403128 ; |Text = "そういう所に BP 仕掛けますか(笑)"
00401229 . FF75 08 PUSH DWORD PTR SS:[EBP+8] ; |hOwner
0040122C . E8 07010000 CALL <JMP.&USER32.MessageBoxA> ; \MessageBoxA

Address 0040122CのMessageBoxAでエラー出力後、終了しているようです。
もう少し、上に行って、

004011EB lea esi, dword ptr ds:[401233]

の行が、怪しいです。
lea命令は、アドレスを取得する命令で、00401233のアドレスを、esiレジスタに入れてす。
そして、

004011FC > 381C30 CMP BYTE PTR DS:[EAX+ESI],BL
004011FF . 74 0D JE SHORT CRKME02A.0040120E
00401201 . 40 INC EAX
00401202 . 3D 91000000 CMP EAX,91
00401207 .^72 F3 JB SHORT CRKME02A.004011FC

のループで、先程ブレークポイントを仕掛けたコード部分をチェックしているようです。
本来なら、ここのコードを換えて進めたいのですが、規則に則ってクラックなしで、パスを出します。
よく見ると、

00401209 call crkme02a.00401233

の行に気がつきます。 ここは、ブレークポイント・チェックで、引っかからない時に実行される行です。
では、GetWindowTextAのブレークポイントを解除して、今度はここに、ブレークポイントを仕掛けてみましょう。


「解析」→「再スタート」(「Debug」→「Restart」)で、再度起動します。
そして、「F9」キーで実行します。
フェークパス「12345678」と入力して、「登録」ボタンクリックしてください。

00401209で、止まります。
これは、先程ブレークポイント仕掛けた所です。

次に、「F7」キーで、call先に行って見ましょう。

00401233 $ 6A 7E PUSH 7E ; /Count = 7E (126.)   ←ココ
00401235 . 68 2D304000 PUSH CRKME02A.0040302D ; |Buffer = CRKME02A.0040302D
0040123A . FF35 04304000 PUSH DWORD PTR DS:[403004] ; |hWnd = 00000E68 (class='Edit',wndproc=0040102B,parent=00000E5C)
00401240 . E8 E7000000 CALL <JMP.&USER32.GetWindowTextA> ; \GetWindowTextA


00401233 $ 6A 7E PUSH 7E


GetWindowTextAの引数Countの値は、入力パスから7Eh=126文字取れという意味です。
入力パス、というよりはテキストボックスから126文字分取って所定のバッファに貯める、
という言い方が正しいです。


その後は、「F8」キーで、

00401245 call crkme02a.00401280

まで実行させます。
このcallの次に、test eax, eaxが有ります。 call後の結果が、eaxに入ってると推測出来ます。

「F7」キーで、call先に行って見ましょう。

00401280 /$ BE 4B314000 MOV ESI,CRKME02A.0040314B ; ASCII "ヨ7Wム" ←ココ
00401285 |. C706 21430000 MOV DWORD PTR DS:[ESI],4321
0040128B |. BE 2D304000 MOV ESI,CRKME02A.0040302D ; ASCII "12345678"
00401290 |. E8 59000000 CALL CRKME02A.004012EE
00401295 |. 3816 CMP BYTE PTR DS:[ESI],DL
00401297 |. 75 52 JNZ SHORT CRKME02A.004012EB
00401299 |. E8 50000000 CALL CRKME02A.004012EE
0040129E |. 3856 01 CMP BYTE PTR DS:[ESI+1],DL
004012A1 |. 75 48 JNZ SHORT CRKME02A.004012EB
004012A3 |. E8 46000000 CALL CRKME02A.004012EE
004012A8 |. 3856 02 CMP BYTE PTR DS:[ESI+2],DL
004012AB |. 75 3E JNZ SHORT CRKME02A.004012EB
004012AD |. E8 3C000000 CALL CRKME02A.004012EE
004012B2 |. 3856 03 CMP BYTE PTR DS:[ESI+3],DL
004012B5 |. 75 34 JNZ SHORT CRKME02A.004012EB
004012B7 |. E8 32000000 CALL CRKME02A.004012EE
004012BC |. 3856 04 CMP BYTE PTR DS:[ESI+4],DL
004012BF |. 75 2A JNZ SHORT CRKME02A.004012EB
004012C1 |. E8 28000000 CALL CRKME02A.004012EE
004012C6 |. 3856 05 CMP BYTE PTR DS:[ESI+5],DL
004012C9 |. 75 20 JNZ SHORT CRKME02A.004012EB
004012CB |. E8 1E000000 CALL CRKME02A.004012EE
004012D0 |. 3856 06 CMP BYTE PTR DS:[ESI+6],DL
004012D3 |. 75 16 JNZ SHORT CRKME02A.004012EB
004012D5 |. E8 14000000 CALL CRKME02A.004012EE
004012DA |. 3856 07 CMP BYTE PTR DS:[ESI+7],DL
004012DD |. 75 0C JNZ SHORT CRKME02A.004012EB
004012DF |. 807E 08 00 CMP BYTE PTR DS:[ESI+8],0
004012E3 |. 75 06 JNZ SHORT CRKME02A.004012EB
004012E5 |. B8 01000000 MOV EAX,1
004012EA |. C3 RETN
004012EB |> 33C0 XOR EAX,EAX
004012ED \. C3 RETN


00401280に来ました。そして、先程入力したフェークパス「12345678」も表示されてます。
さらに、下のほうにcmpとjnzが連続しています。
ここの場所が、パスのチェック場所と考えられます。

00401290 call crkme02a.004012EE

まで、「F7」キーで実行させます。

次は、「F8」キーで、callは飛ばしてみましょう。

00401295 cmp byte ptr ds:[esi], dl

ここで止まってます。
CPUウィンドウの下のほうに、dl=57('W')と、ds:[0040302D]=31('1')と表示されてます。
ds:[0040302D]=31('1')←これは、入力したフェークパスです。
となると、dl=57('W')←これが正解パスのようです。
この、cmpでパスの正当性を比較しているようです。

「F7」キーで、実行します。
比較している値が違うので、jnzでジャンプします。
ジャンプしないように、フラグレジスタ、Zを1に変更します。
フラグレジスタのセット出来ましたか?

「F7」と「F8」を押します。
また、cmp命令ですね。 ここも正解パスは、dlレジスタに入っています。
cmp命令のdlレジスタ値をメモしながら、「F7」・フラグレジスタ、Zを1に変更・「F7「F8」を繰返します。

004012DF cmp byte ptr ds:[esi+8], 0 ← ここまで実行させます。

このcmpは、パスが8文字で終了しているかの判定です。(C言語の'\0'ですね。)
これで、正解パスワードが出ます。

パスのチェックは疑似乱数もどきを使っているそうです。
勿論乱数と言っても疑似なので毎回同じものが出るのでパスが変わることはありません。
入力文字を1文字ずつ分解して、ゴニョゴニュやって出てきた結果をアルファベットにしてます。



Windows NT/2K xpも含む?(試してないので分かりません。)の方へ特別講習

OllyDBGのブレークポイントの種類に、ハードウェア・ブレークポイントってのがあります。
#02aのGetWindowTextAにハードウェア・ブレークポイントを使用して、解析するとブレークポイント・チェック処理に引っかからずに行けます。
これはブレークポイントを仕掛けるとその仕掛けたアドレスは一時的にint3 = 0xCC に置き換わります。
アドレス 004011F1 と 004011F6 を見るとわかりますが、逆汗対策に小細工をやっています。
0x36 + 0x96 = 0xCC とやってからブレークポイントが仕掛けられていないかチェックしているというわけです。
単純にCCを持ってこない辺り、見つけにくくなってますね。

以上補足。