深析OD下断点与找回调的原理

简介: 深析OD下断点与找回调的原理

前言


当我们想要给某种消息下断点的时候,首先要了解的是消息的处理流程,比如消息怎么产生、消息怎么存储、消息怎么处理等等


事件与消息


事件:通俗来说就是动作,可以是用户触发的,也可以是Windows系统自身触发的;


消息:通俗来说是事件的描述信息,比如一个鼠标左键单击的事件,消息记录了这个事件产生的时间、鼠标箭头的坐标等信息,为了精确描述一个事件,Windows系统定义了一个MSG的结构体来记录消息内容

typedef struct tagMSG {
HWND   hwnd;        //窗口句柄,相当于一个编号,用于标识窗口
UINT   message;     //消息类型
WPARAM wParam;      //事件的描述信息
LPARAM lParam;      //事件的描述信息
DWORD  time;        //事件产生时间
POINT  pt;          //坐标结构体
} MSG;
typedef struct tagPOINT {
LONG x;              //x坐标
LONG y;              //y坐标
} POINT;


消息处理


消息处理过程可以参考以下流程图:



b5346174fe58bdc4b5dda4108268b0cb_640_wx_fmt=png&wxfrom=5&wx_lazy=1&wx_co=1.png


根据流程图,我们可以简单书写一个windows窗口进行验证:

  1. 定义一个用户窗口的WNDCLASS类

WNDCLASS类的结构如下:


typedef struct _WNDCLASS {
UINT       style;
WNDPROC    lpfnWndProc;
int        cbClsExtra;
int        cbWndExtra;
HINSTANCE  hInstance;
HICON      hIcon;
HCURSOR    hCursor;
HBRUSH     hbrBackground;
LPCTSTR    lpszMenuName;
LPCTSTR    lpszClassName;
} WNDCLASS, *PWNDCLASS;


1. 用户定义的类变量如下:


//窗口的类名 用于标识窗口
TCHAR className[] = TEXT("My First Window");
// 创建窗口类的对象
WNDCLASS wndclass = { 0 };                          //一定要先将所有值赋值
wndclass.hbrBackground = (HBRUSH)COLOR_MENU;        //窗口的背景色
wndclass.lpfnWndProc = WindowProc;                  //窗口过程函数
wndclass.lpszClassName = className;                 //窗口类的名字
wndclass.hInstance = hInstance;                     //定义窗口类的应用程序的实例句柄

这里需要提及的地方有两个:一是每个属性都要赋值,否则会报错;二是如果结构体一开始没有初始化为0,那属性的值在内存中全用0xCCCCCCCC填充


2. 注册用户定义的类


RegisterClass(&wndclass);


RegisterClass这个函数是寻找回调函数(窗口过程函数)的其中一个点,下面简单演示一下在OD种寻找回调函数:

寻找程序入口函数,相当于C语言函数中的main函数,这个函数有四个参数,F2下断点


int CALLBACK WinMain(
_In_  HINSTANCE hInstance,          //实例句柄,程序的ImageBase
_In_  HINSTANCE hPrevInstance,      //无意义
_In_  LPSTR lpCmdLine,              //程序在命令行执行的附加参数的指针
_In_  int nCmdShow                  //表示窗口是最大化、最小化还是正常
) ;

c75b78c1b2156569a8dd9c07bdfb5ce1_640_wx_fmt=png&wxfrom=5&wx_lazy=1&wx_co=1.png


F7进入"main"函数内部寻找一个参数的RegisterClass,F2下断点,同时可以看到使用寄存器EAX传参


07c8e8fe4f66c0dcb42d6e2e05c0ecff_640_wx_fmt=png&wxfrom=5&wx_lazy=1&wx_co=1.png


在堆栈中跟随EAX


dacfa2798b857a5d891789a0af142b84_640_wx_fmt=png&wxfrom=5&wx_lazy=1&wx_co=1.png


根据前面提及WNDCLASS结构,可知第二个就是回调函数地址


747e6cf021533e317d61c1346996ca59_640_wx_fmt=png&wxfrom=5&wx_lazy=1&wx_co=1.png


查看回调函数的反汇编


17521c5c748527ba64db619e5e048d6f_640_wx_fmt=png&wxfrom=5&wx_lazy=1&wx_co=1.png


c39b33f8b70b551429869ec432791f81_640_wx_fmt=png&wxfrom=5&wx_lazy=1&wx_co=1.png


3. 创建窗口与显示窗口


