首页
社区
课程
招聘
[旧帖] [原创]《地下城》双开辅助逆向分析 0.00雪花
发表于: 2011-3-2 21:42 5906

[旧帖] [原创]《地下城》双开辅助逆向分析 0.00雪花

2011-3-2 21:42
5906

《地下城与勇士》(简称DNF)我想就不用多介绍了,我也是这个游戏的一位爱好者。
    熟悉DNF的人都知道,这个游戏主要就是分为刷图和PK两个部分,但是无论是哪个部分都可以利用双开工具收益,例如刷图双开还可以获得几次免费的深渊派队的机会,PK也可以用双开来刷决斗经验。
不过按理说DNF客户端并不允许大家双开,但是曾经见过网上流行一套驱动写的双开软件,是利用SSDT HOOK NtCreateMutant来实现的。最暴力但是有效的办法如下,其实就是拒绝掉所有创建的Mutant句柄:

int __stdcall HookSSDT_NtCreateMutant(int a1, int a2, int a3, int a4)
{
  Old_NtCreteMutant(a1, a2, a3, a4);
  return 0;
}
     
    但问题是这个方法很暴力有危险不说,更重要的是现在已经不能这么办了。先看看DNF游戏在运行的过程中情况吧:运行游戏先是DNFChina.exe、QQLogin.exe以及TenSafe.exe,它们负责登陆以及环境初始化,还有就是所谓的可疑模块检查和完整性检测。然后登陆之后,即DNF.exe开始运行,这个时候就会加载驱动TenSafe.sys,我们的第一个难点就在这里,即DNF反外挂中隐藏的白名单。
    还是老样子,TenSafe把NtOpenProcess中的ObOpenObjectByPointer的调用改了:
nt!NtOpenProcess+0x224:
805cc625 8d45dc          lea     eax,[ebp-24h]
805cc628 50              push    eax
805cc629 ff75d4          push    dword ptr [ebp-2Ch]
805cc62c e8c17a0000      call    nt!PsLookupProcessByProcessId (805d40f2)
805cc631 ebde            jmp     nt!NtOpenProcess+0x1e7 (805cc611)
805cc633 8d45e0          lea     eax,[ebp-20h]
805cc636 50              push    eax
805cc637 ff75cc          push    dword ptr [ebp-34h]
805cc63a ff35b8495680    push    dword ptr [nt!PsProcessType (805649b8)]
805cc640 56              push    esi
805cc641 8d8548ffffff    lea     eax,[ebp-0B8h]
805cc647 50              push    eax
805cc648 ff75c8          push    dword ptr [ebp-38h]
805cc64b ff75dc          push    dword ptr [ebp-24h]
805cc64e e88d12bb28      call    a917d8e0(还是老样子,把call nt!ObOpenObjectByPointer改了,不过里面的内容变化巨大。。。)
805cc653 8bf8            mov     edi,eax

    进入ObOpenObjectByPointer的函数之后,分为了3个板块:1、检查调用者是不是属于系统进程(即System、Csrss、smss一类的);2、检查是不是特殊的白名单进程(不过我的机器上好像没有这种情况);3、检查是不是腾讯相关软件的进程(例如QQ.exe、TXPlatform.exe)。如果说是白名单进程,TenSafe会直接放行,这样一来就给了我们一个可乘之机,找一个已知的白名单进程,并且可以注入的,我在那个表中果然找到了svchost.exe,因此这个进程就成了我们的载体(当然事实上后来才发现,对于用户名为SYSTEM的svchost进程,注入且运作才是有效的):

lkd> dd 86c6a9ac
86c6a9ac  86d94504 86dd75ac 00000100 0a0a0007
lkd> dd 86d94504-24
86d944e0  86ccf888 00000500 000fffff b494762b

PROCESS 86db0a88  SessionId: 0  Cid: 0354    Peb: 7ffdf000  ParentCid: 047c
    DirBase: 07700160  ObjectTable: e2f60b00  HandleCount: 174.
    Image: svchost.exe

    因此,找到了载体,我们接下来就需要找对互斥句柄了,不过一些辅助工具给了我们明确的信息,其实甚至在网上都能查到,只不过别人的很多是加载驱动的版本,当然我们需要不利用加载驱动的方式而是通过用户模式来解决。要远程关掉DNF.exe进程中的互斥句柄,仅需要找对对象之后,利用Native函数ZwDuplicateObject来实现。具体的代码如下:

