最近在看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+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
如你所见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: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
利用 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
[培训]内核驱动高级班,冲击BAT一流互联网大厂工
作,每周日13:00-18:00直播授课
上传的附件: