crackme#04a 解析手引き

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

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

Names in crkme04a
アドレス  セクション  タイプ ( 名前 コメント 
00401000 .text Export <ModuleEntryPoint>
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」キーを押してください。
「References in crkme04a:.text to USER32.GetWindowTextA」というウィンドウが表示されます。

References in crkme04a:.text to USER32.GetWindowTextA
アドレス  逆アセンブル  コメント 
004011F6 CALL <JMP.&USER32.GetWindowTextA>
0040128E JMP DWORD PTR DS:[<&USER32.GetWindowText USER32.GetWindowTextA

Address 004011F6 call <jmp.&USER32.GetWindowTextA>を選択して、「F2」キーでブレークポイントを設定してください。
「F9」キーで動かしてみましょう。

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

004011E9 > 6A 7E PUSH 7E ; /Count = 7E (126.)
004011EB . 68 2D304000 PUSH crkme04a.0040302D ; |Buffer = crkme04a.0040302D
004011F0 . FF35 04304000 PUSH DWORD PTR DS:[403004] ; |hWnd = 003F022E (class='Edit',parent=001101A0)
004011F6 . E8 93000000 CALL <JMP.&USER32.GetWindowTextA> ; \GetWindowTextA   ←ココ
004011FB . E8 1D000000 CALL crkme04a.0040121D
00401200 . 85C0 TEST EAX,EAX
00401202 . 74 14 JE SHORT crkme04a.00401218
00401204 . 6A 00 PUSH 0 ; /Style = MB_OK|MB_APPLMODAL
00401206 . 68 F5304000 PUSH crkme04a.004030F5 ; |Title = "登録情報"0403
0040120B . 68 FE304000 PUSH crkme04a.004030FE ; |Text = "正解です!"04030
00401210 . FF75 08 PUSH DWORD PTR SS:[EBP+8] ; |hOwner
00401213 . E8 82000000 CALL <JMP.&USER32.MessageBoxA> ; \MessageBoxA
00401218 >^E9 81FEFFFF JMP crkme04a.0040109E

ブレークポイントの次行を見てみましょう。

004011FB call crkme04a.0040121D
00401200 test eax,eax
00401202 je short crkme04a.00401218

っと、なっています。
call crkme04a.0040121Dの後に、test eax,eaxで、eaxレジスタ値をチェックして、
eaxレジスタが0(ゼロ)の時に、je short crkme04a.00401218 でジャンプします。
正解パスが、入力された時は、jeでジャンプせずに、そのまま、下に実行されていきます。

では、「F8」と「F7」を押して、call crkme04a.0040121D のコールの中に入ります。

0040121D /$ 68 2D304000 PUSH crkme04a.0040302D ; ASCII "12345678"
00401222 |. E8 21000000 CALL crkme04a.00401248
00401227 |. 8BD8 MOV EBX,EAX
00401229 |. C1E8 10 SHR EAX,10
0040122C |. 81E3 FFFF0000 AND EBX,0FFFF
00401232 |. 3BC3 CMP EAX,EBX
00401234 |. 77 0F JA SHORT crkme04a.00401245
00401236 |. F7E3 MUL EBX
00401238 |. 3D 954ABA99 CMP EAX,99BA4A95
0040123D |. 75 06 JNZ SHORT crkme04a.00401245
0040123F |. B8 01000000 MOV EAX,1
00401244 |. C3 RETN
00401245 |> 33C0 XOR EAX,EAX
00401247 \. C3 RETN

さらに、「F7」を押してください。

00401222 call crkme04a.00401248

この行は、とりあえず「F8」で飛ばしてしまいましょう。

今のeaxレジスタの値が、0x00BC614E(16進数)となっていると思います。
この値を、10進数に直しますと、12345678となります。

この結果からして、call crkme04a.00401248 では、文字の"12345678"を、数字に変換しています。
次に、「F7」を押してください。 eaxレジスタの値が、ebxレジスタに代入されます。
カーソルが、

00401229 |. C1E8 10 SHR EAX,10

の行に来ていると思います。
ここでは、eax 2 を 16回、行います。 つまり、eaxレジスタ値を、16bit右にシフトします。

「F7」を押してください。次は、

0040122C |. 81E3 FFFF0000 AND EBX,0FFFF

です。 これは、ebxレジスタの上位2byteを、クリアしてます。

「F7」を押してください。

00401232 |. 3BC3 CMP EAX,EBX
00401234 |. 77 0F JA SHORT crkme04a.00401245

この2行は、eaxレジスタ値が、ebxレジスター値より、大きい時にジャンプします。
幸いに、今のフェークパスでは、上記の条件を満たしていないので、無視して実行します。

F7」を2回 押してください。

00401236 |. F7E3 MUL EBX

の行に来ていると思います。

ここでは、かけ算を行っています。 記述では、ebxレジスタしかありませんが、
eaxレジスタ ラ ebxレジスタ の結果が、eaxレジスタに入ります。
そして、次の行を注目してみましょう。
00401238 |. 3D 954ABA99 CMP EAX,99BA4A95

eaxレジスタとの比較ですね
要するに、mul ebx の結果が、0x99BA4A95(16進数)になれば、正解となります。


ここまでを、もう少し分かりやすく噛み砕いてみましょう。
入力パスを「98765432」として説明させて頂きます。

0040121D /$ 68 2D304000 push crkme.0040302D ; 入力文字列のアドレス "98765432"
00401222 |. E8 21000000 call crkme.00401248 ; 文字列->数字ルーチン
00401227 |. 8BD8 mov ebx, eax ; eax には 0x05E30A78 (98765432) が入り、
; ebx レジスタに値がコピーされる
00401229 |. C1E8 10 shr eax, 10 ; eax レジスタに対し右に 0x10(16)bitシフト
0040122C |. 81E3 FFFF0000 and ebx, 0FFFF ; ebx レジスタの下位 16bit(2byte) を取る
00401232 |. 3BC3 cmp eax, ebx ;
00401234 |. 77 0F ja short crkme.00401245 ; eax > ebx ならば残念賞
00401236 |. F7E3 mul ebx ; eax * ebx = edx(上位32bit) eax (下位32bit)
00401238 |. 3D 954ABA99 cmp eax, 99BA4A95 ;
0040123D |. 75 06 jnz short crkme.00401245 ; eax != 0x99BA4A95 ならば残念賞
0040123F |. B8 01000000 mov eax, 1 ; ここにくれば正解だ!
00401244 |. C3 retn
00401245 |> 33C0 xor eax, eax ; 残念賞授与
00401247 \. C3 retn

コメントを入れてみましたが、もうちょい詳しく説明します。

>00401229 |. C1E8 10 SHR EAX,10
>0040122C |. 81E3 FFFF0000 AND EBX,0FFFF

ですが、各処理の前は、eax, ebx レジスタには共に入力文字列から変換した値
(この場合 0x05E30A78 )が入っているはずです。まず、

SHR EAX, 10

ですが、これは eax レジスタに格納されている値を 16(0x10)bit = 2byte ぶん右に
ずらすことを指します。この場合であれば

eax = 05E30A78 <- 命令実行前の値
eax = 000005E3 0A78 <- 16bit (2byte) ぶんずらした後(0A78 は消える)

となります。次に

AND EBX,0FFFF

ですが、これは ebx レジスタに格納されている値に対し、0FFFF で and 演算を行う
ことを指します。(命令実行前の値は 05E30A78 )

05E30A78 <- 命令実行前の値
and) 0000FFFF <- 逆汗リストは "0FFFF" ですが、ゼロを補完すると考えやすいです。
--------------
00000A78 <- 命令実行後の値 (ebx レジスタに格納される)

ここで eax=000005E3, ebx=00000A78 となりましたが、これは
元の値 05E30A78 を [05E3][0A78] このような感じで2つにパキッと分けて考えることが出来ます。




ここで、今までの事を簡単にまとめますと、
正解パス('0'から'9'の文字列)を、数字に変換して、
上位2Byte * 下位2Byte = 0x99BA4A95(16進数) (※ 上位2Byte < 下位2Byte の条件あり)
となってます。
入力したパスを、2つの数値に分けて、それらを掛け算した答えが0x99BA4A95かどうかを調べてます。
この問いの答えは何度トレースしても出てきません。総当たりでの確認になります。
素因数分解なら初歩のアルゴリズム本を見れば載ってます。


a*b=0x99BA4A95から、b=0x99BA4A95/a として、
ぴったり割り切れる(余りの出ない)aを探すのも有効です。

この場合、素因数分解ソフトも有効です。
ただし、今回のように2つの数値が素数でないと使えません。
「素因数分解電卓 1.11」 
http://www.vector.co.jp/soft/dl/win95/edu/se065176.html 

正解:42197*61121 2765483713