MFC程序开发所谓是非常简单,但是对于我们逆向人员来说,如果想要逆向MFC程序,那么我们就必须了解它背后的机制,这样我们才能够清晰地逆向出MFC程序,今天这篇文章就来带领大家了解MFC的第一大机制:程序启动机制:
首先,我们创建一个单文档架构程序,我们来观察一下:
这里我创建的项目名称为:MFCApplication1
我们发现一共有三个类:(这里有一些是MFC自动为我们写好的类,类名称通常为项目名称,继承了MFC库中的一些类,我们只看MFC库中的类)我们发现大致三个类:
CFrame类:这个类是框架窗口类,封装了框架窗口的操作
CWinApp类:这个类是应用程序类,封装了流程的操作
CDocument类:这个类是文档类,封装了数据的处理,例如存储,转换等
CView类:这个类是视图类,封装了视图窗口的操作
我们主要讲解的是CFrame类和CWinApp类
这里我创建好的项目,我们先来看看部分源代码:
CMFCApplication1.h:
// MFCApplication1.h: MFCApplication1 应用程序的主头文件
//
#pragma once
#ifndef __AFXWIN_H__
#error "include 'pch.h' before including this file for PCH"
#endif
#include "resource.h" // 主符号
// CMFCApplication1App:
// 有关此类的实现,请参阅 MFCApplication1.cpp
//
class CMFCApplication1App : public CWinApp
{
public:
CMFCApplication1App() noexcept;
// 重写
public:
virtual BOOL InitInstance();
virtual int ExitInstance();
// 实现
afx_msg void OnAppAbout();
DECLARE_MESSAGE_MAP()
};
extern CMFCApplication1App theApp;
MianFrm.h:
// MainFrm.h: CMainFrame 类的接口
//
#pragma once
class CMainFrame : public CFrameWnd
{
protected: // 仅从序列化创建
CMainFrame() noexcept;
DECLARE_DYNCREATE(CMainFrame)
// 特性
public:
// 操作
public:
// 重写
public:
virtual BOOL PreCreateWindow(CREATESTRUCT& cs);
// 实现
public:
virtual ~CMainFrame();
#ifdef _DEBUG
virtual void AssertValid() const;
virtual void Dump(CDumpContext& dc) const;
#endif
protected: // 控件条嵌入成员
CToolBar m_wndToolBar;
CStatusBar m_wndStatusBar;
// 生成的消息映射函数
protected:
afx_msg int OnCreate(LPCREATESTRUCT lpCreateStruct);
DECLARE_MESSAGE_MAP()
};
看完这部分源码之后,我们来自己实现,不用MFC自动帮我们实现的类,来加深我们的理解:
我们创建一个控制台应用,修改项目属性:
- 常规:MFC的使用:在静态库中使用MFC
- 连接器->系统->子系统:窗口
接下来,我们删除掉main.cpp中的所有代码,我们来自己仿照MFC写一个窗口:
#include <afxwin.h>
class CMyFrameWnd:public CFrameWnd{
public:
}
class CMyApp:public CWinApp{
public:
CMyApp(){};
//必须要重写虚函数:
virtual BOOL InitInstance(){
CMyFrameWnd* pFrame = new CMyFrameWnd;
pFrame->Create(NULL,L"FirstMFC");
m_pMainWnd = pFrame;
pFrame.ShouWindow(SW_SHOW);
pFrame->UpdateWindow();
return TRUE;
}
};
CMyWinApp theApp;
这里的CMyFrameWnd由于只是一个框架类,我们不需要做任何操作,只是继承MFC的框架类就可以了,狗仔函数会调用CFrameWnd中写好的构造函数
接下来我们创建一个自己的应用程序类,继承MFC的CWinApp类,这里我们写一个空的构造函数,当构造的时候,就会调用父类的构造函数了
我们在自己的应用程序类中还必须重写虚函数InitInstance,在这个函数中,我们创建窗口,显示窗口
最后,我们还需要一个应用程序类全局变量theApp
当我们写好这些之后,我们运行,发现窗口已经神奇的创建好了:
我们在创建Win32应用程序的时候,需要做的几个步骤:
- 定义窗口类,创建窗口
- 注册窗口
- 刷新窗口
- 显示窗口
但是在我们这样实现了MFC之后,我们貌似少做了很多操作,几行代码就完成了窗口的创建,甚至我们连WinMain函数都没有看见,那么MFC的程序启动机制到底是怎样的呢?我们首先就来深究一下MFC的程序运行机制:
我们知道,在C++中,全局变量的构造是优先于main函数的,那么我们少做的很多操作,肯定就是在这里全局变量的构造函数中了
我们在我们自己的应用程序类的构造函数上下断点,我们跟到CWinApp的构造函数中来看看:
这里我就写伪代码了:
我们应用程序类的父类:CWinApp的构造函数:
CWinApp::CWinApp(LPCTSTR lpszAppName){
//首先判断lpszAppName是否为空,做了一些操作,我们没必要关注
//AFX_MODULE_STATE这个结构体是MFC中的 程序模块状态信息结构体
//这里是获取当前应用程序模块状态信息
AFX_MODULE_STATE* pModuleState = _AFX_CMDTARGET_GETSTATE();
//这里是获取当前程序线程状态信息,可以看出来,当前程序状态信息是模块状态信息中的一个成员
AFX_MODULE_THREAD_STATE* pThreadState = pModuleState->m_thread;
//这里是给线程信息的某个成员赋值,给的是this指针,也就是theApp的地址
pThreadState->m_pCurrentWinThread = this;
//这里是给theApp的成员赋值,我们自己的应用程序类没有写成员,这里都是CWinApp中写好的,我们只是继承过来了
//获取当前应用程序线程句柄
m_hThread = ::GetCurrentThread();
//获取当前应用程序线程ID
m_nThreadID = ::GetCurrentThreadId();
//给当前应用程序模块状态信息的某个成员赋值
//不难看出这里保存的是当前应用程序类的地址,也就是theApp的地址
pModuleState->m_pCurrentWinApp = this;
//之后就是一些给类中的其他成员赋初值,很多都是NULL,我们不再关注
}
但是跟完构造函数后,我们也没有找到WinMain,更没有找到注册窗口等操作
既然在应用程序类的构造中我们没有找到,我们来看看接下来我们写的pFrame->Create(NULL, L"FirstMFC");
这一段代码,但是这一段代码很明显是框架类的方法,我们猜测很多操作就在这里完成
我们在这一段代码上下断点,断下来之后,我们F11跟进,然后查看调用对战,我们就发现了wWinMian和AfxWInMain,由于我们写过win32程序,我们知道底层就是wWinMain函数,我们就跟到这个函数中来看:
这里还是写伪代码
_tWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
_In_ LPTSTR lpCmdLine, int nCmdShow)
{
//我们发现在wWinMain函数中,做了转发,而且根据名称来看,是MFC的wWimMain函数,我们跟进去看看:
//这里注意,有些人到这里就不会往进去跟了,我们在这行代码上下断点,然后去掉之前的断点,重新调试,就能跟进去了
return AfxWinMain(hInstance, hPrevInstance, lpCmdLine, nCmdShow){
//这里根据名称来看,是获取当前应用程序线程信息,我们跟进去看看:
CWinThread* pThread = AfxGetThread(){
//这里跟前面的应用程序类的构造函数中一样,获取当前应用程序的模块状态信息
AFX_MODULE_THREAD_STATE* pState = AfxGetModuleThreadState();
//取出得到的模块状态信息中的成员->当前线程
CWinThread* pThread = pState->m_pCurrentWinThread;
//返回线程
return pThread;
}
//这里根据名称来看,是获取了当前应用程序的应用程序类的地址,我们还是跟进去看看
CWinApp* pApp = AfxGetApp(){
//这里afx开头的函数
return afxCurrentWinApp{
//这里可以肯定,是返回了应用程序类地址
return pResult = _afxBaseModuleState.GetData();
}
}
这里这两步跟完之后,大家有没有发现,这里实现了C++的多态?
CWinThread,是爷类指针,指向派生类类对象
CWinApp是父类的指针,指向了派生类对象,theApp是我们自己实现的应用程序类嘛
//下一步:执行了这个函数,在源代码中,是在if的条件中实现的,这里直接拉出来了
AfxWinInit(hInstance, hPrevInstance, lpCmdLine, nCmdShow){
//根据名称,这个是初始化的函数
pApp->m_hInstance = hInstance;
hPrevInstance; // Obsolete.
pApp->m_lpCmdLine = lpCmdLine;
pApp->m_nCmdShow = nCmdShow;
pApp->SetCurrentHandles();
}
程序启动初始化操作 虚函数我们可以重写,貌似是关于文档的一些初始化操作
pApp->InitApplication()
//接下来这个函数也是在if条件中进行的,这里直接拉出来
pThread->InitInstance(){
这是虚函数,我们在我们自己的类中已经实现
}
}
}
代码跟到这里,我们已经解决了很多前面我们提出来的问题:WinMain函数等但是还有一个问题:消息循环呢?我们知道,如果没有消息循环,那么这个窗口根本运行不了,那既然我们能够关闭窗口等操作,那肯定是已经写好了消息循环,我们来接着上面的代码来跟:
上面的函数紧接着,我们就看到了一行代码:
这就是MFC的消息循环,我们来跟进去看一下:
pThread->Run(){
//这里也相当于做了转发,在转发之前有一些判断,这里省略
int CWinApp::Run(){
//跟到这里之后,我们就发现了消息循环:
for (;;)//死循环
{
//这里的PeekMessage是到消息列表中检查是否有消息
while (bIdle &&!::PeekMessage(&(pState->m_msgCur), NULL, NULL, NULL, PM_NOREMOVE))
{
//这里是说:如果没有消息的话,就会进行空闲操作,比如刷新窗口,检查定时器等等
if (!OnIdle(lIdleCount++))
bIdle = FALSE;
}
//死循环里面还有一个do——while循环,我们来看看:
do
{
//这里,如果是WM_QUIT消息,才会返回false,才会进去退出程序
if (!PumpMessage(){//在这个PumpMessage中,发现了翻译消息,派发消息
if (pState->m_msgCur.message != WM_KICKIDLE && !AfxPreTranslateMessage(&(pState->m_msgCur)))
{
::TranslateMessage(&(pState->m_msgCur));
::DispatchMessage(&(pState->m_msgCur));
}
}
)
return ExitInstance();
if (IsIdleMessage(&(pState->m_msgCur)))
{
bIdle = TRUE;
lIdleCount = 0;
}
} while (::PeekMessage(&(pState->m_msgCur), NULL, NULL, NULL, PM_NOREMOVE));
}
}
}
至此,我们的消息循环就跟踪完了,这里,再总结一下MFC的程序启动机制:
- 首先是theApp,也就是我们自己的窗口类(CMyApp)的构造,也就是CWinApp的构造,在构造函数中:
- 将theApp的地址,保存到了当前应用程序线程状态信息中
- 将theApp的地址,保存到了当前应用程序的模块状态信息中
- 然后进入了WinMain函数,在WinMain函数中:
- 利用全局函数AfxGetThread()获取到了theApp的地址
- 利用theApp调用虚函数 InitApplication,进行文档类的一些初始化操作
- 利用theApp调用虚函数 InitInstance,也就是我们重写的虚函数,创建窗口
- 利用theApp调用虚函数 Run 消息循环
- 在消息循环中,我们跟踪到了获取消息,翻译消息,派发消息等
- 利用TheApp调用退出函数