void    RunDNFSK()
{
NTSTATUS            status = 0;
DWORD                buflen = 256, needlen = 0;
DWORD                HandleCnt = 0;
OBJECT_ATTRIBUTES    objatr;
UINT                i;

PSYSTEM_HANDLE_INFORMATION    pBuf = NULL;
PSYSTEM_HANDLE_TABLE_ENTRY_INFO    pSysHandleInfo = NULL;

//初始化对对象属性
InitializeObjectAttributes(&objatr,0,0,0,0);

//获得句柄表
do  
{
//申请查询句柄信息所需的内存
ZwAllocateVirtualMemory(NtCurrentProcess(),(PVOID*)&pBuf,0,&buflen,MEM_COMMIT,PAGE_READWRITE);
     
//查询系统句柄信息
status=ZwQuerySystemInformation(SystemHandleInformation,(PVOID)pBuf,buflen,&needlen);
if( status==STATUS_SUCCESS )
    break;    //申请成功,退出执行下步
//不成功:则释放内存,以返回的needlen可以作参考,需要申请一块足够大的内存
ZwFreeVirtualMemory(NtCurrentProcess(),(PVOID*)&pBuf,&buflen,MEM_RELEASE);
    //然后把要申请的内存大小乘2,直至成功为止
    buflen*=2;
    pBuf=NULL;
} while(1);

//返回的缓冲区内容的第一个DWORD是总的句柄的个数
HandleCnt = pBuf->NumberOfHandles;

//跳过句柄计数,句柄信息真正的开始
pSysHandleInfo = (PSYSTEM_HANDLE_TABLE_ENTRY_INFO)( (char*)pBuf + sizeof(DWORD) );

for( i = 0; i < HandleCnt; i++,pSysHandleInfo++/*指向下一个结构*/ )
{
//    初始化句柄信息
    HANDLE    hOwner = NULL;
    HANDLE    hDupHandle = NULL;
//    以复制句柄方式打开进程:hOwner
hOwner=OpenProcess(PROCESS_DUP_HANDLE,FALSE,pSysHandleInfo->UniqueProcessId);
     
//    复制进程到可操作的范围:hDupHandle
    ZwDuplicateObject(hOwner,(HANDLE)pSysHandleInfo->HandleValue,GetCurrentProcess(), &hDupHandle,PROCESS_ALL_ACCESS, FALSE, DUPLICATE_SAME_ACCESS);
     
    if( hDupHandle == NULL )
    {
    //    PRINT("PID:%d DuplicateHandleFailed\n\n",pSysHandleInfo->UniqueProcessId);
        goto DuplicateHandleFailed;         
    }

//    句柄复制成功     
    if( hDupHandle )
    {
    //    获取句柄的基本信息:
        OBJECT_BASIC_INFORMATION    ObjBasicInfo = {0};
        status=ZwQueryObject(hDupHandle,ObjectBasicInformation,&ObjBasicInfo, sizeof(ObjBasicInfo), NULL);
            
    //    获取句柄的类型信息
        POBJECT_TYPE_INFORMATION    pTypeInfo = NULL;
            pTypeInfo=(POBJECT_TYPE_INFORMATION)malloc( 2*ObjBasicInfo.TypeInfoSize );
        memset( pTypeInfo, 0, 2*ObjBasicInfo.TypeInfoSize);
        status=ZwQueryObject(hDupHandle,ObjectTypeInformation,pTypeInfo,2*ObjBasicInfo.TypeInfoSize, NULL);

//    类型名称判断
        if( status == STATUS_SUCCESS &&
            wcsncmp(pTypeInfo->TypeName.Buffer, L"Mutant", 6) == 0 ||
            wcsncmp(pTypeInfo->TypeName.Buffer, L"Section", 7) == 0 )
        {
        //    获取句柄的名称信息
            if( ObjBasicInfo.NameInfoSize )
            {
            POBJECT_NAME_INFORMATION    pNameInfo = NULL;
            pNameInfo=(POBJECT_NAME_INFORMATION)malloc( 2*ObjBasicInfo.NameInfoSize );
            memset( pNameInfo, 0, 2*ObjBasicInfo.NameInfoSize);            
            status = ZwQueryObject(hDupHandle, ObjectNameInformation, pNameInfo, 2*ObjBasicInfo.NameInfoSize, NULL);
                 
            //    对获取的句柄值进行筛选(Start);
            if(status == STATUS_SUCCESS && pNameInfo->Name.Buffer != NULL)
            {
            //        PRINT("ObjName = %ws\n\n", pNameInfo->Name.Buffer);
            }
            else
            {
            //        PRINT("\n\n");
            }
                 
            //    操作数进行操作
            int    OperateValue = 0;    //    操作数
            OperateValue = FindSpecialHandle( (PUNICODE_STRING)pNameInfo );
            if( OperateValue == 1 )
            {
                PRINT("%ws\n", pTypeInfo->TypeName.Buffer);
                PRINT("PID = %d\n", pSysHandleInfo->UniqueProcessId);
                PRINT("HandleValue = 0x%0.8X\n", pSysHandleInfo->HandleValue);
                PRINT("ObjName = %ws\n\n", pNameInfo->Name.Buffer);
                PRINT("Find The Handle Need to Close\n\n");
                ZwDuplicateObject(hOwner, (HANDLE)pSysHandleInfo->HandleValue, 0, 0, 0, 0, DUPLICATE_CLOSE_SOURCE);//(关闭远程句柄)
            }
            //    对获取的句柄值进行筛选(End);
            }
            }

    //    关闭操作的相关句柄
    if(hDupHandle != NULL)
        CloseHandle(hDupHandle);
    }

    //    关闭打开进程的句柄
DuplicateHandleFailed:
        if(hOwner != NULL)
            CloseHandle(hOwner);

    }
}

    我们只需要把这段代码注入到一个用户名为SYSTEM的svchost进程,然后就能利用TenSafe中自带的白名单机制,关闭远程进程(DNF.exe)的互斥句柄。但是,光是这样还不够,因为后来腾讯又增加了一种新的检测方案,既是窗口枚举,用Spy++可以看到DNF游戏的窗口句柄。
    当我再开一个客户端的时候,扫描就会检测窗口句柄,一旦有“地下城与勇士”的窗口,新客户端游戏的窗口仍然不会显示出来,所以我们还需要加入其它的处理方式。
    思路就是,把DNF.exe的“地下城与勇士”窗口,设置为一个已知窗口的子窗口进程(利用函数SetParent),然后再等到新游戏窗口出现后,重新将其恢复。
    这样,就能再新开客户端的时候,躲避检测。因此执行流程就是:打开第一个DNF之后,远程关闭句柄,隐藏窗口,然后待打开第二个DNF之后,恢复第一个DNF的窗口即实现双开。利用SPY++显示打开了两个客户端之后的窗口情况(有两个“地下城与勇士”的窗口同时出现):
    最后,我建了一个小号,通过下图你就可以看出双开的情况(注意喇叭黄字):
