APIフックに負けない強い体づくり

この記事は [Altplus Advent Calendar 2017](https://qiita.com/advent-calendar/2017/altplus) の19日目のエントリです。


こんにちわ。Xamarin人材です。

[前回](https://qiita.com/_pochi/items/4e20e38deee16a7615e1)、`notepad.exe`にAPIフックをしかけてイタズラする手法を紹介しました。
今回は、そういったAPIフックによる攻撃からいかに身を守るか、という方法を紹介したいと思います。方法は色々とありますが、**APIフックの脅威からはAPIフックで身を守る**ことにしましょう。俺はこの力を正しい方向に使うんだ。notepadは俺が守る。

# とりあえずDLLインジェクション
APIフックしたいので、とりあえずDLLをインジェクションさせてもらいましょう。正義のためだからいいよね。手法は前回と同様、`SetWindowsHookEx`を使います。

また、今回は**守る側が先にDLLインジェクションを仕掛ける**こととします。一般的に、この種の戦いは先手を取られるとかなり不利です。

さて、では具体的にどのような方法で`notepad.exe`を守るか考えましょう。

## 特定のAPIがフックされていることを検知する

実際に危害を加えてきた場合にそれを検知するパターンです。今回、敵は`CreateFileW`をフックして悪事に及ぶことはわかっているので、こちらも`CreateFileW`をフックして対処しましょう。

dllの初期化処理はこちらです。
フックと、後々行う`StackWalk64`のための初期化処理(`SymInitialize`)を行なっています。

``` dllmain.cpp
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;
}


```

フックした関数はこちら。フックされてたら死ぬ覚悟です。

``` IiDll.cpp
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`の実装はこちらです。

``` IiDll.cpp
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`によるフックが行われた後にファイルを保存しようとしたタイミングで自害しています。

![guard.gif](https://qiita-image-store.s3.amazonaws.com/0/46184/e1052e85-6519-1af5-3596-cae9e261a99d.gif)


もちろん、きちんと対策するのであれば、`StackWalk64`がフックされている可能性も考えなければなりません。


# 他にも方法はある
**悪意あるDLLのロード自体を妨害する**ことも有効でしょうし、わざわざ`CreateFileW`が呼ばれなくとも、主要なAPIがフックされてる時点で自害するという手もあります。もうちょっと色々試したかったのですが、今回はここまで。

今回のコードは[こちら](https://github.com/takuya-kikuchi/dllinjectiondemo)にアップしてあります。くれぐれも**ご利用は慎重に、自己責任で**よろしくお願いします。

# おわりに

今回は、APIフックに対してAPIフックで応える形で、`notepad.exe`を守る方法について検討しました。

なお今回は守る側が先にフックを仕掛けている前提でしたが、逆の場合もあるでしょう。そうなると、また方針も変わってきます。DLLのロード順序の先取り合戦も、熾烈な争いが繰り広げられていることでしょう。さらには、`CreateRemoteThread`等による、**DLL Injectionを伴わないような攻撃**も当然考えられます。

また、今回はユーザ空間のみについて考えましたが、カーネルドライバが混ざってくるとより戦いは激化することになります。悪意あるハッカーに**カーネルドライバを混入させられたら負けだ。**気をつけましょう。

ともあれ、**攻撃も防御も完璧などありえません。この戦いに終わりもありません。セキュリティいたちごっこは今日も続きます**。

あなたのアプリも狙われているかもしれませんよ。
それではまた。