手游作弊-内存读写实例

手游作弊-内存读写实例

关于手游作弊的博客我鸽的有点久了哈哈,这篇文章就和大家讲解下在内存中数据的存储格式,如何读取和修改

基础数据类型在内存中的存储空间

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

#include

#include

#include

#include

#include

#include

#include

#include

#include

// 支持的搜索类型

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(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(8888.0,ad.addrs[0],FLOAT,true);

return 0;

}

代码不是特别完善,仅供参考,有什么不懂的可以私聊或评论区留言。

下一期我们一起来分析一下GG修改器跨进程读写原理和相应一些优化方案(要是我1个星期没有更新,记得踹我~嘿嘿)

相关推荐

全球迪士尼樂園亮點整理
正规365网址是多少

全球迪士尼樂園亮點整理

📅 09-17 👁️ 8552
萤石设备的n种上云方式
365bet足球网址

萤石设备的n种上云方式

📅 09-19 👁️ 8510
倩女2 新纪元测试区 服务器问题答疑
365资讯下载安装

倩女2 新纪元测试区 服务器问题答疑

📅 09-23 👁️ 4818