Guidelines to MFC Reversing

Software developed with MFC may import MFC80U.dll (MFC80U is the name of the last version of the dll, as I’m writing), it depends on the type of compilation: as a static library or as a shared DLL.
I’ll analyze a software which imports the dll and has debug infos, just to make the job easier.
Once you understand MFC in this way, you can analyze MFC software compiled statically just adding to IDA the signatures of MFC and VisualC.


Prologue: What is MFC?

The Microsoft Foundation Classes Library (also Microsoft Foundation Classes or MFC) is a library that wraps portions of the Windows API in C++ classes, including functionality that allows to use a default application framework. Classes are defined for many of the handle-managed Windows objects and also for predefined windows and common controls.


Tools – References

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



This is a standard C source code for windows:

        case WM_COMMAND:

                case IDC_ABOUT:
                        DialogBoxParam(GetModuleHandle(NULL), MAKEINTRESOURCE(IDD_DIALOG1), NULL, (DLGPROC)MainDialogProc, 0);

                        // ...

Instead this is source code that uses MFC:

class CAboutDlg : public CDialog

// Dialog Data
        enum { IDD = IDD_ABOUTBOX };

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

// Implementation

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

void CAboutDlg::DoDataExchange(CDataExchange* pDX)

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

// App command to run the dialog
void CProvaRevApp::OnAppAbout()
        CAboutDlg aboutDlg;

How you can imagine the disasm of MFC software is harder to understand.

MFC Main

This is the Main disasm of our target:

.text:00401CBB                 public start
.text:00401CBB                 call    ___security_init_cookie
.text:00401CC0                 jmp     ___tmainCRTStartup

.text:004019FB ___tmainCRTStartup proc near            ; CODE XREF: start+5�j
.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    [email protected]    ; wWinMain(x,x,x,x)

; int __stdcall wWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPWSTR lpCmdLine, int nShowCmd)
[email protected] proc near
        jmp     [email protected]@[email protected]@[email protected] ; AfxWinMain(HINSTANCE__ *,HINSTANCE__ *,wchar_t *,int)
[email protected] endp

As you can see WinMain calls AfxWinMain.
If you have VisualStudio you can see MFC source code, in this article I’ll report only the functions we’ll need.

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");
                nReturnCode = pThread->ExitInstance();
                goto InitFailure;
        nReturnCode = pThread->Run();

        return nReturnCode;

This is the disasm of 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 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 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 loc_7831D334:
.text:7831D334                 mov     eax, [esi]
.text:7831D336                 mov     ecx, esi
.text:7831D338                 call    dword ptr [eax+5Ch]
.text:7831D33B loc_7831D33B:  
.text:7831D33B                 mov     ebx, eax
.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

In the code there are calls as call [eax+XXh]: actually the call to AfxGetApp (and AfxGetThread) gives back a pointer to a structure that has offsets of all functions used by MFC framework.
In this case edi (pApp) holds the offset of 405498, which value is 40349C VA, where the virtual functions table of CWinApp is stored:

.rdata:0040349C off_40349C      dd offset [email protected]@@[email protected]@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 [email protected]@@[email protected]@@Z ; CCmdTarget::OnCmdMsg(uint,int,void *,AFX_CMDHANDLERINFO *)
.rdata:004034B4                 dd offset [email protected]@@UAEXXZ ; CCmdTarget::OnFinalRelease(void)
.rdata:004034B8                 dd offset [email protected]@@[email protected] ; CCmdTarget::IsInvokeAllowed(long)
.rdata:004034BC                 dd offset [email protected]@@[email protected]@@Z ; CCmdTarget::GetDispatchIID(_GUID *)
.rdata:004034C0                 dd offset [email protected]@@UAEIXZ ; CCmdTarget::GetTypeInfoCount(void)
.rdata:004034C4                 dd offset [email protected]@@[email protected]@XZ ; CCmdTarget::GetTypeLibCache(void)
.rdata:004034C8                 dd offset [email protected]@@[email protected]@@Z ; CCmdTarget::GetTypeLib(ulong,ITypeLib * *)
.rdata:004034CC                 dd offset sub_401000

Now a question should pop up in your mind: where does MFC get the address? A quick glance at the reference with IDA…

.text:004023B0 sub_4023B0      proc near              
.text:004023B0                 push    0
.text:004023B2                 mov     ecx, offset dword_405498
.text:004023B7                 call    [email protected]@[email protected][email protected] ; 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
.text:004023D1 sub_4023B0      endp

This VA, 004023B0, is present in a structure

