- Windows平台的桌面开发
- C++:MFC、Qt
- C#: WinForm、WPF
1、调试信息打印方法:
MFC基于对话框的程序,不是基于对话框的程序,没法用打印;
- 打印到输出端;
- TRACE函数:类似于C语言的printf,只能在DEBUG调试模式下看到打印信息(F5启动)
TRACE("age is %d\n", age); // age is 20
- AfxMessageBox函数
int age = 10;
CString str;
str.Format(CString("age is %d"), age);
AfxMessageBox(str);
AfxMessageBox(CString("something wrong"));
- MessageBox函数:只在CWnd的子类中使用,功能比AfxMessageBox多
- WIN32 就有MessageBox;AfxMessageBox是 MFC 封装的;
- DLG继承自CDialogEx;继承自CDialog;继承自CWnd;
- MB_YESNOCANCEL 使能三个按键;MB_ICONWARNING改图标为warning
int age = 10;
CString str;
str.Format(CString("age is %d"), age);
MessageBox(str, CString("警告"), MB_YESNOCANCEL | MB_ICONWARNING);
- 自定义log宏,简化打印
\
用来告诉宏,下一行也属于宏;__VA_ARGS__
作为多参数替换;
#define log(fmt, ...) \
CString str; \
str.Format(CString(fmt), __VA_ARGS__); \
AfxMessageBox(str);
2、事件声明与注册
- 绑定事件
ON_BN_CLICKED
按键单击 绑定到 成员函数;
- 第一个参数为控件ID; 通过宏定义在
Resource.h
中定义- 第二个参数为:成员函数地址;(函数名就是地址,取地址符写不写一样的)
BEGIN_MESSAGE_MAP(CPVZCheaterDlg, CDialogEx)
ON_WM_SYSCOMMAND()
ON_WM_PAINT()
ON_WM_QUERYDRAGICON()
ON_BN_CLICKED(IDC_COURSE, &CPVZCheaterDlg::OnBnClickedCourse)
ON_BN_CLICKED(IDC_KILL, &CPVZCheaterDlg::OnBnClickedKill)
ON_BN_CLICKED(IDC_SUN, &CPVZCheaterDlg::OnBnClickedSun)
END_MESSAGE_MAP()
- 事件处理函数需要声明+定义
afx_msg
宏用于标识函数是用于事件处理的
- 自动事件注册 – (也可以直接双击控件)
- 对于按键和checkbox;或者其他控件都可使用
3、控件使用
- 绑定成员变量 – 手动
- 声明 按键成员变量
CButton m_bnKill;
- 通过
DDX_Control(pDX, IDC_KILL, m_bnKill);
将变量绑定到控件ID上;
- 绑定成员变量 – 自动
- 控件上右键
添加变量
;- 修改绑定到控件的 成员变量的类型、名称等;
- 单选框的状态读取和修改
- 方法1
IsDlgButtonChecked
用来判断控件是否被选择;CheckDlgButton
用来修改;
bool checked = IsDlgButtonChecked(IDC_KILL);
if (m_bnSun.GetCheck()) {
log("勾选");
CheckDlgButton(IDC_KILL,true);
} else {
log("没有勾选");
}
- 方法2
GetDlgItem(IDC_KILL);
返回cwnd 类型。用子类CButton
取即可;- 然后调用
int CButton::GetCheck() const
- void CButton::SetCheck(int nCheck) 用来修改
CButton* button = (CButton*)GetDlgItem(IDC_KILL);
bool state = button->GetCheck();
button->SetCheck(true);
- 方法3
CButton m_bnKill;
成员变量 通过DDX_Control(pDX, IDC_KILL, m_bnKill);
绑定到控件ID上;- 然后调用
int CButton::GetCheck() const
- void CButton::SetCheck(int nCheck) 用来修改
if (m_bnSun.GetCheck()) {
log("勾选");
m_bnSun.SetCheck(true);
} else {
log("没有勾选");
}
4、打开URL
- (Uniform Resource Locator,统一资源定位器),它是WWW的统一资源定位标志,就是指网络地址。
ShellExecute
是全局函数
ShellExecute(NULL,
CString("open"),
CString("https://ke.qq.com/course/336509"),
NULL, NULL,
SW_SHOWNORMAL);
//ShellExecuteW(
// _In_opt_ HWND hwnd,
// _In_opt_ LPCWSTR lpOperation,
// _In_ LPCWSTR lpFile,
// _In_opt_ LPCWSTR lpParameters,
// _In_opt_ LPCWSTR lpDirectory,
// _In_ INT nShowCmd)
5、多线程
- windows 多线程:
- 多线程开启API
CreateThread
;返回句柄(ID,void* 类型);线程通过句柄来控制;
//HANDLE
//WINAPI
//CreateThread(
// _In_opt_ LPSECURITY_ATTRIBUTES lpThreadAttributes,
// _In_ SIZE_T dwStackSize,
// _In_ LPTHREAD_START_ROUTINE lpStartAddress,
// _In_opt_ __drv_aliasesMem LPVOID lpParameter,
// _In_ DWORD dwCreationFlags,
// _Out_opt_ LPDWORD lpThreadId
//);
m_monitorThread = CreateThread(NULL, NULL, monitorThreadFunc, NULL, NULL, NULL);
其中传的函数指针类型为:返回
DWORD
参数LPVOID lpThreadParameter
typedef DWORD (WINAPI *PTHREAD_START_ROUTINE)(
LPVOID lpThreadParameter
);
- MFC 多线程:
- 多线程开启API
CreateThread
;返回句柄(ID,void* 类型);线程通过句柄来控制;
//CWinThread* AFXAPI AfxBeginThread(AFX_THREADPROC pfnThreadProc, LPVOID pParam,
// int nPriority = THREAD_PRIORITY_NORMAL, UINT nStackSize = 0,
// DWORD dwCreateFlags = 0, LPSECURITY_ATTRIBUTES lpSecurityAttrs = NULL);
AfxBeginThread(MyTimerProc, this);
其中传的函数指针类型为:返回
DWORD
参数LPVOID lpThreadParameter
typedef DWORD (WINAPI *PTHREAD_START_ROUTINE)(
LPVOID lpThreadParameter
);
6、Windows平台软件必备知识
- 文件格式:PE文件
- 汇编语言:x86、x64汇编
- 工具:Ollydbg // 可以反汇编机器码
- Windows API
- windows 下可执行文件为 PE 格式
- 将exe 格式机器码转为汇编;(工具:Ollydbg)
- 修改汇编中的某些指令,或者直接填充nop;来达到自己的要求;
- 将修改的代码再生成exe,就成为了破结过的代码了;
- 加壳与脱壳
一般的软件破解思路
加壳和脱壳
- 外挂的本质
- 常见的外挂功能有2种做法;其实数据和代码并没有本质区别, 在内存中都是0和1
- 修改内存中的数据(Cheat Engine,可用来观察进程的内存占用)
- 修改内存中的代码
- 其实都是将进程对应内存空间的一部分修改掉;
- 代码段的内存地址是不变的,相对简单;全局变量和代码端这些,在进程内存中是不变的
- 内存数据中的空间,除非是全局变量,否则会发生变化;不好实现;
- 改内存的话:
写个查看内存占用的程序,找到要改的内存地址,填充即可;
- 修改内存中的代码:
- 找出所对应的内存空间是哪块代码修改的,然后分析这块代码,去改即可;
- 其实,改代码也可以只改考进来的内存;即将载入内存的二进制代码改掉即可。而不是改exe里面的二进制代码
void CPVZCheaterDlg::OnBnClickedKill() {
if (m_bnKill.GetCheck()) { // 需要
BYTE data[] = {0xFF, 0x90, 0x90};
WriteMemory(data, sizeof(data), 0x00531310);
} else { // 不需要
BYTE data[] = {0x7c, 0x24, 0x20};
WriteMemory(data, sizeof(data), 0x00531310);
}
}
1.1、监控窗口:
- 多线程监控窗口; // 前面的多线程部分有讲解;
- 监控窗口函数:
FindWindowW
//WINUSERAPI
//HWND
//WINAPI
//FindWindowW(
// _In_opt_ LPCWSTR lpClassName,
// _In_opt_ LPCWSTR lpWindowName);
- 找到窗口名和类名 (用spy++查找)
g_dlg
为类指针,在初始化的时候将this 赋值给这个指针;FindWindowW
通过窗口名称与窗口类名,获得对应窗口的窗口句柄 HWNDSetCheck
取消选择;EnableWindow
使能空间;GetWindowThreadProcessId
通过窗口句柄找到对应进程ID;OpenProcess
通过进程ID ,获得进程的句柄;PROCESS_ALL_ACCESS
为权限;WriteProcessMemory
通过进程句柄,修改对应地址处内存;ReadProcessMemory
通过进程句柄,修改对应地址处内存;
// TODO:在此添加额外的初始化代码
// 创建一条子线程,监控游戏的打开或者关闭
m_monitorThread = CreateThread(NULL, NULL, monitorThreadFunc, NULL, NULL, NULL);
// 用来监控游戏的线程
DWORD monitorThreadFunc(LPVOID lpThreadParameter) {
while (1) {
// 获得植物大战僵尸窗口的句柄
HWND windowHandle = FindWindow(CString("MainWindow"), CString("植物大战僵尸中文版"));
if (windowHandle == NULL) {
g_dlg->m_bnKill.SetCheck(FALSE);
g_dlg->m_bnSun.SetCheck(FALSE);
g_dlg->m_bnKill.EnableWindow(FALSE);
g_dlg->m_bnSun.EnableWindow(FALSE);
g_processHandle = NULL;
} else if (g_processHandle == NULL) {
g_dlg->m_bnKill.EnableWindow(TRUE);
g_dlg->m_bnSun.EnableWindow(TRUE);
// 获得植物大战僵尸的进程ID
DWORD processPid;
GetWindowThreadProcessId(windowHandle, &processPid);
// 获得植物大战僵尸的进程句柄
g_processHandle = OpenProcess(PROCESS_ALL_ACCESS, FALSE, processPid);
}
if (g_dlg->m_bnSun.GetCheck()) { // 需要无限阳光
DWORD value = 9990;
WriteMemory(&value, sizeof(value), 0x6A9EC0, 0x320, 0x8, 0x0, 0x8, 0x144, 0x2c, 0x5560, -1);
}
// 休息睡眠
Sleep(1000);
}
return NULL;
}
- 根据偏移地址修改内存
...
作为参数为可变参数,传参以-1为结尾;
va_list
与va_arg
的使用
// 内存修改(后面的可变参数是地址链,要以-1结尾)
void WriteMemory(void *value, DWORD valueSize, ...) {
if (value == NULL || valueSize == 0 || g_processHandle == NULL) return;
DWORD tempValue = 0;
va_list addresses;
va_start(addresses, valueSize);
DWORD offset = 0;
DWORD lastAddress = 0;
while ((offset = va_arg(addresses, DWORD)) != -1) {
lastAddress = tempValue + offset;
::ReadProcessMemory(g_processHandle, (LPCVOID) lastAddress, &tempValue, sizeof(DWORD), NULL);
}
va_end(addresses);
::WriteProcessMemory(g_processHandle, (LPVOID) lastAddress, value, valueSize, NULL);
}