crackme#18 解析手引き

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

今回は試用期限解除です。
パッチ当ては色々な方法があり、ここで挙げるのはその一例です。

一度crkme18.exeを起動してみましょう。
crackme #18のウィンドウが表示され、なかに試用期限が7日間までと表示されているかと思います。
「試用する」を押すと、#13の時と同じウィンドウが開いていると思います。

では、windowsの時計を8日以上進めて起動し直してみて下さい。
エラー画面「試用期限が終了しました」が出力されたかと思います。
終了ボタンを押して終らさせて下さい。

試用期限が7日間までとなっていますので、前のようにただ、windowsから時間(日付)を取得して判断している
訳ではなく、どこかに情報を格納して判断しているらしいというのが分かります。

と、いうことでそれらしいプログラムを探しましょう。

OllyDBGを、起動して「crkme18.exe」を読込んでください。

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

Names in crkme18
Address Section Type (Known) Name Comment
00401000 .text Export <ModuleEntryPoint>
00402000 .rdata Import (Known) ADVAPI32.RegSetValueExA
00402004 .rdata Import (Known) ADVAPI32.RegQueryValueExA
00402008 .rdata Import (Known) ADVAPI32.RegCreateKeyExA
0040200C .rdata Import (Known) ADVAPI32.RegCloseKey
00402014 .rdata Import (Known) GDI32.CreateFontA
0040201C .rdata Import (Known) KERNEL32.lstrcatA
00402020 .rdata Import (Known) KERNEL32.GetSystemTime
00402024 .rdata Import (Known) KERNEL32.GetModuleHandleA
00402028 .rdata Import (Known) KERNEL32.ExitProcess
00402030 .rdata Import (Known) USER32.LoadCursorA
00402034 .rdata Import (Known) USER32.PostQuitMessage
00402038 .rdata Import (Known) USER32.RegisterClassExA
0040203C .rdata Import (Known) USER32.SendMessageA
00402040 .rdata Import (Known) USER32.LoadIconA
00402044 .rdata Import (Known) USER32.MessageBeep
00402048 .rdata Import (Known) USER32.ShowWindow
0040204C .rdata Import (Known) USER32.TranslateMessage
00402050 .rdata Import (Known) USER32.UpdateWindow
00402054 .rdata Import (Known) USER32.GetSystemMetrics
00402058 .rdata Import (Known) USER32.GetMessageA
0040205C .rdata Import (Known) USER32.GetDlgItem
00402060 .rdata Import (Known) USER32.EndDialog
00402064 .rdata Import (Known) USER32.EnableWindow
00402068 .rdata Import (Known) USER32.DispatchMessageA
0040206C .rdata Import (Known) USER32.DialogBoxParamA
00402070 .rdata Import (Known) USER32.DestroyWindow
00402074 .rdata Import (Known) USER32.DefWindowProcA
00402078 .rdata Import (Known) USER32.CreateWindowExA
0040207C .rdata Import (Known) USER32.SetDlgItemTextA
00402080 .rdata Import (Known) USER32.SetFocus

前のようにGetLocalTimeというのはありませんね。代わりにGetSystemTimeというのがありました。
また、上の方にレジストリに関するラベルもありました。
ということは、取得した時間をレジストリに格納して判断しているように思われます。
では、GetSystemTimeから潜ってみましょう。

「KERNEL32.GetSystemTime」を選択して、「Enter」キーを押してください。
「References in crkme18:.text to KERNEL32.GetSystemTime」というウィンドウが表示されます。