加不了图。。。。


[注意]传递专业知识、拓宽行业人脉——看雪讲师团队等你加入!

收藏
免费 7
支持
分享
最新回复 (15)
雪    币: 100
活跃值: (15)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
2
呵呵,我试过HOOK CreatMutant 不过在函数内部做了判断,只是DNF调用此函数时才返回失败!
结果在第二个实例(DNF)登陆后加载到黑屏后(窗口显示前)就消失了!

如此看来,估计应该是用的查找窗口,如此我就可以再HOOK。。。
2011-3-2 22:02
0
雪    币: 220
活跃值: (721)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
3
LS的都是牛人呀
2011-3-2 22:11
0
雪    币: 3800
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
4
虽然不玩这游戏,但看见如此详细的分析,值得支持一下
2011-3-3 03:16
0
雪    币: 26
活跃值: (25)
能力值: ( LV3,RANK:20 )
在线值:
发帖
回帖
粉丝
5
话说以前用的双开工具经常有两边不同步的情况(非激活DNF窗口处于半掉线假死状态)
2011-3-3 21:17
0
雪    币: 6
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
6
楼主能发个给我玩吗?省的我整天疲劳疲劳值的不知道咋赚,我只想双开带个自己的Q做徒弟,赚徒弟的那点疲劳值。。。非常感谢,非常感谢。。。
2013-8-17 17:56
0
雪    币: 145
活跃值: (85)
能力值: ( LV5,RANK:60 )
在线值:
发帖
回帖
粉丝
7
楼主写的很详细,有值得学习的地方
2013-8-17 22:43
0
雪    币: 882
活跃值: (350)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
8
值得学习~~~
2013-8-17 23:05
0
雪    币: 4
活跃值: (15)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
9
学习学习。。。
2013-8-17 23:34
0
雪    币: 6
活跃值: (1282)
能力值: ( LV3,RANK:20 )
在线值:
发帖
回帖
粉丝
10
以前用dnf双开 双开是双开了可就是两个组不成队友
2013-8-17 23:38
0
雪    币: 278
活跃值: (709)
能力值: ( LV15,RANK:520 )
在线值:
发帖
回帖
粉丝
11
咱一个,好久没看到这样的文章了。
2013-8-17 23:41
0
雪    币: 261
活跃值: (547)
能力值: ( LV4,RANK:50 )
在线值:
发帖
回帖
粉丝
12
目测内存泄露
2013-8-27 19:53
0
雪    币: 77
活跃值: (48)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
13
mark
2014-3-6 18:54
0
雪    币: 202
活跃值: (114)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
14
不是有多玩盒子吗
2014-3-6 20:28
0
雪    币: 199
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
15
支持一下
2014-3-14 07:05
0
雪    币: 1602
活跃值: (14)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
16
如此文章,深深赏析
2014-3-14 07:39
0
游客
登录 | 注册 方可回帖
返回
//