セキュリティ系の勉強、その他開発メモとか雑談. GithubはUnity触っていた頃ものがメイン Twitterフォローもよろしくです

アナライジングマルウェア 4章コードインジェクションをするマルウェアの解析 メモ

メモリ操作によって、既存のプロセスに新たな感染動作を追加発生させるテクニック。
DLLとして実装されたマルウェアやオンメモリで動作することを前提としたマルウェアの感染動作をこれから列挙。


コードインジェクションの方法

SetWindowsHookEx

Windowsに標準で搭載されているUser32.dllが提供しているAPIで、実行中のスレッド(のプロセスメモリ空間)に対してインジェクションしたい関数を実装したDLLのマッピングと、特定のウィンドウメッセージを処理する関数のフックを同時に行ってくれる。自分のプロセス内のスレッドだけでなく、同じシステム上で実行している全てのスレッドのプロセスメモリ空間にDLLをインジェクションできる。

HHOOK SetWindowsHookEx(
  int idHook,        // フックタイプ
  HOOKPROC lpfn,     // フックプロシージャ
  HINSTANCE hMod,    // アプリケーションインスタンスのハンドル
  DWORD dwThreadId   // スレッドの識別子
);

IpfnにはidHookで指定したウィンドウメッセージを受信した際に呼び出される関数を指定。hModにはIpfnが指すフックプロシージャを保持しているDLLのハンドルを指定。自分自身のプロセスを指す場合はNULL。dwThreadIdにはフックしたいスレッドIDを指定。ここで0を指定すると、呼び出し元スレッドと同じデスクトップ内で動作している全てのスレッドをさす。


* 特定のウィンドウメッセージを処理する関数をフックするための利用
キーボードメッセージを処理する関数をフックしキーボードロガーになる
* DLLを特定のプロセス空間にマッピングさせるために利用
 悪意のあるコードを記述したDLLを特定のプロセスメモリ空間に読み込ませ実行することで、特定のプロセスメモリ上で任意のコードを実行可能。プロセスのネットワークトラフィックを覗けたりする。
Windows提供なため動作が安定、実装も容易だが、インジェクション対象がウィンドウメッセージを受信するプロセス(スレッド)に限られる。

レジストリキー"Applnit_DLLs"を使う

任意のDLLを自動的に呼び出させる方法に、レジストリを改変する方法がある。レジストリキーHKEY_LOCAL_MACHINE¥Software¥Microsoft¥Windows NT¥CurrentVersion¥Windows¥AppInit_DLLsのレジストリ値を編集し、自動的に読み込ませたいDLLの名前を追加するだけで任意のDLLを自動的に読み込ませることができる。ただし、user32.dllの初期化処理中に自動的にロードされるため、user32.dllを使用しないプログラムではできない。書籍ではレジストリエディタを使用することで、値の書き換えを行っていた。

CreateRemoteThreadを使う

指定したぷrせす空間内でスレッドを起動させるAPI。PCのスペックが低かった頃プロセスのコンテキスト切り替えさえも重い動作と考えられ、1つのプロセスに複数のスレッドを詰め込めるようにしたとかなんとか、って噂があるらしい。

  HANDLE hProcess,        // 新しいスレッドを稼働させるプロセスを識別するハンドル
  LPSECURITY_ATTRIBUTES lpThreadAttributes,
                            // スレッドのセキュリティ属性へのポインタ
  DWORD dwStackSize,     // 初期のスタックサイズ (バイト数)
  LPTHREAD_START_ROUTINE lpStartAddress,
                           // スレッド関数へのポインタ
  LPVOID lpParameter,     // 新しいスレッドの引数へのポインタ
  DWORD dwCreationFlags,  // 作成フラグ
  LPDWORD lpThreadId      // 取得したスレッド識別子へのポインタ
);

hProcessで指定したプロセス内で新しいスレッド生成。lpStartAddressはスレッドのエントリポイントを指定する。lpStartAddressとlpParameterはhProcessで指定されたプロセス内のメモリ空間を指す必要があるため、起動させたい関数やそのパラメータを事前に用意する必要がある。VirtualAllocExは指定したプロセス空間内にメモリ領域を確保するAPIは返り値で指定したプロセス空間内のアドレスが戻る。WriteProcessMemoryは指定したプロセス空間内の指定したアドレスに値を書き込むAPI。この2つを使用して解決する。

