系统 : Windows xp

程序 : 某游戏客户端

程序下载地址 :不提供

要求 : 远程注入 & 获取MD5值

使用工具 : vc++6.0 & OD

 

案例说明:

该游戏客户端对自身进行散列计算,并将md5值打包加密发给服务端。由于客户端本体带有病毒和压缩壳,索性采用硬件断点,和Debug API获取该MD5的内存地址。

 

逆向该客户端:

客户端启动时,先初始化md5内存:

00419DE4    B9 08000000     mov     ecx, 8
00419DE9    33C0            xor     eax, eax
00419DEB    8DBD B8610200   lea     edi, dword ptr [ebp+261B8]
00419DF1    F3:AB           rep     stos dword ptr es:[edi]          ; (initial cpu selection)

然后开始计算散列值:

004EA3E2    BE B8610200     mov     esi, 261B8
004EA3E7    33D2            xor     edx, edx
004EA3E9    8D45 EE         lea     eax, dword ptr [ebp-12]
004EA3EC    8A543D B4       mov     dl, byte ptr [ebp+edi-4C]
004EA3F0    52              push    edx
004EA3F1    68 E06D5300     push    00536DE0                         ; %02x
004EA3F6    50              push    eax
004EA3F7    E8 ABEE0100     call    005092A7
004EA3FC    8B15 943C5400   mov     edx, dword ptr [543C94]
004EA402    66:8B4D EE      mov     cx, word ptr [ebp-12]
004EA406    83C4 0C         add     esp, 0C
004EA409    47              inc     edi
004EA40A    66:890C16       mov     word ptr [esi+edx], cx
004EA40E    83C6 02         add     esi, 2
004EA411    81FE D8610200   cmp     esi, 261D8
004EA417  ^ 7C CE           jl      short 004EA3E7

跳出循环后,md5值就计算完毕了。我们利用DebugAPI模拟以上操作就可以。

但是要注意一点,传参 DEBUG_PROCESS | DEBUG_ONLY_THIS_PROCESS调用CreateProcess函数会将exe占用。这时,程序打开自身的CreateFile函数将失败:

0012FC98   004EA381  /CALL to CreateFileA from xxx.004EA37F
0012FC9C   0012FCC4  |FileName = "xxx.exe"
0012FCA0   80000000  |Access = GENERIC_READ
0012FCA4   00000000  |ShareMode = 0
0012FCA8   00000000  |pSecurity = NULL
0012FCAC   00000003  |Mode = OPEN_EXISTING
0012FCB0   00000000  |Attributes = 0
0012FCB4   00000000  \hTemplateFile = NULL

解决办法很简单,将ShareMode改成FILE_SHARE_READ即可。

 

最后整理一下我们分析的结果:

1.对程序下硬件断点,保存md5的内存地址。

2.patch掉ShareMode,改为0x01(FILE_SHARE_READ)。

3.对计算完md5之后的指令下断,并读取md5.

 

部分实现代码:

