程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> C語言 >> C++ >> C++入門知識 >> HOOK API(二)—— HOOK自己程序的 MessageBox,hookmessagebox

HOOK API(二)—— HOOK自己程序的 MessageBox,hookmessagebox

編輯:C++入門知識

HOOK API(二)—— HOOK自己程序的 MessageBox,hookmessagebox


HOOK API(二)

—— HOOK自己程序的 MessageBox

0x00 前言

以下將給出一個簡單的例子,作為HOOK API的入門。這裡是HOOK 自己程序的MessageBox,即將自己程序對MessageBox API的調用重定向到自己實現的API中,在自己定義的API中實現內容的替換。

需要注意的是,本例子的HOOK僅僅對自己實現的MFC窗口程序,當開始HOOK 後,自己的程序調用MessageBox將被重定向,但其他程序滴啊用MessageBox時是正常的。

在Windows中,每個進程都有自己的進程控制塊,有自己的安全運行空間,各函數在初始化時被加載到進程的地址空間中,各進程的地址空間是不相交的。本實例中,HOOK API僅僅在自己程序的地址空間中實現了地址的替換,因此不影響其他進程的工作,若想HOOK其他程序,那麼就要想辦法將自己實現的API注入到目標進程的地址空間中,並替換原API的地址,才能實現我們想要的功能,這將在後續的學習中進一步介紹。

本事例僅對自己的程序進行HOOK,實用性不是很大,但是對於入門,理解HOOK API的過程還是很有幫助的。

0x01 實現思想

在自己實現的窗體程序(Windows-A)中實現一個與MessageBox API定義一模一樣的API(MessBox-New),這個API除了完成原API(MessBox-Old)的工作之外,還將顯示內容進行修改。Windows A 加載時,對將自己所使用的API地址都加載到自己的地址空間中,這裡包括我們自己寫的MessBox-New,因此我們可以很方便的使用MessBox-New的調用地址來替換MessBox-Old的入口地址,進而實現對MessBox-Old的重定向、即替換。地址被替換之後,只要本程序調用MessageBox這個API,就會被重定向到我們實現的MessBox-New中。此過程中,若想要恢復正常,只需要將MessBox-Old的入口地址恢復即可。

0x02 HOOK API實現過程

本小節將介紹程序的實現過程。

  1.定義自己的API

定義自己的API,因為我們這裡要HOOK 自己程序的MessageBox,因此就要定義一個原型與MessageBox API一模一樣的API。查MSDN,可得MessageBox有兩種調用形式,分別是MessageBoxA和MessageBoxW,前者處理窄字符串,即每個字符占一個字節;後者處理寬字符串,即一個字符占兩個字節。我們這裡HOOK MessageBoxW,其原型為:

    int WINAPI MessageBoxW(

_In_opt_ HWND hWnd,

_In_opt_ LPCWSTR lpText,

_In_opt_ LPCWSTR lpCaption,

_In_ UINT uType

);

由此,可以定義我們自己的API如下:

這裡很容易漏掉函數前面的 WINAPI,若是少了將無法正常實現HOOK,一定要注意我們實現的函數的原型要與原API一致。

 

//

// 自己定義的,用於替換相應API的,假的API

//

int WINAPI MyMessageBoxW(HWND hwnd,LPCWSTR lpText,LPCWSTR lpCation,UINT uType)

{

    TRACE(lpText);

    

    /*

        調用原函數之前,先停止HOOK,也就是恢復原系統API函數的入口,否則無法調用到原API函數,

而是繼續調用自己的API,會造成死循環,進而造成堆棧溢出,崩潰。

    */

    HookOff();    

 

    /*

        調用原來的MessageBoxW打印我們的信息。

    */

    int ret = MessageBoxW(hwnd,_T("哈哈,被HOOK咯!!"),lpCation,uType);

 

    /*

        調用完原系統API後,記得恢復HOOK,也就是啟動HOOK,將原API函數入口換成我們自己定義的函數入口,

        否則下一次調用MessageBoxW的時候就無法轉到我們自己定義的API函數中,也就無法實現HOOK。

    */

    HookOff();

 

    return ret;

}

 

  2.  定義API類型

