MFC程序原理与逆向

MFC静态分析技巧,以Emotet等一些使用MFC框架木马和其他利用MFC框架木马为例。方便快速定位恶意代码入口技巧。

预备知识

关于消息和消息队列 - Win32 apps | Microsoft Learn
系统维护了一个大的消息队列,由驱动讲鼠标、键盘等硬件交互设备产生的信号包装成一个个数据结构,也就是常说的消息。系统维护了一个大的公共消息队列,每当键盘鼠标产生一个硬件消息后都会讲消息塞到这个大的消息队列中。然后等待各个UI程序的每个窗口线程,从这个大消息队列中取出自己窗口可以处理的消息后拿到自己的窗口回调消息队列里处理。

在开发早期没有各种UI框架的情况下,需要开发者自己注册窗口并设置窗口回调来处理自己想要的消息函数。后来微软推出MFC UI框架,可以使开发者高效的拖拽UI控件,为繁琐的UI代码设计节约了宝贵时间。但却为逆向工作者定位用户代码添加了负担。

MFC逆向基础概念

MFC的消息机制本质是将SDK中的消息与指定窗口ID的控件进行绑定。这种绑定关系由一种全局消息映射表(AFX_MSGMAP)管理存储。该映射表一般存储在.rdata资源节中,所以查找该表应该优先过滤其他节的干扰。

pBaseMessageMap参数存放GetMessageMap函数指针

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)
}

AFX_MSGMAP结构中第一个字段是一个函数指针我们一般并不关注,第二个是我们关注的消息绑定项结构体数组指针。nMessage是窗口消息、nCode是消息附加信息、nID是窗口ID、pfn就是该控件用于处理指定类型消息的回调函数。

两个结构体在IDA内存布局如下,我们在静态逆向的过程就是找到对应的消息绑定结构然后直接定位到用户代码。这里偷懒直接借用Freebuf上其他师傅画的图

image.png

IDA逆向查找用户函数

可以优先查看ResourceHacker从资源节中获取感兴趣的控件ID。

进入IDA使用比较明显的特征码搜索(ALT+B),我这里搜索的是1000的控件ID

过滤掉.text节相关内容

选择过滤设置

排除.text节

可以看到只有两个

点击任意一个向上滑动可以看到窗口类的虚表函数,因为MFC的消息映射绑定默认是跟在虚表后的。

观察虚表函数后的结构体,并手动将后续都修改为dword类型如下图所示

对比该图内存排布一致,则后续都应该为消息绑定的数组,数组最后以全0数据结尾。又由于GetMessageMap函数是pBaseMessageMap字段存储的值所以我们能很轻易的确定AFX_MSGMAP结构为

可以看到freebuf文章的结构布局与IDA实际的布局不同,因为在IDA中有一段dword 0卡在AFX_MSGMAP和AFX_MSGMAP_ENTRY结构中间。该图存在误导


该窗口绑定的所有消息都在此处

最后以全0结构为结尾

我们可以设置结构体便于我们静态分析,右键范围内变量创建结构体

创建原始结构

修改后如下图

点击第一个AFX_MESSAGE_ENTRY按SHIFT+*快捷键,选择结构体数组有7个元素,可以看到CDialog的消息映射关系

同时我们发现下方出现了用户定义主窗口的虚表

向下继续找发现一串0,证明该用户定义主窗口没有消息绑定

由上面总结,如果我们想通过IDA直接找到消息绑定数组,只需要搜索GetThisMessage函数即可

然后根据交叉引用即可定位所有窗口消息绑定

同样的方法确定消息映射,可以发现此时两个结构体之间没有0填充,所以内存布局需要具体实例分析才能确定

由此构建消息映射,可以看到该窗口是系统的help窗口,resourcehacker没有显示出该窗口的ID值。该ID可能是系统分配窗口

我们来到第一个构造的窗口绑定消息数组,可以找到想要的窗口按钮对应的消息处理函数

进入函数内部为用户态代码

观察完整的rdata布局

可以最先看到动态初始化,我们用户定义的主窗口构造函数就是由该处调用

从CWinApp对象绑定的消息函数开始

然后紧接着是自定义的主窗口类对象

最后一个继承的是CWinApp

从源码也可能看到继承了CWinApp,所以逆向时继承CWinApp就是我们的用户可操控的代码。也就是主窗口类的虚表

这种InitInstance函数就是我们可控的窗口初始化代码,固定在CWinApp:Run上方,恶意软件可以藏在该窗口绑定逻辑类的初始化函数中

我们还需要查看窗口逻辑类的InitInstance函数,该函数调用晚于构造函数,快于窗口初始化函数

同时也可以绑定在窗口类的显示绘制模块钱CreateDialogIndirectParamA

还可以通过IDA识别DoModual函数,该函数下方就是对该类窗口初始化的函数。InInitDialog函数是主窗口回调,通过CreateIndirect调用

Emote样本对比

点进去初始化函数如下,可以看到明显的恶意行为

样本2

搜索CWinApp::Run函数

找到run的上一个函数就是初始化窗口函数

可以看到初始化加载恶意的资源

  • 发表于 2025-01-07 09:00:02
  • 阅读 ( 2700 )
  • 分类:二进制

3 条评论

绿冰壶
大佬可以求个样本链接么
只记得是21年的Emote MFC样本,忘了是Freebuf还是安全客看到的
请先 登录 后评论
WMBa0
我赌作者是Windows病毒分析工作
请先 登录 后评论
请先 登录 后评论
马喽打金服
马喽打金服

3 篇文章

站长统计