Hacking Diablo II之完整性檢查(Integrity Scan)

d2hackmap有一個完整性檢查的功能(Integrity Scan),用來檢查遊戲進程的代碼有沒有被改過。這個功能在d2hackmap的“安全開地圖”中有所應用。所謂的“安全開地圖”,其原理大致是在遊戲進程分配一塊空間,把“開地圖”的相關代碼(不是一個完整的DLL模塊)注入這塊空間,這段代碼會在遊戲的主線程context下運行,調用遊戲的內部函數實現“開地圖”邏輯,完事兒後再釋放分配的空間。這個過程時間很短,也不需要修改遊戲進程的代碼,因此安全性比較高,不容易被warden抓到。下圖是“安全開地圖”代碼運行前的警告:


“開地圖”(Reveal Map)的代碼邏輯大致如下,紅色代碼行調用了遊戲內部函數:

void __stdcall RemoteRevealAutomapAct(RevealMapContext *pctx)
{
    AutomapLayer2 
*pLayer;
    UnitAny
* unit = *pctx->p_D2CLIENT_PlayerUnit;
    
if (!unit || !unit->pPos->pRoom1) return;
    DWORD currlvl 
= unit->pPos->pRoom1->pRoom2->pDrlgLevel->nLevelNo;
    DWORD act 
= 0;
    BYTE actlvls[] 
= {14075103109133134135136137};
    
do {} while (currlvl >= actlvls[++act]);
    DWORD lvl 
= currlvl;
    
for (lvl = actlvls[act-1]; lvl < actlvls[act]; lvl++) {
        DrlgLevel 
*pDrlgLevel = pctx->GetDrlgLevel((*pctx->p_D2CLIENT_pDrlgAct)->pDrlgMisc, lvl);
        
if (!pDrlgLevel)
            pDrlgLevel 
= pctx->D2COMMON_GetDrlgLevel((*pctx->p_D2CLIENT_pDrlgAct)->pDrlgMisc, lvl);
        
if (!pDrlgLevel->pRoom2First) {
            pctx
->D2COMMON_InitDrlgLevel(pDrlgLevel);
        }
        pLayer 
= pctx->D2COMMON_GetDrlgLayer(lvl);
        pctx->InitAutomapLayer(pLayer->nLayerNo, (DWORD)pctx->D2CLIENT_InitAutomapLayer_I);
        pctx->
RevealAutomapLevel(pctx, pDrlgLevel);
    }
    pLayer 
= pctx->D2COMMON_GetDrlgLayer(currlvl);
    pctx->InitAutomapLayer(pLayer->nLayerNo, (DWORD)pctx->
D2CLIENT_InitAutomapLayer_I);
}

由於“開地圖”需要調用到遊戲的內部函數,這給warden檢測留下了一點可乘之機:如果warden截獲了這幾個內部函數中的一個,在調用發生時檢查調用者的身份(通過分析函數返回地址得到調用模塊信息),就可抓住外掛。在d2hackmap中,爲了對付warden的這種檢測,“安全開地圖”代碼在執行前,d2hackmap會對遊戲進程做完整性檢查,也就是檢查遊戲進程的代碼有沒有被改過。這篇文章講講“完整性檢查”的實現。
首先要明白的是這裏說的“完整性檢查”主要指的是檢查代碼的完整性。一個可執行程序的構成,大約可分爲文件頭、代碼段和數據段幾部分。程序的代碼在運行時不會改變,一般裝載在只讀內存頁面,數據段又可分爲只讀數據和可讀寫數據兩部分。可讀寫數據裝載在讀寫內存頁面,從通用的角度來說,這部分數據是沒法做完整性檢查的。d2hackmap的完整性檢查功能查的是可執行模塊(exe、dll)的只讀內存頁面,包括代碼段和只讀數據段。
一個windows的進程加載幾十個DLL是很常見的,加上EXE主程序模塊,完整性檢查需要檢測的數據大小一般在幾兆到幾十兆字節之間。對於這樣的數據量,一個好的檢測算法是很必要的。d2hackmap使用的策略是,對於每一個待掃描的模塊,構建出相應的“乾淨”模塊,然後拿兩個模塊逐字節比較。在 x86下,內存比較有專用、高效的彙編指令cmpsd和cmpsb。

