WINDOWS上的 API HOOK 技術

預備知識

  1. 每一個進程都擁有獨立的地址空間
  2. dll動態連接庫是全部進程共享的,可是須要注意,這裏有個前提。前提是,全部的進程都不會去修改dll的內容,若是有進程A修改了dll的內容,則該dll就變成了進程A私有的了(系統使用copy-on-write方法,複製一份dll到進程A的私有地址空間,而後修改dll內容)。試想,若是任何狀況都是共享的,一個進程只須要本身加載的dll中的內容,那麼其他的全部使用了該dll的進程都會受到影響。
  3. 若是進程A加載的dll和進程B加載的dll是同一個dll(不是同一個的拷貝,必須是同一個路徑下的同一個dll),那麼dll加載後映射到進程A地址空間和進程B地址空間的虛擬地址是相同的

API HOOK

API HOOK 有不少方式,本文只介紹最經常使用的dll注入方式進程API HOOK。
dll注入方式大致分爲兩步:一、dll注入,二、修改API入口git

dll注入

假設注入進程爲A,被注入的進程爲B,注入的dll名爲virus.dll。則進程A注入dll到進程B的大致步驟以下github

  • 調整當前進程(A進程)權限
if ( OpenProcessToken(GetCurrentProcess(),TOKEN_ADJUST_PRIVILEGES,&hToken) )
    {
        TOKEN_PRIVILEGES tkp;

        LookupPrivilegeValue( NULL,SE_DEBUG_NAME,&tkp.Privileges[0].Luid );//修改進程權限
        tkp.PrivilegeCount=1;
        tkp.Privileges[0].Attributes=SE_PRIVILEGE_ENABLED;
        AdjustTokenPrivileges( hToken,FALSE,&tkp,sizeof tkp,NULL,NULL );//通知系統修改進程權限
        CloseHandle(hToken);
    }
  • 打開B進程
OpenProcess( PROCESS_CREATE_THREAD |    //容許遠程建立線程
            PROCESS_VM_OPERATION |                //容許遠程VM操做
            PROCESS_VM_WRITE|                    //容許遠程VM寫
            PROCESS_ALL_ACCESS,
            FALSE, dwRemoteProcessId ) )
  • 在B進程中申請一塊內存,申請的區域假設爲REDATA
pszLibFileRemote = (char *) VirtualAllocEx( hRemoteProcess, NULL,         
            lstrlen(DllFullPath)+1,MEM_COMMIT, PAGE_READWRITE);
  • 將virus.dll的路徑寫入到REDATA區域
WriteProcessMemory(hRemoteProcess,pszLibFileRemote,
             (void *) DllFullPath, lstrlen(DllFullPath)+1, NULL)
  • 計算Kernel32.dll中的LoadLibraryA API的地址,記爲pfnStartAddr
PTHREAD_START_ROUTINE pfnStartAddr = (PTHREAD_START_ROUTINE)
            GetProcAddress(GetModuleHandle(TEXT("Kernel32")), "LoadLibraryA");
  • 建立遠程線程,傳入pfnStartAddr的地址做爲線程執行的函數,REDATA的地址做爲參數
hRemoteThread = CreateRemoteThread( hRemoteProcess, NULL, 0, 
            pfnStartAddr, pszLibFileRemote, 0, NULL)

通過了上述步驟,virus.dll就能被成功注入到進程B的地址空間中,上面的步驟就是爲了幹一件事情,讓B進程調用LoadLibraryA("virus.dll"),爲了作這件事情須要上述6個步驟。,此處須要有幾點說明:編程

  1. 提權是爲了有權限訪問遠程線程,讀寫遠程線程
  2. 爲何要申請空間,而後將virus.dll路徑寫入到B進程空間中,不能直接傳一個dll路徑的字符串呢?由於遠程空間調用LoadLibraryA時,須要讀取參數,A進程中寫的dll路徑的字符串存儲在A進程的地址空間中,A把這個地址告訴B,B在本身的進程空間中找這個地址是找不到內容的,因此,須要將申請的空間地址告訴B。
  3. 你要問了,既然路徑的地址須要B進程內存空間的地址,pfnStartAddr的地址是A進程空間相對位置,這個地址直接給B有問題嗎?問得好,這個是沒有問題的,這裏有一個編程經驗:
a、任何應用進程將Kerner32.dll加載到內存的虛擬地址位置都是同樣
   b、dll中的方法在dll中的相對位置是固定的

因此,A進程找到的函數地址位置和在B進程中的位置是同樣的。
至此,咱們將virus.dll加載到了B進程中,接下來就是B進程中的virus.dll怎麼對本身進程空間中的API函數作HOOK的問題了。這裏涉及到兩個問題:api

  • 何時執行API HOOK這件事情
  • API HOOK以後,何時能夠執行HOOK以後的內容(不知道大家聽懂我在說什麼沒有。。)