CreateWindow函数参数如下:
HWND CreateWindow(
LPCTSTR lpClassName,  // registered class name
LPCTSTR lpWindowName, // window name
DWORD dwStyle,        // window style
int x,                // horizontal position of window
int y,                // vertical position of window
int nWidth,           // window width
int nHeight,          // window height
HWND hWndParent,      // handle to parent or owner window
HMENU hMenu,          // menu handle or child identifier
HINSTANCE hInstance,  // handle to application instance
LPVOID lpParam        // window-creation data
);


创建、显示窗口

//创建窗口
HWND hwnd = CreateWindow(
className,                      //类名
TEXT("my first windows"),       //窗口标题
WS_OVERLAPPEDWINDOW,            //窗口外观样式
100,                            //相对于父窗口的X坐标
100,                            //相对于父窗口的Y坐标
600,                            //窗口的宽度
300,                            //窗口的高度
NULL,                           //父窗口句柄,为NULL
NULL,                           //菜单句柄,为NULL
hInstance,                      //当前应用程序的句柄
NULL);                          //附加数据一般为NULL
if (hwnd == NULL){
return 0;                       //是否创建成功
}
// 显示窗口
ShowWindow(hwnd, SW_SHOW);


4. 消息循环


//消息循环
MSG msg;
while (GetMessage(&msg, NULL, 0, 0))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}

GetMessage将取出来的消息放到msg结构体中


5、回调函数


//回调函数
LRESULT CALLBACK WindowProc(
IN  HWND hwnd,          //窗口句柄,相当于一个编号,用于标识窗口
IN  UINT uMsg,          //消息ID
IN  WPARAM wParam,      //消息详细信息
IN  LPARAM lParam       //消息详细信息
)
{
//窗口函数执行成功返回0,switch结构更高效
switch (uMsg)
{
//窗口消息
case WM_CREATE:
{
DbgPrintf("WM_CREATE %d %d\n", wParam, lParam);
CREATESTRUCT* createst = (CREATESTRUCT*)lParam;
DbgPrintf("CREATESTRUCT %s\n", createst->lpszClass);
return 0;
}
case WM_MOVE:
{
DbgPrintf("WM_MOVE %d %d\n", wParam, lParam);
POINTS points = MAKEPOINTS(lParam);
DbgPrintf("X Y %d %d\n", points.x, points.y);
return 0;
}
case WM_SIZE:
{
DbgPrintf("WM_SIZE %d %d\n", wParam, lParam);
int newWidth = (int)(short)LOWORD(lParam);
int newHeight = (int)(short)HIWORD(lParam);
DbgPrintf("WM_SIZE %d %d\n", newWidth, newHeight);
return 0;
}
case WM_DESTROY:
{
DbgPrintf("WM_DESTROY %d %d\n", wParam, lParam);
PostQuitMessage(0);
return 0;
}
//键盘消息
case WM_KEYUP:
{
DbgPrintf("WM_KEYUP %d %d\n", wParam, lParam);
return 0;
}
case WM_KEYDOWN:
{
DbgPrintf("WM_KEYDOWN %d %d\n", wParam, lParam);
return 0;
}
//鼠标消息
case WM_LBUTTONDOWN:
{
DbgPrintf("WM_LBUTTONDOWN %d %d\n", wParam, lParam);
POINTS points = MAKEPOINTS(lParam);
DbgPrintf("WM_LBUTTONDOWN %d %d\n", points.x, points.y);
return 0;
}
}
//返回消息给Windows处理
return DefWindowProc(hwnd, uMsg, wParam, lParam);
}


6. 结果展示


c87c5f8c8907c38ca02d93647f609f42_640_wx_fmt=png&wxfrom=5&wx_lazy=1&wx_co=1.png


父窗口消息断点


我们从父窗口的回调函数反汇编进行分析


fcb8a18676cff9c8aa17996c250eab21_640_wx_fmt=png&wxfrom=5&wx_lazy=1&wx_co=1.png


这是典型的高效的switch语句,EAX就是消息类型的编号,之所以[ESP + 8 ]是消息类型编号,是因为WindowProc(回调函数)的调用约定属于stdcall,传参的时候是按照lParam、wParam、uMsg、hwnd的顺序,我们下消息断点的时候只需要设置[ESP + 8] == WM_LBUTTONDOWN (WM_LBUTTONDOWN可以切换为其他的),这时候我们可以过滤掉我们不要的消息类型


子窗口消息断点


