Mac OS下读取Python进程内存触发Segmentation Fault问题求助
解决Mach VM API操作Python进程内存触发段错误的问题
问题根源
CPython的内存模型和普通C程序差异极大:
- Python的
str是封装后的PyUnicodeObject结构体,你打印的id()地址是结构体本身的指针,而非实际字符数据的起始地址,直接操作这个地址会破坏Python的内部管理结构。 - Python有自己的内存池、引用计数机制,部分内存页有严格的权限保护,随意修改会触发内存错误。
- 普通C程序的字符串是直接的栈/堆字符数组,而Python字符串的实际数据存储在结构体的特定字段或偏移位置,且受版本影响。
解决步骤
准确定位字符数据的实际地址
不要直接用Python输出的id(s)作为读写地址。需要根据对应CPython版本的PyUnicodeObject结构体定义,计算实际字符数据的偏移:- 对于3.9+的紧凑型Unicode字符串,实际UTF-8数据通常存在结构体的
utf8字段中,或紧跟结构体末尾(需查阅对应版本的CPython源码确认偏移量)。 - 例如,你可以先通过
vm_read读取PyUnicodeObject的前几个字段,验证字符串长度、类型标识,再定位数据区域。
- 对于3.9+的紧凑型Unicode字符串,实际UTF-8数据通常存在结构体的
正确处理内存权限
Python进程的内存页可能是只读权限,操作前需临时调整权限,操作后恢复:- 用
vm_protect获取目标内存页的原权限。 - 将权限修改为
VM_PROT_READ | VM_PROT_WRITE。 - 完成读写后,恢复原权限,避免破坏Python的内存安全机制。
- 用
规避Python内部管理区域
只针对字符串的实际字符数据区域操作,绝对不要修改PyUnicodeObject的管理字段(如引用计数、长度、类型指针),也不要触碰Python的内存池(arena)区域,这些操作会直接导致段错误。确保操作的稳定性
操作前用task_suspend暂停Python进程,防止操作过程中Python触发GC或内存重分配导致地址失效;操作完成后用task_resume恢复进程。
操作前先用vm_read验证目标地址的可读性,确认地址有效再执行写入。
示例伪代码
#include <mach/mach.h> #include <stdio.h> #include <string.h> // 需根据目标CPython版本定义PyUnicodeObject的关键字段偏移 #define PY_UNICODE_UTF8_OFFSET 0x30 // 示例偏移,需实际核对对应版本 int main() { task_t target_task; mach_vm_address_t py_obj_addr = 0x100700000; // Python输出的id(s)地址 mach_vm_address_t str_data_addr = py_obj_addr + PY_UNICODE_UTF8_OFFSET; char new_content[] = "modified_string"; vm_prot_t old_prot, old_max_prot; // 假设已通过pid获取到target_task // 先保存原权限并调整为可读写 vm_protect(target_task, str_data_addr, strlen(new_content), FALSE, VM_PROT_READ | VM_PROT_WRITE, &old_prot, &old_max_prot); // 写入新内容 vm_write(target_task, str_data_addr, (vm_offset_t)new_content, strlen(new_content)); // 恢复原权限 vm_protect(target_task, str_data_addr, strlen(new_content), FALSE, old_prot, &old_prot, &old_max_prot); return 0; }
关键注意事项
- 不同CPython版本的
PyUnicodeObject结构差异很大,必须针对目标版本查阅源码(如Include/cpython/unicodeobject.h)确认字段偏移。 - 写入的内容长度不能超过原字符串的已分配长度,否则会覆盖相邻内存区域,触发段错误。
- Bit-Slicer的核心逻辑就是自动解析Python的运行时结构、处理内存权限和进程暂停,你可以参考这类工具的实现思路来完善自己的代码。
内容的提问来源于stack exchange,提问作者Aayush
相关产品推荐
相关产品推荐

