最近,我一直在研究如何将我的旧智能手机的功能推向传统意义上仅仅通过扎根它所能做的事情。智能手机包含大量硬件,这些硬件很好地用于它们的标准用途,但通常被锁定到这样一个点,即你只能在表面上修改它们的行为。然而,当它们可以被推送时,手机就可以被用作有效的攻击工具。
监控模式通常与标准无线加密狗一起使用,用于嗅探Wi-Fi流量和破解WPA密钥。通过改变无线加密狗的模式,使其在比接口传统提供的级别更低的级别上接收所有流量,就有可能访问大量以前无法访问的信息。
在智能手机上,有几种方式可以实现这一点。许多高通SnapdragCPU都完全支持这一点,通过电话上的root访问,用户可以使用以下命令将其设备置于此模式:
在Broadcom芯片组上,这可能会稍微复杂一些,需要内核补丁和自定义固件BLOB,但是,有关于如何实现这一点的公开信息。
许多基于Android的安全工具完全支持此功能,但是也可以使用chroot环境创建自己的设置来利用这些功能。
在我所有扎根的手机上,我将一个基于Debian的chroot放入核心数据分区。这可以在基于Debian的操作系统中使用“qemu-debootstrap”命令轻松生成,该命令将创建您选择的CPU架构和版本的Debian根文件系统。我将其与以下命令结合使用,这些命令通常放在我的手机上的“/data/local/userinit.sh”文件中(一个脚本,通常在Android设备上启动时运行),以便在不影响核心Android操作系统的情况下为这个chroot环境提供对手机硬件的访问,并启动一个SSH服务器,将您直接带到这个环境中。
Mount-o重新装载,rw/data 装载--绑定/proc/data/debian_arm64/proc Mount--bind/sys/data/debian_arm64/sys Mount--bind/dev/data/debian_arm64/dev 挂载devpts/data/debian_arm64/dev/pts-t devpts Chroot/data/debian_arm64//bin/bash--login-c/usr/sbin/sshd&;
Linux,特别是在嵌入式设备上,能够充当USB设备。在Android上,内核通常被编译为支持对用户有用的非常具体的功能,如MTP、PTP、Tethering,对于更高级的用户,则支持ADB。这通常被编译为内核的静态特性,根据设备的/sys/目录中的设备文件进行有限的修改。通过下载手机内核的源代码(通常可以在网上找到),人们可以改变配置,以启用大量执行不同功能的模块。
其中我最喜欢的是小工具文件系统(Gadget Filessystem),这是一个允许操作系统的用户空间控制USB功能的模块。这样,只要配置正确,嵌入式Linux设备就可以像任何USB设备一样工作。要做到这一点,只需要能够访问您首选语言的文件即可。我经常用C语言来做这件事。你可以用它来完整地模拟设备,或者利用USB协议栈中的弱点。
虽然所有这些功能都很有趣,而且很容易获得,但我发现,对Android设备中使用的NFC芯片的低级功能缺乏研究。虽然标准的Android设备能够充当读取器,具有一些有限的标签仿真功能,并且能够用作一些高级数据的中继工具,但它在功能上是有限的,人们通常可以在NFC攻击工具中找到这些功能。
Proxmark、变色龙和基本工具能够通过NFC进行原始级别的通信,并且能够执行在电话上根本不可能进行的攻击,即使使用root访问也是如此。这样做的原因是,手机通过NFC与专门为此目的建造的辅助芯片进行通信。我决定选择一个目标手机,看看我是否可以修改它的NFC芯片的固件,以便将一部标准的智能手机变成NFC攻击工具。
我的第一个目标是我的三星S6。这是我有的一部较旧的智能手机,是为此目的而设置的。我以前曾尝试过它的功能,修改了内核,用Debian的基本设置替换了Android操作系统,我认为它会产生一些有趣的结果。
查看了手机的文件系统和一些日志,我发现它使用的是三星半导体(Samsung Semiconductor)开发的芯片,这在在线拆卸中很少找到,因为这款手机的美国版使用了完全不同的芯片组。然而,所有非美国版本都使用这种芯片。
该芯片被发现是S3FWRN5,这是2014年开发的芯片,在Galaxy S6和Note 4手机中都有发现。对这款芯片及其在线功能的研究表明,它的一个关键功能是安全地更新固件,这意味着手机的文件系统中的某个地方可能存在固件二进制文件。
我设法在网上购买了芯片本身,但我决定从手机上进行所有的反向工程,最终没有在项目中使用它们。
当您在Android设备上查看硬件通信时,它与在任何嵌入式Linux设备上查看几乎相同。通过ADB,可以导航到“/dev/”文件夹,并查看可用的设备文件。看一下三星手机的内核源代码,我很快就能洞察到芯片是如何从手机进行通信的,并注意到它使用I2C进行通信,并使用GPIO引脚来设置电源模式。在文件系统中,可以通过“/dev/i2c-*”和“/dev/gpio*”文件轻松访问这些文件。
但是,芯片的内核驱动程序被发现将其抽象为单个设备文件:“/dev/sec-nfc”。该文件利用IOCTL来设置电源和模式,并且可以写入和读取以发送和接收数据。
因为标准NFC芯片使用称为NCI的标准协议进行通信。该协议由基本命令头构成,用于以旨在降低交互复杂性的方式提取和限制功能。每个NCI命令由以下元素组成:
该协议的能力是巨大的,但确实降低了NFC操作的复杂性,将错综复杂的通信降到了芯片本身。
NCI在协议中内置了几个元素,这些元素允许制造商将其功能扩展到标准要求之外。这有各种目的,例如特定于芯片的配置信息,或者向芯片添加隐藏功能。
其中最关键的是组ID 0xf。此组专门用于特定于供应商的命令,并允许添加任何非标准功能。用户可以通过发送带有递增操作ID的命令并检查错误响应来强行通过这些命令,即使它们没有文档记录也是如此。在NCI中,这些函数最有可能包含有趣的或可利用的功能,因为它们没有记录在案的标准。
以下命令就是一个很好的例子,三星的S3FWRN5使用这些命令来设置通信的频率值。
我看过的所有NFC芯片的固件更新都使用自己的协议。虽然这些协议仍然使用与核心NCI通信相同的端点,但对于S3FWRN5,这是I2C,协议本身是不同的,通常需要将其置于特殊模式才能执行这些操作。在S3FWRN5中,芯片通过IOCTL设置为引导加载模式,允许进行固件更新。
我发现固件更新文件很容易在我手机的“/Vendor/Firmware/”分区中的“sec_s3fwrn5p_Firmware.bin”文件中找到。
我想跟踪一个完整的固件更新,并希望记录它是如何执行的。这样做是为了加快分析速度,因为虽然更新的源代码是在线的,但我觉得分析实际的通信会更快,并让我对更新是如何执行的有一个更复杂的看法。为此,我修改了手机上的“.rc”配置文件,特别是与NFC芯片相关的配置文件。我发现了一个文件,其中包含固件目录,以及提高数据跟踪级别的配置,甚至手机是否总是在启动时执行固件更新。我修改了这个以适应我的需要。
修改了这些功能后,我发现每当我在手机上启用NFC时,都可以通过logcat跟踪固件更新。过滤这些数据使我能够全面了解更新过程的各个方面。
从这个日志中很容易注意到更新是如何执行的。使用四字节头,后跟后续有效载荷数据。记录了每个命令的以下地址。
还注意到,每个交替命令设置传输的第一字节的高位。
在评估了这些更新的细节之后,我进一步查看了我拥有的更新文件。这使我能够注意到哪些方面是元数据,哪些部分值得研究以模拟更新。
注意到一个明显的日期戳(用红色突出显示),然后是可能是版本号的内容,然后是一些地址信息,可能是关于文件特定方面的元数据。
在绿色中,我注意到大量的高熵字节,这很可能是密码签名,元数据中记录了它的起始地址。签名的存在意味着这不仅仅是一个反向工程项目,还需要绕过签名攻击。
最后,我注意到蓝色的数据要统一得多,很可能是固件的开始。通过查看更新中发送的命令证实了这一点。该固件的低熵意味着它也是未加密的。
快速取胜的方法是了解固件所在的体系结构。由于这是一个原始的二进制文件,因此不会提供有关架构的任何细节或关于芯片的任何其他信息。这款芯片可能要么使用嵌入式NFC芯片中常见的8051架构,要么使用ARM Thumb架构,我发现这是近年来嵌入式芯片组中最常用的架构。
我决定首先检查拇指代码,因为有一个快速的助记符,可以让我立即知道是不是这样。Thumb中的一个常见操作是“BX LR”,该操作用于通过分支到链接寄存器来返回其中没有寄存器被推入或弹出的函数。由于此操作的性质,它将大量出现在Thumb固件中。“BX LR”的操作码是0x70 0x47,它在ASCII中转换为“PG”。通过对二进制文件运行string命令并获取此值,您可以很容易地判断芯片是否正在使用拇指代码。在这种情况下,幸运的是,它确实使用了拇指代码。
这是非常棒的,有几个原因。首先,这意味着芯片可能会采用Cortex-M风格或Securcore架构。这些都是基于ARM的架构,具有定义良好的标准。此外,与其他常见的嵌入式体系结构相比,Thumb代码更易于分析、逆向工程和修补。
考虑到这些细节,我决定编写一个工具来实现固件更新。如果我可以这样做,那么我就可以修改它们,看看是否可以在引导加载程序级别攻击芯片。如果我可以这样做,它将允许部署自定义固件,并使任何漏洞更难修补。
我写了一个C应用程序,我可以将它部署到我的手机上,以便与芯片硬件交互。通过重播我在跟踪的固件更新中找到的命令并检查响应,可以快速编写工具。用于设置模式的IOCTL是从原始内核源确定的。
这允许评估特定功能,包括某些命令发送原始命令以外的后续数据有效负载的事实,如更新开始时发送的签名和SHA-1散列所示。
此协议的构建和使用的序列意味着固件的SHA-1散列在更新过程结束时得到验证。这意味着即使不能执行,也可以将修改后的固件写入芯片。
注意到此序列中不存在命令“0x03”。这在很大程度上暗示了那里有一个隐藏的指挥部。
我猜测,如果有隐藏的命令,它们可能也存在于0x06到0xFF之间。我在更新序列的每个点(发生更新之前、更新期间和更新之后)强行遍历每个命令。错误响应用于指示如何使用这些错误响应:响应2表示命令无效,而命令9表示有效负载太小。
发现隐藏命令3执行与命令4相同的功能,唯一的例外是命令3使用512字节的有效载荷大小。这在任何情况下都是不可利用的,但是注意到一个额外的隐藏命令:命令6。
使用错误响应,我设法计算出该命令接受8字节的有效负载数据,并且只有在固件更新发生之前才起作用。最初,我发送了8个空字节,收到了不同的错误值,但没有数据。因此,我开始将8个字节中的单个位设置为递增。
最后,当命中第五个字节的第一位时,该命令返回四个字节的数据:00200020或0x20002000。这很有意义,因为它很容易就是堆栈指针,在大多数Cortex-M和Securcore芯片组的开头都可以找到,它是Vector表的一部分。
递增到下一位时,发现返回了额外的四个字节以及原始的四个字节:BD 02 00 00(0x000002BD)。这很容易就是在Vector表的下一个地址找到的Reset Vector。在这一点上,很明显,隐藏命令6允许从芯片读取任意存储器,前四个字节是32位地址,后四个字节是响应大小。通过读取内存中的递增地址证明了这一点。
将来自地址0x00000000的内存缝合在一起,可以增量地生成包含完整引导加载程序的二进制文件,并显示其大小为8KB,并且遵循与任何标准Cortex-M芯片组相同的结构。我现在可以执行固件的静态分析,但是由于嵌入式固件没有字符串或函数引用,这可能是一项冗长的任务。
上面的代码是在引导加载程序启动的附近发现的,并被确定用于检查用于引导核心固件或引导加载程序的GPIO引脚。这表明在地址0x3000处检查了魔术值(0x5AF00FA5),该值位于部署固件的开始处,并在更新文件中设置为0xFFFFFFFF。如果该选项不存在,引导加载程序将不会引导更新后的固件。最初,我尝试将此值直接放入要发送的固件中,但这并不有效。
如上所示,在执行固件更新之前命令0、1、2和6可用时,很容易停止接收I2C命令的代码。
最后,注意到RSA公钥,它很容易被发现,因为大量的高熵字节跟在0x00010001(65537)后面,0x00010001是传统上用于RSA密钥的指数。
我想在这个引导加载程序中找到一个内存损坏漏洞,这样我就可以部署自定义固件。然而,这有一些限制。
首先,我只有一部手机,如果我通过FUZZING将NFC芯片砖封住,我需要买一部新的才能继续这个项目。我不想这样做,我想尽可能安全地攻击它。
其次,所有的通信都是通过I2C进行的,通过这种方法进行调试会很困难。
我认为最好的方法是尽可能接近地模拟引导加载程序,以找出弱点。
独角兽引擎是在代码中模拟特定固件架构的优秀工具。与作为应用程序运行的标准QEMU不同,Unicorn允许通过可在C或Python中使用的库进行仿真,并允许挂接仿真过程的每个步骤。
我的固件的Unicorn引擎的初始设置很简单:我映射了所有有效内存,在地址0x00000000加载了引导加载程序,并将程序计数器设置为重置向量。
这一点的简单性得益于引导加载器不使用线程,而是使用无限循环来检查I2C命令。
然而,也有一些并发症。嵌入式芯片总是从将硬件外围设备设置为标准配置,并验证这是否正确进行开始。因为这些都没有被仿真,固件会不断地重新启动,认为初始化是无效的。我通过不对初始设置函数执行ping命令来修复此问题。
这样,固件就会一直运行,直到它开始不断地从地址0x40022030读取数据。这是一个硬件地址,用于读取有关芯片外围设备的特定元素的信息。查看它读取此地址的原因发现,它正在检查特定的位,并无限循环,直到找到正确的位。我认为这很可能是I2C接口的状态寄存器,这意味着我们在正确的轨道上。使用Unicorn的挂钩函数,我使此地址始终返回随机值,以便设置特定的位。
接下来,发现芯片不断从地址0x40022038读取字节,接近原始地址。由于这是连续读取数据,因此假定这是芯片的I2C FIFO缓冲区。我让我的代码开始通过这个地址发送引导加载程序命令,看看会发生什么。
完成此操作后,我发现固件将开始写入前两个地址之间的地址0x40022034。通过打印这些写入的值,我发现它正在发送与原始引导加载程序类似的响应。这意味着我对芯片有了足够的表面仿真,可以开始进行模糊处理了。
我现在可以开始随机模糊,而不需要担心芯片被砖砌成的问题,而且有调试功能。这其中有一些机会,包括使用比芯片上可用的8KB RAM更大的16位有效负载大小值,以及在更新期间在命令之后使用分块的附加数据。
我认为最有可能出现内存损坏的地方是固件更新初始化功能。此函数通过发送固件的SHA-1散列的大小(0x14)和签名的大小(0x80)来工作。在此之后,发送包含此散列和签名的分块数据。
最初,有人试图缩短这些值,试图操纵散列和签名之间比较的数据量,但这是一种无效的方法。
使用我的模拟引导加载器,我增加了散列的大小,然后使分块的数据与该大小相匹配。我发现这允许使用此命令发送越来越多的数据,直到最终模拟的引导加载程序由于内存超出界限而遇到异常,访问芯片上8KB RAM之外的内存。
这很可能意味着我发现了一个缓冲区溢出漏洞,它将允许我覆盖堆栈并操作当前存储在其中的链接寄存器值。这是一个可用的内存损坏漏洞。
我想写贝壳鳕鱼。
.