最近在看MFC逆向方面的资料,找了些国外的资料,第一次翻译东西,文章没有翻译难度,但这篇文章对我个人很有帮助,而分享才是最重要的,所以发了出来,希望对初识MFC逆向的朋友有所帮助。


目录
1 MFC逆向指导
1.1 相关工具
1.2 序言: 什么是MFC?
1.3 简介
1.4 实验
1.4.1 MFC 主过程
1.4.2 获取 MESSAGE_MAP
1.4.2.1 IDC 脚本
1.4.3 利用 WM_COMMAND
1.5 最后说明


相关工具

IDA
Reversing Microsoft Visual C++ Part II: Classes, Methods and RTTI
Crackme 

前言:什么是MFC?

微软基础类库(也称为微软基础类 或 MFC)用C++类包装了部分Windows API,并包含了一个应用程序框架。类被定义为很多用句柄来管理的窗口对象,另外还有预定义窗口和各种通用控件。

介绍

使用MFC进行软件开发需要导入MFC80U.dll(MFC80U 是笔者写本文为止最近的一个版本的dll),视编译类型而定,可以使一个静态库或者共享DLL。
我将分析一个导入了这个dll并拥有调试信息的软件,这样会使我们的工作更容易一些。
一旦你通过这种方式而理解了MFC,你就可以通过在IDA中添加MFC和VisualC的签名来分析用MFC开发软件。 

实验

这是windows下的一段标准的C源代码:

代码:
LRESULT CALLBACK DialogProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
        switch(uMsg)
        {
        case WM_COMMAND:
                switch(LOWORD(wParam))
                {

                case IDC_ABOUT:
                        DialogBoxParam(GetModuleHandle(NULL), MAKEINTRESOURCE(IDD_DIALOG1), NULL, (DLGPROC)MainDialogProc, 0);
                        break;
                        
                        // ...
                }
        }
}
相反,这是一段MFC下的源代码: 
代码:
class CAboutDlg : public CDialog
{
public:
        CAboutDlg();

// Dialog Data
        enum { IDD = IDD_ABOUTBOX };

protected:
        virtual void DoDataExchange(CDataExchange* pDX);    // DDX/DDV support

// Implementation
protected:
        DECLARE_MESSAGE_MAP()
};

CAboutDlg::CAboutDlg() : CDialog(CAboutDlg::IDD)  //CAboutDlg::IDD is dialog ID          
{
}

void CAboutDlg::DoDataExchange(CDataExchange* pDX)
{
        CDialog::DoDataExchange(pDX);
}

BEGIN_MESSAGE_MAP(CAboutDlg, CDialog) //Dialog Message Map: is like DialogProc
END_MESSAGE_MAP()

// App command to run the dialog
void CProvaRevApp::OnAppAbout() 
{
        CAboutDlg aboutDlg; 
        aboutDlg.DoModal();
}
你可以猜到MFC软件的反汇编是更难理解的。

MFC Main

这是目标程序的主要反汇编: 
代码:
.text:00401CBB                 public start
.text:00401CBB                 call    ___security_init_cookie
.text:00401CC0                 jmp     ___tmainCRTStartup

.text:004019FB ___tmainCRTStartup proc near            ; CODE XREF: start+5j
.text:004019FB
.text:004019FB                 push    5Ch
.text:004019FD                 push    offset unk_403DD8
.text:00401A02                 call    __SEH_prolog4
;... other initialization code
.text:00401B3E                 push    ecx             ; nShowCmd
.text:00401B3F                 push    eax             ; lpCmdLine
.text:00401B40                 push    ebx             ; hPrevInstance
.text:00401B41                 push    400000h         ; hInstance
.text:00401B46                 call    _wWinMain@16    ; wWinMain(x,x,x,x)

; int __stdcall wWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPWSTR lpCmdLine, int nShowCmd)
_wWinMain@16 proc near
        jmp     ?AfxWinMain@@YGHPAUHINSTANCE__@@0PA_WH@Z ; AfxWinMain(HINSTANCE__ *,HINSTANCE__ *,wchar_t *,int)