//用于createprocess的两个参数
    STARTUPINFO si;
    PROCESS_INFORMATION pi;

    ZeroMemory(&si, sizeof(STARTUPINFO));
    ZeroMemory(&pi, sizeof(PROCESS_INFORMATION));
    si.cb = sizeof(STARTUPINFO);

    char ReadBuffer[128] = {0};
    bool WhileDoFlag = true;

    //创建进程
    if ( !CreateProcess( FileName,NULL,NULL,NULL,FALSE,
        DEBUG_ONLY_THIS_PROCESS | DEBUG_PROCESS,NULL,NULL,&si,&pi ) ){
        MessageBox( "打开程序失败!" );

        DWORD dwRet = GetLastError();
        CString strText(_T(""));
        strText.Format(_T("%d"), dwRet);
        AfxMessageBox("错误代码:"+strText);

        return ;
    }
    m_Pi = pi;
    
    DEBUG_EVENT DBEvent;
    CONTEXT Regs;
    DWORD dwSSCnt = 0;
    //存放md5地址
    DWORD READ_ADDRESS;

    Regs.ContextFlags = CONTEXT_FULL | CONTEXT_DEBUG_REGISTERS ;

    //设置程序在Single Step模式下执行
    GetThreadContext( pi.hThread,&Regs );
    Regs.EFlags |= 0x100;
    SetThreadContext( pi.hThread,&Regs );

    ResumeThread( pi.hThread );

    while (WhileDoFlag) {
        WaitForDebugEvent (&DBEvent, INFINITE);
        switch (DBEvent.dwDebugEventCode)
        {
        case    EXCEPTION_DEBUG_EVENT:

            switch (DBEvent.u.Exception.ExceptionRecord.ExceptionCode)
            {
            case    EXCEPTION_SINGLE_STEP :
                {
                    ++dwSSCnt ;
                    if (dwSSCnt == 1)
                    {   
                        //当收到第一个EXCEPTION_SINGLE_STEP异常信号,表示中断在程序的第一条指令,即入口点
                        //把Dr0设置成程序的入口地址
                        
                        GetThreadContext(pi.hThread,&Regs);
                        
                        Regs.Dr0=Regs.Eax;
                        Regs.Dr7=0x101;
                        
                        SetThreadContext(pi.hThread,&Regs);
                        
                    }
                    else if (dwSSCnt == 2)
                    {
                        //第二次中断在起先设置的入口点,在BP_MOLLC处设置硬件断点

                        GetThreadContext(pi.hThread, &Regs) ;

                        Regs.Dr0 = BP_MOLLC;
                        Regs.Dr7 = 0x101 ;

                        SetThreadContext(pi.hThread, &Regs) ;
                    }
                    else if (dwSSCnt == 3)
                    {
                        //第三次中断,己到指定的地址,读取EDI寄存器的数据
                        GetThreadContext(pi.hThread, &Regs) ;
                        
                        Regs.Dr0 = BP_INITMD5;
                        Regs.Dr7 = 0x101 ;
                    
                        READ_ADDRESS = Regs.Edi;

                        BYTE NewMode = 0x01;
                        int a = WriteProcessMemory( pi.hProcess,(LPVOID)SET_SHAREMODE,&NewMode,1,0 );
                        
                        SetThreadContext(pi.hThread, &Regs) ;
    
                        /*
                        //挂起进程
                        SuspendThread( pi.hThread );
                        //通知进程异常已处理
                        ContinueDebugEvent(DBEvent.dwProcessId, DBEvent.dwThreadId, DBG_CONTINUE) ;
                        //卸载调试器
                        int a = _DebugSetProcessKillOnExit( FALSE );
                        int b = _DebugActiveProcessStop(DBEvent.dwProcessId);
                        //最后运行线程
                        ResumeThread( pi.hThread );

                        //int b = GetLastError();
                        WhileDoFlag=FALSE;
                        */
                    }
                    else if (dwSSCnt == 4)
                    {
                        //第四次中断,MD5算出,读取数据.
                        GetThreadContext(pi.hThread, &Regs) ;

                        Regs.Dr0 = Regs.Dr7 = 0 ;
                        
                        ReadProcessMemory( pi.hProcess,(LPVOID)READ_ADDRESS,ReadBuffer,32,NULL );
                        SetDlgItemText( IDC_EDITMD5,ReadBuffer );

                        SetThreadContext(pi.hThread, &Regs) ;
                    }
                    break;
                }    
            }
            break ;

        case    EXIT_PROCESS_DEBUG_EVENT :
                WhileDoFlag=FALSE;
                break ;
        }
        //pi.dwProcessId和pi.dwThreadId 要改成如下的代码
        ContinueDebugEvent(DBEvent.dwProcessId, DBEvent.dwThreadId, DBG_CONTINUE) ;
    } //.end while