MFC框架軟體逆向研究

蚁景网安实验室發表於2024-08-14

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.png

那麼,對於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)
}

其中這個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.png

逆向實驗-以CTF賽題為例講解

demo1 - MFC初探

開啟程式軟體

image.png

程式的標題Flag就在控制元件中,然後介面內容是讓我們找一個key。很明顯,我們需要找到兩個東西

  1. 標題找Flag(也就是找視窗控制代碼)

  2. 內容找key

根據這些內容,告訴我們我們去找控制元件,然後這時候就要掏出xspy了。不然的話,我們如果使用老一套經典分析流程,die+ida對用架構分析,會發生下面這樣的事。首先die查個架構,查個殼

image.png

好傢伙,VMP殼,PE32ida走起,如下圖,emmm....

image.png

這樣的話,我們很難繼續往下分析,所以我們使用xspy分析。使用方法如下圖

image.png

首先我們找到了Flag_enc(944c8d100f82f0c18b682f63e4dbaa207a2f1e72581c2f1b)我們知道特定的,視窗控制代碼叫 HWND

image.png

然後我們可以發現一條特殊的onMsgOnMsg:0464,func= 0x00402170(MFC1.exe+ 0x002170 )為什麼特殊呢,因為只有它並不是以宏的形式出現,應該是作者自定義的訊息,沒有button等東西,所以程式怎麼點選都無法觸發任何效果;並且傳入一個特殊數字0464,來觸發效果。

image.png

那麼,我們需要去傳送這條訊息來出發func函式以獲取我們需要的key

#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.png

最後DES解密即可

image.png

flag{thIs_Is_real_kEy_hahaaa}

【----幫助網安學習,以下所有學習資料免費領!加vx:dctintin,備註 “部落格園” 獲取!】

 ① 網安學習成長路徑思維導圖
 ② 60+網安經典常用工具包
 ③ 100+SRC漏洞分析報告
 ④ 150+網安攻防實戰技術電子書
 ⑤ 最權威CISSP 認證考試指南+題庫
 ⑥ 超1800頁CTF實戰技巧手冊
 ⑦ 最新網安大廠面試題合集(含答案)
 ⑧ APP客戶端安全檢測指南(安卓+IOS)

Junk_instruction-西湖論劍

下面,再講解一道大型比賽的賽題來實驗開啟,看到這個樸素的介面可以鑑定是MFC框架。

image.png

我們看到了一個input,還有一個check button,很明顯,我們首先就需要去找check button的id&註冊函式。

xspy-MFC分析

image.png

check按鈕的id為03e9,同時視窗存在OnCommand: notifycode=0000 id=03e9,func= 0x00C72420(Junk_Instruction.exe+ 0x002420 )函式。那麼對應的check邏輯肯定在基址+偏移0x002420處。開啟ida,找到check函式 sub_402420 ,如下圖

image.png

可以看到有一個條件判斷:if ( (unsigned __int8)sub_402600(v2 + 16) )。一眼頂針,兩個分支分別是彈出正確和錯誤的對話方塊,為什麼呢?if else函式體內容基本一樣。當然我們還是動態除錯一下

image.png

所以enc函式很明顯就是sub_402600這個函式中就出現了很多垃圾指令了,也就對應上題目名稱Junk_instruction了。

去花-IDA分析

爆紅

image.png

花指令,經典call $+5起手,就是先用一個call壓好返回地址,再把棧裡的返回地址彈出來,改一下,壓回去,如此反覆。去掉也很簡單,我們把下述累死指令塊全部nop掉即可,有好幾處,一模一樣。

image.png

當然,我們使用idapython指令碼自動去花

    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.png

加密

去除花指令,簡單審計發現是對程式進行RC4加密,最後還對輸入進行了個倒敘

image.png

image.png

image.png

去花後,整理一下,程式碼如下

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.png

key-->qwertyuiop

cyberchef 得解

image.png

flag{973387a11fa3f724d74802857d3e052f}

image.png

更多網安技能的線上實操練習,請點選這裡>>

相關文章