关于逆向Windows窗口程序
一、窗口程序
窗口程序也就是非终端程序,带有图形化界面窗口的程序。
这类程序的起始函数一般为WinMain函数,start函数中会调用该函数。
关于WinMain函数的定义如下:
int WINAPI WinMain( _In_ HINSTANCE hInstance, _In_opt_ HINSTANCE hPrevInstance,//已弃用 _In_ LPSTR lpCmdLine, _In_ int nCmdShow );
二、创建一个窗口程序的过程
步骤可以分为五步:
- 定义一个窗口类;
- 注册该窗口类;
- 创建窗口;
- 显示窗口;
- 创建消息循环。
1.定义窗口类
在定义窗口类的过程中,设置窗口的各项属性,其中对于我们逆向工作最值得注意的是其中的窗口过程函数设置。
窗口类定义示例:
WNDCLASSEX wcex; wcex.cbSize = sizeof(WNDCLASSEX); wcex.style = CS_HREDRAW | CS_VREDRAW; wcex.lpfnWndProc = WndProc; //窗口过程函数设置 wcex.cbClsExtra = 0; wcex.cbWndExtra = 0; wcex.hInstance = hInstance; wcex.hIcon = LoadIcon(wcex.hInstance, IDI_APPLICATION); wcex.hCursor = LoadCursor(NULL, IDC_ARROW); wcex.hbrBackground = (HBRUSH)(COLOR_WINDOW+1); wcex.lpszMenuName = NULL; wcex.lpszClassName = szWindowClass; wcex.hIconSm = LoadIcon(wcex.hInstance, IDI_APPLICATION);
其中wcex.lpfnWndProc就是定义窗口过程函数,窗口中接收到的消息都通过这个函数去决定如何去处理,所以我们需要重点关注它。
窗口过程函数的定义如下:
LRESULT CALLBACK WndProc(
_In_ HWND hWnd,
_In_ UINT message,
_In_ WPARAM wParam,
_In_ LPARAM lParam
);
窗口过程函数示例:
LRESULT CALLBACK WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam) { switch (uMsg) { case WM_SIZE: { int width = LOWORD(lParam); // Macro to get the low-order word. int height = HIWORD(lParam); // Macro to get the high-order word. // Respond to the message: OnSize(hwnd, (UINT)wParam, width, height); } break; } } void OnSize(HWND hwnd, UINT flag, int width, int height) { // Handle resizing }
2.注册定义好的窗口类
我们通过RegisterClassEx(&wcex)来将定义好的窗口类向操作系统注册。tip:在调试过程中,我们也可以对该函数下断点,再查看分析参数指向内存地址来定位到窗口过程函数的地址。
3.创建窗口
使用 CreateWindowEx 函数将窗口实例化,这里是通过实例句柄将这个窗口和窗口类关联到一起的,实例句柄是当前代码模块的句柄(该程序实例或dll的模块),窗口是在不同实例中是可以重名的(窗口它们都是通过实例区分的)。
HWND hWnd = CreateWindowEx( WS_EX_OVERLAPPEDWINDOW, szWindowClass, szTitle, WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, CW_USEDEFAULT, 500, 100, NULL, NULL, hInstance, NULL );
4.将窗口显示出来
接下来就是通过showWindow函数来显示窗口。
示例如下:
ShowWindow(hWnd,
nCmdShow);
UpdateWindow(hWnd);
5.定义消息循环
这个过程是一个套路,没什么变化。简单来说,就是定义一个循环,从消息队列中取消息,然后翻译消息(消息结构体中的部分字段需要解码),最后分发消息给窗口过程函数处理。
GetMessage函数用于查看消息队列中是否有消息并取出。
TranslateMessage函数用于将虚拟码消息转换为字符消息。
DispatchMessage函数用于将消息分发到窗口过程中函数。
注意,消息队列有两个,一个是系统消息队列,另一个应用程序自己的消息队列(每一个应用程序都有自己的消息队列)。消息产生后是先到系统消息队列,然后分发到应用程序消息队列里等待被处理。
消息循环示例如下:
MSG msg; while (GetMessage(&msg, NULL, 0, 0)) { TranslateMessage(&msg); DispatchMessage(&msg); } return (int) msg.wParam;
消息循环工作原理如下图所示:
三、逆向时经验
逆向窗口程序时,我们要从WinMain函数入手,找到窗口类定义过程中关于窗口过程函数的赋值部分,追踪到窗口过程函数中去分析,分析其中每一条消息对应的处理过程,从而还原出程序功能逻辑。
窗口过程函数并不难找到,我们只需要在 RegisterClass函数调用前查看(WNDCLASSA *)lpWndClass对象,该对象成员中有窗口过程函数。
麻烦的是控件的消息处理函数,MFC框架通过消息映射表来绑定消息和消息处理函数的关系。
开发者使用宏“DECLARE_MESSAGE_MAP” 和 “END_MESSAGE_MAP”来定义消息映射表。
示例:
BEGIN_MESSAGE_MAP(CHelloDlg, CDialogEx) ON_WM_SYSCOMMAND() ON_WM_PAINT() ON_WM_QUERYDRAGICON() ON_BN_CLICKED(IDC_BUTTON1, &CHelloDlg::OnBnClickedButton1) END_MESSAGE_MAP()
对应使用的结构体为AFX_MSGMAP,定义如下:
struct AFX_MSGMAP { const AFX_MSGMAP* (PASCAL* pfnGetBaseMap)(); // 父类消息映射表 const AFX_MSGMAP_ENTRY* lpEntries; // 自身消息映射表 };
AFX_MSGMAP_ENTRY结构体定义如下:
struct AFX_MSGMAP_ENTRY { UINT nMessage; // windows message UINT nCode; // control code or WM_NOTIFY code UINT nID; // control ID (or 0 for windows messages) UINT nLastID; // used for entries specifying a range of control id's UINT_PTR nSig; // signature type (action) or pointer to message # AFX_PMSG pfn; // routine to call (or special value) };
识别AFX_MSGMAP_ENTRY结构体的思路有:
内存搜索该结构体
我们可以通过Resource Hacker查看控件ID和消息ID,也就是nID和nCode。根据它们对应的十六进制数据在内存中搜索。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· 没有Manus邀请码?试试免邀请码的MGX或者开源的OpenManus吧
· 园子的第一款AI主题卫衣上架——"HELLO! HOW CAN I ASSIST YOU TODAY
· 【自荐】一款简洁、开源的在线白板工具 Drawnix