質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

ただいまの
回答率

87.37%

Ntdll.dllに存在するNtUnmapViewOfSectionのフックを行い,NtUnmapViewOfSectionを行ったプロセスの特定を行いたいです.

解決済

回答 1

投稿 編集

  • 評価
  • クリップ 2
  • VIEW 1,098

実現したいこと

Ntdll.dllに存在するAPI「NtUnmapViewOfSection」のフックを行い,NtUnmapViewOfSectionを呼び出したプロセスを特定しようとしています.
notepad.exeをJavaコードをかけない体に改造するを参考にしています.
自分の以前の質問と似たような質問ですが,変更点,エラー内容が違っているので回答お願いします.

悩んでいること

1.rewriteファンクション内でNtdll.dllの参照を行うが参照できず「アプリケーションが正しく動作しませんでした」と表示される.他のdll(kernel32.dllなど)では参照,フックを行うときにそのようなエラーは出なかった.アドレスの問題と推測しているが不明.
2.GetModuleFileName(GetModuleHandle(NULL), fileNameBuf, MAX_PATH) > 0)でAPIを呼び出したプロセスを特定できるのか?
HookedNtUnmapViewOfSection()内でOutputDebugString(fileNameBuf);としているがこれで正しいのかわからない.

コード

自作dll(WaruiDll)の作成部分とdllファイルばらまき部分に分かれています.

自作dllファイルの作成

dllmainでdllファイルのエントリポイントの作成とrewriteファンクションの呼び出しを行います.
このコードによって作成されるdllファイルはプロセスがアタッチされたときに実行されます.
rewriteファンクションはrewrite.cppの中で定義されています.

// dllmain.cpp : DLL アプリケーションのエントリ ポイントを定義します。
#include "stdafx.h"
#include "rewrite.h"
#include <windows.h>
#include <stdio.h>

fp_NtUnmapViewOfSection NtUnmapViewOfSectionPtr = NULL;
TCHAR fileNameBuf[MAX_PATH]=("not complete");

NTSTATUS
WINAPI
HookedNtUnmapViewOfSection(
    HANDLE ProcessHandle,
    PVOID  BaseAddress
)
{
    OutputDebugString(fileNameBuf);
    OutputDebugString(TEXT("HookedNtUnmapViewOfSection!\n"));
    return NtUnmapViewOfSectionPtr(ProcessHandle,BaseAddress);
}


void APIHook() {
    if (GetModuleFileName(GetModuleHandle(NULL), fileNameBuf, MAX_PATH) > 0) {
            NtUnmapViewOfSectionPtr = (fp_NtUnmapViewOfSection)RewriteFunction("Ntdll.dll", "NtUnmapViewOfSection", HookedNtUnmapViewOfSection);
    }
}

BOOL APIENTRY DllMain( HMODULE hModule,
                       DWORD  dwReason,
                       LPVOID lpReserved
                     )
{
    if (dwReason == DLL_PROCESS_ATTACH)
    {
        DisableThreadLibraryCalls(hModule);
        APIHook();
    }
    return TRUE;
}


rewriteの中でrewriteファンクションを定義しています.
rewriteファンクションでは,第一引数にフックを行いたいAPIが含まれるdllファイル名,第二引数にフックを行いたいAPI名,第三引数にフックを行った時の挙動を記したファンクションのポインタを渡します.
dllファイルのハンドルを求め,NULLチェックをしたのちに,dllファイル内を全探索して第二引数のAPI名を探し,見つかったら第三引数のファンクションに置き換える.

#include "stdafx.h"
#include <stdlib.h>
#include <imagehlp.h>
#include "rewrite.h"
#pragma comment(lib,"imagehlp.lib")