子窗口(例如按钮)想要下消息断点,按照父窗口下断点的方式就行不通了,这就涉及到子窗口消息处理方式的差异。按钮属于一种特殊的窗体,并不需要像父窗口那样提供独立的回调函数,当按钮有事件产生时,其会向父窗口消息处理程序发送一个WM_COMMAND消息,因此我们只需要在父窗口回调函数处添加一个WM_COMMAND消息的处理即可


abf7ce2b79a1d15950635dae101e3962_640_wx_fmt=png&wxfrom=5&wx_lazy=1&wx_co=1.png


case WM_COMMAND:
{
switch (LOWORD(wParam))
{
case 1001:
MessageBox(hwnd, TEXT("Hello Button 1"), TEXT("Demo"), MB_OK);
return 0;
case 1002:
MessageBox(hwnd, TEXT("Hello Button 2"), TEXT("Demo"), MB_OK);
return 0;
case 1003:
MessageBox(hwnd, TEXT("Hello Button 3"), TEXT("Demo"), MB_OK);
return 0;
}
return DefWindowProc(hwnd, uMsg, wParam, lParam);
}

LOWORD()函数作用是取数值的低两位,case 1001、case 1002、case 1003中的数字就是HMENU hMenu(子窗口的ID),用于区分按钮


3532b7738c844396a3be1972fa949295_640_wx_fmt=png&wxfrom=5&wx_lazy=1&wx_co=1.png


那我们如果想要下子窗口消息的断点,需要换一下条件,如[ESP + 8] == WM_COMMAND


简单实战寻找回调函数


分析回调函数功能


ee2daa164f4db660ff72635f7a25bff8_640_wx_fmt=png&wxfrom=5&wx_lazy=1&wx_co=1.pngae261fb970da252170b5917d9b14d950_640_wx_fmt=png&wxfrom=5&wx_lazy=1&wx_co=1.png


父窗口回调函数初始处下断点,并添加条件


0b2b011c571fa6f1d0b35e5de3bd3991_640_wx_fmt=png&wxfrom=5&wx_lazy=1&wx_co=1.png


b89303a1d7bff6530a2febfd1506d1d2_640_wx_fmt=png&wxfrom=5&wx_lazy=1&wx_co=1.png


点击按钮得知WM_COMMAND 值为0x111


4df262d85e0c25f3b5687d41fd7b785f_640_wx_fmt=png&wxfrom=5&wx_lazy=1&wx_co=1.png


取消0x00401100处的断点,在0x00401114处下断点,[esp + 0xc]就是wParam,子窗口ID,这样就可以查看每个按钮的ID


7b190de63c2ed9556af4ec84e5cfa9be_640_wx_fmt=png&wxfrom=5&wx_lazy=1&wx_co=1.png


往下拉查看整个case(WM_COMMAND )逻辑,很容易理解


00401114   .  8B4424 0C     MOV EAX,DWORD PTR SS:[ESP+C]
00401118   .  75 7E         JNZ SHORT ReverseT.00401198
0040111A   .  3D E9030000   CMP EAX,3E9                              ;  Switch (cases 3E9..3EB)
0040111F   .  75 23         JNZ SHORT ReverseT.00401144
00401121   .  6A 00         PUSH 0                                   ; /Style = MB_OK|MB_APPLMODAL; Case 3E9 of switch 0040111A
00401123   .  68 74604000   PUSH ReverseT.00406074                   ; |Title = "Demo"
00401128   .  68 68604000   PUSH ReverseT.00406068                   ; |Text = "Find Me 1"
0040112D   .  6A 00         PUSH 0                                   ; |hOwner = NULL
0040112F   .  C705 14854000>MOV DWORD PTR DS:[408514],1              ; |
00401139   .  FF15 9C504000 CALL NEAR DWORD PTR DS:[40509C]          ; \MessageBoxA
0040113F   .  33C0          XOR EAX,EAX
00401141   .  C2 1000       RETN 10
00401144   >  3D EA030000   CMP EAX,3EA
00401149   .  75 23         JNZ SHORT ReverseT.0040116E
0040114B   .  6A 00         PUSH 0                                   ; /Style = MB_OK|MB_APPLMODAL; Case 3EA of switch 0040111A
0040114D   .  68 74604000   PUSH ReverseT.00406074                   ; |Title = "Demo"
00401152   .  68 5C604000   PUSH ReverseT.0040605C                   ; |Text = "Find Me 2"
00401157   .  6A 00         PUSH 0                                   ; |hOwner = NULL
00401159   .  C705 14854000>MOV DWORD PTR DS:[408514],2              ; |
00401163   .  FF15 9C504000 CALL NEAR DWORD PTR DS:[40509C]          ; \MessageBoxA
00401169   .  33C0          XOR EAX,EAX
0040116B   .  C2 1000       RETN 10
0040116E   >  3D EB030000   CMP EAX,3EB
00401173   .  75 23         JNZ SHORT ReverseT.00401198
00401175   .  6A 00         PUSH 0                                   ; /Style = MB_OK|MB_APPLMODAL; Case 3EB of switch 0040111A
00401177   .  68 74604000   PUSH ReverseT.00406074                   ; |Title = "Demo"
0040117C   .  68 50604000   PUSH ReverseT.00406050                   ; |Text = "Find Me 3"
00401181   .  6A 00         PUSH 0                                   ; |hOwner = NULL
00401183   .  C705 14854000>MOV DWORD PTR DS:[408514],3              ; |
0040118D   .  FF15 9C504000 CALL NEAR DWORD PTR DS:[40509C]          ; \MessageBoxA
00401193   .  33C0          XOR EAX,EAX
00401195   .  C2 1000       RETN 10
00401198   >  8B5424 10     MOV EDX,DWORD PTR SS:[ESP+10]            ;  Default case of switch 0040111A

