返回资讯中心
外部精选
软件工程
#社区热议

Jailbreaking the Switch 2

Codex has reset the goal to complete these steps. It’s a side bet; it may succeed1, but let’s be real: the accessibility of WebKit and the Nintendo BBP reward have probably incentivized many before me to look here2.

Hacker Newsledoge2026/10/22 · 5 天前16 分钟阅读

以下正文同步自 Hacker News,版权归原站所有,已转换为易读排版。

Codex has reset the goal to complete these steps. It’s a side bet; it may succeed1, but let’s be real: the accessibility of WebKit and the Nintendo BBP reward have probably incentivized many before me to look here2.

While that runs, let’s explore other options.

My idea was to target Switch 1 games, for two reasons:

Maybe we can still get the upsides of the browser attack surface—orchestrating from JavaScript, using existing n-days, etc—while avoiding the downsides of WebKit mitigations by finding a Switch 1 game that embeds a browser? Something analogous to Electron, but for games, so we could pull off an exploit like this Discord RCE. I can’t disclose this approach’s findings publicly at the moment, but luckily, there are other Switch 1 games.

In particular, I’m interested in SmileBASIC. This is because it’s been pwned before, in smilehax, giving userspace code execution on the 3DS. It’s a giant C++ codebase; surely, if there was an exploitable memory corruption in the BASIC interpreter, there can be another.

Codex found a useful 32-bit integer overflow in the interpreter. When removing items from an array, the interpreter checked position + count <= array.length, but the sum could wrap and become negative. To fill the gap created by removing elements, it’s supposed to shift the remaining elements left, like so:

memmove( backing + position * 8, // start of the gap backing + (position + count) * 8, // elements after the gap (length - position) * 8 // bytes to copy );

Say we have a string array R of length 4, and we run REMOVE R,&H60000000,&H3FFFFFFF. position + count wraps to 0x9fffffff. As a negative signed 32-bit integer, it’s less than array.length. The multiplications by 8 also wrap, so we end up performing memmove(backing, backing - 8, 32), pulling the previous 8 bytes into the array. The command does report an Out of memory error, but it doesn’t matter, since the copy has happened already.

This is useful because the 8 bytes preceding any array in SmileBASIC contain a pointer to the array itself! After the REMOVE, operations on R[0] will treat the array backing as if it were a string.

Since a pointer occupies what would be the length field of R[0], the string length is enormous, and we can perform reads and writes out of bounds. We read and write to memory by copying to and from substrings of R[0].

This was a Switch 1 game, so the SmileBASIC agent could work on the exploit on my Switch 1, while the WebKit agent worked on the Switch 2. After some time, it got ROP.

Okay, now we need to compromise a more privileged process. We’re interested in ordinary sysmodules and built-in sysmodules.

Which sysmodule should we pursue? We don’t have their code, so it’s unclear which sysmodules have which privileges.

I gave a few Codex instances the goal of getting sysmodule code execution. Most chose to focus on audio and ldn. I think that’s because audio and ldn have had bugs on the Switch 2. Codex also looked at the NV service, probably because GPU drivers are generally fruitful?

In the meantime, I started looking at the built-in fs sysmodule, because:

The first reason is the most attractive to me. Even if we can’t get a useful jailbreak from fs, we can at least decrypt some of the other sysmodules so Codex can find bugs in them. Other sysmodules like ldr and ro look enticing because they have permission to map executable memory3, but they don’t have large attack surfaces, especially from SmileBASIC. We’ll come back to them later.

An IFileSystem handle has an IPC command, GetEntryType. This takes a path string, normalized with Path::Normalize.

Paths containing a parent-directory component with a backslash, like /.., call into a helper that allocates a temporary heap buffer of 0x301 bytes. Our path is copied in, but it can be larger than the buffer, causing a buffer overflow. lol

We’ll need a good victim allocation to overflow into. Codex found that the BufferedStorage object allocates a cache entry for its files. If that entry has its dirty bit set, reads and writes to the file follow a pointer to cached data. We can hijack that for arbitrary read/write4.

From here, I scanned through memory to find SmileBASIC’s connection object and set the saved PID to 1. Now, my process has “FullPermission”, meaning I can perform privileged filesystem actions. I dumped internal storage and all ordinary sysmodule code, so Codex could perform offline analysis and I could understand where to go next.

Homebrew and modding typically rely on executing our own native code. Looking at the dumps, the only ordinary sysmodule with permission to map executable code is ro. All these sysmodules have CFI, PAC, and XOM. The interface for the module seems pretty minimal too. It seemed more fruitful to pursue a logic bug than to look for memory corruption in ro directly.

Let’s revisit that first point about fs serving decrypted code. This was useful for dumping the code for ordinary sysmodules. What’s the intended functionality of this interface? Loading programs?

Games and ordinary sysmodules are distributed as Nintendo Content Archives, or NCAs, containing a filesystem image called ExeFS. This contains a file for the main executable, main, and program metadata, main.npdm.

To load a program, the built-in ldr sysmodule asks fs to open ExeFS, and fs replies with an IPC handle to the filesystem. fs also provides two pieces of cryptographic information about the NCA header: the header’s SHA-256 hash and the developer’s signature of that hash. The loader uses this information to perform two signature checks:

Seems cryptographically sound to me, because, in theory:

In practice, however, I’m doubtful of that last claim, because it’s the responsibility of the fs module to ensure the served filesystem matches the NCA header’s filesystem hash, and we have ARW in it.

Indeed, with ARW, we can replace the ExeFS object behind the loader’s IPC handle with our own temporary filesystem. This filesystem still needs to serve a valid developer main.npdm for the loader’s first check, but the executable main itself can be arbitrary. The second check will pass because, by the time the loader opens the filesystem, fs has already submitted the valid NCA header hash and signature for the original filesystem.

正文由 探How 从来源站点 RSS 同步,内容未经改写;遇到排版缺失或需要图片、视频时请以原文为准。

打开原文
去社区讨论 →