15. 进程的地址空间
进程的地址空间
一个很基本 (但也很困难) 的问题
进程的状态机模型
- 进程状态 = 内存 + 寄存器
- 到底什么是 “进程的内存”?
- 以下程序的 (可能) 输出是什么?
printf("%p\n", main);
- 何种指针访问不会引发segmentation fault?
1 | char *p = random(); |
进程的地址空间
RTFM: /proc/[pid]/maps (man 5 proc)
- 进程地址空间中的每一段
- 地址 (范围) 和权限 (rwxsp)
- 对应的文件: offset, dev, inode, pathname
- TFM 里有更详细的解释
- 和 readelf (-l) 里的信息互相验证
更多的提问:我们能 “控制” pmap 的输出吗?
- 修改堆 (bss) 内存的大小
- 在栈上分配大数组……
管理进程地址空间
状态机的视角
- 地址空间 = 带访问权限的内存段
- 不存在 (不可访问)
- 不存在 (可读/写/执行)
- 管理 = 增加/删除/修改一段可访问的内存
你会提供怎样的系统调用?
Memory Map 系统调用
在状态机状态上增加/删除/修改一段可访问的内存
- MAP_ANONYMOUS: 匿名 (申请) 内存
- fd: 把文件 “搬到” 进程地址空间中 (例子:加载器)
- 更多的行为请参考手册 (复杂性暴增)
1 | // 映射 |
使用 mmap
Example 1: 申请大量内存空间
- 瞬间完成内存分配
- mmap/munmap 为 malloc/free 提供了机制
- libc 的大 malloc 会直接调用一次 mmap 实现
- 不妨 strace/gdb 看一下
Example 2: Everything is a file
- 映射大文件、只访问其中的一小部分
1 | with open('/dev/sda', 'rb') as fp: |
入侵进程地址空间
Hacking Address Spaces
进程 (状态机) 在 “无情执行指令机器” 上执行
- 状态机是一个封闭世界
- 但如果允许一个进程对其他进程的地址空间有访问权?
- 意味着可以任意改变另一个程序的行为
一些 “入侵” 进程地址空间的例子
- 调试器 (gdb)
- gdb 可以任意观测和修改程序的状态
- Profiler (perf)
- M3 中借助它理解程序的性能瓶颈
物理入侵进程地址空间
金手指:直接物理劫持内存
- 听起来很离谱,但 “卡带机” 时代的确可以做到!
- 今天我们有 Debug Registers 和 Intel Processor Trace
- 帮助系统工具 “合法入侵” 地址空间
Game Genie: 一个 Look-up Table (LUT)
- 简单、优雅:当 CPU 读地址 时读到,则替换为
- Technical Notes (专利, How did it work?)
Game Genie as a Firmware
- 配置好 LUT、加载卡带上的代码 (像是一个 “Boot Loader”)
随着游戏越来越大……
地址空间那么大,哪个才是 “金钱”?
- 包含动态分配的内存,每次地址都不一样
- 思路:Everything is a state machine
- 观察状态机的 trace,就知道哪个是金钱了
查找 + Filter
- 进入游戏时
打了个怪
符合变化的内存地址是很少的- 好了,出门就是满级了
入侵进程地址空间:金山游侠
一招制胜
- 包含非常贴心的 “游戏内呼叫” 功能 (Hack DirectX)
- 它就是专为游戏设计的 “调试器”
给进程发送 GUI 事件
按键精灵
大量重复固定的任务 (例如 2 秒 17 枪)
现按键精灵
给进程发送键盘/鼠标事件
- 做个驱动 (可编程键盘/鼠标)
- 利用操作系统/窗口管理器提供的 API
- xdotool
- (我们用这玩意测试 vscode 的插件)
- ydotool
- evdev (按键显示脚本;主播常用)
- xdotool
2024 年的应用:实现 AI Copilot Agent
- 文本/截图 → AI 分析 → 执行动作
改变进程对时间的感知
调整游戏的逻辑更新速度
比如神秘公司神秘游戏慢到难以忍受的跑图和战斗
- 今天游戏市场已经内卷到新手成长路线不顺滑就会被喷了
变速齿轮
变速齿轮:原理
程序 = 状态机
- “计算指令” 是不能感知时间的
- spin count 计时会出现 “机器变快,游戏没法玩” 的情况
- syscall 是感知时间的唯一方法
“劫持” 和时间相关的syscall/库函数
- 改变程序对时间的认知
- 就像手表调快/慢了一样
定制游戏外挂
“劫持代码” 的本质是 debugger 行为
- 游戏也是程序,也是状态机
- 外挂就是 “为这个游戏专门设计的 gdb”
例子:锁定生命值
-
创建,线程 spin modify:
while (1) hp = 9999;
-
但还是可能出现 hp < 0 的判定 (一刀秒)
-
可以 patch 掉判定的代码 (软件动态更新)
代码注入
用一段代码 “勾住” (hook) 函数的执行
- 然后就可以入侵程序逻辑 (从而为所欲为) 了
关于外挂/代码注入
也可以用来做 “好” 的事情
- “软件动态更新”:在不停止系统的时候打热补丁 Live kernel patching
-
技术,无论是计算机系统、编程语言还是人工智能,都是给人类带来福祉的——我们甚至可以开发游戏外挂辅助你练习、提高成绩。
-
与此同时,强大的技术总有 “负面” 的用途。用任何技术损害他人的利益,都是一件可耻的事情 (academic integrity)。同样,如果你希望在人生这场 game (博弈) 中走得更远。
-
状态机的视角自然地将我们引入 “内存到底是什么” 的问题——它的答案同样也很自然:带有访问权限控制的连续内存段。我们可以通过 mmap、munmap、mprotect 三个系统调用调整状态机的地址空间,包括分配匿名的内存、映射文件内容到内存、修改访问权限等。更有趣的是操作系统有 “能够实现一切应用程序” 的需求,调试器也不在话下——这也给了我们入侵其他进程地址空间的机制。