【C++】【mfc】小demo;多线程、事件的声明注册、打印方式、外挂本质(修改内存)

发布于:2023-07-27 ⋅ 阅读:(94) ⋅ 点赞:(0)

  • 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、控件使用

  • 绑定成员变量 – 手动
  1. 声明 按键成员变量CButton m_bnKill;
  2. 通过 DDX_Control(pDX, IDC_KILL, m_bnKill); 将变量绑定到控件ID上;
  • 绑定成员变量 – 自动
  1. 控件上右键添加变量
  2. 修改绑定到控件的 成员变量的类型、名称等;
    在这里插入图片描述
  • 单选框的状态读取和修改
  • 方法1
  • IsDlgButtonChecked 用来判断控件是否被选择;CheckDlgButton 用来修改;
	bool checked = IsDlgButtonChecked(IDC_KILL);
	if (m_bnSun.GetCheck()) {
		log("勾选");
		CheckDlgButton(IDC_KILL,true);
	} else {
		log("没有勾选");
	}
  • 方法2
  1. GetDlgItem(IDC_KILL); 返回cwnd 类型。用子类CButton取即可;
  2. 然后调用int CButton::GetCheck() const
  3. void CButton::SetCheck(int nCheck) 用来修改
	CButton* button = (CButton*)GetDlgItem(IDC_KILL);
	bool state = button->GetCheck();
	button->SetCheck(true);
  • 方法3
  1. CButton m_bnKill; 成员变量 通过 DDX_Control(pDX, IDC_KILL, m_bnKill); 绑定到控件ID上;
  2. 然后调用int CButton::GetCheck() const
  3. 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
  1. windows 下可执行文件为 PE 格式
  2. 将exe 格式机器码转为汇编;(工具:Ollydbg)
  3. 修改汇编中的某些指令,或者直接填充nop;来达到自己的要求;
  4. 将修改的代码再生成exe,就成为了破结过的代码了;
  • 加壳与脱壳

一般的软件破解思路
在这里插入图片描述

加壳和脱壳
在这里插入图片描述

  • 外挂的本质
  • 常见的外挂功能有2种做法;其实数据和代码并没有本质区别, 在内存中都是0和1
  1. 修改内存中的数据(Cheat Engine,可用来观察进程的内存占用)
  2. 修改内存中的代码
  • 其实都是将进程对应内存空间的一部分修改掉;
  • 代码段的内存地址是不变的,相对简单;全局变量和代码端这些,在进程内存中是不变的
  • 内存数据中的空间,除非是全局变量,否则会发生变化;不好实现;
  • 改内存的话:

写个查看内存占用的程序,找到要改的内存地址,填充即可;

  • 修改内存中的代码:
  • 找出所对应的内存空间是哪块代码修改的,然后分析这块代码,去改即可;
  • 其实,改代码也可以只改考进来的内存;即将载入内存的二进制代码改掉即可。而不是改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++查找)
    在这里插入图片描述
  1. g_dlg为类指针,在初始化的时候将this 赋值给这个指针;
  2. FindWindowW 通过窗口名称与窗口类名,获得对应窗口的窗口句柄 HWND
  3. SetCheck 取消选择;
  4. EnableWindow 使能空间;
  5. GetWindowThreadProcessId 通过窗口句柄找到对应进程ID;
  6. OpenProcess 通过进程ID ,获得进程的句柄;PROCESS_ALL_ACCESS 为权限;
  7. WriteProcessMemory 通过进程句柄,修改对应地址处内存;
  8. 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_listva_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);
}