Android手游绘制基础--04动态获取模块头

动态获取模块头

在读取矩阵的时候,或者读取其他数据,如果在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中的模块头信息对比可以确认读取是否正常。

  • 27
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

hello223344

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值