search
LoginSignup
0
Help us understand the problem. What are the problem?

More than 1 year has passed since last update.

posted at

updated at

API Hook (Sleep関数に対するIAT Hook)

IATの場所

image.png
注意としてIATはプログラムロード前とロード後で入ってる内容が違う。今回は実際にプログラムを走らせてそのプロセスのIATをいじっていくので、ロード後をいじっていくことになる。
ILTの方も関数の名前を調べるのに使う(このアドレスがSleep関数のものかみたいな)。

IAT Hook のイメージ

image.png
image.png

自プロセスに対するIAT Hook

まずは自分の実行ファイルのSleep関数の呼び出しを MessageBox を表示する MySleepFunc に書き換える方法が以下。しかし、本来の "Hook" は別の処理をさせた後、本来のSleepに処理を戻すので、 MySleepFuncoriginalSleep(dwMilliseconds) をコメントアウトすれば元のSleepに処理が戻るようになっている。

self-iat-hook.cpp
#include <windows.h>
#include <stdio.h>
#include <Dbghelp.h>
#include <sstream>
#pragma comment(lib, "Dbghelp")


typedef void* (WINAPI* OriginalSleep)(DWORD);
OriginalSleep originalSleep;


void WINAPI MySleepFunc(DWORD dwMilliseconds) {
    MessageBox(NULL, TEXT("Hooked Sleep!!!"), TEXT("success"), MB_OK | MB_ICONEXCLAMATION);
    // originalSleep(dwMilliseconds);
    return;
}



int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow) {
    TCHAR szMyPath[MAX_PATH];
    ULONG cbSize = 0;

    GetModuleFileName(NULL, szMyPath, _countof(szMyPath));
    HANDLE hModule = GetModuleHandle((LPCSTR)szMyPath);

    originalSleep = (OriginalSleep)GetProcAddress(GetModuleHandle("kernel32.dll"), "Sleep");
    PIMAGE_IMPORT_DESCRIPTOR pImageImportDescriptor = (PIMAGE_IMPORT_DESCRIPTOR)ImageDirectoryEntryToData(hModule, TRUE, IMAGE_DIRECTORY_ENTRY_IMPORT, &cbSize);

    for (; pImageImportDescriptor->Name; pImageImportDescriptor++) {
        LPCSTR pModuleName = (LPCSTR)((PBYTE)hModule + pImageImportDescriptor->Name);
        DWORD ModuleBase = (DWORD)GetModuleHandle(pModuleName);

        PIMAGE_THUNK_DATA pFirstThunk = (PIMAGE_THUNK_DATA)((PBYTE)hModule + pImageImportDescriptor->FirstThunk);
        PIMAGE_THUNK_DATA pOriginalFirstThunk = (PIMAGE_THUNK_DATA)((PBYTE)hModule + pImageImportDescriptor->OriginalFirstThunk);

        for (; pFirstThunk->u1.Function; pFirstThunk++, pOriginalFirstThunk++) {
            FARPROC pfnImportedFunc = (FARPROC)(pFirstThunk->u1.Function);
            PIMAGE_IMPORT_BY_NAME pImageImportByName = (PIMAGE_IMPORT_BY_NAME)((PBYTE)hModule + pOriginalFirstThunk->u1.AddressOfData);

            if (pfnImportedFunc == (FARPROC)originalSleep) {
                MEMORY_BASIC_INFORMATION mbi;
                DWORD dwJunk = 0;

                VirtualQuery(pFirstThunk, &mbi, sizeof(MEMORY_BASIC_INFORMATION));
                if (!VirtualProtect(mbi.BaseAddress, mbi.RegionSize, PAGE_EXECUTE_READWRITE, &mbi.Protect)) {
                    MessageBox(NULL, TEXT("VirtualProtect Failed!"), TEXT("error"), MB_OK | MB_ICONERROR);
                    return FALSE;
                }

                pFirstThunk->u1.Function = (ULONGLONG)(DWORD_PTR)MySleepFunc;
                if (VirtualProtect(mbi.BaseAddress, mbi.RegionSize, mbi.Protect, &dwJunk))
                    break;
            }

        }
    }

    MessageBox(NULL, TEXT("start sleep"), TEXT("info"), MB_OK);
    Sleep(3000);
    MessageBox(NULL, TEXT("end sleep"), TEXT("info"), MB_OK);

    return TRUE;
}

注意としては、

originalSleep = (OriginalSleep)GetProcAddress(GetModuleHandle("kernel32.dll"), "Sleep");

をやっているので、originalSleep=MySleepFunc とすればいいのではと思うかもしれないが、GetProcAddress はDLLがエクスポートしている関数のアドレスをただ取得しているだけで、IATに格納されているアドレスへのポインタではない。つまり、originalSleep を書き換えてもIATを書き換えている事にはならない。

