My First MFC Program

0x00 前言

由于这几天在忙着研究外挂开发…

但是很遗憾,我并不会图形化界面程序开发…

昨晚开着二倍速和快进用俩小时看完了清华大学的一位教授出的Windows桌面应用程序开发教程…

看完发现,用Windows桌面应用程序开发来开发一款外挂对我来说还要学不少,比如还要再去研究各种类,如Button类等,学习周期比较长。

但我想尽快开发出来目标软件,这时候似乎只有VB、Delphi、MFC、易语言可以选择了。

VB和Delphi我不太熟悉,易语言的话,许多年前接触过,但基本语法忘得差不多了,以后再复习吧。

最终我还是选择了MFC,记得大一下学期老师让用MFC开发飞机大战,但我全程在摸鱼…

用了半天大概学会了MFC开发流程,并开发出了一个简易的植物大战僵尸辅助。

0x01 主要代码

一些宏和全局变量

1
2
3
4
5
6
7
8
9
10
//一些宏
#define SUN_SHINE_BASE_ADDR 0x755E0C //阳光基址
#define SUN_SHINE_OFFSET_FIRST 0x868 //一级偏移
#define SUN_SHINE_OFFSET_SECOND 0x5578 //二级偏移值
#define NoCD_ADDR 0x49E947

//全局变量处
DWORD NumOfSum;
HANDLE Process;
DWORD Size;

启动

功能是获取程序句柄,赋值给HANDLE类型的全局变量Process

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
void CMFCTestDlg::OnBnClickedButton1()
{
// TODO: 在此添加控件通知处理程序代码
//获取程序句柄
DWORD PID = 0;
HWND hWinmine = ::FindWindow(NULL, _T("Plants vs. Zombies")); //找到窗口
GetWindowThreadProcessId(hWinmine, &PID); //获取进程标识
if (0 == PID)
{
MessageBox(_T("获取PID失败\n"));
exit(0);
}

Process = OpenProcess(PROCESS_ALL_ACCESS, 0, PID);
if (NULL == Process)
{
MessageBox(_T("进程打开失败\n"));
exit(0);
}
MessageBox(_T("启动成功!"));
UpdateSunNum();//自定义的函数,作用是更新当前阳光值
}

文本框

目的是获取用户输入的想修改的阳光值,赋值给int型的全局变量NumOfSum

1
2
3
4
5
6
7
8
9
10
11
12
void CMFCTestDlg::OnEnChangeEdit1()
{
// TODO: 如果该控件是 RICHEDIT 控件,它将不
// 发送此通知,除非重写 CDialogEx::OnInitDialog()
// 函数并调用 CRichEditCtrl().SetEventMask(),
// 同时将 ENM_CHANGE 标志“或”运算到掩码中。

// TODO: 在此添加控件通知处理程序代码
CString str;
GetDlgItem(IDC_EDIT1)->GetWindowText(str);
NumOfSum = StrToInt(str);
}

修改阳光值

顾名思义,根据用户在文本框中输入的阳光值来修改阳光值

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
void CMFCTestDlg::OnBnClickedButton2()
{
// TODO: 在此添加控件通知处理程序代码
if (Process == NULL) {
MessageBox(_T("请先点启动!"));
return;
}

//修改阳光值
DWORD SunShineBaseAddressValue = 0;
DWORD SunShineOffsetFirstValue = 0;
DWORD SunShineNum = 0;
if (0 == ReadProcessMemory(Process, (LPVOID)SUN_SHINE_BASE_ADDR, &SunShineBaseAddressValue, sizeof(DWORD), &Size))
{
MessageBox(_T("获取基址失败\n"));
return;
}

if (0 == ReadProcessMemory(Process, (LPVOID)(SunShineBaseAddressValue + SUN_SHINE_OFFSET_FIRST), &SunShineOffsetFirstValue, sizeof(DWORD), &Size))
{
MessageBox(_T("获取一级偏移失败\n"));
return;
}

if (0 == WriteProcessMemory(Process, (LPVOID)(SunShineOffsetFirstValue + SUN_SHINE_OFFSET_SECOND), &NumOfSum, sizeof(DWORD), &Size))
{
MessageBox(_T("内存写入失败\n"));
return;
}
MessageBox(_T("阳光值修改成功!"));
}

