某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が記載されたデスクトップのスクリーンショットであることがわかった。
flagはRC13{pr0c3ss_h0ll0w1ng_15_da_b0mb}
以上。