この記事は Altplus Advent Calendar 2017 の14日目のエントリです。
こんにちわ。Xamarin人材です。
最近、ゲームのチートだったりハッキングだったり、怖い話が多いですね。
ああいう手口から身を守るには、まず攻撃手段について学ばなければなりません。
というわけで今回は、「notepad.exeを、2度とJavaコードかけない体に改造する」というテーマで、悪いプログラマーになりきってみようと思います。
実演
「意味がわからない」という声が聞こえますので、まずはデモを見ていただきたいと思います。
おわかりいただけただろうか。
ファイル保存時にHelloWorld.java
と入力したにも関わらず
HelloWorld.cs
となっているのです。悪魔的所業。これではJavaコードがかけないじゃないか。許せない!!!
解説と実装
何をやっているのかというと、
notepad.exeが、"*.java" というファイルをオープンしようとした場合に、そのファイル拡張子を".cs"に差し替えています。
つまり、その気になれば、JavaコードだけでなくC++コードを書くことすら叶わない体に改造することも可能なのです。
さて、このような非人道的なハックを行う際に必要な手順は、以下に示すわずか3ステップです。
- DLLインジェクション
- CreateFileW APIをフック
- CreateFileWに渡されるファイルパスを任意に差し替える
以下、これらの処理をざっくりと解説します。
DLLインジェクション
DLLインジェクションとは、ざっくり申し上げて「他のプロセスに対して任意のDLLをロードさせる行為」です。
やりたいことは「notepad.exeのCreateFileW
に渡されるすべての.java
ファイルのパスを、渡される前に消し去りたい」ということですが、そんなことは外部のプロセスからは行えません。
従って、まずはnotepad.exeのプロセスに自身のコード(DLL)をロードさせる必要があります。
そんなことできるの?とお思いになるかもしれませんが、そのための手段をOSはいくつか提供しており1、その中の1つがSetWindowsHookExです。
HHOOK WINAPI SetWindowsHookEx(
_In_ int idHook,
_In_ HOOKPROC lpfn,
_In_ HINSTANCE hMod,
_In_ DWORD dwThreadId
);
このAPI自体はかなり多機能で、例えばキーボード入力やマウス操作、WindowMessageをフックするような手段を提供しています。ただし、今回はその中身は対して重要ではありません。このAPIの強力なところは、起動中のほぼすべてのプロセスに対して、hModで指定したモジュール(=DLL)をロードさせることができる点です。
例えば今回作成した悪いツールでは、以下のような使い方をします。
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
の中身なんでもよいですし、何もしなくて構いません。
ちなみにこの悪いツールのエントリポイントはこちら。
int main()
{
Inject();
getchar();
return 0;
}
WaruiApp.exeという実行ファイルとしてビルドされます。起動するや否やSetWindowsHookEx
を実行し、WaruiDllをばら撒く悪いやつです。
なお、親プロセスが終了するとばらまいたDLLも居なくなるようなので、getchar
で待機させています。
CreateFileWをフックする
notepad.exeで書き込むファイル名を差し替えてしまいたいので、CreateFileW
をフックすればよいでしょう。では、いつフック処理を行えばよいでしょうか。
Dllmain
Windows環境ではDLLロード時に実行されるDllMain
というエントリポイントがありますので、そちらで処理を行うことにします。以下のようなコードになりました。
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だけフック
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の書き換え) と、その応用例を参考にさせていただき、掲載されているコードをほぼそのまま使わせていただきました3ので、そちらをご参照いただければと思います。
HookedCreateFileW
CreateFileWの差し替え先あるHookedCreateFileWの実装はこちら。
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
を呼べない体になってしまうので気をつけましょう。
手口まとめ
- WaruiApp.exeがnotepad.exeにWaruiDll.dllをインジェクションする(
SetWindowsHookEx
を使う) - WaruiDll.dllが、notepad.exeの
CreateFileW
をフックし、悪意ある処理に差し替える - Javaコードが保存できない
おわりに
今回のコードはこちらにアップしてあります。くれぐれもご利用は慎重に、自己責任でよろしくお願いします。
特にSetWindowsHookによるグローバルフックやAPIフックは下手をするとOSを巻き込んで問題を起こしてしまう可能性が多分にあるので、ご注意ください。ただし、そのぶんいじって楽しい分野ですね。個人の趣味の範囲であればね。
余談ですが、DLLインジェクションやAPIフックは、マルウェア・セキュリティソフトウェア双方が活用している技術です。使い方次第で毒にも薬にもなりますが、個人の感想としては、毒の成分のほうが多めであるのでは感は否めません。なので近頃OSのアップデートによってどんどん用途や自由度が狭められている印象があります。
あと、notepadでコーディングをするのはおすすめしません。