crackme#19 解析手引き

本日のお題「crack me #19」

この解説は、OllyDBG1.07b(英語版)を基準に書いてあります。
違う方はバージョンアップしてから始めてください。

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

Names in CRKME19
Address Section Type ( Name Comment
00401000 .text Export <ModuleEntryPoint>
00402000 .rdata Import ( KERNEL32.CloseHandle
00402004 .rdata Import ( KERNEL32.CreateFileA
00402008 .rdata Import ( KERNEL32.lstrcatA
0040200C .rdata Import ( KERNEL32.ReadFile
00402010 .rdata Import ( KERNEL32.GetFileSize
00402014 .rdata Import ( KERNEL32.ExitProcess
00402018 .rdata Import ( KERNEL32.GetModuleHandleA
00402020 .rdata Import ( USER32.MessageBeep
00402024 .rdata Import ( USER32.EndDialog
00402028 .rdata Import ( USER32.GetDlgItem
0040202C .rdata Import ( USER32.SetDlgItemTextA
00402030 .rdata Import ( USER32.SendMessageA
00402034 .rdata Import ( USER32.LoadIconA
00402038 .rdata Import ( USER32.DialogBoxParamA
0040203C .rdata Import ( USER32.EnableWindow
00402044 .rdata Import ( comdlg32.GetOpenFileNameA


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


References in CRKME19:.text to KERNEL32.CreateFileA
Address Disassembly Comment
0040112E CALL <JMP.&KERNEL32.CreateFileA>
0040139E JMP DWORD PTR DS:[<&KERNEL32.CreateFileA


Address 0040112E call <jmp.&KERNEL32.CreateFileA>を選択して、「Enter」キーを押してください。
CPUウィンドウのAddress 0040112E に操作が移ります。
そして、CPUウィンドウで、20行ほど上を表示するようにスクロールします。



004010A6 > FF75 FC PUSH DWORD PTR SS:[EBP-4] ; /hObject
004010A9 . E8 EA020000 CALL <JMP.&KERNEL32.CloseHandle> ; \CloseHandle
004010AE . BE 00304000 MOV ESI,CRKME19.00403000 ; ASCII "ユーザ名 : "0
004010B3 . C646 09 00 MOV BYTE PTR DS:[ESI+9],0
004010B7 . E9 E9010000 JMP CRKME19.004012A5
004010BC > C705 6F314000 >MOV DWORD PTR DS:[40316F],4C
004010C6 . FF75 08 PUSH DWORD PTR SS:[EBP+8]
004010C9 . 8F05 73314000 POP DWORD PTR DS:[403173]
004010CF . FF35 3E304000 PUSH DWORD PTR DS:[40303E]
004010D5 . 8F05 77314000 POP DWORD PTR DS:[403177]
004010DB . 68 57304000 PUSH CRKME19.00403057 ; ASCII "Key files (*.key)"
004010E0 . 8F05 7B314000 POP DWORD PTR DS:[40317B]
004010E6 . 68 BB314000 PUSH CRKME19.004031BB
004010EB . 8F05 8B314000 POP DWORD PTR DS:[40318B]
004010F1 . C705 8F314000 >MOV DWORD PTR DS:[40318F],104
004010FB . 6A 00 PUSH 0
004010FD . 8F05 9F314000 POP DWORD PTR DS:[40319F]
00401103 . C705 A3314000 >MOV DWORD PTR DS:[4031A3],81004
0040110D . 68 6F314000 PUSH CRKME19.0040316F ; /pOpenFileName = CRKME19.0040316F
00401112 . E8 AB020000 CALL <JMP.&comdlg32.GetOpenFileNameA> ; \GetOpenFileNameA
00401117 . 6A 00 PUSH 0 ; /hTemplateFile = NULL
00401119 . 68 80000000 PUSH 80 ; |Attributes = NORMAL
0040111E . 6A 03 PUSH 3 ; |Mode = OPEN_EXISTING
00401120 . 6A 00 PUSH 0 ; |pSecurity = NULL
00401122 . 6A 00 PUSH 0 ; |ShareMode = 0
00401124 . 68 00000080 PUSH 80000000 ; |Access = GENERIC_READ
00401129 . 68 BB314000 PUSH CRKME19.004031BB ; |FileName = ""
0040112E . E8 6B020000 CALL <JMP.&KERNEL32.CreateFileA> ; \CreateFileA


0040112E . E8 6B020000 CALL <JMP.&KERNEL32.CreateFileA> ; \CreateFileA

にAPIコールがあります。
ここでOpenCommonDialog(開くダイアログ)で指定されたファイルパスを取得してます。
さらに上を見ると、

004010DB . 68 57304000 PUSH CRKME19.00403057 ; ASCII "Key files (*.key)"

があります。
これは、ダイアログでの「ファイル種類」の設定のようです。
よって、デフォルトのファイル拡張子は、「.key」となります。

次は、Address 0040112E の下側を見てみましょう。


0040112E . E8 6B020000 CALL <JMP.&KERNEL32.CreateFileA> ; \CreateFileA
00401133 . 83F8 FF CMP EAX,-1
00401136 . 0F84 69010000 JE CRKME19.004012A5
0040113C . 8945 FC MOV DWORD PTR SS:[EBP-4],EAX
0040113F . 6A 00 PUSH 0 ; /pFileSizeHigh = NULL
00401141 . FF75 FC PUSH DWORD PTR SS:[EBP-4] ; |hFile
00401144 . E8 61020000 CALL <JMP.&KERNEL32.GetFileSize> ; \GetFileSize
00401149 . 8945 F8 MOV DWORD PTR SS:[EBP-8],EAX
0040114C . 83F8 32 CMP EAX,32
0040114F .^0F85 51FFFFFF JNZ CRKME19.004010A6
00401155 . 6A 00 PUSH 0 ; /pOverlapped = NULL
00401157 . 8D45 F4 LEA EAX,DWORD PTR SS:[EBP-C] ; |
0040115A . 50 PUSH EAX ; |pBytesRead
0040115B . FF75 F8 PUSH DWORD PTR SS:[EBP-8] ; |BytesToRead
0040115E . 8D85 74FFFFFF LEA EAX,DWORD PTR SS:[EBP-8C] ; |
00401164 . 50 PUSH EAX ; |Buffer
00401165 . FF75 FC PUSH DWORD PTR SS:[EBP-4] ; |hFile
00401168 . E8 49020000 CALL <JMP.&KERNEL32.ReadFile> ; \ReadFile


00401144 . E8 61020000 CALL <JMP.&KERNEL32.GetFileSize> ; \GetFileSize

にAPIコールがあります。
このAPIは、過去何回か使用されてますね。(ファイル・サイズの獲得です。)
そして、APIコールの下の方にファイル・サイズの比較があります。

0040114C . 83F8 32 CMP EAX,32

ここですね。
キー・ファイルのサイズは、50(0x32)バイトとなります。

それでは、まず Address 0040112E にブレークポイント(「F2」キー)設定します。
次にエディタ等で、空白50バイト・ファイル拡張子「.key」のファイルを作成します。
とりあえず、「serial.key」とでもしておきましょう。
「F9」キーで実行させます。

crackme #19のウィンドウが表示されました。
「ユーザー登録(R)」のボタンをクリックすると、「ファイルを開く」ダイアログが表示されます。
先程、作成したキー・ファイルを選択して、「開く」ボタンをクリックしてください。
ブレークポイントを仕掛けた、Address 0040112E で止まります。


そこから「F8」を数回押して、Address 0040115E にカーソルがくるまで実行させます。

0040115E . 8D85 74FFFFFF LEA EAX,DWORD PTR SS:[EBP-8C] ; |

CPUウィンドウの下に、Stack address=xxxxxxxとEAXレジスタ値が表示されてます。
このStack addressに、キー・ファイルの内容を読込むように指定してます。
ここで、Stack address=xxxxxxxを選択して、右クリック→Follow in Dumpで下のダンプ表示が変ります。

あと、このアドレスはメモしておいた方が良いです。


また、「F8」を数回押して、Address 0040116D にカーソルがくるまで実行させます。

0040116D . 83F8 FF CMP EAX,-1

ここで、ReadFileのリターン値チェックです。
今、EAXレジスタ値は、-1(0xffffffff)以外ですので、気にせず進めます。

「F7」キーを2回押すと、Address 00401176 で止まってます。

00401176 . 8DB5 74FFFFFF LEA ESI,DWORD PTR SS:[EBP-8C]

ここは、ESIレジスタに、キー・ファイル内容域のアドレスを代入してます。

下8行を見てください。

  0040117C . BF 46304000 MOV EDI,crkme19.00403046 ; ASCII "CRKME#19 KEYFILE"
  00401181 . B9 04000000 MOV ECX,4
+→00401186 > 8B06 MOV EAX,DWORD PTR DS:[ESI]
| 00401188 . 3B07 CMP EAX,DWORD PTR DS:[EDI]
| 0040118A .^0F85 16FFFFFF JNZ crkme19.004010A6
| 00401190 . 83C6 04 ADD ESI,4
| 00401193 . 83C7 04 ADD EDI,4
+←00401196 .^E2 EE LOOPD SHORT crkme19.00401186


  0040117C . BF 46304000 MOV EDI,crkme19.00403046 ; ASCII "CRKME#19 KEYFILE"


これは、ACSII文字列域にアドレスを、EDIレジスタに代入してます。
それで、Address 00401186~00401196 のループ処理ですが、
ESIレジスタは、読込んだキー・ファイルの内容(16文字)を示しています。

00401181 . B9 04000000 MOV ECX,4         ← ループ処理の回数です。(4回)
00401186 > 8B06 MOV EAX,DWORD PTR DS:[ESI]
00401188 . 3B07 CMP EAX,DWORD PTR DS:[EDI]

ここでキー・ファイルの内容を4バイト読込んで、ACSII文字列との比較です。

0040118A .^0F85 16FFFFFF JNZ crkme19.004010A6

↑4バイトが不一致の時ジャンプします。(ClodeHandle処理へ)
その下に、ESI・EDIレジスタそれぞれに4足して、ループ回数処理します。
キー・ファイルの内容先頭16文字が、ASCII "CRKME#19 KEYFILE"かの比較です。

それでは、読込んだ内容の先頭16文字を、変更します。
先程のダンプ表示(キー・ファイルの内容エリア:画面の左下の窓)を見て下さい。
変更方法は、ダンプ表示部分に、先頭からキー・ファイルの内容が表示されていますので、
そこから16バイト選択して、「Ctrl」+「e」(右クリック→バイナリ→編集)でデータ編集ウィンドウが表示されます。
で、ASCIIの部分に"CRKME#19 KEYFILE"を入力して「OK」を押して下さい。


「F7」キー数回押して、

004011A2 > 0FB60431 MOVZX EAX,BYTE PTR DS:[ECX+ESI]

にカーソルがくるまで実行させます。
ここで、キー・ファイルの内容17文字目から、1文字づつ取り出して処理してます。
そして下の方に、

004011C2 .^72 DE JB SHORT crkme19.004011A2

と現在行にジャンプする記述があります。
さらに、その上の記述は、

004011BF . 83F9 0C CMP ECX,0C

と比較してます。
ECXレジスタが、ループの回数を現しているようです。
この間では、以下2行を覚えていてください。(後で説明に出てきます。)

004011A8 . 81F3 80000000 XOR EBX,80
004011AE . 881C39 MOV BYTE PTR DS:[ECX+EDI],BL


先程の Address 004011C2 から3行下に

004011CB . 3B56 04 CMP EDX,DWORD PTR DS:[ESI+4]

と比較してますね。
このループが演算結果の比較のようですので、このAddress 004011CB にブレーク・ポイントを設定して、
「F9」で実行されてみましょう。

CPUウィンドウの下のEDXレジスタ値が、0x121c756bとなってます。
それを右クリック→Follow in Dump→Memory Addressでダンプを表示させ、4バイト選択後に、
「Ctrl」+「e」(右クリック→バイナリ→編集)でデータ編集ウィンドウからキー・ファイルの内容を変えて見ましょう。
今回は、レジスター値(一番下の欄)ですから上位下位反転させて入力します。 (0x6b751c12)

終わったら、「F7」キーを2回押してください。

Address 004011D4 で止まってます。

004011D4 . 33C0 XOR EAX,EAX

Address 004011D4~0040122F までキー・ファイルの内容を元にしての演算があります。
で、下の方に

00401231 . 81E2 FF000000 AND EDX,0FF

で 演算結果に対しての比較部分らしいのがあります。
ここにブレーク・ポイント(「F2」)を設定して実行(「F9」)させましょう。

この時、以下の演算結果を復帰させてます。

004011D6 . B9 03000000 MOV ECX,3
004011DB > 034431 0B ADD EAX,DWORD PTR DS:[ECX+ESI+B]
004011DF .^E2 FA LOOPD SHORT crkme19.004011DB
004011E1 . 50 PUSH EAX

これも、キー・ファイルの内容を元にしての演算です。
先程までの変更部分が合っていれば、Address 00401231 まで進みます。


00401231 . 81E2 FF000000 AND EDX,0FF   ←ここで止まってます。
00401237 . 85D2 TEST EDX,EDX
00401239 .^0F85 67FEFFFF JNZ crkme19.004010A6

この3行から、察するに演算結果ご、最下位1バイトが 0x00 にならないと行けないようですね。
CPUウィンドウの下で確認できる今の EDX レジスター値は、0x44eになってます。

さて、そこで

0040120D . 0FB65E 0B MOVZX EBX,BYTE PTR DS:[ESI+B]
00401211 . 03D3 ADD EDX,EBX
00401213 . 0FB64E 0D MOVZX ECX,BYTE PTR DS:[ESI+D]
00401217 . 03D1 ADD EDX,ECX
00401219 . 0FB646 0C MOVZX EAX,BYTE PTR DS:[ESI+C]

ここの辺りを注目してみます。
ここは、キー・ファイル値を足しこんでいるようです。
ここで、最下位1バイトが 0x00 になるよな値を、足して上げればいいようです。

00401219 . 0FB646 0C MOVZX EAX,BYTE PTR DS:[ESI+C]

ここで、補正するようにしましょう。
ここで再起動(「Ctrl」+「F2」)し、address 00401219 までトレースし直します。
間違っても先程の、Address 004011D4 から「F9」は押さないようにして下さい。(「F8」でずっと来るのが良いでしょう。)
Address 00401219 まで来たら、今までのようにキーファイルの内容を変更しますが、変更する値がわかっていなければなりませんね。

で、逆算します 0x100 - 0x4e + 0x20 です。 (0x20は、現在値です。)
答えは、0xd2です。
Address 00401219 の行を選択して、右クリック→Follow in Dump→Memory Addressでダンプを表示させます。
ダンプ画面の左上端の0x20を0xd2に変更します。



変更されたキー・ファイル内容は、以下の通りです。

-------------------------------- Start --------------------------------
00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F
 
0000 43 52 4B 4D 45 23 31 39 20 4B 45 59 46 49 4C 45 CRKME#19 KEYFILE
0010 20 20 20 20 20 20 20 20 20 20 20 20 6B 75 1C 12 ku..
0020 20 20 20 20 20 20 20 20 20 20 20 20 D2 20 20 20 .
0030 20 20
-------------------------------- E n d --------------------------------

ここで、バイナリ・エディタの登場です。

その前に、OllyDBGのメニュー 「Debug」→「Close」で、crkme19.exe を閉じてください。
キー・ファイルをバイナリ・エディタで読込んでください。
見やすく、変更部分のみ色替えしてます。

変更が終わったら保存して、閉じて下さい。

今度はOllyDBGで、「Ctrl」+「F2」で、再起動してください。
何故か、EXEファイルを読まなくても動きます。

「F9」でcrackme#19を起動させ、キー・ファイルを読込んで
Address 00401231 まで実行させてください。
キーファイル変更後の EDX レジスター値は、 0x500 になってます。
これで、下の条件もクリアーでしょう。

「F7」キーを、3回押して

0040123F . 58 POP EAX

にカーソルがくるまで実行させます。
ここで、スタック域から EAXレジスタ に値を復帰させてます。
「F7」キーを1回押して、EAXレジスタに値を、復帰させます。

00401240 . 3B46 0E CMP EAX,DWORD PTR DS:[ESI+E]

ここで演算結果との比較ですね。
その前に、ファイル内容を変更します。

00401240 . 3B46 0E CMP EAX,DWORD PTR DS:[ESI+E]

を選択して、右クリック→Follow in Dump→Memory Addressでダンプを表示させます。
CPUウィンドウの下で確認できる EAXレジスタ値は、0x60606060 ですね。
ダンプ表示した4バイトを EAX のレジスタ値 0x60606060 に「Ctrl」+「e」(右クリック→バイナリ→編集)で
データ編集ウィンドウからキー・ファイルの内容を変えて見ましょう。

変更したら「F7」キーを2回押してください。

00401249 . 8DB5 34FFFFFF LEA ESI,DWORD PTR SS:[EBP-CC]

ここで止まります。
Address 00401249 を選択して、右クリック→Follow in Dump→Memory Addressでダンプを表示させてください。

A0 A0 A0 A0 A0 A0 A0 A0 A0 A0 A0 A0

と12バイト並んでます。


これは、始めの方でメモした方が良いと言った部分の結果が入ってます。

004011A8 . 81F3 80000000 XOR EBX,80
004011AE . 881C39 MOV BYTE PTR DS:[ECX+EDI],BL


ここですね。

EDX レジスタには、キー・ファイルの17バイト~28バイトの値が1バイトづつ読込まれて入ります。
0x80 との XOR の結果を BYTE PTR DS:[ECX+EDI] に代入です。
今は、全て 0x20 なので、0xA0 となってます。

さらに下の処理に注目すると、

0040124F . B9 0C000000 MOV ECX,0C
00401254 > 0FB64431 FF MOVZX EAX,BYTE PTR DS:[ECX+ESI-1]
00401259 . 85C0 TEST EAX,EAX
0040125B . 74 12 JE SHORT crkme19.0040126F
0040125D . 83F8 20 CMP EAX,20
00401260 .^0F82 40FEFFFF JB crkme19.004010A6
00401266 . 83F8 7E CMP EAX,7E
00401269 .^0F87 37FEFFFF JA crkme19.004010A6
0040126F >^E2 E3 LOOPD SHORT crkme19.00401254

ここで、英数字文字かの判定をしてます。
その処理後、

00401271 . 56 PUSH ESI ; /StringToAdd
00401272 . 68 00304000 PUSH crkme19.00403000 ; |ConcatString = "ユーザ名 : "
00401277 . E8 40010000 CALL <JMP.&KERNEL32.lstrcatA> ; \lstrcatA

lstrcatA で、"ユーザ名 : " の後に文字列連結してます。

っとなると、17バイト~28バイトの部分が、ユーザ名ですね。
では、ユーザー名を、#serial にしてみましょう。

'#' 's' 'e' 'r' 'i' 'a' 'l'
0x23 0x73 0x65 0x72 0x69 0x61 0x6c  コレに、 xor 0x80 します。

        ↓

0xa3 0xf3 0xe5 0xf2 0xe9 0xe1 0xec  ← これを、ユーザー名部分に入れます。


変更されるキー・ファイル内容は、以下の通りです。

-------------------------------- Start --------------------------------
00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F

0000 43 52 4B 4D 45 23 31 39 20 4B 45 59 46 49 4C 45 CRKME#19 KEYFILE
0010 A3 F3 E5 F2 E9 E1 EC A0 A0 A0 A0 A0 6B 75 1C 12
0020 20 20 20 20 20 20 20 20 20 20 20 20 D2 20 60 60
0030 60 60
-------------------------------- E n d --------------------------------

又、OllyDBGのメニュー 「Debug」→「Close」で、crkme19.exe を閉じてください。
フェーク・キー・ファイルをバイナリ・エディタで読込んで、編集・保存してください。
変更後は、OllyDBGを「Ctrl」+「F2」で、再起動してください。

「F9」でcrackme#19を起動させ、キー・ファイルを読込んで
Address 004011CB まで実行させてください。

004011CB . 3B56 04 CMP EDX,DWORD PTR DS:[ESI+4]

ここで、結果値が変ってます。 (ここは、ユーザー名の部分で演算しているので当然ですね。)
Address 004011CB の行を選択して、右クリック→Follow in Dump→Memory Addressでダンプを表示させます。
EDXレジスタ値と同じになるように、再度変更します。

EDXレジスタ値が 0xDDE073E5 となっていると思いますので、4バイト選択後に、「Ctrl」+「e」
(右クリック→バイナリ→編集)でデータ編集ウィンドウからキー・ファイルの内容を変えて見ましょう。
レジスター値(一番下の欄)ですから上位下位反転させて入力します。 (0xE573E0DD)



変更後、「F9」で実行し、
Address 00401231 まで実行させてください。

00401231 . 81E2 FF000000 AND EDX,0FF

ここでも、結果値が変ってます。
今の EDXレジスタ値が、 0x0eca です。
今回は、 0xd2 - 0xca = 0x08 としましょう。


という事で、キー・ファイルを変更して確認しましょう。

キー・ファイル内容は、以下の通りです。

-------------------------------- Start --------------------------------
00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F
 
0000 43 52 4B 4D 45 23 31 39 20 4B 45 59 46 49 4C 45 CRKME#19 KEYFILE
0010 A3 F3 E5 F2 E9 E1 EC A0 A0 A0 A0 A0 E5 73 E0 DD
0020 20 20 20 20 20 20 20 20 20 20 20 20 08 20 60 60
0030 60 60
-------------------------------- E n d --------------------------------

又、OllyDBGのメニュー 「Debug」→「Close」で、crkme19.exe を閉じてください。
キー・ファイルをバイナリ・エディタで編集・保存してください。
その後、OllyDBGを「Ctrl」+「F2」で、再起動してください。

「F9」でcrackme#19を起動させ、キー・ファイルを読込んで
一気に進んでください。
ユーザ名に、"#serial"と表示されれば成功です。