セキュリティを楽しく学ぶ、触れる。セキュリティごった煮ブログ

ネットエージェント
セキュリティごった煮ブログ

 コース:元祖こってり

「元祖こってり」記事はネットエージェント旧ブログ[netagent-blog.jp]に掲載されていた記事であり、現在ネットエージェントに在籍していないライターの記事も含みます。

NetAgent Security Contest 2010 解答 Level5 ~ Level8 (1/2)

愛甲健二

 こんにちは、愛甲です。今回は、2010年11月27~28日に当サイトで行われたNetAgent Security Contest 2010のLevel5~Level8までの解答を行いたいと思います。問題文とファイルも公開していますので、興味がある方、またはコンテストに参加できなかった方は、よろしければ挑戦してみてください。

-----

■Level5 Crack ZIP files (FILE)
NASecConPic13 Level5は、Level2で見つけた暗号化されているxyz.zipファイルを解析し、中に入っているhoukoku.zipを取り出す問題です。まずポイントとして、xyz.zipの中にはa.txt、kaigi1.jpg、kaigi2.jpgが入っていること。そして、これらはサイズから考えてもxyz.zipと同じディレクトリにあるものと同一であるということが挙げられます。
NASecConPic11 つまり、xyz.zipの中の一部のファイルに関しては、同一のものがすでに手元にある状態と言えます。また、ヒントの「Paul C. Kocher」と「zip」という単語で検索すると、Eli Biham & Paul C. KocherのZIPクラックに関する論文にたどりつきます。
 詳細な理論は論文を読んでいただくとして、内容を簡単に説明すると、暗号化されたZIPファイルの中に存在する任意のファイルと同一のファイルをすでに保持している場合、ZIP内のそれ以外のファイルも抜き出せる(復号できる)可能性がある、というものです。そして、それを行うツールpkcrackも公開されています。
 今回の場合は、暗号化されたZIPファイルの中に存在する任意のファイルと同一のファイルとして、「a.txt」「kaigi1.jpg」「kaigi2.jpg」の3つがあります。この3つのいずれかを利用して、それ以外のファイル、つまりhoukoku.zipを取り出します。

# unzip xyz.zip
Archive: xyz.zip
[xyz.zip] houkoku.zip password:
skipping: houkoku.zip incorrect password
skipping: a.txt incorrect password
skipping: kaigi1.jpg incorrect password
skipping: kaigi2.jpg incorrect password

 見ての通り、パスワードロックがかかっています。上記のツール(pkcrack)を使用して、xyz.zipの中のhoukoku.zipを取り出します。

# ./pkcrack-1.2.2/src/pkcrack -C xyz.zip -c kaigi1.jpg [折返し]
-P kaigi1.zip -p kaigi1.jpg -d houkoku.zip
Files read. Starting stage 1 on Sat Nov 20 16:22:23 2010
Generating 1st generation of possible key2_57064 values...done.
Found 4194304 possible key2-values.
Now we're trying to reduce these...
Lowest number: 879 values at offset 45937
Lowest number: 864 values at offset 45925
(省略)
Lowest number: 337 values at offset 44136
Done. Left with 337 possible Values. bestOffset is 44136.
Stage 1 completed. Starting stage 2 on Sat Nov 20 16:23:23 2010
Ta-daaaaa! key0=dd26feae, key1=12282822, key2=b236d71f
Probabilistic test succeeded for 12933 bytes.
Ta-daaaaa! key0=dd26feae, key1=12282822, key2=b236d71f
Probabilistic test succeeded for 12933 bytes.
(省略)
Ta-daaaaa! key0=dd26feae, key1=12282822, key2=b236d71f
Probabilistic test succeeded for 12933 bytes.
Stage 2 completed. Starting zipdecrypt on Sat Nov 20 16:23:38 2010
Decrypting houkoku.zip (c6984a31f30fd10fda881fcb)... OK!
Decrypting a.txt (82b394ae79f29601b935fa40)... OK!
Decrypting kaigi1.jpg (2c6c04366c4b29a5d1872844)... OK!
Decrypting kaigi2.jpg (66d60c194f07f4643b743fba)... OK!
Finished on Sat Nov 20 16:23:38 2010

 kaigi1.jpgを利用して暗号化されたxyz.zipの中からhoukoku.zipを取り出しました。houkoku.zipはロックがかかっていないただのZIPファイルであるため、展開し、パスワードを得られます。

