淺談MFC類CrackMe中訊息處理函式查詢方法
*本文作者:kdsj,本文屬 FreeBuf 原創獎勵計劃,未經許可禁止轉載。
最近一個學姐發給我了一份CrackMe希望我解一下,其中涉及到了MFC的訊息函式查詢的問題,就順便以此為例談一下自己使用的訊息函式查詢的方法。本人萌新,如果有任何錯漏與解釋不清的地方,歡迎各路大佬指正。
這個CrackMe是一個典型的MFC型別的程式,其框體如下:
一、目標以及方法
首先我們確認我們的目標是找到兩個”註冊”按鈕的對應訊息處理函式,那麼有什麼手段可以達到我們的目標?在MFC中有一個訊息對映表的概念,參考候老的描述[1],實現程式碼如下:
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) }
其中我們想要的某個控制元件的訊息處理函式,就存放在該結構體的pfn中(其中nID與我們的控制元件ID相同的AFX_MSGMAP_ENTRY中的pfn就是我們所尋找的訊息響應函式)。
而由於AFX_MSGMAP一般只有一張,而且一般不是很大,所以我們只需要找到以下兩個資訊,即可定位訊息響應函式。
我們需要的控制元件ID
AFX_MSGMAP
二、使用ResourceHacker尋找目標按鈕控制元件
ResourceHacker是一個32位與64位的資源編輯器,它既是一個資源編譯器(對於.rc檔案),也是一個反編譯器——支援檢視和編輯可執行檔案中的資源(* .exe;. dll;)以及已編譯的資源庫(.res;.mui)。ResourceHacker既支援GUI模式也支援命令列模式。
在此我們使用ResourceHacker的檢視可執行檔案中的資源的功能。
使用ResourceHacker載入CrackXX.exe,得到如下圖的結果:
我們需要查詢的控制元件ID在對話方塊中,選擇對應的對話方塊,之後點選“註冊”一行(我們需要的控制元件),得到對應的控制元件ID:
用同樣的方法得到兩個註冊控制元件的ID,分別為1002與1005(注意此處控制元件ID是十進位制不是16進位制)。
那麼我們已經完成第一步目標,之後就是尋找AFX_MSGMAP即可。
三、尋找AFX_MSGMAP
查詢可知,我們有兩個思路可以獲取該AFX_MSGMAP。
AFX_MSGMAP存在於.rdata段,而.rdata段一般有RTTI,虛擬函式表與AFX_MSGMAP,所以MSG_MAP資料結構特徵相對容易分辨,可以通過編寫一個指令碼找到。
存在一個GetMessageMap函式,可以獲得AFX_MSGMAP。而一般GetMessageMap在編譯器自動生成的程式碼中會被呼叫,所以我們可以通過查詢GetMessageMap呼叫者來完成對GetMessageMap的定位。
3.1 編寫指令碼查詢
首先我們可以看下上面給出的AFX_MSGMAP的定義,它由一個指向GetMessageMap的函式指標以及一個指向AFX_MSGMAP__ENTYR的指標組成,而往往該指標指向的位置就是緊鄰AFX_MSGMAP的下一個結構(也就是AFX_MSGMAP_ENTRY)。
0044E880 AFX_MSGMAP
pBaseMessageMap=0041AE27 lpEntries=0044E888
0044E888 AFX_MSGMAP_ENTRY1
nMessage nCode nID nLastID nSig pfn
0044E8A0 AFX_MSGMAP_ENTRY2
……
順便值得一提的是pBaseMessageMap指向的地址是GetMessageMap的地址,其彙編程式碼如下
.text:0041AE27 sub_41AE27proc near; DATA XREF: .rdata:off_44E880↓o .text:0041AE27; .rdata:0044EFDC↓o ... .text:0041AE27moveax, offset off_44F120 .text:0041AE2Cretn .text:0041AE2C sub_41AE27endp
顯然GetMessageMap函式是將AFX_MSGMAP的地址靜態生成,所以也證明了我們可以使用MessageMap函式獲取AFX_MSGMAP這一點。
那麼回到正題,我們可以用這樣的判斷邏輯來搜尋AFX_MSGMAP:
搜尋的起始地址從.rdata段的起始地址開始,以4為倍數增加。
起始地址+4儲存的DWORD(AFX_MSGMAP->lpEntries)等於起始地址+8(第一個AFX_MSGMAP_ENTRY)。
根據定義,AFX_MSGMAP__ENTYR以全0結束,可以作為判定結束條件。
在此基礎上(搜尋到結束之前),每個AFX_MSGMAP__ENTRY的pfn元素必須是一個有效地址(因為這個pfn指向對應訊息的處理函式),不包括全0那個結構。
那麼對此我們可以寫出idc指令碼查詢可能的滿足條件的AFX_MSGMAP,idc指令碼如下:
#include <idc.idc> static NotEndAddr(pAddr){ auto i=0; for (i=0;i<6;i++){ if (Dword(i*4+pAddr)!=0) return 1;//not end } return 0;//reach the end } static isMsgMap(checkAddr,startVa,endVa){ auto tmp1=Dword(checkAddr); auto tmp2=Dword(checkAddr+4); auto pAddr=checkAddr+8; if (tmp2==checkAddr+8){ while(NotEndAddr(pAddr)){ if(Dword(pAddr+20)<startVa||Dword(pAddr+20)>endVa){ //Message("Invalid Addr at %0x.\n",pAddr); return 0; } pAddr=pAddr+24; } return 1; } return 0; } static main(){ auto startRdataVa=0x0044E880;//the start addr of .rdata auto size=0x0000DAA8;//the size of .rdata auto startValidVa=0x00400000;//check the addr is valid or not auto endValidVa=0x0046A000; auto i=0; for(i=0;i<size;i=i+4){ if(isMsgMap(i+startRdataVa,startValidVa,endValidVa)){ Message("Found Possible MessageMap at %0x.\n",i+startRdataVa); } } Message("Finish searching.\n"); return 0; }
最終嘗試使用這個指令碼搜尋,發現若干可能地址(測試過多個程式,一般生成的可能地址非常少,可以手動過濾):
Found Possible MessageMap at 44e880. Found Possible MessageMap at 44ee88. Found Possible MessageMap at 44f120. Found Possible MessageMap at 44ff10. Found Possible MessageMap at 451410. Finish searching.
那麼此時我們就可以一個個檢視,依據有:
AFX_MSGMAP–>pBaseMessageMap是GetMessageMap(封裝函式,非常短,只返回AFX_MSGMAP地址);
其中一定有不為0的元素;
其中一定存在你所查詢的控制元件ID(AFX_MSGMAP_ENTRY–>nID),而且是全部ID(在本CrackMe中一定有1002與1005)。
具體也可以根據這三條對指令碼進行優化。若將指令碼用於不同程式,建議修改startRdataVa,size,startValidVa以及endValidVa四項引數。
3.2 通過查詢GetMessageMap來獲得
在3.1節中我們已經證明GetMessageMap的確能獲得AFX_MSGMAP地址,然而找到GetMessageMap的方法是使用AFX_MSGMAP,顯然這本末倒置了。所以現在我們使用查詢GetMessageMap的呼叫函式,之後逆向追溯的辦法。
從網上查詢可得[2],OnWndMsg呼叫了GetMessageMap,OnWndMsg大體邏輯如下:
BOOL CWnd::OnWndMsg(UINT message, WPARAM wParam, LPARAM lParam, LRESULT* pResult) { LRESULT lResult = 0; const AFX_MSGMAP* pMessageMap; //取得訊息對映結構,GetMessageMap為虛擬函式,所以實際取的是CmainFrame的訊息對映 pMessageMap = GetMessageMap(); // 查詢對應的訊息處理函式 for (pMessageMap != NULL; pMessageMap = pMessageMap->pBaseMap) if (message < 0xC000) if ((lpEntry = AfxFindMessageEntry(pMessageMap->lpEntries, message, 0, 0)) != NULL) goto LDispatch; ... ... LDispatch: //通過聯合來匹配正確的函式指標型別 union MessageMapFunctions mmf; mmf.pfn = lpEntry->pfn; ……
所以為了獲取GetMessageMap我們需要先獲取Cwnd::OnWndMsg,這個函式在IDA中同樣沒有被識別,所以我們需要找到它的呼叫函式。同樣,我們在網上找到了類似的實現:
LRESULT CWnd::WindowProc(UINT message, WPARAM wParam, LPARAM lParam) { LRESULT lResult = 0; if (!OnWndMsg(message, wParam, lParam, &lResult)) lResult = DefWindowProc(message, wParam, lParam); return lResult; }
這次我們在IDA中找到了CWnd::WindowProc的識別,HexRay輸出大致如下:
int __thiscall CWnd::WindowProc(CWnd *this, unsigned int a2, unsigned int a3, int a4) { CWnd *v4; // esi int v6; // [esp+4h] [ebp-4h] v6 = 0; v4 = this; if ( !(*(int (__thiscall **)(CWnd *, unsigned int, unsigned int, int, int *))(*(_DWORD *)this + 276))( this, a2, a3, a4, &v6) ) v6 = (*(int (__thiscall **)(CWnd *, unsigned int, unsigned int, int))(*(_DWORD *)v4 + 280))(v4, a2, a3, a4); return v6; }
那麼顯然第一個if中巢狀的呼叫就是Cwnd::OnWndMsg,由於沒有識別出來,我們需要用OD動態跟蹤一下,在該處下斷點,之後F9執行,執行結果如下:
顯然得到Cwnd::OnWndMsg的地址是0042259F,IDA查詢發現是一個被隱藏的函式unknown_libname_93,進入後F5檢視HexRay結果。
v5 = this; v62 = 0; v61 = 0x7FFFFFFF; v63 = 0; if ( a2 != 273 ) { if ( a2 != 78 ) { v7 = (unsigned int)a4; if ( a2 == 6 ) { v8 = CWnd::FromHandle(a4); _AfxHandleActivate(v5, (WPARAM)a3, v8); } if ( a2 == 32 && _AfxHandleSetCursor(v5, (signed __int16)a4, (unsigned int)a4 >> 16) ) goto LABEL_3; v9 = *((_DWORD *)v5 + 19); if ( v9 && *(_DWORD *)(v9 + 116) > 0 && ((unsigned int)a2 >= 0x200 && (unsigned int)a2 <= 0x209 || (unsigned int)a2 >= 0x100 && (unsigned int)a2 <= 0x10F || (unsigned int)(a2 - 641) <= 0x10) && (*(int (__stdcall **)(int, HDC, HWND, int *))(**((_DWORD **)v5 + 19) + 148))(a2, a3, a4, &v62) ) { goto LABEL_117; } ......
發現函式較大,結構有些混亂,靜態分析不好識別,那麼用OD進入分析。顯然由網上原始碼邏輯看得出,第一個呼叫的應該是GetMessageMap,然後OD一步步跟,發現第一個call顯然不是:
.text:0042259F ; __unwind { // loc_44C480 .text:0042259F push 70h .text:004225A1 mov eax, offset loc_44C480 .text:004225A6 call __EH_prolog3 .text:004225AB mov edi, ecx .text:004225AD xor eax, eax
這個EH_prolog3猜測是編譯器加的異常處理,繼續跟,下一個call出現在0x4226A1處:
.text:0042269Dmoveax, [edi] .text:0042269Fmovecx, edi .text:004226A1calldword ptr [eax+28h] .text:004226A4movebx, eax .text:004226A6xorebx, [ebp+arg_0] .text:004226A9push7; int
那麼此處顯然就是我們尋找的GetMessageMap了,那麼我們跟入就可以成功找到AFX_MSGMAP結構。
四、結構優化
找到AFX_MSGMAP,獲得控制元件ID之後,我們就可以優化結構使得結構更加易讀。
參考網上內容[3],使用結構定義如下:
struct AFX_MSGMAP_ENTRY { UINT nMessage; UINT nCode; UINT nID; UINT nLastID; UINT_PTR nSig; void (*pfn)(void); }; struct AFX_MSGMAP { const AFX_MSGMAP *(__stdcall *pfnGetBaseMap)(); const AFX_MSGMAP_ENTRY *lpEntries; };
首先IDA上方選單–>View–>Open Subview–>Local types,進入本地結構定義選單。
右鍵Insert,在彈出的結構視窗中輸入上述結構。
之後翻到最底部,找到上一步定義的兩個結構體(一般就是最後兩個),選擇後右鍵Synchronize To idb。
最後回到IDA-ViewA視窗,選中需要改變的結構體Alt+Q進行結構變換:
變換前的結構與變換後的結構對比。
那麼接下來我們根據我們查到的控制元件ID確認1002與1005(對應hex為0x3EA與0x3ED)的訊息處理函式分別為sub_401620與sub_401840。
五、參考文獻
[1] 候俊傑,《深入淺出MFC》,P133
[2] MFC訊息對映的原理, https://www.cnblogs.com/lidabo/p/3694726.html
[3] 使用IDA定位基於MFC的CrackMe的按鈕函式, https://blog.csdn.net/SilverMagic/article/details/40622413
*本文作者:kdsj,本文屬 FreeBuf 原創獎勵計劃,未經許可禁止轉載。