MFC逆向
MFC逆向
MFC框架
CObject → CCmdTarget → CWnd → CFrameWnd(框架窗口)
→ CDialog(对话框)
→ CButton(按钮控件)
→ CEdit(输入框控件)
→ CListCtrl(列表控件)
→ ...(其他控件类)
CCmdTarget → CWinThread(线程类)
CCmdTarget → CDocument(文档类)→ CView(视图类)
其中CObject是核心的根类。
MFC程序的运行过程分为以下四步:
利用全局应用程序对象theApp启动应用程序。
调用全局应用程序对象的构造函数,从而调用基类(CWinApp)的构造函数,完成应用程序的一些初始化工作,并将应用程序对象的指针保存起来。
进入WinMain函数。在AfxWinMain函数中获取子类的指针,利用指针实现上述的三个函数,从而完成窗口的创建注册等工作。
进入消息循环,一直到WM_QUIT。
MFC的消息映射表
在mfc中,程序是使用消息机制来实现操作响应的,这个是消息映射表的代码:
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) }
补充一些适合MFC逆向的工具

MFC逆向步骤
下面来一道例题

思路一(常规快捷思路)
使用die查看后发现基址

再使用xspy查看其核心“确定”处的id&注册函数

结果没有发现按钮的id和偏移量,思考后:“找不到ID”是正常的:因为 xspy 当前显示的是窗口基础类的消息映射,而控件ID相关的 WM_COMMAND消息处理在派生类中。(这个是问AI的答案,具体正确的情况后续分析)
思路二(更加万能)
1.在IDA中寻找函数声明段,在rdata段处,对一堆函数(包括虚函数进行了声明)

发现在函数声明处,出现了一个标识,明显此处有问题,点进去分析。

此处有push等关键字词,明显是一个函数,并且ida爆红,可以判断出此处有花。
所以在之前的xspy分析时,因为在“确定”按钮处的id存在花指令,所以xspy无法分析出来,再重新看xspy处的分析

可以看到,在一堆虚函数之间,存在了一个onOK的函数,也刚好指向了该处00401640。
花指令
对这段IDA明显标注的一段汇编代码进行分析(地址00401678-00401863),发现以下
| 地址范围 | 花指令类型 | 干扰方式 |
|---|---|---|
| 00401678-0040167C | 互斥条件跳转 + 无效调用 | JO/JNO互斥跳转 + 非法地址call,破坏反汇编对齐 |
| 00401761-00401765 | 重复互斥跳转 + 无效调用 | 同上述逻辑,二次干扰 |
| 004017A9-004017AD | 三次互斥跳转 + 无效调用 | 强化干扰,混淆控制流 |
| 0040176A | 无意义lock前缀指令 | lock push 0无业务意义,仅增加静态分析复杂度 |
| 00401765/17AD | 非法远调用 | call near ptr 8BA45D0Ch等地址超出程序镜像范围,纯干扰性无效指令 |
| 对这些地址进行Nop之后,再重新反汇编得到主函数 |
主函数解密

发现主函数中很多变量使用了sub_4011D0函数进行初始化,点进这个函数

可以发现sub_4011D0 是封装了 MFC 的标准 Base64 解码函数,
再结合主函数sub_401640使用了sub_4015B0的部分,
`CString *__cdecl sub_4015B0(CString *a1, int a2)
{
int v2; // eax
int i; // esi
v2 = a2;
for ( i = 0; i < *(_DWORD *)(a2 - 8); ++i )
{
CString::SetAt((CString *)&a2, i, *(_BYTE *)(i + v2) + 1);
v2 = a2;
}
CString::CString(a1, (const struct CString *)&a2);
CString::~CString((CString *)&a2);
return a1;
}`
可以看到该函数的功能是这个函数的功能是对输入字符串的每个字符 ASCII 值 +1。
总逻辑梳理
sub_401640(主函数) ↓ 初始化种子串(Base64格式) sub_4011D0(Base64解码)→ 输出:解码后的ASCII字符串 ↓ 传入解码结果 sub_4015B0(字符+1)→ 输出:目标验证字符串 ↓ 回到sub_401640 对比用户输入 ↔ 目标验证字符串 → 弹窗提示匹配结果
其中初始化的种子串其实就是主函数中“ aR05wteV6r7ozfa ”的实际值,如下图

点进“ aR05wteV6r7ozfa”,看到它的值

总结:正确输入 = (Base64 解码 R05wteV6r7ozfa4zwsJj2RnZA==)的每个字符 ASCII 值 +1
解密
但是其实通过base64解码发现该题目并非正常的base64编码,因为运行脚本后会出现下面错误

猜测这是一个修改后的table
动态调试运行看
在比较函数0x00401743处下断点,动态运行到此处,观察eax的值。

发现输入会和这个东西做比较。
这个就是正确的输入。
最后解码后发现正确输入是HOWMP半块西瓜皮hehe
输入后得到正确flag
flag{881baed760e0fb03d47xeafdf616f322}
总结
这是一道套着MFC框架的动调题目,总体来说不是很难,但是可以给我们一个学习MFC逆向的机会,熟悉常见的对MFC逆向的操作。
参考链接
https://writeup.ctfhub.com/Challenge/2016/西普杯京津冀信息安全挑战赛/Reverse/oSmDDhDzg3Dh7YWazqnFoj.html

浙公网安备 33010602011771号