某CTFのバイナリ問題 "expl0rer" のwriteup
※このCTFチャレンジは個人的なツテで入手したもので、作問者からCTFのイベント名を公開しないことを条件にWrite Up記載の許可をもらっています。
サマリ
提供されるバイナリは"expl0rer"というUPXパックされた32ビットのPEファイル。ファイルを実行するとパックされたコードがメモリ上にアンパックされ、リソース・セクションに埋め込まれているペイロードを抽出し、"kerne132.dll"としてドロップする。ドロップされた"kerne132.dll"はDLLインジェクションの要領で別のプロセスにロードされる。この際、"kerne132.dll"は自身がどのプロセスによってロードされているかチェックする。もし"svcmgmt.exe"というプロセスによってロードされている場合はメモリ内に平文のflagをロードし、"Flag decrypted!"というメッセージボックスを生成する。"svcmgmt.exe"以外のプロセスによってロードされている場合は"Try harder!"というメッセージボックスを生成する。
まとめると、"expl0rer"はドロッパー及びインジェクターで、実行すると"kerne132.dll"というDLLをドロップして、DLLインジェクションの要領で標的プロセスに読み込ませる。不正コード(今回の場合はflag生成のコード)の本体は"kerne132.dll"で、"kerne132.dll"は自身が"svcmgmt.exe"に読み込まれている場合に限り、平文のflagをメモリ内にロードする。
解析
※逆アセンブル内の変数名や関数名は一部解析に当たり、わかりやすいようにデフォルトのものから変更しています。
"expl0rer"をCFF Explorerで確認してみるとUPXパックされていることがわかる。ただし、セクション名がデフォルトのUPX0、UPX1からRC0、RC1に変更されているため、upx -d コマンドを用いてもアンパックできない。セクション名RC0、RC1をそれぞれUPX0、UPX1に書き換えるとupx -d コマンドで"expl0rer"をアンパックできる。※参考1, 参考2
アンパックしたファイルをIDAで眺めてみると、いくつかアンチ・デバッグのコードを発見した。
自身がVMware仮想環境内で実行されているか確認
.text:00401577 8D 4C 24 18 lea ecx, [esp+168h+vmtoolsd.exe]
.text:0040157B E8 30 FD FF FF call enumProcess_4012B0
.text:00401580 84 C0 test al, al
.text:00401582 75 55 jnz short loc_4015D9
自身がVirtualBox仮想環境内で実行されているか確認
.text:00401584 8D 4C 24 28 lea ecx, [esp+168h+vboxtray.exe]
.text:00401588 E8 23 FD FF FF call enumProcess_4012B0
.text:0040158D 84 C0 test al, al
.text:0040158F 75 48 jnz short loc_4015D9
デバッガの有無を確認
.text:0040159E C6 44 24 38 73 mov byte ptr [esp+168h+var_130], 73h
.text:004015A3 FF 15 50 E0 40 00 call ds:IsDebuggerPresent
.text:004015A9 85 C0 test eax, eax
.text:004015AB 75 18 jnz short loc_4015C5
解析を続けるには上記のコードを適宜書き換える必要がある。自分はジャンプ・インストラクションを書き換えることで対応した。
IDAでのレビューを続けていると、FindResourceA、LoadResource、CreateFileA、WriteFileを呼び出している関数(アドレス:0x4010A0)を発見。どうやらリソースセクションからペイロードを抽出してファイルを作成する様子。OllyDbgでCreateFileAにブレークポイントをセットしてデバッグしてみたところ、systemディレクトリ配下に"kerne132.dll"というファイルを作成していた(自分の環境下ではC:\Windows\SysWOW64\kerne132.dllとして作成されていた)。メモして、引き続きIDAでのコードレビューを続ける。
.text:00401591 8D 4C 24 58 lea ecx, [esp+168h+Buffer] ; lpFileName
.text:00401595 E8 06 FB FF FF call dropDLL_4010A0 ; drops DLL "kerne132.dll" under system directory
.text:0040159A 84 C0 test al, al
.text:0040159C 74 3B jz short loc_4015D9
VirtualAllocEx、WriteProcessMemory、GetProcAddress、CreateRemoteThreadを呼び出している関数(アドレス:0x401380)を発見。これら4つの関数はDLLインジェクションに用いられる典型的な関数である。
.text:00401397 6A 40 push 40h ; flProtect
.text:00401399 68 00 30 00 00 push 3000h ; flAllocationType
.text:0040139E 2B F1 sub esi, ecx
.text:004013A0 56 push esi ; dwSize
.text:004013A1 6A 00 push 0 ; lpAddress
.text:004013A3 57 push edi ; hProcess
.text:004013A4 FF 15 30 E0 40 00 call ds:VirtualAllocEx
.text:004013AA 8B F0 mov esi, eax
.text:004013AC 85 F6 test esi, esi
.text:004013AE 74 5E jz short loc_40140E
---
.text:004013BC 6A 00 push 0 ; lpNumberOfBytesWritten
.text:004013BE 2B CA sub ecx, edx
.text:004013C0 51 push ecx ; nSize
.text:004013C1 53 push ebx ; lpBuffer
.text:004013C2 56 push esi ; lpBaseAddress
.text:004013C3 57 push edi ; hProcess
.text:004013C4 FF 15 34 E0 40 00 call ds:WriteProcessMemory
.text:004013CA 85 C0 test eax, eax
.text:004013CC 74 40 jz short loc_40140E
---
.text:004013CE 68 C4 2B 41 00 push offset ProcName ; "LoadLibraryA"
.text:004013D3 68 D4 2B 41 00 push offset ModuleName ; "kernel32.dll"
.text:004013D8 FF 15 3C E0 40 00 call ds:GetModuleHandleA
.text:004013DE 50 push eax ; hModule
.text:004013DF FF 15 38 E0 40 00 call ds:GetProcAddress
.text:004013E5 85 C0 test eax, eax
.text:004013E7 74 25 jz short loc_40140E
---
.text:004013E9 6A 00 push 0 ; lpThreadId
.text:004013EB 6A 00 push 0 ; dwCreationFlags
.text:004013ED 56 push esi ; lpParameter
.text:004013EE 50 push eax ; lpStartAddress
.text:004013EF 6A 00 push 0 ; dwStackSize
.text:004013F1 6A 00 push 0 ; lpThreadAttributes
.text:004013F3 57 push edi ; hProcess
.text:004013F4 FF 15 40 E0 40 00 call ds:CreateRemoteThread
.text:004013FA 85 C0 test eax, eax
.text:004013FC 74 10 jz short loc_40140E
WriteProcessMemoryにブレークポイントをセットしてデバッグしたところ、先述した"kerne132.dll"へのフルパスを標的プロセスに書き込もうとしていた。
.text:004015CE loc_4015CE: ; lpBuffer
.text:004015CE 8D 54 24 58 lea edx, [esp+168h+Buffer]
.text:004015D2 8B C8 mov ecx, eax ; hObject
.text:004015D4 E8 A7 FD FF FF call DLL_Injection_401380 ; injects DLL "kerne132.dll" to target process
次にドロップされた"kerne132.dll"をIDAで見てみる。どうやらDLL内に暗号化されたflagがハードコードされており、flagを復号するための関数もDLL内にコードされている。
flagが正しく復号されていれば平文のflagをメモリにロードして、'Flag decrypted!というメッセージボックスを生成し、そうでない場合は'Try harder!'というメッセージボックスを生成する。
.text:10001162 C7 45 D4 21 35 52 5E mov [ebp+enc_flag], 5E523521h
.text:10001169 C7 45 D8 1C 09 38 62 mov [ebp+var_28], 6238091Ch
.text:10001170 C7 45 DC 3A 49 2B 19 mov [ebp+var_24], 192B493Ah
.text:10001177 C7 45 E0 45 63 39 56 mov [ebp+var_20], 56396345h
.text:1000117E C7 45 E4 5D 1A 71 54 mov [ebp+var_1C], 54711A5Dh
.text:10001185 C7 45 E8 4D 3A 35 03 mov [ebp+var_18], 3353A4Dh
.text:1000118C 66 C7 45 EC 2D 10 mov [ebp+var_14], 102Dh
.text:10001192 E8 C9 00 00 00 call TryHarder_10001260
.text:10001197 68 04 01 00 00 push 104h ; nSize
.text:1000119C 8D 85 D0 FE FF FF lea eax, [ebp+BaseName]
.text:100011A2 50 push eax ; lpBaseName
.text:100011A3 6A 00 push 0 ; lpModuleName
.text:100011A5 C7 45 FC 00 00 00 00 mov [ebp+var_4], 0
.text:100011AC FF 15 08 30 00 10 call ds:GetModuleHandleW
.text:100011B2 50 push eax ; hModule
.text:100011B3 FF 15 0C 30 00 10 call ds:GetCurrentProcess
.text:100011B9 50 push eax ; hProcess
.text:100011BA FF 15 BC 30 00 10 call ds:GetModuleBaseNameA
.text:100011C0 8D 8D D0 FE FF FF lea ecx, [ebp+BaseName]
.text:100011C6 51 push ecx
.text:100011C7 8D 45 D4 lea eax, [ebp+enc_flag]
.text:100011CA E8 E1 FE FF FF call flagDecryption_100010B0
.text:100011CF 83 C4 04 add esp, 4
.text:100011D2 80 38 52 cmp byte ptr [eax], 'R'
.text:100011D5 75 1D jnz short loc_100011F4
---
.text:100011D7 80 78 01 43 cmp byte ptr [eax+1], 'C'
.text:100011DB 75 17 jnz short loc_100011F4
---
.text:100011DD 80 78 02 31 cmp byte ptr [eax+2], '1'
.text:100011E1 75 11 jnz short loc_100011F4
---
.text:100011E3 80 78 03 33 cmp byte ptr [eax+3], '3'
.text:100011E7 75 0B jnz short loc_100011F4
---
.text:100011E9 8D 85 B4 FE FF FF lea eax, [ebp+var_14C]
.text:100011EF E8 BC 00 00 00 call sucessMessage_100012B0
どうやら自身をロードしているプロセス名をecxに格納し、flagを復号するための鍵として利用する模様。
.text:100011AC FF 15 08 30 00 10 call ds:GetModuleHandleW
.text:100011B2 50 push eax ; hModule
.text:100011B3 FF 15 0C 30 00 10 call ds:GetCurrentProcess
.text:100011B9 50 push eax ; hProcess
.text:100011BA FF 15 BC 30 00 10 call ds:GetModuleBaseNameA
.text:100011C0 8D 8D D0 FE FF FF lea ecx, [ebp+BaseName]
.text:100011C6 51 push ecx
.text:100011C7 8D 45 D4 lea eax, [ebp+enc_flag]
.text:100011CA E8 E1 FE FF FF call flagDecryption_100010B0
OllyDbgでデバッグしている場合、ロード元のプロセスがLOADDLL.EXEとなり、正しくflagを復号できない。(OllyDbgはLOADDLL.EXEというファイルに解析対象のDLLをロードしてデバッグするため)
もう一度 アンパックした"expl0rer"を眺めてみるとDLLインジェクションの直前に何らかの暗号化または復号化の処理をしている関数を発見。(アドレス:0x401000)
ブレークポイントをセットして関数を実行し、eaxに格納されている戻り値を確認してみると"svcmgmt.exe"というプロセス名が確認できた。どうやら"svcmgmt.exe"がflag復号のための鍵の模様。
.text:004015AD 8D 54 24 38 lea edx, [esp+168h+key]
.text:004015B1 8D 4C 24 3C lea ecx, [esp+168h+enc_msg]
.text:004015B5 E8 46 FA FF FF call generateProcname_401000 ; returns process name "svcmgmt.exe"
.text:004015BA 8B C8 mov ecx, eax
.text:004015BC E8 FF FB FF FF call enumProcess_4011C0
.text:004015C1 85 C0 test eax, eax
.text:004015C3 75 09 jnz short loc_4015CE
再度、"kerne132.dll"をOllyDbgでデバッグする。flag復号の処理に移る前にecxレジスタに"svcmgmt.exe"と書き込む。OllyDbgでレジスタの書き換えをする場合は、画面右のレジスタ画面からECXを選択し、右クリック、Follow in Dumpを選択。画面左下にダンプ画面が現れるので、書き換えたい箇所をドラッグして右クリック、BinaryからEditを選択。立ち上がったウィンドウで内容の書き換えを行う。
ecxの書き換えが済んだら、flag復号の関数を実行し、eaxレジスタをダンプする。(画面右のレジスタ画面からEAXを選択し、右クリック、Follow in Dumpを選択。画面左下にダンプ画面が現れる)
00163950 52 43 31 33 7B 64 4C 4C RC13{dLL
00163958 5F 31 4E 6A 33 63 54 31 _1Nj3cT1
00163960 30 6E 5F 31 35 5F 46 75 0n_15_Fu
00163968 4E 7D N}
flagはRC13{dLL_1Nj3cT10n_15_FuN}
以上。