References in crkme18:.text to KERNEL32.GetSystemTime
Address Disassembly Comment
00401574 CALL <JMP.&KERNEL32.GetSystemTime>
0040175E JMP DWORD PTR DS:[<&KERNEL32.GetSystemTi KERNEL32.GetSystemTime


00401574 CALL <JMP.&KERNEL32.GetSystemTime>を選択して、「F2」キーでブレークポイントを設定してください。

「F9」キーで動かしてみましょう。
先程ブレークポイントを仕掛けた場所で止まります。


0040156A /$ 55 PUSH EBP
0040156B |. 8BEC MOV EBP,ESP
0040156D |. 83C4 D4 ADD ESP,-2C
00401570 |. 8D45 DC LEA EAX,DWORD PTR SS:[EBP-24]
00401573 |. 50 PUSH EAX ; /pSystemTime
00401574 |. E8 E5010000 CALL <JMP.&KERNEL32.GetSystemTime> ; \GetSystemTime
00401579 |. FF75 E8 PUSH DWORD PTR SS:[EBP-18]
0040157C |. FF75 E4 PUSH DWORD PTR SS:[EBP-1C]
0040157F |. FF75 E0 PUSH DWORD PTR SS:[EBP-20]
00401582 |. FF75 DC PUSH DWORD PTR SS:[EBP-24]
00401585 |. E8 68FDFFFF CALL crkme18.004012F2
0040158A |. 8B75 08 MOV ESI,DWORD PTR SS:[EBP+8]
0040158D |. 8906 MOV DWORD PTR DS:[ESI],EAX
0040158F |. EB 1C JMP SHORT crkme18.004015AD
00401591 |. 53 6F 66 74 77>ASCII "Software\eagle0w"
004015A1 |. 6C 5C 63 72 61>ASCII "l\crackme18",0
004015AD |> 8D45 EC LEA EAX,DWORD PTR SS:[EBP-14]
004015B0 |. 50 PUSH EAX ; /pDisposition
004015B1 |. 8D45 F0 LEA EAX,DWORD PTR SS:[EBP-10] ; |
004015B4 |. 50 PUSH EAX ; |pHandle
004015B5 |. 6A 00 PUSH 0 ; |pSecurity = NULL
004015B7 |. 68 3F000F00 PUSH 0F003F ; |Access = KEY_ALL_ACCESS
004015BC |. 6A 00 PUSH 0 ; |Options = REG_OPTION_NON_VOLATILE
004015BE |. 6A 00 PUSH 0 ; |Class = NULL
004015C0 |. 6A 00 PUSH 0 ; |Reserved = 0
004015C2 |. 68 91154000 PUSH crkme18.00401591 ; |Subkey = "Software\eagle0wl\crackme18"
004015C7 |. 68 01000080 PUSH 80000001 ; |hKey = HKEY_CURRENT_USER
004015CC |. E8 A5010000 CALL <JMP.&ADVAPI32.RegCreateKeyExA> ; \RegCreateKeyExA
004015D1 |. 837D EC 02 CMP DWORD PTR SS:[EBP-14],2
004015D5 |. 75 36 JNZ SHORT crkme18.0040160D
004015D7 |. C745 D8 040000>MOV DWORD PTR SS:[EBP-28],4
004015DE |. C745 D4 040000>MOV DWORD PTR SS:[EBP-2C],4
004015E5 |. 8B75 0C MOV ESI,DWORD PTR SS:[EBP+C]
004015E8 |. 6A 00 PUSH 0
004015EA |. 8D45 D4 LEA EAX,DWORD PTR SS:[EBP-2C]
004015ED |. 50 PUSH EAX ; /pBufSize
004015EE |. FF75 0C PUSH DWORD PTR SS:[EBP+C] ; |Buffer
004015F1 |. 8D45 D8 LEA EAX,DWORD PTR SS:[EBP-28] ; |
004015F4 |. 50 PUSH EAX ; |pValueType
004015F5 |. 6A 00 PUSH 0 ; |Reserved = NULL
004015F7 |. 68 AE304000 PUSH crkme18.004030AE ; |ValueName = "Date"
004015FC |. FF75 F0 PUSH DWORD PTR SS:[EBP-10] ; |hKey
004015FF |. E8 78010000 CALL <JMP.&ADVAPI32.RegQueryValueExA> ; \RegQueryValueExA
00401604 |. 8B55 0C MOV EDX,DWORD PTR SS:[EBP+C]
00401607 |. 85C0 TEST EAX,EAX
00401609 |. 75 02 JNZ SHORT crkme18.0040160D
0040160B |. EB 23 JMP SHORT crkme18.00401630

0040160D |> 8B75 08 MOV ESI,DWORD PTR SS:[EBP+8]
00401610 |. 6A 01 PUSH 1
00401612 |. 6A 04 PUSH 4 ; /BufSize = 4
00401614 |. 56 PUSH ESI ; |Buffer
00401615 |. 6A 04 PUSH 4 ; |ValueType = REG_DWORD
00401617 |. 6A 00 PUSH 0 ; |Reserved = 0
00401619 |. 68 AE304000 PUSH crkme18.004030AE ; |ValueName = "Date"
0040161E |. FF75 F0 PUSH DWORD PTR SS:[EBP-10] ; |hKey
00401621 |. E8 5C010000 CALL <JMP.&ADVAPI32.RegSetValueExA> ; \RegSetValueExA
00401626 |. 8B75 08 MOV ESI,DWORD PTR SS:[EBP+8]
00401629 |. 8B06 MOV EAX,DWORD PTR DS:[ESI]
0040162B |. 8B75 0C MOV ESI,DWORD PTR SS:[EBP+C]
0040162E |. 8906 MOV DWORD PTR DS:[ESI],EAX
00401630 |> FF75 F0 PUSH DWORD PTR SS:[EBP-10] ; /hKey
00401633 |. E8 38010000 CALL <JMP.&ADVAPI32.RegCloseKey> ; \RegCloseKey
00401638 |. 58 POP EAX
00401639 |. C9 LEAVE
0040163A \. C2 0800 RETN 8


00401573 |. 50 PUSH EAX ; /pSystemTime
00401574 |. E8 E5010000 CALL <JMP.&KERNEL32.GetSystemTime> ; \GetSystemTime

ここで時間を取得しているようですね。
同じルーチン内をずっと下まで見ていくと、レジストリで何かしている部分があるのがわかりました。
ということは、00401574で取得した時間を00401585のコール先で計算、結果をレジストリに入れて判断しているとみて
間違いなさそうです。

ここで簡単にレジストリキーについて説明します。


004015CC |. E8 A5010000 CALL <JMP.&ADVAPI32.RegCreateKeyExA> ; \RegCreateKeyExA   指定されたキーを作ります。もしそのキーが既に存在する場合はそれを開きます。

004015FF |. E8 78010000 CALL <JMP.&ADVAPI32.RegQueryValueExA> ; \RegQueryValueExA  開いたキーに関連する指定された値のタイプやデータを取得します。

00401621 |. E8 5C010000 CALL <JMP.&ADVAPI32.RegSetValueExA> ; \RegSetValueExA   キーに関連する指定された値のタイプやデータをセットします。

00401633 |. E8 38010000 CALL <JMP.&ADVAPI32.RegCloseKey> ; \RegCloseKey   レジストリキーを閉じます。



と言った感じです。
これを参考に、ここで注目した所は、

004015D5 |. 75 36 JNZ SHORT crkme18.0040160D

と、

00401609 |. 75 02 JNZ SHORT crkme18.0040160D
0040160B |. EB 23 JMP SHORT crkme18.00401630


の3行です。
試用期限内だと、0040160Dへ飛び、期限切れ後は飛ばずに00401630へ飛びます。
004015D5 と 00401609 で飛んだ先は、

0040160D |> 8B75 08 MOV ESI,DWORD PTR SS:[EBP+8]
00401610 |. 6A 01 PUSH 1
00401612 |. 6A 04 PUSH 4 ; /BufSize = 4
00401614 |. 56 PUSH ESI ; |Buffer
00401615 |. 6A 04 PUSH 4 ; |ValueType = REG_DWORD
00401617 |. 6A 00 PUSH 0 ; |Reserved = 0
00401619 |. 68 AE304000 PUSH crkme18.004030AE ; |ValueName = "Date"
0040161E |. FF75 F0 PUSH DWORD PTR SS:[EBP-10] ; |hKey
00401621 |. E8 5C010000 CALL <JMP.&ADVAPI32.RegSetValueExA> ; \RegSetValueExA
00401626 |. 8B75 08 MOV ESI,DWORD PTR SS:[EBP+8]
00401629 |. 8B06 MOV EAX,DWORD PTR DS:[ESI]
0040162B |. 8B75 0C MOV ESI,DWORD PTR SS:[EBP+C]
0040162E |. 8906 MOV DWORD PTR DS:[ESI],EAX

この部分で処理が行われています。
つまり、レジストリキーが開いてから計算・比較される EBP の値を RegSetValueExA にセットする事で
チェックしています。

そして、試用期限内であるという判断をさせていることになりますね?
と言う事は、強制的にこの処理へ行くようにしてあげれば良いようです。
では、004015D5を変更してみましょう。

004015D5 |. 75 36 JNZ SHORT crkme18.0040160D

変更方法は、00401609を選択し、右クリック→逆アセ修正でアセンブル画面を出します。
JNZ SHORT crkme18.0040160D を JMP SHORT crkme18.0040160D へ変更します。
もしくは、Ctrl + E(右クリック→バイナリ→編集)でコード編集画面をだします。
Hex +00 の欄に表示されている 75 36 を EB 36 へ変更して下さい。
どちらも同じ結果を反映します。

変更後は、

004015D5 EB 36 JMP SHORT crkme18.0040160D

となります。
または、

00401609 |. 75 02 JNZ SHORT crkme18.0040160D



00401609 EB 02 JMP SHORT crkme18.0040160D

へ、もしくは
 
0040160B |. EB 23 JMP SHORT crkme18.00401630

の行を

0040160B 75 23 JNZ SHORT crkme18.00401630

に変更しします。
どれか1箇所を変更出来たら最初に仕掛けた 00401574 のBPを「F2」キーで解除して、
「F9」キーで実行してみましょう。
時計は進めてあるので、crackme #18のウィンドウが表示されたら成功です。



バイナリを編集して結果を反映させたい場合は変更箇所は次の通りです。

==============
FILENAME crkme18.exe
000009D5: 75 EB
==============
FILENAME crkme18.exe
00000A09: 75 EB
==============
FILENAME crkme18.exe
00000A0B: EB 75
==============




=補足=

いちばん簡単なのはバッチファイルでレジストリをクリアしてから起動することですが、
レジストリ消去の欠点は、有る程度ナグを我慢する必要があるということです。

crackme#18 試用期限情報初期化ツールを作ってしまうという手もあります。

その他、どれだけ綺麗なパッチあてれるかの検証として、命令を変呼応するより比較命令を変更する等の
やり方や、起動日を常にレジストリに書き込むようにする方法もあります。

(wipipiさんの場合)

004011D3 8B55 F8 MOV EDX,DWORD PTR SS:[EBP-8]

から

004011D3 8B55 F4 MOV EDX,DWORD PTR SS:[EBP-C]
000005D5 F8 F4

こんなです(^^; 直接は飛びません(汗
または、

004015D1 |. 837D EC 02 CMP DWORD PTR SS:[EBP-14],2

から

004015D1 |. 837D EC 01 CMP DWORD PTR SS:[EBP-14],1



(mom[kuma]さんの場合)
重要部分

004015FF |. E8 78010000 CALL <JMP.&ADVAPI32.RegQueryValueExA> ; \RegQueryValueExA
00401604 |. 8B55 0C MOV EDX,DWORD PTR SS:[EBP+C]
00401607 |. 85C0 TEST EAX,EAX
00401609 |. 75 02 JNZ SHORT crkme18.0040160D

より、

00401607 33C0 XOR EAX,EAX
00401609 EB 02 JMP SHORT crkme18.0040160D

これで毎回書きかな