1. 课程导论:什么是沙箱?为什么需要绕过沙箱?异架构Pwn(ARM/MIPS/RISC-V)与x86的区别
各位同学好。我是这门课的主讲,做了十来年二进制安全,从x86一路折腾到ARM、MIPS,最近几年又开始跟RISC-V较劲。今天咱们先聊聊沙箱——这个在漏洞利用里绕不过去的坎。
说实话,我刚开始做Pwn那会儿,沙箱还没这么普及。那时候拿到一个漏洞,直接弹shell就完事了。现在?你想想看,一个浏览器漏洞,前面套着三层沙箱,后面还有seccomp过滤。不把沙箱研究透,你连漏洞的边都摸不着。
1.1 什么是沙箱?
沙箱,说白了就是一个隔离环境。它把不可信的代码关在笼子里,限制它能做什么、不能做什么。
核心思想:即使攻击者控制了某个进程,也无法影响系统其他部分。
我见过很多新手问:沙箱和虚拟机有什么区别?嗯,这里要注意——虚拟机是模拟一整台机器,沙箱只是限制一个进程的行为。轻量得多,也常见得多。
常见的沙箱实现方式有几种:
| 类型 | 原理 | 典型代表 | 绕过难度 |
|---|---|---|---|
| 系统调用过滤 | 拦截syscall,只允许白名单调用 | seccomp-bpf | 中 |
| 能力限制 | 删除进程的capability | Linux capabilities | 低-中 |
| 命名空间隔离 | 隔离文件系统、网络、PID等 | Docker容器 | 中-高 |
| 代码执行限制 | 只允许执行特定区域的代码 | CFI、XOM | 高 |
| 系统服务沙箱 | 浏览器/PDF阅读器内置 | Chrome Sandbox | 高 |
我个人习惯把沙箱分成两类:内核级沙箱和用户态沙箱。内核级沙箱靠操作系统机制,比如seccomp;用户态沙箱靠运行时库,比如Java的SecurityManager。绕过思路完全不同。
1.2 为什么需要绕过沙箱?
你拿到了一个漏洞,控制了程序流程。然后呢?
如果程序没有沙箱,你可以直接:
- 调用execve弹shell
- 用open/read/write读写任意文件
- 用mmap执行shellcode
但如果程序有沙箱,这些操作可能全被禁止。你只能做沙箱允许的事——比如只允许read、write、exit。这时候你就得想办法绕过沙箱。
我的经验:绕过沙箱往往比触发漏洞更难。我在一次CTF比赛中,花了3天研究一个seccomp规则,最后发现可以用ORW(open-read-write)链读flag。那之后我就养成了一个习惯——拿到二进制先看沙箱规则。
绕过沙箱的常见思路:
- 利用沙箱漏洞——沙箱本身也有bug
- 利用未过滤的系统调用——比如只禁了execve,但没禁open
- 利用文件描述符继承——父进程打开的fd,子进程还能用
- 利用共享内存——通过共享内存与未沙箱化的进程通信
- 利用内核漏洞——直接提权,沙箱就形同虚设
你想想看,沙箱本质上是一套规则。规则就有漏洞,有盲区。我们的工作就是找到这些盲区。
1.3 异架构Pwn与x86的区别
好,接下来聊聊异架构。ARM、MIPS、RISC-V,这些架构和x86到底差在哪?
我最早接触异架构Pwn是在一次IoT设备漏洞挖掘中。一个路由器,MIPS架构,远程命令执行漏洞。我拿着x86的shellcode往上怼,结果程序直接崩了。嗯,从那以后我就老老实实学异架构了。
1.3.1 指令集差异
| 特性 | x86/x64 | ARM | MIPS | RISC-V |
|---|---|---|---|---|
| 指令长度 | 变长(1-15字节) | 固定4字节(ARM)/2字节(Thumb) | 固定4字节 | 固定4字节(基础指令集) |
| 寄存器数量 | 16(通用)+ 段寄存器 | 16/31 | 32 | 32 |
| 寻址模式 | 复杂,多种组合 | 简单,Load/Store架构 | 简单,Load/Store架构 | 简单,Load/Store架构 |
| 条件执行 | 通过标志位+条件跳转 | 大部分指令可条件执行 | 通过条件跳转 | 通过条件跳转 |
| 栈使用 | push/pop指令 | 手动管理sp | 手动管理sp | 手动管理sp |
这里有个关键点:ARM和MIPS都是Load/Store架构。什么意思?就是内存操作必须通过专门的load/store指令,其他指令只能在寄存器上操作。x86就不一样,很多指令可以直接操作内存。
这对漏洞利用有什么影响?
- ROP gadget的寻找方式不同——x86有大量内存操作指令,异架构相对少
- shellcode的编写方式不同——异架构通常需要手动管理指令流水线
- 栈布局不同——ARM有LR寄存器,MIPS有$ra寄存器,返回地址不总在栈上
1.3.2 调用约定差异
调用约定决定了参数怎么传、返回值怎么拿、寄存器谁保存。我整理了一个对比表:
| 架构 | 参数传递 | 返回值 | 调用者保存 | 被调用者保存 |
|---|---|---|---|---|
| x86 | 栈 | EAX | EAX, ECX, EDX | EBX, ESI, EDI, EBP |
| x64 | RDI, RSI, RDX, RCX, R8, R9 → 栈 | RAX | RAX, RCX, RDX, RSI, RDI, R8-R11 | RBX, RBP, R12-R15 |
| ARM | R0-R3 → 栈 | R0 | R0-R3, R12 | R4-R11, LR |
| MIPS | $a0-$a3 → 栈 | $v0 | $t0-$t9 | $s0-$s7, $gp, $sp, $fp, $ra |
| RISC-V | a0-a7 → 栈 | a0 | ra, t0-t6 | s0-s11, sp, gp, tp |
你看,x86用栈传参,ARM和MIPS先用寄存器。这意味着什么?
- 构造ROP链时,x86需要大量pop指令来清理栈
- ARM和MIPS只需要mov指令设置寄存器
- 异架构的ROP链通常更短,但gadget更难找
避坑指南:我曾经在ARM上做ROP,以为LR寄存器保存的返回地址可以直接用。结果发现ARM的BL指令会把返回地址写到LR,但函数开头通常会push {lr}。如果你没注意到这个push,ROP链就会断掉。嗯,血的教训。
1.3.3 内存布局差异
不同架构的内存布局差异很大,直接影响漏洞利用:
- x86/x64:代码段、数据段、栈、堆,地址随机化(ASLR)
- ARM:类似x86,但有些嵌入式设备不开ASLR
- MIPS:kseg0/kseg1映射,缓存一致性是个大坑
- RISC-V:分S-mode和U-mode,页表结构不同
我记得有一次调试MIPS设备,shellcode写进去了,但死活执行不了。查了两天,发现是缓存一致性问题——指令缓存里还是旧数据。从那以后,我在MIPS上写shellcode都会加cache flush。
1.4 本章知识体系
下面这张图总结了本章的核心知识结构:
1.5 课程路线图
这门课一共30章,我按这个逻辑组织:
- 基础篇(1-5章):沙箱原理、seccomp深入、常见绕过手法
- ARM篇(6-12章):ARM指令集、ROP、shellcode、沙箱绕过实战
- MIPS篇(13-19章):MIPS特性、缓存问题、利用技巧
- RISC-V篇(20-25章):新兴架构、工具链、漏洞利用
- 综合实战(26-30章):多架构沙箱绕过、真实设备漏洞、CTF真题
学习建议:别急着跳到自己感兴趣的章节。沙箱基础不牢,后面异架构绕过你会很痛苦。我见过太多人一上来就搞ARM ROP,结果连seccomp规则都看不懂。嗯,稳扎稳打才是正道。
好,第一章就到这里。记住三个核心问题:沙箱是什么、为什么要绕过、异架构和x86差在哪。后面我们会一步步深入。