双进程保护及实现

考前2个星期都一直在搞这个,然后考试周考的死去活来,全部忘光了。最近瞅了一下,发现远远没我想象的那么简单,双进程保护如果用的好的话,SMC+调试进程与被调试进程处理不同异常,的确能在很大程度上限制动态调试。 《加密与解密》上写的很简单,只是大体讲了一下思路,差不多步骤如下
1.加载或者附加一个正在运行的进程(可以用createprocess创建或者用debugactiveprocess附加)
2.获取被调试程序的信息(waitfordebugevent等待调试事件发生)
3.接受被调试进程发来的调试事件并处理然后我们在程序的开始,就可以做一些手脚,然后让调试进程,和被调试进程处理不同的事件,然后程序会沿着不同的逻辑进行,逆向起来就很头疼了。
下面我贴上以为看雪大牛之前发过的源码,然后自行填上了注释。

#define _WIN32_WINNT 0x0500
#include "windows.h"
int DebugMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,LPSTR lpCmdLine,int nCmdShow);
void DecryptCode(HANDLE hProcess,DWORD begin,DWORD end);
int APIENTRY WinMain(HINSTANCE hInstance,HINSTANCE hPrevInstance,LPSTR lpCmdLine,int nCmdShow)
{
        if(IsDebuggerPresent())   //区分调试进程与被调试进程,以执行不同代码,被调试的进程则调用DeBugMain
        {
                return DebugMain(hInstance,hPrevInstance,lpCmdLine,nCmdShow);
        }

        __try
        {
                __asm int 3        //断点异常想让调试进程处理
        }
        __except(1)
        {
                __asm pop eax; //如果调试器不处理断点异常,这里会被执行
                __asm pop esp;//这两句汇编是破坏了堆栈
        }


        int div=0;
        __try
        {
                __asm int 3        //断点异常交给被调试进程处理
        }
        __except(1)
        {
                div++;
        }
        div=1/div;                //如果调试进程的异常处理模块未被执行,那么这里产生除0异常而使程序退出


        __asm int 3;
        MessageBox(0,"这是一个简单的例子","TraceMe",0);


        return 0;
}


//加密messagebox函数,采用的是异或函数
void DecryptCode(HANDLE hProcess,DWORD begin,DWORD end)
{
        DWORD flOldProtect;
        BYTE ch[1]={0};
        DWORD num=end-begin;
        VirtualProtectEx(hProcess, (LPVOID)begin,num,PAGE_EXECUTE_READWRITE,&flOldProtect);
        for(DWORD i=begin;isizeof(ch),NULL) ;
                ch[0]^=0xDE;
                WriteProcessMemory(hProcess,(LPVOID)i,&ch,sizeof(ch),NULL);
        }
        VirtualProtectEx(hProcess,(LPVOID)begin,num,flOldProtect,NULL);
}


//调试进程主函数
int DebugMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,LPSTR lpCmdLine,int nCmdShow)
{        
        char filename[260];
        GetModuleFileName(0,filename,260); //获取自身文件名
        STARTUPINFO        si={0};
        GetStartupInfo(&si);                //获取创建信息
        PROCESS_INFORMATION        pi={0};
        if(!CreateProcess(filename,NULL,NULL,NULL,FALSE,DEBUG_PROCESS|DEBUG_ONLY_THIS_PROCESS,NULL,NULL,&si,&pi)) //创建被调试进程
        {
                return 0;
        }

        BOOL WhileDoFlag=TRUE;
        DEBUG_EVENT DBEvent ;
        DWORD dwState;
        CONTEXT Regs ;
        Regs.ContextFlags = CONTEXT_FULL | CONTEXT_DEBUG_REGISTERS;
        while (WhileDoFlag)
        {
                /*WaitForDebugEvent:用来等待被调试事件,如果成功,则返回等待调试事件发生的毫秒数,如果没有调试事件发生,则返回函数的调用者,如果
                函数被定义为INFINITE,那么则一直等待的函数的发生*/
                WaitForDebugEvent (&DBEvent, INFINITE);
                dwState = DBG_EXCEPTION_NOT_HANDLED ;
                switch (DBEvent.dwDebugEventCode)
                {
                        case CREATE_PROCESS_DEBUG_EVENT:
                                dwState = DBG_CONTINUE ;
                                break;                        

                        case EXIT_PROCESS_DEBUG_EVENT :
                                WhileDoFlag=FALSE;
                                break ;

                        case EXCEPTION_DEBUG_EVENT:
                                switch (DBEvent.u.Exception.ExceptionRecord.ExceptionCode)
                                {
                                        case EXCEPTION_BREAKPOINT:                        //断点异常处理过程
                                        {
                                                GetThreadContext(pi.hThread, &Regs) ;
                                                if(Regs.Eip==(DWORD)0x0040CC10) //地址值需纠正,处理上面第一个int 3指令地址+1
                                                        dwState = DBG_CONTINUE ;
                                                else if(Regs.Eip==(DWORD)0x0040CC20)        //地址值需纠正,处理上面第二个int 3,指令地址+1
                                                        dwState = DBG_EXCEPTION_NOT_HANDLED ;
                                                else if(Regs.Eip==(DWORD)0x0040CC30) //地址值需纠正,对加密代码的地址修正
                                                {
                                                        DecryptCode(pi.hProcess,0x0040CC30,0x00401200); //地址值需纠正,对加密代码的地址修正
                                                        dwState = DBG_CONTINUE;
                                                }
                                                else
                                                        dwState = DBG_CONTINUE ;
                                                break;
                                        }
                                }
                                break;
                }               
                ContinueDebugEvent(pi.dwProcessId, pi.dwThreadId, dwState) ;
        }


        CloseHandle(pi.hProcess) ;
        CloseHandle(pi.hThread)  ;
        return 0;
}

