描述
- 二次开发的含义:通过直接编辑二进制,来修改已编译好的程序,实现目标功能
- 本文的原程序模拟一个windows是最常见的采用事件循环机制的窗口程序,通过二次开发,给这个程序上锁,加上一个验证身份框,只有输对用户名密码,才能正常使用程序功能
原程序
- 创建一个窗口,加入事件循环,响应窗口消息
- 代码如下
#include <windows.h>
#include <cstdio>
#define ID_EDIT 1 // Added this line to define ID_EDIT
LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
switch (msg)
{
case WM_CLOSE:
DestroyWindow(hwnd);
break;
case WM_DESTROY:
PostQuitMessage(0);
break;
default:
return DefWindowProc(hwnd, msg, wParam, lParam);
}
return 0;
}
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
LPSTR lpCmdLine, int nCmdShow)
{
WNDCLASSEX wc;
HWND hwnd;
MSG Msg;
HWND hEdit;
//Step 1: Registering the Window Class
wc.cbSize = sizeof(WNDCLASSEX);
wc.style = 0;
wc.lpfnWndProc = WndProc;
wc.cbClsExtra = 0;
wc.cbWndExtra = 0;
wc.hInstance = hInstance;
wc.hIcon = LoadIcon(NULL, IDI_APPLICATION);
wc.hCursor = LoadCursor(NULL, IDC_ARROW);
wc.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1);
wc.lpszMenuName = NULL;
wc.lpszClassName = "WindowClass";
wc.hIconSm = LoadIcon(NULL, IDI_APPLICATION);
if (!RegisterClassEx(&wc))
{
MessageBox(NULL, "Window Registration Failed!", "Error!",
MB_ICONEXCLAMATION | MB_OK);
return 0;
}
// Step 2: Creating the Window
hwnd = CreateWindowEx(
WS_EX_CLIENTEDGE,
"WindowClass",
"成功",
WS_OVERLAPPEDWINDOW,
CW_USEDEFAULT, CW_USEDEFAULT, 500, 300,
NULL, NULL, hInstance, NULL);
if (hwnd == NULL)
{
MessageBox(NULL, "Window Creation Failed!", "Error!",
MB_ICONEXCLAMATION | MB_OK);
return 0;
}
// Step 3: Creating the Edit Control
hEdit = CreateWindowEx(WS_EX_CLIENTEDGE, "EDIT", "",
WS_CHILD | WS_VISIBLE | ES_MULTILINE | ES_AUTOVSCROLL | ES_AUTOHSCROLL,
50, 50, 200, 100, hwnd, (HMENU)ID_EDIT, hInstance, NULL);
if (hEdit == NULL)
{
MessageBox(NULL, "Edit Control Creation Failed!", "Error!",
MB_ICONEXCLAMATION | MB_OK);
return 0;
}
// Step 4: Setting the Text of the Edit Control
SendMessage(hEdit, WM_SETTEXT, NULL, (LPARAM)"恭喜你成功进入游戏!");
ShowWindow(hwnd, nCmdShow);
UpdateWindow(hwnd);
for (int i = 0; i++; i < 10) {
printf("第%d轮\n", i);
}
// Step 5: The Message Loop
while (GetMessage(&Msg, NULL, 0, 0) > 0)
{
TranslateMessage(&Msg);
DispatchMessage(&Msg);
}
return Msg.wParam;
}
- 运行效果:运行Msg.exe,效果如下
对原程序进行二次开发,上锁
原理
- 将要添加的功能编写为dll,注入到目标程序中
- 在目标程序中找到一个合适的位置,调用dll中的函数,特别要注意堆栈平衡和参数的设置,还要注意不能破坏原有程序的指令,如果原有指令被覆盖,需要在调用dll函数后再将被覆盖的指令执行一遍
步骤
- 编写dll:创建一个注册窗口函数MyCreateWindow,
GetMessage
部分不用写,用原程序的就行,并编写窗口回调函数MyWndProc,在其中处理用户输入,将上述两个函数编写到dll中导出,只需要给原程序添加一个对MyCreateWindow的调用,就可以创建注册窗口并响应用户输入,dll部分的代码如下
// dllmain.cpp : 定义 DLL 应用程序的入口点。
#include <windows.h>
BOOL APIENTRY DllMain(HMODULE hModule,
DWORD ul_reason_for_call,
LPVOID lpReserved
)
{
switch (ul_reason_for_call)
{
case DLL_PROCESS_ATTACH:
case DLL_THREAD_ATTACH:
case DLL_THREAD_DETACH:
case DLL_PROCESS_DETACH:
break;
}
return TRUE;
}
extern "C" __declspec(dllexport) LRESULT CALLBACK MyWndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
{
static HWND hwndUsername, hwndPassword, hwndButton;
switch (message)
{
case WM_CREATE:
hwndUsername = CreateWindow(TEXT("edit"), NULL,
WS_CHILD | WS_VISIBLE | WS_BORDER,
50, 50, 200, 25,
hwnd, (HMENU)1, NULL, NULL);
hwndPassword = CreateWindow(TEXT("edit"), NULL,
WS_CHILD | WS_VISIBLE | WS_BORDER | ES_PASSWORD,
50, 100, 200, 25,
hwnd, (HMENU)2, NULL, NULL);
hwndButton = CreateWindow(TEXT("button"), TEXT("OK"),
WS_CHILD | WS_VISIBLE | BS_DEFPUSHBUTTON,
100, 150, 80, 25,
hwnd, (HMENU)3, NULL, NULL);
break;
case WM_COMMAND:
if (LOWORD(wParam) == 3)
{
TCHAR username[20], password[20];
GetWindowText(hwndUsername, username, 20);
GetWindowText(hwndPassword, password, 20);
if (lstrcmp(password, TEXT("test")) != 0) {
MessageBox(hwnd, TEXT("密码错误,退出程序!"), TEXT("Error"), MB_OK);
exit(0);
}
else {
MessageBox(hwnd, TEXT("密码正确,继续你的游戏!"), TEXT("Success"), MB_OK);
SendMessage(hwnd, WM_CLOSE, 0, 0);
}
}
break;
case WM_DESTROY:
exit(0);
break;
case WM_CLOSE:
exit(0);
break;
}
return DefWindowProc(hwnd, message, wParam, lParam);
}
extern "C" __declspec(dllexport) int WINAPI MyCreateWindow(HINSTANCE hInstance, HINSTANCE hPrevInstance, PSTR szCmdLine, int iCmdShow)
{
static TCHAR szAppName[] = TEXT("HelloWin");
HWND hwnd2;
WNDCLASS wndclass;
wndclass.style = CS_HREDRAW | CS_VREDRAW;
wndclass.lpfnWndProc = MyWndProc;
wndclass.cbClsExtra = 0;
wndclass.cbWndExtra = 0;
wndclass.hInstance = hInstance;
wndclass.hIcon = LoadIcon(NULL, IDI_APPLICATION);
wndclass.hCursor = LoadCursor(NULL, IDC_ARROW);
wndclass.hbrBackground = (HBRUSH)GetStockObject(WHITE_BRUSH);
wndclass.lpszMenuName = NULL;
wndclass.lpszClassName = szAppName;
if (!RegisterClass(&wndclass))
{
MessageBox(NULL, TEXT("This program requires Windows NT!"),
szAppName, MB_ICONERROR);
return 0;
}
hwnd2 = CreateWindow(szAppName, // window class name
TEXT("The Hello Program"), // window caption
WS_OVERLAPPEDWINDOW, // window style
CW_USEDEFAULT, // initial x position
CW_USEDEFAULT, // initial y position
500, // initial x size
300, // initial y size
NULL, // parent window handle
NULL, // window menu handle
hInstance, // program instance handle
NULL); // creation parameters
ShowWindow(hwnd2, iCmdShow);
UpdateWindow(hwnd2);
}
- 将dll注入到原程序:通过PE Tools,在输入表中添加MsgDll和MyCreateWindow函数
- patch原程序:运行x64dbg,在目标程序中选取一处存放不重要代码的位置(更好的做法是选取区块间的间隙并跳转执行,这里偷个懒随便找了个不重要的赋值语句(1B40处))
- 首先给我们的MyCreateWindow函数传入参数,这里需要传入WinMain的四个参数,可以看到程序刚进入函数时将四个参数保存到了栈上
- 那么我们只要从栈上取出四个参数并放入寄存器,就可以给MyCreateWindow提供参数了
- 放好参数好,准备写入
call MyCreateWindow
,call指令的十六进制为ff 15
,地址的计算结果为1552A
- 写入
call MyCreateWindow
,可以看到反汇编器成功解析处call函数的地址
- 由于前面写入的指令破坏了后面的指令,所以加一个jmp跳过这些坏字节
- 被调用函数MyCrateWindow已经进行了堆栈平衡,所以不需要在调用函数中抬高rsp
- 本来到这里已经可以了,但出了点小问题,程序中间将rsp抬高了258个字节,所以后面从栈上取参数的位置也要相应变化,加上258个字节
- 修改后程序如下
运行效果
- 将MsgDll同Msg.exe放到同一目录下,运行Msg.exe,可以看到在程序正常窗口前,弹出了密码验证框,输错后程序退出