關于手游作弊的博客我鴿的有點久了哈哈,這篇文章就和大家講解下在記憶體中資料的存盤格式,如何讀取和修改
基礎資料型別在記憶體中的存盤空間
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個星期沒有更新,記得踹我~嘿嘿)
轉載請註明出處,本文鏈接:https://www.uj5u.com/ruanti/244710.html
標籤:其他
上一篇:IFramework 框架學習筆記 2021 (一): LanguageModule(語言模塊)
下一篇:資料結構之查找
