本文编写于147 天前,最后修改于114 天前,其中某些信息可能已经过时。

接上一篇

这里继续写上一篇没写完的NX-Atmosphere调试部分, 如果没看过上一篇的, 可以在这里传送:

Switch 硬破折腾记之 NX-Atmosphere 大气层的坑和调试 (1)

Notice: 我是正版玩家,无论是 Switch 卡带还是 Steam 库存,从头到尾都是倾向于支持正版.
搞这些技术性研究只是为了增添乐趣和分享思路,因此也请大家不要问我破解游戏如何安装、如何在游戏中使用金手指 (作弊脚本)、在哪下载破解游戏等等。此类问题超过我的研究范围,我也无法回答,谢谢 ww


Sept模块夹带的坑

自从Switch的7.0.0版本固件开始, TSEC Key已经无法直接获得, 然而这个Key是后续解密(package2)中不可缺少的一个加密密钥. 而如果要获得TSEC Key, 则需要启动Tegra SOC内部的SE引擎来获取TSEC Key.
大气层为了避免这个问题, 特别编写了一个叫“Sept”的模块. “Sept”这个名字本身即包含着“SE”的意思, 大意为SE引擎的第三方可执行模块; 同时“Sept”本身也是7的意思, 象征着对7.0.0版本的支持和更新.

我们回到之前的那个代码:


其中第805行是这样写的:

reboot_to_sept(tsec_fw, tsec_fw_size, sept_secondary_enc, sept_secondary_enc_size);

这块的代码顾名思义, 当系统固件版本大于7.0.0时, 为了获取TSEC Key, 这里的代码将故意制造一个warmboot重启, 将控制权交给Sept模块, 并由Sept模块加载并执行tsec固件, 从而返回TSEC Key.
也就是说, 之所以我们的Switch突然间黑屏且没有任何后续输出, 那么很可能是这里出现了问题.

那么到这里的话, 要调试这里的代码, 就必须把几个必要的东西先弄明白:

  1. 首先是tsec_fw, 即tsec固件. 这部分代码是做什么的? 从哪里来的?
  2. 整个Sept由哪几部分构成? 分别拿来做什么?
  3. 整个Sept模块的生命周期是怎么样的一个过程?

tsec_fw

tsec_fw, 顾名思义, tsec (SE引擎) 的固件嘛. 要拿到TSEC Key, 那么这块必然是不可少的.
这块的源码位于 fusee/fusee-secondary/src/nxboot.c 中, 第685行.


如上的代码中可以看出, 这个tsec固件是从任天堂官方的package1中直接获取而来的, 使用的是任天堂的官方tsec固件.


Sept的组成部分

进入Sept的源码文件夹, 不难看出, 整个Sept除了tsec_fw, 由sept-primary和sept-secondary两部分构成.


大致的主要功能分别如下:

  • Primary部分负责将tsec固件、Secondary部分加载并启动SE引擎.
  • Secondary部分则负责将tsec固件中返回的TSEC Key获取, 并读取目录下的payload.bin, 链式加载.

Sept的生命周期

首先, fusee-secondary中的nxboot.c需要获取TSEC Key, 以便对package2进行解密. 因为TSEC Key从7.0.0版本开始不再可以直接获得, 那么需要调用Sept模块来获取TSEC Key.
这里的reboot_to_sept函数的功能其实就是加载Sept模块的代码, 并触发一个热重启来启动Sept模块:

warmboot之后, Switch理所应当的进入了sept-primary的代码部分.
而sept-primary负责写寄存器将SE引擎启动:

启动SE引擎之后, tsec_fw将会执行一些操作(因为这部分代码是任天堂的, 具体做了什么并不清楚, 没有详细跟进研究). 之后tsec_fw将会跳转到sept-secondary部分, 并传递TSEC Key到sept-secondary.

而sept-secondary获取到TSEC Key之后, 所做的事情就是直接chainload加载payload.bin了 (这个payload.bin在这里其实是fusee-secondary的副本, 之所以这样设计, 大概是为了让payload.bin可被替换为其他文件, 方便其他人二次开发).

这里有个超级天坑要注意, 后文马上就要撞上:

