notepad.exeをJavaコードをかけない体に改造する

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

こんにちわ。Xamarin人材です。
最近、ゲームのチートだったりハッキングだったり、怖い話が多いですね。
ああいう手口から身を守るには、まず攻撃手段について学ばなければなりません。

というわけで今回は、「notepad.exeを、2度とJavaコードかけない体に改造する」というテーマで、悪いプログラマーになりきってみようと思います。

# 実演

「意味がわからない」という声が聞こえますので、まずはデモを見ていただきたいと思います。

![hack.gif](https://qiita-image-store.s3.amazonaws.com/0/46184/8b13d231-1ce1-743f-bf31-5aea084a41a2.gif)

おわかりいただけただろうか。

![image.png](https://qiita-image-store.s3.amazonaws.com/0/46184/4b36ba56-60a2-a98c-957a-43bbc77d9980.png)

ファイル保存時に`HelloWorld.java`と入力したにも関わらず

![image.png](https://qiita-image-store.s3.amazonaws.com/0/46184/7a302d08-624c-53f2-2da7-55bf6792d2aa.png)

`HelloWorld.cs`となっているのです。悪魔的所業。これではJavaコードがかけないじゃないか。許せない！！！

# 解説と実装

何をやっているのかというと、
**notepad.exeが、"*.java" というファイルをオープンしようとした場合に、そのファイル拡張子を".cs"に差し替え**ています。
つまり、その気になれば、JavaコードだけでなくC++コードを書くことすら叶わない体に改造することも可能なのです。

さて、このような非人道的なハックを行う際に必要な手順は、以下に示すわずか3ステップです。

1. DLLインジェクション
2. CreateFileW APIをフック
3. CreateFileWに渡されるファイルパスを任意に差し替える

以下、これらの処理をざっくりと解説します。

## DLLインジェクション
DLLインジェクションとは、ざっくり申し上げて「他のプロセスに対して任意のDLLをロードさせる行為」です。

やりたいことは「notepad.exeの`CreateFileW`に渡されるすべての`.java`ファイルのパスを、渡される前に消し去りたい」ということですが、そんなことは外部のプロセスからは行えません。
従って、まずはnotepad.exeのプロセスに自身のコード（DLL)をロードさせる必要があります。

そんなことできるの？とお思いになるかもしれませんが、そのための手段をOSはいくつか提供しており[^1]、その中の1つが[SetWindowsHookEx](https://msdn.microsoft.com/ja-jp/library/windows/desktop/ms644990(v=vs.85).aspx)です。

```SetWindowsHookEx.h
HHOOK WINAPI SetWindowsHookEx(
  _In_ int       idHook,
  _In_ HOOKPROC  lpfn,
  _In_ HINSTANCE hMod,
  _In_ DWORD     dwThreadId
);
```

このAPI自体はかなり多機能で、例えばキーボード入力やマウス操作、WindowMessageをフックするような手段を提供しています。ただし、今回はその中身は対して重要ではありません。このAPIの強力なところは、起動中のほぼすべてのプロセスに対して、**hModで指定したモジュール(=DLL)をロードさせることができる**点です。

例えば今回作成した悪いツールでは、以下のような使い方をします。

``` WaruiDll.cpp

LRESULT WINAPI HookProc(int code, WPARAM wParam, LPARAM lParam) {
	return NULL;
}

void Inject() {
	SetWindowsHookEx(WH_CALLWNDPROC, HookProc, GetModuleHandle(__TEXT("WaruiDll")), 0);
}
```

まず、`SetWindowsHookEx`で指定するモジュールハンドルは、DLLのハンドルでなければなりません。(exeではダメ)。なので、HookProcはWaruiDll.dllというDLLに定義しています。なお、今回の目的は**notepad.exeにWaruiDll.dllをロードさせること**なので、`SetWindowsHookEx`に渡す`HookProc`の中身なんでもよいですし、何もしなくて構いません。

ちなみにこの悪いツールのエントリポイントはこちら。

``` WaruiApp.cpp
int main()
{
	Inject();
	
	getchar();
    return 0;
}

```

WaruiApp.exeという実行ファイルとしてビルドされます。起動するや否や`SetWindowsHookEx`を実行し、WaruiDllをばら撒く悪いやつです。

なお、親プロセスが終了するとばらまいたDLLも居なくなるようなので、`getchar`で待機させています。

## CreateFileWをフックする
notepad.exeで書き込むファイル名を差し替えてしまいたいので、`CreateFileW`をフックすればよいでしょう。では、**いつ**フック処理を行えばよいでしょうか。


### Dllmain

Windows環境ではDLLロード時に実行される`DllMain`というエントリポイントがありますので、そちらで処理を行うことにします。以下のようなコードになりました。

``` dllmain.cpp
BOOL APIENTRY DllMain( HMODULE hModule,
                       DWORD  ul_reason_for_call,
                       LPVOID lpReserved
					 )
{
	switch (ul_reason_for_call)
	{
	case DLL_PROCESS_ATTACH:
		NotepadHook();
		break;
	case DLL_THREAD_ATTACH:
	case DLL_THREAD_DETACH:
	case DLL_PROCESS_DETACH:
		break;
	}
	return TRUE;
}

```

`DllMain`は複数回呼ばれることがありますが、引数として`ul_reason_for_call`が渡さます。これにより、どういう状況で`Dllmain`が呼び出されたのかがわかります。今回は1回だけフックをかけてやれば良いはずなので、`DLL_PROCESS_ATTACH`(=プロセスにDLLがロードされたときに呼ばれる）のときに処理を行うこととします。

では、次はいよいよフック処理です。

### notepad.exeだけフック


``` dllmain.cpp
void NotepadHook() {
	TCHAR fileNameBuf[MAX_PATH];

	// only for notepad.exe
	if (GetModuleFileName(GetModuleHandle(NULL), fileNameBuf, MAX_PATH) > 0) {
		if (_tcsstr(fileNameBuf, TEXT("notepad.exe")) != NULL) {
			createFileWPtr = (fpCreateFileW)RewriteFunction("kernel32.dll", "CreateFileW", HookedCreateFileW);
		}
	}
}
```
今回はnotepad.exeのみを対象にしたかったので、プロセス自身のモジュール名を取得し、notepad.exe以外はスルーすることにしました。[^2]
`GetModuleHandle(NULL)`でプロセスのモジュールを取得し、`GetModuleFileName()`でファイルパスを取得し、notepad.exeであるか判定を行ないます。

実際にAPIフックを行う処理は`RewriteFunction`ですが、
`RewriteFunction`の実装は、[APIフック (Cライブラリ関数やWindowsAPIの書き換え) と、その応用例](https://qiita.com/kobake@github/items/8d3d3637c7af0b270098)を参考にさせていただき、掲載されているコードをほぼそのまま使わせていただきました[^3]ので、そちらをご参照いただければと思います。

### HookedCreateFileW

CreateFileWの差し替え先あるHookedCreateFileWの実装はこちら。

``` dllmain.cpp
HANDLE
WINAPI
HookedCreateFileW(
	_In_ LPCWSTR lpFileName,
	_In_ DWORD dwDesiredAccess,
	_In_ DWORD dwShareMode,
	_In_opt_ LPSECURITY_ATTRIBUTES lpSecurityAttributes,
	_In_ DWORD dwCreationDisposition,
	_In_ DWORD dwFlagsAndAttributes,
	_In_opt_ HANDLE hTemplateFile
)
{
	if (lpFileName != NULL) {
		if (auto fileNameLength = lstrlen(lpFileName) > 5) {
			auto extPtr = lpFileName + lstrlen(lpFileName) - 5;
			if (lstrcmpW(extPtr, TEXT(".java")) == 0) {
				// ファイルパスを書き換える。よい子はこういうキャストはしないこと
				lstrcpy((LPWSTR)extPtr, TEXT(".cs"));
			}
		}
	}
	// オリジナルのAPIを呼び出す
	return createFileWPtr(lpFileName, dwDesiredAccess, dwShareMode, lpSecurityAttributes, dwCreationDisposition, dwFlagsAndAttributes, hTemplateFile);
}
```

渡されたlpFileNameの末尾が`.java`であった場合に`.cs`に書き換える悪魔的所業はこちらで行われております。`lpFileName`に小細工を行うだけで、最終的にはオリジナルのAPI(`CreateFileW`)を呼び出しています。

以上で実装は完了です。WaruiApp.exeをひとたび起動すれば、あなたのWindows上で動いているnotepad.exeはjavaファイルを作成できなくなっていることでしょう。

### 立つ鳥跡を濁さず
DLLがデタッチされるときにはちゃんとフックを解除してあげないと、フックされたnotepad.exeは2度と`CreateFileW`を呼べない体になってしまうので気をつけましょう。


## 手口まとめ
1. WaruiApp.exeがnotepad.exeにWaruiDll.dllをインジェクションする(`SetWindowsHookEx`を使う)
2. WaruiDll.dllが、notepad.exeの`CreateFileW`をフックし、悪意ある処理に差し替える
3. Javaコードが保存できない

# おわりに
今回のコードは[こちら](https://github.com/takuya-kikuchi/dllinjectiondemo)にアップしてあります。くれぐれも**ご利用は慎重に、自己責任で**よろしくお願いします。
特にSetWindowsHookによるグローバルフックやAPIフックは下手をするとOSを巻き込んで問題を起こしてしまう可能性が多分にあるので、ご注意ください。ただし、そのぶんいじって楽しい分野ですね。**個人の趣味の範囲であればね**。

余談ですが、DLLインジェクションやAPIフックは、マルウェア・セキュリティソフトウェア**双方が**活用している技術です。使い方次第で毒にも薬にもなりますが、個人の感想としては、毒の成分のほうが多めであるのでは感は否めません。なので近頃OSのアップデートによってどんどん用途や自由度が狭められている印象があります。

あと、notepadでコーディングをするのはおすすめしません。


# 脚注
[^1]: 昔はもっと手軽にインジェクションする手段(AppInit_Dlls)も提供されていましたが、そりゃまぁ危険な技術なので、最近はどんどん肩身が狭くなってきました
[^2]: ここで絞るのを忘れてexplorerとかに変なコードを仕込むと、最終的にはOSを巻き込んで死ぬこと請け合いなので、気をつけよう！
[^3]: 64bitOSで動かしたかったので、コメント欄で指摘されているようにDWORDキャストの問題だけ修正させていただきました。