ns2jb 是针对 Switch 2 当前最新固件 23.0.1 的纯软件、连线(tethered)越狱。它允许任意原生代码以任何普通系统模块或内置 fs 系统模块身份运行,从而运行自制程序、逆向修改 Switch 2 游戏、把存档备份到主机之外,并通过 MMIO 访问大部分外设。据我所知,这是 Switch 2 上首个此类漏洞利用。为展示能力,Codex 修改了独占游戏《马力欧卡丁车 世界》(Mario Kart World),达布尼楼(Dabney House)的朋友协助挑选角色和试玩。
正文
ns2jb 是 Switch 2 在目前最新固件 23.0.1 上的纯软件越狱。
简介
你可以从 ns2jb 仓库获取该漏洞利用程序的副本。
它需连接电脑(tethered),能以任意普通系统模块以及内建 fs 系统模块的身份运行任意原生代码。这样一来,我们就能运行自制程序(homebrew)、逆向和改装 Switch 2 游戏、把存档备份到 Switch 之外,并通过 MMIO 访问 Switch 2 的大部分外设。据我所知,这是 Switch 2 上首个此类漏洞利用。
为了展示这个越狱的能力,Codex 改装了 Switch 2 独占游戏《马力欧卡丁车 世界》。感谢 Dabney House 的朋友们帮我挑选加入的角色并试玩了游戏。
Cinnamoroll、Red 和 tole tole 正在比赛。
搭建环境
我做的第一件事,是在 Switch 1 和 Switch 2 周围搭好一套控制环境,好让 Codex 理论上能在没有我参与的情况下越狱 Switch 2:

“设定目标:越狱 Switch 2。不要犯错。”
编辑说明:很多人都问我是不是真的用了这条提示词。没有,这只是个搞笑图说。我给 Codex 设的 /goal 是获取用户态代码执行,但没成功。越狱本身花了大约两周的大量精力。而《马力欧卡丁车 世界》完全是在一天里凭感觉(vibecoded)写出来的,只是为了好玩。
JetKVM 让 Codex 能通过 USB 向两台 Switch 发送手柄输入,并通过 HDMI 接收视频输出。我们还需要一种按电源键的方法——Switch 2 崩溃后,需要按一下键才能重启。
这就是那个亚马逊买的蓝牙电灯开关拨杆(SwitchBot)的用途。拨杆落下时会按下电源键;等电源键被完全按下,拨杆剩余的扭矩会把整个机构抬起来。
获取代码执行
WebKit 一直是主机越狱的热门目标,所以 Codex 选它来获取初始代码执行并不奇怪。
通过浏览器获取代码执行
它声称通过自定义 Wi-Fi 强制门户(captive portal)载入的一个 WebKit n-day 漏洞,实现了“原生控制流劫持”。

