The JTAG/SMC Hack On Xbox360
原文位于:
http://free60.git.sourceforge.net/git/gitweb.cgi?p=free60/tools;a=blob;f=imgbuild/hack.txt;h=a5675bfa4e414b3688197528b76307f0f5af2ccc;hb=HEAD
名词解释:
KK expl. (KK exploit) KK指(KING KONG),是游戏《金刚》的名字,该漏洞的是说通过游戏可编辑的着色/渲染操作,最终可实现对物理内存的修改。配合管理程序漏洞可实现执行任意代码。
unsigned shader
vertex shader
shader
hypervisor 管理程序
HRMO
FSB 系统前端总线
XeLL (Xenon Linux Loader) 由free60.org发布的一个Loader用于引导Linux操作系统,它属于第二阶段的loader,意思是说是由另一个loader来引导的
HV offset
thermal diodes 电子器件:热敏二极管
(H)ANA 芯片厂商名
VM code (Virtual Machine code) 虚拟机代码
RROD (Red Ring of Death) 指三个红色橙光形成一个环形,俗称三红,也有人要做死亡的红色光环或死亡红环
1BL (One stage bootloader) 第一阶段的bootloader,后面的nBL类似
"CB"
(Xenon
Zephyr
Falcon
Jasper) 括号中的4个东东指的是Xbox的4个硬件版本代号
Fuses 见有人翻译成:电子保险
"CF"
"CG"
pairing block 配对块
dashboard
timing attack 文档中有时简写成TA
工作原理
为了能理解这个破解的实现,我们还是先看看KK的漏洞是怎样产生的:
系统管理程序的系统调用处理函数存在严重缺陷,这个问题在4532版本的内核升级(说明)中有介绍。下面这个URL对这个问题进行了详细的描述。
http://www.securityfocus.com/archive/1/461489/30/0/
KK漏洞利用内存存在的缺陷,通过着色/渲染操作来实现了一系列对内存的访问,通过这样的操作GPU可以将像素或者vertex shader写入到物理内存中。这些预先构造好的阴影数据覆写了空闲线程的上下文,这样可以使内核跳转到期望的内存地址上,同时CPU的一些寄存器也可以填充成你期望的值。为了能控制所有的寄存器还需要第二阶段的工作,这个阶段我们让PC指针掉转到中断的处理函数。最终的结果是所有的CPU通用寄存器被填充成你期望的值。这时将PC指针填充成内核系统调用指令的地址,根据预先在寄存器上填充好的值(译者注:系统调用和函数调用一样是需要参数的,而和函数调用的区别是不会使用到堆栈而是直接将参数放在通用寄存器中,当然也不是说函数调用就一定要用堆栈来传递参数,具体要看编译器的实现),就可以利用漏洞做我们想做的事情(译者注:直白的说这样已经可以调用系统的API函数了)。
这个漏洞允许PC跳转到系统管理程序领空的任何32bits的有效地址空间。为了能跳转的任意地址,我们需要使用到"mtctr, bctr"这2条指令,当然必须先在系统管理程序的领空先找到指令的地址,将PC设到这里就可以间接的实行到任何地址空间的指令。(译者注:mtctr指令后面需要带一个寄存器作为参数,而这条指令的目的就是将PC设置成这个寄存器的值,而前面讲到寄存器是可控的)(译者注:如果你了解过Windows的漏洞利用,一定会知道有一个指令叫jmp esp,非常类似,当我们利用漏洞的时候,也是要设法找到在核心的动态库如kernel32.dll里该指令对应的地址,然后设法将PC跳转到这条指令对应地址上)这点非常重要,因为我们要清零高32bits?(例如:通过设置MSB来禁止HRMO),因为我们希望代码跳转到一块未加密的内存空间。
这部分代码经常被用于加载第二阶段的loader,例如XeLL(译者注:Xenon Linux Loader, Xenon上加载Linux的引导程序, Xenon为Xbox板卡硬件版本名),到内存空间,并且执行。XeLL这时候会尝试catch到所有的cpu线程上(因为我们的漏洞程序之影响到了主线程),然后加载用户代码,例如从DVD上。
所以,下面这部分内存空间是需要用到的。
- 空闲线程的上下文,位于物理内存的00130360
这里存放了堆栈指针(和别的一些填充数据)。当空闲线程被挂起时,我们修改了堆栈指针,当内核重新调度到空闲线程的时候,我们就获得了对CPU的控制。任务切换会根据我们提供的堆栈地址做上下文恢复。
- 上下文恢复,第一步,任意地址,KK漏洞中使用了80130AF0
线程的上下文恢复并不包含恢复所有的寄存器,但确可以控制NIP(下条机器指令的地址)。我们设置NIP指向中断的上下文恢复函数,这个函数会根据当前的堆栈指针来加载大多数的寄存器。
- 上下文恢复,第二步,类似于第一步的基地址
我们在两次上下文切换中使用了相同的堆栈指针,因为这2次操作并不重叠。第二次的恢复可以将所有寄存器(译者注:上面才说是大多数,这里成所有了,不严谨哦)写成任意的64bits值。
- HV偏移量,4532版本上对应的0x46号系统调用在00002080地址上
源于HV的缺陷,我们可以将这个地址写到没有加密的内存空间,因为我们可以跳转到系统管理程序领空的任何地址(例如:通过一个固定的加密前缀)。我们经常在这里写00000350,这个地址在系统管理程序领空对应了指令"mtctr %r4; bctr",这样可以跳转到寄存器r4对应的地址来运行。
- 我们loader的代码,位于任何地址。
代码通过系统管理程序来执行,这是最先运行到的我们自己的代码。r4对应的地址并不在系统调用列表中(译者注:这是当然,因为r4指向的是我们loader的入口地址,而我们的loader肯定不会是微软写的系统调用,
这里他没有提到loader是如何被加载到内存上的,当然对于KK的话,他是可以访问任何物理内存的,这个自然不是问题)
只要空闲线程和HV的偏移填充好了固定地址。将这2块数据写入内存是很容易的事情,但是放在一块上是不行的(译者注:不知道为什么)幸运的是NAND控制器在做DMA读操作的时候,静载荷数据和ECC数据是分开的。每个页面包含512字节的静载荷和16字节的ECC校验数据。因此一次DMA的读操作就可以将上面2块数据同时加载到内存上。我们用静载荷来放空闲线程的上下文,上下文恢复数据和loader代码。ECC部分来访HV的偏移量。
为了做DMA操作,需要访问下面几个NAND控制寄存器。
ea00c01c Address for Payload 存放静载荷的地址
ea00c020 Adresss for ECC 存放ECC的地址
ea00c00c address inside NAND NAND的内部地址
ea00c008 command: read DMA (07) 命令ID:DMA读(07)
系统管理控制器(SMC)内部是一个8051的核,SMC位于南桥芯片内部。他来负责电源的上电顺序,只要Xbox360接上电源,他就一直处于工作状态(无论xobx在待机还是工作状态)。他控制了前面板上的按键,内部有一个实时时钟,解码红外模块,控制温度传感器风扇和DVDROM。他通过前面板来设置LEDs灯的状态。系统运行的时候内核通过和SMC的通信,可以查询当前实时时钟,打开DVD托盘,等等。他们是通过一个双向FIFO来通信的(位于系统内存ea001080 / ea001090)。通过阅读XeLL SMC部分的代码可以了解更多信息。
SMC也可以访问到NAND芯片,因为在NAND的一个页面上存放了SMC的配置信息。该配置页上存放了温敏二极管的校准信息和监测热源点的信息,等等。51可以访问NAND的控制寄存器,该寄存器被影射到51的特殊功能寄存器中。51访问NAND的协议和内核是一样的,通过对地址的访问实现一个读命令,这时从DATA寄存器中获取读到的数据。(译者注:这个协议自然是一样的,因为是NAND芯片决定的,可参考芯片的datasheet)因为SMC可以实现DMA的读操作,所以破解他就可以执行漏洞程序,不需要KK上说的shader了。SMC可以在任何时候访问NAND控制器,甚至在内核运行的情况下。(当然这样可能会影响到内核的运行)。
所以当kernel加载完成后我们就触发DMA读操作,这样就可以了么?
虽然大多数的NAND控制器被影射了,但是DMA地址寄存器却没有,所以这个时候使用它,地址是默认值0,或者是内存上次DMA操作完,保存下来的临时数据。所以还是不行的。
GPU和(H)ANA(定标器-他并没有起到任何缩放的功能,只是一堆数模转换和DVI/HDMI的解码器),南桥芯片和CPU的JTAG口暴漏在板卡上。PCB对应焊点上没有安装连接头,但是信号是连到PIN上了。CPU的JTAG说起来又是一个复杂的故事了,SB JTAG并没有实现太多的功能。ANA JTAG很无聊,因为他没有加入任何有意思的总线,只是把GPU的JTAG放出来。(译者注:不清楚作者什么意思,应该说通过它的JTAG做不了什么事情吧)
GPU的JTAG一直没有得到逆向工程的关注,直到PCI仲裁器的写操作成为可能。这样可以访问系统PCI总线上的设备,也包括了NAND的控制器。所以这样做我们就可以代替SMC来发起DMA操作了么?对不对呢?
差不多吧,但不全对。问题是"VM 这部分代码上",该代码实现了许多系统的初始化工作,例如内存,同时设置了GPU寄存器上一些特定位,这样就禁用了JTAG接口。VM代码是在内核有效前被执行到的。所以这条路也不通。
但是将上面2个方法组合起来就能用了 - 通过JTAG编程DMA的目标地址,这时通过SMC加载攻击代码。这样一旦Kernel运行起来攻击就会产生,非常简单,kernel的确会通过SMC来查询实时时钟。我们使用攻击代码来代替这条系统调用,这就是精髓所在。
但接下来我们怎么运行起来一个含有漏洞的内核呢?很多机器都升级到没有漏洞的版本了。这里还是让我先讲解下启动的过程吧:
Bootloader第一阶段(Bootrom)
这部分烧录在CPU的裸片里,大概是32kb左右的代码空间。主要的职责是负责将第二阶段的Bootloader从NAND Flash中读取出来,经过解密后放在CPU芯片内部的静态RAM中运行。他需要校验解密过程序镜像的哈希值,不对就不会执行。这部分代码包含了一堆的测试函数,我们可以通过拉起第5PIN“POST IN”,来激活这部分测试程序(译者注:POST为上电自测,例如PC BIOS里的内存检测之类),这个PIN可以在PCB的背面找到。这些测试项看上去没什么意思(当然是站在破解的角度) - 这些测试项看上去都和FSB(译者注:系统前端总线)相关(连接CPU和GPU的总线)。这部分代码是固定的,所有的系统用的是一份。
Bootloader第二阶段("CB")
这部分代码位于NAND flash里的0x8000地址上,由Bootloader第一阶段进行解密到内部静态RAM上并运行。他实现了硬件的基本初始化工作,并包含了"电子保险检查代码",用于校验Bootloader第二阶段的版本。电子保险包含了预期的版本号。2BL中保存了版本号和允许功能掩码位,通常保存在0x3B1 / 0x3B2..0x3B3地址上。这时去校验存贮在2BL头部的配对信息。其中的一部分校验功能是检测NAND区域中的校验和。这块区域里的代码被SMC加载运行。Xbox中包含了一个虚拟机,一些代码需要在上面运行。虚拟机的代码有些复杂,包括了下面一些操作:
- 初始化PCI桥
- 禁用GPU PCIE上的JTAG测试口
- 初始化串口
- 和SMC通信,清握手位
- 初始化内存控制器
- 希望不要:内存初始化失败会产生RROD(译者注:可怕的三红)
之后,外部的(512MB)内存就被初始化并且可以使用了。2BL这时将4BL解密并放在内存里。内存加密功能被使能 - 这时不会在内存上出现任何未被加密的代码段。
Bootloader第四阶段("CD")
这部分代码用于检测并解压缩5BL,同时也要做应用升级补丁的操作。首先通过读取电子保险来决定xbox的升级顺序,这里有个计数器来保存有多少升级补丁已被安装好了。因为这些补丁和2BL一样在xbox上是配对出现的,所以通过配置可以实现不使用老的升级包。每个升级槽保保存着最大可烧录的电子保险的个数。对于基内核来说也包含了一个对应值,通常是零,但是我们可以通过2BL里的配对数据段来更改他。基于这样的原理出现了一些时间攻击(TA),用于还原到1888内核。
Bootloader第五阶段("HV/Kernel")
HV和内核部分被合并到一个镜像文件里,用所有权算法进行了压缩(算法是:LDIC).
Bootloader第六阶段("CF")
Bootloader第七阶段("CG")
这部分是关于系统升级的。每个Xbox有一个叫做基内核的东西,使用的是1888核。他有2个升级槽每个是64k的空间(在Jasper的版本上是128K),里面包含了6BL和7BL。系统更新是大于64k的,所以只有前64k放在更新槽里,剩下的部分存放在文件系统的一个文件里。因为6BL没有文件系统解析的功能,所以通过映射的方法将更新程序剩下部分的扇区地址指针告诉6BL。
零填充
这里有个特殊情况:如果2BL的配对块中都是零的话,配对块就不会被检测。这样会设置一个标记位,内核就不会引导dashboard程序,而是引导了一个叫做"MfgBootLauncher"的程序,这里"Mfg"可能意思是说工厂生产。所以这是保留给生产过程的程序,这个程序适用于所有的硬件版本,很可能是在CPU-key 被烧录之前就使用的。
利用这个特性,我们可以生成一个适用于所有硬件版本的flash镜像。然而,4BL在这种模式下是不会去检测升级槽的,所以我们在1888内核里停了下来,不能运行dashboard,而且也不能离开这个状态。
前面讲的这些看起来非常枯燥,首先1888内核是不能使用KK漏洞的,也不能通过各种方法来运行《金刚》游戏。
但是,如果2BL里启动的是1920版本的内核,有趣的事情就来了:
4BL中使用到的密钥在CPU-key的帮助下产生了。意思是说没有CPU-key是不能解密4BL的。注意2BL和4BL在版本上是有绑定关系的-因为在2BL里保护了4BL的哈希值,是硬件实现的,用的不是RSA算法。
然而,当检测到全零的配对数据时,如前面所说的那样,CPU-key就不会被使用了。也就是说你不能只填全零的数据 - 这样会使4BL使用错误的密钥来解密。你需要的是使用正确的解密(这样需要知道CPU key),然后再用前面的算法来加密(译者注:加密的时候就是用了你期望的密钥了)
然而,1920可以用于时间攻击(timing attack) - 也就是可以恢复出CPU-key,这样就可以解码出4BL。1920版本的内核做了一个有意思的改动: 当发现全零的配对数据时,升级槽也会被使用。
这个改动使得我们可以引导任何版本的kernel,前提是只要我们有一份1920版本的2BL/4BL运行起来。重要的是这样我们可以引导起来4532版本的kernel。然而2BL校验的部分必须要通过,所以我们还是要关注里面的一些安全检查的。因为使用了零配对数,SMC哈希就没关系了。同样我们加载了MfgBootLauncher(这里运行起来4532版本,你会发现这时候led出现,红色/绿色闪烁,很好识别
不是三红之类的现象)。这个阶段就可以加载上面将的SMC/JTAG攻击方法了。
新出的xbox机器(TA的缺陷被修复)不再运行1920的版本了,而是别的版本,例如1921。因为不能运行HV代码,所以无法知道CPU key。然而在比较1921和1920的2BL的时候(我们有办法将这部分解密),唯一的区别就是添加了对时间攻击的修复代码(例如将两个memcmp实例替换成memdiff函数)。因为我们知道预期的4BL解码出来的哈希值,以及解码出来的尺寸,所以可以生成一个程序镜像来通过2BL的哈希校验。这不是哈希冲突 - 我们仅是借助于1920上2BL解码4BL的过程,再根据1920和1921的2BL的差别来生成了1921版本的4BL。
理论上说1921的2BL可以在所有的硬件版本上运行,甚至是TA-proof。但是在Zephyr, Falcon and Jasper的版本上程序会崩溃,原因在于VM这部分代码,这部分代码对于不同的GPU来说是无法做到二进制兼容的。(总共有3个版本的GPU:
1. Xenon用的是90nm的GPU
2. Zephyr和Falcon用的是80nm的
3. Jasper用的是60nm的)
内部版本从1921到4558之间的改动是非常小的。仅仅在memcpy函数中做了细微的改动,剩下也就是版本号不同了,这个修改都很容易合并。(译者注:参考上面的1920到1921的方法,他的意思是说我们可以自己产生1921到4558版本的4BL)
但Jasper's上运行的67xx的代码就不一样了,因为添加了对大块flash支持的代码。我们通过了一些魔法来完成了合并工作。(译者注:看来有些复杂,花了不少精力,真的Hacker是编程世界里至高无上的存在,他们的技能在我们眼中接近魔法级别 ;-) )
现在我们所有版本的4BL。很强是不是?意思是说所有硬件版本都可以运行4532内核。
问题解决办法
---------------
Q: 出入电源后电源LED变成红色
A:你短接了电源脚,可能是V33_SB这个信号,这个信号接入了NAND Flash中。在板卡上仔细检测下看看有没有焊锡的残留落在板卡上。可以用多些助焊剂,注意烙铁不要过热。
Q: "按电源键后电源指示灯停在黄色状态,然后就没什么反应了。"
A: SMC代码是无效的。这可能是说你flash连接有问题,或者flash里镜像是无效的。简单的说是Flash坏了,或者是flash里的代码不对。
检查方法:
- 首先是电路连接
- 编程时ECC设置是否正确?flash里的镜像文件通常包括了原始的ECC信息,例如:512+16字节每个扇区。确保你在做编程的时候没有修改那16个字节的信息,原来是什么就写成什么。
- 你使用的是不是正确的SMC的镜像文件。
Q:"风扇离刻全速运行"
A: 这个很像是NAND里的SMC配置扇区里的内容不对。你是否将工具生成的完整的镜像烧到了对应的地址上
Q: "E79错误"
A: 首先祝贺你,你的Xbox还是能引导到内核的,但是下不去了(例如没有文件系统之类的问题)。可能是你没有使用打过补丁的SMC代码或者目标地址写的不对。
Q: "Xbox是上电了,可是一直黑屏。"
A: 很多情况可以导致这样的问题。首先等上大概1分钟,看看有没有出现RROD的问题。有的话就说明VM代码和SMC的握手失败(错误码是xxxx),通常的意思是说崩溃了,在尝试了很多次后由SMC的看门狗来触发三红。你是否根据你的硬件类型选用了正确的2BL/4BL的镜像文件。因为VM代码运行需要的时间越来越多,从大致半秒(1888)到好几秒(1920),SMC的代码后面被改成超时。确信使用可用的SMC版本,如果可能的话最好基于Flash上原先的版本。如果你没有看到三红的问题,可以试试去运行POST代码。可以通过CPU的JTAG,或者测量那8个POST的pin脚。
错误码 6c:
因未知原因漏洞使用失败
错误码 10:
我们的代码在运行,很好,但是不能从flash中拷贝出XeLL的静载荷。尝试从另外的loader启动(见下面的"loader使用"部分),或者对flash重新编程。
错误码 11:
漏洞代码在运行,并跳转到了XeLL中。XeLL崩溃了。尝试从另外的loader启动,或者从串口上传程序来恢复。
错误码 >= 0x80:
这次错误来自bootloader。检查loader的反编译代码,看看到底是哪里出了问题。
如果你的flash没有问题的话,这些问题一个不会产生。
错误码 0xA0:
2BL不能在你的硬件上运行。更新2BL/4BL的版本。如果你已经在运行{1921, 4558, 5770, 6712}那就没办法了。你的Xbox已经更新到了新的版本,我们描述的漏洞已经失效了。把flash里的内容更新到出厂的情况吧。换台机器再试吧。
请注意在漏洞执行的时候系统刚刚启动,所以相关的一些硬件模块没有被完整的初始化过,这会影响到下面部分:
CPU:
- CPU在低电源模式被启动,这时CPU的频率只是正常情况的1/4。设置CPU的电源模式是可能的, 当然就必须对系统管理程序中相关的系统调用做逆向工程。
GPU:
- 需要做全屏设置,包括对ANA芯片的一些控制。设置成640x480的VGA模式的代码已经完成了,对于别的分辩模式还需要添加。
SATA:
- SATA可能需要做一些复位操作。Linux内核这里没有问题,但是XeLL不行。
所有的这些问题都需要解决。
这种破解方法也可以用来引导微软的内核,这样是为了能让本地运行程序成为可能。这不是本文要涉及的内容,甚至可以说和hack扯不上关系。这个破解方法可以让你运行任何你想要运行的代码。可以是Linux,可以是你喜欢的游戏模拟器,也可以是另外一个booter。
任何情况下,我们不会在微软提供的内核上的补丁。用我们提供的方法修改过的机器,玩在线游戏的时候也不可能避免被封号。而且微软已将放置了一些安全策略来侦测那些非法的修改。我劝解你不要将我们的研究成果滥用于那些不道德的地方。
loader使用
--------------
首先运行起来的我们的代码是一个小的loader,它支持下面的操作:
- 如果在串口上输入的字符loader可以读到
- 如果输入'@',进入串口加载模式
- 如果输入' ',则使用备份的bootloader
- 如果不是在串口加载模式:
- POST 0x10
- 从flash里去读bootloader
- POST 0x11
- 运行
- 串口加载模式
- 输出'>'
- 开始接受字符
- 当出现10个连续的'x'时,停止加载
- 输出'!'
- 运行
如果你要更新flash你的bootloader,下面的信息会有帮助。
需要使用到的地址是:
Flash基地址,备份的bootloader地址
Flash基地址+0x40000是主bootloader的地址
CODE_BASE是bootloader在ram中运行的内存地址
默认情况下,使用下面的地址影射关系
00000000..00100000 : SMC, KV, CB, CD, CE, CF, CG, backup bootloader
00100000..00140000 : main bootloader
00140000..00f7c000 : empty space
00f7c000 : smc config block
00ffc000 : exploit buffer
但这些地址可能会变化。