查看原文
其他

消息断点在x64dbg中的应用

2017-10-02 蓝铁 看雪学院



介绍



你曾试图逆向一个应用程序中特定的函数,但是却无法真正找到它吗?比如,在点击按钮或者按键之后想找到正在调用的代码的。在某些程序(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)。



WinPoc条件断电



完成此功能的最后一件事是可能暂停应用程序的时机只有在特定句柄(handles)和消息(messages)处理时。我们可以在帮助文档中阅读,x64dbg集成了一组非常好的强大的表达式来实现这一点。 如上图所示,有三个选项:

1. Break on any window(在任何窗口中断):使用此选项,我们停止给定的消息,而不管窗口句柄。 为此,我们需要最简单的表达式:

bpcnd WINPROC, "arg.get(1) == MESSAGE"


2. Break on current window only(仅在当前窗口中断):此功能将为表达式添加一个附加条件,以便仅在涉及特定窗口的句柄时停止执行,在这种情况下,表达式将为:

bpcnd WINPROC, "arg.get(1) == MESSAGE && arg.get(0) == HANDLE"


3. 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设置消息断点即可。按钮点击、显示窗口等控制一样,有一大堆消息可以使用消息断点进行调试分析。



最后的话





书写这篇文章,是想更深入了解消息断点功能和在多种场景下如何使用消息断点,到此结束,希望对你有帮助


参考

  1. https://www.technologyuk.net/computing/software-development/event-driven-programming.shtml

  2. https://msdn.microsoft.com/en-us/library/windows/desktop/ms644927(v=vs.85).asp





本文由看雪论坛 蓝铁 编译,来源Official x64dbg blog@ThunderCls

转载请注明来自看雪社区

热门阅读


点击阅读原文/read,

更多干货等着你~




您可能也对以下帖子感兴趣

文章有问题?点此查看未经处理的缓存