DWORD _declspec(naked) __fastcall mymemcmpd(DWORD nSize, void* pleft, void* pright)
{
    __asm
    {
        push esi;
        push edi;
        shr ecx, 
2;
        mov eax, edx;
        mov esi, edx; 
// pleft
        mov edi, [esp+0x0c]; // pright
        rep cmpsd;
        sub eax, esi;
        neg eax;
        pop edi;
        pop esi;
        ret 
4;
    }
}

DWORD _declspec(naked) __fastcall mymemcmpb(DWORD nSize, LPBYTE pleft, LPBYTE pright)
{
    __asm
    {
        push esi;
        push edi;
        mov eax, edx;
        mov esi, edx;
        mov edi, [esp
+0x0c];
        rep cmpsb;
        test ecx, ecx;
        jz notfound;
        sub eax, esi;
        not eax;
        pop edi;
        pop esi;
        ret 
4;
notfound:
        xor eax, eax;
        pop edi;
        pop esi;
        ret 
4;
    }
}

現在問題的關鍵是如何構建一個“乾淨”的模塊,這跟黑客的反擊中一文中提到的“模塊重建”是非常相似的,唯一的區別在於“模塊重建”的代碼運行在遊戲進程中,和目標模塊在同一個內存空間。

構建一個“乾淨”模塊的算法步驟和手工加載DLL的步驟是比較類似的,描述如下:
1,把目標模塊的數據完整複製一份到本地進程空間(ReadProcessMemory),以下稱爲“髒”模塊;
2,分配一塊空間以存放“乾淨”模塊。
3,把目標模塊的磁盤文件映射到本地進程空間(CreateFile/CreateFileMapping/MapViewOfFile),以下稱爲磁盤文件映象;
4,把“髒”模塊數據再複製到“乾淨”模塊空間(memcpy)-這樣保證了可寫數據段是相同的;
5,把磁盤文件映象的可執行文件頭(PE header)複製到“乾淨”模塊(memcpy)-pe header需要檢測;
6,分析pe header,把磁盤文件映象中的只讀section逐一複製到“乾淨”模塊-只讀section需要檢測;
7,接下來對“乾淨”模塊做進一步的修正(fix-up),包括導入表(IAT)和重定位表(relocation table);
8,IAT的修正稍微有點兒繁瑣,也和普通的加載DLL不同,主要的問題是同一個DLL,在本地加載和在遊戲進程加載的基地執有可能是不一樣的。對於IAT中鏈接到的DLL,修正時應該以該DLL在目標遊戲進程中加載的基地址爲基準;
9,重定位表的修正也類似,應該使用“髒”模塊的重定位數據-這和普通的加載DLL也不同。
經過這幾步以後,“乾淨”模塊就構建好了。接下來的完整性檢查用前面給出的mymemcmpd和mymemcmpb函數就行了。使用這種方法,完整性檢查的效率還是比較高的,一般情況下掃描一個進程的時間在幾秒鐘(<5秒)以內。下圖是d2hackmap插件(d2hackmap.dll)注入後對遊戲進程的完整性檢查的結果,可以看到d2hackmap.dll修改了很多處,視圖中的每一項列出了被修改的dll名稱(入d2win.dll),修改地址,修改長度,修改後的指令,如是跳轉指令,還給出了跳轉模塊的名稱(如圖中都是d2hackmap.dll,根據這點我們就可以判斷出該處是被 d2hackmap.dll修改的)。

完整性檢查還可以有很多其他用途,不僅僅限於遊戲外掛方面。比如說有些流氓軟件可能會在一些敏感進程中截獲某些API來監控用戶的行爲,完整性檢查可以把它檢測出來。另外,完整性檢查還可以用來分析那些依賴於代碼截獲技術的程序,比如說你想分析D2JSP.DLL的實現技術,那麼通過觀測它的截獲點,以截獲點爲起點進行逆向分析是一種很有效的方法。下圖是D2JSP加載後的完整性檢查結果:

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章