修改API入口代碼

咱們先看怎麼修改API函數的入口代碼,再討論執行時機的問題。這件事情是在virus.dll中作的,執行的步驟以下函數

  • 獲取API函數所在的模塊句柄
HMODULE hmod=::LoadLibrary(_TEXT("add.dll"));
  • 獲取該模塊中API函數的地址
add=(AddProc)::GetProcAddress(hmod,"add");
  • 修改API函數的彙編代碼,讓他跳轉到咱們本身寫的函數(這個函數纔是真正的幹壞事的地方)
// 將add()的入口代碼保存到OldCode裏
        _asm 
        { 
            lea edi,OldCode 
                mov esi,pfadd 
                cld 
                movsd 
                movsb 
        }

        NewCode[0]=0xe9;//第一個字節0xe9至關於jmp指令
        //獲取Myadd()的相對地址
        _asm 
        { 
            lea eax,Myadd
                mov ebx,pfadd 
                sub eax,ebx 
                sub eax,5 
                mov dword ptr [NewCode+1],eax 
        } 
        //填充完畢,如今NewCode[]裏面就至關於指令 jmp Myadd
        HookOn();
void HookOn() 
    { 
        ASSERT(hProcess!=NULL);
    
        DWORD dwTemp=0;
        DWORD dwOldProtect;
    
        //將內存保護模式改成可寫,老模式保存入dwOldProtect
        VirtualProtectEx(hProcess,pfadd,5,PAGE_READWRITE,&dwOldProtect); 
        //將所屬進程中add的前5個字節改成Jmp Myadd 
        WriteProcessMemory(hProcess,pfadd,NewCode,5,0);
        //將內存保護模式改回爲dwOldProtect
        VirtualProtectEx(hProcess,pfadd,5,dwOldProtect,&dwTemp);
    
        bHook=true; 
    }

這段代碼將add函數(API函數)的前5個字節存儲到OldCode裏面(爲了之後恢復add函數),而後生成一個jmp指令到NewCode中,都存儲好以後,NewCode的5個字節的內容,替換到原來add函數入口處的5個字節內容。
注意幾個問題:ui

  1. 爲何是5個字節?

由於jmp XXX 指令佔用5個字節(32位體系結構中).net

  1. jmp XXX 中的跳轉目標是計算的

xxx = Myadd - pfadd - 5線程

爲何是這個公式呢,先引用《深刻理解計算機系統-第三版》中一段話:code

當CPU計算跳轉指令的目標地址時,程序計數器的值是當前指令的後一條指令的地址,而不是跳轉指令的地址

舉個例子blog

pfadd:
01FF0000 : ?? ?? ?? ??
01FF0004 : ?? ?? ?? ??
01FF0008 : ?? ?? ?? ??
...
Myadd:
01FF00A0 : ?? ?? ?? ??
01FF00A4 : ?? ?? ?? ??
01FF00A8 : ?? ?? ?? ??
...
relpalce the first 5 bytes pfadd as jmp(E9 xx xx xx xx),the content of pfadd:
pfadd:
01FF0000 : E9 xx xx xx
01FF0004 : xx ?? ?? ??
01FF0008 : ?? ?? ?? ??
...
satisfy :  01FF00A0 = PC + XXX
PC = 01FF0000 + 5
then the addr : XXX = 01FF00A0 - 01FF0000 - 5

以上就是修改api入口代碼的方法
執行時機問題就比較簡單了,咱們還記得進程A(起始就是注入器)建立了一個遠程線程,讓進程B執行了LoadLibraryA("virus.dll")函數。dll中有個入口函數

BOOL APIENTRY DllMain(HANDLE hModule, DWORD dwReason, void* lpReserved)

{

    HANDLE g_hModule;

    switch(dwReason)

    {

    case DLL_PROCESS_ATTACH:

       cout<<"Dll is attached!"<<endl;

       g_hModule = (HINSTANCE)hModule;

       break;

    case DLL_PROCESS_DETACH:

       cout<<"Dll is detached!"<<endl;

       g_hModule=NULL;

       break;

    }

    return true;

}

當dll被加載以後,就會執行DLL_PROCESS_ATTACH後面的代碼,若是咱們將修改API入口代碼的動做放到這裏面作,則建立遠程線程加載dll後,HooK的動做就會被執行,API函數的入口代碼就會被修改。而當進程B再次調用該API時,該API就會跳轉到咱們本身寫的函數了。

總結

以上就是API HOOK 的dll遠程注入技術的詳細步驟,若是有不明白的地方,能夠參考:
遠程注入 : http://blog.csdn.net/ithzhang...
api修改 : http://blog.csdn.net/friendan...
還能夠參考個人代碼 : https://github.com/Jasey/hook...裏面有大量的例子和解釋。

相關文章
相關標籤/搜索