Linux 内存读写的一些理解


最近尝试给 Arcaea 这款游戏制作了一个全自动计分器,于是就有了这篇文章。

就像游戏外挂那些软体一样,都是基于对进程内存的读写以实现需要的功能。在 Windows 中,比较常用的分析软件是 Cheat Engine 。如果我没记错,利用的是 tlhelp 获取进程句柄,然后使用 windows.h 里面的 ReadProcessMemory/WriteProcessMemory 函数进行读写。

但上述函数都是在 Windows 里提供的非 std 标准函数,也就是说换个系统,这些函数就无法使用了。

不过在 Linux 下的一个特性,使其所有的系统资源都是以文件的形式出现的,其中包括所有进程的内存,都可以以文件的形式来操纵,即使是个开关,也可以用文件的形式出现。也就是说在 Linux 中,操作进程内存的其中一种方式就是读写 对应的入口“文件”。

procfs 全称 Process File System(进程文件系统),进程的资源都可以通过这个文件系统进行访问。如果需要访问进程的各种数据,就需要 cd 到 /proc/<进程ID>/ 目录即可,如果没有进程就不会出现对应目录。

mem 文件就是进程的内存数据访问入口,需要注意的是,他并不是一个真正的文件,也就是说他并不会占用你的磁盘空间。他的内容大小是取决于你的 RAM 大小,你的 RAM 越大,他的尺寸就越大,为了标准稳定起见,所有 proc 的 mem 内容尺寸都是一样的。但不意味着就可以跨进程访问了,如果访问了不属于该进程的内存空间,将会返回无效的数据。

maps 文件里面都是进程已加载模块的内存地址,可以通过该文件定位进程的起始地址和结束地址,还可以定位模块的地址,访问 mem 之前解析 maps 文件可以比较高效的读取内存数据。

结合这两个文件,基本上就可以对进程进行最基础的修改了(比如外挂的血量修改)。

我制作的自动计分器思路就是先通过 mem 获取 libcocos2dcpp.so 的结束,因为目标的指针地址就在该模块的后面一点,这样就可以免除从 0x00 慢慢寻找而导致的效率过慢问题。

如果不事先定位模块地址,要从 0x0000000000000000 搜寻到 0x0000007FFFFFFFFF,这样的效率一定是超级慢,即使使用 Sunday 算法也无法对如此庞大的数据进行准确定位(32 位的设备效率也是一样慢)。

确定大致地址后,通过 unistd.h 的 open 函数打开文件并获取句柄:

int pid = getpid();
int fp = open("/proc/"+to_string(pid)+"/mem",O_RDONLY);

然后通过 lseek64 函数设置该句柄的指针位置:

lseek64(fp,address,SEEK_SET)

要留意的是,lseek64 只适合 64 位的系统,32位需要使用 lseek。

最后通过 read 函数读取需要的数据:

char buffer[1024];
read(fp,&buffer,sizeof(buffer)/sizeof(buffer[0]));

程序是靠特征码定位最终的指针地址的,所以实际代码会比这里的复杂许多。

完成了精准定位后,剩下的就是分析数据结构,计算数据偏移地址的事情了。

Linux 读写内存的思路和 Windows 还是有点相似之处的:读取 maps 就相当于 Windows 下的创建进程快照。读写对应进程的 mem 就相当于使用进程句柄读写进程内存,因为只有指定进程的 mem 才可以读写对应进程的数据。

好了,这就是我对 Linux 的一些理解。

想深入了解内存读取的相关代码,可以看看这个项目:https://github.com/ChinaYuanGe/ArcaeaScoreReader

分组于: 技术

发布于: 2023年06月27日 11时19分

编辑于: 2023年06月27日 11时20分

Linux
procfs
内存读取

用 Cookie 保存: 别名、Email