记得学习编程时的第一个helloworld程序:
#include<stdio.h>
Int main(int argc, char **argv)
{
printf(“Hello World”);
return 0;
}
打印” Hello World”,使用的是printf函数。但是,我们并没有去实现printf函数的功能,而是由C语言标准库去实现,我们直接加载该库就可以了。像这样的例子,还有很多。
在开发软件时,我们通常只实现软件的核心逻辑功能,而把基础的功能交给第三方完成,这样的好处是代码体积更小、占用更小的存储空间,而且更方便管理。如果我们想要知道软件加载了哪些库,就需要进行内存模块遍历。
Android内存模块遍历的原理
在Android系统上,要进行内存模块遍历,必须要了解proc文件系统。在linux系统上,把一切信息通过文件的形式输出。proc也是文件系统的一种,但是,它是伪文件系统,不占用磁盘空间,而是由内核挂载到内存中。proc文件系统提供了内核配置、进程状态输出等功能。
关于进程内存模块的信息,存放到proc文件系统下以pid为目录名称下的maps文件中,可以通过”cat /proc/21358/maps”命令打印(21358为假设的进程号)出,如下图:
maps文件中每份模块信息的组织方式为:
第1列:模块内容在内存中的地址范围,以16进制显示。
第2列:模块内容在内存中的读取权限,r代表可读,w代表可写,x代表可执行,p代表私有,s代码共享。
第3列:模块内容对应模块文件中的偏移。
第4列:模块文件在文件系统中的主次设备号。
第5列:模块文件在文件系统中的节点号。
第6列:模块文件在文件系统中的路径。
即linux内核的mm.h里面的vm_area_struct数据结构内容。Linux内核中,关于虚存管理的最基本的管理单元应该是struct vm_area_struct了,它描述的是一段连续的、具有相同访问属性的虚存空间,该虚存空间的大小为物理内存页面的整数倍。vm_area_struct的详细解析可参考这篇文章(超链接至http://blog.csdn.net/ywf861029/article/details/6114794)
Android内存模块遍历的实现
下面将结合代码讲解获取模块信息的实现,GetModuleBase函数可进程中某一模块基地址,GetModuleFullName可获取某一模块的全路径名,实现如下:
/*****************************************************************************
Function: GetModuleBase
Description: 获取进程中某一模块的基址
Input: pid,是进程ID;pszModName,是要查找的模块名称
Output: ulModBase,用来输出模块的基址。
Return:如果获取模块的基址成功,返回true;否则,返回false
*****************************************************************************/
bool GetModuleBase(unsigned long &ulModBase, pid_t pid, const char *pszModName) {
bool bRet = false;
FILE *fp = NULL;
char szMapFilePath[32] = {0};
char szMapFileLine[1024] = {0};
if (pszModName == NULL)
{
return bRet;
}
if (pid < 0) // 获取自己进程的模块信息
{
sprintf(szMapFilePath, “/proc/self/maps”);
}
else // 获取其它进程的模块信息,此时需要root权限
{
sprintf(szMapFilePath, “/proc/%d/maps”, pid);
}
fp = fopen(szMapFilePath, “r”);
if (fp != NULL)
{
while (fgets(szMapFileLine, 1023, fp) != NULL)
{
if (strstr(szMapFileLine, pszModName))
{
char *pszModAddrStart = strtok(szMapFileLine, “-”);
if (pszModAddrStart)
{
ulModBase = strtoul(pszModAddrStart, NULL, 16);
if (ulModBase == 0x8000)
ulModBase = 0;
bRet = true;
break;
}
}
}
fclose(fp);
}
return bRet;
}
/*****************************************************************************
Function: GetModuleFullName
Description: 获取进程中某一模块的绝对路径
Input: pid,是进程ID;pszModName,是要查找的模块名称;nBuffSize是pszFullModName的内存大小,防止溢出。
Output: pszFullModName,用来输出模块的绝对路径。
Return:如果获取模块的绝对路径成功,返回true;否则,返回false
*****************************************************************************/
bool GetModuleFullName(pid_t pid, const char *pszModName, char *pszFullModName, int nBuffSize)
{
bool bRet = false;
FILE *fp = NULL;
char szMapFilePath[32] = {0};
char szMapFileLine[1024] = {0};
char *pszFullName = NULL;
if (pszModName == NULL || pszFullModName == NULL || nBuffSize <= 0 )
{
return bRet;
}
if (pid < 0) // 获取自己进程的模块信息
{
sprintf(szMapFilePath, “/proc/self/maps”);
}
else // 获取其它进程的模块信息,此时需要root权限
{
sprintf(szMapFilePath, “/proc/%d/maps”, pid);
}
fp = fopen(szMapFilePath, “r”);
if (fp != NULL)
{
while (fgets(szMapFileLine, 1023, fp) != NULL)
{
if (strstr(szMapFileLine, pszModName))
{
if (szMapFileLine[strlen(szMapFileLine) - 1] == ‘\n’) // 去掉末尾的换行符
{
szMapFileLine[strlen(szMapFileLine) - 1] = 0;
}
pszFullName = strchr(szMapFileLine, ‘/’);
if (pszFullName == NULL)
{
continue;
}
strncpy(pszFullModName, pszFullName, nBuffSize - 1);
bRet = true;
}
}
fclose(fp);
}
return bRet;
}
三、Android内存模块遍历的运用场景
模块遍历的运用场景非常广泛,如:
l 获取自己进程的内存模块信息。
l 获取目标进程的内存模块信息。
l 检查注入模块的信息。
l 检查Hook模块的信息。
总之,通过内存模块遍历拿到模块的基础信息,结合文件操作,可以对模块的情况掌握得很清楚。