微微风簇浪
散作满河星
如何获取存储在bss段上的,未初始化的“其他进程”的全局变量?
首先考虑到的是使用ptrace,但是ptrace获取其他进程的全局变量,需要该变量的地址。
那如何得到对应bss段变量的地址呢?
使用对应程序段的起始地址+变量的地址偏移量,接下来进行验证和说明。
这里先贴出被测试的程序:
#include <cstdio>
#include <iostream>
#include <unistd.h>
struct temp {
int a;
int b;
int c;
};
temp g_test;
int test = 1;
void func() {
printf("0x%016lx\n", &g_test);
sleep(100000);
}
int main() {
printf("%d\n", getpid());
func();
return 0;
}
变量的地址以及调试信息
调试符号
可以参考符号表
关于elf中的st_value,一定要看,很重要!!!!
关于调试信息,可以自行查资料,目前比较常用的就是dwarf,也就是在编译程序的时候加上-g选项,就能在对应可执行文件中加入符号信息。
linux下查看调试符号可以通过readelf
和objdump
两个工具。
如果使用readelf -s <exename>
会打印出所有符号的信息:
如果需要找到对应变量的信息,可以直接grep
,比如我使用readelf -s testo | grep g_test
:
其中value有可能是偏移量,也有可能就是绝对地址。
目前关于st_value的值,因为我们查看的就是elf可执行文件,所以实际上该地址就是内存中的虚拟地址。
特别是对于全局变量来说,无论在.data段还是.bss段,都是在编译后就确定了其虚拟地址,后面会进行验证和分析。
地址范围
程序运行后,代码加载进入内存,并打印结果:
第一行是进程id,第二行是未初始化的全局变量g_test
的地址,然后再使用gdb attach到目标进程中,可以查找到已经初始化的test
全局变量的地址:
同时会在/proc/<pid>/maps
文件中存储下每个段的地址范围:
每一行前面表示的是地址范围和读写权限等信息,最后表示的是文件名。
主要关注前四行和倒数第四行,行数从大到小(内存由低到高)分别表示:
- 第一行代码段,因为只读+可执行
- 第二行我也不知道23333
- 第三行是data+bss段,根据前面打印的初始化以及未初始化的全局变量的地址,可以看到是吻合的
- 第四行是堆地址
- 还有倒数第四行是栈地址
说这么多,基本就是想说明,我们可以通过调试信息得到全局变量的地址,无论是bss段还是data段,然后就能使用ptrace读取些变量的值,下面进行实践和验证。
获取进程中全局变量的值
绝对地址验证(gcc较低版本或者clang)
关于ptrace,相关的博客介绍很多,相关使用可以参考我的博客文章如何获取进程函数参数,这里不再过多追说了。
获取变量值需要两个参数,分别是进程id和变量地址,进程id就是之前看到的11976,而变量的地址就是我们在读取elf文件得到的:
很简单,贴上代码:
#include <sys/ptrace.h>
#include <iostream>
#include <errno.h>
#include <unistd.h>
#include <wait.h>
int wait4stop(pid_t pid) {
int status = 99;
do {
if (waitpid(pid, &status, 0) == -1 || WIFEXITED(status) || WIFSIGNALED(status))
return 0;
} while(!WIFSTOPPED(status));
return 1;
}
int get_value(int *address, pid_t pid) {
ptrace(PTRACE_ATTACH, pid, nullptr, nullptr);
if (!wait4stop(pid))
std::cerr << "wait SIGSTOP of ptrace failed" << std::endl;
long ret = ptrace(PTRACE_PEEKDATA, pid, address, nullptr);
if (errno != 0) {
std::cerr << "ptrace error!---" << errno << std::endl;
ret = -1;
}
ptrace(PTRACE_DETACH, pid, nullptr, nullptr);
return static_cast<int>(ret);
}
int main() {
pid_t pid = 1880;
int *address1 = reinterpret_cast<int *>(0x601060);
int *address2 = reinterpret_cast<int *>(0x601070);
printf("%d\n", get_value(address1, pid));
printf("%d\n", get_value(address2, pid));
return 0;
}
输出:
1
0
注:如果报错为3,说明没有权限或者进程id错误,使用sudo或者检查进程id。
相对地址验证(gcc6.3较高版本)
需要说明的是,在gcc/g++较高版本,我发现这个问题时,使用的gcc6.3,是相对地址 。
使用如下待验证程序:
#include <cstdio>
#include <unistd.h>
#include <vector>
struct test {
int a;
int b;
std::vector<int> c;
};
test aaa;
void func(test a, test b, int c, int d) {
printf("%016lx\n", &aaa);
sleep(100000);
}
int main() {
printf("%d\n", getpid());
printf("%016lx\n", &aaa);
test a, b;
func(a, b, 11, 12);
return 0;
}
首先通过objdump命令查看bss段信息获取aaa的相对地址,假设该地址为X:
然后直接运行该程序,打印结果如下:
其中第一行打印是进程id,第二三行是aaa的实际地址,设该地址为Z。
接下来查看程序起始地址:
也就是563e937b4000,假设为Y。
可以很明显的看到:
563e937b4000 + 2030a0 = 0000563e939b70a0,也就是X + Y = Z。
说明通过这样的方法可以获得变量的实际地址。