根据官方文档, sept-secondary是被tsec_fw验证rsa签名的, 如果签名不过, 那么tsec_fw根本不会加载.
这也就是说根本不可以自己尝试去编译一个sept-secondary, 除非你自己获得了任天堂官方的tsec_fw私钥 (不知道NX-Atmosphere怎么搞到这个私钥的).
这简直是个巨坑, 在这里我一开始也没有注意这个问题……导致浪费了相当大的时间在这里. 还好后来发现sept-secondary部分没有什么bug, 要不然大家都玩完了.


sept-primary中的问题

那么, 问题又回来了. 为什么Switch进入了Sept模块就直接卡死了呢?
既然知道了Sept模块的工作流程, 那么我们可以想办法继续调试了. 但是这个地方没有任何可以拿来作为输出的地方, 怎么调试呢?
嗯……比较尴尬……
……
但是我们可以将背光初始化, 用闪烁背光的方式来调试呀ww~
于是就写了这么一个函数用于初始化屏幕背光, 并控制背光闪烁的次数:


这样一来, 只要根据背光的闪烁, 就能知道当前程序运行到哪里了. 那么我们在main函数的开始部分闪烁背光20次, 以证明sept-primary部分完好:

这样一来我们的sept一旦加载, 应该就能看到背光闪烁次数20次才对……
编译!走你~
……
……
不幸的是, Switch依旧没有任何反应. emmmmmm?
……
……
这时候突然反应过来, 进入main函数之前, 是有一段start.s的汇编的, 难不成是它出了问题么? 赶快过去一看:

这里的这些汇编代码非常清晰易懂, 且注释充足.

很明显, 这些代码是对bootrom进行一些patch, 并将自己重定向, 之后跳进bootrom执行. 整个代码利用了一个叫fusee-gelee的漏洞来完成操作.
但是! 在我们的这个Switch上, fusee-gelee漏洞可能早就已经被修掉了. 这样做显然肯定过不掉bootrom的校验, 甚至是直接导致bootrom产生异常而崩溃.
而我们使用Modchip来对Switch进行硬破, 也没必要再利用这个漏洞了.

因此, 不妨试试将汇编改成重定向后直接跳过去执行的样子? 绕开bootrom部分?

那么, 接下来编译, 走你~
……结果Switch正确的闪烁了20次屏幕背光, 依旧是没有进到sept-secondary的logo里面.


sept-secondary中的问题

唔, 看来sept-primary是跑起来了呢……那么现在是什么情况呢……
……
……
又是一天过去了……
……
没有任何思路……
……
……
这时候突然看到了一个巨坑:


sept-secondary是要签名的!
要! 签! 名! 的!
这……要是sept-secondary模块里面有什么问题, 那我们岂不是都玩完了……
不过, 看到sept-secondary的代码就那一小段, 心想也不会有什么太大的问题吧, 本着死马当活马医的原则, 我们试试直接加载官方那个带签名的sept-secondary一试:

这里我继续修改了nxboot.c的源码, 令其强制从sd卡加载官方给出的sept-secondary文件.

编译……开机……这次可算是看到Sept的logo了:


……只是, 过了Spet的logo之后, 回到了fusee-secondary, 显示了大气层的蓝色logo页面后, 依然是继续黑屏.
这……?


0.13.0大气层的未知坑

找了一天, 还是找不到任何问题. 怎么办呢?
于是我很无聊的去逛NX-Atmosphere官方github的Issue, 结果这一逛, 还真有收获:



好像这是个大气层0.13.0的普遍问题? 相当多的人都反应大气层0.13.0黑屏, 这…… 难不成我们也撞上这个坑了?
而官方也表实0.13.0完整重构了整个exosphere(安全监视器层):

唔, 既然这样的话, 我们重新换成0.12.0的代码, 然后对之前修改过的代码再次做出同样的修改, 重新编译再试一次……
结果这一试不要紧……




就这……? 这也……
至此终于成功的在SX Core ModChip上跑起大气层了, 可喜可贺www~


番外话

