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の日記