コードの大まかな流れは、

  1. ImageBase取得
  2. Sleep関数のアドレスをDLLのエクスポートを見て取得(originalSleep)
  3. DLL毎にある各インポートテーブル(IMAGE_IMPORT_DESCRIPTOR)のNameメンバから、Dllの名前とそのDLLが配置されてるベースアドレスを取得
  4. インポートテーブルのメンバからIAT(FirstThunk)、ILT(OriginalFirstThunk)へのポインタ取得
  5. IAT/ILTには各関数毎にIMAGE_THUNK_DATAがあるのでそれをfor文で回してる
  6. pfnImportedFuncにu1.Function(IAT内の関数の実際のアドレス)へのポインタが入る
  7. pdfImporedFunc(IATのアドレス)がSleepの関数のアドレスとマッチしたらif文内に入る
  8. IATを書き換えるため、読み書きの権限にメモリの権限をセット
  9. pFirstThunk->u1.Function(IAT内のSleepのアドレス)にMySleepFuncのアドレスをセット
  10. メモリの権限戻す
  11. Sleepを呼び出す(これがhookされる)

リモートプロセスに対するIAT Hook

ぶっちゃけ上記のコードをDLLにして、DLL Injectionすればいいだけ(DLL Injectionは適当にツール使う)。
というのも、

GetModuleFileName(NULL, szMyPath, _countof(szMyPath));
HANDLE hModule = GetModuleHandle((LPCSTR)szMyPath);

これは、DLL InjectionしてDLLがアタッチされた後に上記が実行されると、Injectした対象のプロセスが取得でき、かつInjectした対象のメモリをいじれるようになるので、あまりリモートにやるのも自分自身のプロセスにやるのも変わらない。

以下ソース。

remote-iat-hook.cpp
#include "pch.h"
#include <windows.h>
#include <Dbghelp.h>
#include <sstream>
#pragma comment(lib, "Dbghelp")


typedef void* (WINAPI* OriginalSleep)(DWORD);
OriginalSleep originalSleep;


void WINAPI MySleepFunc(DWORD dwMilliseconds) {
    MessageBox(NULL, TEXT("Hooked Sleep!!!"), TEXT("success"), MB_OK | MB_ICONEXCLAMATION);
    // originalSleep(dwMilliseconds);
    return;
}


BOOL APIENTRY DllMain( HMODULE hModule,
                       DWORD  ul_reason_for_call,
                       LPVOID lpReserved
                     )
{
    if (ul_reason_for_call == DLL_PROCESS_ATTACH) {
        TCHAR szMyPath[MAX_PATH];
        ULONG cbSize = 0;

        GetModuleFileName(NULL, szMyPath, _countof(szMyPath));
        HANDLE hModule = GetModuleHandle((LPCSTR)szMyPath);
        std::stringstream s;
        s << szMyPath << std::endl;
        MessageBox(NULL, s.str().c_str(), TEXT("target"), MB_OK);

        originalSleep = (OriginalSleep)GetProcAddress(GetModuleHandle("kernel32.dll"), "Sleep");
        PIMAGE_IMPORT_DESCRIPTOR pImageImportDescriptor = (PIMAGE_IMPORT_DESCRIPTOR)ImageDirectoryEntryToData(hModule, TRUE, IMAGE_DIRECTORY_ENTRY_IMPORT, &cbSize);

        for (; pImageImportDescriptor->Name; pImageImportDescriptor++) {
            LPCSTR pModuleName = (LPCSTR)((PBYTE)hModule + pImageImportDescriptor->Name);
            DWORD ModuleBase = (DWORD)GetModuleHandle(pModuleName);

            PIMAGE_THUNK_DATA pFirstThunk = (PIMAGE_THUNK_DATA)((PBYTE)hModule + pImageImportDescriptor->FirstThunk);
            PIMAGE_THUNK_DATA pOriginalFirstThunk = (PIMAGE_THUNK_DATA)((PBYTE)hModule + pImageImportDescriptor->OriginalFirstThunk);

            for (; pFirstThunk->u1.Function; pFirstThunk++, pOriginalFirstThunk++) {
                FARPROC pfnImportedFunc = (FARPROC)(pFirstThunk->u1.Function);
                PIMAGE_IMPORT_BY_NAME pImageImportByName = (PIMAGE_IMPORT_BY_NAME)((PBYTE)hModule + pOriginalFirstThunk->u1.AddressOfData);

                if (pfnImportedFunc == (FARPROC)originalSleep) {
                    MEMORY_BASIC_INFORMATION mbi;
                    DWORD dwJunk = 0;

                    VirtualQuery(pFirstThunk, &mbi, sizeof(MEMORY_BASIC_INFORMATION));
                    if (!VirtualProtect(mbi.BaseAddress, mbi.RegionSize, PAGE_EXECUTE_READWRITE, &mbi.Protect)) {
                        MessageBox(NULL, TEXT("VirtualProtect Failed!"), TEXT("error"), MB_OK | MB_ICONERROR);
                        return FALSE;
                    }

                    pFirstThunk->u1.Function = (ULONGLONG)(DWORD_PTR)MySleepFunc;
                    if (VirtualProtect(mbi.BaseAddress, mbi.RegionSize, mbi.Protect, &dwJunk))
                        break;
                }
            }
        }
    }

    return TRUE;
}

これをInjectする対象として、1秒間隔でずっとSleepを実行する以下を使う。

test-sleep.cpp
#include <windows.h>
#include <stdio.h>

int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow) {
    int i=0;
    while(1) {
        if(i%5==0) printf("%d\n", i);
        Sleep(1000);
        i++;
    }
    return TRUE;
}

上記をコンパイルし、実際にインジェクトしてみると、

image.png

Hookできる!

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
What you can do with signing up
0
Help us understand the problem. What are the problem?