定義原API的類型,下面的TypeMessageBoxW其它普通的數據類型的使用方法是一樣的。定義一個TypeMessageBoxW類型的變量,用於存儲原API的指針,還定義一個遠指針類型,pfOldMsgBoxW,因為系統API是在動態鏈接庫(DLL)中實現的,因此程序實際上是通過遠地址指針來DLL中相應的API調用。而本實例中設計的MessageBox是在 User32.dll 中的。關於遠地址指針這裡不做多介紹,需要了解的可以查閱相關資料。

typedef int (WINAPI *TypeMessageBoxW)(HWND hwnd,LPCWSTR lpText,LPCWSTR lpCaption,UINT uType);

TypeMessageBoxW OdlMsgBoxW = NULL;    // 指向函數原型指針

FARPROC pfOldMsgBoxW;                // 指向函數遠指針

 

  3.獲取原API函數入口

由MSDN可知,原API在User32.dll中實現,因此在此之前要加載User32.dll,並獲取到原API的函數入口。實現代碼如下:

HMODULE hmod = LoadLibrary(_T("User32.dll"));

    if ( NULL == hmod)

    {

        AfxMessageBox(_T("加載User32.dll失敗"));

        return;

    }

    OdlMsgBoxW = (TypeMessageBoxW)::GetProcAddress(hmod,"MessageBoxW");

    pfOldMsgBoxW = (FARPROC)OdlMsgBoxW;

    if ( pfOldMsgBoxW == NULL)

    {

        AfxMessageBox(_T("獲取原API入口地址出錯"));

        return;

    }

 

  4.保存原API入口的前5個字節

為了最後恢復原API的地址,必須要在HOOK之前將原API的入口地址保存起來。而這裡為什麼是5個字節呢?因為我們使用jmp xxxx 指令實現原API的重定向,該指令的長度為5個字節,jmp占一個字節,而xxxx表示新API的入口地址,占4個字節,我們使用jmp xxxx這條指令來替換掉原API入口的5個字節,這樣一來當本程序調用MessageBoxW時,就會跳轉到我們實現的API。綜上所述,我們這裡需要保存原API的前5個字節,實現代碼如下:

//

    //    將原API的入口5個字節代碼保存到OdeCode[]中

    //

    _asm

    {

            lea edi,OldCode            // 取數組OldCode[]地址,存放到edi中

            mov esi,pfOldMsgBoxW     // 獲取原API入口地址,存入esi中

            cld                        // 設置方向

            movsd                    // 移動dword ,4 Byte

            movsb                    // 移動 1 Byte

    }

 

  5.設置新的(自己的)API入口的前5個字節

保存好原API的入口之後,我們這裡需要設置jmp xxxx指令,xxxx為新API的入口地址,以便之後實現地址的替換。

而xxxx如何計算呢,可遵循前人總結的一條計算公式:

int xxxx = MyFunAddr – SystemFunAddr - CodeLength;

jmp xxxx;

MyFunAddr                :        我們編寫的新的API的地址;

SystemFunAddr        :        原API的地址;

CodeLength               :        入口指令長度,本實例是 jmp xxxx 的長度,為5個字節。

//

    // 新的API入口保存到NewCode[]中,即jmp xxxx,xxxx為新API地址,該指令總長度為5個字節

    //

    NewCode[0] = 0xe9; //    0xe9相當於jmp指令

 

    _asm

    {

        lea eax,MyMessageBoxW

        mov ebx,pfOldMsgBoxW

        sub eax,ebx

        sub eax,CODE_LENGTH

        mov dword ptr[NewCode+1],eax

    }

 

  6.修改原(真實)API入口的前5個字節為新的(自己的)API入口地址

在保存了原API和新API的入口地址之後,接下來就是要實現地址的替換,即使用新API入口替換原API入口,從而實現HOOK MessageBoxW。這裡涉及到兩個重要的API,VirtualProtectEx 和 WriteProcessMemory,關於API的詳細說明可以查詢MSDN。

/*

    啟動HOOK,將原API的入口地址換成我們自己定義函數的入口地址

*/

VOID HookOn()

