Log in

View Full Version : Guidelines to MFC reversing


pnluck
December 30th, 2008, 03:27
Tools - References
Reversing Microsoft Visual C++ Part II: Classes, Methods and RTTI http://www.openrce.org/articles/full_view/23
IDA http://www.hex-rays.com/idapro
Crackme http://quequero.org/uicwiki/Lezione6.zip

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.

Introduction
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.

Essay
This is a standard C source code for windows:
Code:
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;

// ...
}
}
}

Instead this is source code that uses MFC:
Code:
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:oDataExchange(CDataExchange* pDX)
{
CDialog:oDataExchange(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();
}

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

MFC Main
This is the Main disasm of our target:
Code:
.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
.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

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.
Code:
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;
}

This is the disasm of AfxWinMain:
Code:
.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

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:
Code:
.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
;.......................................................

Now a question should pop up in your mind: where does MFC get the address? A quick glance at the reference with IDA...
Code:
.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
.text:004023D1 sub_4023B0 endp

This VA, 004023B0, is present in a structure
Code:
.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

which is pushed to __initterm, called before WinMain
Code:
.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...
Code:
.text:00403534 dd offset ?InitApplication@CWinApp@@UAEHXZ ; CWinApp::InitApplication(void)

...while call dword ptr [eax+58h], that is pThread->InitInstance, calls the function:
Code:
.rdata:004034F4 dd offset sub_401030

This function shows the dialog window, here is the main part of the code:
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 ??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:oModal(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

Get MESSAGE_MAP
But where is MESSAGE_MAP?: Message Map can be get from
Code:
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
Code:
.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: The call in 78312E95 leads us to:
Code:
;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

At 403580 there's the MESSAGE_MAP of this dialog.

So we can get the MessageMap quickly this way:

Find before a call to CDialogoModal an istruction like this: mov dword ptr [esi], offset off_XXXXXX (it is used to load virtual functions table).
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
Add 4 to pMessageMap to get Dialog MessageMap

Now an example. This is the software resource:
Code:
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
Code:
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)
};

Code:
.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
Code:
// mfc_message_map.idc version 0.2 by Pnluck 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;
}

This is the disasm after I used the script on it:
Code:
.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

Retrieve WM_COMMAND
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.

Final Note
Thanks to Ntoskrnl (Daniel Pistelli), EvilCry, Quequero, Zairon, emdel, DrWatson, Brnocrist, ocean and quequero forum members.

Pnluck

Daniel Pistelli
December 30th, 2008, 12:01
Good work, pnluck. This is surely useful. However, a little remark. The IDC script to convert messages into their named definition isn't complete. You put only a handful of messages and the result is that the code example you presented above is missing some definitions:

Quote:
.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


Two messages remain undefined. To avoid writing all that boring code by hand, write a little python script (or in a language you know) to parse the message definitions and produce the IDC code automatically. This would make the whole thing much more useful.

reverser
January 8th, 2009, 20:08
Just make an enum with all WM_ messages (IDA's standard typelib has them too but they're split over several ranges) and apply that enum to the first member of AFX_MSGMAP_ENTRY (in structures window, press 'M' on nMessage).