Help us understand the problem. What is going on with this article?

Windows Kernel Mode Injection

Windows Kernel Mode Injection

この記事は、@_pochiさまの「notepad.exeをJavaコードをかけない体に改造する」
を基に、攻撃手法の一つを紹介する記事です。
当記事では、具体的にどうインジェクションしているのかを解説していこうと思います。

上記記事の手法では、WinAPIを使用してインジェクションされていましたが、そういった手法であれば簡単に対策できます。
しかしながら、別の手法の一つとして、カーネルモードからインジェクションを行う手法もあリます。
具体的にはカーネルドライバーを通してオペレーティングシステムに命令を出します。
オペレーティングシステムの「Ring0」領域で動作するということです。

「Ring」とは、オペレーティングシステム(OS)におけるカースト制のようなものです。
image.png
画像をご覧いただければお分りいただけると思います。
通常のユーザーモードアプリケーションは「Ring3」で動作します。
これは、OSの重要な処理を妨げないようにするためです。
また、このような仕組みの一つに「IRQL」と呼ばれるものがあります。
それについては後々解説していこうと思います。

そもそもインジェクションとは?

image.png
「インジェクション」という言葉の通り、該当プロセスに対して自分の行いたい処理を注入できます。
通常、プロセスが起動すると、オペレーティングシステムが必要なメモリ領域を割り当て、その範囲のメモリ上で動作します。
そのメモリの中には、プログラムを指す全ての情報が含まれています。
DLLインジェクションは、該当プロセスに対して動的にメモリ領域の確保(拡張)を行い、DLL(の処理)を埋め込みます。
これが、「インジェクション」と呼ばれる由来の一つです。
DLLインジェクションの中にも様々な手法が存在しますが、今回は省きます。
(マニュアルマップインジェクションがその手法のひとつです)

これらの方法を悪用して、ゲームにてチート行為を行うサードパーティアプリケーションが多く存在します。

前提知識

Kernel IRQL

「IRQL」とは、Windowsにおける実行の割り込み要求レベル(Interrupt Request Level)のことです。
他のものに重要な処理を邪魔されては困りますから、重要な処理ほど優先度が高くなります。

image.png

参考記事&画像出展:https://sciencepark.co.jp/device_driver/dvdr/report-11/

Kernel APC

「APC」とは非同期プロシージャコール(Asynchronous Procedure Calls)のことです。
3種類のAPCがあります。
1. ユーザーモードAPC
→通常のユーザモードアプリケーションで実行されます。
参考記事:Win32API QueueUserAPC
http://s-kita.hatenablog.com/entry/20100206/1265422276

2. カーネルAPC[通常]
→通常のカーネルモードで実行されます。
IRQLはPASSIVE_LEVELです。
ユーザモードの実行より優先度が高い(横取りできる)
KeInitializeApc() -> KeInsertQueueApc()

3. カーネルAPC[特別]
→特別なカーネルモードで実行されます。
IRQLはAPC_LEVELです。
ユーザモードや通常カーネルAPCより優先度が高いことが特徴の一つです。

優先度: 1 < 2 < 3

実践

主なインジェクションフロー

  1. プロセスを開く.
    ZwOpenProcess()
  2. ターゲットプロセス内にDLLサイズ分のメモリを確保.
    ZwAllocateVirtualMemory()
  3. ターゲットプロセス内のスレッドにアタッチ.
    KeStackAttachProcess()
  4. 確保したメモリ空間にDllを埋め込み.
    strcpy()
  5. ターゲットプロセス内のスレッドから脱出.
    KeUnstackDetachProcess()
  6. 通常カーネルAPCを動作させるメモリの確保.
    ExAllocatePool()
  7. APCの初期化.
    KeInitializeApc()
  8. APCをキューに登録.
    KeInsertQueueApc()
  9. ハンドルを閉じる.
    ZwClose()

全体的なインジェクションフロー

1. 各コールバックルーチンから情報を受け取る.
2. kernel32.dllがロードされているか確認.
FsRtlIsNameInExpression()
3. kernel32.dllのベースアドレスを取得.
pImageInfo->ImageBase
4. kernel32.dll内にあるLoadLibraryExAのアドレスを取得.
→ インジェクションフローに情報を渡す.

コールバックルーチン

NTSTATUS PsSetCreateProcessNotifyRoutineEx
( PLOAD_IMAGE_NOTIFY_ROUTINE NotifyRoutine )

プロセス(アプリケーション)が起動された時や、終了した際に
該当関数に対してコールバック(通知)を行い、情報を引き渡します。
→破棄:PsRemoveCreateProcessNotifyRoutine

NTSTATUS PsSetLoadImageNotifyRoutine
( PLOAD_IMAGE_NOTIFY_ROUTINE NotifyRoutine )

イメージ(例えばkernel32.dll)が読み込まれた時に該当関数に対してコールバック(通知)を行い、情報を引き渡します。
→破棄:PsRemoveLoadImageNotifyRoutine

TIPS: これらの関数は、カーネルセキュリティチェックの対象です。
セキュリティ違反があった場合、KERNEL_SECRITY_CHECK_FAILUEが発生します。

サンプルコード

NTSTATUS DoInject(HANDLE hProc, PEPROCESS Peprocess, PETHREAD Pethread, BOOLEAN Alert)
{
    //対象プロセスのプロセスハンドル
    HANDLE _hProcess = NULL;

    //属性
    OBJECT_ATTRIBUTES attributes =
    {
        sizeof(OBJECT_ATTRIBUTES)
    };

    //情報
    CLIENT_ID clientId =
    {
        NULL
    };
    clientId.UniqueProcess = hProc;
    clientId.UniqueThread = 0;

    CHAR path[] = "C:\\SOMETHING.dll";
    ULONG size = strlen(DllFormatPath) + 1;
    PVOID mem = NULL;

    //プロセスハンドルを開く
    if (NT_SUCCESS(ZwOpenProcess(&_hProc, PROCESS_ALL_ACCESS, &attributes, &clientId)))
    {
        //DLLのサイズ分メモリ領域を確保
        if (NT_SUCCESS(ZwAllocateVirtualMemory(_hProc, &mem, 0, &size, MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE)))
        {
            //APCの状態
            KAPC_STATE kapcState;
            PKAPC pkApc;

            //該当プロセスのスレッドにアタッチ
            KeStackAttachProcess(Peprocess, &kapcState);
            //埋め込み
            strcpy(mem, path);
            //ディアタッチ
            KeUnstackDetachProcess(&KapcState);

            //APCのメモリ領域の確保
            pkApc = (PKAPC)ExAllocatePool(NonPagedPool, sizeof(KAPC));
            if (pkApc)
            {
                //APCの初期化
                KeInitializeApc(pkApc, Pethread, 0, (PKKERNEL_ROUTINE)APCKernelRoutine, 0, /*LoadLibraryのアドレス*/, UserMode, mem);
                //APCをキューに入れる
                KeInsertQueueApc(pkApc, 0, 0, IO_NO_INCREMENT);
                return STATUS_SUCCESS;
            }
        }
        ZwClose(_hProc);
    }
    return STATUS_UNSUCCESSFULL;
}
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
No comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
ユーザーは見つかりませんでした