消息回调函数定位3法
/www.pediy.com
E MAIL:nemo314@gmail.com
在WINDOWS系列的操作系统中,有所谓消息驱动事件的概念。系统会根据用户对应用程序的操作产生出对应的消息,发送给程序,程序则通过响应该消息进行相关的活动。例如在一个注册窗口中,当你点击“注册”这个按钮时,系统便会将WM_command这个消息发送给程序,当程序收到这个消息时,便开始对注册码进行验证。这一基本框架,为我们寻找程序关键代码提供了一条捷径。
VC的工程向导为我们提供了一个简单的HELLO WORLD的例子,里面包含生成窗口最基本的代码。由于代码量很少,这比较适合我们分析窗口的建立和运行过程。
窗口建立过程调用API的顺序
CreateWindow正如字面的意思,它的作用是建立一个窗口。它的定义如下:
<b>HWND CreateWindow(<o:p>
<b> LPCTSTR<i> lpClassName<b>, // pointer to registered class name<o:p>
<b> LPCTSTR<i> lpWindowName<b>, // pointer to window name<o:p>
<b> DWORD<i> dwStyle<b>, // window style<o:p>
<b> int<i> x<b>, // horizontal position of window<o:p>
<b> int<i> y<b>, // vertical position of window<o:p>
<b> int<i> nWidth<b>, // window width<o:p>
<b> int<i> nHeight<b>, // window height<o:p>
<b> HWND<i> hWndParent<b>, // handle to parent or owner window<o:p>
<b> HMENU<i> hMenu<b>, // handle to menu or child-window identifier<o:p>
<b> HANDLE<i> hInstance<b>, // handle to application instance<o:p>
<b> LPVOID<i> lpParam // pointer to window-creation data<o:p>
<b>);<o:p>
注意到lpClassName,它是由RegisterClassEx注册的类。定义如下:
<b>ATOM RegisterClassEx(<o:p>
<b> CONST WNDCLASSEX<i> *lpwcx // address of structure with class data<o:p>
<b>);<o:p>
typedef struct _WNDCLASSEX { // wc
UINT cbSize;
UINT style;
WNDPROC lpfnWndProc;
int cbClsExtra;
int cbWndExtra;
HANDLE hInstance;
HICON hIcon;
HCURSOR hCursor;
HBRUSH hbrBackground;
LPCTSTR lpszMenuName;
LPCTSTR lpszClassName;
HICON hIconSm;
} WNDCLASSEX;
WNDPROC,这就是本文的主角了。该项填写的是消息回调函数的地址。类似地,建立对话框的两个函数,CreateDialogParam和DialogBoxParam中的DLGPROC参数只是对同一样事物的不同名称。此外,应用程序也有可能通过GetMessage这个API取得并处理消息。现在,我们可以通过这些API取得消息处理函数的地址。
仍然依赖API作为寻找关键代码并体现不出理解消息机制的优越性。它仍然易于被隐藏API调用,API断点的检测等方法阻止我们找到消息处理程序的位置。在消息处理程序入口下断点,停下来之后,在当前堆栈可以得到一个返回地址,它指向系统区域。在那里,我看到有趣的东西:
|
|
处于user32.dll内部的代码
77DF2CA5处便是系统调用应用程序消息函数的代码,正如你所见的一样,系统把它的地址放在堆栈中。我们知道,程序通过一个循环,不断地接收和处理系统发送过来的消息。也就是说,该地址很可能一直存在于堆栈之中。为了寻找该地址,我们需要一个特殊标记。正如你看到的那样,系统在把控制权交给消息处理程序之前,将“DCBAABCD”这个特殊标记压入堆栈,并在消息处理程序返回之后检查这个标记以判断消息函数是否破坏了堆栈。现在让我们一起来测试这个方法,打开NOTEPAD.exe,F9运行它。
在当前堆栈中,用鼠标右键激活菜单,选择“查找地址”,输入“DCBAABCD”。
|
|
0006F534处保存的数据指向NOTEPAD的领空。在反汇编窗口可以看到下面代码:
|
|
我们看到PostQuitMessage这个消息处理程序中常用的API,这里便是消息处理函数了。该方法在WIN2K和WINXP下测试通过。然而,对于CreateDialogParam和DialogBoxParam却无能为力。我并不打算去找原因,因为对于这两个函数,有更好的方法。在调用这两个函数建立对话框的时候,在对话框被关闭之前,函数是绝对不会返回的。也就是说,调用该函数的参数,一直都留在堆栈之中。当然,在堆栈中找我们想要的东西,我们需要一个标记。下面先让我们来看看这两个函数的定义:
<b>HWND CreateDialogParam(<o:p>
<b> HINSTANCE<i> hInstance<b>, // handle to application instance<o:p>
<b> LPCTSTR<i> lpTemplateName<b>, // identifies dialog box template<o:p>
<b> HWND<i> hWndParent<b>, // handle to owner window<o:p>
<b> DLGPROC<i> lpDialogFunc<b>, // pointer to dialog box procedure<o:p>
<b> LPARAM<i> dwInitParam // initialization value<o:p>
<b>);<o:p>
<b>int DialogBoxParam(<o:p>
<b> HINSTANCE<i> hInstance<b>, // handle to application instance<o:p>
<b> LPCTSTR<i> lpTemplateName<b>, // identifies dialog box template<o:p>
<b> HWND<i> hWndParent<b>, // handle to owner window<o:p>
<b> DLGPROC<i> lpDialogFunc<b>, // pointer to dialog box procedure<o:p>
<b> LPARAM<i> dwInitParam // initialization value<o:p>
<b>);<o:p>
这两个函数的相同点都是,左边第一个参数是HINSTANCE,即应用程序的基址。左边第四个参数DLGPROC则是消息处理函数的地址。现在,我们便有了一个标记,可惜的是,需要该参数的API比较多,在内存中搜索这一标记时,有很多干扰数据。当然你可以写一个脚本来判断在堆栈中相应的地址,是否指向程序的代码段。这里就留给读者做练习了。
在前面的示例中,我通过PostQuitMessage这个函数的调用认出消息处理函数(对编程不了解的读者可以看看VC自带的HELLO WORLD示例)。同样地,可以通过DefWindowProc这个只在消息处理函数中出现的API去确定是否消息处理函数。换一句话来说,我们同样可以通过在这些API下断点找到消息处理函数。
当然,这里仍然有一个不需要API便能定位消息处理函数的方法。我们知道,消息处理函数通过处理一系列的消息常量来响应用户的操作。这一常量,也可以当作是一个特征,搜索出来。一个经常会被应用程序处理的消息是WM_Command(常量值为111),该消息的意义,在文章开始的地方便说明过,这里就不重复了。最后让我们用这个方法来寻找一个CRACKME的关键代码。
http://bbs.pediy.com/showthread.php?t=39759
你可以通过上面地址下载到该CRACKME。用OD打开该CRACKME。在反汇编窗口,鼠标右键点击,弹出菜单。
“所有常量”选项用于查找立即数
查找数值111,结果如下:
|
参考位于 cm01:.text 到常量 111, 条目 0 地址=00401598 反汇编=cmp dword ptr [ebp-4], 111 |
按照搜索结果,我们来到了处理该消息的位置。0040159F处代码的意义,如果消息号为111,则跳至004015B2执行。接下来,程序将会比较BUTTON的ID以确认用户按下了“注册”还是“关于”。既然我们是在调试,那便没必要知道ID号,直接在004015B2处下断。
|
00401598 |. 817D FC 11010>cmp dword ptr [ebp-4], 111 0040159F |. 74 11 je short 004015B2 004015A1 |. EB 4C jmp short 004015EF 004015A3 |> 8B45 08 mov eax, dword ptr [ebp+8] 004015A6 |. A3 00CE4000 mov dword ptr [40CE00], eax 004015AB |. E8 1AFFFFFF call 004014CA 004015B0 |. EB 41 jmp short 004015F3 004015B2 |> 8B45 10 mov eax, dword ptr [ebp+10] |
F9运行程序,点击“注册”,调试器便得到了控制权。F7单步跟踪几行代码,程序在确认用户按下了“注册”之后,便把你带到注册代码的位置了。爆破?还是写注册机?就留给读者自己探索了。
