【免费下载链接】vue-after-free
PlayStation Vue code execution exploit
Vue-After-Free 是一款基于 PS Vue(CUSA00960)应用用户态代码执行漏洞(CVE-2017-7117)的PS4 越狱 exploit,其背后是一套运行在主机专有 JavaScript 虚拟机上的JS 运行时层。这篇文章将带你深入解析其中手写 BigInt、fn 函数注册、syscalls 表扫描三大核心模块的构造思路,看看 64 位内存操作与系统调用是如何在 32 位 JavaScript 中"炼"出来的。
先搞清楚背景:为什么要在 JS 里写"系统调用"
对于不熟悉 PS4 越狱的读者,先快速对齐概念:
- 用户态(Userland):exploit 在应用进程沙箱内拿到任意读写/执行能力,本项目的 Vue UAF 漏洞就属于这一层;
- 内核态(Kernel):用户态能力只能读到沙箱,真正的越狱需要再打一层内核漏洞(本项目默认 7.00–12.02 使用 Lapse,12.50–13.00 使用 Netctrl);
- PS Vue 的 JS 虚拟机:Vue 应用内嵌了一个私有 JS 运行时(代码里叫
jsmaf),它提供include()、log()、Image、Video等 API(声明见 src/types/jsmaf.d.ts),但没有 DOM、没有 fetch,更没有 64 位整数类型。
问题就出在这里:JS 的number是 64 位浮点数,精度只有 53 位有效位,而 PS4 的 64 位内存地址(形如0xFFFFFFFF8000xxxx)根本存不下。于是开发者在纯 JS 之上手工搭了一整套"底层环境模拟器",这就是本文的主角——JS 运行时层。
运行时层的模块全景如下:
| 模块 | 所在文件 | 职责 |
|---|---|---|
BigInt | src/download0/types.ts | 手写 64 位整数 |
mem/utils | src/download0/types.ts | 内存读写原语 |
fn | src/download0/types.ts | 原生函数注册与调用 |
gadgets/rop | src/download0/types.ts | ROP/JOP 指令链执行引擎 |
syscalls | src/download0/types.ts | 系统调用表字节模式扫描 |
struct | src/download0/types.ts | C 结构体定义与字段访问 |
下面按依赖顺序逐个拆解。
手写BigInt:32位JavaScript里的64位整数
为什么不能直接用 JS 的 number
一个 64 位地址最高位往往落在0xFFFF...区间,而 JS 数字超过 2^53 后就会丢精度——0xFFFFFFFF80000000一旦存成浮点,低 11 位直接"蒸发"。对 exploit 来说,地址错一个字节全盘皆输,所以必须自建整数类型。
lo/hi 双半字设计
开发者在 src/download0/types.ts 定义了一个自定义BigInt类,核心只有两个字段:
class BigInt { lo: number // 低 32 位 hi: number // 高 32 位 }构造器做了"多态入参",一行代码就能覆盖绝大多数场景:
constructor (valueOrHi?: BigIntLike, lo_?: number) // 支持:整数 number、浮点(按位模式取)、布尔、 // 16 进制字符串、BigInt 实例、或 (hi, lo) 双参数几个值得注意的细节:
- 溢出保护:
add高位溢出直接throw RangeError('add overflowed !!'),sub同样检测下溢——exploit 代码里宁可崩溃也不接受静默截断; - 位运算全家桶:
xor/and/or/not/shl/shr/getBit/setBit,按 lo/hi 两半分别处理; divmod按位长除法:从第 63 位到第 0 位逐位移位减除,是最朴素的 64 位除法实现;toString()输出大写 16 进制,d()把 64 位值重新解释为 float64(利用DataView的位域重排技巧),endian()做 32 位字交换;- DataView 扩展:在 src/download0/types.ts 给
DataView.prototype挂上了getBigInt/setBigInt,从此内存视图可以按 8 字节原子量读写地址。
一句话总结:BigInt 用两个 32 位整数 + 手工进位,把 JS 从"53 位浮点玩具"升级成了能操作真实虚拟地址的 64 位整数引擎。
mem内存原语:view、addrof、fakeobj三件套
光有整数还不够,exploit 需要"读任意地址、写任意地址"(ARW 原语)。mem对象(src/download0/types.ts)提供了四件工具:
| 方法 | 作用 |
|---|---|
mem.view(addr) | 把addr设为"读窗口",之后所有slaveDataView 的读写都发生在这个地址上 |
mem.addrof(obj) | 泄漏一个 JS 对象的真实内存地址(经由leak_obj中转) |
mem.fakeobj(addr) | 反向操作:把地址"伪装"成对象引用,这是伪造对象的关键 |
mem.malloc(n) | 通过 TypedArray 的 backing store 机制"分配"一段内存并拿到其地址 |
addrof与fakeobj是一对:前者让 JS 对象"现形",后者让任意内存"成精"。配合utils.get_backing/utils.set_backing(src/download0/types.ts)改写 TypedArray 的底层缓冲指针,mem.malloc就能凭空获得一块可控内存。这套组合拳是后面所有"伪造 ROP 栈""伪造 JS 函数"动作的地基。
fn注册:把任意原生调用包装成"普通函数"
这是运行时层最优雅的抽象。fn对象(src/download0/types.ts)让你用一行声明式代码注册任何原生函数:
fn.register(0x14, 'getpid', [], 'bigint') fn.register(libc_addr.add(0x5F0), 'sceKernelGetModuleInfoForUnwind', ['bigint'], 'bigint')注册项分两种来源:
- 函数地址(
BigInt):直接指向某个可执行函数,比如 libc 里的sceKernelGetModuleInfoForUnwind; - 系统调用编号(
number):自动从syscalls.map查表取地址——这个 map 怎么来的,下一节讲。
注册完成后,fn.getpid()就像调用普通 JS 函数一样。幕后fn.wrapper的干活流程是:
- 生成
POP_RAX_RET+ 调用编号(或地址 0); - 按参数顺序生成
POP_RDI_RET/POP_RSI_RET/POP_RDX_RET/POP_RCX_RET/POP_R8_RET/POP_R9_JO_RET+ 参数值(字符串参数会先用utils.cstr在内存里写成 C 字符串,布尔转 0/1,最多 6 个参数); - 压入目标函数地址,交给 rop.execute 通过 JOP 执行;
- 若声明了返回类型(
bigint/boolean/string),从 store 缓冲区读回 8 字节结果并按类型转换——string返回值会自动按 C 字符串解析成 JS 字符串。
更妙的是 TypeScript 层的类型魔法:register的签名使用asserts this is Fn<...>(src/download0/types.ts),意味着注册之后fn.open、fn.write会自动获得参数类型推断与返回值类型,写 exploit 时编译器全程帮你盯着——这在一个"调用内核函数"的场景里是实打实的安全网。
syscalls初始化:用12字节模式反查libkernel
PS4 沙箱里没有任何符号表,open、read、sysctl这些系统调用的包装函数地址从何而来?答案藏在 src/download0/types.ts 的syscalls对象里。
核心思路:libkernel 里每个 syscall 包装函数都有相同的指令前导。以open为例,反编译后的前几个字节固定是:
mov rax, -1 → 48 C7 C0 FF FF FF FF mov rcx, r9 → 49 89 CA syscall → 0F 05于是syscalls.init(libkernel_addr)只需:
- 以这 12 字节(
0x48 0xC7 0xC0 0xFF 0xFF 0xFF 0xFF 0x49 0x89 0xCA 0x0F 0x05)为模式,在 libkernel 前0x40000字节内做朴素逐字节匹配; - 每命中一处,从匹配点 +3 字节处读出一个
int32,那就是该 syscall 的编号(例如0x05= open); - 把
编号 → 地址存入Map。
一次扫描,整张系统调用表全部到手(日志里会打印Found N syscalls)。此后fn.register(0x0ca, 'sysctl', [...], 'bigint')(见 src/download0/kernel.ts)就只需要填一个编号,连硬编码地址都省了——这是"免符号表定位函数"的经典手法。
从UAF到ROP:运行时层的完整"诞生"过程
各模块不是凭空出现的,它们的初始化链条在 src/download0/userland.ts 中一气呵成:
第一步:UAF → ARW。借助 Vue 特制的触发器 make_uaf(一段利用for...in混淆解释器的 7 行代码)制造 use-after-free,随后喷出 4096 个填充0x13371337标记的数组,找到被 UAF 视图命中的"标记数组"并篡改其长度头——数组瞬间变成任意读写窗口(userland.ts)。
第二步:伪造 master/slave。通过mem.addrof泄漏对象地址、mem.fakeobj伪造引用,逐步伪造出一个Uint32Array(master)和一个DataView(slave),并暴力递增 structure_id 直到引擎认账——ARW 原语正式落地,日志打印Achieved ARW !!。
第三步:泄漏所有基址。通过Math.min这个内置函数顺藤摸瓜,一次泄漏出 jsc(JS 引擎)、libc、libkernel、eboot 四大模块基址(userland.ts),然后:
- 写 1 字节关闭 GC(防止运行时对象被回收,地址失效);
rop.init(jsc_addr):以 jsc 基址为锚点,初始化gadgets表里全部 20 多个 gadget 偏移,搭建 JOP 栈;syscalls.init(libkernel_addr):执行上一节描述的字节模式扫描,生成 syscall 表。
第四步:loader 编排。src/download0/loader.ts 用include()依次装载 userland、binloader、lapse、kernel、check-jailbroken,先通过sysctlbyname('kern.sdk_version')(见 kernel.ts)识别固件版本,再按CONFIG.jb_behavior自动选择 Lapse 或 Netctrl 内核漏洞;随后用getuid()+is_in_sandbox()轮询验证越狱状态(kernel.ts),成功则打 ucred 补丁完成沙箱逃逸(jailbreak_shared),并通过劫持sysent[661]执行 kpatch shellcode 完成内核补丁(apply_kernel_patches)。
如果中途失败,你看到的就是这样一只猫——按 README 的说法,重启再来一次即可:
小结:这套运行时层的可借鉴之处
- BigInt:用
lo/hi双半字 + 显式溢出抛错,是任何"32 位 JS 操作 64 位地址"场景的通用解法; - mem:
addrof/fakeobj/view三件套把对象与地址互相转换,是伪造一切结构的前提; - fn + syscalls:声明式注册 + 字节模式扫表,让"调内核"写起来像调普通函数,且 TS 类型推断全程兜底;
- rop:把 JOP 执行封装成
push/execute/store/load,让 shellcode 层面的事退化成一次普通函数调用。
整套代码全部位于src/download0/目录,构建时由 Babel 编译进download0.dat(见 package.json)。成功越狱后等待你的,是这只猫:
核心文件速查:
| 文件 | 内容 |
|---|---|
| src/download0/types.ts | BigInt / mem / fn / rop / syscalls / struct 运行时层全家桶 |
| src/download0/userland.ts | UAF→ARW、基址泄漏、运行时初始化 |
| src/download0/kernel.ts | 各版本内核偏移、ucred 沙箱逃逸、kpatch 注入 |
| src/download0/loader.ts | 版本检测与 Lapse/Netctrl 编排 |
| src/download0/defs.ts | UAF 触发器 make_uaf |
【免费下载链接】vue-after-free
PlayStation Vue code execution exploit
相关推荐
React Native Boilerplate 3.0.0 深度解析:轻量、可扩展的 React Native 脚手架是怎么炼成的
React Native Boilerplate 3.0.0 深度解析:轻量、可扩展的 React Native 脚手架是怎么炼成的 导读:本文以 React
移动开发跨平台33ms 是怎么炼成的:拆解 Laya 非自回归架构的每一步省时
33ms 是怎么炼成的:拆解 Laya 非自回归架构的每一步省时 当 2026 年 9 月 Laya 以"421M 参数、单次前向推理 33ms"的面目出现在开
人工智能NLP强化学习解读BrasilAPI架构:Next.js API Routes + Vercel Serverless的零成本API是怎么炼成的
解读BrasilAPI架构:Next.js API Routes + Vercel Serverless的零成本API是怎么炼成的 BrasilAPI 是一个把
后端
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考