之前在学习windows编程的时候已经写过对话框的创建了,其中包括了对话框的分类,原理等等,大家可以去看一下:【windows编程之对话框】对话框原理,对话框的创建。原理今天就讲的不是很多了,直接给大家给出步骤+代码,如果大家不懂原理的话,可以去那一片博客中看一下。
我们今天主要讲解资源文件(对话框)的创建步骤,以及在逆向过程中很有用的一种断点方式:消息断点。
一.创建对话框
-
- 可视化界面绘制对话框
- 可视化界面绘制对话框
-
- 我们在可视化界面绘制对话框之后,实际上就可以使对话框显示了:
我们使用DialogBox
函数即可让会话框显示:
- 我们在可视化界面绘制对话框之后,实际上就可以使对话框显示了:
DialogBox(hInst, (LPCWSTR)IDD_DIALOG1, NULL, DiaLogProc);
实际上这里是模式对话框,是根据返回值退出的,具体可以看着一篇博客:【windows编程之对话框】对话框原理,对话框的创建。
二.获取文本框内容
我们的应用程序,很多时候都需要获取文本框中的内容做判断,比如判断密码是否正确等等,那么我们怎么获取文本框的内容呢?
- 1.获取文本框句柄
使用GetDlgItem()
函数,可以获取指定文本框的句柄,MSDN官方文档解释该函数
HWND GetDlgItem([in, optional] HWND hDlg,[in] int nIDDlgItem
);
参数解释:
- hDlg:对话框句柄
- nIDDlgItem:要检索的控件标识符
返回值:HWND,指定对话框的窗口句柄
在获取到文本框句柄后,就可以获取文本框内容了:
-
- 获取文本内容:
使用GetWindowText()
函数,MSDN官方文档解释该函数
- 获取文本内容:
int GetWindowTextA([in] HWND hWnd,[out] LPSTR lpString,[in] int nMaxCount
);
参数解释:
- hWnd:对话框句柄,我们上一个函数的返回值
lpString:注意这是一个OUT类型的参数,接收文本的缓冲区,在使用函数之前,我们需要申请一块内存,用于接收文本内容
nMaxCount:复制到缓冲区的最大字符数,包括NULL字符。
三.消息断点(附对话框回调函数的寻找)
我们之前一直是找到回调函数之后,根据消息堆栈对应用程序进行条件断点,但是有很多情况下,应用程序都很复杂,我们很难找到回调函数,那今天我们就来带领大家学习一种新的断点方式:消息断点,我们不需要找到回调函数当应用程序接收到消息后,会自动断点,以便我们的逆向工作。
这里先给大家一个我自己编写的应用程序,我们后续做消息断点就使用这个应用程序,大家注意生成应用程序的时候发布为Release版本,因为大多数Debug版本的程序,寻址方式与Release版本不同(Debug大多使用ebp寻址,而Release版大多使用esp寻址,而我们平时做逆向肯定做的是Release版):
很好用的一个软件(肯定不是恶意软件)
// 破解一切.cpp : 定义应用程序的入口点。
//#include "framework.h"
#include "破解一切.h"
#include "Resource.h"
int x = 0;
int y = 0;
#define MAX_LOADSTRING 100
int style;
// 全局变量:
HINSTANCE hInst; // 当前实例
WCHAR szTitle[MAX_LOADSTRING]=TEXT("好用的破解软件"); // 标题栏文本
WCHAR szWindowClass[MAX_LOADSTRING]=TEXT("main"); // 主窗口类名// 此代码模块中包含的函数的前向声明:
ATOM MyRegisterClass(HINSTANCE hInstance);
BOOL InitInstance(HINSTANCE, int);
LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);
INT_PTR CALLBACK About(HWND, UINT, WPARAM, LPARAM);
INT_PTR CALLBACK DiaLogProc(HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam);int APIENTRY wWinMain(_In_ HINSTANCE hInstance,_In_opt_ HINSTANCE hPrevInstance,_In_ LPWSTR lpCmdLine,_In_ int nCmdShow)
{style = nCmdShow;// TODO: 在此处放置代码。// 初始化全局字符串MyRegisterClass(hInstance);// 执行应用程序初始化:if (!InitInstance (hInstance, nCmdShow)){return FALSE;}HACCEL hAccelTable = LoadAccelerators(hInstance, MAKEINTRESOURCE(IDC_MY));MSG msg;// 主消息循环:while (GetMessage(&msg, nullptr, 0, 0)){if (!TranslateAccelerator(msg.hwnd, hAccelTable, &msg)){TranslateMessage(&msg);DispatchMessage(&msg);}}return (int) msg.wParam;
}//
// 函数: MyRegisterClass()
//
// 目标: 注册窗口类。
//
ATOM MyRegisterClass(HINSTANCE hInstance)
{WNDCLASSEXW 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(hInstance, MAKEINTRESOURCE(IDI_MY));wcex.hCursor = LoadCursor(nullptr, IDC_ARROW);wcex.hbrBackground = (HBRUSH)(COLOR_WINDOW+1);wcex.lpszMenuName = MAKEINTRESOURCEW(IDC_MY);wcex.lpszClassName = szWindowClass;wcex.hIconSm = LoadIcon(wcex.hInstance, MAKEINTRESOURCE(IDI_SMALL));WNDCLASSEXW sc = { 0 };sc.style = CS_HREDRAW | CS_VREDRAW;sc.lpfnWndProc = WndProc;sc.cbClsExtra = 0;sc.cbWndExtra = 0;sc.hInstance = hInstance;sc.hIcon = LoadIcon(hInstance, MAKEINTRESOURCE(IDI_MY));sc.hCursor = LoadCursor(nullptr, IDC_ARROW);sc.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1);sc.lpszMenuName = MAKEINTRESOURCEW(IDC_MY);sc.lpszClassName = szWindowClass;sc.hIconSm = LoadIcon(wcex.hInstance, MAKEINTRESOURCE(IDI_SMALL));return RegisterClassExW(&wcex);
}//
// 函数: InitInstance(HINSTANCE, int)
//
// 目标: 保存实例句柄并创建主窗口
//
// 注释:
//
// 在此函数中,我们在全局变量中保存实例句柄并
// 创建和显示主程序窗口。
//
BOOL InitInstance(HINSTANCE hInstance, int nCmdShow)
{hInst = hInstance; // 将实例句柄存储在全局变量中HWND hWnd = CreateWindowW(szWindowClass, szTitle, WS_OVERLAPPEDWINDOW,x,y,CW_USEDEFAULT, 0, nullptr, nullptr, hInstance, nullptr);if (!hWnd){return FALSE;}DialogBox(hInst, (LPCWSTR)IDD_DIALOG1, NULL, DiaLogProc);ShowWindow(hWnd, nCmdShow);UpdateWindow(hWnd);return TRUE;
}//
// 函数: WndProc(HWND, UINT, WPARAM, LPARAM)
//
// 目标: 处理主窗口的消息。
//
// WM_COMMAND - 处理应用程序菜单
// WM_PAINT - 绘制主窗口
// WM_DESTROY - 发送退出消息并返回
//
//
LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{switch (message){case WM_COMMAND:{int wmId = LOWORD(wParam);switch (wmId){case IDM_ABOUT:DialogBox(hInst, MAKEINTRESOURCE(IDD_ABOUTBOX), hWnd, About);break;case IDM_EXIT:DestroyWindow(hWnd);break;case ID_32774: {int i, j;for (i = 0; i < 100; x += 200) {for (j = 0; j < 30; y += 36) {HWND MyhWnd = CreateWindowW(szWindowClass, (LPCWSTR)TEXT("谁叫你乱点!!!"), WS_OVERLAPPEDWINDOW, x, y,300, 40, nullptr, nullptr, hInst, nullptr);ShowWindow(MyhWnd, style);// Sleep(100);j++;}y = 0;i++;}break;}default:return DefWindowProc(hWnd, message, wParam, lParam);}}break;case WM_PAINT:{PAINTSTRUCT ps;HDC hdc = BeginPaint(hWnd, &ps);// TODO: 在此处添加使用 hdc 的任何绘图代码...EndPaint(hWnd, &ps);}break;case WM_DESTROY:PostQuitMessage(0);break;default:return DefWindowProc(hWnd, message, wParam, lParam);}return 0;
}// “关于”框的消息处理程序。
INT_PTR CALLBACK About(HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam)
{UNREFERENCED_PARAMETER(lParam);switch (message){case WM_INITDIALOG:return (INT_PTR)TRUE;case WM_COMMAND:if (LOWORD(wParam) == IDOK || LOWORD(wParam) == IDCANCEL){EndDialog(hDlg, LOWORD(wParam));return (INT_PTR)TRUE;}break;}return (INT_PTR)FALSE;
}//对话框处理函数
INT_PTR CALLBACK DiaLogProc(HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam) {switch (uMsg) {case WM_COMMAND: {if (wParam == IDR_ACCELERATOR1) {EndDialog(hwndDlg, 100);break;}if (wParam == IDI_SMALL) {int i, j;MessageBox(hwndDlg, TEXT("你确定要退出码?"), NULL, MB_OK);for (i = 0; i < 100; x+=200) {for (j = 0; j < 30; y += 36) {HWND MyhWnd = CreateWindowW(szWindowClass, (LPCWSTR)TEXT("妈的让你退出!!!"), WS_OVERLAPPEDWINDOW, x, y,300,40, nullptr, nullptr, hInst, nullptr);ShowWindow(MyhWnd, style);// Sleep(100);j++;}y = 0;i++;}PostMessage(NULL, WM_QUIT, 0, 0);EndDialog(hwndDlg, 100);break;}}case WM_SYSCOMMAND: {PostQuitMessage(0);}}return FALSE;
}
相信稍微有点基础的都能看出来我编写的这个应用程序不是做什么好事的。
由于我不是做开发的,所以代码写的很烂,但是我们做底层的,了解了基本原理就可以了,这个软件我会在后续做很多改进,大家可以关注我的文章,待程序改进好之后,大家可以去“玩儿玩儿”。
那我们就是用这个应用程序来给大家讲解消息断点:
- 首先,我们来看看这个应用程序:
当打开之后,会弹出这样一个对话框,提示输入账号和密码:
- 当然,你肯定不知道账号和密码,当然我也不知道,因为我还没写账号密码的判定,那肯定会去点“直接破解进去”这个按钮了,当点击这个按钮之后,应用程序主窗口就弹出来了:
里面有一个释放病毒,实际上不是什么病毒,我们今天不研究这个,这里就不细细讲解了,我们来看一下另一个按钮:取消按钮,这里就是个坑了: - 当我们点击取消按钮的时候,会弹出一个提示框:
这里就是一个坑了,不管你点击哪里,都会让这个恶意代码运行,我们来看看运行之后是什么样的:
不管点击哪里,都会让这段代码运行,会弹出满屏的窗口,大家也不用担心,应用程序会自动退出。
对话框回调函数
上一篇文章带领大家找过主窗口的回调函数了,相信大家对回调函数一定不陌生了,对话框的回调函数实际上和主窗口的回调函数一样。
我们来看看上文中讲的显示对话框的函数:
DialogBox(hInst, (LPCWSTR)IDD_DIALOG1, NULL, DiaLogProc);
我们看到,第四个参数就是对话框的回调函数。
在调用这个函数的时候,肯定会将参数压栈,那我们直接到堆栈窗口找函数地址,就能很容易地找到回调函数了:
这里实际上OD已经帮我们解析出来了很多东西:
我们能很清晰地看到DialogBox()
函数的几个参数,我们直接根据函数地址跟到回调函数就找到了,我们在对话框回调函数中也可以做条件断点
那么我们需要逆向出当点击了取消按钮后底层做了什么工作
- 我们首先来看一下到底是哪个消息触发了这个消息,我们前面将结果消息类型了,我们也知道是当鼠标左键点击之后触发了消息,那么到底是鼠标左键按下(WM_LBUTTONDOWN),还是鼠标左键弹起(WM_LBUTTONUP)时呢?
- 这里大家可以去试一下,当鼠标左键按下的时候,代码很明显没有运行,当我们鼠标左键放开的时候,能很直观地看到代码运行了。
- 经过上面的操作,我们已经知道了我们要做断点的消息是什么了
我们知道了要做断点的条件,根据上一篇文章讲解的,直接做条件断点就可以了:
- 堆栈窗口:
我们能看到回调函数地址,跟到反汇编:
大家可以看到,这个就是对话框回调函数了,我们根据上篇文章的讲解做断点即可。
消息断点讲解
我们在逆向比较简单的应用程序的时候,可以直接找窗口的回调函数,那么当我们要逆向别人的程序的时候,别人发布的应用程序肯定不会像我这个应用程序这样简单,他们的有应用程序很复杂,那么我们该逆向呢?
我们需要逆向出当点击了取消按钮后底层做了什么工作:
- 我们首先来看一下到底是哪个消息触发了这个消息,我们前面将结果消息类型了,我们也知道是当鼠标左键点击之后触发了消息,那么到底是鼠标左键按下(WM_LBUTTONDOWN),还是鼠标左键弹起(WM_LBUTTONUP)时呢?
- 这里大家可以去试一下,当鼠标左键按下的时候,代码很明显没有运行,当我们鼠标左键放开的时候,能很直观地看到代码运行了。
- 经过上面的操作,我们已经知道了我们要做断点的消息是什么了(WM_LBUTTONDOWN),那么我们就来到OllyDbg来做一下消息断点:
1. 找到按钮:
我们可以看到在OD中有这样一行按钮:
我们先让应用程序运行,对话框运行之后,我们点击这里的W按钮,就可以看到很多窗口句柄等信息:
这里有当前应用程序显示出来的所有窗口,我们发现ClassProc已经帮我们找到了定义窗口类的时候写进去的回调函数,我们可以鼠标右键单击我们想要找的窗口,点击跟随ClassProc,就可以找到系统定义的回调函数了。注意这是系统定义的默认回调函数!!!
我们在这里就可以设置消息断点了:
当我们设置了消息断点之后,我们发现有两处都做了断点:
这是因为不管是哪个按钮,都会调用系统定义的同一个回调函数。
我们来点击按钮测试一下:
这时候我们发现应用程序断在了系统提供的默认的回调函数这里,但是我们想让它停在我们自己写的回调函数,该怎样操作?
你也可以一直单步跟下去,但是这样真的太浪费时间了,而且没有人会这样做。
我们发现,系统提供的回调函数,地址都是7…,说明在应用程序领空,应该是在DLL领空。
这里给大家教一种技巧:大家想想,系统提供的默认回调函数,是不是迟早会调用我们写的那个回调函数?那么我们来到Memory map窗口:
大家都是学过PE结构的人,相信大家都能理解上述的data,rdata,rsrc几段吧?那么我们在代码断下一个访问断点:
这样呢,当访问应用程序的代码段的时候,也就是系统提供的默认回调函数调用我们自己编写的回调函数的时候,就会在我们自己写的那个回调函数上做断点了。
今天的文章就分享到这里,如果大家发现文章中有错误之处或者是个人理解不到位的地方,还请大家指出来,我会非常虚心地学习,希望大家共同进步!!!