前些天看了关于在密码学应用中使用java.lang.String
与byte[]
的相关讨论,不推荐使用java.lang.String
的重点就是其将在JVM中驻留,从而可能被窃取。但是,如何从内存中获取这些内容?JVM当然提供了一些机制,但是个人更喜欢从内核的角度来看看这个问题。
/proc/${pid}/maps
首先当然是确定进程堆栈在物理内存的位置啦。很遗憾,没有找到相关的方案。毕竟进程记录的都是虚拟线性地址,而通过内核分段、分页机制最终映射到物理内存。不过,从/proc
虚拟文件系统中,提供了进程虚拟地址映射。
address perm offset dev inode pathname
556566cb5000-556566cb6000 r-xp 00000000 fc:01 2496598 /root/ffTrace/run
556566eb5000-556566eb6000 r--p 00000000 fc:01 2496598 /root/ffTrace/run
556566eb6000-556566eb7000 rw-p 00001000 fc:01 2496598 /root/ffTrace/run
55656814f000-556568170000 rw-p 00000000 00:00 0 [heap]
7f2a95f91000-7f2a96178000 r-xp 00000000 fc:01 1835434 /lib/x86_64-linux-gnu/libc-2.27.so
7f2a96178000-7f2a96378000 ---p 001e7000 fc:01 1835434 /lib/x86_64-linux-gnu/libc-2.27.so
7f2a96378000-7f2a9637c000 r--p 001e7000 fc:01 1835434 /lib/x86_64-linux-gnu/libc-2.27.so
7f2a9637c000-7f2a9637e000 rw-p 001eb000 fc:01 1835434 /lib/x86_64-linux-gnu/libc-2.27.so
7f2a9637e000-7f2a96382000 rw-p 00000000 00:00 0
7f2a96382000-7f2a963a9000 r-xp 00000000 fc:01 1835410 /lib/x86_64-linux-gnu/ld-2.27.so
7f2a965a0000-7f2a965a2000 rw-p 00000000 00:00 0
7f2a965a9000-7f2a965aa000 r--p 00027000 fc:01 1835410 /lib/x86_64-linux-gnu/ld-2.27.so
7f2a965aa000-7f2a965ab000 rw-p 00028000 fc:01 1835410 /lib/x86_64-linux-gnu/ld-2.27.so
7f2a965ab000-7f2a965ac000 rw-p 00000000 00:00 0
7ffe2cf5e000-7ffe2cf7f000 rw-p 00000000 00:00 0 [stack]
7ffe2cfed000-7ffe2cff0000 r--p 00000000 00:00 0 [vvar]
7ffe2cff0000-7ffe2cff2000 r-xp 00000000 00:00 0 [vdso]
ffffffffff600000-ffffffffff601000 r-xp 00000000 00:00 0 [vsyscall]
/proc/${pid}/maps
记录了当前进程虚拟内存区域的分配以及其访问控制。
- 前三行表述的是当前进程ELF文件在虚拟内存中的地址(这里使用的ELF文件名为
run
)- 第一行
r-xp
表示其将配合Code Segment Register (CS)
作为CPU执行指令的直接依据。 - 第二三行分别用作可读、可写数据区,将配合
Data Segment Register (DS), ES, FS, GS
等使用
- 第一行
- 第四行直截了当,就是分配给堆的地址空间。当然,如果不够,可以不断地向上扩张。
xxx.so
文件描述的是C共享库在虚拟内存中的地址。- 最后才是栈内存,将以倒序的方式下内存低地址扩张。
- 至于之后的内容,不了解,不表。
ptrace
拿到了进程虚拟内存分布,又如何获取其中的内容。ptrace
总算是派上用场了。之前在阅读内核源码的时候,任务数据结构 struct task
专门为此预留了一些字段来加以描述,但始终找不到其用途。现在总算对其有了初步的了解。
一般来说,进程彼此之间应该相互独立,虽然运行在同一台机器上,但应该是相互间不知道其他进程的存在。那又如何能够通过一个进程的代码来获取另一个进程的堆栈数据呢?ptrace
提供的就是这么一种可能性。通过 PTRACE_ATTACH
和 PTRACE_DETACH
,A进程会使得目标进程B陷入Sleeping状态,而等待A继续通过其他命令来获取其数据。至于为什么会是陷入Sleeping呢?一旦B进程的在运行,数据等随时可能改变,显然不适合读取数据啊。
如何读取?PTRACE_PEEKTEXT
就是这样一个实现进程间交互的好工具。
void attach()
{
if (ptrace(PTRACE_ATTACH, options.pid, NULL, NULL) == -1)
{
fprintf(stderr, "ptract attach failed. %s(errno: %d)\n", strerror(errno), errno);
exit(0);
}
fprintf(stderr, "attach to %d success!\n", options.pid);
wait(NULL);
}
void peek()
{
char maps[17];
sprintf(maps, "/proc/%d/maps", options.pid);
FILE *fd = fopen(maps, "r");
if (fd == NULL)
{
fprintf(stderr, "open /proc/%d/maps failed. %s(errno: %d)\n", strerror(errno), errno);
exit(0);
}
struct map *map = (struct map *) malloc(sizeof(struct map *));
long word;
while (fscanf(fd, "%llx-%llx %s %lx %*s %*s%*[^\n]", &map->start_addr, &map->end_addr, map->op_flag, &map->offset) != EOF)
{
if (map->op_flag[0] == '-')
continue;
fprintf(stderr, "peek from [%llx-%llx]\n", map->start_addr, map->end_addr);
long mem_len = map->end_addr - map->start_addr;
char *data = malloc(mem_len + 1);
for (long cursor = map->start_addr;cursor < map->end_addr;cursor += sizeof(long))
{
if ((word = ptrace(PTRACE_PEEKTEXT, options.pid, cursor, NULL)) == -1 && errno)
{
fprintf(stderr, "peek failed. %s(errno: %d)\n", strerror(errno), errno);
free(data);
exit(0);
}
memcpy(data+cursor-map->start_addr, &word, sizeof(word));
}
dump(data, mem_len);
free(data);
}
free(map);
}
void detach()
{
if (ptrace(PTRACE_DETACH, options.pid, NULL, NULL) == -1)
{
fprintf("ptract detach failed. %s(errno: %d)\n", strerror(errno), errno);
exit(0);
}
fprintf(stderr, "detach from %d success!", options.pid);
}
int main(int argc, char **argv)
{
// ...
attach();
peek();
detach();
}
此处的代码片段就能完成dump堆栈的工作了(当然,由于没有对其它内容进行处理,同时会dump下ELF数据等)。完整代码
小结
当然,之后才发现 GDB 的实现借用的也正是这样一套机制。同时也意味着上面这段代码的实现在 GDB 中有现成的工具了:<
__ __
/ _| __ _ _ __ __ _ / _| ___ _ __ __ _
| |_ / _` | '_ \ / _` | |_ / _ \ '_ \ / _` |
| _| (_| | | | | (_| | _| __/ | | | (_| |
|_| \__,_|_| |_|\__, |_| \___|_| |_|\__, |
|___/ |___/