CreateRemoteThread関数によるDLLインジェクションをやってみる
Windowsにおいて、キーロガーなどが自身のプロセスを隠蔽する手法にDLLインジェクションがある。 これは、プログラムをDLLとして作成し、他のプロセスに読み込ませることで実行するというものである。 ここでは、CreateRemoteThread関数を使ったDLLインジェクションをやってみる。
環境
Windows 8.1 Pro 64 bit版、Visual Studio Community 2013 with Update 4
>systeminfo OS 名: Microsoft Windows 8.1 Pro OS バージョン: 6.3.9600 N/A ビルド 9600 OS 製造元: Microsoft Corporation システムの種類: x64-based PC プロセッサ: 1 プロセッサインストール済みです。 [01]: Intel64 Family 6 Model 69 Stepping 1 GenuineIntel ~758 Mhz >cl Microsoft (R) C/C++ Optimizing Compiler Version 18.00.31101 for x64
プログラムコードを書いてみる
DLLインジェクションは、動作中の他のプロセスのスレッドとしてLoadLibrary関数を実行させることによって行われる。 実際にコードを書くと次のようになる。
/* injector.c */ #include <windows.h> #include <stdio.h> int main(int argc, char *argv[]) { char dllpath[] = "C:\\Users\\user\\Desktop\\test\\spy.dll"; int pid = atoi(argv[1]); HANDLE hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, pid); void *datamemory = VirtualAllocEx(hProcess, NULL, sizeof(dllpath), MEM_COMMIT, PAGE_READWRITE); WriteProcessMemory(hProcess, datamemory, (void *)dllpath, sizeof(dllpath), NULL); HMODULE kernel32 = GetModuleHandle("kernel32"); FARPROC loadlibrary = GetProcAddress(kernel32, "LoadLibraryA"); HANDLE hThread = CreateRemoteThread(hProcess, NULL, 0, (LPTHREAD_START_ROUTINE)loadlibrary, datamemory, 0, NULL); if (!hThread) { /* 32 bit (WOW64) -> 64 bit (Native) won't work */ char errmsg[512]; FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM, NULL, GetLastError(), 0, errmsg, sizeof(errmsg), NULL); printf("%hs", errmsg); return 1; } WaitForSingleObject(hThread, INFINITE); CloseHandle(hThread); VirtualFreeEx(hProcess, datamemory, sizeof(dllpath), MEM_RELEASE); return 0; }
上のコードは、第一引数にDLLを読み込ませるプロセス(以下リモートプロセス)のpidをとる。 コードの内容を簡単に説明すると次のようになる。
- OpenProcess関数でリモートプロセスのハンドルを取得する
- VirtualAllocEx関数でリモートプロセス上のメモリ領域を確保する
- 確保したメモリ領域にWriteProcessMemory関数でDLLのパス文字列を書き込む
- GetModuleHandle関数とGetProcAddress関数でkernel32.dll内にあるLoadLibrary関数のアドレスを取得する
- CreateRemoteThread関数でリモートプロセス上でLoadLibrary関数を実行し、指定したDLLを読み込ませる
- WaitForSingleObject関数でLoadLibrary関数の終了を待つ
- スレッドハンドルおよび確保したメモリ領域を解放する
プログラムをコンパイルする際は、リモートプロセスのビット数(32ビットまたは64ビット)に合わせておく必要がある。 このときkernel32.dllはすべてのプロセスで同じアドレスにあるため、取得したLoadLibrary関数のアドレスをリモートプロセスでも用いることができる。
読み込ませるDLLを書いてみる
次に、読み込ませるDLLとして、動作しているプロセスのイメージパスをダイアログボックスで表示するコードを書いてみる。
/* spy.c */ #include <windows.h> #pragma comment(lib, "user32") BOOL WINAPI DllMain(HINSTANCE hinstDLL, DWORD fdwReason, LPVOID lpvReserved) { char filename[MAX_PATH]; switch (fdwReason) { case DLL_PROCESS_ATTACH: GetModuleFileName(NULL, filename, sizeof(filename)); MessageBox(NULL, filename, "Hello from", MB_SYSTEMMODAL); break; } return TRUE; }
DLLインジェクションをやってみる
まず、DLLインジェクションを行うプログラムおよびDLLをそれぞれ64ビットコンパイラでコンパイルする。
>cl injector.c >cl spy.c /LD
適当な64ビットアプリケーション(たとえばメモ帳)を起動し、tasklistコマンドでpidを調べる。 このpidを使い、DLLインジェクションを行うプログラムを実行する。
>notepad >tasklist イメージ名 PID セッション名 セッション# メモリ使用量 ========================= ======== ================ =========== ============ (snip) notepad.exe 6464 Console 1 8,288 K (snip) >injector.exe 6464
ダイアログボックスが表示され、リモートプロセスからコードが実行されていることが確認できる。
DLLを使わないコードインジェクション
ここではRemoteCreateThread関数でLoadLibrary関数を呼び出したが、WriteProcessMemory関数で実行可能メモリを確保し、そこにコードをコピーして直接実行させることも可能である。 この場合DLLが不要となる一方、利用するAPI関数一式のアドレスを事前に取得し、構造体などの形でコピーしたコードに渡す必要があるため、複雑なコードの実行には適さない。
関連リンク
- 常駐プログラム隠蔽テクニック
- Three Ways to Inject Your Code into Another Process - CodeProject
- 64ビット対応Dllインジェクション
- なのは完売 とある関数の電脳戦 (じょうほうせん とある関数のバトルプログラム) - お前の血は何色だ!! 4
- rarirurero: 64bitプロセスから32bitプロセスにDLL Injection (C言語)
- DLL Injection Part 2: CreateRemoteThread and More
- Win32API エラーコードの説明を取得する FormatMessage - s-kitaの日記