手游作弊(二)-内存读写实例
关于手游作弊的博客我鸽的有点久了哈哈,这篇文章就和大家讲解下在内存中数据的存储格式,如何读取和修改
基础数据类型在内存中的存储空间
int - 4字节
float - 4字节
double - 8字节
long - 4字节或8字节
char - 1字节
short - 2字节
32位手游地址指针在内存占用4个字节,64位手游地址指针在内存中占用8个字节。
关于指针,我们在后面的文章会详细讲解。
在手游中,还有一个特殊的类型:XOR
XOR类型实际是int类型,这是游戏常用的防止玩家直接搜索到关键数值的一种加密方式(地址^值),其实也很简单
我们都知道,A ^ B = C,C ^ B = A,C ^ A = B
那么,加密后值^地址 = 实际值
上一期我们使用C语言的pread和pwrite函数来读取和修改内存,不过这样是很容易被游戏检测到的。
今天我们要说的方式是系统调用,使用syscall来调用SYS_process_vm_readv和SYS_process_vm_writev,这两个函数的参数和返回值可以在百度上直接找到,我就不详细说明了。
这里我直接贴上详细的代码,供大家参考:
AndroidMemDebug.h
#ifndef _ANDROIDMEMDEBUG_H_
#define _ANDROIDMEMDEBUG_H_
#include <stdio.h>
#include <stdlib.h>
#include <sys/uio.h>
#include <sys/types.h>
#include <sys/syscall.h>
#include <pthread.h>
#include <unistd.h>
#include <dirent.h>
#include <fcntl.h>
#include <iostream>
// 支持的搜索类型
enum
{
DWORD,
FLOAT,
BYTE,
WORD,
QWORD,
XOR,
DOUBLE,
};
// 支持的内存范围(请参考GG修改器内存范围)
enum
{
Mem_Auto, // 所以内存页
Mem_A,
Mem_Ca,
Mem_Cd,
Mem_Cb,
Mem_Jh,
Mem_J,
Mem_S,
Mem_V,
Mem_Xa,
Mem_Xs,
Mem_As,
Mem_B,
Mem_O,
};
struct MemPage
{
long start;
long end;
char flags[8];
char name[128];
void *buf = NULL;
};
struct AddressData
{
long *addrs = NULL;
int count = 0;
};
// 根据类型判断类型所占字节大小
size_t judgSize(int type)
{
switch (type)
{
case DWORD:
case FLOAT:
case XOR:
return 4;
case BYTE:
return sizeof(char);
case WORD:
return sizeof(short);
case QWORD:
return sizeof(long);
case DOUBLE:
return sizeof(double);
}
return 4;
}
int memContrast(char *str)
{
if (strlen(str) == 0)
return Mem_A;
if (strstr(str, "/dev/ashmem/") != NULL)
return Mem_As;
if (strstr(str, "/system/fonts/") != NULL)
return Mem_B;
if (strstr(str, "/data/app/") != NULL)
return Mem_Xa;
if (strstr(str, "/system/framework/") != NULL)
return Mem_Xs;
if (strcmp(str, "[anon:libc_malloc]") == 0)
return Mem_Ca;
if (strstr(str, ":bss") != NULL)
return Mem_Cb;
if (strstr(str, "/data/data/") != NULL)
return Mem_Cd;
if (strstr(str, "[anon:dalvik") != NULL)
return Mem_J;
if (strcmp(str, "[stack]") == 0)
return Mem_S;
if (strcmp(str, "/dev/kgsl-3d0") == 0)
return Mem_V;
return Mem_O;
}
class MemoryDebug
{
private:
pid_t pid = 0; // 调试应用的PID
public:
//设置调试的应用包名,返回PID
int setPackageName(const char* name);
//获取模块的基址,@name:模块名,@index:模块在内存中的内存页位置(第几位,从1开始,默认1)
long getModuleBase(const char* name,int index = 1);
//获取模块的BSS基址
long getBssModuleBase(const char *name);
//读内存的基础函数
size_t preadv(long address, void *buffer, size_t size);
//写内存的基础函数
size_t pwritev(long address, void *buffer, size_t size);
//根据值搜索内存,并返回相应地址
template < class T > AddressData search(T value, int type, int mem, bool debug = false);
//修改内存地址值,返回-1,修改失败,返回1,修改成功
template < class T > int edit(T value,long address,int type,bool debug = false);
//读取一个DWORD(int)数值
int ReadDword(long address);
//读取一个int指针地址数值
long ReadDword64(long address);
//读取一个float类型数值
float ReadFloat(long address);
//读取一个long类型数值
long ReadLong(long address);
};
#include "AndroidMemDebug.cpp"
#endif
AndroidMemDebug.cpp
int MemoryDebug::setPackageName(const char* name)
{
int id = -1;
DIR *dir;
FILE *fp;
char filename[32];
char cmdline[256];
struct dirent *entry;
dir = opendir("/proc");
while ((entry = readdir(dir)) != NULL)
{
id = atoi(entry->d_name);
if (id != 0)
{
sprintf(filename, "/proc/%d/cmdline", id);
fp = fopen(filename, "r");
if (fp)
{
fgets(cmdline, sizeof(cmdline), fp);
fclose(fp);
if (strcmp(name, cmdline) == 0)
{
pid = id;
return id;
}
}
}
}
closedir(dir);
return -1;
}
long MemoryDebug::getModuleBase(const char* name,int index)
{
int i = 0;
long start = 0,end = 0;
char line[1024] = {
0};
char fname[128];
sprintf(fname, "/proc/%d/maps", pid);
FILE *p = fopen(fname, "r");
if (p)
{
while (fgets(line, sizeof(line), p))
{
if (strstr(line, name) != NULL)
{
i++;
if(i==index){
sscanf(line, "%lx-%lx", &start,&end);
break;
}
}
}
fclose(p);
}
return start;
}
long MemoryDebug::getBssModuleBase(const char *name)
{
FILE *fp;
int cnt = 0;
long start;
char tmp[256];
fp = NULL;
char line[1024];
char fname[128];
sprintf(fname, "/proc/%d/maps", pid);
fp = fopen(fname, "r");
while (!feof(fp))
{
fgets(tmp, 256, fp);
if (cnt == 1)
{
if (strstr(tmp, "[anon:.bss]") != NULL)
{
sscanf(tmp, "%lx-%*lx", &start);
break;
}
else
{
cnt = 0;
}
}
if (strstr(tmp, name) != NULL)
{
cnt = 1;
}
}
return start;
}
size_t MemoryDebug::pwritev(long address, void *buffer, size_t size)
{
struct iovec iov_WriteBuffer, iov_WriteOffset;
iov_WriteBuffer.iov_base = buffer;
iov_WriteBuffer.iov_len = size;
iov_WriteOffset.iov_base = (void *)address;
iov_WriteOffset.iov_len = size;
return syscall(SYS_process_vm_writev, pid, &iov_WriteBuffer, 1, &iov_WriteOffset, 1, 0);
}
size_t MemoryDebug::preadv(long address, void *buffer, size_t size)
{
struct iovec iov_ReadBuffer, iov_ReadOffset;
iov_ReadBuffer.iov_base = buffer;
iov_ReadBuffer.iov_len = size;
iov_ReadOffset.iov_base = (void *)address;
iov_ReadOffset.iov_len = size;
return syscall(SYS_process_vm_readv, pid, &iov_ReadBuffer, 1, &iov_ReadOffset, 1, 0);
}
template < class T > AddressData MemoryDebug::search(T value, int type, int mem, bool debug)
{
size_t size = judgSize(type);
MemPage *mp = NULL;
AddressData ad;
long * tmp, *ret = NULL;
int count = 0;
char filename[32];
char line[1024];
snprintf(filename, sizeof(filename), "/proc/%d/maps", pid);
FILE *fp = fopen(filename, "r");
if (fp != NULL)
{
//临时存储搜索结果地址,空间大小可以存储1024000条地址,如果觉得不够可以自己加大
tmp = (long*)calloc(1024000,sizeof(long));
while (fgets(line, sizeof(line), fp))
{
mp = (MemPage *) calloc(1, sizeof(MemPage));
sscanf(line, "%p-%p %s %*p %*p:%*p %*p %[^\n]%s", &mp->start, &mp->end,
mp->flags, mp->name);
// 判断内存范围和内存页是否可读(如果内存页不可读,此时如果去
// 读取这个地址,系统便会抛出异常[信号11,分段错误]并终止调试进程)
if ((memContrast(mp->name) == mem || mem == Mem_Auto)
&& strstr(mp->flags, "r") != NULL)
{
mp->buf = (void *)malloc(mp->end - mp->start);
preadv(mp->start, mp->buf, mp->end - mp->start);
// 遍历内存页中地址,判断地址值是否和想要的值相同
for (int i = 0; i < (mp->end - mp->start) / size; i++)
{
// 异或类型数值有点特殊,他是游戏防止破解者搜索到
// 正确数组的一种方式,其加密方式为:值 ^ 地址
if((type == XOR ? (*(int *) (mp->buf + i * size) ^ mp->start + i * size)
: *(T *) (mp->buf + i * size)) == value)
{
*(tmp + count) = mp->start + i * size;
count++;
if (debug)
{
std::cout
<< "index:" << count
<< " value:" << (type == XOR?*(int *) (mp->buf + i * size) ^ (mp->start + i * size):*(T *) (mp->buf + i * size));
printf(" address:%p\n", mp->start + i * size);
}
}
}
free(mp->buf);
}
}
fclose(fp);
}
if(debug)
printf("搜索结束,共%d条结果\n", count);
ret = (long*)calloc(count,sizeof(long));
memcpy(ret,tmp,count*(sizeof(long)));
free(tmp);
ad.addrs = ret;
ad.count = count;
free(ret);
return ad;
}
template < class T > int MemoryDebug::edit(T value,long address,int type,bool debug)
{
if(-1 == pwritev(address,&value,judgSize(type)))
{
if(debug)
printf("修改失败-> addr:%p\n",address);
return -1;
}else
{
if(debug)
printf("修改成功-> addr:%p\n",address);
return 1;
}
return -1;
}
long MemoryDebug::ReadDword64(long address)
{
long local_ptr = 0;
preadv(address, &local_ptr, 4);
return local_ptr;
}
int MemoryDebug::ReadDword(long address)
{
int local_value = 0;
preadv(address, &local_value, 4);
return local_value;
}
float MemoryDebug::ReadFloat(long address)
{
float local_value = 0;
preadv(address, &local_value, 4);
return local_value;
}
long MemoryDebug::ReadLong(long address)
{
long local_value = 0;
preadv(address, &local_value, 8);
return local_value;
}
void getRoot(char **argv)
{
char shellml[64];
sprintf(shellml, "su -c %s", *argv);
if (getuid() != 0)
{
system(shellml);
exit(1);
}
}
使用:
int main(int argc, char **argv)
{
//获取ROOT
getRoot(argv);
//定义内存调试工具类
MemoryDebug md;
//设置调试应用包名(我这里调试的是QQ)
md.setPackageName("com.tencent.mobileqq");
//搜索内存,方式1,手动声明值类型
md.search<float>(1234, FLOAT, Mem_Ca,true);
//搜索内存,方式2,自动识别值类型
md.search(1234, FLOAT, Mem_Ca,true);
//搜索内存,不开启搜索结果打印
md.search(1234, FLOAT, Mem_Ca);
//搜索内存后修改第一个地址值
AddressData ad = md.search(1234, FLOAT, Mem_Ca);
md.edit<float>(8888.0,ad.addrs[0],FLOAT,true);
return 0;
}
代码不是特别完善,仅供参考,有什么不懂的可以私聊或评论区留言。
下一期我们一起来分析一下GG修改器跨进程读写原理和相应一些优化方案(要是我1个星期没有更新,记得踹我~嘿嘿)
「其他文章」
- 小成开发日记----物联网项目LoveTv实现web网页传输数据到单片机(技术栈涉及web前端,php后端,c/c socket,嵌入式前后端)
- STM32学习笔记(二十)
- mybatis中:returned more than one row, where no more than one was expected.异常
- SpringBoot SpringSecurity JWT实现认证和授权
- 引燃AI社区,不用跨界也能从文本生成图像,OpenAI新模型打破自然语言与视觉次元壁2 - 知乎
- 手游作弊(二)-内存读写实例
- [上海]景栗 (天使轮 A轮A B轮)CDP/MA 35-50K 年终奖
- idea控制台乱码(tomcat日志乱码)的解决办法
- 【Linux】硬链接和软链接
- Nginx负载均衡
- 他本硕博连跨3大专业,毕业后没多久被破格聘为985高校教授!
- 真涨工资了:多所高校博士生资助标准大幅度提升
- ”12306“秒杀系统的设计艺术
- 实战排查|为什么遮挡推流摄像头,会导致播放绿屏?
- 社区团购是腾讯的电商大赛马,美团、京东、拼多多合围买菜:战无不胜的“流量 资本” - 知乎
- Covetrus完成与原母公司的分离
- 赠书 | 在Python领域,你与专家之间的距离只差这两本书
- Adam真的是最好的优化器吗?有人认为不过是神经网络进化的结果 - 知乎
- Spring Cloud Alibaba 新一代微服务解决方案
- 深入解析 C# 的 String.Create 方法