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. 关键点总结

  1. 虚拟内存布局:理解进程内存布局是基础,特别是堆和栈的位置。

  2. /proc文件系统

    • maps文件提供内存映射信息
    • mem文件允许直接访问进程内存
  3. 权限检查:修改内存前必须确认区域有读写权限。

  4. 地址转换:需要将虚拟地址转换为文件偏移量。

  5. 字符串替换限制:新字符串长度不应超过原字符串,否则可能破坏内存结构。

8. 安全注意事项

  1. 必须以root权限运行脚本才能访问/proc/[pid]/mem
  2. 修改运行中进程的内存可能导致程序崩溃
  3. 仅用于学习和研究目的

9. 扩展学习

  1. 尝试修改其他类型的数据(如整数)
  2. 研究如何修改栈上的变量
  3. 了解ELF文件格式与内存映射的关系
  4. 探索更复杂的数据结构在内存中的布局

通过本教程,你应该已经掌握了如何利用/proc文件系统访问和修改运行中进程内存的基本技术。这是理解Linux内存管理和进程间通信的重要一步。

虚拟内存黑客教学: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 显示进程当前映射的内存区域及其权限,格式为: 4.2 /proc/[ pid ]/mem 可用于访问进程内存页面,通过open、read和lseek系统调用。 5. 实践示例 5.1 示例C程序 5.2 strdup函数分析 使用malloc分配内存 返回新字符串的指针 内存位于堆中 5.3 无限循环版本 6. 实际操作步骤 6.1 查找进程PID 6.2 检查/proc/[ pid ]/maps 示例输出: 6.3 Python脚本修改内存 6.4 运行脚本 7. 关键点总结 虚拟内存布局 :理解进程内存布局是基础,特别是堆和栈的位置。 /proc文件系统 : maps文件提供内存映射信息 mem文件允许直接访问进程内存 权限检查 :修改内存前必须确认区域有读写权限。 地址转换 :需要将虚拟地址转换为文件偏移量。 字符串替换限制 :新字符串长度不应超过原字符串,否则可能破坏内存结构。 8. 安全注意事项 必须以root权限运行脚本才能访问/proc/[ pid ]/mem 修改运行中进程的内存可能导致程序崩溃 仅用于学习和研究目的 9. 扩展学习 尝试修改其他类型的数据(如整数) 研究如何修改栈上的变量 了解ELF文件格式与内存映射的关系 探索更复杂的数据结构在内存中的布局 通过本教程,你应该已经掌握了如何利用/proc文件系统访问和修改运行中进程内存的基本技术。这是理解Linux内存管理和进程间通信的重要一步。