svch0st writeup

某CTFのバイナリ問題 "svch0st" のwriteup
※このCTFチャレンジは個人的なツテで入手したもので、作問者からCTFのイベント名を公開しないことを条件にWrite Up記載の許可をもらっています。

サマリ
提供されるバイナリは"svch0st"という32ビットのPEファイルと"weird_file"という形式不明のバイナリファイル。"svch0st"はsvchost.exeに対してプロセス・ホロウイングを行う。ホロウされたsvchost.exeはユーザーのデスクトップ画面のスクリーンショットを撮る。スクリーンショットは暗号化され、"index.dat"というファイル名でディレクトリ"C:\users\[username]\AppData\Local\Temp\"以下に保存される。"weird_file"は暗号化されたスクリーンショット・ファイルで、このファイルを復号するとflagの載っている画像ファイルが現れる。

解析
※逆アセンブル内の変数名や関数名は一部解析に当たり、わかりやすいようにデフォルトのものから変更しています。

"svch0st"をIDAで眺めてみると、GetModuleHandleAとGetProcAddressを繰り返し呼び出している関数を発見。(アドレス:0x401080)
この関数はGetModuleHandleAとGetProcAddressを利用して下記の関数のアドレスを取得する。これらの関数はプロセス・ホロウイングに用いられる典型的な関数であり、"svch0st"はプロセス・ホロウイングを行うと推測できる。プロセス・ホロウイングとは一言で説明すると正規のプロセスをまるごと悪意のあるコードで上書きすること。

- CreateProcessA
- NtUnmapViewOfSection
- VirtualAllocEx
- WriteProcessMemory
- ReadProcessMemory
- GetThreadContext
- SetThreadContext
- ResumeThread

例:GetModuleHandleAとGetProcAddressを呼び出してCreateProcessAのアドレスを取得している


.text:00401404 E8 57 FC FF FF          call    GetProcModuleString_401060
.text:00401409 8B CA                   mov     ecx, edx
.text:0040140B 51                      push    ecx             ; lpModuleName ntdll
.text:0040140C FF D7                   call    edi ; GetModuleHandleA
.text:0040140E 89 85 B0 FE FF FF       mov     [ebp+hModule], eax
---
.text:00401431 E8 2A FC FF FF          call    GetProcModuleString_401060
.text:00401436 8B 3D 04 70 40 00       mov     edi, ds:GetProcAddress
.text:0040143C 52                      push    edx             ; lpProcName CreateProcessA
.text:0040143D 53                      push    ebx             ; hModule
.text:0040143E FF D7                   call    edi ; GetProcAddress


引き続きIDAで"svch0st"を眺めていると、いくつかアンチ・デバッグのコードを発見した。

自身がVirtualBox仮想環境内で実行されているか確認


.text:00401D4B 8D 55 C8                lea     edx, [ebp+var_38] ; C:\Windows\system32\vboxdisp.dll
.text:00401D4E E8 0D F3 FF FF          call    GetProcModuleString_401060
---
.text:00401D66 8D 45 C8                lea     eax, [ebp+var_38] ; C:\Windows\system32\vboxdisp.dll
.text:00401D69 E8 D2 FE FF FF          call    sub_401C40


CheckRemoteDebuggerPresent関数を呼び出して、自身がデバッグされているか確認


.text:00401D58 8D 45 9C                lea     eax, [ebp+var_64] ; CheckRemoteDebuggerPresent
.text:00401D5B E8 E0 FE FF FF          call    sub_401C40
---
.text:00401D72 E8 E9 FE FF FF          call    CheckRemoteDebuggerPresent_401C60
.text:00401D77 85 C0                   test    eax, eax
.text:00401D79 75 0C                   jnz     short loc_401D87


解析を続けるには上記のコードを適宜書き換える必要がある。自分はジャンプ・インストラクションを書き換えることで対応した。

IDAでのレビューを続けていると、先述したCreateProcessA、NtUnmapViewOfSection、VirtualAllocEx、WriteProcessMemory、ReadProcessMemory、GetThreadContext、SetThreadContext、ResumeThreadを呼び出してプロセス・ホロウイングを行う関数を発見した。(アドレス:0x401910)
OllyDbgでデバッグしたところ、svchost.exeに対してプロセス・ホロウイングを行うことがわかった。


.text:00401964 6A 04                   push    4               ; suspend mode
.text:00401966 6A 00                   push    0
.text:00401968 6A 00                   push    0
.text:0040196A 6A 00                   push    0
.text:0040196C 50                      push    eax             ; svchost.exe
.text:0040196D 6A 00                   push    0
.text:0040196F FF 15 64 B8 40 00       call    CreateProcessA_40B864
---
.text:00401990 51                      push    ecx             ; lpBaseAddress
.text:00401991 52                      push    edx             ; hProcess
.text:00401992 89 75 FC                mov     [ebp+var_4], esi
.text:00401995 E8 D6 04 00 00          call    myReadProcessMemory_401E70
.text:0040199A 83 C4 08                add     esp, 8
.text:0040199D E8 CE FD FF FF          call    someDecryption_401770
.text:004019A2 8B F8                   mov     edi, eax
.text:004019A4 89 45 F8                mov     [ebp+var_8], eax
.text:004019A7 E8 64 F6 FF FF          call    sub_401010
.text:004019AC 8B 4D F8                mov     ecx, [ebp+var_8]
.text:004019AF 8B F8                   mov     edi, eax
.text:004019B1 E8 4A F6 FF FF          call    sub_401000
.text:004019B6 8B 0B                   mov     ecx, [ebx]
.text:004019B8 89 45 F4                mov     [ebp+var_C], eax
.text:004019BB 8B 46 08                mov     eax, [esi+8]
.text:004019BE 50                      push    eax
.text:004019BF 51                      push    ecx
.text:004019C0 FF 15 68 B8 40 00       call    ZwUnmapViewOfSection_40B868
.text:004019C6 85 C0                   test    eax, eax
.text:004019C8 75 B1                   jnz     short loc_40197B
---
.text:004019D5 6A 40                   push    40h
.text:004019D7 68 00 30 00 00          push    3000h
.text:004019DC 50                      push    eax
.text:004019DD 51                      push    ecx
.text:004019DE 52                      push    edx
.text:004019DF FF 15 6C B8 40 00       call    VirtualAllocEx_40B86C
.text:004019E5 85 C0                   test    eax, eax
---
.text:00401A08 51                      push    ecx
.text:00401A09 52                      push    edx
.text:00401A0A 50                      push    eax
.text:00401A0B FF 15 70 B8 40 00       call    WriteProcessMemory_40B870
.text:00401A11 85 C0                   test    eax, eax
---
.text:00401C2A 8B 53 04                mov     edx, [ebx+4]
.text:00401C2D 52                      push    edx
.text:00401C2E FF 15 80 B8 40 00       call    ResumeThread_40B880
.text:00401C34 5F                      pop     edi
.text:00401C35 85 C0                   test    eax, eax


