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中,程式是使用訊息機制來實現操作響應的,這個是訊息對映表的程式碼:
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軟體程式的逆向分析,前輩們已經開發了一些非常好用的小工具,我們可以直接使用它們。例如:
-
xspy
-
ResourceHacker
-
彗星小助手
其中我們主要用的是xspy,mfc分析利器如下圖所示
逆向實驗-以CTF賽題為例講解
demo1 - MFC初探
開啟程式軟體
程式的標題Flag就在控制元件中,然後介面內容是讓我們找一個key。很明顯,我們需要找到兩個東西
-
標題找Flag(也就是找視窗控制代碼)
-
內容找key
根據這些內容,告訴我們我們去找控制元件,然後這時候就要掏出xspy了。不然的話,我們如果使用老一套經典分析流程,die+ida對用架構分析,會發生下面這樣的事。首先die查個架構,查個殼
好傢伙,VMP殼,PE32ida走起,如下圖,emmm....
這樣的話,我們很難繼續往下分析,所以我們使用xspy分析。使用方法如下圖
首先我們找到了Flag_enc(944c8d100f82f0c18b682f63e4dbaa207a2f1e72581c2f1b)我們知道特定的,視窗控制代碼叫 HWND
然後我們可以發現一條特殊的onMsgOnMsg:0464,func= 0x00402170(MFC1.exe+ 0x002170 )
為什麼特殊呢,因為只有它並不是以宏的形式出現,應該是作者自定義的訊息,沒有button等東西,所以程式怎麼點選都無法觸發任何效果;並且傳入一個特殊數字0464,來觸發效果。
那麼,我們需要去傳送這條訊息來出發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}
最後DES解密即可
flag{thIs_Is_real_kEy_hahaaa}
【----幫助網安學習,以下所有學習資料免費領!加vx:dctintin,備註 “部落格園” 獲取!】
① 網安學習成長路徑思維導圖
② 60+網安經典常用工具包
③ 100+SRC漏洞分析報告
④ 150+網安攻防實戰技術電子書
⑤ 最權威CISSP 認證考試指南+題庫
⑥ 超1800頁CTF實戰技巧手冊
⑦ 最新網安大廠面試題合集(含答案)
⑧ APP客戶端安全檢測指南(安卓+IOS)
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指令碼自動去花
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")
加密
去除花指令,簡單審計發現是對程式進行RC4加密,最後還對輸入進行了個倒敘
去花後,整理一下,程式碼如下
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
key-->qwertyuiop
cyberchef 得解
flag{973387a11fa3f724d74802857d3e052f}
更多網安技能的線上實操練習,請點選這裡>>