windows_DLLインジェクションで自分自身のプロセスのMessageBox関数のアドレスを変更する

前回まではsetWindowsHookExを使ってメッセージボックス表示の関数呼び出しをフックしていたのですが、今回はDLLを読み込んだ際に既存のMessageBox関数のアドレスを上書きすることでフックできるようにしたいと思います。

やりたいことはこちらの東京工業大学 ロボット技術研究会のページに載ってありました。ロボ技研のページでは指定したプロセスに対してDLLインジェクションさせていたのですが、今回はまずLoadlibraryを実行して自分自身のプロセスに対してDLLインジェクションを実行したいと思います。

まず、DLLを生成するプロジェクトを作成します。

DLL生成プロジェクトの作成

DllMainを実行するファイルの全体像は以下のようになります。

dllmain.cpp

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

BOOL APIENTRY DllMain( HMODULE hModule, DWORD  ul_reason_for_call, LPVOID lpReserved )
{
    HANDLE hThread;
    switch (ul_reason_for_call)
    {
    case DLL_PROCESS_ATTACH:
        hThread = CreateThread(NULL, 0, apiHook, (LPVOID)NULL, 0, NULL);
        if (hThread == NULL) {
            MessageBox(NULL, _TEXT("CreateThread"), _TEXT("Error"), MB_OK);
            return FALSE;
        }
        CloseHandle(hThread);
        break;
    case DLL_THREAD_ATTACH:
    case DLL_THREAD_DETACH:
    case DLL_PROCESS_DETACH:
        break;
    }
    return TRUE;
}

DWORD WINAPI apiHook(LPVOID pData)
{
    HMODULE baseAddr = GetModuleHandle(NULL);
    DWORD dwIdataSize;
    PIMAGE_IMPORT_DESCRIPTOR pImgDesc = (PIMAGE_IMPORT_DESCRIPTOR)ImageDirectoryEntryToData(baseAddr, TRUE, IMAGE_DIRECTORY_ENTRY_IMPORT, &dwIdataSize);

    while (pImgDesc->Name) {
        char* lpModule = (char*)(baseAddr)+pImgDesc->Name;
        if (!_stricmp(lpModule, MODULE_DLL)) {
            break;
        }
        pImgDesc++;
    }

    if (!pImgDesc->Name) {
        return -1;
    }

    PIMAGE_THUNK_DATA pIAT, pINT;
    pIAT = (PIMAGE_THUNK_DATA)((char*)baseAddr + pImgDesc->FirstThunk);
    pINT = (PIMAGE_THUNK_DATA)((char*)baseAddr + pImgDesc->OriginalFirstThunk);

    while (pIAT->u1.Function)
    {
        if (IMAGE_SNAP_BY_ORDINAL(pINT->u1.Ordinal))continue;
        PIMAGE_IMPORT_BY_NAME pImportName = (PIMAGE_IMPORT_BY_NAME)((char*)baseAddr + (DWORD)pINT->u1.AddressOfData);

        DWORD dwOldProtect;
        if (!_stricmp((const char*)pImportName->Name, "MessageBoxA"))
        {
            VirtualProtect(&pIAT->u1.Function, sizeof(DWORD), PAGE_READWRITE, &dwOldProtect);
            pIAT->u1.Function = (ULONGLONG)Hook_MessageBoxA;
            VirtualProtect(&pIAT->u1.Function, sizeof(DWORD), dwOldProtect, &dwOldProtect);
        }
        else if (!_stricmp((const char*)pImportName->Name, "MessageBoxW"))
        {
            VirtualProtect(&pIAT->u1.Function, sizeof(DWORD), PAGE_READWRITE, &dwOldProtect);
            pIAT->u1.Function = (ULONGLONG)Hook_MessageBoxW;
            VirtualProtect(&pIAT->u1.Function, sizeof(DWORD), dwOldProtect, &dwOldProtect);
        }

        pIAT++;
        pINT++;
    }
    return 0;
}

int WINAPI Hook_MessageBoxA( HWND hWnd, LPCSTR lpText, LPCSTR lpCaption, UINT uType)
{
    MESSAGEBOXA_FUNC MsgBoxAProc;
    if ((MsgBoxAProc = (MESSAGEBOXA_FUNC)GetProcAddress(GetModuleHandle(_T("user32")), "MessageBoxA")) == NULL) {
        //GetProcAddress Error
    }
    return MsgBoxAProc(hWnd, "hook", "hook", uType);
}
int WINAPI Hook_MessageBoxW( HWND hWnd, LPCWSTR lpText, LPCWSTR lpCaption, UINT uType)
{
    MESSAGEBOXW_FUNC MsgBoxWProc;
    if ((MsgBoxWProc = (MESSAGEBOXW_FUNC)GetProcAddress(GetModuleHandle(_T("user32")), "MessageBoxW")) == NULL) {
        //GetProcAddress Error
    }
    return MsgBoxWProc(hWnd, L"hook", L"hook", uType);
}

これのヘッダファイルは以下のようになります。

dllmain.h

#pragma once

#include <windows.h>
#include <tchar.h>
#include <dbghelp.h>

#pragma comment(lib, "dbghelp.lib")

#define MODULE_DLL "user32.dll"

typedef int (WINAPI *MESSAGEBOXA_FUNC)(HWND hWnd, LPCSTR lpText, LPCSTR lpCaption, UINT uType);
typedef int (WINAPI *MESSAGEBOXW_FUNC)(HWND hWnd, LPCWSTR lpText, LPCWSTR lpCaption, UINT uType);

DWORD WINAPI apiHook(LPVOID pData);
int WINAPI Hook_MessageBoxA( HWND hWnd, LPCSTR lpText, LPCSTR lpCaption, UINT uType);
int WINAPI Hook_MessageBoxW( HWND hWnd, LPCWSTR lpText, LPCWSTR lpCaption, UINT uType);

各処理についてですが、DllMain関数内でCreateThreadを実行して作成したスレッド内でapiHook関数を実行し、既存のMessageBoxA,MessageBoxWの関数のアドレスをHook_MessageBoxAとHook_MessageBoxWのものに書き換えています。
apiHook関数についてGetModuleHandle関数にNULLを渡して読み込んだプロセスのハンドルを取得し、ImageDirectoryEntryToDataにそれを渡すことでdllを読み込んだプロセスにすでに読み込まれているDLLの一覧を取得します。

  HMODULE baseAddr = GetModuleHandle(NULL);
    DWORD dwIdataSize;
    PIMAGE_IMPORT_DESCRIPTOR pImgDesc = (PIMAGE_IMPORT_DESCRIPTOR)ImageDirectoryEntryToData(baseAddr, TRUE, IMAGE_DIRECTORY_ENTRY_IMPORT, &dwIdataSize);

それからuser32.dllのベースアレスを取得しています。 続く以下の処理では、インポートネームテーブルとインポートアドレステーブルから所定の関数(今回は"MessageBoxA"と"MessageBoxW")を探し、見つかったらpIAT->u1.Functionの内容を変更しインポートアドレステーブルが指す関数のアドレスを変更しているようです。書き換えようとしているpIAT->u1.Functionは読み取り専用なのですが、VirtualProtectを実行することで一時的に書き換え可能にしているようです。

PIMAGE_THUNK_DATA pIAT, pINT;
pIAT = (PIMAGE_THUNK_DATA)((char*)baseAddr + pImgDesc->FirstThunk);
pINT = (PIMAGE_THUNK_DATA)((char*)baseAddr + pImgDesc->OriginalFirstThunk);

while (pIAT->u1.Function)
{
  if (IMAGE_SNAP_BY_ORDINAL(pINT->u1.Ordinal))continue;
  PIMAGE_IMPORT_BY_NAME pImportName = (PIMAGE_IMPORT_BY_NAME)((char*)baseAddr + (DWORD)pINT->u1.AddressOfData);

  DWORD dwOldProtect;
  if (!_stricmp((const char*)pImportName->Name, "MessageBoxA"))
  {
    VirtualProtect(&pIAT->u1.Function, sizeof(DWORD), PAGE_READWRITE, &dwOldProtect);
    pIAT->u1.Function = (ULONGLONG)Hook_MessageBoxA;
    VirtualProtect(&pIAT->u1.Function, sizeof(DWORD), dwOldProtect, &dwOldProtect);
  }
  else if (!_stricmp((const char*)pImportName->Name, "MessageBoxW"))
  {
    VirtualProtect(&pIAT->u1.Function, sizeof(DWORD), PAGE_READWRITE, &dwOldProtect);
    pIAT->u1.Function = (ULONGLONG)Hook_MessageBoxW;
    VirtualProtect(&pIAT->u1.Function, sizeof(DWORD), dwOldProtect, &dwOldProtect);
  }

  pIAT++;
  pINT++;
}

DLLのソースはこれで終了でDLLを読み込ませるプロジェクトを作成する前にソリューションのビルドでDLLを生成しておきます。

LoadLibraryでDLLインジェクションさせるプロジェクトの作成

DLLの読み込み時に実行するDllMain関数が動けばメッセージボックス実行時の関数が書き換わるので、LoadLibraryを実行できさえすれば大丈夫です。

以下のようにDLLを読み込みます。

DllInject.cpp

#include "stdafx.h"
#include "DllInject.h"

HHOOK hDllInjectHook;
HMODULE hModule;

bool DllInjectHookStart(HWND hWnd)
{
    hModule = LoadLibrary("C:\\Users\\****\\source\\repos\\dll_inject\\Release\\dll_inject.dll");
    if (!hModule) {
        MessageBox(hWnd, "dll not found", "Error", MB_OK);
        return false;
    }
    else {
        return true;
    }
}

bool DllInjectHookEnd(HWND hWnd)
{
    if (!hModule) {
        MessageBox(hWnd, "dll not loaded yet", "Error", MB_OK);
    } else {
        MessageBox(hWnd, "dll cant unload", "disable", MB_OK);
    }
    return false;
}

DllInjectHookStart関数実行時にLoadLibraryを実行するくらいです。 これのヘッダファイルは以下のようになります。

DllInject.h

#pragma once
#include<Windows.h>

bool DllInjectHookStart(HWND hWnd);
bool DllInjectHookEnd(HWND hWnd);

それからボタンクリックでDLLの読み込みを行わせるフォームですが、dll読み込みのボタンとメッセージボックスを表示するボタンがあれば良いので、以下のようなフォームで動作が確認できると思います。

#pragma once
#include "DllInject.h"
#pragma comment(lib, "User32.lib")

namespace CppCLR_WinformsProjekt {

    using namespace System;
    using namespace System::ComponentModel;
    using namespace System::Collections;
    using namespace System::Windows::Forms;
    using namespace System::Data;
    using namespace System::Drawing;

    /// <summary>
    /// Zusammenfassung f・ Form1
    /// </summary>
    public ref class Form1 : public System::Windows::Forms::Form
    {
    public:
        Form1(void)
        {
            InitializeComponent();
            //
            //TODO: Konstruktorcode hier hinzuf・en.
            //
        }

    protected:
        /// <summary>
        /// Verwendete Ressourcen bereinigen.
        /// </summary>
        ~Form1()
        {
            if (components)
            {
                delete components;
            }
        }
    private: System::Windows::Forms::Button^  button1;
    private: System::Windows::Forms::Button^  button2;
    private: System::Windows::Forms::Label^  label1;
    private: System::Windows::Forms::Button^  button3;


    protected:

    private:
        /// <summary>
        /// Erforderliche Designervariable.
        /// </summary>
        System::ComponentModel::Container ^components;

#pragma region Windows Form Designer generated code
        /// <summary>
        /// Erforderliche Methode f・ die Designerunterst・zung.
        /// Der Inhalt der Methode darf nicht mit dem Code-Editor ge舅dert werden.
        /// </summary>
        void InitializeComponent(void)
        {
            this->button1 = (gcnew System::Windows::Forms::Button());
            this->button2 = (gcnew System::Windows::Forms::Button());
            this->label1 = (gcnew System::Windows::Forms::Label());
            this->button3 = (gcnew System::Windows::Forms::Button());
            this->SuspendLayout();
            //
            // button1
            //
            this->button1->Location = System::Drawing::Point(33, 53);
            this->button1->Name = L"button1";
            this->button1->Size = System::Drawing::Size(101, 40);
            this->button1->TabIndex = 0;
            this->button1->Text = L"start";
            this->button1->UseVisualStyleBackColor = true;
            this->button1->Click += gcnew System::EventHandler(this, &Form1::button1_Click);
            //
            // button2
            //
            this->button2->Location = System::Drawing::Point(152, 53);
            this->button2->Name = L"button2";
            this->button2->Size = System::Drawing::Size(101, 40);
            this->button2->TabIndex = 1;
            this->button2->Text = L"stop";
            this->button2->UseVisualStyleBackColor = true;
            this->button2->Click += gcnew System::EventHandler(this, &Form1::button2_Click);
            //
            // label1
            //
            this->label1->AutoSize = true;
            this->label1->Font = (gcnew System::Drawing::Font(L"MS UI Gothic", 22));
            this->label1->Location = System::Drawing::Point(37, 9);
            this->label1->Name = L"label1";
            this->label1->Size = System::Drawing::Size(156, 30);
            this->label1->TabIndex = 2;
            this->label1->Text = L"HOOK OFF";
            this->label1->Click += gcnew System::EventHandler(this, &Form1::label1_Click);
            //
            // button3
            //
            this->button3->Location = System::Drawing::Point(33, 100);
            this->button3->Name = L"button3";
            this->button3->Size = System::Drawing::Size(220, 48);
            this->button3->TabIndex = 3;
            this->button3->Text = L"click";
            this->button3->UseVisualStyleBackColor = true;
            this->button3->Click += gcnew System::EventHandler(this, &Form1::button3_Click_1);
            //
            // Form1
            //
            this->AutoScaleDimensions = System::Drawing::SizeF(6, 12);
            this->AutoScaleMode = System::Windows::Forms::AutoScaleMode::Font;
            this->ClientSize = System::Drawing::Size(283, 160);
            this->Controls->Add(this->button3);
            this->Controls->Add(this->label1);
            this->Controls->Add(this->button2);
            this->Controls->Add(this->button1);
            this->Name = L"Form1";
            this->Text = L"Form1";
            this->ResumeLayout(false);
            this->PerformLayout();

        }
#pragma endregion
    private: System::Void button1_Click(System::Object^  sender, System::EventArgs^  e) {
        HWND hWnd = static_cast<HWND>(this->Handle.ToPointer());
        if( DllInjectHookStart(hWnd)) {
            this->label1->Text = L"HOOK ON";
        }
    }
    private: System::Void button2_Click(System::Object^  sender, System::EventArgs^  e) {
        HWND hWnd = static_cast<HWND>(this->Handle.ToPointer());
        if (DllInjectHookEnd(hWnd)) {
            this->label1->Text = L"HOOK OFF";
        }
    }
    private: System::Void richTextBox1_TextChanged(System::Object^  sender, System::EventArgs^  e) {
    }
private: System::Void label1_Click(System::Object^  sender, System::EventArgs^  e) {
}
private: System::Void button3_Click_1(System::Object^  sender, System::EventArgs^  e) {
    MessageBoxW(NULL, L"not hook", L"not hook", MB_OK);
}
};
}