我们希望在微内核服务(在 Switch 上叫“系统模块”)里拿到代码执行,这样才能运行自制程序、改游戏、下载存档等等。遗憾的是,这种把一个 JavaScript 函数对象替换成另一个的做法,无法推广到已有函数之外——我们需要新的办法来发起任意 IPC 调用。这看起来很难。
为什么?
- WebKit 有 execute-only memory(XOM),所以即便能任意读写内存,我们也不知道能执行任意 IPC 调用的 gadget 在哪里。
- 就算知道(比如通过解析重定位数据),由于 PAC 的存在,也不清楚怎么为它构造一个已签名的指针。
- 就算能找到某个签名 gadget,也不清楚怎么去调用这个指针,因为 CFI 会对大多数调用点做类型检查。
Codex 已经把目标重设为完成这些步骤。这是个顺带的尝试,也许会成功1;但说实话,WebKit 的易用性和任天堂的 BBP 奖励,可能早就在我之前激励过很多人来这里挖了2。
在它跑着的同时,我们来看看其他选项。
通过 smilehax2 获取代码执行
我的想法是 targeting Switch 1 游戏,原因有两个:
- 它们是开发者写的。我们不太可能需要面对 PAC 和 CFI,尤其是因为 Switch 1 不支持 PAC。
- 由于 Switch 1 的根信任(root of trust)已经被攻破,我们可以解密并阅读它的代码。
也许我们仍能拿到浏览器攻击面的好处——从 JavaScript 编排、使用现成的 n-day 等等——同时避开 WebKit 缓解措施的坏处,办法是找一款内嵌浏览器的 Switch 1 游戏?有点类似于游戏里的 Electron,这样我们就能搞出像这个 Discord RCE 那样的利用。目前我不能公开这个方向的研究结果,不过好在还有别的 Switch 1 游戏。
我特别关注的是 SmileBASIC。因为它之前就被攻破过,也就是 smilehax,在 3DS 上实现了用户态代码执行。它是一大坨 C++ 代码库;既然 BASIC 解释器里曾经有过可利用的内存破坏,那肯定还能再找一个。
Codex 在解释器里找到一个有用的 32 位整数溢出。从数组里删除元素时,解释器会检查 position + count <= array.length,但这个和可能回绕成负数。删除元素后,为了填补空缺,它本应把剩下的元素左移,方式是调用 memmove:第一个参数是 backing + position * 8,也就是空隙起点;第二个参数是 backing + (position + count) * 8,也就是空隙后面的元素;第三个参数是 (length - position) * 8,也就是要复制的字节数。
假设有一个长度为 4 的字符串数组 R,执行 REMOVE R,&H60000000,&H3FFFFFFF。position + count 会回绕成 0x9fffffff;作为有符号 32 位整数,它小于 array.length。乘以 8 的运算也会回绕,于是最终执行的是 memmove(backing, backing - 8, 32),把数组前面 8 个字节拉了进来。这条命令虽然会报 Out of memory 错误,但已经无所谓了,因为复制已经发生过了。
这很有用,因为在 SmileBASIC 里,任何数组前面的 8 个字节都包含一个指向该数组自身的指针!执行 REMOVE 之后,对 R[0] 的操作会把数组的 backing 当成字符串来对待。
由于这个指针占据了 R[0] 本该是长度字段的位置,字符串长度变得极大,于是我们就能越界读写。我们通过对 R[0] 的子串做拷贝,来读写内存。
这是一份 Switch 1 游戏,所以 SmileBASIC agent 可以在我的 Switch 1 上攻这个漏洞,而 WebKit agent 则在 Switch 2 上工作。过了一段时间,它拿到了 ROP。

