之前在学习windows编程的时候已经写过对话框的创建了,其中包括了对话框的分类,原理等等,大家可以去看一下:【windows编程之对话框】对话框原理,对话框的创建。原理今天就讲的不是很多了,直接给大家给出步骤+代码,如果大家不懂原理的话,可以去那一片博客中看一下。
我们今天主要讲解资源文件(对话框)的创建步骤,以及在逆向过程中很有用的一种断点方式:消息断点。
DialogBox(hInst, (LPCWSTR)IDD_DIALOG1, NULL, DiaLogProc);
实际上这里是模式对话框,是根据返回值退出的,具体可以看着一篇博客:【windows编程之对话框】对话框原理,对话框的创建。
我们的应用程序,很多时候都需要获取文本框中的内容做判断,比如判断密码是否正确等等,那么我们怎么获取文本框的内容呢?
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()
函数的几个参数,我们直接根据函数地址跟到回调函数就找到了,我们在对话框回调函数中也可以做条件断点
那么我们需要逆向出当点击了取消按钮后底层做了什么工作
我们知道了要做断点的条件,根据上一篇文章讲解的,直接做条件断点就可以了:
我们在逆向比较简单的应用程序的时候,可以直接找窗口的回调函数,那么当我们要逆向别人的程序的时候,别人发布的应用程序肯定不会像我这个应用程序这样简单,他们的有应用程序很复杂,那么我们该逆向呢?
我们需要逆向出当点击了取消按钮后底层做了什么工作:
我们可以看到在OD中有这样一行按钮:
我们先让应用程序运行,对话框运行之后,我们点击这里的W按钮,就可以看到很多窗口句柄等信息:
这里有当前应用程序显示出来的所有窗口,我们发现ClassProc已经帮我们找到了定义窗口类的时候写进去的回调函数,我们可以鼠标右键单击我们想要找的窗口,点击跟随ClassProc,就可以找到系统定义的回调函数了。注意这是系统定义的默认回调函数!!!
我们在这里就可以设置消息断点了:
当我们设置了消息断点之后,我们发现有两处都做了断点:
这是因为不管是哪个按钮,都会调用系统定义的同一个回调函数。
我们来点击按钮测试一下:
这时候我们发现应用程序断在了系统提供的默认的回调函数这里,但是我们想让它停在我们自己写的回调函数,该怎样操作?
你也可以一直单步跟下去,但是这样真的太浪费时间了,而且没有人会这样做。
我们发现,系统提供的回调函数,地址都是7…,说明在应用程序领空,应该是在DLL领空。
这里给大家教一种技巧:大家想想,系统提供的默认回调函数,是不是迟早会调用我们写的那个回调函数?那么我们来到Memory map窗口:
大家都是学过PE结构的人,相信大家都能理解上述的data,rdata,rsrc几段吧?那么我们在代码断下一个访问断点:
这样呢,当访问应用程序的代码段的时候,也就是系统提供的默认回调函数调用我们自己编写的回调函数的时候,就会在我们自己写的那个回调函数上做断点了。
今天的文章就分享到这里,如果大家发现文章中有错误之处或者是个人理解不到位的地方,还请大家指出来,我会非常虚心地学习,希望大家共同进步!!!