0
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

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

Last updated at Posted at 2021-06-01

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できる!

0
1
0

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
  3. You can use dark theme
What you can do with signing up
0
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?