.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 [email protected]@YAXXZ ; `dynamic initializer for '_afxInitAppState''(void)
.rdata:00403310                 dd offset sub_4023B0

which is pushed to __initterm, called before WinMain

.text:00401AAC                 push    offset unk_403314
.text:00401AB1                 push    offset unk_403304
.text:00401AB6                 call    _initterm

After this excursus, let’s go back to AfxWinMain:
call dword ptr [eax+98h] (40349C + 98 = 00403534) calls…

.text:00403534                 dd offset [email protected]@@UAEHXZ ; CWinApp::InitApplication(void)

…while call dword ptr [eax+58h], that is pThread->InitInstance, calls the function:

.rdata:004034F4                 dd offset sub_401030

This function shows the dialog window, here is the main part of the code:

.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    [email protected]@[email protected]@@@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    [email protected]@[email protected]@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    [email protected]@@UAEHXZ ; CDialog::DoModal(void)
.text:004010BD                 lea     ecx, [esp+8+arg_4]
.text:004010C1                 mov     [esp+8+arg_88], 0FFFFFFFFh
.text:004010CC                 call    [email protected]@[email protected] ; CDialog::~CDialog(void)
.text:004010E3                 mov     esp, ebp
.text:004010E5                 pop     ebp
.text:004010E6                 retn



But where is MESSAGE_MAP?: Message Map can be get from

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)
     // ...


This is the disasm

.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

The call in 78312E95 leads us to:

.text:004011E0                 mov     eax, offset off_403628 ;eax = pMessageMap
.text:004011E5                 retn
.rdata:00403628 off_403628      dd offset [email protected]@@[email protected]@XZ
.rdata:00403628                                         ; CDialog::GetThisMessageMap(void)
.rdata:0040362C                 dd offset unk_403580 ;pMessageMap->lpEntries

At 403580 there’s the MESSAGE_MAP of this dialog.

So we can get the MessageMap quickly this way:

  1. Find before a call to CDialog:DoModal an istruction like this: mov dword ptr [esi], offset off_XXXXXX (it is used to load virtual functions table).
  2. Add 0x30 to that offset to get GetMessageMap function: into that function, look for the instruction mov eax, offset off_XXXXXX, where eax is pMessageMap
  3. Add 4 to pMessageMap to get Dialog MessageMap

Now an example. This is the software resource:

CONTROL "Register", 1006, BUTTON, //1006 = 0x3ee
CONTROL "About", 1007, BUTTON, //1007 = 0x3ef
CONTROL "Cancel", 1008, BUTTON, //1008 = 0x3f0

And this is its MESSAGE_MAP, which is an array of structures

        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

Every event has a structure where window ID and the function to use are stored.

IDC Script

// 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) {
                return 0;

        if(AddStrucMember(idStruct, "nCode", 4, FF_DWRD|FF_DATA, -1, 4) != 0) {
                return 0;

        if(AddStrucMember(idStruct, "nID", 8, FF_DWRD|FF_DATA, -1, 4) != 0) {
                return 0;

        if(AddStrucMember(idStruct, "nLastID", 12, FF_DWRD|FF_DATA, -1, 4) != 0) {
                return 0;

        if(AddStrucMember(idStruct, "nSignature", 16, FF_DWRD|FF_DATA, -1, 4) != 0) {
                return 0;

        if(AddStrucMember(idStruct, "pFunction", 20, FF_DWRD|FF_0OFF, -1, 4) != 0) {
                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");  

        ptr = addr;
        isOk = 1;

        while( Dword(ptr) != 0) {
                if(MakeStructEx(ptr, 24, "AFX_MSGMAP_ENTRY") == 0) {
                        isOk = 0;

                ptr = ptr + 24;

        if(isOk == 0) {
                Warning("\nCannot set the structure at %x\n", addr);
        } else {


This is the disasm after I used the script on it:

.rdata:00403580 stru_403580     AFX_MSGMAP_ENTRY <112h, 0, 0, 0, 1Eh, offset sub_4012D0> ; WM_SYSCOMMAND
.rdata:00403580                                         ; DATA XREF: .rdata:0040362C�o
.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



The function BOOL CCmdTarget::OnCmdMsg(UINT nID, int nCode, void* pExtra, AFX_CMDHANDLERINFO* pHandlerInfo), precisely the function _AfxDispatchCmdMsg, handles WM_COMMAND event.
Actually if you set a bp on it you can see that after a button or a menu is clicked on, the debugger halts the execution. By stepping you can enter the function called for that event, without having to retrieve the MESSAGE_MAP.