# unzip houkoku.zip
# cd houkoku
# ls
?+?q???X?g.xls ?R?s?[Book1.xls
# strings * | grep Password
Password: sangokushi38
Password: sangokushi38

 xlsファイルなので本当はExcelで開く必要がありますが、解答パスワードを得るだけなら、stringsコマンドを用いてもよいです。上記の結果から、解答パスワードは「sangokushi38」です。
NASecConPic12 模範解答は上記に記述した通りですが、問題文を完全に無視し、純粋にフォレンジックを行うことでも、この問題は解答できます。実は、配布されたイメージファイル(Forensics.img)には、未使用領域にhoukoku.zipが置かれています。つまり、未使用領域を探せばhoukoku.zipそのものが得られます。
 右図のページ(KEYWORD SEARCH)より、houkokuをキーワード検索します。そして見つかったデータをダンプするとhoukoku.zipと同じものとなり、展開するとxlsファイルが得られ、解答パスワード「sangokushi38」が得られます。

■Level6 UnPack EXE (FILE)
6 Level6はパックされた実行ファイルを解析し、解答パスワードを得る問題です。
 パックされているため、IDAProでは解析が難しく、OllyDbgではパッカーのデバッガ検知ルーチンが働いてうまく実行できないため、WinDbgを使います。WinDbgでは正常に実行できますが、今度はIsDebuggerPresent関数の呼び出しによりデバッガ検知されるため、まずはIsDebuggerPresent関数にブレイクポイントをセットして実行します。1度目のIsDebuggerPresent関数呼び出しは無視し、2度目の呼び出しの際に、呼び出し元まで処理を進めます。

// WinDbg
00403889 call dword ptr [ScriptGame+0x11e094 (0051e094)]
0040388f test eax,eax
00403891 jne ScriptGame+0x38d2 (004038d2)
00403893 mov eax,dword ptr [ebp-15Ch]
00403899 cmp eax,offset +0x270e (0000270f)
0040389e jg ScriptGame+0x38c8 (004038c8)
004038a0 cmp edi,offset +0x270e (0000270f)
004038a6 jg ScriptGame+0x38c8 (004038c8)
004038a8 cmp dword ptr [ScriptGame+0x4a6794 (008a6794)],0
004038af jne ScriptGame+0x36c0 (004036c0)

 デバッガ検知ルーチンの後に、0000270eという数値とcmpされています(00403899)。0000270eは10進数で9999であるため、ここが試合数の比較処理だと推測できます。EIPをjgのジャンプ先004038c8に変更すると、強制的にゲームセットとなり、解答パスワードが表示されます。
NASecConPic14 解答パスワードは「45MM3R」となります。また、当問題で使われているパッキング技術については TLS Callbacks を参照してください。
 この問題は TLS Callbacks により、エントリポイントより前の段階でテキストセクションの復号処理を行っています。

C:\>dumpbin /HEADERS /TLS ScriptGame_pro.exe
Microsoft (R) COFF/PE Dumper Version 10.00.30319.01
Copyright (C) Microsoft Corporation. All rights reserved.
(省略)
TLS Callbacks

Address
--------
008A9382
00000000

 008A9382が、エントリポイントへ進む前に実行されます。OllyDbgなどの動的デバッガで開くと以下のコードが見つかります。

// 最初の1度だけ実行されるようにフラグを確認
008A9382 CALL ScriptGa.008A9387
008A9387 POP EAX
008A9388 CMP DWORD PTR DS:[EAX+BF],1
008A9392 JNZ SHORT ScriptGa.008A9397
008A9394 RETN 0C

008A9397 PUSH EBP
008A9398 PUSH ESI
008A9399 PUSH EDI
008A939A CALL ScriptGa.008A939F
008A939F POP EBP
008A93A0 MOV ESI,EBP
008A93A2 XOR EAX,EAX
008A93A4 CDQ
008A93A5 MOV ECX,EDX
008A93A7 SUB ECX,1
008A93AD SHL ECX,9
008A93B0 AND ESI,ECX
008A93B2 INC EDX
008A93B3 SHL EDX,9

// 5A4Dh("MZ")を頼りにモジュールの先頭を探索
008A93B6 CMP WORD PTR DS:[ESI],5A4D
008A93BB JNZ SHORT ScriptGa.008A93D2
008A93BD MOV EAX,DWORD PTR DS:[ESI+3C]
008A93C0 MOV EBP,ESI
008A93C2 LEA EBP,DWORD PTR SS:[EBP+EAX]
008A93C6 MOV EAX,DWORD PTR SS:[EBP]
008A93C9 NOT EAX
008A93CB CMP EAX,FFFFBAAF
008A93D0 JE SHORT ScriptGa.008A93D9
008A93D2 SUB ESI,EDX
008A93D4 JMP ScriptGa.008A93B6

// エントリポイントがint 3hなら終了(OllyDbg 1.xx対策)
008A93D9 MOV EAX,DWORD PTR SS:[EBP+28]
008A93DC OR EAX,ESI
008A93DE CMP BYTE PTR DS:[EAX],0CC
008A93E1 JNZ SHORT ScriptGa.008A93EC
008A93E3 MOV BYTE PTR DS:[EAX],0C3
008A93E6 POP EDI
008A93E7 POP ESI
008A93E8 POP EBP
008A93E9 RETN 0C

008A93EC XOR ECX,ECX
008A93EE MOV ECX,1
008A93F3 SHL ECX,6
008A93F6 XOR EDI,EDI

// 復号キーを作成(EDI=b31ce937)
008A93F8 MOV EAX,DWORD PTR DS:[ESI+ECX*4]
008A93FB XOR EDI,EAX
008A93FD ROR EDI,4
008A9400 ROR EAX,9
008A9403 XOR EDI,EAX
008A9405 LOOPD SHORT ScriptGa.008A93F8

// テキストセクションのアドレスとサイズを取得
008A9407 ADD EBP,18
008A940D MOV EDX,DWORD PTR SS:[EBP+14]
008A9410 MOV ECX,DWORD PTR SS:[EBP+4]
008A9413 OR EAX,ESI
008A9415 OR ESI,EDX
008A9417 ADD ECX,-0C1
008A941D XOR EAX,EAX
008A941F MOV EBP,ESI

// テキストセクションを復号
008A9421 MOV EAX,EDI
008A9423 XOR BYTE PTR DS:[ESI],AL
008A9425 XCHG EBP,EDI
008A9427 MOVS BYTE PTR ES:[EDI],BYTE PTR DS:[ESI]
008A9428 XCHG EBP,EDI
008A942A ROR EDI,8
008A942D DEC EDI
008A942E LOOPD SHORT ScriptGa.008A9421

008A9430 POP EDI
008A9431 POP ESI
008A9432 POP EBP
008A9433 CALL ScriptGa.008A9438
008A9438 POP EAX

// このコードが実行されたことを示すフラグをONにする
008A9439 MOV DWORD PTR DS:[EAX+E],1
008A9440 RETN 0C

 復号を行う前にOllyDbg 1.xx対策として、エントリポイントのint 3hを評価しています。つまり、もしエントリポイントが0xCCならばプログラムを終了します。
 この演算を行うアンパッカーを作成すれば、元々のテキストセクションが得られます。

■Level7 JavaScript (FILE)
NASecConPic15 難読化されたJavaScriptを解読する問題です。「データ(文字列)をJavaScriptとして実行する」方法については、「難読化JavaScriptで利用可能なテクニック」を参照してください。
 この問題のポイントは、文字列をJavaScriptとして実行するタイミング(関数呼び出し)を捉えることです。
NASecConPic16 では、解析を始めます。FirefoxのアドオンであるFirebugを使います。適当な場所にブレイクポイントを仕掛けて、GOボタンをクリックし、ステップオーバー(F10)で処理を進めていくと、1160行目で、_(アンダーバー)変数に怪しいデータが展開されます。これは6文字で構成されたJavaScriptです。
 1165行目からのコードには ','(カンマ) が多く使われています。カンマで1165行目以降のソースコードを区切っていくと、以下になります。

__[$.$$_]+((+[])[$.$_]+"")[$.$__$]+"="+(+[]),
-[],
+[],
/{/[$.$$$_+__[$.$__]+$.$$$_+$.$$__]('}'),
_,
+($._$$+""+$.$___+$.$__),
/{/[$.$$$_+__[$.$__]+$.$$$_+$.$$__]('}'),
(!""+"")[-~[]]+(![]+"")[$._$$]+$.$_$_+"-"+$.$$_$+$._+$.$_$_+(![]+"")
[$._$_]+"-"+(!""+"")[$._$_]+(![]+"")[$._$$]+$.$$$_)

 難読化されていますが、これは各行が引数になっています。この直前まで処理を進めた後に、最後の引数である (!""+"")[-~[]]+(![]+"")[$._$$]+$.$_$_+"-"+$.$$_$+$._+$.$_$_+(![]+"")[$._$_]+"-"+(!""+"")[$._$_]+(![]+"")[$._$$]+$.$$$_) をalertで表示すると「rsa-dual-use」という文字列になっています。この文字列を検索すると、crypto.generateCRMFRequestに関するサイトがヒットします。「難読化JavaScriptで利用可能なテクニック」でも紹介されていますが、crypto.generateCRMFRequestを利用したものだと分かります。
 次に6文字で構成されたJavaScriptですが、まずは「記号だけのJavaScriptプログラミングの基本原理」を参照してください。ポイントは「JavaScriptは記号を組み合わせて十分な種類の文字を作り出せる」という点です。記号を組み合わせて平文なJavaScriptを作成したら、evalや前述のcrypto.generateCRMFRequestなどを利用してコード実行します。
 6文字JavaScriptはfirebugでは追いにくいので、JavaScriptをコードとして実行する関数をフックして特定します。dojoライブラリとfirebugを使います。

<script src="dojo.xd.js" type="text/javascript"></script>
<script>
dojo.connect(Number, "constructor",
null, function(a) {
console.debug("constructor:" + a);
});
dojo.connect(null, "setTimeout",
null, function(a) {
console.debug("setTimeout:" + a);
});
dojo.connect(null, "Function",
null, function(a) {
console.debug("Function:" + a);
});
dojo.connect(null, "eval",
null, function(a) {
console.debug("eval:" + a);
});
dojo.connect(crypto, "generateCRMFRequest",
null, function(a,b,c,d,e,f,g,h) {
console.debug("CRMFRequest:" + e);
});

// 6文字JavaScript
(+[])[([][(![]+[])[+[]]+([![]]+[][[]])
(省略)
+!+[]+!+[]]))()
</script>

 データをJavaScriptとして実行するために利用される関数を、思いつく限りフックします。これをfirebugで実行するとfirebugのコンソールにJavaScriptとして渡された文字列が出力されます。

constructor:
var s="$=/p..D/.exec(window.Na['Pwd']);";
alert(s);
var Answer=s.replace(/\W/g,"");

NASecConPic17 このコードが最終的にJavaScriptとして実行されます。Answer変数に入る文字列が解答となるため、このAnswerを表示するように書き換えたJavaScriptを用意して実行すれば解答が得られます。
 以上から解答パスワードは「pDexecwindowNaPwd」となります。ちなみに、6文字JavaScriptは、コードの先頭にて (0)["constructor"]["constructor"]("x")() というJavaScriptを [ ] ( ) + ! の6文字で作り出しています(xの部分には実行したいJavaScriptのコードが入ります)。よってNumber.constructorの関数フックに引っかかったことになります。
 また、この解答では6文字のJavaScriptに対して関数フックを行いましたが、そもそもの問題ファイルにあるJavaScriptに対して関数フックを行っても同様の結果が得られます。

「NetAgent Security Contest 2010 解答 Level5 ~ Level8 (2/2)」

メルマガ読者募集 採用情報 2020年卒向けインターンシップ

月別