LoadLibraryによるDLLのインジェクション

1つの引数を持つ関数であればCreateRemoteThreadでスレッドとして実行できる。

/*remote_loadlib.c*/
#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, pid);
    pathSize = strlen(libPath) + 1;
    remoteLibPath = VirtualAllocEx(proc,
        NULL, pathSize, MEM_COMMIT, PAGE_READWRITE);
    WriteProcessMemory(proc, remoteLibPath, libPath,
        pathSize, NULL);
    CreateRemoteThread(proc, NULL, 0,
        (LPTHREAD_START_ROUTINE)LoadLibrary,
        remoteLibPath, 0, NULL);
    return 0;
}
/*evil.c*/
#include <windows.h>
void evilCode()
{
    char    buf[256];
    GetModuleFileName(NULL, buf, sizeof(buf));
    OutputDebugString(buf);
}
BOOL WINAPI DllMain(HINSTANCE inst, DWORD reason, LPVOID reserved)
{
    if(reason == DLL_PROCESS_ATTACH){
        evilCode();
    }
    return TRUE;
}
/*VSコマンドプロンプトで実行 evil.dllを生成*/
cl /LD evil.c
remote_loadlib.exe C:~~¥evil.dll プロセスのPID

任意コードのインジェクション

WriteProcessMemoryで感染動作をする機械語命令列を書き込みCreateRemoteThreadで起動する方法。DLLを事前に用意することなくインジェクションが可能。


コードインジェクションマルウェアの解析

コードインジェクションの発生を検出し、インジェクションされているコードを特定する必要がある。

コードインジェクションの検出

検出に有用なツール

Autoruns

Windowsの起動時に自動的に起動するプロセスやサービス、自動的にロードされるDLLの一覧を表示する。AppInit_DLLsの確認ができる。PCが再起動した後感染動作を行うマルウェアの検出にも使用できる。


インジェクションされたコード(DLL)の解析-> ImmunityDebuggerで開いてる。LordPE等でdllをexeにする。

CreateRemoteThreadでインジェクションされたコードの解析

WriteProcessMemoryを監視し、そこで書き込まれる内容を別途ダンプすることでコードを解析できる。 以下はImmunityDebuggerで行うサンプル

import immlib
DUMPDIR = "C:\\temp"
class WriteProcessMemoryHook(immlib.LogBpHook):
    def __init__(self):
        immlib.LogBpHook.__init__(self)
    def run(self, regs):
        imm = immlib.Debugger()
        procHandle = imm.readLong(regs["ESP"] + 4)
        dstBuff = imm.readLong(regs["ESP"] + 8)
        srcBuff = imm.readLong(regs["ESP"] + 12)
        srcSize = imm.readLong(regs["ESP"] + 16)
        fp = open("%s\\%08x-%08x-%08x.dmp"
            % (DUMPDIR, procHandle, dstBuff, srcSize),
            "wb")
        fp.write(imm.readMemory(srcBuff, srcSize))
        fp.close()
class CreateRemoteThreadHook(immlib.LogBpHook):
    def __init__(self):
        immlib.LogBpHook.__init__(self)
    def run(self, regs):
        imm = immlib.Debugger()
        procHandle = imm.readLong(regs["ESP"] + 4)
        startAddr = imm.readLong(regs["ESP"] + 16)
        imm.Log("CreateRemoteThread(proc=%08x,start=%08x)"
            % (procHandle, startAddr))
def main(args): 
    imm = immlib.Debugger()
    addr = imm.getAddress("kernel32.WriteProcessMemory")
    wpmHook = WriteProcessMemoryHook()
    wpmHook.add("WriteProcessMemoryHook", addr)
    addr = imm.getAddress("kernel32.CreateRemoteThread")
    crtHook = CreateRemoteThreadHook()
    crtHook.add("CreateRemoteThreadHook", addr)
    return "Dump injected code"