void* RewriteFunctionImp(const char* szRewriteModuleName, const char* szRewriteFunctionName, void* pRewriteFunctionPointer)
{
    for (int i = 0; i < 2; i++) {
        // ベースアドレス
        intptr_t pBase = 0;
        if (i == 0) {
            if (szRewriteModuleName) {
                pBase = (intptr_t)::GetModuleHandleA(szRewriteModuleName);
            }
        }
        else if (i == 1) {
            pBase = (intptr_t)GetModuleHandle(NULL);
        }
        if (!pBase)continue;

        // イメージ列挙
        ULONG ulSize;
        PIMAGE_IMPORT_DESCRIPTOR pImgDesc = (PIMAGE_IMPORT_DESCRIPTOR)ImageDirectoryEntryToData((HMODULE)pBase, TRUE, IMAGE_DIRECTORY_ENTRY_IMPORT, &ulSize);
        for (; pImgDesc->Name; pImgDesc++) {
            const char* szModuleName = (char*)(intptr_t)(pBase + pImgDesc->Name);
            // THUNK情報
            PIMAGE_THUNK_DATA pFirstThunk = (PIMAGE_THUNK_DATA)(intptr_t)(pBase + pImgDesc->FirstThunk);
            PIMAGE_THUNK_DATA pOrgFirstThunk = (PIMAGE_THUNK_DATA)(intptr_t)(pBase + pImgDesc->OriginalFirstThunk);
            // 関数列挙

            for (; pFirstThunk->u1.Function; pFirstThunk++, pOrgFirstThunk++) {
                if (IMAGE_SNAP_BY_ORDINAL(pOrgFirstThunk->u1.Ordinal))continue;
                PIMAGE_IMPORT_BY_NAME pImportName = (PIMAGE_IMPORT_BY_NAME)(intptr_t)(pBase + (intptr_t)pOrgFirstThunk->u1.AddressOfData);
                if (!szRewriteFunctionName) {
                    // 表示のみ
                    printf("Module:%s Hint:%d, Name:%s\n", szModuleName, pImportName->Hint, pImportName->Name);
                }
                else {
                    // 書き換え判定
                    if (stricmp((const char*)pImportName->Name, szRewriteFunctionName) != 0)continue;

                    // 保護状態変更
                    DWORD dwOldProtect;
                    if (!VirtualProtect(&pFirstThunk->u1.Function, sizeof(pFirstThunk->u1.Function), PAGE_READWRITE, &dwOldProtect))
                        return NULL; // エラー
                    //OutputDebugString(pImportName->Name);
                    // 書き換え
                    void* pOrgFunc = (void*)(intptr_t)pFirstThunk->u1.Function; // 元のアドレスを保存しておく
                    WriteProcessMemory(GetCurrentProcess(), &pFirstThunk->u1.Function, &pRewriteFunctionPointer, sizeof(pFirstThunk->u1.Function), NULL);
                    pFirstThunk->u1.Function = (intptr_t)pRewriteFunctionPointer;

                    // 保護状態戻し
                    VirtualProtect(&pFirstThunk->u1.Function, sizeof(pFirstThunk->u1.Function), dwOldProtect, &dwOldProtect);
                    return pOrgFunc; // 元のアドレスを返す
                }
            }
        }
    }
    return NULL;
}

void* RewriteFunction(const char* szRewriteModuleName, const char* szRewriteFunctionName, void* pRewriteFunctionPointer)
{
    //OutputDebugString(TEXT("Rewrite1!\n"));
    return RewriteFunctionImp(szRewriteModuleName, szRewriteFunctionName, pRewriteFunctionPointer);
}


Inject()の中でSetWindowsHookEx()を呼び出す.

#include "stdafx.h"

LRESULT WINAPI HookProc(int code, WPARAM wParam, LPARAM lParam) {
    return NULL;
}

void Inject() {
    //OutputDebugString(TEXT("Inject!\n"));
    SetWindowsHookEx(WH_CALLWNDPROC, HookProc, GetModuleHandle(__TEXT("WaruiDll")), 0);
}

dllばらまき部分

メインの中で自作したdllファイルをばらまくInject()を呼び出す.

#include "stdafx.h"

int main()
{
    Inject();
    printf("I am WaruiApp!\n");
    getchar();
    return 0;
}
  • 気になる質問をクリップする

    クリップした質問は、後からいつでもマイページで確認できます。

    またクリップした質問に回答があった際、通知やメールを受け取ることができます。

    クリップを取り消します

  • 良い質問の評価を上げる

    以下のような質問は評価を上げましょう

    • 質問内容が明確
    • 自分も答えを知りたい
    • 質問者以外のユーザにも役立つ

    評価が高い質問は、TOPページの「注目」タブのフィードに表示されやすくなります。

    質問の評価を上げたことを取り消します

  • 評価を下げられる数の上限に達しました

    評価を下げることができません

    • 1日5回まで評価を下げられます
    • 1日に1ユーザに対して2回まで評価を下げられます

    質問の評価を下げる

    teratailでは下記のような質問を「具体的に困っていることがない質問」、「サイトポリシーに違反する質問」と定義し、推奨していません。

    • プログラミングに関係のない質問
    • やってほしいことだけを記載した丸投げの質問
    • 問題・課題が含まれていない質問
    • 意図的に内容が抹消された質問
    • 過去に投稿した質問と同じ内容の質問
    • 広告と受け取られるような投稿

    評価が下がると、TOPページの「アクティブ」「注目」タブのフィードに表示されにくくなります。

    質問の評価を下げたことを取り消します

    この機能は開放されていません

    評価を下げる条件を満たしてません

    評価を下げる理由を選択してください

    詳細な説明はこちら

    上記に当てはまらず、質問内容が明確になっていない質問には「情報の追加・修正依頼」機能からコメントをしてください。

    質問の評価を下げる機能の利用条件

    この機能を利用するためには、以下の事項を行う必要があります。

回答 1

checkベストアンサー

+3

