Hack 虚拟内存系列(一):C字符串和/proc
字数 1084 2025-08-20 18:17:31
虚拟内存黑客教学:C字符串与/proc文件系统
1. 概述
本教程将指导你如何通过/proc文件系统访问和修改运行中进程的虚拟内存内容,特别是堆中的C字符串。这是"Hack虚拟内存"系列的第一部分,重点介绍基础概念和实际操作。
2. 环境准备
2.1 系统要求
- Ubuntu 14.04 LTS
- Linux内核版本: 4.4.0-31-generic
- GCC版本: 4.8.4
- Python 3.4.3
2.2 前提知识
- C语言基础
- Python基础
- Linux文件系统和shell基础
3. 虚拟内存基础
3.1 虚拟内存概念
虚拟内存是一种内存管理技术,它将程序使用的虚拟地址映射到物理内存地址。关键特点包括:
- 每个进程有自己的虚拟地址空间
- 虚拟内存大小取决于系统架构(32位或64位)
- 操作系统负责管理虚拟地址空间
3.2 虚拟内存布局
典型Linux进程的虚拟内存布局:
高地址
-----------------
命令行参数和环境变量
栈(向下增长)
...
...
堆(向上增长)
程序可执行代码
低地址
4. 关键工具:/proc文件系统
/proc是一个伪文件系统,提供内核数据结构的接口。我们将重点关注:
4.1 /proc/[pid]/maps
显示进程当前映射的内存区域及其权限,格式为:
address perms offset dev inode pathname
00400000-00452000 r-xp 00000000 08:02 173521 /usr/bin/dbus-daemon
00651000-00652000 r--p 00051000 08:02 173521 /usr/bin/dbus-daemon
4.2 /proc/[pid]/mem
可用于访问进程内存页面,通过open、read和lseek系统调用。
5. 实践示例
5.1 示例C程序
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
int main(void) {
char *s;
s = strdup("Holberton");
if (s == NULL) {
fprintf(stderr, "Can't allocate mem with malloc\n");
return (EXIT_FAILURE);
}
printf("%p\n", (void *)s);
return (EXIT_SUCCESS);
}
5.2 strdup函数分析
- 使用malloc分配内存
- 返回新字符串的指针
- 内存位于堆中
5.3 无限循环版本
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
int main(void) {
char *s;
unsigned long int i;
s = strdup("Holberton");
if (s == NULL) {
fprintf(stderr, "Can't allocate mem with malloc\n");
return (EXIT_FAILURE);
}
i = 0;
while (s) {
printf("[%lu] %s (%p)\n", i, s, (void *)s);
sleep(1);
i++;
}
return (EXIT_SUCCESS);
}
6. 实际操作步骤
6.1 查找进程PID
ps aux | grep ./loop | grep -v grep
6.2 检查/proc/[pid]/maps
cat /proc/[pid]/maps
示例输出:
010ff000-01120000 rw-p 00000000 00:00 0 [heap]
6.3 Python脚本修改内存
#!/usr/bin/env python3
'''
Locates and replaces the first occurrence of a string in the heap of a process
Usage: ./read_write_heap.py PID search_string replace_by_string
'''
import sys
def print_usage_and_exit():
print('Usage: {} pid search write'.format(sys.argv[0]))
sys.exit(1)
# 检查参数
if len(sys.argv) != 4:
print_usage_and_exit()
pid = int(sys.argv[1])
search_string = str(sys.argv[2])
write_string = str(sys.argv[3])
# 打开maps和mem文件
maps_filename = "/proc/{}/maps".format(pid)
mem_filename = "/proc/{}/mem".format(pid)
try:
maps_file = open(maps_filename, 'r')
except IOError as e:
print("[ERROR] Can not open file {}:".format(maps_filename))
print("I/O error({}): {}".format(e.errno, e.strerror))
sys.exit(1)
for line in maps_file:
sline = line.split(' ')
# 查找堆区域
if sline[-1][:-1] != "[heap]":
continue
print("[*] Found [heap]:")
# 解析行内容
addr = sline[0]
perm = sline[1]
# 检查读写权限
if perm[0] != 'r' or perm[1] != 'w':
print("[*] {} does not have read/write permission".format(pathname))
maps_file.close()
exit(0)
# 获取堆的起始和结束地址
addr = addr.split("-")
addr_start = int(addr[0], 16)
addr_end = int(addr[1], 16)
# 打开mem文件
try:
mem_file = open(mem_filename, 'rb+')
except IOError as e:
print("[ERROR] Can not open file {}:".format(mem_filename))
print("I/O error({}): {}".format(e.errno, e.strerror))
maps_file.close()
exit(1)
# 读取堆内容
mem_file.seek(addr_start)
heap = mem_file.read(addr_end - addr_start)
# 查找字符串
try:
i = heap.index(bytes(search_string, "ASCII"))
except Exception:
print("Can't find '{}'".format(search_string))
maps_file.close()
mem_file.close()
exit(0)
print("[*] Found '{}' at {:x}".format(search_string, i))
# 写入新字符串
print("[*] Writing '{}' at {:x}".format(write_string, addr_start + i))
mem_file.seek(addr_start + i)
mem_file.write(bytes(write_string, "ASCII"))
# 关闭文件
maps_file.close()
mem_file.close()
break
6.4 运行脚本
sudo ./read_write_heap.py [pid] "Holberton" "Fun w vm!"
7. 关键点总结
-
虚拟内存布局:理解进程内存布局是基础,特别是堆和栈的位置。
-
/proc文件系统:
- maps文件提供内存映射信息
- mem文件允许直接访问进程内存
-
权限检查:修改内存前必须确认区域有读写权限。
-
地址转换:需要将虚拟地址转换为文件偏移量。
-
字符串替换限制:新字符串长度不应超过原字符串,否则可能破坏内存结构。
8. 安全注意事项
- 必须以root权限运行脚本才能访问/proc/[pid]/mem
- 修改运行中进程的内存可能导致程序崩溃
- 仅用于学习和研究目的
9. 扩展学习
- 尝试修改其他类型的数据(如整数)
- 研究如何修改栈上的变量
- 了解ELF文件格式与内存映射的关系
- 探索更复杂的数据结构在内存中的布局
通过本教程,你应该已经掌握了如何利用/proc文件系统访问和修改运行中进程内存的基本技术。这是理解Linux内存管理和进程间通信的重要一步。