MFC框架简介 什么是mfc?
MFC库是开发Windows应用程序的C++接口。MFC提供了面向对象的框架,采用面向对象技术,将大部分的Windows API 封装到C++类中,以类成员函数的形式提供给程序开发人员调用。
 
简单来说,MFC是一种面向对象,用于开发windows应用程序的框架,突出特点是封装了大部分windows API,便于开发人员使用(写win挂方便)。
MFC程序的运行过程分为以下四步:
利用全局应用程序对象theApp启动应用程序。 
调用全局应用程序对象的构造函数,从而调用基类(CWinApp)的构造函数,完成应用程序的一些初始化工作,并将应用程序对象的指针保存起来。 
进入WinMain函数。在AfxWinMain函数中获取子类的指针,利用指针实现上述的三个函数,从而完成窗口的创建注册等工作。 
进入消息循环,一直到WM_QUIT。 
 
那么问题来了,我们如何逆向mfc程序呢? 因为其封装了大部分windows API,逆向起来也复杂了不少,因为需要了解大量的windows api 并且熟悉windows编程。 下面进行讲解。
MFC如何逆向 如下图,是MFC框架软件的基本界面,可以看到,就是一堆button,主要逆向也是check button。 那么,对于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;         UINT nCode             UINT nID;              UINT nLastID;          UINT nSig;             AFX_PMSG pfn;      } 
 
其中这个AFX_MSGMAP_ENTRY 中的最后一个成员AFX_PMSG 就是一个函数指针,指向了当前控件绑定的函数。同时,这个nID成员描述的是当前控件的ID,利用这个ID就能确定我们所寻找的控件。然后这个AFX_MSGMAP结构体则会记录一个指向AFX_MSGMAP_ENTRY的指针,于是查找控件的注册函数的思路可以缩小为:
找到AFX_MSGMAP 
找到控件的ID — 关键就是找ID 
 
那么,我们又该怎么找到控件ID呢,俗话说“工欲善其事,必先利其器 ”,作为逆向分析人员,肯定要选择好分析的工具了,很庆幸,我们站在巨人的肩膀上,针对mfc软件程序的逆向分析,前辈们已经开发了一些非常好用的小工具,我们可以直接使用它们。 例如:
xspy 
ResourceHacker 
彗星小助手 
 
其中我们主要用的是xspy,mfc分析利器 如下图所示
逆向实验-以CTF赛题为例讲解 demo1 - MFC初探 打开程序软件 程序的标题Flag就在控件中 ,然后界面内容是让我们找一个key。 很明显,我们需要找到两个东西
标题找Flag(也就是找窗口句柄) 
内容找key 
 
根据这些内容,告诉我们我们去找控件,然后这时候就要掏出 xspy了。 不然的话,我们如果使用老一套经典分析流程,die+ida对用架构分析,会发生下面这样的事。 首先die查个架构,查个壳 好家伙,VMP壳,PE32 ida走起,如下图,emmm…. 这样的话,我们很难继续往下分析,所以我们使用xspy分析。 使用方法如下图 首先我们找到了Flag_enc (944c8d100f82f0c18b682f63e4dbaa207a2f1e72581c2f1b) 我们知道特定的,窗口句柄叫 HWND
然后我们可以发现一条特殊的onMsgOnMsg:0464,func= 0x00402170(MFC1.exe+ 0x002170 ) 为什么特殊呢,因为只有它并不是以宏的形式出现,应该是作者自定义的消息,没有button等东西,所以程序怎么点击都无法触发任何效果;并且传入一个特殊数字0464,来触发效果。 那么,我们需要去发送这条消息来出发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} 最后DES解密即可 flag{thIs_Is_real_kEy_hahaaa}
Junk_instruction-西湖论剑 下面,再讲解一道大型比赛的赛题来实验 打开,看到这个朴素的界面可以鉴定是MFC框架。 我们看到了一个input,还有一个check button,很明显,我们首先就需要去找check button的id&注册函数。
xspy-MFC分析  check按钮的id为03e9,同时窗口存在OnCommand: notifycode=0000 id=03e9,func= 0x00C72420(Junk_Instruction.exe+ 0x002420 )函数。 那么对应的check逻辑肯定在基址+偏移0x002420处。 打开ida,找到check函数  sub_402420 ,如下图 可以看到有一个条件判断:if ( (unsigned __int8)sub_402600(v2 + 16) )。一眼顶针, 两个分支分别是弹出正确和错误的对话框,为什么呢?if else函数体内容基本一样。 当然我们还是动态调试一下 所以enc函数很明显就是sub_402600 这个函数中就出现了很多垃圾指令了,也就对应上题目名称Junk_instruction了。
去花-IDA分析 爆红
花指令,经典call    $+5起手,就是先用一个call压好返回地址,再把栈里的返回地址弹出来,改一下,压回去,如此反复。 去掉也很简单,我们把下述累死指令块全部nop掉即可,有好几处,一模一样。 当然,我们使用idapython脚本自动去花
1 2 3 4 5 6 7 8 9 10 11 12 13 14 from  ida_bytes import  get_bytes, patch_bytesimport  readdr = 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" )
 
加密 去除花指令,简单审计 发现是对程序进行RC4加密,最后还对输入进行了个倒叙 去花后,整理一下,代码如下
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;    void  *v3;    char  v5[511 ];    int  v6;    char  *v7;    int  v8;    size_t  Count;    int  v10;    size_t  v11;    char  *v12;    char  *v13;    int  v14;    char  v15[4 ];    char  *Source;    void  *v17;    char  cipher[32 ];    const  char  *v19;    char  *v20;    int  i;    char  *p_Destination;    char  v23;    char  v24;    char  v25;    char  v26[28 ];    char  v27[256 ];    char  key[256 ];    char  Destination;    char  v30[39 ];    int  v31;    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" );                       memset (&key[11 ], 0 , 0xF5 u);     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 ]);          p_Destination = &Destination;     v12 = v30;     p_Destination += strlen (p_Destination);     v8 = ++p_Destination - v30;     RC4_crypt((int )v27, (int )&Destination, p_Destination - v30);     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 key–>qwertyuiop cyberchef 得解 flag{973387a11fa3f724d74802857d3e052f}