看雪上一位大牛nOpnOp写了个MFC入口地址查找工具(http://bbs.pediy.com/showthread.php?t=81855),一用,果然好用,可是没源代码,很想知道它的运行原理,所以逆向研究了一番,有所收获,和大家分享一下。
程序使用了调试API,相当于是使用了OD啊,真厉害。ASPPACK加壳,让我用ODSCRIPT脱掉了。点击按钮,判断是否MFC程序等等,用SetTimer启动一个计时器,开始每隔50毫秒运行一次分析程序,该分析程序做了很多判断之类的,通过F5插件获得伪代码,关键点:

    使用GetThreadContext获得上下文句柄

    Context.ContextFlags = 65543;
    GetThreadContext(ProcessInformation.hThread, &Context);
    if ( *(_DWORD *)(v5 + 16) && *(_DWORD *)(v5 + 112) )
    {
      v8 = *(int (__stdcall **)(_DWORD, _DWORD, _DWORD, _DWORD, _DWORD))ReadProcessMemory;
    }
    else
    {
      v8 = *(int (__stdcall **)(_DWORD, _DWORD, _DWORD, _DWORD, _DWORD))ReadProcessMemory;
      v9 = Context.Ebp + 20;
      Buffer = 0;
      lpBaseAddress = 0;
      if ( !ReadProcessMemory(ProcessInformation.hProcess, (LPCVOID)(Context.Ebp + 12), &Buffer, 4u, 0)
        || Buffer != 0x111
        || !v8(ProcessInformation.hProcess, v9, &lpBaseAddress, 4, 0)
        || lpBaseAddress != *(LPCVOID *)(v5 + 12) )
        goto LABEL_59;
    }
    lpBaseAddress = 0;
    if ( !v8(ProcessInformation.hProcess, Context.Ebp + 8, &lpBaseAddress, 4, 0) )
    {
      if ( *(_DWORD *)(v5 + 16) )
      {
        str_Show((LPARAM)"读取ESP失败!");
        goto LABEL_14;
      }
LABEL_60:
      (*(void (__thiscall **)(int, HANDLE, _DWORD))(*(_DWORD *)v5 + 52))(v5, ProcessInformation.hProcess, 0);
      Context.ContextFlags = 65543;
      --Context.Eip;
      SetThreadContext(ProcessInformation.hThread, &Context);
      goto LABEL_61;
    }

也就是读取了两次调试进程的堆栈,第一次是读取 EBP+12 处的数据第二次是读取 EBP+20处的数据,也就是判断压入的堆栈是否等于0x111即WM_COMMAND,它拦截的是哪个函数呢,经过一番调试终于确定是CCmdTarget::OnCmdMsg里的AfxFindMessageEntry函数,AfxFindMessageEntry压入4个参数lpEntries, nMsg, nCode, nID,也就是nMsg==0x111和nID==?之类的来确定这个函数,确定之后再次读取EBP+8处的堆栈,也就是第三次读取,读取到的正是lpEntries也就是消息映射表的地址,通过这个地址可以遍历出所有的消息映射的地址,至此也就真相大白了,但要理解消息映射表,得从MFC的消息映射机制说起,说来话长,自己看书吧。
总结:作者用这个方法不尽完美,如果程序运行得很快,那么计时器就没有用了,因为该函数马上就返回了,拦截不到,在逆向这个程序之前就有想过,可以使用静态的方法去分析MFC程序,首先要获得GetMessageMap()这个函数的地址,逆向过程中发现在MFC40.DLL中是类似[EDI+30]之类的方法来获得这个函数地址,可能类都是用EDI+XXX之类的来表示,那么30就是固定的,那么首先就找到这个EDI,也就是类的基地址,这个基地址硬编码地址在某个代码段中,因为是自动编写的代码,那么应该可以用搜索代码之类的找到这个地址,得到地址+30得到GetMessageMap()的地址,再者消息映射表也是硬编码到GetMessageMap()函数中,也就是说直接可以找到消息映射表的地址。另一种想法是拦截从特定DLL返回到主程序的所有入口,也就是说获得返回程序的入口点地址,这样就应该有好几个入口点,不过这种方法比较通用,从中找一个最有可能的入口点。