浅谈MFC类CrackMe中消息处理函数查找方法
最近一个学姐发给我了一份CrackMe希望我解一下,其中涉及到了MFC的消息函数查找的问题,就顺便以此为例谈一下自己使用的消息函数查找的方法。本人萌新,如果有任何错漏与解释不清的地方,欢迎各路大佬指正。
这个CrackMe是一个典型的MFC类型的程序,其框体如下:

一、目标以及方法
首先我们确认我们的目标是找到两个”注册”按钮的对应消息处理函数,那么有什么手段可以达到我们的目标?在MFC中有一个消息映射表的概念,参考候老的描述[1],实现代码如下:
struct AFX_MSGMAP{
AFX_MSGMAP * pBaseMessageMap;
AFX_MSGMAP_ENTRY * lpEntries;
}
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 specical value)
}
其中我们想要的某个控件的消息处理函数,就存放在该结构体的pfn中(其中nID与我们的控件ID相同的AFX_MSGMAP_ENTRY中的pfn就是我们所寻找的消息响应函数)。
而由于AFX_MSGMAP一般只有一张,而且一般不是很大,所以我们只需要找到以下两个信息,即可定位消息响应函数。
我们需要的控件ID
AFX_MSGMAP
二、使用ResourceHacker寻找目标按钮控件
ResourceHacker是一个32位与64位的资源编辑器,它既是一个资源编译器(对于.rc文件),也是一个反编译器——支持查看和编辑可执行文件中的资源(* .exe;. dll;)以及已编译的资源库(.res;.mui)。ResourceHacker既支持GUI模式也支持命令行模式。
在此我们使用ResourceHacker的查看可执行文件中的资源的功能。
使用ResourceHacker加载CrackXX.exe,得到如下图的结果:

我们需要查找的控件ID在对话框中,选择对应的对话框,之后点击“注册”一行(我们需要的控件),得到对应的控件ID:

