freeBuf
主站

分类

漏洞 工具 极客 Web安全 系统安全 网络安全 无线安全 设备/客户端安全 数据安全 安全管理 企业安全 工控安全

特色

头条 人物志 活动 视频 观点 招聘 报告 资讯 区块链安全 标准与合规 容器安全 公开课

官方公众号企业安全新浪微博

FreeBuf.COM网络安全行业门户,每日发布专业的安全资讯、技术剖析。

FreeBuf+小程序

FreeBuf+小程序

    浅谈MFC类CrackMe中消息处理函数查找方法
    2019-03-22 10:00:27

    最近一个学姐发给我了一份CrackMe希望我解一下,其中涉及到了MFC的消息函数查找的问题,就顺便以此为例谈一下自己使用的消息函数查找的方法。本人萌新,如果有任何错漏与解释不清的地方,欢迎各路大佬指正。

    这个CrackMe是一个典型的MFC类型的程序,其框体如下:

    一、目标以及方法

    首先我们确认我们的目标是找到两个"注册"按钮的对应消息处理函数,那么有什么手段可以达到我们的目标?在MFC中有一个消息映射表的概念,参考候老的描述[1],实现代码如下:

    struct AFX_MSGMAP{
        AFX_MSGMAP * pBaseMessageMap;
        AFX_MSGMAP_ENTRY * lpEntries;
    }
    struct AFX_MSGMAP_ENTRY{
        UINT nMessage;    //Windows Message
        UINT nCode        //Control code or WM_NOTIFY code
        UINT nID;         //control ID (or 0 for windows messages)
        UINT nLastID;     //used for entries specifying a range of control id's
        UINT nSig;        //signature type(action) or pointer to message 
        AFX_PMSG pfn;     //routine to call (or specical value)
    }

    其中我们想要的某个控件的消息处理函数,就存放在该结构体的pfn中(其中nID与我们的控件ID相同的AFX_MSGMAP_ENTRY中的pfn就是我们所寻找的消息响应函数)。

    而由于AFX_MSGMAP一般只有一张,而且一般不是很大,所以我们只需要找到以下两个信息,即可定位消息响应函数。

    我们需要的控件ID

    AFX_MSGMAP

    二、使用ResourceHacker寻找目标按钮控件

    ResourceHacker是一个32位与64位的资源编辑器,它既是一个资源编译器(对于.rc文件),也是一个反编译器——支持查看和编辑可执行文件中的资源(* .exe;. dll;)以及已编译的资源库(.res;.mui)。ResourceHacker既支持GUI模式也支持命令行模式。

    在此我们使用ResourceHacker的查看可执行文件中的资源的功能。

    使用ResourceHacker加载CrackXX.exe,得到如下图的结果:

    我们需要查找的控件ID在对话框中,选择对应的对话框,之后点击“注册”一行(我们需要的控件),得到对应的控件ID:

    用同样的方法得到两个注册控件的ID,分别为1002与1005(注意此处控件ID是十进制不是16进制)。

    那么我们已经完成第一步目标,之后就是寻找AFX_MSGMAP即可。

    三、寻找AFX_MSGMAP

    查询可知,我们有两个思路可以获取该AFX_MSGMAP。

    AFX_MSGMAP存在于.rdata段,而.rdata段一般有RTTI,虚函数表与AFX_MSGMAP,所以MSG_MAP数据结构特征相对容易分辨,可以通过编写一个脚本找到。

    存在一个GetMessageMap函数,可以获得AFX_MSGMAP。而一般GetMessageMap在编译器自动生成的代码中会被调用,所以我们可以通过查找GetMessageMap调用者来完成对GetMessageMap的定位。

    3.1 编写脚本查找

    首先我们可以看下上面给出的AFX_MSGMAP的定义,它由一个指向GetMessageMap的函数指针以及一个指向AFX_MSGMAP__ENTYR的指针组成,而往往该指针指向的位置就是紧邻AFX_MSGMAP的下一个结构(也就是AFX_MSGMAP_ENTRY)。

    0044E880 AFX_MSGMAP

                        pBaseMessageMap=0041AE27
                        lpEntries=0044E888
    

    0044E888 AFX_MSGMAP_ENTRY1

                        nMessage
                        nCode
                        nID
                        nLastID    
                        nSig
                        pfn
    

    0044E8A0 AFX_MSGMAP_ENTRY2

    ......

    顺便值得一提的是pBaseMessageMap指向的地址是GetMessageMap的地址,其汇编代码如下

    .text:0041AE27 sub_41AE27      proc near               ; DATA XREF: .rdata:off_44E880↓o
    .text:0041AE27                                         ; .rdata:0044EFDC↓o ...
    .text:0041AE27                 mov     eax, offset off_44F120 
    
    .text:0041AE2C                 retn
    .text:0041AE2C sub_41AE27      endp

    显然GetMessageMap函数是将AFX_MSGMAP的地址静态生成,所以也证明了我们可以使用MessageMap函数获取AFX_MSGMAP这一点。

    那么回到正题,我们可以用这样的判断逻辑来搜索AFX_MSGMAP:

    搜索的起始地址从.rdata段的起始地址开始,以4为倍数增加。

    起始地址+4保存的DWORD(AFX_MSGMAP->lpEntries)等于起始地址+8(第一个AFX_MSGMAP_ENTRY)。

    根据定义,AFX_MSGMAP__ENTYR以全0结束,可以作为判定结束条件。

    在此基础上(搜索到结束之前),每个AFX_MSGMAP__ENTRY的pfn元素必须是一个有效地址(因为这个pfn指向对应消息的处理函数),不包括全0那个结构。

    那么对此我们可以写出idc脚本查找可能的满足条件的AFX_MSGMAP,idc脚本如下:

    #include <idc.idc>
    
    static NotEndAddr(pAddr){
     auto i=0;
     for (i=0;i<6;i++){
      if (Dword(i*4+pAddr)!=0)
       return 1;  //not end
     }
     return 0;    //reach the end
    }
    static isMsgMap(checkAddr,startVa,endVa){
     auto tmp1=Dword(checkAddr);
     auto tmp2=Dword(checkAddr+4);
    
     auto pAddr=checkAddr+8;
     if (tmp2==checkAddr+8){
    
      while(NotEndAddr(pAddr)){
       if(Dword(pAddr+20)<startVa||Dword(pAddr+20)>endVa){
    //    Message("Invalid Addr at %0x.\n",pAddr);
        return 0;
       }
    
       pAddr=pAddr+24;  
      }
      return 1;
     }
     return 0;
    }
    
    static main(){
     auto startRdataVa=0x0044E880;   //the start addr of .rdata
     auto size=0x0000DAA8;     //the size of .rdata
    
     auto startValidVa=0x00400000;   //check the addr is valid or not
     auto endValidVa=0x0046A000;
    
     auto i=0;
     for(i=0;i<size;i=i+4){
      if(isMsgMap(i+startRdataVa,startValidVa,endValidVa)){
       Message("Found Possible MessageMap at %0x.\n",i+startRdataVa);
      }
     }  
     Message("Finish searching.\n");
    
     return 0;
    }

    最终尝试使用这个脚本搜索,发现若干可能地址(测试过多个程序,一般生成的可能地址非常少,可以手动过滤):

    Found Possible MessageMap at 44e880.

    Found Possible MessageMap at 44ee88.

    Found Possible MessageMap at 44f120.

    Found Possible MessageMap at 44ff10.

    Found Possible MessageMap at 451410.

    Finish searching.

    那么此时我们就可以一个个查看,依据有:

    AFX_MSGMAP-->pBaseMessageMap是GetMessageMap(封装函数,非常短,只返回AFX_MSGMAP地址);

    其中一定有不为0的元素;

    其中一定存在你所查找的控件ID(AFX_MSGMAP_ENTRY-->nID),而且是全部ID(在本CrackMe中一定有1002与1005)。

    具体也可以根据这三条对脚本进行优化。若将脚本用于不同程序,建议修改startRdataVa,size,startValidVa以及endValidVa四项参数。

    3.2 通过查找GetMessageMap来获得

    在3.1节中我们已经证明GetMessageMap的确能获得AFX_MSGMAP地址,然而找到GetMessageMap的方法是使用AFX_MSGMAP,显然这本末倒置了。所以现在我们使用查询GetMessageMap的调用函数,之后逆向追溯的办法。

    从网上查询可得[2],OnWndMsg调用了GetMessageMap,OnWndMsg大体逻辑如下:

    BOOL CWnd::OnWndMsg(UINT message, WPARAM wParam, LPARAM lParam, LRESULT* pResult)
    {
        LRESULT lResult = 0;
        const AFX_MSGMAP* pMessageMap;
    
         //取得消息映射结构,GetMessageMap为虚函数,所以实际取的是CmainFrame的消息映射
        pMessageMap = GetMessageMap();
    
        // 查找对应的消息处理函数
        for (pMessageMap != NULL; pMessageMap = pMessageMap->pBaseMap)
            if (message < 0xC000)
                if ((lpEntry = AfxFindMessageEntry(pMessageMap->lpEntries, message, 0, 0)) != NULL)
                    goto LDispatch;
        ... ...
    LDispatch:
        //通过联合来匹配正确的函数指针类型
        union MessageMapFunctions mmf;
    mmf.pfn = lpEntry->pfn;
    ……

    所以为了获取GetMessageMap我们需要先获取Cwnd::OnWndMsg,这个函数在IDA中同样没有被识别,所以我们需要找到它的调用函数。同样,我们在网上找到了类似的实现:

    LRESULT CWnd::WindowProc(UINT message, WPARAM wParam, LPARAM lParam)
    {
        LRESULT lResult = 0;
    
        if (!OnWndMsg(message, wParam, lParam, &lResult))
    
            lResult = DefWindowProc(message, wParam, lParam);
    
        return lResult;
    }

    这次我们在IDA中找到了CWnd::WindowProc的识别,HexRay输出大致如下:

    int __thiscall CWnd::WindowProc(CWnd *this, unsigned int a2, unsigned int a3, int a4)
    {
      CWnd *v4; // esi
      int v6; // [esp+4h] [ebp-4h]
    
      v6 = 0;
      v4 = this;
      if ( !(*(int (__thiscall **)(CWnd *, unsigned int, unsigned int, int, int *))(*(_DWORD *)this + 276))(
              this,
              a2,
              a3,
              a4,
              &v6) )
        v6 = (*(int (__thiscall **)(CWnd *, unsigned int, unsigned int, int))(*(_DWORD *)v4 + 280))(v4, a2, a3, a4);
      return v6;
    }

    那么显然第一个if中嵌套的调用就是Cwnd::OnWndMsg,由于没有识别出来,我们需要用OD动态跟踪一下,在该处下断点,之后F9运行,运行结果如下:

    显然得到Cwnd::OnWndMsg的地址是0042259F,IDA查找发现是一个被隐藏的函数unknown_libname_93,进入后F5查看HexRay结果。

    v5 = this;
      v62 = 0;
      v61 = 0x7FFFFFFF;
      v63 = 0;
      if ( a2 != 273 )
      {
        if ( a2 != 78 )
        {
          v7 = (unsigned int)a4;
          if ( a2 == 6 )
          {
            v8 = CWnd::FromHandle(a4);
            _AfxHandleActivate(v5, (WPARAM)a3, v8);
          }
          if ( a2 == 32 && _AfxHandleSetCursor(v5, (signed __int16)a4, (unsigned int)a4 >> 16) )
            goto LABEL_3;
          v9 = *((_DWORD *)v5 + 19);
          if ( v9
            && *(_DWORD *)(v9 + 116) > 0
            && ((unsigned int)a2 >= 0x200 && (unsigned int)a2 <= 0x209
             || (unsigned int)a2 >= 0x100 && (unsigned int)a2 <= 0x10F
             || (unsigned int)(a2 - 641) <= 0x10)
            && (*(int (__stdcall **)(int, HDC, HWND, int *))(**((_DWORD **)v5 + 19) + 148))(a2, a3, a4, &v62) )
          {
            goto LABEL_117;
          }
          ......

    发现函数较大,结构有些混乱,静态分析不好识别,那么用OD进入分析。显然由网上源码逻辑看得出,第一个调用的应该是GetMessageMap,然后OD一步步跟,发现第一个call显然不是:

    .text:0042259F ; __unwind { // loc_44C480
    .text:0042259F push 70h
    .text:004225A1 mov eax, offset loc_44C480
    .text:004225A6 call __EH_prolog3
    .text:004225AB mov edi, ecx
    .text:004225AD xor eax, eax

    这个EH_prolog3猜测是编译器加的异常处理,继续跟,下一个call出现在0x4226A1处:

    .text:0042269D                 mov     eax, [edi]
    .text:0042269F                 mov     ecx, edi
    .text:004226A1                 call    dword ptr [eax+28h]
    .text:004226A4                 mov     ebx, eax
    .text:004226A6                 xor     ebx, [ebp+arg_0]
    .text:004226A9                 push    7               ; int

    那么此处显然就是我们寻找的GetMessageMap了,那么我们跟入就可以成功找到AFX_MSGMAP结构。

    四、结构优化

    找到AFX_MSGMAP,获得控件ID之后,我们就可以优化结构使得结构更加易读。

    参考网上内容[3],使用结构定义如下:

    struct AFX_MSGMAP_ENTRY
    
      {
    
       UINT nMessage;
    
       UINT nCode;
    
       UINT nID;
    
       UINT nLastID;
    
       UINT_PTR nSig;
    
       void (*pfn)(void);
    
      };
    
      struct AFX_MSGMAP
    
      {
    
        const AFX_MSGMAP *(__stdcall *pfnGetBaseMap)();
    
        const AFX_MSGMAP_ENTRY *lpEntries;
    
      };

    首先IDA上方菜单-->View-->Open Subview-->Local types,进入本地结构定义菜单。

    右键Insert,在弹出的结构窗口中输入上述结构。

    之后翻到最底部,找到上一步定义的两个结构体(一般就是最后两个),选择后右键Synchronize To idb。

    最后回到IDA-ViewA窗口,选中需要改变的结构体Alt+Q进行结构变换:

    变换前的结构与变换后的结构对比。

    那么接下来我们根据我们查到的控件ID确认1002与1005(对应hex为0x3EA与0x3ED)的消息处理函数分别为sub_401620与sub_401840。

    五、参考文献

    [1] 候俊杰,《深入浅出MFC》,P133

    [2] MFC消息映射的原理,https://www.cnblogs.com/lidabo/p/3694726.html

    [3] 使用IDA定位基于MFC的CrackMe的按钮函数,https://blog.csdn.net/SilverMagic/article/details/40622413

    *本文作者:kdsj,转载请注明来自FreeBuf.COM

    # CrackMe # MFC
    本文为 独立观点,未经允许不得转载,授权请联系FreeBuf客服小蜜蜂,微信:freebee2022
    被以下专辑收录,发现更多精彩内容
    + 收入我的专辑
    + 加入我的收藏
    相关推荐
    • 0 文章数
    • 0 关注者