最后, 还是要说的是, 我手里这个机器只是一台修复过USB RCM漏洞的Erista, 并不是大家熟知的Mariko(续航增强版)机型.
之所以要强调这个, 是因为本文中的办法未必适用于Mariko, 因为我手头没有Mariko机器(当然大家也可以赞助我一个……嘤嘤嘤……), 无法确定Mariko机器是否存在寄存器不一致的问题, 也无法确定Mariko是不是修改或新增了什么东西.
另外还有一个重要原因, 还记得在第一篇文章中提到的那句话么:

还好这个SXOS有个加载第三方可执行代码(payload)的选项, 因此还是能继续折腾下的www~
(后来发现这个功能似乎有巨坑, 但是似乎没有对我造成什么影响, 文章最后会说明)

那么这个巨坑是什么呢, 我们一起来看看Team Xecuter的难看吃相:

sub_88000020()
{
  __int64 v0; // x19
  __int64 v1; // x0
  unsigned int v2; // w19
  __int64 v3; // x0
  __int64 v5; // x0
  char v6[16]; // [xsp+30h] [xbp+30h]
  char v7; // [xsp+40h] [xbp+40h]

  v0 = 0i64;
  do
  {
    v6[v0] = get_time();
    v1 = get_time();
    *(&v7 + v0++) = BYTE1(v1);
  }
  while ( v0 != 0x10 );
  v2 = 0;
  if ( (unsigned int)is_mariko() )
  {
    do
    {
      se1_clear_keyslot(v2);
      v3 = v2++;
      se2_clear_keyslot(v3);
    }
    while ( v2 != 0xE );
    se_ctx_save();
    tzram_ctx_save();
  }
  else
  {
    do
    {
      se1_clear_keyslot(v2);
      v5 = v2++;
      se2_clear_keyslot(v5);
    }
    while ( v2 != 0xC );
  }
  se1_set_keyslot(5i64, (__int64)v6, 0x10u);
  se1_set_iv((__int64)&v7);
  se_decrypt_with_keyslot(5u, 0x81000000i64, 0x81000000i64, 0x190000i64);
  se_decrypt_with_keyslot(5u, 0x81300000i64, 0x81300000i64, 0x190000i64);
  se_decrypt_with_keyslot(5u, 0x88000000i64, 0x88000000i64, 0x40000i64);
  se_decrypt_with_keyslot(5u, 0x81000000i64, 0x81000000i64, 0x40000i64);
  return;
}

这段代码正位于那个SXOS的bootloader里面, 地址为0x88000020. 其功能大概如下:

  1. 将安全引擎se(security engine)的keyslots清空
  2. 如果是Mariko机型, 对SE进行一次上下文保存操作 (重点)
  3. 将keysolt 5填充为随机数
  4. 把bootloader和其他数据重新加密以防止后面加载的payload读取、截获SX家自己的payload内容.

这段代码直接破坏了SE引擎, 直接让SE引擎彻底爆炸无法使用.
在此之前, 理论上我们可以通过触发一个热重启来恢复SE引擎, 但是这段代码对SE进行了上下文保存操作, 而且保存的指针指向0. 触发热重启之后将导致SE引擎恢复上下文, 直接导致PC指针指向空指针, 从而使我们的代码无法正确执行下去.
Team Xecuter正是通过这种办法, 屏蔽第三方自制系统的运行. 这波操作甚至破坏了安卓系统和Ubuntu系统的自制固件, 让各种自制固件全部叫苦连天.
而且Team Xecuter的那个boot.dat这次还多加了个RSA签名校验, 让你不能修改它的代码.
吃相属实难看, 不予置评.

但是Team Xecuter随即给出了回应, 指出这段代码只存在于SXOS 3.0.0泄露版中, 新版本移除了所谓的“屏蔽第三方自制系统”功能, 并要求其他网友现场反汇编其代码为其正名:

sub_88000020()
{
  __int64 v0; // x19
  __int64 v1; // x0
  unsigned int v2; // w19
  __int64 v3; // x0
  __int64 v5; // x0
  char v6[16]; // [xsp+30h] [xbp+30h]
  char v7; // [xsp+40h] [xbp+40h]

  v0 = 0i64;
  do
  {
    v6[v0] = get_time();
    v1 = get_time();
    *(&v7 + v0++) = BYTE1(v1);
  }
  while ( v0 != 0x10 );
  v2 = 0;
  if ( is_mariko() )
  {
    do
    {
      se1_clear_keyslot(v2);
      v3 = v2++;
      se2_clear_keyslot(v3);
    }
    while ( v2 != 0xE );
  }
  else
  {
    do
    {
      se1_clear_keyslot(v2);
      v5 = v2++;
      se2_clear_keyslot(v5);
    }
    while ( v2 != 0xC );
  }
  se1_set_keyslot(5i64, (__int64)v6, 0x10u);
  se1_set_iv((__int64)&v7);
  se_decrypt_with_keyslot(5u, 0x81000000i64, 0x81000000i64, 0x190000i64);
  se_decrypt_with_keyslot(5u, 0x81300000i64, 0x81300000i64, 0x190000i64);
  se_decrypt_with_keyslot(5u, 0x88000000i64, 0x88000000i64, 0x40000i64);
  se_decrypt_with_keyslot(5u, 0x81000000i64, 0x81000000i64, 0x40000i64);
  return;
}

可以在这段代码中看到, 虽然打乱keyslots的代码仍然存在, 但是保存SE上下文的代码被移除掉了.
不要问, 问就是 贵 圈(juan) 真 乱. 不过即使是这个样子, 似乎那个清空并打乱keyslots的代码并没有干扰我的使用…… 因此我也并没有深入研究.


关于Ban机

很多人说Switch破解联网就会Ban, 要说明的是 —— 这是绝对的误传.
理论上讲, 任天堂不太可能检测你对Switch做了什么, 也很难去技术上检测你做了什么. 这样做只会增大对Switch的性能开销, 让本来就性能不咋样的Switch卡上加卡, 慢上加慢.
任天堂也更不可能简单的读取SD卡中有什么文件, 然后就将Switch给Ban掉 —— 作为一家商业公司, 他们要考虑一个误报率的问题. 如果SD卡中有可疑文件就直接Ban掉机器, 那么任天堂可能会无缘无故收到一大把的售后投诉订单, 甚至会被集体起诉. 这点可以参考之前的“手柄漂移门”.
个人认为, 唯一一个有十足的证据能把你Ban掉的, 那么就是你安装了破解游戏, 并联网、带着机器序列号证书信息请求了任天堂的更新服务. 这种行为可能会在任天堂服务器上留下日志. 而后期人工排查, 发现机器更新了没有授权的游戏, 其结果几乎一定是送你一个Ban.
这也很好理解为什么看起来破解过的机器“联网就会Ban”. 其原因就在这里. 而至于解封, 因为任天堂的机器序列号信息实际上是一张由任天堂官方签发的RSA证书, 所以目前来说, 篡改序列号并不可能, 也不可能简单的修改序列号来故意Ban掉别人的机器.

而正确使用emuMMC并抹掉序列号之后, 再次回到官方系统, 是根本不会看到emuMMC系统里面的任何内容的. 很多黑心淘宝商家, 为了赶时间, 根本就不会给你做emuMMC, 甚至是根本不会做, 并且还会给你下满盗版游戏. 这种机器到手之前可能就已经被Ban了, 结果你联网发现被Ban, 商家会找理由说“破解机器不能联网”, 把锅推给用户.
到这篇文章发表时, 我的Switch已经交叉使用大气层(emuMMC+删除prodinfo序列号信息)、官方HorizonOS(无任何修改)大概两个月了, 其官方应用商店和在线服务完全正常, 不受任何影响.
这也一定程度上说明了这个思路的正确性!


总结

这是一次印象深刻的嵌入式逆向、调试过程, 其中因为没有好的调试工具, 使用了各种各样的奇技淫巧大集合来对未知的bug进行调试和定位.
而被调试的设备也是完全没有任何官方资料且高强度加密的Nintendo Switch, 调试过程可谓是异常艰难.
破解Switch并不重要, 重要的在于能够在玩的开心的前提下, 研究更多的嵌入式安全相关知识 —— 这就足够了呀www~