crackme#08 解析手引き

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


OllyDBGを起動して「crkme08.exe」を読込んでください。
いつものとおりCPUウィンドウで右クリックし、
「検索」→「ラベル一覧」(「Search for」→「Name(Label)」)で「Names in CRKME08」を表示します。


Names in crkme08
Address Section Type ( Name Comment
00401000 .text Export <ModuleEntryPoint>
00402000 .rdata Import ( KERNEL32.GetModuleHandleA
00402004 .rdata Import ( KERNEL32.ExitProcess
0040200C .rdata Import ( USER32.GetDlgItemTextA ←ココ
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


Address 0040200C の「USER32.GetDlgItemTextA」を選択して、「Enter」キーを押して下さい。
「References in CRKME08:.text to USER32.GetDlgItemTextA」というウィンドウが表示されます。


References in crkme08:.text to USER32.GetDlgItemTextA
Address Disassembly Comment
00401203 CALL <JMP.&USER32.GetDlgItemTextA>           ←ココ
00401219 CALL <JMP.&USER32.GetDlgItemTextA>
00401336 JMP DWORD PTR DS:[<&USER32.GetDlgItemTex USER32.GetDlgItemTextA


Address 00401203 call <jmp.&USER32.GetDlgItemTextA> を選択して、「F2」キーでブレークポイントを設定します。
では、「F9」キーで実行します。

crackme #08 のウィンドウが表示されました。
フェークネーム「abc」とフェークパス「87654321」と入力して、「登録」ボタンクリックしてください。
先程ブレークポイントを仕掛けた場所で止まります。


004011D2 . 6A 00 PUSH 0 ; /lParam = 0
004011D4 . 6A 02 PUSH 2 ; |wParam = 2
004011D6 . 68 A1000000 PUSH 0A1 ; |Message = WM_NCLBUTTONDOWN
004011DB . FF75 08 PUSH DWORD PTR SS:[EBP+8] ; |hWnd
004011DE . E8 65010000 CALL <JMP.&USER32.SendMessageA> ; \SendMessageA
004011E3 >^E9 B6FEFFFF JMP CRKME08.0040109E
004011E8 > 6A 00 PUSH 0 ; /Result = 0
004011EA . FF75 08 PUSH DWORD PTR SS:[EBP+8] ; |hWnd
004011ED . E8 38010000 CALL <JMP.&USER32.EndDialog> ; \EndDialog
004011F2 .^E9 A7FEFFFF JMP CRKME08.0040109E
004011F7 > 6A 7E PUSH 7E ; /Count = 7E (126.)
004011F9 . 68 30304000 PUSH CRKME08.00403030 ; |Buffer = CRKME08.00403030
004011FE . 6A 64 PUSH 64 ; |ControlID = 64 (100.)
00401200 . FF75 08 PUSH DWORD PTR SS:[EBP+8] ; |hWnd
00401203 . E8 2E010000 CALL <JMP.&USER32.GetDlgItemTextA> ; \GetDlgItemTextA ←ココ
00401208 . 83F8 00 CMP EAX,0
0040120B . 74 37 JE SHORT CRKME08.00401244
0040120D . 6A 7E PUSH 7E ; /Count = 7E (126.)
0040120F . 68 B0304000 PUSH CRKME08.004030B0 ; |Buffer = CRKME08.004030B0
00401214 . 6A 65 PUSH 65 ; |ControlID = 65 (101.)
00401216 . FF75 08 PUSH DWORD PTR SS:[EBP+8] ; |hWnd
00401219 . E8 18010000 CALL <JMP.&USER32.GetDlgItemTextA> ; \GetDlgItemTextA
0040121E . 83F8 00 CMP EAX,0
00401221 . 74 3A JE SHORT CRKME08.0040125D
00401223 . 83F8 08 CMP EAX,8
00401226 . 75 4E JNZ SHORT CRKME08.00401276
00401228 . 68 30304000 PUSH CRKME08.00403030            「ASCII "abc"」    ←00401208へ来ると表示
0040122D . E8 76000000 CALL CRKME08.004012A8
00401232 . 8BD0 MOV EDX,EAX
00401234 . 68 B0304000 PUSH CRKME08.004030B0            「ASCII "87654321"」  ←0040121Eへ来ると表示
00401239 . E8 92000000 CALL CRKME08.004012D0
0040123E . 3BC2 CMP EAX,EDX
00401240 . 74 4D JE SHORT CRKME08.0040128F
00401242 . EB 32 JMP SHORT CRKME08.00401276



00401203 で止まりましたか?
ここで12行下の Address 00401228 の一番右の列(ニーモニック PUSH CRKME08:00403030 とある列の右側の列で、
この時点では空白)が見えるようにCPUウィンドウの大きさを調整します。

「F8」キーを1回押して,Address 00401203 call <jmp.&USER32.GetDlgItemTextA>を飛ばして、Address 00401208 CMP EAX,0 へ行きます。
すると、先ほどまで空白だった Address 00401228 の一番右の列に、「ASCII "abc"」と先ほど入れたフェークネームが出ました。

ポインタをそのままにしておいて次の行を見てみましょう。
Address 00401208 CMP EAX,0 は EAXレジスタに入っている値
(CPUウィンドウの左下を見ると EAX=00000003)=読込んだフェークネーム(abc)の長さを「0」と比較して、
Address 0040120B JE SHORT CRKME08.00401244 の JE(Jump if Equal)で分岐しています。
これまでの crakme で何度も出てきた例の比較命令、そして分岐命令です。
と云うことは、登録ウィンドウで上側の Name を空白のままで「登録」を押すと Address 00401208 CMP EAX,0 で EAX=00000000 となります。
ちょっと横道に逸れましたが、今回はフェークネームを空白ではなく「abc」としたので、分岐せずにそのまま次の処理に行きます。

「F8」キーを1回押して、Address 0040120B JE SHORT CRKME08.00401244 へ行くと、CPUウィンドウの左下に Jump is NOT taken とあります。
Olly画面右上のレジスタウィンドウでゼロフラグ(Z)は「0」で旗は立っていないことを確認します。

では、「F8」を5回押して Address 00401219 へ行って下さい。

今度は Address 00401234 PUSH CRKME08:004030B0 の一番右のブランク列を見ながら「F8」を1回押して下さい。
先ほど入れたフェークパス「ASCII "87654321"」が出ました。
ポインタのある Address 0040121E CMP EAX,0 は
EAXレジスタに入っている値(CPUウィンドウの左下を見ると EAX=00000008)=読込んだフェークパス(87654321)の長さを「0」と比較しています。

「F8」キーを1回押した次の Address 00401221 JE SHORT CRKME08.0040125D は分岐(ジャンプ)先が Address 0040125D で、
今度は「パスを入力してください。」を出す処理ですが、CPUウィンドウの左下に Jump is NOT taken とあります。
上の Name(名前) の時と同じルーチンです。
ここまで来ると、「ASCII "87654321"」のチョット下の方にある「不正解です。」や「正解です。」が気になりますが、もう少しトレースして行きます。

この先,また比較命令(CMP)と分岐命令(J*)があります。
「F8」キーを1回押して下さい。

今度は Address 00401223 CMP EAX,8 でEAXレジスタに入っている値
(CPUウィンドウの左下を見ると EAX=00000008)=読込んだフェークパス(87654321)の長さを「8」と比較しています。
と云うことは、パスは8バイトでないと、次の分岐(ジャンプ)先 Address 00401276 で「不正解です。」を出す処理へ行きます。
今回のフェークパスは偶々「87654321」ですから分岐(ジャンプ)はせずに次の処理に行きますが、
例えば「654321」や「987654321」では指定先に分岐(ジャンプ)してしまいます。

「F8」キーを順々に押しながら見て行きます。

Address 00401226 では分岐(ジャンプ)はせずに次の処理へ、Address 00401228 ではフェークネームの「abc」を Address 00403030 へ押し込んで(push)から、
Address 00401228 でサブルーチンを呼んでパス生成、パス生成の結果をAddress 00401232 でEDXレジスタへ入れています。

Address 004012A8が生成ルーチンです。
では順に追っていきましょう。


004012A8 /$ 55 PUSH EBP
004012A9 |. 8BEC MOV EBP,ESP
004012AB |. 33C0 XOR EAX,EAX          ← eax 初期化、ここに生成した値が入る
004012AD |. 8B75 08 MOV ESI,DWORD PTR SS:[EBP+8]  ← 入力名のポインタが入る
004012B0 |> 0FBE0E /MOVSX ECX,BYTE PTR DS:[ESI]  ← 入力名から一文字取り出しています。
004012B3 |. 83F9 00 |CMP ECX,0           ← ecx には入力名が一文字入っています。null文字(入力名の終端)か どうかを比較しています。
004012B6 |. 74 09 |JE SHORT CRKME08.004012C1   ← 入力名が終端ならチェック終了
004012B8 |. 03C1 |ADD EAX,ECX          ← eax に ecx(入力名文字)を足しています。eax にどんどん値が積み重なっていくことがわかります。
004012BA |. F7E0 |MUL EAX            ← これは eax * eax を示しています。つまり2乗です。
004012BC |. 33D2 |XOR EDX,EDX          ← mulを行うとedxとeaxに値がまたがって格納されますが、上位4バイトを消しています。
004012BE |. 46 |INC ESI            ← esi は入力名のポインタ、これをインクリメントすることで次の文字にポインタが合わさります。
004012BF |.^EB EF \JMP SHORT CRKME08.004012B0  ← ループです。ぐるぐる回ります。ぐるぐるループを回ったところで、入力名のポインタが終端を
指したらループを脱出し、004012c1 に抜けます。
004012C1 |> 05 2BA43F18 ADD EAX,183FA42B        ← eax には入力名から作った値が入っていますが、これに値を足しています。
004012C6 |. 83F8 FF CMP EAX,-1           ← これは eax と FFFFFFFFh とを比較しています。
004012C9 |. 75 01 JNZ SHORT CRKME08.004012CC   ← eax!=FFFFFFFFhならジャンプ、値生成終了。
004012CB |. 48 DEC EAX            ← eax = FFFFFFFFh ならデクリメントして eax = FFFFFFFEh にします。

004012CC |> C9 LEAVE
004012CD \. C2 0400 RETN 4


これで値生成部分は終わりです。なぜcmp eax, -1 があるのかは、入力パスを値に変換するルーチンを見れば分かると思います。

これを生成ルーチンを解りやすくする為に式にしてみます。

EAXは、始めは0「ゼロ」です。
で、'a' 16進で 0x61 であり、この0x61が、eaxに入ります。
次に、かけ算で、0x61 * 0x61 となります。
その結果が、EAXレジスタに入ります。

次に、2文字目の'b'です。
16進で 0x62 であり、 add で、先程のかけ算の結果(0x61 * 0x61) + 0x62 となります。
かけ算で、上記の値に0x62を掛けます。

これを、文字数分掛けて、最後に

004012C1 |> 05 2BA43F18 ADD EAX,183FA42B

ここで、0x183FA42Bを足しこんでます。

abcを例にして計算式書きますと
        (((((0x61 * 0x61) + 0x62)^2) + 0x63)^2) + 0x183FA42B
ちゃんと書くと (((((0+0x61)^2) + 0x62)^2) + 0x63)^2) + 0x183FA42B

BASIC的に書くと(sum+asc(mid$(in$,i,1))^2+ 0x183FA42Bです。


今度は入力パスの変換ルーチンです。
Address 00401234 でフェークパスの「87654321」を Address 004030B0 へ押し込んで(push)から、
Address 00401239 でサブルーチンを呼んで入力パスを数値に変換しています。
キージェネを作るためには、「F7」キーで下記のサブルーチンの中を徘徊する必要があります。


004012D0 /$ 55 PUSH EBP
004012D1 |. 8BEC MOV EBP,ESP
004012D3 |. 8B75 08 MOV ESI,DWORD PTR SS:[EBP+8]
004012D6 |. B9 08000000 MOV ECX,8
004012DB |. 33DB XOR EBX,EBX
004012DD |> C1E3 04 /SHL EBX,4
004012E0 |. 0FBE06 |MOVSX EAX,BYTE PTR DS:[ESI]
004012E3 |. 83F8 30 |CMP EAX,30
004012E6 |. 72 2D |JB SHORT crkme08.00401315
004012E8 |. 83F8 39 |CMP EAX,39
004012EB |. 77 05 |JA SHORT crkme08.004012F2
004012ED |. 83E8 30 |SUB EAX,30
004012F0 |. EB 1C |JMP SHORT crkme08.0040130E
004012F2 |> 83F8 61 |CMP EAX,61
004012F5 |. 72 0A |JB SHORT crkme08.00401301
004012F7 |. 83F8 66 |CMP EAX,66
004012FA |. 77 05 |JA SHORT crkme08.00401301
004012FC |. 83E8 57 |SUB EAX,57
004012FF |. EB 0D |JMP SHORT crkme08.0040130E
00401301 |> 83F8 41 |CMP EAX,41
00401304 |. 72 0F |JB SHORT crkme08.00401315
00401306 |. 83F8 46 |CMP EAX,46
00401309 |. 77 0A |JA SHORT crkme08.00401315
0040130B |. 83E8 37 |SUB EAX,37
0040130E |> 03D8 |ADD EBX,EAX
00401310 |. 46 |INC ESI
00401311 |.^E2 CA \LOOPD SHORT crkme08.004012DD
00401313 |. EB 03 JMP SHORT crkme08.00401318
00401315 |> 33DB XOR EBX,EBX
00401317 |. 4B DEC EBX
00401318 |> 8BC3 MOV EAX,EBX
0040131A |. C9 LEAVE
0040131B \. C2 0400 RETN 4

入力パスを数値に変換している部分は、単純に値に変換しているので詳しい解説は省略します。

入力パスの変換ルーチンは、例えば"ABCDEF12"という文字列を eax = ABCDEF12 という値に変換していますが、
このときに16進数を無視して"ZYXRQER"などと打ち込んだらeax の値は、 eax = FFFFFFFFh となるはずです。
つまり、名前から値を生成する際、偶然にも名前からeax=FFFFFFFFhという値が
生成されてしまった場合は、パスにどんな値を入れても(16進数としての形態を満たさない文字列を入力した場合)当たりになってしまいます。


それでは、今回の答えの説明の戻ります。
Address 0040123E CMP EAX,EDX でCPUウィンドウの左下を見ると EAX=87654321 は読込んだフェークパス(87654321)そのまんまです。
EDX=091CB3BBとは何か? EDXはフェークネーム「abc」から生成したパスの結果です。
そして、次の分岐(ジャンプ)先の Address 0040128F は、求める「正解です。」の処理です。
ここへ分岐(ジャンプの中でも JE つまり Jump if Equal)する為には,その前の比較命令で比べているものが同じであれば良いことになります。

もう判ったと思いますが,ネーム「abc」に対するパスは「091CB3BB」です。