动态获取模块头
在读取矩阵的时候,或者读取其他数据,如果在gg中找到了矩阵的头地址,可以直接用代码读取,但是游戏重启后地址又变化了,要找到矩阵和模块之间的偏移来稳定读取。获取模块头就是第一步。
从maps中读取
在Android或其他基于Linux的系统中,/proc/pid/maps
文件提供了关于进程内存使用的详细信息。这个文件是一个虚拟文件,其中包含了进程所使用的所有内存段的信息。
每一行都表示一个内存段,包含以下信息:
内存段的起始和结束地址
内存段的权限(读、写、执行、私有或共享)
内存段的使用偏移
使用该段的设备
内存段的inode
文件名(如果该内存段映射到文件)
通过读取这个文件,可以了解到进程的内存布局,包括堆、栈、已加载模块及其位置等信息。这对于调试内存相关的问题,如内存泄漏、段错误等,非常有用。
电脑连接手机后进入终端:
dipper:/ $ su
dipper:/ # pidof com.clapfootgames.laserwars
7170
dipper:/ # cat /proc/7170/maps
0ec00000-0fe33000 rw-p 00000000 00:00 0 [anon:dalvik-zygote space]
0fe33000-0fe34000 rw-p 00000000 00:00 0 [anon:dalvik-non moving space]
0fe34000-0fe46000 rw-p 00000000 00:00 0 [anon:dalvik-non moving space]
0fe46000-12401000 ---p 00000000 00:00 0 [anon:dalvik-non moving space]
12401000-12c00000 rw-p 00000000 00:00 0 [anon:dalvik-non moving space]
12c00000-32c00000 rw-p 00000000 00:00 0 [anon:dalvik-main space (region space)]
这里是我贴出来部分maps的内容,文件中包含了各种可执行文件,或者文件的映射。现在过滤某个模块的信息:grep
就是对内容进行过滤libnative.so
dipper:/ # cat /proc/7170/maps | grep libnative.so
bbe8a000-bbed1000 r-xp 00000000 fd:08 74637 /data/app/~~NkbsTddg0SiRa1opzkJdqA==/com.clapfootgames.laserwars-eGV0rv7vhb1mZJwV7WDleA==/lib/arm/libnative.so
bbed2000-bbed3000 r--p 00047000 fd:08 74637 /data/app/~~NkbsTddg0SiRa1opzkJdqA==/com.clapfootgames.laserwars-eGV0rv7vhb1mZJwV7WDleA==/lib/arm/libnative.so
bbed3000-bbed5000 rw-p 00048000 fd:08 74637 /data/app/~~NkbsTddg0SiRa1opzkJdqA==/com.clapfootgames.laserwars-eGV0rv7vhb1mZJwV7WDleA==/lib/arm/libnative.so
通常情况是游戏代码逻辑在可执行区间,也就是r-xp
这个标记的。前面bbe8a000-bbed1000
就是这个段的地址,我们需要的就是bbe8a000
这个地址了。
规律就是有libnative.so
的文件的映射的第一条数据的前面就是目标地址了。
/**
* 用于从/proc/pid/maps文件中获取指定so_name模块的基址
* @param so_name 模块的名字
* @return 模块的地址
*/
long long get_base(char *so_name) {
char path[50], line[1024];
long long so;
// 构造/proc/pid/maps文件的路径
sprintf(path, "/proc/%d/maps", my_pid);
// 打开/proc/pid/maps文件
FILE *fd = fopen(path, "r");
// 逐行读取/proc/pid/maps文件
while (fgets(line, sizeof(line), fd) != NULL) {
// 如果在当前行找到了指定的so_name
if (strstr(line, so_name) != NULL) {
// 如果当前行描述的内存段具有读和执行权限
if (strstr(line, "r-xp") != NULL) {
// 从当前行提取模块的基址
sscanf(line, "%llx", &so);
// 返回模块的基址
return so;
}
}
}
}
测试代码:
读取出来的地址和上面cat maps中得到:bbe8a000-bbed1000 r-xp 00000000 fd:08 74637一样的。也就是读取成功了。
也可以通过gg来查看地址的:打开gg,选择这个箭头。
分别点击几个类型的内存来查看r-xp的libnative.so。找到了就看前面的地址。
在xa中找到符合的。
所以后面需要跳进去看矩阵数据就用xa内存。
完整的代码:
#include <stdio.h>
#include <unistd.h>
#include <string>
#include <dirent.h>
#include <sys/uio.h>
#include <sys/syscall.h>
int my_pid;
/**
* 传递包名
* @param name 目标包名
* @return 返回app的pid
*/
pid_t get_game_pid(char *name) {
FILE *fp;
// 储存拿到的pid
pid_t pid;
// 命令拼接
char cmd[0x100] = "pidof %s";
char exec[128] = {0};
// 把pidof 和包名拼接在一起组成命令
sprintf(exec, cmd, name);
// popen执行命令,等同于终端执行
fp = popen(exec, "r");
// 拿到结构后储存到pid变量中
fscanf(fp, "%d", &pid);
pclose(fp);
return pid;
}
int getPID(const char *packageName) {
// 默认pid是-1,如果返回-1就是读取失败
int id = -1;
DIR *dir;
FILE *fp;
char filename[64];
char cmdline[64];
struct dirent *entry;
// 打开proc目录
dir = opendir("/proc");
while ((entry = readdir(dir)) != NULL) {
// 目录名字就是pid,转int
id = atoi(entry->d_name);
if (id != 0) {
// 把目录和cmdline拼接一起读取
sprintf(filename, "/proc/%d/cmdline", id);
fp = fopen(filename, "r");
if (fp) {
fgets(cmdline, sizeof(cmdline), fp);
fclose(fp);
// 判断传递进来的包名和cmdline中字符串是否一样
if (strcmp(packageName, cmdline) == 0) {
// 如果一致,上面目录就是pid了
return id;
}
}
}
}
closedir(dir);
return -1;
}
int read_dword(int pid, long long addr) {
struct iovec local, remote;
// 读取到数据保存到这个data中
int data;
local.iov_base = &data;
// 长度8位
local.iov_len = 8;
// 读取模板地址
remote.iov_base = (void *) addr;
// 读取长度8位
remote.iov_len = 8;
// 下面模板
long int call_result = syscall(__NR_process_vm_readv,
pid,
&local,
1,
&remote,
1,
0);
return data;
}
/**
* 用于从/proc/pid/maps文件中获取指定so_name模块的基址
* @param so_name 模块的名字
* @return 模块的地址
*/
long long get_base(char *so_name) {
char path[50], line[1024];
long long so;
// 构造/proc/pid/maps文件的路径
sprintf(path, "/proc/%d/maps", my_pid);
// 打开/proc/pid/maps文件
FILE *fd = fopen(path, "r");
// 逐行读取/proc/pid/maps文件
while (fgets(line, sizeof(line), fd) != NULL) {
// 如果在当前行找到了指定的so_name
if (strstr(line, so_name) != NULL) {
// 如果当前行描述的内存段具有读和执行权限
if (strstr(line, "r-xp") != NULL) {
// 从当前行提取模块的基址
sscanf(line, "%llx", &so);
// 返回模块的基址
return so;
}
}
}
}
int main() {
printf("读取游戏的pid\n");
my_pid = getPID("com.clapfootgames.laserwars");
printf("pid_proc: %d\n", my_pid);
uint32_t base = get_base("libnative.so");
printf("base_addr : %p\n", base);
return 0;
}
总结
1、通过maps中加载的模块信息找到基址。
2、代码实现读取基址,这个是模板代码,其他游戏也通用的。
3、代码读取的数据和maps对比,和gg中的模块头信息对比可以确认读取是否正常。