程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> C語言 >> C++ >> C++入門知識 >> 在已有軟件加殼保護 下實現 Inline hook,inlinehook

在已有軟件加殼保護 下實現 Inline hook,inlinehook

編輯:C++入門知識

在已有軟件加殼保護 下實現 Inline hook,inlinehook


如寫的不好請見諒,本人水平有限。

個人簡歷及水平:。 http://www.cnblogs.com/hackdragon/p/3662599.html

正常情況:

接到一個項目實現對屏幕輸出內容的獲取,於是OD載入,發現是XX加殼保護,正常情況寫代碼采用jmp跳轉到自己的代碼處 采用前人使用的CHookApi_Jmp類 源碼在http://www.cnblogs.com/showna/articles/850279.html。(我自己用的時候把CHookApi_Jmp修改成了CHookApi)當然我是截取目標程序的繪制函數TextOutA,所以代碼如下:

// 原函數聲明
//__gdi_entry WINGDIAPI BOOL  WINAPI TextOutA( __in HDC hdc, __in int x, __in int y, __in_ecount(c) LPCSTR lpString, __in int c);
//全局初始化類
CHookApi  m_HookTextOutA;
//自己的函數
BOOL  WINAPI FuncJMP_TextOutA(HDC hdc, int x, int y,LPCSTR lpString, int c)
{
    m_HookTextOutA.SetHookOff();//關閉HOOK  實現自己調用原來的WINAPI
    {
        //做你想做的事情
    }
    int Func_Ret = TextOutA(hdc,x,y,lpString,c);
    m_HookTextOutA.SetHookOn();//開啟HOOK   實現攔截下一次函數
    return Func_Ret;
}
//適當的位置初始化
void Func_Init()//
{
    m_HookTextOutA.Initialize(_T("Gdi32.dll"),"TextOutA",(FARPROC)Hook_FuncTextOutA);
    m_HookTextOutA.SetHookOn();
}

正常情況下已經完成了Inline hook,但是由於軟件加殼保護了TextOutA函數的前5個字節,我們的jmp轉移大法不行了,你修改完 他就給你修改回去,造成程序異常。

尋找辦法:

於是OD往下跟蹤,發現他繼續調用的其他DLL也是軟件保護下的,只有動態鏈接才不保護,本人比較懶不愛繼續跟蹤了,於是想了一下,他是保護前幾個字節,他不能全部保護,於是往下看了看匯編代碼(win7的GDI32.dll)。

看到這裡調用了下一個函數,那麼他這裡E8(CALL)後面也是4個字節,於是乎馬上我想到自己也可以篡改他的這個地址到咱自己的函數中,鬼使神差的我看了下X版本的GDI32的代碼

發現一個問題 他們在TextOutA函數頭處到E8的位置並不一樣。具體來說

WIN7的 089D-0878 = 0x25(偏移)

XP的      BA7C-BA4F = 0x2D (偏移)

2個的偏移量明顯不一樣了,這怎麼辦呢,如果在有其他版本的GDI32是不是也不一樣呢。於是想到如下解決辦法。

解決問題:

我們要實現inline hook 首先要知道我們要修改的內存地址,其次要知道原來的函數地址。下面這個函數來獲取我們的地址。

DWORD GetGDI32_TextOutAEx(DWORD &dwHook,DWORD &dwCall)
{
    //取得TextOutA函數地址 也可以直接TextOutA
    DWORD dwGDI32TextOutA = (DWORD)GetProcAddress(GetModuleHandle(_T("Gdi32.dll")),"TextOutA");
    //轉換下
    BYTE *lpBuf = (BYTE *)dwGDI32TextOutA;
    //每字節對比
    for(int i=0;i<0x30;i++)
    {
        DWORD dwAddress = (DWORD)(&lpBuf[i]);    //字節地址
        DWORD dwFind = *(DWORD*)(dwAddress);    //轉換獲取內容
        //為什麼是0xE80875FF 這個可以在OD上看下內存內容 
        //由於這個函數的前一部分都是 push dword ptr ss:[ebp+0x8]  緊跟這就是CALL 
        //那麼對應的內存二進制就是 0xE80875FF 了
        if(dwFind==0xE80875FF)
        {
            //當前地址+3那就是E8了
            dwHook = dwAddress+3;
            //由於“CALL到哪裡”的計算方式是計算2個地址的差
            //所以我們要取CALL 後面的 4個字節的內容
            //當前地址+4那就是E8後面的了
            DWORD dwNow = dwAddress+4;
            //讀出來這個值
            DWORD dwOff = *(DWORD*)(dwNow);
            //和當前的地址相加+(這個自己想一下吧為什麼是+)
            //在+4是因為本身占用4個字節
            dwCall = dwNow+dwOff+4;
            //得出這個CALL實際的內存地址了。
            return 1;
        }
    }
    return 0;
}

給我們的CHookApi增加一個函數。