按钮回调函数功能:弹出messagebox并且修改窗口的样式


相关文章
|
6月前
|
C#
C#拾遗补漏之goto跳转语句
在我们日常工作中常用的C#跳转语句有break、continue、return,但是还有一个C#跳转语句很多同学可能都比较的陌生就是goto,今天大姚带大家一起来认识一下goto语句及其它的优缺点。
|
3月前
hyengine 代码块问题之跳转目标地址如何解决
hyengine 代码块问题之跳转目标地址如何解决
|
存储 C语言 C++
Win32程序入口识别,定位回调函数,具体事件处理的定位,ESP寻址方式,压栈方式复习
Win32程序入口识别,定位回调函数,具体事件处理的定位,ESP寻址方式,压栈方式复习
JavaScirpt基础do while循环
JavaScirpt基础do while循环
87 0
|
C++
C++程序调试详解(包括打断点 单步调试 数据断点...)
C++程序调试详解(包括打断点 单步调试 数据断点...)
364 0
C++程序调试详解(包括打断点 单步调试 数据断点...)
|
前端开发 JavaScript 容器
LPL Ban/Pick 选人阶段的遮罩效果是如何实现的?
LPL Ban/Pick 选人阶段的遮罩效果是如何实现的?
172 0
LPL Ban/Pick 选人阶段的遮罩效果是如何实现的?
|
Windows
【Windows 逆向】OD 调试器工具 ( CE 中获取子弹动态地址前置操作 | OD 中调试指定地址的数据 )(二)
【Windows 逆向】OD 调试器工具 ( CE 中获取子弹动态地址前置操作 | OD 中调试指定地址的数据 )(二)
149 0
【Windows 逆向】OD 调试器工具 ( CE 中获取子弹动态地址前置操作 | OD 中调试指定地址的数据 )(二)
|
Windows
【Windows 逆向】OD 调试器工具 ( CE 中获取子弹动态地址前置操作 | OD 中调试指定地址的数据 )(一)
【Windows 逆向】OD 调试器工具 ( CE 中获取子弹动态地址前置操作 | OD 中调试指定地址的数据 )(一)
186 0
【Windows 逆向】OD 调试器工具 ( CE 中获取子弹动态地址前置操作 | OD 中调试指定地址的数据 )(一)
|
Windows
【Windows 逆向】OD 调试器工具 ( CE 中获取子弹动态地址前置操作 | OD 中调试指定地址的数据 )(三)
【Windows 逆向】OD 调试器工具 ( CE 中获取子弹动态地址前置操作 | OD 中调试指定地址的数据 )(三)
197 0
【Windows 逆向】OD 调试器工具 ( CE 中获取子弹动态地址前置操作 | OD 中调试指定地址的数据 )(三)
|
存储 移动开发 NoSQL
开源代码分析技巧之一——打印调用逻辑
在研究开源代码时,大家或许都有这样的感慨: (1)代码太庞大,少则几万行代码,多则几百万行代码,不知道如何入手; (2)相关的帮助文档有限,很难短时间内理清头绪; (3)有了代码在手,但代码之间的调用相当复杂,一层一层追踪总是理不清调用逻辑顺序。
265 0
开源代码分析技巧之一——打印调用逻辑