_wWinMain@16 endp
如你所见WinMain调用了AfxWinMain。
如果你安装了VisualStudio你可以看下MFC的源代码,在这篇文章中我只会讲解我们要用到的函数。 
代码:
[
int AFXAPI AfxWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
        _In_ LPTSTR lpCmdLine, int nCmdShow)
{
        ASSERT(hPrevInstance == NULL);

        int nReturnCode = -1;
        CWinThread* pThread = AfxGetThread();
        CWinApp* pApp = AfxGetApp();

        // AFX internal initialization
        if (!AfxWinInit(hInstance, hPrevInstance, lpCmdLine, nCmdShow))
                goto InitFailure;

        // App global initializations (rare)
        if (pApp != NULL && !pApp->InitApplication())
                goto InitFailure;

        // Perform specific initializations
        if (!pThread->InitInstance())
        {
                if (pThread->m_pMainWnd != NULL)
                {
                        TRACE(traceAppMsg, 0, "Warning: Destroying non-NULL m_pMainWnd\n");
                        pThread->m_pMainWnd->DestroyWindow();
                }
                nReturnCode = pThread->ExitInstance();
                goto InitFailure;
        }
        nReturnCode = pThread->Run();

InitFailure:
        AfxWinTerm();
        return nReturnCode;
}
这是AfxWinMain函数的反汇编: 
代码:
.text:7831D2D2                 public AfxWinMain
.text:7831D2D2 AfxWinMain      proc near
.text:7831D2D2                 push    ebx
.text:7831D2D3                 push    esi
.text:7831D2D4                 push    edi
.text:7831D2D5                 or      ebx, 0FFFFFFFFh
.text:7831D2D8                 call    AfxGetModuleThreadState
.text:7831D2DD                 mov     esi, [eax+4] ;pThread
.text:7831D2E0                 call    AfxGetModuleState
.text:7831D2E5                 push    [esp+0Ch+arg_C]
.text:7831D2E9                 mov     edi, [eax+4]  ;pApp
.text:7831D2EC                 push    [esp+10h+arg_8]
.text:7831D2F0                 push    [esp+14h+arg_4]
.text:7831D2F4                 push    [esp+18h+arg_0]
.text:7831D2F8                 call    AfxWinInit
.text:7831D2FD                 test    eax, eax
.text:7831D2FF                 jz      short loc_7831D33D
.text:7831D301                 test    edi, edi     
.text:7831D303                 jz      short loc_7831D313
.text:7831D305                 mov     eax, [edi] 
.text:7831D307                 mov     ecx, edi
.text:7831D309                 call    dword ptr [eax+98h] 
.text:7831D30F                 test    eax, eax
.text:7831D311                 jz      short loc_7831D33D
.text:7831D313
.text:7831D313 loc_7831D313: 
.text:7831D313                 mov     eax, [esi]
.text:7831D315                 mov     ecx, esi
.text:7831D317                 call    dword ptr [eax+58h] 
.text:7831D31A                 test    eax, eax
.text:7831D31C                 jnz     short loc_7831D334
.text:7831D31E                 cmp     [esi+20h], eax
.text:7831D321                 jz      short loc_7831D32B
.text:7831D323                 mov     ecx, [esi+20h]
.text:7831D326                 mov     eax, [ecx]
.text:7831D328                 call    dword ptr [eax+68h]
.text:7831D32B
.text:7831D32B loc_7831D32B:
.text:7831D32B                 mov     eax, [esi]
.text:7831D32D                 mov     ecx, esi
.text:7831D32F                 call    dword ptr [eax+70h] 
.text:7831D332                 jmp     short loc_7831D33B
.text:7831D334
.text:7831D334 loc_7831D334: 
.text:7831D334                 mov     eax, [esi]
.text:7831D336                 mov     ecx, esi
.text:7831D338                 call    dword ptr [eax+5Ch]
.text:7831D33B
.text:7831D33B loc_7831D33B:  
.text:7831D33B                 mov     ebx, eax
.text:7831D33D
.text:7831D33D loc_7831D33D: 
.text:7831D33D                 call    AfxWinTerm
.text:7831D342                 pop     edi
.text:7831D343                 pop     esi
.text:7831D344                 mov     eax, ebx
.text:7831D346                 pop     ebx
.text:7831D347                 retn    10h
.text:7831D347 AfxWinMain      endp
在上面的代码中有一些调用如call [eax + XXh]:事实上对AfxGetApp(和AfxGetThread)的调用将返回一个指向结构的指针,这个结构记录了所有MFC框架所使用的函数的偏移。
EDI(pApp) 的值为40349C VA,指向的位置是CwinApp中保存的虚函数表。 
代码:
.rdata:0040349C off_40349C      dd offset ?GetRuntimeClass@CWinApp@@UBEPAUCRuntimeClass@@XZ ;CWinApp::GetRuntimeClass(void)
.rdata:004034A0                 dd offset sub_401010
.rdata:004034A4                 dd offset nullsub_1
.rdata:004034A8                 dd offset nullsub_2
.rdata:004034AC                 dd offset nullsub_1
.rdata:004034B0                 dd offset ?OnCmdMsg@CCmdTarget@@UAEHIHPAXPAUAFX_CMDHANDLERINFO@@@Z ; CCmdTarget::OnCmdMsg(uint,int,void *,AFX_CMDHANDLERINFO *)
.rdata:004034B4                 dd offset ?OnFinalRelease@CCmdTarget@@UAEXXZ ; CCmdTarget::OnFinalRelease(void)
.rdata:004034B8                 dd offset ?IsInvokeAllowed@CCmdTarget@@UAEHJ@Z ; CCmdTarget::IsInvokeAllowed(long)
.rdata:004034BC                 dd offset ?GetDispatchIID@CCmdTarget@@UAEHPAU_GUID@@@Z ; CCmdTarget::GetDispatchIID(_GUID *)
.rdata:004034C0                 dd offset ?GetTypeInfoCount@CCmdTarget@@UAEIXZ ; CCmdTarget::GetTypeInfoCount(void)
.rdata:004034C4                 dd offset ?GetTypeLibCache@CCmdTarget@@UAEPAVCTypeLibCache@@XZ ; CCmdTarget::GetTypeLibCache(void)
.rdata:004034C8                 dd offset ?GetTypeLib@CCmdTarget@@UAEJKPAPAUITypeLib@@@Z ; CCmdTarget::GetTypeLib(ulong,ITypeLib * *)
.rdata:004034CC                 dd offset sub_401000
;.......................................................
现在您应该会有个疑问,MFC在哪里得到的地址?让我们快来看一下它的引用…
代码:
.text:004023B0 sub_4023B0      proc near              
.text:004023B0                 push    0
.text:004023B2                 mov     ecx, offset dword_405498
.text:004023B7                 call    ??0CWinApp@@QAE@PB_W@Z ; CWinApp::CWinApp(wchar_t const *)
.text:004023BC                 push    offset sub_4023F0 ; void (__cdecl *)()
.text:004023C1                 mov     dword_405498, offset off_40349C ;<-- this is our offset
.text:004023CB                 call    _atexit
.text:004023D0                 pop     ecx
.text:004023D1                 retn
虚拟地址(VA)004023B0包含在一个结构中。
代码:
.rdata:00403304 unk_403304      db    0    
.rdata:00403305                 db    0
.rdata:00403306                 db    0
.rdata:00403307                 db    0
.rdata:00403308                 dd offset _pre_cpp_init
.rdata:0040330C                 dd offset ??__E_afxInitAppState@@YAXXZ ; `dynamic initializer for '_afxInitAppState''(void)
.rdata:00403310                 dd offset sub_4023B0
WinMain运行之前由__initterm压入。
代码:
.text:00401AAC                 push    offset unk_403314
.text:00401AB1                 push    offset unk_403304 
.text:00401AB6                 call    _initterm
现在,让我们回到AfxWinMain中:
call dword ptr [eax+98h] (40349C + 98 = 00403534) 调用... 
代码:
.text:00403534                 dd offset ?InitApplication@CWinApp@@UAEHXZ ; CWinApp::InitApplication(void)
...而 call dword ptr [eax+58h], 就是 pThread->InitInstance, 调用了这个函数:
代码:
.rdata:004034F4                 dd offset sub_401030
这个函数显示对话框,代码的主要部分都在这里了。
.
代码:
text:00401030 sub_401030      proc near  
.text:00401030                 push    ebp
.text:00401031                 mov     ebp, esp
;..........................................................................
.text:0040109F                 call    sub_401130 
;--------------------------------------------------------------------------
;entrato nella call
.text:00401155                 push    0               ; lpIconName
.text:00401157                 push    66h             ; Dialog ID
.text:00401159                 mov     ecx, esi
.text:0040115B                 call    ??0CDialog@@QAE@IPAVCWnd@@@Z ; CDialog::CDialog(uint,CWnd *)
.text:00401160                 mov     [esp+14h+var_4], 0
.text:00401168                 mov     dword ptr [esi], offset off_403744 ;virtual functions table offset which is store
; in CDialog.DoModal -> CDialog__PreModal -> AfxHookWindowCreate  
.text:0040116E                 call    ?AfxGetModuleState@@YGPAVAFX_MODULE_STATE@@XZ ; AfxGetModuleState(void)
;exit the call
;---------------------------------------------------------------------------
.text:004010A4                 lea     edx, [esp+8+arg_4]
.text:004010A8                 mov     [esp+8+arg_88], 0
.text:004010B3                 mov     ecx, edx
.text:004010B5                 mov     [esi+20h], edx
.text:004010B8                 call    ?DoModal@CDialog@@UAEHXZ ; CDialog::DoModal(void)
.text:004010BD                 lea     ecx, [esp+8+arg_4]
.text:004010C1                 mov     [esp+8+arg_88], 0FFFFFFFFh
.text:004010CC                 call    ??1CDialog@@UAE@XZ ; CDialog::~CDialog(void)
;..........................................................................
.text:004010E3                 mov     esp, ebp
.text:004010E5                 pop     ebp
.text:004010E6                 retn
获取 MESSAGE_MAP

MESSAGE_MAP在哪呢? Message Map 可以再这里找到:
代码:
BOOL CWnd::OnWndMsg(UINT message, WPARAM wParam, LPARAM lParam, LRESULT* pResult)
{
     // ....
     const AFX_MSGMAP* pMessageMap; 
     pMessageMap = GetMessageMap();
     // ....
          if ((lpEntry = AfxFindMessageEntry(pMessageMap->lpEntries, message, 0, 0)) != NULL)
     // ...
  
}
这是反汇编后:
代码:
.text:78312E91                 mov     eax, [edi] ; eax = 403744
.text:78312E93                 mov     ecx, edi
.text:78312E95                 call    dword ptr [eax+30h] ; eax+30h = 00403774 = GetMessageMap()
;.rdata:00403774                 dd offset sub_4011E0 
;...................................................................
.text:78312F1B                 push    0
.text:78312F1D                 push    0
.text:78312F1F                 jnb     short loc_78312F67
.text:78312F21                 push    [ebp+arg_0] ;messagge
.text:78312F24                 push    dword ptr [esi+4] ; lpEntries (0040362C)
.text:78312F27                 call    AfxFindMessageEntry
从78312E95 的调用进入到了这里:
代码:
;GetMessageMap()
.text:004011E0                 mov     eax, offset off_403628 ;eax = pMessageMap
.text:004011E5                 retn
;----------------------------------------------------------------
;pMessageMap
.rdata:00403628 off_403628      dd offset ?GetThisMessageMap@CDialog@@KGPBUAFX_MSGMAP@@XZ 
.rdata:00403628                                         ; CDialog::GetThisMessageMap(void)
.rdata:0040362C                 dd offset unk_403580 ;pMessageMap->lpEntries
在403580 处就是这个对话框的 MESSAGE_MAP。 
我们可以通过下面的方式快速获取MessageMap:
1.  在CDialog:DoModal调用之前找一条指令,类似于: mov dword ptr [esi], offset off_XXXXXX (通常是用来定位虚函数表的)。
2.  在指令中的offset地址上加0x30就得到了GetMessageMap函数: 在函数中找到一条指令 mov eax, offset off_XXXXXX, eax 为 pMessageMap。
3.  在 pMessageMap上+4 就得到了对话框的MessageMap。


现在来看一个例子。 这是软件资源信息:
代码:
CONTROL "Register", 1006, BUTTON,   //1006 = 0x3ee
CONTROL "About", 1007, BUTTON,      //1007 = 0x3ef
CONTROL "Cancel", 1008, BUTTON,     //1008 = 0x3f0
下面是该资源所对应的MESSAGE_MAP结构体
代码:
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_PTR nSig;       // signature type (action) or pointer to message #
        AFX_PMSG pfn;    // routine to call (or special value)
};
.rdata:00403580 MESSAGE_MAP    dd 112h                 
.rdata:00403584                 dd 0
.rdata:00403588                 dd 0
.rdata:0040358C                 dd 0
.rdata:00403590                 dd 1Eh
.rdata:00403594                 dd offset sub_4012D0

.rdata:00403598                 dd 0Fh
.rdata:0040359C                 dd 0
.rdata:004035A0                 dd 0
.rdata:004035A4                 dd 0
.rdata:004035A8                 dd 13h
.rdata:004035AC                 dd offset sub_401370

.rdata:004035B0                 dd 37h
.rdata:004035B4                 dd 0
.rdata:004035B8                 dd 0
.rdata:004035BC                 dd 0
.rdata:004035C0                 dd 28h
.rdata:004035C4                 dd offset sub_401450

.rdata:004035C8                 dd 111h
.rdata:004035CC                 dd 0
.rdata:004035D0                 dd 3EFh  
.rdata:004035D4                 dd 3EFh  
.rdata:004035D8                 dd 38h
.rdata:004035DC                 dd offset sub_401460

.rdata:004035E0                 dd 111h
.rdata:004035E4                 dd 0
.rdata:004035E8                 dd 3F0h  
.rdata:004035EC                 dd 3F0h
.rdata:004035F0                 dd 38h
.rdata:004035F4                 dd offset sub_4014F0

.rdata:004035F8                 dd 111h
.rdata:004035FC                 dd 0
.rdata:00403600                 dd 3EEh  
.rdata:00403604                 dd 3EEh
.rdata:00403608                 dd 38h
.rdata:0040360C                 dd offset sub_401510

.rdata:00403610                 dd 0
...
每一个时间都有一个结构体,用于保存窗口ID和调用的函数。

IDC 脚本
代码:
// mfc_message_map.idc version 0.2 by Pn 2008
#include <idc.idc>

//Only some WM_ command are recognized
static messageName(ptr, message) {
        
        if(message == 1) // WM_CREATE
                MakeComm(ptr, "WM_CREATE");
        else if(message == 2) // WM_DESTROY
                MakeComm(ptr, "WM_DESTROY");
        else if(message == 5) // WM_SIZE
                MakeComm(ptr, "WM_SIZE");
        else if(message == 0x10) // WM_CLOSE
                MakeComm(ptr, "WM_CLOSE");
        else if(message == 0x18) // WM_SHOWWINDOW
                MakeComm(ptr, "WM_SHOWWINDOW");
        
        else if(message == 0x0100) // WM_KEYDOWN
                MakeComm(ptr, "WM_KEYDOWN");
        else if(message == 0x0101) // WM_KEYUP
                MakeComm(ptr, "WM_KEYUP");
        else if(message == 0x0102) // WM_CHAR
                MakeComm(ptr, "WM_KEYCHAR");
        
        else if(message == 0x0110) // WM_INITDIALOG 
                MakeComm(ptr, "WM_INITDIALOG");
        else if(message == 0x0111) // WM_COMMAND
                MakeComm(ptr, "WM_COMMAND");
        else if(message == 0x0112) // WM_SYSCOMMAND
                MakeComm(ptr, "WM_SYSCOMMAND");
        else if(message == 0x0113) // WM_TIMER
                MakeComm(ptr, "WM_TIMER");
        else if(message == 0x0116) // WM_INITMENU
                MakeComm(ptr, "WM_INITMENU");
        else if(message == 0x0117) // WM_INITMENUPOPUP
                MakeComm(ptr, "WM_INITMENUPOPUP");
        else if(message == 0x0126) // WM_MENUCOMMAND
                MakeComm(ptr, "WM_MENUCOMMAND");

}
static DefineStruct() {
        auto idStruct;
       
        idStruct = AddStrucEx(-1,"AFX_MSGMAP_ENTRY",0);
        if(idStruct == 0) return 0;
       
        if(AddStrucMember(idStruct, "nMessage", 0, FF_DWRD|FF_DATA, -1, 4) != 0) {
                Warning("\n1\n");
                DelStruc(idStruct);
                return 0;
        }
       
        if(AddStrucMember(idStruct, "nCode", 4, FF_DWRD|FF_DATA, -1, 4) != 0) {
                Warning("\n2\n");
                DelStruc(idStruct);
                return 0;
        }
       
        if(AddStrucMember(idStruct, "nID", 8, FF_DWRD|FF_DATA, -1, 4) != 0) {
                Warning("\n3\n");
                DelStruc(idStruct);
                return 0;
        }
       
        if(AddStrucMember(idStruct, "nLastID", 12, FF_DWRD|FF_DATA, -1, 4) != 0) {
                Warning("\n4\n");
                DelStruc(idStruct);
                return 0;
        }
       
        if(AddStrucMember(idStruct, "nSignature", 16, FF_DWRD|FF_DATA, -1, 4) != 0) {
                Warning("\n5\n");
                DelStruc(idStruct);
                return 0;
        }
       
        if(AddStrucMember(idStruct, "pFunction", 20, FF_DWRD|FF_0OFF, -1, 4) != 0) {
                Warning("\n6\n");
                DelStruc(idStruct);
                return 0;
        }
       
        return idStruct;
}

static GenerateMFCMap(addr) {
        auto idStruct, ptr, message, isOk;
       
        idStruct = GetStrucIdByName("AFX_MSGMAP_ENTRY");
        if( idStruct == -1) {
                idStruct = DefineStruct();
                if(idStruct == 0) {
                        Warning("\nCannot declare the structure\n");  
                        return;
                }
        }
       
        ptr = addr;
        isOk = 1;
       
        while( Dword(ptr) != 0) {
                if(MakeStructEx(ptr, 24, "AFX_MSGMAP_ENTRY") == 0) {
                        isOk = 0;
                        break;
                }
                                messageName(ptr,Dword(ptr));
                                
                ptr = ptr + 24;
        }
               
        if(isOk == 0) {
                Warning("\nCannot set the structure at %x\n", addr);
        } else {
                Message("Completed");
        }
       
        return;
}
这是应用了脚本之后的反汇编:
代码:
.rdata:00403580 stru_403580     AFX_MSGMAP_ENTRY <112h, 0, 0, 0, 1Eh, offset sub_4012D0> ; WM_SYSCOMMAND
.rdata:00403580                                         ; DATA XREF: .rdata:0040362Co
.rdata:00403598                 AFX_MSGMAP_ENTRY <0Fh, 0, 0, 0, 13h, offset sub_401370>
.rdata:004035B0                 AFX_MSGMAP_ENTRY <37h, 0, 0, 0, 28h, offset sub_401450>
.rdata:004035C8                 AFX_MSGMAP_ENTRY <111h, 0, 3EFh, 3EFh, 38h, offset sub_401460> ; WM_COMMAND
.rdata:004035E0                 AFX_MSGMAP_ENTRY <111h, 0, 3F0h, 3F0h, 38h, offset sub_4014F0> ; WM_COMMAND
.rdata:004035F8                 AFX_MSGMAP_ENTRY <111h, 0, 3EEh, 3EEh, 38h, offset sub_401510> ; WM_COMMAND
.rdata:00403610                 db    0
利用 WM_COMMAND

函数 BOOL CCmdTarget::OnCmdMsg(UINT nID, int nCode, void* pExtra, AFX_CMDHANDLERINFO* pHandlerInfo), 准确的说是函数 _AfxDispatchCmdMsg, 来处理WM_COMMOND消息。
实际上如果你设置一个断点在它上面,当你点击按钮或者菜单的时候调试器将会停下。然后你就可以步入这个事件的处理函数,而不用检索 MESSAGE_MAP。

最后说明

感谢看到这里的所有朋友。
文章来源:http://quequero.org/Basic_MFC_Reversing(eng)#Guidelines_to_MFC_reversing