0x0 环境
系统:Windows XP
编译器:VC++ 6.0
0x1 为什么要了解MFC按钮事件
没饭吃是Visual C++ 6.0内提供的c++快速界面开发库历史上也是火过一段时间。哪怕是现在也还是有很多人直接用MFC来开发一些小工具比如说网络上别人发布的CreateMe。
0x2 创建一个MFC对话框
要了解MFC的按钮事件首先你得先创建一个MFC对话框程序并且添加一个按钮事件后你会得到如下由编译器给你创建新的东西。
一个按钮事件对应的函数申明 afx_msg void OnButton1(); 一个按钮事件对应的函数实现,自己加个MessageBox吧: void CMy1Dlg::OnButton1() { MessageBox("Hello WOrld"); } MFC的消息映射表: BEGIN_MESSAGE_MAP(CMy1Dlg, CDialog) //….省略 ON_BN_CLICKED(IDC_BUTTON1, OnButton1) END_MESSAGE_MAP()
MFC的按钮响回调函应全部都是存储在这个消息映射表里面,也就是说如果找到消息映射表那么就知道了MFC的所有事件对应的回调函数。
0x3 MFC的消息事件表
消息事件表示由三条宏组成。BEGIN_MESSAGE_MAP、ON_BN_CLICKED和END_MESSAGE_MAP结束。所以我们需要直接对这三个宏进行展然后整理后得到这样的代码
AFX_MSGMAP* PASCAL CMy1Dlg::_GetBaseMessageMap(){ return &CDialog::messageMap; } const AFX_MSGMAP* CMy1Dlg::GetMessageMap() const { return &CMy1Dlg::messageMap; } AFX_COMDAT AFX_DATADEF const AFX_MSGMAP CMy1Dlg::messageMap = { &CMy1Dlg::_GetBaseMessageMap , &CMy1Dlg::_messageEntries[0] }; 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 nSig; // signature type (action) or pointer to message # AFX_PMSG pfn; // routine to call (or special value) }; AFX_COMDAT const AFX_MSGMAP_ENTRY CMy1Dlg::_messageEntries[] = { { WM_COMMAND, (WORD)BN_CLICKED, (WORD)IDC_BUTTON1, (WORD)IDC_BUTTON1, AfxSig_vv, (AFX_PMSG)&OnButton1 } {0, 0, 0, 0, AfxSig_end, (AFX_PMSG)0 } };
经过整理后可以看到MFC的所有消息事件都存储在了AFX_MSGMAP_ENTRY CMy1Dlg::_messageEntries[]这个结构体数组中,AFX_MSGMAP_ENTRY是一个结构体信息,结构体存储了Windows消息,按钮事件ID,以及按钮事件回调函数。 那么就可以知道最重要的就是AFX_MSGMAP_ENTRY CMy1Dlg::_messageEntries这个全局的数组了。
在搞清楚按钮回调函数存储在那后那么我们就需要知道如何在内存中找到_ AFX_MSGMAP_ENTRY CMy1Dlg::_messageEntries然后来找到所有的消息回调。
0x4 观察按钮事件流程
用VC6对OnButton1下一个断点后F5起来。后观察栈信息就能得到MFC的完整消息派发流程。但是这个流程并不重要,重要的是从OnButton1往上回溯找到AFX_MSGMAP_ENTRY CMy1Dlg::_messageEntries。
观察栈上函数可以看到这样的逻辑,在CCmdTarget::OnCmdMsg函数中最后调用_AfxDispatchCmdMsg()函数,可以通过监视窗口查看lpEntry指针中pfn存储的就是按钮OnButton1的回调函数。而lpEntry指针来自AfxFindMessageEntry()函数的返回值。

AfxFindMessageEntry()实现能看到第一个参数就是我们需要找的AFX_MSGMAP_ENTRY CMy1Dlg::_messageEntries指针头部。下面还可以看到消息搜索的逻辑我就不截图了。

那么就是说,当我们在按钮回调中往上返回两个函数后观察当前函数内的call,只要call的参数有4个,那么就能确定参数一就是我们要找的AFX_MSGMAP_ENTRY CMy1Dlg::_messageEntries数组头部。
0x5 在OD中搜索AFX_MSGMAP_ENTRY的内存头部
在MFC按钮回调中调用MessageBox弹框卡主后知道OnButton1的回调函数地址00401450然后我们在返回两个函数。

返回第一层

返回第二层后往上找有4个参数的call,它的第一个参数就是我们要找的消息映射表。上图很近的地方就是004159DB的这个call它就是AfxFindMessageEntry()这个函数。

我们在0041777C这个函数头下断点。点击MFC控件按钮,然后在内存中查看第一个参数内存地址。在按照AFX_MSGMAP_ENTRY的结构体大小来进行搜索就能找到OnButton1的回调函数地址00401450。

发表评论