消息斷點在x64dbg中的應用 by lantie@15PB
介紹
你曾試圖逆向一個應用程序中特定的函數,但是卻無法真正找到它嗎?比如,在點擊按鈕或者按鍵之后想找到正在調用的代碼的。在某些程序(Delphi、CBuilder、Visual Basic等)中,可以使用工具反編譯程序并在幾秒鐘內定位到相應事件/地址。但如果程序被加殼或是使用了反-反編譯技術,亦或是程序不是事件驅動的,在這種情況下,你可以使用類似方法很快定位到地址嗎?
本文介紹的方式是在程序運行中使用消息斷點來定位特定函數。(好不好使,看你的了??)
使用Windows消息
讓我們來看一個簡單的CrackMe作為示例。其是一個簡單的exe,使用VC++編寫:
如果我們嘗試輸入一個文本并點擊按鈕Check!
,會發現沒有任何反應,沒有文本信息,什么也沒有。這個時候,我們可以開動腦筋,有創意地尋找其他替代方案來找到我們的序列號被處理的確切位置,也許成功,也許失敗。但是如果我告訴你,有一個更簡單的方法可以找到按鈕呢,就像Delphi、Visual Basic或者其他事件驅動的語言?然我們一起來看看這種方法吧。
在x64dbg中加載和執行文件后,我們輸入一些文本,然后點擊按鈕Check!
,之后我們轉到句柄(Handles)
選項卡,并刷新視圖以獲取調試程序的窗口列表。然后我們可以在列表中看到按鈕Check!
,選擇按鈕Check!
所在列然后右鍵單擊它,并選擇Message Breakpoint
選項設置消息斷點:
到此會進行消息斷點的設置,具體的配置是這樣的:
以上設置是當WM_LBUTTONUP
(鼠標左鍵點擊)消息被發送到我們的按鈕控件,程序就會停止。設置斷點之后,我們點擊CrackMe中的按鈕Check!
,不久之后,我們進入斷點。
到此,我們實現了我們想要的程序暫停。按鈕點擊后我們剛剛停止執行,程序會停在user32.dll
中,但我們的目的是在主模塊代碼中。 到達主模塊就像在我們的可執行文件的代碼部分使用斷點一樣簡單。 我們可以使用運行到用戶代碼選項(Ctrl + F9)。
當嘗試恢復執行時,調試器將再次停止執行,但這次正好在我們正在尋找的代碼中間。 在這種情況下,我們進入了 DLGPROC 函數(負責處理發送到主窗口對話框中的每個窗口控件的消息的回調)。
事件驅動程序設計
如果你是一個程序員,或者一直在做與編程語言和寫代碼相關的工作,你應該知道所謂的事件驅動編程的概念。事件驅動的編程是一種編程范例,程序執行流由事件決定;用戶動作,鼠標點擊,按鍵等。事件驅動的應用程序用于識別事件發生時,然后使用適當的事件處理過程進行管理。一些編程語言使用事件驅動編程,并給出一個IDE,它可以讓代碼自動生成,并且可以選擇內置的對象和控件,Visual Basic,Borland Delphi / CBuilder,C#,Java等等都是可以做到。(1)
Windows消息
即使程序員沒有使用上述語言之一,或者即使以非事件驅動的方式使用它們,Microsoft Windows應用程序也是事件驅動的,這意味著您將要處理窗口消息。以下是MSDN的的說明:
系統將應用程序的所有輸入傳遞到應用程序中的各種窗口。 每個窗口都有一個稱為窗口過程的函數,系統在窗口輸入時調用。 窗口過程處理輸入并將控制返回給系統。
系統使用一組四個參數向窗口過程發送消息:窗口句柄,消息標識符和兩個稱為消息參數的值。 窗口句柄標識消息所針對的窗口。 系統使用它來確定哪個窗口過程應該接收消息。
消息標識符是用于標識消息目的的命名常量。 當窗口過程接收到消息時,它使用消息標識符來確定如何處理消息。(2)
Windows窗口回調函數
根據上面的信息,攔截某個窗口的窗口消息,我們需要首先找到所需“控件”的窗口回調函數。 從包含窗口回調函數的應用程序這樣做是很容易的,我們可以通過使用以下函數來定位:
LONG WINAPI GetWindowLong(
_In_ HWND hWnd,
_In_ int nIndex
);
DWORD WINAPI GetClassLong(
_In_ HWND hWnd,
_In_ int nIndex
);
nIndex = GWL_WNDPROC
:指定獲取的信息是窗口回調函數的地址,或表示窗口回調函數地址的句柄。 您必須使用CallWindowProc
函數調用窗口回調函數。
BOOL WINAPI GetClassInfo(
_In_opt_ HINSTANCE hInstance,
_In_ LPCTSTR lpClassName,
_Out_ LPWNDCLASS lpWndClass
);
BOOL WINAPI GetClassInfoEx(
_In_opt_ HINSTANCE hinst,
_In_ LPCTSTR lpszClass,
_Out_ LPWNDCLASSEX lpwcx
);
typedef struct tagWNDCLASS {
UINT style;
WNDPROC lpfnWndProc;
...
} WNDCLASS, *PWNDCLASS;
lpfnWndProc
:指向窗口回調函數的指針。
有了以上函數,獲取窗口類信息和窗口函數地址會非常簡單。但是Windows操作系統有一個限制:Microsoft Windows 不允許從外部應用程序(如調試器)中獲取這些信息。 如果要使用上述函數之一獲取另一個進程所擁有的給定窗口或控件的窗口回調函數地址,則最終會出現ACCESS_VIOLATION異常。在我們這個例子中x64dbg應該是一樣的,無法獲取信息。但是x64dbg可以獲取的真實的地址,接下來我們看看x64dbg是怎么做的
獲取外部Windows窗口回調函數
在這點上,我們不清楚為什么會出現這種情況,以及是否是操作系統的bug。事實上它可以在以前的Windows操作系統版本中使用。這其中關鍵函數是這個:
DWORD WINAPI GetClassLong(
_In_ HWND hWnd,
_In_ int nIndex
);
在相應地調用GetClassLong(ANSI / UNICODE)的正確功能版本之前,黑客依靠對給定窗口的字符集進行測試。 x64dbg使用的代碼很簡單:
duint wndProc;
if(IsWindowUnicode(hWnd))
wndProc = GetClassLongPtrW(hWnd, GCLP_WNDPROC);
else
wndProc = GetClassLongPtrA(hWnd, GCLP_WNDPROC);
要編寫與32位和64位版本的Windows兼容的代碼,您必須使用GetClassLongPtr。 當編譯32位Windows時,GetClassLongPtr被定義為調用通常的GetClassLong函數。
截取消息
現在已經定位到窗口回調函數,任何消息都可以用適當的條件表達式攔截,但在此之前,讓我們檢查一下這個邏輯。 每次通過窗口回調函數處理的結構如下所示:
typedef struct tagMSG {
HWND hwnd;
UINT message;
WPARAM wParam;
LPARAM lParam;
DWORD time;
POINT pt;
} MSG, *PMSG, *LPMSG;
正如我們可以看到結構給我們提供了一些有用的信息,最重要的是hwnd
和message
。 根據這些字段,我們可以知道哪個特定的控件發送什么消息。 在進一步介紹之前,讓我們看一個發送給給定Button
控件的特定消息(WM_LBUTTONUP
)的示例。
點擊
OK
按鈕后,我們會觸發斷點,當我們檢查堆棧參數時,我們可以看到這樣的東西第一個值可以看到的是對應于我們的
Button
控件的句柄,第二個值對應于消息WM_LBUTTONUP
(0x202)。
WinProc 條件斷點
完成此功能的最后一件事是可能暫停應用程序的時機只有在特定句柄(handles)和消息(messages)處理時。我們可以在幫助文檔中閱讀,x64dbg集成了一組非常好的強大的表達式來實現這一點。 如上圖所示,有三個選項:
- Break on any window(在任何窗口中斷):使用此選項,我們停止給定的消息,而不管窗口句柄。 為此,我們需要最簡單的表達式:
bpcnd WINPROC, "arg.get(1) == MESSAGE"
- Break on current window only(僅在當前窗口中斷):此功能將為表達式添加一個附加條件,以便僅在涉及特定窗口的句柄時停止執行,在這種情況下,表達式將為:
bpcnd WINPROC, "arg.get(1) == MESSAGE && arg.get(0) == HANDLE"
- Use TranslateMessage(使用TranslateMessage函數):有時winproc技術可能不是預期的結果,那這個時候程序可能使用的是TranslateMessage API 來截獲消息,而不是在窗口回調本身。
BOOL WINAPI TranslateMessage(
_In_ const MSG *lpMsg
);
看到這個函數使用了我們之前看到的相同的MSG結構,因此根據操作系統平臺,使用表達式會增加一些微小的變化:
ifdef _WIN64
bpcnd TranslateMessage, "4:[arg.get(0)+8] == MESSAGE"
bpcnd TranslateMessage, "4:[arg.get(0)+8] == MESSAGE && 4:[arg.get(0)] == HANDLE"
#else //x86
bpcnd TranslateMessage, "[arg.get(0)+4] == MESSAGE"
bpcnd TranslateMessage, "[arg.get(0)+4] == MESSAGE && [arg.get(0)] == HANDLE"
#endif //_WIN64
案例說明
如在這篇文章中看到的,消息斷點是x64dbg中非常方便和強大的功能,可以在許多場景中使用,我們可以盡可能的控制任何事件暫停調試。即使不是類似Delphi或Visual Basic一樣的事件驅動程序,也可以為逆向者打開一扇門,帶來更多的思路進行調試。如果想在MASM編寫的應用程序的“Edit”控件中輸入字符時暫停執行,只需要在控件上對消息WM_KEYUP設置消息斷點即可。按鈕點擊、顯示窗口等控制一樣,有一大堆消息可以使用消息斷點進行調試分析。
最后的話
書寫這篇文章,是想更深入了解消息斷點功能和在多種場景下如何使用消息斷點,到此結束,希望對你有幫助
原作者: ThunderCls
翻譯者: lantie@15PB
原文地址:https://x64dbg.com/blog/2017/07/07/messages-breakpoints-in-x64dbg.html
參考
- https://www.technologyuk.net/computing/software-development/event-driven-programming.shtml
- https://msdn.microsoft.com/en-us/library/windows/desktop/ms644927(v=vs.85).asp
注釋
無
幫助
官網:www.15pb.com.cn,專注于信息安全教育,軟件逆向,二進制安全,網絡安全