MFC框架简介

什么是mfc?

MFC库是开发Windows应用程序的C++接口。MFC提供了面向对象的框架,采用面向对象技术,将大部分的Windows API 封装到C++类中,以类成员函数的形式提供给程序开发人员调用。

简单来说,MFC是一种面向对象,用于开发windows应用程序的框架,突出特点是封装了大部分windows API,便于开发人员使用(写win挂方便)。

MFC程序的运行过程分为以下四步:

  1. 利用全局应用程序对象theApp启动应用程序。
  2. 调用全局应用程序对象的构造函数,从而调用基类(CWinApp)的构造函数,完成应用程序的一些初始化工作,并将应用程序对象的指针保存起来。
  3. 进入WinMain函数。在AfxWinMain函数中获取子类的指针,利用指针实现上述的三个函数,从而完成窗口的创建注册等工作。
  4. 进入消息循环,一直到WM_QUIT。

那么问题来了,我们如何逆向mfc程序呢?
因为其封装了大部分windows API,逆向起来也复杂了不少,因为需要了解大量的windows api 并且熟悉windows编程。
下面进行讲解。

MFC如何逆向

如下图,是MFC框架软件的基本界面,可以看到,就是一堆button,主要逆向也是check button。
image-20240902115322285
那么,对于MFC逆向,我们主要需要知道的是,当我们执行某个操作(点击某个按钮)的时候,程序会执行什么处理函数。
在mfc中,程序是使用消息机制来实现操作响应的,这个是消息映射表的代码:

1
2
3
4
5
6
7
8
9
10
11
12
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_ENTRY中的最后一个成员AFX_PMSG就是一个函数指针,指向了当前控件绑定的函数。同时,这个nID成员描述的是当前控件的ID,利用这个ID就能确定我们所寻找的控件。然后这个AFX_MSGMAP结构体则会记录一个指向AFX_MSGMAP_ENTRY的指针,于是查找控件的注册函数的思路可以缩小为:

  • 找到AFX_MSGMAP
  • 找到控件的ID — 关键就是找ID

那么,我们又该怎么找到控件ID呢,俗话说“工欲善其事,必先利其器”,作为逆向分析人员,肯定要选择好分析的工具了,很庆幸,我们站在巨人的肩膀上,针对mfc软件程序的逆向分析,前辈们已经开发了一些非常好用的小工具,我们可以直接使用它们。
例如:

  1. xspy
  2. ResourceHacker
  3. 彗星小助手

其中我们主要用的是xspy,mfc分析利器
如下图所示
image-20240902115336471

逆向实验-以CTF赛题为例讲解

demo1 - MFC初探

打开程序软件
image-20240902115344529
程序的标题Flag就在控件中,然后界面内容是让我们找一个key。
很明显,我们需要找到两个东西

  1. 标题找Flag(也就是找窗口句柄)
  2. 内容找key

根据这些内容,告诉我们我们去找控件,然后这时候就要掏出
xspy了。
不然的话,我们如果使用老一套经典分析流程,die+ida对用架构分析,会发生下面这样的事。
首先die查个架构,查个壳
image-20240902115350509
好家伙,VMP壳,PE32
ida走起,如下图,emmm….
image-20240902115355061
这样的话,我们很难继续往下分析,所以我们使用xspy分析。
使用方法如下图
image-20240902115401071
首先我们找到了Flag_enc
(944c8d100f82f0c18b682f63e4dbaa207a2f1e72581c2f1b)
我们知道特定的,窗口句柄叫 HWND
image-20240902115406110

然后我们可以发现一条特殊的onMsg
OnMsg:0464,func= 0x00402170(MFC1.exe+ 0x002170 )
为什么特殊呢,因为只有它并不是以宏的形式出现,应该是作者自定义的消息,没有button等东西,所以程序怎么点击都无法触发任何效果;并且传入一个特殊数字0464,来触发效果。
image-20240902115411170
那么,我们需要去发送这条消息来出发func函数以获取我们需要的key

1
2
3
4
5
6
7
8
9
10
11
12
13
#include<Windows.h>
#include<stdio.h>
int main()
{
HWND h = FindWindowA(NULL, "Flag就在控件里");
if (h)
{
SendMessage(h, 0x0464, 0, 0);
printf("success");
}
else printf("failure");
}

使用 API FindWindow 获取窗口句柄,SendMessage发送消息,得到了key
{I am a Des key}
image-20240902115416928
最后DES解密即可
image.png
flag{thIs_Is_real_kEy_hahaaa}

Junk_instruction-西湖论剑

下面,再讲解一道大型比赛的赛题来实验
打开,看到这个朴素的界面可以鉴定是MFC框架。
image-20240902115423041
我们看到了一个input,还有一个check button,很明显,我们首先就需要去找check button的id&注册函数。

xspy-MFC分析

image-20240902115427974
check按钮的id为03e9,同时窗口存在OnCommand: notifycode=0000 id=03e9,func= 0x00C72420(Junk_Instruction.exe+ 0x002420 )函数。
那么对应的check逻辑肯定在基址+偏移0x002420处。
打开ida,找到check函数 sub_402420 ,如下图
image-20240902115432343
可以看到有一个条件判断:if ( (unsigned __int8)sub_402600(v2 + 16) )。一眼顶针,
两个分支分别是弹出正确和错误的对话框,为什么呢?if else函数体内容基本一样。
当然我们还是动态调试一下
image-20240902115436534
所以enc函数很明显就是sub_402600
这个函数中就出现了很多垃圾指令了,也就对应上题目名称Junk_instruction了。

去花-IDA分析

爆红
image-20240902115440694