更新当前阳光值

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
void CMFCTestDlg::UpdateSunNum()
{
if (Process == NULL) {
return;
}
DWORD SunShineBaseAddressValue = 0;
DWORD SunShineOffsetFirstValue = 0;
DWORD SunShineNum = 0;
if (0 == ReadProcessMemory(Process, (LPVOID)SUN_SHINE_BASE_ADDR, &SunShineBaseAddressValue, sizeof(DWORD), &Size))
{
MessageBox(_T("获取基址失败\n"));
return;
}

if (0 == ReadProcessMemory(Process, (LPVOID)(SunShineBaseAddressValue + SUN_SHINE_OFFSET_FIRST), &SunShineOffsetFirstValue, sizeof(DWORD), &Size))
{
MessageBox(_T("获取一级偏移失败\n"));
return;
}

if (0 == ReadProcessMemory(Process, (LPVOID)(SunShineOffsetFirstValue + SUN_SHINE_OFFSET_SECOND), &SunShineNum, sizeof(DWORD), &Size))
{
MessageBox(_T("获取二级偏移失败\n"));
return;
}
CString ssn;
ssn.Format(_T("%d"), SunShineNum);
SetDlgItemText(IDC_STATIC1, ssn);
}

计时器

每秒刷新一下当前阳光值,这块因为一点小问题研究了半个多小时才搞明白….

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
//在Resource.h定义一下Timer
#define Timer1 0x01

//转到xxxxxxDlg.cpp
BEGIN_MESSAGE_MAP(CMFCTestDlg, CDialogEx)
......//此处和下面出现的...表示我写博客的时候没把其他代码放上来,以突出重点
ON_WM_TIMER()
END_MESSAGE_MAP()

BOOL CMFCTestDlg::OnInitDialog()
{
......//同上
// TODO: 在此添加额外的初始化代码
StartTimer();
}


// 启动timer
void CMFCTestDlg::StartTimer()
{
SetTimer(Timer1, 1000, NULL);
}

// 线束timer
void CMFCTestDlg::StopTimer()
{
KillTimer(Timer1);
}

// timer响应函数
void CMFCTestDlg::OnTimer(UINT nIDEvent)
{
switch (nIDEvent) {
case Timer1:
UpdateSunNum();//每1秒刷新一下当前阳光值
break;
default:
break;
}
}

无CD

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
void CMFCTestDlg::OnBnClickedCheck1()
{
// TODO: 在此添加控件通知处理程序代码
if (Process == NULL) {
MessageBox(_T("请先点启动!"));
return;
}

CButton* pBtn = (CButton*)GetDlgItem(IDC_CHECK1);
int state = pBtn->GetCheck();
if (0 == state) {//未被选中
DWORD hasCD = 0x0;
if (0 == WriteProcessMemory(Process, (LPVOID)(NoCD_ADDR), &hasCD, sizeof(BYTE), &Size))
{
MessageBox(_T("设置无CD失败 :( \n"));
}
}
else if (1 == state) {//被选中
DWORD NOCD = 0x01;
if (0 == WriteProcessMemory(Process, (LPVOID)(NoCD_ADDR), &NOCD, sizeof(BYTE), &Size))
{
MessageBox(_T("恢复有CD失败 :( \n"));
}
}
}

0x02 效果图

无CD不太好放效果图。。。

0x03 总结

这也算是初次接触MFC吧

感觉好多地方可以优化

比如 每次都需要重新获取一下基址的值、一级偏移的值…这样多做了许多无用功吧,可以直接把一级偏移的值存入一个全局变量。

还有一些地方我感觉写的应该不太规范吧

比如没对句柄进行close….因为我没找到MFC中像Win32图形化编程里面那种switch/case中destory的情况在哪。

还有些地方并没对非法输入进行处理,没有异常处理之类的。

今后我会逐步改进的。

-------------本文结束感谢您的阅读-------------
如果觉得这篇文章对您有用,请随意打赏。 (๑•⌄•๑) 您的支持将鼓励我继续创作!