双进程保护及实现

来源:互联网 发布:知乎 人生目标 编辑:程序博客网 时间:2024/04/29 11:18

考前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;i<end;i++)        {                ReadProcessMemory(hProcess,(LPCVOID)i,&ch,sizeof(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)进行调试,看看程序的走向。
具体的逆向过程下次再跟吧,码字好累。。。

0 0