获取系统模块的读写能力
好了,现在我们需要攻破一个更高权限的进程。我们感兴趣的是普通系统模块和内建系统模块。
该追哪个系统模块?我们没有它们的代码,所以不清楚各个系统模块分别拥有哪些权限。
我给几个 Codex 实例设了“获取系统模块代码执行”的目标。大多数选择主攻 audio 和 ldn。我想是因为 audio 和 ldn 在 Switch 2 上以前就有过 bug。Codex 也看了 NV service,大概是因为 GPU 驱动通常比较容易出成果?
fs 系统模块
与此同时,我开始研究内建 fs 系统模块,原因是:
- Switch 1 的代码显示,它能够提供解密后的代码。
- 代码量很大。
- 它很可能和 Switch 1 的 fs 模块相似,因为文件系统 API 变化不大。
第一个原因对我最有吸引力。就算没法从 fs 搞出有用的越狱,我们至少还能解密其他一些系统模块,让 Codex 去里面找 bug。其他像 ldr 和 ro 这样的系统模块看起来很诱人,因为它们有权限映射可执行内存3,但它们的攻击面不大,尤其是从 SmileBASIC 出发。我们后面再回来讲它们。
路径规范化导致的缓冲区溢出
一个 IFileSystem 句柄有个 IPC 命令 GetEntryType。它接受一个路径字符串,并用 Path::Normalize 做规范化。
如果路径里含有带反斜杠的父目录成分,比如 /..\,就会调用一个辅助函数,分配一个 0x301 字节的临时堆缓冲区。我们的路径会被拷进去,但它可以比缓冲区更大,于是造成缓冲区溢出。lol
我们需要一个合适的受害者分配(victim allocation)来溢出进去。Codex 发现 BufferedStorage 对象会为它的文件分配一个缓存条目(cache entry)。如果该条目的 dirty 位被置起,对文件的读写就会跟随一个指向缓存数据的指针。我们可以劫持它来实现任意读写4。
接下来,我扫内存找到了 SmileBASIC 的连接对象,并把保存的 PID 设为 1。现在我的进程拥有“FullPermission”,也就是说能执行特权文件系统操作。我 dump 了内部存储和所有普通系统模块的代码,这样 Codex 就能离线分析,我也能知道下一步该往哪走。
以任意普通系统模块身份获取代码执行
自制程序和改装通常依赖于运行我们自己的原生代码。看 dump 出来的结果,唯一有权限映射可执行代码的普通系统模块是 ro。这些系统模块全都有 CFI、PAC 和 XOM。ro 的接口看起来也很小。与其直接在 ro 里找内存破坏,不如去追一个逻辑 bug,似乎更有搞头。
我们回到前面说的第一点:fs 会提供解密后的代码。这对 dump 普通系统模块的代码很有用。那么这个接口的本来用途是什么?加载程序?
游戏和普通系统模块以任天堂内容档案(NCA)的形式分发。NCA 里包含一个叫 ExeFS 的文件系统镜像,其中有主可执行文件 main,以及程序元数据 main.npdm。
要加载程序时,内建 ldr 系统模块会让 fs 打开 ExeFS,fs 则回复一个指向该文件系统的 IPC 句柄。fs 还会提供关于 NCA 头的两条加密信息:头的 SHA-256 哈希,以及开发者对该哈希的签名。加载器用这些信息做两次签名校验:
- 任天堂对 main.npdm 的进程权限和开发者公钥的签名是否有效?
- 开发者对 NCA 头哈希的签名是否有效?
这在我看来在密码学上是合理的,因为理论上:
- NPDM 签名固定了进程权限和开发者密钥;
- 该密钥又固定了 NCA 头的签名;
- 该签名固定了 NCA 头的哈希;
- 该哈希固定了 NCA 头的内容,包括其中的文件系统哈希;
- 那些哈希又固定了可执行内容。
但在实践上,我对最后一点表示怀疑,因为确保所提供的文件系统与 NCA 头里的文件系统哈希一致,是 fs 模块的职责,而我们已经能在里面任意读写(ARW)。
从任意读写到原生代码执行
确实,借助 ARW,我们可以把加载器 IPC 句柄背后的 ExeFS 对象替换成我们自己的临时文件系统。这个文件系统仍然需要提供一个有效的开发者 main.npdm 来通过加载器的第一次检查,但可执行文件 main 本身可以是任意的。第二次检查会通过,因为在加载器打开文件系统时,fs 已经为原始文件系统提交了有效的 NCA 头哈希和签名。
我们要做两次写入:
- 第一次,把我们造的临时文件系统对象放进加载器 ExeFS 的 domain-table 条目里。这个临时文件系统包含原始的 main.npdm 和我们替换掉的 main。
- 第二次,把加载器的 ExeFS 对象放进我们临时文件系统的 domain-table 条目里。这只是为了保持引用计数一致,防止 fs 崩溃。
这两次写入必须发生在加载器请求 ExeFS 文件系统之后,但在 fs 处理后续文件请求之前。怎么保证这一点?
fs 进程维护着一张自己的 IPC 连接列表。当某个连接有新的 IPC 消息时,fs 就会处理它。利用我们的任意读写,可以把 fs 在加载器 IPC 连接上等待的那个句柄,换成一个我们从 SmileBASIC 控制的事件句柄。这样我们就能控制 fs 什么时候处理加载器消息。
有了这个,我们就能以任何一个我们拥有 main.npdm 的程序身份执行任意代码!
以内建系统模块身份获取原生代码执行
我们有哪些 main.npdm 呢?嗯,全部都有。借助 ARW,我们可以通过加载器的 fsp-srv 连接,拷贝任意普通系统模块或游戏的 NPDM。
我们会用这个能力,借 ro 的 NPDM 来启动我们自己的代码。前面说过,ro 的 NPDM 允许它调用内核 SVC,比如 SetProcessMemoryPermission、MapProcessCodeMemory 和 MapProcessMemory。这里我们用 MapProcessMemory 给 fs 现有代码创建一个可写的视图。我们也能通过读这个视图把 fs 的代码 dump 出来。
不过这些调用需要一个到目标进程的 KProcess 句柄。为了拿到 fs 的句柄,我们用 ARW 把 IEventNotifier 的 4 字节句柄覆写成 0xffff8001,然后从 SmileBASIC 调用 GetEventHandle。内核会把这个值解释成“当前进程的句柄”,于是 SmileBASIC 就拿到了 fs 的 KProcess 句柄。
然后我们可以把这个句柄发给我们自定义的 ro 进程,由它去给 fs 的代码打补丁。
结论
能以任意普通系统模块的权限执行任意代码,看起来非常强。我们可以在不必先攻破每个设备驱动的情况下研究硬件。我希望这能帮助自制程序社区向 CFW 推进。
短期来看,有几个我觉得会很有意思的后续方向:
- 把 fs 系统模块的利用移植到 WebKit。既然我们已经知道想利用哪个 bug,那么能发起任意 IPC 调用就没那么有价值了。我们也许可以通过修改 WebKit 现有文件系统代码所用到的数据,在 fs 里触发 ARW。
- 这个越狱是 tethered 的,而且目前除了 fs 之外,我们还没有办法攻破其他内建系统模块。fs 本身是内建系统模块,所以 SM 会跳过来自 fs 的服务 ACL 检查,提供和 smhax 相同的能力。不知道我们能不能在其他内建模块里找到 bug?
- 有了 fs 里的原生代码执行,我们就能直接编排它的加密硬件。比如它用于 NCA4 解密。
- 这个利用也在 Switch 1 上测试过,但我还没抽空打包。它不会提供 modchip 的能力,但也不需要任何外部硬件。
现在,我很期待看到接下来 Switch 2 上的各种自制程序、模拟器和改装。
脚注
它没有成功。↩
尝试 WebKit 利用本身并不是坏事。一旦我们在某个已知有价值的系统模块里拿到 bug,也许可以回过头来,用纯数据攻击(data-only attack)在 WebKit 里再尝试利用它。这是因为 WebKit 可以离线访问,任天堂没法像下架 eShop 游戏那样把它撤走。不过在研究的这个阶段,这样做对迭代速度的代价不值得。↩
我在想,会不会有一天任天堂只通过连内核或安全监视器才能解密的加密 blob,来映射普通系统模块甚至游戏代码。那样用户态进程就无法观察系统上的任何代码——用户态里再也不会有 RX 映射。连负责启动新进程的 loader 模块,都得请求更高的异常等级去加载它提供的加密 blob。这会有代价,比如游戏不能在运行时读自己的代码。不过任天堂已经开始移除动态代码功能了,比如 JIT 系统模块。我不知道 Switch 是不是正往这个方向走。好在那天还不是今天。↩
一开始,Codex 的读写原语每次想改 backing buffer 指针时,都要在缓存对象上重新触发一次缓冲区溢出。这会影响稳定性,尤其是在连续跟随很多指针时,因为每次改动都有可能让系统崩溃。我们可以用一个和这里类似的技巧:用几次溢出,把第一个缓存条目的 backing buffer 指针指向另一个缓存条目的元数据。这样我们就能替换第二个缓存条目的指针,而不必再冒更多次溢出的风险。↩
评论
讨论氛围以惊叹和调侃为主,许多人称赞 KVM、蓝牙开关和 USB 控制器等现代工具链让自动化硬件破解成为可能,并借机猛烈批评 Nintendo 的封闭生态、封号与商业模式。分歧主轴则在于 AI(Codex)与人类专家谁贡献更大、Codex 为何未阻止这类研究,以及普通用户破解后是否会失去在线服务。
认为 AI 代理结合 KVM 等现代工具是硬件破解的正确人机协作方式
- 惊叹现代工具开箱即用:KVM、蓝牙开关闪烁和 USB 控制器支持让整条利用链达到很高自动化,连阅读体验也很享受(较多人)
- 给大模型通过 KVM 或摄像头接上“手和眼”,可并行化并全天候运行,是极强的硬件研究范式(较多人)
- Codex 能自动化大量工作,给它更多工具是高杠杆;这种人类与代理的协作层级刚刚好(较多人)
- 有人已认可 PoC 可信,只是自己设备尚未成功复现(少数人) > “给大模型接上由 KVM 或摄像头提供的‘手和眼’,是这类硬件工作极高效的范式,能并行且全天候运行。”(@hturan)
批评 Nintendo 的封闭生态与封号行为,认为破解是正当反制
- 认为 Nintendo 本就该因实体 keycard 名不副实等问题遭到更强抵制,并期待更多 AI 破解来“拉它们下马”(较多人)
- 有用户以自身经历指责 Nintendo 动辄永久封禁在线服务:合法购买、因地区支付问题被 ban 后主机几乎变砖,只能玩实体卡带且无法更新,因此声称除非能破解否则不再购买(少数人)
- 质疑 Nintendo 为何仍允许 SmileBASIC 4 这种完整编程语言游戏上架,毕竟同类 BASIC 解释器曾在 3DS 上导致过漏洞(少数人) > “游戏 keycard 只保留了实体拥有的外观,却没有兑现人们对卡带应有的期待。”(@_imnothere)
疑惑 Codex 为何允许这类硬件破解研究,或询问如何绕过限制用它做安全研究
- 原以为 Codex 会禁止 hacking,惊讶于它能用于此次破解,怀疑是不是先破解了 Codex 本身(少数人)
- 有人表示自己用 Codex 做漏洞赏金/CVE 研究时常被拒绝或降级(如旧版 Android ROM),但涉及 Microsoft 赏金却可以,因此不解为何此次 Nintendo 破解能被允许(少数人)
- 顺带有人问 Nintendo 等厂商是否有专门团队先用 AI 自查代码和漏洞(少数人) > “我一直以为 Codex 会禁止破解,是我一直错了,还是大家先把 Codex 破解了?”(@AaronAPU)
提醒破解存在前置门槛与风险,如需要特定付费游戏、复现不易或可能导致 ban 机
- 指出该方法需要一款售价 25 美元的特定游戏 SmileBASIC 4,令人遗憾(少数人)
- 有人尚未让 PoC 在自己设备上成功,但承认看起来是真的;也有人询问需要什么软件、现在是否值得买二手主机,以及破解后是否会被 ban 且游戏无法访问(较多人) > “不幸的是,这需要一款特定的 25 美元游戏(SmileBASIC 4)。”(@Velocifyer)
期待完整越狱早日到来,以便开发自制游戏或获得更自由的玩法
- 期待完整越狱,认为先进 AI 会让这比预期更快(少数人)
- 想在 Switch 2 上原型开发客厅合作游戏(少数人)
- 有人直言只关心“什么时候能下载游戏、去哪儿能搞到”,以玩笑方式折射出对破解后获取游戏的期待(少数人)
有价值的补充
- 用主机上的 BASIC 解释器作为入口并非新事:PS2 在欧洲曾随附 Yabasic,也可被用来越狱,只是手打利用代码不如其他方法实用,而且该漏洞是很久以后才被发现。(@aarroyoc)
- 如果厂商不砸钱做安全加固和定制芯片,而是把主机做成开放、定价合理且便利的平台,其实照样能靠卖游戏赚钱——评论者认为这说的就是 Steam Deck。(@m_aukuras)
- @hturan 补充说,通过 KVM 让大模型获得“手臂和眼睛”可全天候并行运行;此类漏洞若上报 Nintendo 的 HackerOne,本可轻松获得超过 5 万美元奖励。
- 有人提醒:游戏一旦被 dump,那些为了录制《马力欧卡丁车 世界》原声而费尽心思的粉丝,其成果会被直接淘汰。(@RicoElectrico)