この記事は Altplus Advent Calendar 2017 の19日目のエントリです。
こんにちわ。Xamarin人材です。
前回、notepad.exe
にAPIフックをしかけてイタズラする手法を紹介しました。
今回は、そういったAPIフックによる攻撃からいかに身を守るか、という方法を紹介したいと思います。方法は色々とありますが、APIフックの脅威からはAPIフックで身を守ることにしましょう。俺はこの力を正しい方向に使うんだ。notepadは俺が守る。
とりあえずDLLインジェクション
APIフックしたいので、とりあえずDLLをインジェクションさせてもらいましょう。正義のためだからいいよね。手法は前回と同様、SetWindowsHookEx
を使います。
また、今回は守る側が先にDLLインジェクションを仕掛けることとします。一般的に、この種の戦いは先手を取られるとかなり不利です。
さて、では具体的にどのような方法でnotepad.exe
を守るか考えましょう。
特定のAPIがフックされていることを検知する
実際に危害を加えてきた場合にそれを検知するパターンです。今回、敵はCreateFileW
をフックして悪事に及ぶことはわかっているので、こちらもCreateFileW
をフックして対処しましょう。
dllの初期化処理はこちらです。
フックと、後々行うStackWalk64
のための初期化処理(SymInitialize
)を行なっています。
void ShieldNotepad() {
TCHAR fileNameBuf[MAX_PATH];
// notepad.exeのみを対象にする
if (GetModuleFileName(GetModuleHandle(NULL), fileNameBuf, MAX_PATH) > 0) {
if (_tcsstr(fileNameBuf, TEXT("notepad.exe")) != NULL) {
// StackWalk用の初期化処理
SymInitialize(GetCurrentProcess(), NULL, TRUE);
// フックする。戻り値は本来のCreateFileWのアドレス
createFileWPtr = (fpCreateFileW)RewriteFunction("kernel32.dll", "CreateFileW", ShieldedCreateFileW);
}
}
}
BOOL APIENTRY DllMain( HMODULE hModule,
DWORD ul_reason_for_call,
LPVOID lpReserved
)
{
switch (ul_reason_for_call)
{
case DLL_PROCESS_ATTACH:
ShieldNotepad();
case DLL_THREAD_ATTACH:
case DLL_THREAD_DETACH:
case DLL_PROCESS_DETACH:
break;
}
return TRUE;
}
フックした関数はこちら。フックされてたら死ぬ覚悟です。
HANDLE
WINAPI
ShieldedCreateFileW(
_In_ LPCWSTR lpFileName,
_In_ DWORD dwDesiredAccess,
_In_ DWORD dwShareMode,
_In_opt_ LPSECURITY_ATTRIBUTES lpSecurityAttributes,
_In_ DWORD dwCreationDisposition,
_In_ DWORD dwFlagsAndAttributes,
_In_opt_ HANDLE hTemplateFile
)
{
TerminateIfHooked();
return createFileWPtr(lpFileName, dwDesiredAccess, dwShareMode, lpSecurityAttributes, dwCreationDisposition, dwFlagsAndAttributes, hTemplateFile);
}
さて、TerminateIfHooked
の実装はこちらです。
void TerminateIfHooked() {
CONTEXT context;
STACKFRAME64 stackFrame;
// StackWalkのためのコンテキストの準備。
RtlCaptureContext(&context);
// StackWalkのために、現在のスタックフレームの状態を設定。
ZeroMemory(&stackFrame, sizeof(STACKFRAME64));
stackFrame.AddrPC.Offset = context.Rip;
stackFrame.AddrPC.Mode = AddrModeFlat;
stackFrame.AddrFrame.Offset = context.Rsp;
stackFrame.AddrFrame.Mode = AddrModeFlat;
stackFrame.AddrStack.Offset = context.Rsp;
stackFrame.AddrStack.Mode = AddrModeFlat;
auto frameCount = 0;
// 深さ10くらいまで探索。特に意味はない
while (frameCount < 10)
{
// 1個上のスタックフレームを取得
if (!StackWalk64(IMAGE_FILE_MACHINE_AMD64, GetCurrentProcess(), GetCurrentThread(), &stackFrame, &context, NULL, SymFunctionTableAccess64, SymGetModuleBase64, NULL)) {
break;
}
// ただしく取得できていたら、モジュール情報をチェック
if (stackFrame.AddrPC.Offset != 0)
{
HMODULE mod;
// アドレスから、属するモジュールベースを取得してくれるAPI
if (GetModuleHandleExW(GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS, (LPCWSTR)stackFrame.AddrPC.Offset, &mod)) {
TCHAR fileNameBuf[MAX_PATH];
// モジュール名を取得
if (GetModuleFileNameW(mod, fileNameBuf, sizeof(fileNameBuf)) > 0) {
if (wcsstr(fileNameBuf, TEXT("WaruiDll.dll")) != NULL) {
// アラートを表示して
MessageBoxW(NULL, TEXT("API Hooking Detected!"), TEXT("Good bye."), MB_OK);
// さようなら
TerminateProcess(GetCurrentProcess(), 0);
}
}
}
frameCount++;
}
else {
break;
}
}
return;
}
少しわかりづらいのですが、やりたいことは、スタックトレースを1個ずつ上っていき、モジュール名を取得。気にくわない名前が見つかったら自害する、というかんじです。
実演はこちら。わかりにくいですが、WaruiApp.exe
によるフックが行われた後にファイルを保存しようとしたタイミングで自害しています。
もちろん、きちんと対策するのであれば、StackWalk64
がフックされている可能性も考えなければなりません。
他にも方法はある
悪意あるDLLのロード自体を妨害することも有効でしょうし、わざわざCreateFileW
が呼ばれなくとも、主要なAPIがフックされてる時点で自害するという手もあります。もうちょっと色々試したかったのですが、今回はここまで。
今回のコードはこちらにアップしてあります。くれぐれもご利用は慎重に、自己責任でよろしくお願いします。
おわりに
今回は、APIフックに対してAPIフックで応える形で、notepad.exe
を守る方法について検討しました。
なお今回は守る側が先にフックを仕掛けている前提でしたが、逆の場合もあるでしょう。そうなると、また方針も変わってきます。DLLのロード順序の先取り合戦も、熾烈な争いが繰り広げられていることでしょう。さらには、CreateRemoteThread
等による、DLL Injectionを伴わないような攻撃も当然考えられます。
また、今回はユーザ空間のみについて考えましたが、カーネルドライバが混ざってくるとより戦いは激化することになります。悪意あるハッカーに**カーネルドライバを混入させられたら負けだ。**気をつけましょう。
ともあれ、攻撃も防御も完璧などありえません。この戦いに終わりもありません。セキュリティいたちごっこは今日も続きます。
あなたのアプリも狙われているかもしれませんよ。
それではまた。