expl0rer writeup

某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}

以上。

Leave a Reply

Your email address will not be published. Required fields are marked *