svchost.exeに書き込まれるコードを抽出するため、OllyDbgで以下を行った。
1. 暗号化された不正コードを復号して抽出していると思われる箇所(アドレス:0x40199D)にブレークポイントをセットして実行し、画面右のレジスタ画面からeaxレジスタを選択して右クリック、"Follow in Dump"を選択
2. 画面左下のダンプ画面を右クリック、CopyからSelect allを選択
3. 全選択の後、再び右クリック、BinaryからBinary copyを選択
4. eaxレジスタのデータがhexエンコード状態でコピー完了。
5. コピーしたeaxレジスタのデータをhexデコードすると32ビットのPEファイルが抽出できる。(MZヘッダー以前のゴミデータは削除する)

上記の方法だと、抽出したPEファイルの末尾に余分なデータが含まれてしまうが、IDAやOllyDbgで解析するにあたり、とくに問題はなかった。

※追記。x32dbg (x64dbg)を用いて、上記のように特定のメモリ領域からペイロードをダンプする場合はこちらの記事メモリ領域からペイロードをダンプするの項を参照

抽出したPEファイルをデバッグしてみると、"c:\users\[username]\AppData\Local\Temp\"以下に"index.dat" という奇妙なファイルを作成していた。このファイルは"weird_file"同様、形式不明のバイナリファイルである。
解析を続けると、ファイル作成の際に暗号化処理を行うカスタムの関数(アドレス:0x401230)を実行してファイルを暗号化していることがわかった。

以下は暗号化処理のコードを抜粋したもの


.text:00401253 8A 04 39                mov     al, [ecx+edi]
.text:00401256 84 C0                   test    al, al
.text:00401258 74 08                   jz      short loc_401262
---
.text:0040125A 3C F1                   cmp     al, 0F1h
.text:0040125C 74 04                   jz      short loc_401262
---
.text:00401262                         loc_401262:
.text:00401262 F6 D0                   not     al
---
.text:0040125E 34 F1                   xor     al, 0F1h
.text:00401260 EB 02                   jmp     short loc_401264


上記のコードは作成されたファイルの先頭から1バイトずつ取り出し、
1. 取り出した値が0x0または0xf1だった場合はビット反転を行う。0x0は0x1に、0xf1は0xeとなる。
2. 取り出した値が0x0または0xf1以外だった場合は0xf1を鍵としてXOR演算する。

ここまで判明したところで、"weird_file"を上記の暗号化手順をもとに復号すればflagを取得できるのではと予想する。

"weird_file"復号のため以下のスクリプトを書いた。


#!/usr/bin/env python

'''
XOR the file
'''
import binascii
import argparse

def XORfile(input_file, key):

    output_file = 'decoded.bin'
    key = bytearray(binascii.unhexlify(key))
    key_length = len(key)

    with open(input_file, 'rb') as fin:
        file_contents = bytearray(fin.read())

    file_size = len(file_contents)
    XORed_bytearray = bytearray(file_size)
    j = 0

    if (key_length > 1):
        for i in range(0, file_size):
            if (j >= key_length): ## if the key gets to end, set the key back to beginning.
                j = 0
            XORed_bytearray[i] = file_contents[i] ^ key[j]
            j += 1
    else:
        for i in range(0, file_size):
            if (file_contents[i] == key[j]):
                #print('hello')
                XORed_bytearray[i] = 14 #0xe
            elif (file_contents[i] == 255): #0x1
                XORed_bytearray[i] = 0
            else:
                #print(file_contents[i])
                XORed_bytearray[i] = file_contents[i] ^ key[j]

    with open(output_file, 'wb') as fout:
        fout.write(XORed_bytearray)

    print('check out ' + str(output_file))

parser = argparse.ArgumentParser(description="XOR the file")
parser.add_argument("-i", "--input_file", action="store", required=True)
parser.add_argument("-k", "--key", action="store", help="XOR key in hex format", required=True)
args = parser.parse_args()

XORfile(args.input_file, args.key)



$ python XORdecoder.py -k f1 -i weird_file
check out decoded.bin
$ file decoded.bin
decoded.bin: PC bitmap, Windows 3.x format, 1909 x 946 x 32

デコードされたファイルはbitmap形式の画像ファイルで、開いてみるとflagが記載されたデスクトップのスクリーンショットであることがわかった。
flag4blog.png

flagはRC13{pr0c3ss_h0ll0w1ng_15_da_b0mb}

以上。

Leave a Reply

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