我们回过头来细看这段源码,程序一旦被调试,就会调用debugmain这个函数 在这个函数中有两个至关重要的函数。
第一个是双进程保护的实现:CreateProcess,贴上msdn文档

BOOL CreateProcess
(
LPCTSTR lpApplicationName,   
LPTSTR lpCommandLine,
LPSECURITY_ATTRIBUTES lpProcessAttributes。
LPSECURITY_ATTRIBUTES lpThreadAttributes,
BOOL bInheritHandles,
DWORD dwCreationFlags,
LPVOID lpEnvironment,
LPCTSTR lpCurrentDirectory,
LPSTARTUPINFO lpStartupInfo,
LPPROCESS_INFORMATION lpProcessInformation
)

然后百科中有函数的讲解:http://baike.baidu.com/view/697167.htm?fr=aladdin(鸟文好的看msdn最好了)
在这个函数的第6个参数dwCreationFlags此参数为指定 附加的用来控制优先类和进程的创建标志。
这里源码中设置为DEBUG_PROCESS,则代表着:调用进程将被当做一个调试程序,并且新进程会被当做被调试的进程,即父进程会调试子进程以及创建的所有进程。系统把被调试程序发生的所有调试事件通知给调试器。另外源码中与上了一个DEBUG_ONLY_THIS_PROCESS,表示为:创建的新进程为此调试程序的调试对象,此时子进程发生的所有特定事件都将通知父进程。反正个人理解相当于DEBUG_PROCESS将DEBUG_ONLY_THIS_PROCESS所能调试的进程做了推广。

创建完进程之后开始进入双进程保护的核心部分——调试循环。这里有点类似于windows下的消息循环,在一个死循环下等待调试事件,如果有调试事件发生则处理调试事件,并执行相应操作,同时告知父进程。
当有调试事件被告知父进程时,子进程的线程会被挂起,父进程会调用WaitForDebugEvent来等待事件,并返回一个DEBUG_EVENT
源码里采用了 CREATE_PROCESS_DEBUG_EVENT(进程被创建),EXIT_PROCESS_DEBUG_EVENT(退出调试进程中最后一个线程) ,EXCEPTION_DEBUG_EVENT(调试异常),这里又会对异常在做出具体的判断,从而修改部分汇编代码,利用的算是简单是SMC代码自修改技术,可见写源码的人心有多狠。

紧接着是双进程保护中第二个关键的函数,WaitForDebugEvent,用来等待被调试进程发生的调试事件。贴上函数定义:
BOOL WaitForDebugEvent(LPDEBUG_ENENT lpDebugEvent, DWORD dwMilliseconds)
lpDebugEvent :指向接收调试事件信息的DEBUG_ ENENT结构的指针
dwMilliseconds:指定用来等待调试事件发生的毫秒数,如果 这段时间内没有调试事件发生,函数将返回调用者;如果将该参数指定为INFINITE,函数将一直等待直到调试事件发生
如果函数成功,则返回非零值;如果失败,则返回零
在调试器调用WaitForDebugEvent返回后,得到事件通知,然后解析DEBUG_EVENT结构,并对事件进行响应,处理完成后调试器将会调用ContinueDebugEvent,并根据参数来通知调试目标执行相应操作。

与此函数对应着的是ContinueDebugEvent,用于调试器恢复先前犹豫调试事件挂起的线程。
BOOL ContinueDebugEvent(DWORD dwProcessId,DWORD dwThreadId, DWORD dwContinueStatus )
dwProcessId 为被调试进程的进程标识符
dwThreadId 为欲恢复线程的线程标识符
dwContinueStatus指定了该线程将以何种方式继续,包含两个定义值DBG_CONTINUE和DBG_EXCEPTION_NOT_HANDLED
如果函数成功,则返回非零值;如果失败,则返回零。

在这个源码中这理解这两个函数是逆向的关键。
一般的思路都是

while(Condition)  
{
DEBUG_EVENT DebugEvent={0};  
WaitForDebugEvent(&DebugEvent,INFINITE);//等待调试事件   
ProcessEvenet(DebugEvent)//处理调试事件。  
ContinueDebugEvent(DebugEvent.dwProcessId,DebugEvent.dwThreadId,Condition);//通知调试目标继续执行。

}

这里也一样.
在源码中处理会给continuedebug的第三个参数赋为不同的值
DBG_CONTNUE表明该异常一杯妥善处理,
DBG_EXCEPTION_NOT_HANDLED则表明系统未被处理,会返回给操作系统,这个时候会返给调试器,就是我们开始的调试进程去处理。这就是双进程保护的精髓。
通过异常的处理与为处理来使程序走向不同的分支,从而来大大增加逆向的难度。
假如你此时进行黑盒测试的时候,如果并不清楚源码怎么写的,那么就很难搞清楚在不同的进程中的代码是怎样被执行的。
程序的大体流程应该就是这些了。然后可以对着这些先用编辑器(VS或者VC)进行调试,看看程序的走向。
具体的逆向过程下次再跟吧,码字好累。。。

你可能感兴趣的:(逆向分析)