【文章作者】: icefisher
【作者邮箱】: [email protected]
【软件下载】:
【软件名称】: echap518.exe(只是供学习的crackme)
【加壳方式】: 里面直接有个unpacked.ExE,是脱过壳的见【原创】我也来谈谈消息断点二
【保护方式】:
【使用工具】: OllyICE
【文章日期】: 20080813
-----------------------------------------------------------------------
第一篇原创希望大家支持下,首先我想和我一样菜的好多新人,一定都看过了论坛上的那个ollydbg教程,其中的消息断点真是让人难以理解,痛苦不堪啊,那么今天我就讲讲我自己的体会吧,希望望大家共勉。
首先,我觉得我们应该先了解消息,什么是消息,而谈到消息,就不能不说到windows程序设计的结构(大家放心。。。我大概讲下,不是什么重点)。
如下,这是一个典型windows程序结构,其中很多细节地方我都略去了,
1 /*------------------------------------------------------------------------ 2 HELLOWIN.C -- Displays "Hello, Windows 98!" in client area 3 4 (c) Charles Petzold, 1998 5 6 -----------------------------------------------------------------------*/ 7 8 #include <windows.h> 9 LRESULT CALLBACK WndProc (HWND, UINT, WPARAM, LPARAM) ; 10 11 12 int WINAPI WinMain (HINSTANCE hInstance, HINSTANCE hPrevInstance,PSTR szCmdLine, int iCmdShow) 13 14 { 15 16 static TCHAR szAppName[] = TEXT ("HelloWin") ; 17 18 HWND hwnd ; 19 20 MSG msg ; 21 //前面都是变量定义,不关注。 22 23 WNDCLASwndclass ; 24 25 26 wndclass.style = CS_HREDRAW | CS_VREDRAW ; 27 28 wndclass.lpfnWndProc = WndProc ; //关键句,指出消息的处理函数所在位置 29 30 wndclass.cbClsExtra = 0 ; 31 32 wndclass.cbWndExtra = 0 ; 33 34 wndclass.hInstance = hInstance ; 35 36 wndclass.hIcon = LoadIcon (NULL, IDI_APPLICATION) ; 37 38 wndclass.hCursor = LoadCursor (NULL, IDC_ARROW) ; 39 40 wndclass.hbrBackground= (HBRUSH) GetStockObject (WHITE_BRUSH) ; 41 42 wndclass.lpszMenuNam = NULL ; 43 44 wndclass.lpszClassName= szAppName ; 45 46 //这一连串的wndclass是在定义一类窗口的属性,形象地说就是在定义一个模子,然后后面可以用这个模子去建立更多的窗口,其中这个模子中我们需要注意的就是红字的 wndclass.lpfnWndProc = WndProc ;,他指出了这个模子的消息处理函数是哪个?也就是说这个窗口内发生的一切事件,比如说点击,双击,鼠标移动,按键,按下按钮这一切都会生消息,而系统会自动调用WndProc去处理这些问题。*/ 47 if (!RegisterClass (&wndclass)) 48 49 { 50 51 MessageBox ( NULL, TEXT ("This program requires Windows NT!"), 52 53 szAppName, MB_ICONERROR) ; 54 55 return 0 ; 56 57 } 58 //上面的是很简单的一个注册模子,你开始定义了这个模子wndclass,那你得让系统知道啊,这部分就是这个功能,不须太关注。 59 hwnd = CreateWindow( szAppName, // window class name 60 61 TEXT ("The Hello Program"), // window caption 62 63 WS_OVERLAPPEDWINDOW, // window style 64 65 CW_USEDEFAULT,// initial x position 66 67 CW_USEDEFAULT,// initial y position 68 69 CW_USEDEFAULT,// initial x size 70 71 CW_USEDEFAULT,// initial y size 72 73 NULL, // parent window handle 74 75 NULL, // window menu handle 76 77 hInstance, // program instance handle 78 79 NULL) ; // creation parameters 80 81 //注意上面便是利用这个模子,再加上一些自己独有的特点建立了一个窗口,大家不需关注 82 83 ShowWindow (hwnd, iCmdShow) ; 84 85 UpdateWindow (hwnd) ; 86 87 88 while (GetMessage (&msg, NULL, 0, 0)) 89 90 { 91 92 TranslateMessage (&msg) ; 93 94 DispatchMessage (&msg) ; 95 96 } 97 //上面这个需要关注,前面提到过,如果窗口产生了消息我们需要调用WndProc来处理,那么之前我们需要上面的哪个while循环先处理下,这个现在我们也不需要了解。 98 return msg.wParam ; 99 100 } 101 102 103 LRESULT CALLBACK WndProc (HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam) 104 { 105 106 HDC hdc ; 107 108 PAINTSTRUCT ps ; 109 110 RECT rect ; 111 112 //重点来了,大家注意当我们到了WndProc这个消息处理函数中时,我们会不自然地想到:那么多种的消息,系统如何分辨是那个呢,这是我们就要注意到UINT message,他说明了消息的种类,而WPARAM,LPARAM都是消息具体的参数,大家这时看下面,因为message表示的消息不同,系统使用了switch来选择对应的处理程序,这里要注意WM_CREATE:像这些WM_开头的都是消息,而实质上都是数字,不过是头文件定义了一些等价字符串而已。 113 switch (message) 114 115 { 116 117 case WM_CREATE: 118 119 PlaySound (TEXT ("hellowin.wav"), NULL, SND_FILENAME | SND_ASYNC) ; 120 121 return 0 ; 122 123 case WM_PAINT: 124 125 hdc = BeginPaint (hwnd, &ps) ; 126 127 GetClientRect (hwnd, &rect) ; 128 129 DrawText (hdc, TEXT ("Hello, Windows 98!"), -1, &rect, 130 131 DT_SINGLELINE | DT_CENTER | DT_VCENTER) ; 132 133 EndPaint (hwnd, &ps) ; 134 135 return 0 ; 136 137 case WM_DESTROY: 138 139 PostQuitMessage (0) ; 140 141 return 0 ; 142 } 143 return DefWindowProc (hwnd, message, wParam, lParam) ; 144 }
Ok,大家是不是觉得很抽象,大家只要把我的注释看下,大概了解这是干嘛就可以了,其实为了理解消息,只需要注意我红字标出的部分,wndclass.lpfnWndProc = WndProc ;这个就是我们的重点,他指出了这类windows窗口的消息处理交给了那个函数。然后重点就是CALLBACK,里面根据不同的消息转到不同的处理地方。
而大家在crackme中广为使用的则是对话框,比窗口实现方便些,他调用
1 DialogBox ( hInstance, 2 TEXT("AboutBox"), 3 hwnd, 4 AboutDlgProc 5 )
来实现,其中最后一个参数即为消息处理函数,等同于上面所说的WndProc
===========================================================================
好了当我们大概理解消息处理函数后我们就可以来看看这个例子了,首先ollyice载入unpacked.exe。F8单步运行。
如图地方F7,进入后F8,
如图地方F7,进入后可以看到F8几步就可以看到
回想起上篇我提到过的DialogBox (
hInstance,
TEXT("AboutBox"),
hwnd,
AboutDlgProc
)
因为DialogBoxParamA比DialogBox 最后多一个参数用来做记录,不影响我们理解,
(当然我们也可以用Ctrl+N查找函数DialogBoxParamA来直接定位到这里
)
我们看到DLGProc=004015A1,那么我们明显可以理解4015A1处是消息处理函数所在。我们Ctrl+G跟过去看,
我们会看到cmp eax,110 或者cmp eax,111之类的比较,
回想起上篇,这就等同于
switch (message)
{
case WM_CREATE:
case WM_PAINT:
case WM_DESTROY:
}
而其中我们需要知道的是WM_COMMAD =111(用来定位按钮的消息)
WM_LBUTONUP =202(鼠标左键放开)
而且我们还要注意一个地方回想上次我提到的
WndProc (HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
当我们产生一个消息时,必然是调用这个函数,那么根据windows函数调用进栈法则,我们不难得到在消息处理函数开头如果下断点,栈结果必然如此
{
返回地址
hwnd
message
wParam
lParam
}
好了,基础讲完,就再看程序(因为前面push ebp,所以后面在取message时,为ebp+C,大家应该能明白吧),好了我们先F9运行次程序,在查看->窗口明显我们可以看到Check的id值为1。
现在我们来仔细看看这个消息处理函数,他首先从ebp+C中取出了message,存入了eax中,然后开始switch的过程,他不断比较这些以确定是哪个消息,
好了,我们知道按下按钮会产生WM_COMMAD(111) 消息,那么当我们看到cmp eax,111,je跳走时,我们离目标已经很近了,跟踪je到目的地,我们会奇怪,怎么还比较啊,这是因为按钮有很多个,每一个按下都会产生WM_COMMAD(111),那么这时我们需要再次判断是哪个按钮?
我们利用的就是wParam的低四位,他记录了按下按钮的ID值,所以就有了movz eax,
[ebp+10],好了我们已经知道了Check的ID值为1,那么我们看到
cmp eax,1
je xxxxxxxx
跟过去下断点,这时我们就完成了对check的断点,只要一按check必然会来到此处,后续的也会从这里开始,一切就会光明无比
方法二
当我们面对一个很庞杂的系统时我们又该如何来找那关键的消息处理函数呢?茫茫多的程序中又如何去发现
cmp eax,111 与cmp eax,1呢
回到前题,我觉得消息处理函数无论他在那里,当他被调用时,栈中的内容必然是
{
返回地址
hwnd
message
wParam
lParam
}只要我们能抓住他时,我觉得我们就能抓住一切。
首先当我们无法找到消息处理函数时,也许代码太多我们遗漏了,也许有别的技巧可以隐藏。但是我们可以先F9运行起来,在查看->窗口中,我们选择他们的共有父窗口,点击跟随ClassProc
然后我们进入了系统领空,然后在第一句上下条件断点:
[ESP+8]==WM_COMMAND&&[ESP+c]==1
然后我们在对话框中输入数据,点击check,我们发现我们会中断在系统领空,并且注意观察栈
大家都明白了吧,然后在查看->内存,代码段下内存访问断点,F9运行下,直接到达消息处理函数
这时我们这需要静静的F8一路跟踪就可以了
个人总结,记住[ESP+8]==WM_COMMAND&&[ESP+c]==按钮ID