本章旨在讲解如何利用高级语言根据变量数值寻找内存地址。涉及代码以C#为例。
我用C#写了一个WinForm形式的Demo,界面如下:
源代码:
//血量初始值 private int value = 1000; public Form1() { InitializeComponent(); } /// <summary> /// 刷新界面:将最新的血量显示在界面 /// </summary> /// <param name="sender"></param> /// <param name="e"></param> private void btn_refresh_Click(object sender, EventArgs e) { this.label_display.Text = value.ToString(); } /// <summary> /// 更新血量:将自定义的数值写入血量变量 /// </summary> /// <param name="sender"></param> /// <param name="e"></param> private void btn_update_Click(object sender, EventArgs e) { int iVaule = -1; bool ParseResult = int.TryParse(this.textBox_value.Text,out iVaule); if (ParseResult) { value = iVaule; this.label_display.Text = this.textBox_value.Text; } }
很简单的一个Demo:一个名为value的变量,整数型,赋予其初始值1000;两个按钮:修改按钮点击后把文本框的数值赋值给value,并且修改标签文本=修改后value的值;另一个刷新按钮,点下后更新标签文本=value的最新值。
回顾上一章节,我们讲到查询数值的内存地址需要用到两个函数VirtualQueryEx和ReadProcessMemory:
其中VirtualQueryEx的第三个参数是一个用于接收内存信息的结构体指针,来看一下组成这个结构体的成员
//接收内存信息的结构体 public struct MEMORY_BASIC_INFORMATION { //区域基地址 public int BaseAddress; //分配基地址 public int AllocationBase; //区域被初次保留时赋予的保护属性 public int AllocationProtect; //区域大小 public int RegionSize; //状态 public int State; //保护属性 public int Protect; //类型 public int lType; }
这些注释是我从百度百科上摘抄的,更准确的解释建议查阅MSDN的API,下面贴代码的时候我也会据我的理解去解释涉及到的成员,但还是建议深入学习计算机的操作系统原理才能真正掌握这些术语与它们的意义。特地贴出这一段是我认为这是整个外挂制作过程中最重要的一个步骤。套用我们现成的模板或者利用后续我也会提到的一系列辅助工具去完成外挂的制作,是很难成长的,一些知名的游戏靠工具搜索基址千难万难,只有慢慢的去理解这些API、了解寄存器和汇编语言,才能走的更远。
原归正传,我就直接跟着实际测试来一步一步讲解:
1). 打开测试程序
程序的名称:WinMemory_Test
2). 根据上一章节通过进程名称获取PID=7956
3). 还是上一章节提到的通过PID=7956获取进程句柄Handle=1072
4). 通过Handle循环遍历可读写内存地址,取得字节数组。
public void SearchAddress() { MEMORY_BASIC_INFORMATION MBInfo = new MEMORY_BASIC_INFORMATION(); //获取结构体大小[单次读取字节数] int MBSize = Marshal.SizeOf(MBInfo); //从0x00开始查询 StartAddress = 0x000000; //实际读取的字节数 int ReadSize = 0; //从0开始查询,直到查询到整形的最大值2147483647 while (StartAddress >= 0 && StartAddress <= 0x7fffffff && MBInfo.RegionSize >= 0) { //读取结果存入输出参数MBInfo MBSize = VirtualQueryEx(hProcess, (IntPtr)StartAddress, out MBInfo, Marshal.SizeOf(MBInfo)); //如果实际读取到的字节数等于结构体MEMORY_BASIC_INFORMATION字节数,表示读取成功 if (MBSize == Marshal.SizeOf(typeof(MEMORY_BASIC_INFORMATION))) { //PAGE_READWRITE:允许读写的内存区。 //MEM_COMMIT:已分配物理内存[要找的数值确定了,那么内存肯定提前分配了]。 if (MBInfo.Protect == PAGE_READWRITE && MBInfo.State == MEM_COMMIT) { byte[] FindArray = new byte[MBInfo.RegionSize]; //把读取到的字节写入上面定义的数组byData中 if (ReadProcessMemory(hProcess, (IntPtr)StartAddress, FindArray, MBInfo.RegionSize, out ReadSize)) //如果读取的字节数无误 if (ReadSize == MBInfo.RegionSize) { //处理数据[对比分析] DealData(DataArray, StartAddress); } } } else { break; } StartAddress += MBInfo.RegionSize; } }
5). 将获取的字节数组转化整型与1000进行对比,将寻找到的所有结果保存到全局List.
public void DealData(byte[] DataArray, int StartAddress) { byte[] intBuff = new byte[4]; for (int i = 0; i < DataArray.Length - 4; i++) { Array.Copy(DataArray, i, intBuff, 0, 4); int num = BitConverter.ToInt32(intBuff, 0); if (num == 1000) { AddressList.Add(StartAddress + i); } } }
看一下结果:
至此,Demo中整形数值等于1000的地址已经全部被我们找到了,下一章节讲解如何定位我们所要查找的那个“1000”以及修改其值。
PS:转载请附带原文路径:http://www.cnblogs.com/lene-y/p/7107526.html ,我已委托“维权骑士”为我的文章进行维权行动。
欢迎关注微信公众号[游戏外挂原理解析与制作],对本文有不理解的地方或者不同的观点可以给我留言,一定回复。