ichirin2501's diary

いっちりーん。

不出来な自作crackmeの解説その2

問題のcrackmeはこちら
前回の解説はこちら

第4段階?

実行ファイルを見て行きましょう。
まず何のファイルなのか一応調べます。


$ file hoge.exe
hoge.exe: PE32 executable for MS Windows (console) Intel 80386 32-bit
32bitのWindowsの実行ファイルです。
@ucq氏に指摘を受けまして、64bit環境 or windows7 だとクラッシュして動きません。
それを含めてのcrackmeなんだZE、HAHAHA(不勉強でごめんなさいごめんなさい)
では早速解析していきましょう。
いきなり逆アセンブラして見てみると、おかしいことに気付きます。
ええ、パッカーを使用してるんです。
パッカーとは、実行できる状態のまま圧縮するものですが詳しくはぐぐってください。
解凍する前に何のパッカーが使われてるか調べてみます。
今回はバイナリ見るだけでもわかりますが、PEiDとかで調べることができます。
調べてみますと、UPXとかいう単語が目に入ってそしてあなたはぐぐりだす…。
というわけで、解凍するソフトを落としてくだしぁ。手動で行う変態さんもいます(@ucq)。
debian系なら

$ sudo apt-get install upx
$ upx -d hoge.exe
で解凍できると思います。
改めて逆アセンブラ開始です、以下は IDAPro で逆アセンブラしたコードを貼り付けたものです。

.text:00401000 var_2C = dword ptr -2Ch
.text:00401000 flOldProtect = dword ptr -28h
.text:00401000 var_24 = dword ptr -24h
.text:00401000 var_4 = dword ptr -4
.text:00401000
.text:00401000 sub esp, 2Ch
.text:00401003 mov eax, dword_403000
.text:00401008 xor eax, esp
.text:0040100A mov [esp+2Ch+var_4], eax
.text:0040100E push ebx
.text:0040100F push esi
.text:00401010 push offset aR ; "r"
.text:00401015 xor ebx, ebx
.text:00401017 push offset aKey_txt ; "key.txt"
.text:0040101C mov [esp+3Ch+var_2C], ebx
.text:00401020 call ds:fopen
.text:00401026 mov esi, eax
.text:00401028 add esp, 8
.text:0040102B test esi, esi
.text:0040102D jnz short loc_401037
.text:0040102F push 1 ; int
.text:00401031 call ds:exit
fopen関数でkey.txtをreadモードで取得しようとしてます。
もし、ファイルが存在しなければ、exitが実行されてしまいます。
つまり、key.txtを自前で同ディレクトリに用意する必要があります。

.text:00401037 loc_401037: ; CODE XREF: sub_401000+2D↑j
.text:00401037 push edi
.text:00401038 lea eax, [esp+38h+flOldProtect]
.text:0040103C push eax ; lpflOldProtect
.text:0040103D push 8 ; flNewProtect
.text:0040103F push 0Eh ; dwSize
.text:00401041 push offset sub_403018 ; lpAddress
.text:00401046 call ds:VirtualProtect
.text:0040104C mov ecx, offset sub_403018
.text:00401051 call ecx ; sub_403018
.text:00401053 push esi ; FILE *
.text:00401054 push 20h ; size_t
.text:00401056 lea edx, [esp+40h+var_24]
.text:0040105A push 1 ; size_t
.text:0040105C push edx ; void *
.text:0040105D mov edi, eax
.text:0040105F call ds:fread
.text:00401065 mov esi, eax
.text:00401067 add esp, 10h
.text:0040106A cmp esi, 0Dh
.text:0040106D jnb short loc_401077
.text:0040106F
.text:0040106F loc_40106F: ; CODE XREF: sub_401000+94↓j
.text:0040106F ; sub_401000+98↓j
.text:0040106F push 1 ; int
.text:00401071 call ds:exit
VirtualProtectと自作関数とfread関数の3つが呼ばれてるのがわかりますね。
動的解析で0x0040103Cにbreakpointを設定して実行してみると、
VirtualProtectで0x00403018から8byte分、flNewProtectに0x85が入れられて、
アクセス保護の状態をPAGE_WRITECOPYに変更してることがわかります。
しかし、PAGE_WRITECOPYに変更しても実行権限はないままです。。。
致命的ミス><、@ucqさんご指摘ありがとうございます。
アクセス保護についてはこちらが詳しいです。 http://www.ertl.jp/~takayuki/readings/info/no02.html
Vista以下はこれでも動くみたいなのですが、windows7環境だとflNewProtectの値を変更する必要が出てきます。
PAGE_EXECUTE_READとかでいいのかな?…不安。(windows7と64bit環境ほしいよぅ)
あ、64bitだとどちらにしろ動きません。
ということなので、0x00401051のcallを xor eax,eax とかで書き換えてください。
(64bit環境だとこれでいいのかな?くっそー実験できない)