{

    //

    //    確保本程序進程句柄hProcess不為NULL

    //

    ASSERT(hProcess!=NULL);

 

    DWORD dwTemp;

    DWORD dwOldProtect;

    SIZE_T writedByte;

 

    //

    // 修改API入口的前5個字節,jmp xxxx

    //

    VirtualProtectEx(hProcess,pfOldMsgBoxW,CODE_LENGTH,PAGE_READWRITE,&dwOldProtect);

    WriteProcessMemory(hProcess,pfOldMsgBoxW,NewCode,CODE_LENGTH,&writedByte);

    if (writedByte == 0)

    {

        AfxMessageBox(_T("替換原API地址失敗"));

    }

    VirtualProtectEx(hProcess,pfOldMsgBoxW,CODE_LENGTH,dwOldProtect,&dwTemp);

}

 

  7.恢復原API入口地址

在HOOK後,對MessageBox的調用會被重定向到我們實現的API中,若需要調用原API,則必須回復原API的入口地址,否則會出現死循環。實現代碼如下:

/*

    停止HOOK,將入口換成原來的API入口地址

*/

VOID HookOff()

{

    ASSERT(hProcess != NULL);

 

    DWORD dwTemp;

    DWORD dwOldProtect;

 

    SIZE_T wirtedByte;

 

//

    //    回復原API地址

//

    VirtualProtectEx(hProcess,pfOldMsgBoxW,CODE_LENGTH,PAGE_READWRITE,&dwOldProtect);

    WriteProcessMemory(hProcess,pfOldMsgBoxW,OldCode,CODE_LENGTH,&wirtedByte);

    if (wirtedByte == 0)

    {

        AfxMessageBox(_T("回復原API地址失敗"));

    }

    VirtualProtectEx(hProcess,pfOldMsgBoxW,CODE_LENGTH,dwOldProtect,&dwTemp);

}

0x03 窗口按鈕實現

 

  1.啟動HookMessageBoxW

//

//    啟動 HookMessageBoxW

//

void CHookMessageboxWindowDlg::OnBnClickedButtonStart()

{

    // TODO: 在此添加控件通知處理程序代碼

 

    AdjustPrivileges();        //    提升權限,因為調用 OpenProcess() 需要合適的權限

    DWORD dwPid = ::GetCurrentProcessId();

    hProcess = OpenProcess(PROCESS_ALL_ACCESS,0,dwPid);

    if (hProcess == NULL)

    {

        CString logInfo;

        logInfo.Format(_T("獲取進程句柄失敗!!,進程 id = 0x%x ,錯誤代碼 = 0x%x"),dwPid,GetLastError());

        AfxMessageBox(logInfo);

        return;

    }

 

    

    GetApiEntrancy();    //    獲取新舊API入口,並開始HOOK

 

    m_status.SetWindowText(_T("Hook已啟動"));

}

 

  2.終止HookMessageBoxW

//

//    終止 HookMessageBoxW

//

void CHookMessageboxWindowDlg::OnBnClickedButtonStop()

{

    // TODO: 在此添加控件通知處理程序代碼

 

    HookOff();

    m_status.SetWindowText(_T("Hook已停止"));

}

 

  3.調用MessageBoxW

//

//    調用 HookMessageBoxW

//

void CHookMessageboxWindowDlg::OnBnClickedButtonCall()

{

    // TODO: 在此添加控件通知處理程序代碼

    ::MessageBoxW(m_hWnd,_T("這是正常的MessageBoxW"),_T("Hello"),0);

}

  4.提升權限

bool AdjustPrivileges() {

    HANDLE hToken;

    TOKEN_PRIVILEGES tp;

    TOKEN_PRIVILEGES oldtp;

    DWORD dwSize=sizeof(TOKEN_PRIVILEGES);

    LUID luid;

 

    if (!OpenProcessToken(GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY, &hToken)) {

        if (GetLastError()==ERROR_CALL_NOT_IMPLEMENTED) return true;

        else return false;

    }

    if (!LookupPrivilegeValue(NULL, SE_DEBUG_NAME, &luid)) {

        CloseHandle(hToken);

        return false;

    }

    ZeroMemory(&tp, sizeof(tp));

    tp.PrivilegeCount=1;

    tp.Privileges[0].Luid=luid;

    tp.Privileges[0].Attributes=SE_PRIVILEGE_ENABLED;

    /* Adjust Token Privileges */

    if (!AdjustTokenPrivileges(hToken, FALSE, &tp, sizeof(TOKEN_PRIVILEGES), &oldtp, &dwSize)) {

        CloseHandle(hToken);

        return false;

    }

    // close handles

    CloseHandle(hToken);

    return true;

}

 

