2026/1/10
Red Team Operationや高度なマルウェア開発において、ユーザーランドフックの回避は永遠の課題です。EDR(Endpoint Detection and Response)は、Windows APIを監視することで攻撃者の挙動を検知しますが、攻撃者はその監視網をすり抜けるためにDirect Syscallという手法が使われるようになりました。
本記事では、Direct Syscallの基本原理と実装、そして 「なぜ現代のEDRに対してこの手法が通用しなくなりつつあるのか」 というOpSec(運用セキュリティ)の観点から解説します。
通常、アプリケーションがカーネル(Ring 0)の機能を必要とする場合、kernel32.dll や ntdll.dll を経由してシステムコールを発行します。EDRは、この ntdll.dll 内の関数(例: NtAllocateVirtualMemory)の先頭を jmp 命令に書き換えることで、実行フローを自社の監視エンジンにリダイレクトさせます(これをAPI Hookingと呼びます)。
Direct Syscallは、このフックが施されたntdllを経由せず、自前のコード内(.textセクション)で直接アセンブリ言語のsyscall 命令を実行する手法です。これにより、EDRの監視フックを完全にバイパスしてカーネルモードへ遷移することが可能になります。
以下は、ntdll.dll から動的にSSN(System Service Number)を取得し、直接システムコールを発行してメモリを確保するPoCです。今回は分かりやすさを重視しているため簡単に実装しています。
#include <Windows.h>
#include <cstdio>
#include "syscalls.h"
DWORD wNtAllocateVirtualMemory;
int main(void) {
HMODULE hNtdll = GetModuleHandleA("ntdll.dll");
UINT_PTR pNtAllocateVirtualMemory = (UINT_PTR)GetProcAddress(hNtdll, "NtAllocateVirtualMemory");
wNtAllocateVirtualMemory = ((BYTE*)(pNtAllocateVirtualMemory + 4))[0];
PVOID allocAddr = NULL;
SIZE_T size = 0x1000;
NTSTATUS status = NtAllocateVirtualMemory(
(HANDLE)-1,
(PVOID*)&allocAddr,
(ULONG_PTR)0,
&size,
(ULONG)(MEM_COMMIT | MEM_RESERVE),
PAGE_EXECUTE_READWRITE
);
printf("[+] Memory Allocated at: 0x%p\n", allocAddr);
return 0;
}
ちなみにNtAllocateVirtualMemoryのベースアドレスに4バイト追加するのは、システムコールのSSNを含む mov eax, SSN のメモリアドレスを取得するために必要なオフセットだからです。これは他の関数にも同じ操作をしなければなりません。ただし、EDRによって既に関数の先頭がフック(JMP命令への書き換え)されている場合、このオフセットはズレてしまい、正しいSSNが取得できない可能性があります。この問題を解決するにはHell's Gateなどの手法を使う必要があります。
※ 本PoCでは簡略化のために PAGE_EXECUTE_READWRITE (RWX) を使用していますが、実際のRed Team Operationではメモリの属性監視を回避するため、 RW で確保して書き込み、その後に RX に変更する等の工夫が必要です。
NASMアセンブリコードは次のようになります。
section .text
default rel
extern wNtAllocateVirtualMemory
global NtAllocateVirtualMemory
NtAllocateVirtualMemory:
mov r10, rcx
mov eax, [wNtAllocateVirtualMemory]
syscall
ret
externを使用すると、C/C++コード内でグローバル変数として宣言され、対応するSSNを含む変数wNtAllocateVirtualMemoryなどにアクセスできます。これにより、アセンブリコードにSSNをハードコードする必要がなくなります。
かつては無敵と思われたDirect Syscallですが、現代のEDR/AVはこれを見抜くためのシグネチャを持っています。
これらDirect Syscallの弱点を克服し、現在のEDR回避の主流となっているのが、次回解説するIndirect Syscallです。