.data:00403018 mov eax, large fs:18h
.data:0040301E mov eax, [eax+30h]
.data:00403021 movzx eax, byte ptr [eax+2]
.data:00403025 retn
実はこの処理は32bitのIsDebuggerPresent関数です。
グローバルの配列にこのバイナリデータを初期化しておいて、関数として呼び出してみました。
(後々の処理を考えるとあまり意味はありませんw)
次に、fread関数でkey.txtから1byteずつ32個、つまり、32byteまで読みこんで
バッファの[esp+40h+var_24]の配列に格納してます。
freadの返り値は読み込んだデータ数を返します。これがeaxに格納されてesiにコピーされてます。
それから、cmp esi, 0Dh で比較してますね。
jnb命令は cmp op1, op2 という命令で、op1 >= op2 ならばジャンプ なので、
13byte未満ならexitが実行されてしまい、正しい処理に移行しません。
よって、key.txtの中身は13byte以上の内容でなければいけないです。

.text:00401077 loc_401077: ; CODE XREF: sub_401000+6D↑j
.text:00401077 xor ecx, ecx
.text:00401079 test esi, esi
.text:0040107B jbe short loc_401092
.text:0040107D lea ecx, [ecx+0]
.text:00401080
.text:00401080 loc_401080: ; CODE XREF: sub_401000+90↓j
.text:00401080 mov al, byte ptr [esp+ecx+38h+var_24]
.text:00401084 test al, al
.text:00401086 jz short loc_401092
.text:00401088 movzx eax, al
.text:0040108B inc ecx
.text:0040108C add ebx, eax
.text:0040108E cmp ecx, esi
.text:00401090 jb short loc_401080
.text:00401092
.text:00401092 loc_401092: ; CODE XREF: sub_401000+7B↑j
.text:00401092 ; sub_401000+86↑j
.text:00401092 cmp ecx, esi
.text:00401094 jnz short loc_40106F
.text:00401096 cmp esi, ebx
.text:00401098 jnz short loc_40106F
がおー、わんわんお(少し疲れてきたようです)
というか、ソースコード貼りつけたほうが早くね?wwwと思ったけど、がんばって書いてみる。
0x00401077~が初期化で、0x00401080~0x00401090がループ処理になってます。
[esp+ecx+38h+var_24]がfreadのときに指定したバッファを指しており、
先頭から1文字ずつ取得しています。もし取得した文字がヌル文字だったらジャンプ。
そうでなければ、ecxを次の添え字にして、ebxに取得した文字の値を加算します。
esiにはfreadの返り値が保存されており、ループ回数のチェックをしています。
ループを抜けると、ecxとesiの比較、esiとebxの比較が行われており、これは、

  1. ループ回数がfreadの返り値と等しいか
  2. freadの返り値と文字の値の和が等しいか

をチェックしています。両方等しくなければexitです。
なので、必然的にバイナリ値0x01を13文字以上key.txtに書き込むことになります。
13文字以上32文字以下のうち何文字が正しいのか?ということですが、


.text:004010D2 cmp [esp+3Ch+var_2C], 4E0h
.text:004010DA pop ebp
.text:004010DB jnz short loc_4010EB
.text:004010DD push offset aCongraturation ; "\nCongraturation!!"
.text:004010E2 call ds:puts
なんかてきとーに処理されて0x4e0の値になるケースは一意に決まります。
ここは全探索とかすれば良いかと思います。(正しいのは13文字です)
ということで、コマンドプロンプトから実行すると、
!-8BKSZ`eilno
Congraturation!!
これがzipのパスワードキーになってます。

第5段階

zipを解凍して得られたテキストファイルにはkey:〜 と書かれてます。
ここで、愛らしい猫の画像を思い出して下さい。
はてなブログの文字数制限の関係で低画質になったのが非常やまれる)
猫画像はファイル名が outguess_2.jpg となってます。
「outguess」とは、音声や画像に情報を隠すステガノグラフィーのツール名です。
debian系なら


$ sudo apt-get install outugess
$ outguess -k ここにkey -r outguess_2.jpg answer.txt
これでおしまいです。猫かわいいよ猫。

感想とか

非常に不出来なcrackmeとなってしまった…。
解説のところどころ間違ってるかもしれません。話半分で読んでいただければ、と思いますw
それでも、問題を作ることで新しい発見も多かったので満足しております。


偉大なる元ネタの@rofiさん(元ネタリンク
ご指摘を頂いた変態@ucqさん
この場を借りて、お礼を申し上げたいと思います。


チャレンジしてくださった皆さん、有難うございました!