用同样的方法得到两个注册控件的ID,分别为1002与1005(注意此处控件ID是十进制不是16进制)。
那么我们已经完成第一步目标,之后就是寻找AFX_MSGMAP即可。
三、寻找AFX_MSGMAP
查询可知,我们有两个思路可以获取该AFX_MSGMAP。
AFX_MSGMAP存在于.rdata段,而.rdata段一般有RTTI,虚函数表与AFX_MSGMAP,所以MSG_MAP数据结构特征相对容易分辨,可以通过编写一个脚本找到。
存在一个GetMessageMap函数,可以获得AFX_MSGMAP。而一般GetMessageMap在编译器自动生成的代码中会被调用,所以我们可以通过查找GetMessageMap调用者来完成对GetMessageMap的定位。
3.1 编写脚本查找
首先我们可以看下上面给出的AFX_MSGMAP的定义,它由一个指向GetMessageMap的函数指针以及一个指向AFX_MSGMAP__ENTYR的指针组成,而往往该指针指向的位置就是紧邻AFX_MSGMAP的下一个结构(也就是AFX_MSGMAP_ENTRY)。
0044E880 AFX_MSGMAP
pBaseMessageMap=0041AE27
lpEntries=0044E888
0044E888 AFX_MSGMAP_ENTRY1
nMessage
nCode
nID
nLastID
nSig
pfn
0044E8A0 AFX_MSGMAP_ENTRY2
……
顺便值得一提的是pBaseMessageMap指向的地址是GetMessageMap的地址,其汇编代码如下
.text:0041AE27 sub_41AE27 proc near ; DATA XREF: .rdata:off_44E880↓o
.text:0041AE27 ; .rdata:0044EFDC↓o ...
.text:0041AE27 mov eax, offset off_44F120
.text:0041AE2C retn
.text:0041AE2C sub_41AE27 endp
显然GetMessageMap函数是将AFX_MSGMAP的地址静态生成,所以也证明了我们可以使用MessageMap函数获取AFX_MSGMAP这一点。
那么回到正题,我们可以用这样的判断逻辑来搜索AFX_MSGMAP:
搜索的起始地址从.rdata段的起始地址开始,以4为倍数增加。
起始地址+4保存的DWORD(AFX_MSGMAP->lpEntries)等于起始地址+8(第一个AFX_MSGMAP_ENTRY)。
根据定义,AFX_MSGMAP__ENTYR以全0结束,可以作为判定结束条件。
在此基础上(搜索到结束之前),每个AFX_MSGMAP__ENTRY的pfn元素必须是一个有效地址(因为这个pfn指向对应消息的处理函数),不包括全0那个结构。
那么对此我们可以写出idc脚本查找可能的满足条件的AFX_MSGMAP,idc脚本如下:
#include <idc.idc>
static NotEndAddr(pAddr){
auto i=0;
for (i=0;i<6;i++){
if (Dword(i*4+pAddr)!=0)
return 1; //not end
}
return 0; //reach the end
}
static isMsgMap(checkAddr,startVa,endVa){
auto tmp1=Dword(checkAddr);
auto tmp2=Dword(checkAddr+4);
auto pAddr=checkAddr+8;
if (tmp2==checkAddr+8){
while(NotEndAddr(pAddr)){
if(Dword(pAddr+20)<startVa||Dword(pAddr+20)>endVa){
// Message("Invalid Addr at %0x.\n",pAddr);
return 0;
}
pAddr=pAddr+24;
}
return 1;
}
return 0;
}
static main(){
auto startRdataVa=0x0044E880; //the start addr of .rdata
auto size=0x0000DAA8; //the size of .rdata
auto startValidVa=0x00400000; //check the addr is valid or not
auto endValidVa=0x0046A000;
auto i=0;
for(i=0;i<size;i=i+4){
if(isMsgMap(i+startRdataVa,startValidVa,endValidVa)){
Message("Found Possible MessageMap at %0x.\n",i+startRdataVa);
}
}
Message("Finish searching.\n");
return 0;
}
最终尝试使用这个脚本搜索,发现若干可能地址(测试过多个程序,一般生成的可能地址非常少,可以手动过滤):
Found Possible MessageMap at 44e880.
Found Possible MessageMap at 44ee88.
Found Possible MessageMap at 44f120.
Found Possible MessageMap at 44ff10.
Found Possible MessageMap at 451410.
Finish searching.
那么此时我们就可以一个个查看,依据有:
AFX_MSGMAP–>pBaseMessageMap是GetMessageMap(封装函数,非常短,只返回AFX_MSGMAP地址);
其中一定有不为0的元素;
其中一定存在你所查找的控件ID(AFX_MSGMAP_ENTRY–>nID),而且是全部ID(在本CrackMe中一定有1002与1005)。
具体也可以根据这三条对脚本进行优化。若将脚本用于不同程序,建议修改startRdataVa,size,startValidVa以及endValidVa四项参数。
3.2 通过查找GetMessageMap来获得
在3.1节中我们已经证明GetMessageMap的确能获得AFX_MSGMAP地址,然而找到GetMessageMap的方法是使用AFX_MSGMAP,显然这本末倒置了。所以现在我们使用查询GetMessageMap的调用函数,之后逆向追溯的办法。
从网上查询可得[2],OnWndMsg调用了GetMessageMap,OnWndMsg大体逻辑如下:
BOOL CWnd::OnWndMsg(UINT message, WPARAM wParam, LPARAM lParam, LRESULT* pResult)
{
LRESULT lResult = 0;
const AFX_MSGMAP* pMessageMap;
//取得消息映射结构,GetMessageMap为虚函数,所以实际取的是CmainFrame的消息映射
pMessageMap = GetMessageMap();
// 查找对应的消息处理函数
for (pMessageMap != NULL; pMessageMap = pMessageMap->pBaseMap)
if (message < 0xC000)
if ((lpEntry = AfxFindMessageEntry(pMessageMap->lpEntries, message, 0, 0)) != NULL)
goto LDispatch;
... ...
LDispatch:
//通过联合来匹配正确的函数指针类型
union MessageMapFunctions mmf;
mmf.pfn = lpEntry->pfn;
……
所以为了获取GetMessageMap我们需要先获取Cwnd::OnWndMsg,这个函数在IDA中同样没有被识别,所以我们需要找到它的调用函数。同样,我们在网上找到了类似的实现:
LRESULT CWnd::WindowProc(UINT message, WPARAM wParam, LPARAM lParam)
{
LRESULT lResult = 0;
if (!OnWndMsg(message, wParam, lParam, &lResult))
lResult = DefWindowProc(message, wParam, lParam);
return lResult;
}
这次我们在IDA中找到了CWnd::WindowProc的识别,HexRay输出大致如下:
int __thiscall CWnd::WindowProc(CWnd *this, unsigned int a2, unsigned int a3, int a4)
{
CWnd *v4; // esi
int v6; // [esp+4h] [ebp-4h]
v6 = 0;
v4 = this;
if ( !(*(int (__thiscall **)(CWnd *, unsigned int, unsigned int, int, int *))(*(_DWORD *)this + 276))(
this,
a2,
a3,
a4,
&v6) )
v6 = (*(int (__thiscall **)(CWnd *, unsigned int, unsigned int, int))(*(_DWORD *)v4 + 280))(v4, a2, a3, a4);
return v6;
}
那么显然第一个if中嵌套的调用就是Cwnd::OnWndMsg,由于没有识别出来,我们需要用OD动态跟踪一下,在该处下断点,之后F9运行,运行结果如下:

显然得到Cwnd::OnWndMsg的地址是0042259F,IDA查找发现是一个被隐藏的函数unknown_libname_93,进入后F5查看HexRay结果。
v5 = this;
v62 = 0;
v61 = 0x7FFFFFFF;
v63 = 0;
if ( a2 != 273 )
{
if ( a2 != 78 )
{
v7 = (unsigned int)a4;
if ( a2 == 6 )
{
v8 = CWnd::FromHandle(a4);
_AfxHandleActivate(v5, (WPARAM)a3, v8);
}
if ( a2 == 32 && _AfxHandleSetCursor(v5, (signed __int16)a4, (unsigned int)a4 >> 16) )
goto LABEL_3;
v9 = *((_DWORD *)v5 + 19);
if ( v9
&& *(_DWORD *)(v9 + 116) > 0
&& ((unsigned int)a2 >= 0x200 && (unsigned int)a2 <= 0x209
|| (unsigned int)a2 >= 0x100 && (unsigned int)a2 <= 0x10F
|| (unsigned int)(a2 - 641) <= 0x10)
&& (*(int (__stdcall **)(int, HDC, HWND, int *))(**((_DWORD **)v5 + 19) + 148))(a2, a3, a4, &v62) )
{
goto LABEL_117;
}
......
发现函数较大,结构有些混乱,静态分析不好识别,那么用OD进入分析。显然由网上源码逻辑看得出,第一个调用的应该是GetMessageMap,然后OD一步步跟,发现第一个call显然不是:
.text:0042259F ; __unwind { // loc_44C480
.text:0042259F push 70h
.text:004225A1 mov eax, offset loc_44C480
.text:004225A6 call __EH_prolog3
.text:004225AB mov edi, ecx
.text:004225AD xor eax, eax
这个EH_prolog3猜测是编译器加的异常处理,继续跟,下一个call出现在0x4226A1处:
.text:0042269D mov eax, [edi]
.text:0042269F mov ecx, edi
.text:004226A1 call dword ptr [eax+28h]
.text:004226A4 mov ebx, eax
.text:004226A6 xor ebx, [ebp+arg_0]
.text:004226A9 push 7 ; int
那么此处显然就是我们寻找的GetMessageMap了,那么我们跟入就可以成功找到AFX_MSGMAP结构。
四、结构优化
找到AFX_MSGMAP,获得控件ID之后,我们就可以优化结构使得结构更加易读。
参考网上内容[3],使用结构定义如下:
struct AFX_MSGMAP_ENTRY
{
UINT nMessage;
UINT nCode;
UINT nID;
UINT nLastID;
UINT_PTR nSig;
void (*pfn)(void);
};
struct AFX_MSGMAP
{
const AFX_MSGMAP *(__stdcall *pfnGetBaseMap)();
const AFX_MSGMAP_ENTRY *lpEntries;
};
首先IDA上方菜单–>View–>Open Subview–>Local types,进入本地结构定义菜单。
右键Insert,在弹出的结构窗口中输入上述结构。
之后翻到最底部,找到上一步定义的两个结构体(一般就是最后两个),选择后右键Synchronize To idb。
最后回到IDA-ViewA窗口,选中需要改变的结构体Alt+Q进行结构变换:

变换前的结构与变换后的结构对比。

那么接下来我们根据我们查到的控件ID确认1002与1005(对应hex为0x3EA与0x3ED)的消息处理函数分别为sub_401620与sub_401840。
五、参考文献
[1] 候俊杰,《深入浅出MFC》,P133
[2] MFC消息映射的原理,https://www.cnblogs.com/lidabo/p/3694726.html
[3] 使用IDA定位基于MFC的CrackMe的按钮函数,https://blog.csdn.net/SilverMagic/article/details/40622413
浅谈MFC类CrackMe中消息处理函数查找方法的更多相关文章
- 浅谈线程池(中):独立线程池的作用及IO线程池
原文地址:http://blog.zhaojie.me/2009/07/thread-pool-2-dedicate-pool-and-io-pool.html 在上一篇文章中,我们简单讨论了线程池的 ...
- 浅谈java类集框架和数据结构(2)
继续上一篇浅谈java类集框架和数据结构(1)的内容 上一篇博文简介了java类集框架几大常见集合框架,这一篇博文主要分析一些接口特性以及性能优化. 一:List接口 List是最常见的数据结构了,主 ...
- 【ASP.NET MVC系列】浅谈NuGet在VS中的运用
一 概述 在我们讲解NuGet前,我们先来看看一个例子. 1.例子: 假设现在开发一套系统,其中前端框架我们选择Bootstrap,由于选择Bootstrap作为前端框架,因此,在项目中,我们 ...
- 浅谈surging服务引擎中的rabbitmq组件和容器化部署
1.前言 上个星期完成了surging 的0.9.0.1 更新工作,此版本通过nuget下载引擎组件,下载后,无需通过代码build集成,引擎会通过Sidecar模式自动扫描装配异构组件来构建服务引擎 ...
- Oracle数据库中调用Java类开发存储过程、函数的方法
Oracle数据库中调用Java类开发存储过程.函数的方法 时间:2014年12月24日 浏览:5538次 oracle数据库的开发非常灵活,不仅支持最基本的SQL,而且还提供了独有的PL/SQL, ...
- 浅谈如何检查Linux中开放端口列表
给大家分享一篇关于如何检查Linux中的开放端口列表的详细介绍,首先如果你想检查远程Linux系统上的端口是否打开请点击链接浏览.如果你想检查多个远程Linux系统上的端口是否打开请点击链接浏览.如果 ...
- 浅谈局域网ARP攻击的危害及防范方法(图)
浅谈局域网ARP攻击的危害及防范方法(图) 作者:冰盾防火墙 网站:www.bingdun.com 日期:2015-03-03 自 去年5月份开始出现的校内局域网频繁掉线等问题,对正常的教育教 ...
- vlookup函数基本使用--如何将两个Excel表中的数据匹配;excel表中vlookup函数使用方法将一表引到另一表
vlookup函数基本使用--如何将两个Excel表中的数据匹配:excel表中vlookup函数使用方法将一表引到另一表 一.将几个学生的籍贯匹配出来‘ 二.使用查找与引用函数 vlookup 三. ...
- 查看dll中的函数(方法)
https://jingyan.baidu.com/article/5553fa82b953b365a23934b7.html 查看dll中的函数(方法) 听语音 | 浏览:2004 | 更新:201 ...
随机推荐
- Zk 集群概念
https://blog.csdn.net/gs80140/article/details/51496925
- day 35 协程与gil概念
博客链接: http://www.cnblogs.com/linhaifeng/articles/7429894.html 今日概要: 1 生产者消费者模型(补充) 2 GIL(进程与线程的应用场景) ...
- react-antd 按需加载报错
基于create-react-app 搭建的 react 项目 引入 antd UI 配置按需加载 但是报一下错误 .翻译过了一下 是内嵌JavaScript选项没有开启什么的 大白话就是 les ...
- Manjaro安装配置笔记
简单介绍: Manjaro和Ubuntu的都使用有段时间了,还是AUR大法用着舒服趁着由KDE桌面更换deepin时系统崩溃,直接重装了系统,版本:Manjaro-deepin-17.1.7-x86_ ...
- centos6.5 nginx安装pcre错误
由于没有一步步记录,所以没有具体的代码和命令,就写一下过程,具体脑补一下吧~ wget下载 nginx 1.12.0后 tar解压 ./configure的时候,pcre出错 1. 按照网上的说法, ...
- 数据结构--图 的JAVA实现(上)
1,摘要: 本系列文章主要学习如何使用JAVA语言以邻接表的方式实现了数据结构---图(Graph),这是第一篇文章,学习如何用JAVA来表示图的顶点.从数据的表示方法来说,有二种表示图的方式:一种是 ...
- Docker 搭建 Tomcat + Mysql
Docker 搭建 Tomcat + Mysql 准备 虚拟机 虚拟机安装Docker 在纯净的Centos镜像上搭建 Centos镜像准备 虚拟机上拉取 Centos 镜像: docker pull ...
- 聊聊微服务熔断降级Hystrix
在现在的微服务使用的过程中,经常会遇到依赖的服务不可用,那么如果依赖的服务不可用的话,会导致把自己的服务也会拖死,那么就产生了熔断,熔断顾名思义就是当服务处于不可用的时候采取半开关的状态,达到一定数量 ...
- JavaScript中值类型与引用类型
JavaScript中的变量类型有哪些? 值类型:字符串(string).数值(number).布尔值(boolean).null.undefined 引用类型:对象(Object).数组(Array ...
- 利用nginx,腾讯云免费证书制作https
之前一直在研究,https怎么弄.最近看到了腾讯云提供的免费得ssl证书,寻思把网站弄成https. 首先先去腾讯云购买一个免费得证书. 点击后填写内容, 然后下载证书 解压证书就可以看到,提供四种方 ...