まず、Native API の Hook に SetWindowsHookEx は無意味です。
なぜなら、DLL Injection の対象が GUI アプリに限定されてしまうから。
NtUnmapViewOfSection() は CUI (コンソール) アプリでも利用可能なので、SetWindowsHookEx では対応できません。

不特定のプロセスに対して NtUnmapViewOfSection() をフックしたいのであれば、まずプロセスの Create / Terminate を監視する機能が必要です。

プロセスの Create / Terminate を監視は色々と方法がありますが、一番ベストな方法はカーネル モード ドライバを作成し、その中で PsSetCreateProcessNotifyRoutine (あるいは PsSetCreateProcessNotifyRoutineEx) で新規プロセス生成を監視する実装です。

PsSetCreateProcessNotifyRoutineEx

https://docs.microsoft.com/en-us/windows-hardware/drivers/ddi/ntddk/nf-ntddk-pssetcreateprocessnotifyroutineex

ただしカーネル モード ドライバは、ユーザ モード側で動作する一般的なアプリやサービスとは全く異なる知識が必要になるので、それなりに難易度は高いです。
それ以外としては、EnumProcessModules() で定期的にプロセスの増減を監視する方法もありますが、CPU リソースを無駄に消費するのでパフォーマンス的に難があり、かつ一部のサービス プロセスに対しては検出することができないという制限もあります。

で、とにかくプロセスの Create / Terminate を監視する機能が実現出来たら、OpenProcess(), VirtualAllocEx(), WriteProcessMemory(), CreateRemoteThreadEx() の各API を組み合わせて、外部プロセスから対象 DLL を Injection します。

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

https://snoozy.hatenablog.com/entry/2019/12/22/195234

DLL Injection に成功したら、その DLL 内から Hook Routine を仕掛けます。
質問に提示されているコードは PEHeader 内の IAT 書き換えによる実装方法ですが、これも意味がないです。
対象プロセス内に、GetProcAddress() により自前で NtUnmapViewOfSection() ルーチン アドレスを取得しているモジュールが存在する場合、IAT に NtUnmapViewOfSection() ルーチン アドレスは存在しないので、Catch することもできません。
(さらに言えば、IAT による Hook では、そのプロセス内にロードされているすべてのモジュールの PEHeaser をチェックする必要があるので、とても面倒なのに上記のような「落ち」がある。)
漏れなく監視したいのであれば、NtUnmapViewOfSection() ルーチンの prologue code を改ざんする必要があります。

(お手軽に実現したいなら "Detours" がお勧め。)

メモリパッチによるAPIフックコードの実装をやってみる

https://snoozy.hatenablog.com/entry/2020/02/29/093033

投稿

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2020/11/27 17:09

    その方法だと、NtUnmapViewOfSection が呼び出されるちゃんとした頻度を把握することはできません。
    なぜなら、ntdll.dll 内の NtUnmapViewOfSection() Hook では、Session 0 以外の、かつ User Mode 側で発生した NtUnmapViewOfSection() しか検知できないから。
    つまり、Session 0 で動作している各種 Service Process や Kernel Mode 側から発行された NtUnmapViewOfSection() は検出できないけど、NtUnmapViewOfSection() を呼び出す頻度は、Session 0 で動作している各種 Service Process や Kernel Mode 側からの方が圧倒的に多いから、一般的な API Hook 方法ではまともな呼び出し頻度を把握することはできません。
    NtUnmapViewOfSection の呼び出し頻度を調べたいなら、WinDbg をカーネル デバッグ接続して、NT カーネル側の NtUnmapViewOfSection および ZwUnmapViewOfSection に Breakpoint を張れば、すべての Call を Catch できます。
    (軽く確認しただけでも1秒間に数十回のコールが発生していましたが、そのすべてが System Process と Service Process からでした。なので API Hook での確認は意味がない。)

    なお、マルウェアが NtUnmapViewOfSection() をコールするとすれば、Kernel Mode 側もしくは Session 0 で動作する Service Process から。
    一般の User Mode Process から NtUnmapViewOfSection() をコールするなんて、セキュリティ ソフトに「私を見つけて!!」って言ってるようなもんです。

    キャンセル

  • 2020/11/30 16:07

    ご指摘ありがとうございます.
    WinDbgを使用して試してみようと思います.
    API Hookの使用については吟味します.

    キャンセル

  • 2021/05/12 14:42 編集

    間違えました。

    キャンセル

15分調べてもわからないことは、teratailで質問しよう!

  • ただいまの回答率 87.37%
  • 質問をまとめることで、思考を整理して素早く解決
  • テンプレート機能で、簡単に質問をまとめられる

関連した質問

同じタグがついた質問を見る

  • トップ
  • C++に関する質問
  • Ntdll.dllに存在するNtUnmapViewOfSectionのフックを行い,NtUnmapViewOfSectionを行ったプロセスの特定を行いたいです.