Hack 虚拟内存系列(二):Python 字节对象
字数 1198 2025-08-29 08:32:24
Python 字节对象与虚拟内存操作详解
1. 概述
本教程将深入探讨如何通过虚拟内存操作来修改Python 3中运行的字节对象。我们将学习Python字节对象在内存中的表示方式,以及如何定位和修改这些对象。
2. 前提知识
在开始之前,需要掌握以下基础知识:
- C语言基础
- Python基础
- Linux文件系统和shell基础
- /proc文件系统基础
3. 环境准备
测试环境:
- Ubuntu 14.04 LTS
- Linux内核版本:4.4.0-31-generic
- gcc 4.8.4
- Python 3.4.3
4. Python字节对象基础
4.1 字节对象与字符串的区别
在Python 3中,字节对象(bytes)和字符串(str)是不同的类型:
s = "Betty" # <class 'str'>
s = b"Betty" # <class 'bytes'>
4.2 Python字节对象的内存表示
Python中的所有东西都是对象,包括字节对象。字节对象在CPython中的内部表示如下(来自bytesobject.h):
typedef struct {
PyObject_VAR_HEAD
Py_hash_t ob_shash;
char ob_sval[1];
/* 不变量:
* ob_sval包含'ob_size+1'个元素的空间
* ob_sval[ob_size] == 0
* ob_shash是字符串的哈希值,如果尚未计算则为-1
*/
} PyBytesObject;
其中:
PyObject_VAR_HEAD包含引用计数和类型信息ob_size存储字符串长度ob_sval存储实际的字节数据,以null结尾
5. 定位字节对象的内存地址
5.1 使用id()函数
Python的id()函数返回对象的内存地址:
s = b"Holberton"
print(hex(id(s))) # 输出对象地址
5.2 检查/proc/[pid]/maps
通过检查进程的maps文件,可以确定对象所在的虚拟内存区域:
cat /proc/[pid]/maps
6. 创建C扩展来检查字节对象
为了更深入地检查字节对象,我们可以创建一个C扩展:
6.1 C函数实现
#include "Python.h"
void print_python_bytes(PyObject *p) {
PyBytesObject *s;
unsigned int i;
printf("[.] bytes object info\n");
s = (PyBytesObject *)p;
if (s && PyBytes_Check(s)) {
printf(" address of the object: %p\n", (void *)s);
printf(" size: %ld\n", s->ob_base.ob_size);
printf(" trying string: %s\n", s->ob_sval);
printf(" address of the data: %p\n", (void *)(s->ob_sval));
printf(" bytes:");
for (i = 0; i < s->ob_base.ob_size + 1; i++) {
printf(" %02x", s->ob_sval[i] & 0xff);
}
printf("\n");
} else {
fprintf(stderr, " [ERROR] Invalid Bytes Object\n");
}
}
6.2 编译动态库
gcc -Wall -Wextra -pedantic -Werror -std=c99 -shared -Wl,-soname,libPython.so -o libPython.so -fPIC -I/usr/include/python3.4 bytes.c
6.3 Python中调用
import ctypes
lib = ctypes.CDLL('./libPython.so')
lib.print_python_bytes.argtypes = [ctypes.py_object]
s = b"Holberton"
lib.print_python_bytes(s)
7. 修改内存中的字节对象
7.1 内存搜索与替换脚本
创建一个可以搜索和替换所有可读写内存区域的Python脚本:
#!/usr/bin/env python3
'''
Locates and replaces all occurrences of an ASCII string in the
entire virtual memory of a process.
'''
import sys
def print_usage_and_exit():
print('Usage: {} pid search write'.format(sys.argv[0]))
exit(1)
# 检查参数
if len(sys.argv) != 4:
print_usage_and_exit()
# 获取参数
pid = int(sys.argv[1])
if pid <= 0:
print_usage_and_exit()
search_string = str(sys.argv[2])
if search_string == "":
print_usage_and_exit()
write_string = str(sys.argv[3])
if search_string == "":
print_usage_and_exit()
# 打开进程的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))
exit(1)
for line in maps_file:
# 解析maps文件行
sline = line.split(' ')
name = sline[-1][:-1]
addr = sline[0]
perm = sline[1]
# 只检查可读写的内存区域
if perm[0] != 'r' or perm[1] != 'w':
continue
# 获取内存区域起始和结束地址
addr_start, addr_end = [int(x, 16) for x in addr.split("-")]
try:
mem_file = open(mem_filename, 'rb+')
mem_file.seek(addr_start)
region = mem_file.read(addr_end - addr_start)
# 查找并替换字符串
try:
i = region.index(bytes(search_string, "ASCII"))
while True:
print("Found '{}' at {:x}".format(search_string, addr_start + i))
mem_file.seek(addr_start + i)
mem_file.write(bytes(write_string, "ASCII"))
mem_file.flush()
# 更新缓冲区继续搜索
region = region[:i] + bytes(write_string, "ASCII") + region[i+len(search_string):]
i = region.index(bytes(search_string, "ASCII"))
except ValueError:
pass
mem_file.close()
except IOError:
continue
maps_file.close()
8. 操作步骤
- 运行目标Python脚本:
./main_bytes.py
- 获取进程ID:
ps aux | grep main_bytes.py | grep -v grep
- 运行内存修改脚本:
sudo ./rw_all.py [pid] Holberton "~ Betty ~"
- 在目标脚本中按Enter键查看修改结果
9. 关键发现
-
Python字节对象在内存中的表示包含:
- 对象头信息
- 字符串长度(ob_size)
- 实际数据(ob_sval),以null结尾
-
Python对象不是存储在堆或栈中,而是在特定的内存区域
-
通过/proc文件系统可以访问和修改进程的虚拟内存
-
id()函数返回的是Python对象的内存地址
10. 未解决的问题
- 为什么在堆内存区域也能找到字符串"Holberton"?
- Python如何在堆外分配内存?
- 如果Python没有使用堆,object.h中"对象是在堆上分配的结构"是什么意思?
这些问题将在后续教程中探讨。
11. 总结
本教程详细介绍了:
- Python字节对象的内存结构
- 如何通过/proc文件系统访问进程内存
- 创建C扩展来检查Python对象
- 编写脚本搜索和修改内存中的字符串
通过这些技术,我们能够深入理解Python内部实现,并实现对运行中Python程序的内存操作。