BOOL CHookApi::InitializeE8(FARPROC lpOldFunc, FARPROC lpNewFunc)
{
    m_lpHookFunc = lpOldFunc;
    hProc = GetCurrentProcess();
    DWORD dwOldFlag;
    if(VirtualProtectEx(hProc,m_lpHookFunc,5,PAGE_READWRITE,&dwOldFlag)) 
    {  
        memcpy_s(m_OldFunc,5,m_lpHookFunc,5);
        //if(ReadProcessMemory(hProc,m_lpHookFunc,m_OldFunc,5,0))  
        {   
            if(VirtualProtectEx(hProc,m_lpHookFunc,5,dwOldFlag,&dwOldFlag))   
            {    
                m_NewFunc[0]=0xE8;    
                DWORD *pNewFuncAddress;    
                pNewFuncAddress=(DWORD*)&m_NewFunc[1];    
                *pNewFuncAddress=(DWORD)lpNewFunc-(DWORD)m_lpHookFunc-5;    
                return TRUE;   
            }  
        }
    }
    return FALSE;
}

他原來是E9(JMP)那麼我們要實現自己的CALL 那麼我們用E8 其實我只是偷懶而已。因為原來就是E8 我們只要寫入自己的函數的偏移就行了。

實現過程:

//全局初始化類
CHookApi  m_HookTextOutAEx;
//要掛鉤的地址
DWORD dwHook;
//原來的CALL的地址
DWORD dwCall;
//自己的函數
int WINAPI FuncJMP_TextOutEx(HDC hdc, int x, int y,int A,int B,LPCSTR lpString, int c,int C,int D)
{
    //處理函數形式
    typedef int (__stdcall *MyTextOut )(HDC hdc, int x, int y,int A,int B,LPCSTR lpString, int c,int C,int D);
    //函數指針
    MyTextOut TextOutStr = (MyTextOut)dwCall;
    //調用
    int Ret = TextOutStr(hdc,x,y,A,B,lpString,c,C,D);
    //取得返回的地址
    DWORD *Ptr = (DWORD *)&D;
    if(Ptr[2]==0x12345678)//這裡你可以加入以提升性能,根據返回的地址來判斷是不是你要攔截的輸出內容
    {
        //做你想做的事情
    }
    return Ret;
}
//適當的位置初始化
void Func_Init2()//
{
    GetGDI32_TextOutAEx(dwHook,dwCall);
    m_HookTextOutAEx.InitializeE8((FARPROC)dwHook,(FARPROC)FuncJMP_TextOutEx);
    m_HookTextOutAEx.SetHookOn();
}

函數參數的判斷可以根據push情況自己來處理下,我隨便處理下了。不知道多的幾個參數干什麼用的。

最後總結:

對於有保護的軟件,如果不脫殼處理,那麼可以曲線實現自己的目的。對於其他有內存校驗的軟件,則可根據它對應的調用其他未保護的API進行代碼插入。算是寫完了,也算是我cnblog的第二篇技術類文章吧。寫的不對的地方歡迎指正啊。本人QQ:78486367


對於inline hook、hook意思的疑問

hook 計算機裡面一般是指 掛鉤某函數, 就是替換掉原來的函數。

inline hook , 是直接在以前的函數替裡面修改指令,用一個跳轉或者其他指令來達到掛鉤的目的。
這是相對普通的hook來說,因為普通的hook只是修改函數的調用地址,而不是在原來的函數體裡面做修改。

一般來說 普通的hook比較穩定使用。 inline hook 更加高級一點,一般也跟難以被發現。所以很多人比如病毒制作者都比較推崇inline hook。
 

VC注入dll後,怎用代碼實現inline hook?

關鍵點嗎……
1、恢復現場,要存多少字節根據你填入的jmp占用多少字節而定,jmp rel32是5個字節。
2、od找0那一段,程序操作的話你可以用malloc申請的內存來,而不一定是exe裡的0
3、VirtualProtect調用可以使代碼段可寫(否則修改的時候會出現寫入異常,程序崩潰)
4、jmp rel32指令的機器代碼是 E9 rel32 一共5個字節。如果嫌計算相對地址麻煩你也可以用 jmp mem32,機器指令是FF 25 mem32(沒記錯的話……也有可能是FF 15)。但是我覺得這樣更麻煩
5、jmp rel32指令中,rel32相對的是執行jmp之後的eip。例如
40000: E9 11 22 33 44
40005: ......
那麼翻譯過來就是
40000: jmp 44372216
6、不確定要hook的代碼位置的指令多長的話,你可能需要一個反匯編庫來確定,當然更容易(但是不通用)的方式是人工查看……跳轉來跳轉去記得一定要跳轉到機器代碼邊界上,比如剛才那個
40000: E9 11 22 33 44
你不可以跳轉到(40000, 40005)區間的任何一個地址上,但是可以跳轉到40000或40005地址上。
 

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