花指令,经典call $+5起手,就是先用一个call压好返回地址,再把栈里的返回地址弹出来,改一下,压回去,如此反复。
去掉也很简单,我们把下述累死指令块全部nop掉即可,有好几处,一模一样。
image-20240902115444505
当然,我们使用idapython脚本自动去花

1
2
3
4
5
6
7
8
9
10
11
12
13
14
from ida_bytes import get_bytes, patch_bytes
import re
addr = 0x402600
end = 0x402fe3
buf = get_bytes(addr, end-addr)
def nopp(s):
s = s.group(0)
print("".join(["%02x"%i for i in s]))
s = b"\x90"*len(s)
return s
pattern = b"\xe8\x00\x00\x00\x00\x58\x89.*?\xc3.*?\x22"
buf = re.sub(pattern , nopp, buf, flags=re.I)
patch_bytes(addr, buf)
print("Done")

image-20240902115452273

加密

去除花指令,简单审计
发现是对程序进行RC4加密,最后还对输入进行了个倒叙
image-20240902115457550
image-20240902115502026image.png
去花后,整理一下,代码如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
char __thiscall sub_402600(void *this, int a2)
{
const WCHAR *v2; // eax
void *v3; // eax
char v5[511]; // [esp+9h] [ebp-4BBh] BYREF
int v6; // [esp+208h] [ebp-2BCh]
char *v7; // [esp+20Ch] [ebp-2B8h]
int v8; // [esp+210h] [ebp-2B4h]
size_t Count; // [esp+214h] [ebp-2B0h]
int v10; // [esp+218h] [ebp-2ACh]
size_t v11; // [esp+21Ch] [ebp-2A8h]
char *v12; // [esp+220h] [ebp-2A4h]
char *v13; // [esp+224h] [ebp-2A0h]
int v14; // [esp+228h] [ebp-29Ch]
char v15[4]; // [esp+22Ch] [ebp-298h] BYREF
char *Source; // [esp+230h] [ebp-294h]
void *v17; // [esp+234h] [ebp-290h]
char cipher[32]; // [esp+238h] [ebp-28Ch]
const char *v19; // [esp+258h] [ebp-26Ch]
char *v20; // [esp+25Ch] [ebp-268h]
int i; // [esp+260h] [ebp-264h]
char *p_Destination; // [esp+264h] [ebp-260h]
char v23; // [esp+26Dh] [ebp-257h]
char v24; // [esp+26Eh] [ebp-256h]
char v25; // [esp+26Fh] [ebp-255h]
char v26[28]; // [esp+270h] [ebp-254h] BYREF
char v27[256]; // [esp+28Ch] [ebp-238h] BYREF
char key[256]; // [esp+38Ch] [ebp-138h] BYREF
char Destination; // [esp+48Ch] [ebp-38h] BYREF
char v30[39]; // [esp+48Dh] [ebp-37h] BYREF
int v31; // [esp+4C0h] [ebp-4h]

v17 = this;
v31 = 3;
cipher[0] = 91;
cipher[1] = -42;
cipher[2] = -48;
cipher[3] = 38;
cipher[4] = -56;
cipher[5] = -35;
cipher[6] = 25;
cipher[7] = 126;
cipher[8] = 110;
cipher[9] = 62;
cipher[10] = -53;
cipher[11] = 22;
cipher[12] = -111;
cipher[13] = 125;
cipher[14] = -1;
cipher[15] = -81;
cipher[16] = -35;
cipher[17] = 118;
cipher[18] = 100;
cipher[19] = -80;
cipher[20] = -9;
cipher[21] = -27;
cipher[22] = -119;
cipher[23] = 87;
cipher[24] = -126;
cipher[25] = -97;
cipher[26] = 12;
cipher[27] = 0;
cipher[28] = -98;
cipher[29] = -48;
cipher[30] = 69;
cipher[31] = -6;
v2 = (const WCHAR *)sub_401570(&a2);
v14 = sub_4030A0(v2);
v10 = v14;
v3 = (void *)sub_401570(v14);
sub_403000(v3);
sub_4012A0(v15);
Source = (char *)unknown_libname_1(v26);
v20 = Source;
v13 = Source + 1;
v20 += strlen(v20);
v11 = ++v20 - (Source + 1);
Count = v11;
Destination = 0;
memset(v30, 0, sizeof(v30));
strncpy(&Destination, Source, v11);
if ( sub_402AF0(&Destination) )
{
v23 = 0;
v25 = 0;
LABEL_7:
v24 = v25;
}
else
{
strcpy(key, "qwertyuiop"); // key
memset(&key[11], 0, 0xF5u);
memset(v27, 0, sizeof(v27));
memset(v5, 0, sizeof(v5));
v19 = key;
v7 = &key[1];
v19 += strlen(v19);
v6 = ++v19 - &key[1];
RC4_init((int)v27, key, v19 - &key[1]); // RC4_init
p_Destination = &Destination;
v12 = v30;
p_Destination += strlen(p_Destination);
v8 = ++p_Destination - v30;
RC4_crypt((int)v27, (int)&Destination, p_Destination - v30);// RC4_crypto
for ( i = 31; i >= 0; --i )
{
if ( v30[i - 1] != cipher[i] ) // 倒叙
{
v25 = 0;
goto LABEL_7;
}
}
v24 = 1;
}
LOBYTE(v31) = 0;
sub_403060(v26);
v31 = -1;
sub_4012A0(&a2);
return v24;
}

解密

首先提取密文,利用插件Lazy_ida
5BD6D026C8DD197E6E3ECB16917DFFAFDD7664B0F7E58957829F0C009ED045FA
image-20240902115509492
key–>qwertyuiop
cyberchef 得解image-20240902115514831
flag{973387a11fa3f724d74802857d3e052f}
image-20240902115523670