☰
Vue-After-Free的JS运行时层深度解析:手写BigInt、fn注册与syscalls是怎么炼成的
2026/10/16 2:57:10 网站建设 项目流程

【免费下载链接】vue-after-free

PlayStation Vue code execution exploit

项目地址:https://gitcode.com/gh_mirrors/vu/vue-after-free
点击查看免费下载

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 运行时层。

运行时层的模块全景如下:

模块所在文件职责
BigIntsrc/download0/types.ts手写 64 位整数
mem/utilssrc/download0/types.ts内存读写原语
fnsrc/download0/types.ts原生函数注册与调用
gadgets/ropsrc/download0/types.tsROP/JOP 指令链执行引擎
syscallssrc/download0/types.ts系统调用表字节模式扫描
structsrc/download0/types.tsC 结构体定义与字段访问

下面按依赖顺序逐个拆解。

手写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')

注册项分两种来源:

  1. 函数地址(BigInt):直接指向某个可执行函数,比如 libc 里的sceKernelGetModuleInfoForUnwind;
  2. 系统调用编号(number):自动从syscalls.map查表取地址——这个 map 怎么来的,下一节讲。

注册完成后,fn.getpid()就像调用普通 JS 函数一样。幕后fn.wrapper的干活流程是:

  1. 生成POP_RAX_RET+ 调用编号(或地址 0);
  2. 按参数顺序生成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 个参数);
  3. 压入目标函数地址,交给 rop.execute 通过 JOP 执行;
  4. 若声明了返回类型(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)只需:

  1. 以这 12 字节(0x48 0xC7 0xC0 0xFF 0xFF 0xFF 0xFF 0x49 0x89 0xCA 0x0F 0x05)为模式,在 libkernel 前0x40000字节内做朴素逐字节匹配;
  2. 每命中一处,从匹配点 +3 字节处读出一个int32,那就是该 syscall 的编号(例如0x05= open);
  3. 把编号 → 地址存入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.tsBigInt / mem / fn / rop / syscalls / struct 运行时层全家桶
src/download0/userland.tsUAF→ARW、基址泄漏、运行时初始化
src/download0/kernel.ts各版本内核偏移、ucred 沙箱逃逸、kpatch 注入
src/download0/loader.ts版本检测与 Lapse/Netctrl 编排
src/download0/defs.tsUAF 触发器 make_uaf

【免费下载链接】vue-after-free

PlayStation Vue code execution exploit

项目地址:https://gitcode.com/gh_mirrors/vu/vue-after-free
点击查看免费下载
上一篇:FoundationDB Hot Shard(热分片)原理与治理:分裂机制、集群级限流与 Server Knobs 全解析
下一篇:swagger-codegen 生成的 C 客户端 PetApi 使用指南:从 Swagger Petstore 文档到源码级调用原理

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询