6: DLL Injectionで任意のプログラムを埋め込む
概要
ゲームやその他実行ファイルに自分で書いたプログラムを読み込ませて実行させたい場合、おそらく一番使われる手法がこの DLL Injection という手法。今回は、メッセージボックスを出すプログラムをDLL Injectionしてゲームに実行させることを目標とする。
Unityで作ったゲームを対象にやっていき、ゲームのサンプルはココからダウンロードできる。 また、Windows対象で、
C++
を言語として用いる。ソースコードはまとめてページの一番下にある。
DLLとは
DLL(Dynamic Link Library)とは、動的にEXEファイルなどにリンクされて使用される実行ファイル形式のライブラリです。例えば、普通にC/C++でハローワールドのプログラムを書いて、標準出力として
printf
を使用した際、実際のprintf
の処理はコンパイルした実行ファイルの中にあるわけではなく、
その実行ファイルが読み込むDLLの中に処理があり、printf
を呼び出す際にDLL内の方の該当アドレスにジャンプします。
メッセージボックスを表示するDLLの作成と単体実行
ここでは Visual Studio 2019 を使って作っていく。まずは、VisualStudioを起動後、以下のようにしてDLL用のプロジェクトを作成する。 バージョンが違うとレイアウトも違うと思うが、
DLL & C++ & Windows
の条件がそろってればよさげ。この後、適当に名前や保存先フォルダを指定して作成すると、DLLのテンプレのコードと共にC++コードが開かれるはずである。
最後に、デフォルトで
x86
用でビルドするようになっているので、下図の部分を変えてx64
用にビルドするようにしましょう。
どのDLLにも
DllMain
というメイン関数があり、これはDLLがプロセスに読み込まれた時(DLL_PROCESS_ATTACH
)や、
DLLがスレッドからアンロードされる時(DLL_PROCESS_DETACH
)に、この関数が呼ばれる。今回は、DLLが読み込まれた瞬間にメッセージボックスを出したいだけなので、下図のように一行追加して終了で、書けたら
Ctrl-b
を押してビルドしましょう。
C++なのになんか見慣れないコードだなと思った方もいるかもですが、Windowsプログラミングはこんな感じでマクロが多く、WindowsAPIを主に使用してプログラミングしていきます。
これでDLLを作れましたが、DLLはライブラリなので普通は単体で実行する事ができません。
しかし、rundll32.exe
というWindowsに標準で入ってる実行ファイルを使って、x86やx64のDLLを実行する事ができます。使い方は、
rundll32.exe DLLファイル名 DLLのエクスポート関数名
となっているが、今回のDLLはライブラリとして何か関数を提供(エクスポート)しているわけではないので、
DLLのエクスポート関数名
は適当で良いです。下図のように実行してメッセージボックスが無事出てきたら終了です。
DLL Injectionで上記のDLLをゲームに読み込ませる
ゲームのプロセスに、自分で書いた(チートの)DLLを読み込ませて、そのDLL処理を実行させる手法です。どのDLLをそのEXEファイルがインポートするかというのは予めEXEファイルのインポートセクションという場所に記載されているのですが、ここに書かれてないDLLでも、 その実行ファイルが
LoadLibrary("/path/to/hoge.dll")
というWindowsAPIを実行して呼び出す事により、DLLを動的にインポートすることができます。
DLL Injectionの中でもやり方は様々にあるのですが、今回はこのLoadLibrary("/path/to/上記で作ったDLL")
関数をゲームのプロセスに実行させることにより、DLLをInjectします。他のプロセスにどうやってこれを実行させるんだという点については、
CreateRemoteThread
という、他のプロセスで走らせるスレッドを作成するWindowsAPIを使用して、
LoadLibrary
を呼び出すスレッドを登録することで実行させます。では早速、DLL Injectionを行うEXEを作るための、VisualStudioプロジェクトを作成しましょう。
基本的な流れは上記DLLの時と同じですが、DLLではなく今回はEXEなので、下図を選びましょう。 今回は先ほどと違って、作成した後にテンプレファイルなどは現れず、まっさらだと思います。 とりあえず、
x86
からx64
に変更した後、以下のようにソリューション名の所で右クリックしてC++コードを追加しましょう。
この後、何を追加するかが聞かれるので、C++コードを選びましょう。
これで、何も書かれてないC++ファイルが出てきたと思います。最後に、空のプロジェクトを選択する際に
Console
と書いてあったから分かると思いますが、メッセージボックスとかのGUI系がデバッグ用に使えなくなっているので、
下図のようにソリューション名を右クリックしてプロパティを開き、サブシステムの所を下図のように変えましょう。
また、Windowsでは文字コードが普通のUTF-8ではなく、ワイド文字列のUTF-16なので、そのままプロパティ画面の詳細
を開き、下図のように設定しましょう。
Windowsのプログラムの場合、一番最初に実行されるメイン関数は
WinMain
と言って、以下のようになっているので、まずは下図を映しちゃいましょう。
このWinMain
にDLL Injectionの処理を書いていきます。まずはゲームのプロセスのポインタ(Windows風ではハンドルという)を下図のように、プロセスIDを用いて取得します(
#include <stdio.h>
を追加しているので忘れずに…)。
これで、hproc
にゲームのプロセスのハンドルが入るので、これに対してLoadLibrary
を呼び出させる処理を
CreateRemoteThread
で登録すればいいのですが、その前に、メッセージボックスを表示するDLLへのパスをゲームのプロセスのメモリ内に書き込んだり、
その書き込みを行うためのメモリスペースをアロケートしないといけません。まずは、DLLへのパスを書き込むためのメモリスペースを下図のように
VirtualAllocEx
で確保しましょう。
これで、DLLへのパスを格納できる領域を確保し、そのアドレスをdllPathAddr
に取得したので、実際にWriteProcessMemory
でパスを書き込んでいきます。
これで、準備が整ったので、CreateRemoteThread
を使って、ゲームプロセスにLoadLibrary("/path/to/上記で作ったDLL")
を呼ばせるスレッドを登録します。
これで完成です! Ctrl-b
でビルドしちゃいましょう。実際に試してみましょう。
まずはゲームを起動した後、powershellを起動して以下のコマンドでゲームのプロセスIDを調べます。 PIDがわかったら、以下のようにDLL Injectionの実行ファイルを実行しましょう。 メッセージボックスが出てくるはずです!
もしメッセージボックスが出てきた後、ゲームがクラッシュしたりすぐ終了してしまう場合、アンチウイルスソフトに検知されて消されてる可能性があります。 これは、DLL Injectionというのはマルウェアもよく用いる手法だからです。それなので、アンチウイルスソフトにこの実行ファイルを除外するよう登録しましょう。
DLL Injectionのツールに関して
DLL Injectionは結構多様するので、一々自分でプログラムを書かずにツールを使うことが多いです。ググれば色々出てくるので何かしら一つ入れておきましょう。自分は、自分で作ったこのツールを使っていきます。
ソースコード
メッセージボックスを表示するDLL
// dllmain.cpp : DLL アプリケーションのエントリ ポイントを定義します。
#include "pch.h"
BOOL APIENTRY DllMain( HMODULE hModule, DWORD ul_reason_for_call, LPVOID lpReserved) {
switch (ul_reason_for_call) {
case DLL_PROCESS_ATTACH:
MessageBox(NULL, TEXT("Hello world"), TEXT("hoge"), MB_OK);
case DLL_THREAD_ATTACH:
case DLL_THREAD_DETACH:
case DLL_PROCESS_DETACH:
break;
}
return TRUE;
}
DLL Injectionの実行ファイル
#include <windows.h>
#include <stdio.h>
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow) {
int argc;
LPWSTR* argv;
int pid;
HANDLE hproc;
LPSTR dllPathAddr;
TCHAR dllPath[MAX_PATH] = "C:\\Users\\hoge\\Desktop\\dlli\\msgbox_dll\\x64\\Debug\\msgbox_dll.dll";
// dllinjector.exe <ゲームのプロセスID> で実行する
argv = CommandLineToArgvW(GetCommandLineW(), &argc);
pid = _wtoi(argv[1]);
hproc = OpenProcess(PROCESS_CREATE_THREAD | PROCESS_VM_OPERATION | PROCESS_VM_WRITE, FALSE, pid);
dllPathAddr = (LPSTR)VirtualAllocEx(hproc, NULL, strlen(dllPath), MEM_COMMIT, PAGE_READWRITE);
WriteProcessMemory(hproc, dllPathAddr, dllPath, strlen(dllPath), NULL);
CreateRemoteThread(hproc, NULL, 0, (LPTHREAD_START_ROUTINE)LoadLibrary, dllPathAddr, 0, NULL);
CloseHandle(hproc);
return 0;
}