0x04 測試

 

 

0x04 附錄——全部源碼

以下給出主要實現的所有源代碼,以便從整體上把握整個實現過程。

 

// HookMessageboxWindowDlg.cpp : 實現文件

//

 

#include "stdafx.h"

#include "HookMessageboxWindow.h"

#include "HookMessageboxWindowDlg.h"

#include "afxdialogex.h"

 

 

//    定義API類型

#define CODE_LENGTH 5                //    入口指令長度

typedef int (WINAPI *TypeMessageBoxW)(HWND hwnd,LPCWSTR lpText,LPCWSTR lpCaption,UINT uType);

TypeMessageBoxW OdlMsgBoxW = NULL;    // 指向函數原型指針

FARPROC pfOldMsgBoxW;                // 指向函數遠指針

BYTE OldCode[CODE_LENGTH];            // 原系統API入口

BYTE NewCode[CODE_LENGTH];            // 自己實現的API的入口,(jmp xxxx),xxxx為新API入口地址

 

HANDLE hProcess = NULL;                // 本程序進程句柄

HINSTANCE hInst = NULL;                // API所在的dll文件句柄

 

VOID HookOn();            //    開始HOOK

VOID HookOff();            //    停止HOOK

VOID GetApiEntrancy();    //    獲取API入口地址

bool AdjustPrivileges();//    提高權限

 

//

// 自己定義的,用於替換相應API的,假的API

//

int WINAPI MyMessageBoxW(HWND hwnd,LPCWSTR lpText,LPCWSTR lpCation,UINT uType)

{

    TRACE(lpText);

    

    /*

        調用原函數之前,先停止HOOK,也就是恢復原系統API函數的入口,

        否則無法調用到原API函數,而是繼續調用自己的API,會造成死

        循環,進而造成堆棧溢出,崩潰。

    */

    HookOff();    

 

    /*

        調用原來的MessageBoxW打印我們的信息。

    */

    int ret = MessageBoxW(hwnd,_T("哈哈,被HOOK咯!!"),lpCation,uType);

 

    /*

        調用完原系統API後,記得恢復HOOK,也就是啟動HOOK,將原API函數入口換成我們自己定義的函數入口,

        否則下一次調用MessageBoxW的時候就無法轉到我們自己定義的API函數中,也就無法實現HOOK。

    */

    HookOff();

 

    return ret;

}

 

 

/*

    啟動HOOK,將原API的入口地址換成我們自己定義函數的入口地址

*/

VOID HookOn()

{

    //

    //    確保本程序進程句柄hProcess不為NULL

    //

    ASSERT(hProcess!=NULL);

 

    DWORD dwTemp;

    DWORD dwOldProtect;

    SIZE_T writedByte;

 

    //

    // 修改API入口的前5個字節,jmp xxxx

    //

    VirtualProtectEx(hProcess,pfOldMsgBoxW,CODE_LENGTH,PAGE_READWRITE,&dwOldProtect);

    WriteProcessMemory(hProcess,pfOldMsgBoxW,NewCode,CODE_LENGTH,&writedByte);

    if (writedByte == 0)

    {

        AfxMessageBox(_T("替換原API地址失敗"));

    }

    VirtualProtectEx(hProcess,pfOldMsgBoxW,CODE_LENGTH,dwOldProtect,&dwTemp);

}

 

/*

    定制HOOK,將入口換成原來的API入口地址

*/

VOID HookOff()

{

    ASSERT(hProcess != NULL);

 

    DWORD dwTemp;

    DWORD dwOldProtect;

 

    SIZE_T wirtedByte;

 

    //    回復原API地址

    VirtualProtectEx(hProcess,pfOldMsgBoxW,CODE_LENGTH,PAGE_READWRITE,&dwOldProtect);

    WriteProcessMemory(hProcess,pfOldMsgBoxW,OldCode,CODE_LENGTH,&wirtedByte);

    if (wirtedByte == 0)

    {

        AfxMessageBox(_T("回復原API地址失敗"));

    }

    VirtualProtectEx(hProcess,pfOldMsgBoxW,CODE_LENGTH,dwOldProtect,&dwTemp);

}

 

/*

    保存原API和新API的地址

*/

