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

来源:互联网 发布:帝国cms采集正文图片 编辑:程序博客网 时间:2024/04/29 12:59

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加载后的完整性检查结果:

原创粉丝点击