南京大学 操作系统 (JYY) 学习笔记:进程地址空间与内存“外挂”魔法
写在前面:这是本系列的第六篇。
在上一讲中,我们学习了如何用
fork和execve创造和洗脑一个进程。但是,当一个新进程苏醒时,它的脑子(内存)里到底有什么?今天,我们将深入探索进程的地址空间。不仅要学习正规军的
mmap内存映射魔法,还要化身黑客,看看如何利用状态机的弱点,“合法入侵”并篡改其他进程的内存(比如做个游戏外挂)。

课前思考与回顾
关于进程管理的延续思考:
- 深度强化学习 (Reinforcement learning) 与大模型的崛起正在改变编程,但操作系统依然很重要。
- 编程语言(Markdown, JS, Python 等)是“可信可验证”的桥梁。
- 操作系统是运行支撑(提供虚拟环境、文件系统快照、网络等)。
- 程序必须要到一个真实的环境中运行,大语言模型终究会产生幻觉,而操作系统底层的隔离机制(如 Docker)能确保我们在试错时不会把系统搞崩溃。
状态机的生命周期管理 API:
fork,execve和_exit- 操作系统视角的本质:状态机的复制、重置和删除。
int pid = fork();
if (pid == -1) { // 错误
perror("fork"); goto fail;
} else if (pid == 0) { // 子进程
execve(...);
perror("execve"); exit(EXIT_FAILURE);
} else { // 父进程
...
int status;
waitpid(pid, &status, 0); // 等待状态机结束
}
进程的初始状态
当我们调用 execve 创建(重置)一个进程后,它的初始状态是怎样的?更深层次的理解需要我们去窥探它的寄存器和内存。
对于内存,有点难办,因为我们可以把任何整数强转成指针去访问,但进程空间里的绝大部分区域是没有访问权限的(Segmentation Fault)。
那么,到底哪些可以访问???计算机系统里没有黑魔法,所有一切都是有答案的。
结论:进程 execve 后的初始状态
- ABI 中规定的 initial state:
在 System V ABI 手册的 Section 3.4 “Process Initialization” 中,只规定了部分寄存器和栈的初始状态(比如argv和envp中的字符串保存在栈中)。 - Binary 中指定的 PT_LOAD 段:
可执行文件(ELF)里声明了内存是分成“一段一段”的,每一段都有严格的访问权限 (r-读, w-写, x-执行)。
观测进程的地址空间
配合 gdb 调试器,我们甚至可以“随时查看程序的地址空间”。(这些代码也是 AI 生成的,AI 是帮助我们理解复杂细节的利器)。
root@LAPTOP-GT06V0GS:/mnt/d/CSLab/osCourse/lec6/address-space# ./alloc
mmap: 7fd2c4e88000
Read get: 2
Read get: 0
Read get: 3
root@LAPTOP-GT06V0GS:/mnt/d/CSLab/osCourse/lec6/address-space# ./mmap-demo
This is allocated memory at 0x7fe150f8a000
Mapped executable at 0x7fe150ec0000, size: 823704 bytes
First 16 bytes of executable: 7f 45 4c 46 02 01 01 03 00 00 00 00 00 00 00 00
root@LAPTOP-GT06V0GS:/mnt/d/CSLab/osCourse/lec6/address-space# ./simple
进程的地址空间管理
往后想一想
进程的初始状态,只有 ELF 文件里声明的内存和一些操作系统分配的栈内存。任何其他指针的访问都是非法的。
那么问题来了:如果我们从输入读了一个非常大的 size,然后调用 malloc(size),这些新内存是从哪里来的呢?
结论:一定有一个“系统调用”可以改变进程的地址空间!
Memory Map 系统调用 (mmap)
如果你来设计操作系统的 API,你会怎么设计?UNIX 给出的终极答案是 mmap。它可以在状态机上增加、删除或修改一段可访问的内存。
// 映射
void *mmap(void *addr, size_t length, int prot, int flags,
int fd, off_t offset);
// 解除映射
int munmap(void *addr, size_t length);
// 修改映射权限
int mprotect(void *addr, size_t length, int prot);
- MAP_ANONYMOUS (匿名映射): 不需要文件,直接向操作系统伸手要一块纯粹的物理内存(这就是大块
malloc的底层原理)。 - 文件映射 (fd): 把文件直接“搬到”进程地址空间中,不需要
read和write,直接用指针像读写数组一样读写文件!(加载器底层就是这么干的)。
我们可以用 pmap 命令查看进程的地址空间。
优雅使用 mmap 的两个例子
Example 1: 申请大量内存空间
- 瞬间完成内存分配。
mmap/munmap为malloc/free提供了底层机制。libc 的malloc在申请大块内存时,会直接调用一次mmap实现。- 底层原理:
mmap申请内存时,操作系统只是在页表里做个记录,并不会立即分配真实的物理内存。只有当你的代码真的一刀劈下去(发生缺页中断访问它时),物理内存才会被分配,因此申请极快。
Example 2: Everything is a file (万物皆文件)
- 映射几个 G 的大文件,但只访问其中的一小部分。
# Python 中的 mmap 演示
import mmap
import hexdump
# 打开物理磁盘设备文件
with open('/dev/sda', 'rb') as fp:
# 映射文件 (128 GB 虚拟空间)
mm = mmap.mmap(fp.fileno(),
prot=mmap.PROT_READ, length=128 << 30)
# 打印前 512 字节 (MBR 扇区)
hexdump.hexdump(mm[:512])
入侵进程的地址空间 (Hacking Address Space)
进程在“无情执行指令的机器”上执行,本来是一个封闭世界。
但如果允许一个进程对另一个进程的地址空间有访问权呢?
这意味着我们可以任意改变另一个程序的行为!听起来就很 cool。
- 例子:我们可以改变 gdb 或 其他编译程序的内部代码。
- 这不仅可以用来调试,还能让你获得“开挂”的权利。
物理入侵进程地址空间
金手指:直接物理劫持内存
听起来很离谱,但在古早的“卡带机”时代,这的确是可以做到的!
- Game Genie (游戏金手指): 本质上是一个硬件级别的 Look-up Table (LUT)。
- 它的逻辑简单且优雅:串联在卡带和主机之间,当 CPU 想要读地址
a,原本应该读出数值x时,它直接在物理引脚上拦截,替换为我们要修改的数值y(比如无限命、无敌)。
在现代系统中,虽然不能轻易拔插引脚,但 CPU 提供了特定的硬件支持:
- Debug Registers (调试寄存器): CPU 提供的一种机制,用于支持硬件断点。
- Intel Processor Trace: 记录程序的执行路径和内存访问情况。这些机制帮助系统工具(如 gdb)“合法”入侵地址空间。
内存扫描:外挂的软件演进
随着游戏越来越大,地址空间动辄几十个 GB,你根本分不清哪些地址存的是玩家的生命值,哪些是怪物的属性,更何况包含大量动态分配 (malloc) 的内存,每次重启游戏地址都不一样!
解决思路:Everything is a state machine (一切皆状态机)
我们只需要观察状态机的变化轨迹 (Trace):
- 查找 + Filter (Cheat Engine 原理):
- 进入游戏时,经验值
exp = 4950,在几十 GB 内存中搜索所有值为 4950 的地址(可能有几十万个)。 - 去打个怪,此时
exp = 5100。 - 在刚才找到的那些地址中,再次筛选出当前值变为
5100的地址。 - 符合
4950 -> 5100连续变化的内存地址,通常只剩下个位数。 - 锁定地址,把里面的值改成
99999999,好了,出门就是满级了!
极客精神的启示: 只要你认为一件事情符合逻辑法则,这件事情就可以干下去。哪怕网上没有先例、没有教程,AI 会辅佐你完成这一切!
降维打击:物理级“外挂”
反作弊系统越来越强,读写别人内存的行为会被检测封号怎么办?
用魔法打败魔法:跳出操作系统!
- 采集视频信号: 用视频采集卡 (如 MS2130) + 树莓派,直接从物理线缆上截取屏幕画面。
- 用 AI 跑图像识别(如 YOLO),分析出敌人的头部坐标。
- 用单片机模拟一个物理的 USB 鼠标,向电脑发送移动指令(自动瞄准)。
- 进阶: 甚至可以用 FPGA 设计一个电路,直接在硬件底层把 bounding-box(瞄准框)绘制到视频流上输出到显示器,连主机操作系统都毫无察觉。
创造变得前所未有的容易。发挥你的想象力,一旦思路打开,也许就会改变人类的命运!
openEuler 是由开放原子开源基金会孵化的全场景开源操作系统项目,面向数字基础设施四大核心场景(服务器、云计算、边缘计算、嵌入式),全面支持 ARM、x86、RISC-V、loongArch、PowerPC、SW-64 等多样性计算架构
更多推荐


所有评论(0)