VOID GetApiEntrancy()

{

    //

    // 保存原來API地址

    //

    HMODULE hmod = LoadLibrary(_T("User32.dll"));

    if ( NULL == hmod)

    {

        AfxMessageBox(_T("加載User32.dll失敗"));

        return;

    }

    OdlMsgBoxW = (TypeMessageBoxW)::GetProcAddress(hmod,"MessageBoxW");

    pfOldMsgBoxW = (FARPROC)OdlMsgBoxW;

    if ( pfOldMsgBoxW == NULL)

    {

        AfxMessageBox(_T("獲取原API入口地址出錯"));

        return;

    }

 

    //

    //    將原API的入口5個字節代碼保存到OdeCode[]中

    //

    _asm

    {

            lea edi,OldCode            // 取數組OldCode[]地址,存放到edi中

            mov esi,pfOldMsgBoxW    // 獲取原API入口地址,存入esi中

            cld                        // 設置方向

            movsd                    // 移動dword ,4 Byte

            movsb                    // 移動 1 Byte

    }

 

    //

    // 新的API入口保存到NewCode[]中,即jmp xxxx,xxxx為新API地址,該指令總長度為5個字節

    //

    NewCode[0] = 0xe9; //    0xe9相當於jmp指令

 

    _asm

    {

        lea eax,MyMessageBoxW

        mov ebx,pfOldMsgBoxW

        sub eax,ebx

        sub eax,CODE_LENGTH

        mov dword ptr[NewCode+1],eax

    }

 

    //

    // 填充完畢,開始HOOK,即使用NewCode[]替換原API入口

    //

    HookOn();

}

 

bool AdjustPrivileges() {

    HANDLE hToken;

    TOKEN_PRIVILEGES tp;

    TOKEN_PRIVILEGES oldtp;

    DWORD dwSize=sizeof(TOKEN_PRIVILEGES);

    LUID luid;

 

    if (!OpenProcessToken(GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY, &hToken)) {

        if (GetLastError()==ERROR_CALL_NOT_IMPLEMENTED) return true;

        else return false;

    }

    if (!LookupPrivilegeValue(NULL, SE_DEBUG_NAME, &luid)) {

        CloseHandle(hToken);

        return false;

    }

    ZeroMemory(&tp, sizeof(tp));

    tp.PrivilegeCount=1;

    tp.Privileges[0].Luid=luid;

    tp.Privileges[0].Attributes=SE_PRIVILEGE_ENABLED;

    /* Adjust Token Privileges */

    if (!AdjustTokenPrivileges(hToken, FALSE, &tp, sizeof(TOKEN_PRIVILEGES), &oldtp, &dwSize)) {

        CloseHandle(hToken);

        return false;

    }

    // close handles

    CloseHandle(hToken);

    return true;

}

 

//

//    啟動 HookMessageBoxW

//

void CHookMessageboxWindowDlg::OnBnClickedButtonStart()

{

    // TODO: 在此添加控件通知處理程序代碼

 

    AdjustPrivileges();        //    提升權限,因為調用 OpenProcess() 需要合適的權限

    DWORD dwPid = ::GetCurrentProcessId();

    hProcess = OpenProcess(PROCESS_ALL_ACCESS,0,dwPid);

    if (hProcess == NULL)

    {

        CString logInfo;

        logInfo.Format(_T("獲取進程句柄失敗!!,進程 id = 0x%x ,錯誤代碼 = 0x%x"),dwPid,GetLastError());

        AfxMessageBox(logInfo);

        return;

    }

 

    

    GetApiEntrancy();    //    獲取新舊API入口,並開始HOOK

 

    m_status.SetWindowText(_T("Hook已啟動"));

}

 

//

//    終止 HookMessageBoxW

//

void CHookMessageboxWindowDlg::OnBnClickedButtonStop()

{

    // TODO: 在此添加控件通知處理程序代碼

 

    HookOff();

    m_status.SetWindowText(_T("Hook已停止"));

}

 

//

//    調用 HookMessageBoxW

//

void CHookMessageboxWindowDlg::OnBnClickedButtonCall()

{

    // TODO: 在此添加控件通知處理程序代碼

    ::MessageBoxW(m_hWnd,_T("這是正常的MessageBoxW"),_T("Hello"),0);

}

 

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved