本节将以一辆智能汽车作为攻击目标。我们平时对汽车的了解仅限于简单的驾驶操作,所以要在短时间内攻破它需要经过认真思考。整车攻击面如图11-1所示,我们将在接下来的内容中进一步介绍。
假设我们要攻击一辆智能汽车,首先,根据以往经验,我们会查看这辆车的功能,包括进入汽车、下载手机应用、登录、在中控大屏上进行各种设置等一系列动作。经过数小时后,我们终于完成了初步了解,此时我们很可能对它的高级功能感到惊叹。同时,我们知道越高级、复杂的东西越容易被攻破。
在熟悉了功能后,我们会尝试构建攻击场景,这是一个非常自然的过程,因为我们天生具有联想能力。即使一个没有任何安全攻防经验的人,也能想象自己的汽车有可能受到哪些威胁。例如:我的手机可以打开车门,那么黑客能否做到这点呢?我的车可以上网,那么黑客能否通过网络给我的车发送病毒呢?我的车上有各种插口,那么黑客能否通过插口破坏汽车呢?
作为攻击者,我们需要更专业一些,要尽可能不遗漏地分析出所有的攻击场景。如何做到呢?我们采用最容易的思考方法——聚类模式。首先,我们不需要列举出所有可能的攻击场景,因为车辆功能太多,一时间无法全面考虑。因此,我们首先思考攻击者想要攻击汽车的必要条件,即初始访问条件是什么。这对攻击者来说,在熟悉车辆的功能后可以很快地分析出来。例如:通过手机可以对汽车远程解锁,那么黑客也可以远程攻击;车辆具有近程通信功能,黑客也可以近程攻击;车辆具有物理接口,黑客也可以进行接口攻击。
下面列举一些初始访问条件。
❑无授权的远程攻击场景:表示攻击者不受距离影响,无须授权就能控制车辆。
❑无授权的中程攻击场景:表示攻击者在一定距离范围内无须授权就能控制车辆。
❑有授权的远程攻击场景:表示攻击者需要获取某些授权才能远程控制车辆。
❑需要用户交互的远程攻击场景:表示攻击者需要通过车主在车内执行一些操作,才能获取车辆控制权。
❑有授权的中程攻击场景:表示攻击者需要在一定距离范围内,获取某些授权后才能控制车辆。
❑车外物理接触攻击场景:表示攻击者需要与车的外表面进行物理接触,以获取车辆控制权。
❑车内物理接触攻击场景:表示攻击者需要通过车内的接口等进行操作,从而获取车辆控制权。
❑具有车机应用权限的攻击场景:表示攻击者需要在目标车机中获取应用权限以执行自己的代码。
❑具有其他ECU权限的攻击场景:表示攻击者需要在除车机以外的ECU中执行自己的代码。
❑需要拆车的攻击场景:表示攻击者需要将车辆拆开,因为ECU的接口并非都暴露在外面,有些是隐藏在车辆的内饰后面以及ECU的盒子里面的,攻击者需要拆车后才能接触到。
相信大多数人都可以做到初始访问条件分析,那么接下来该怎么做呢?我们需要思考,在这些前提条件下,能通过什么来攻击汽车。在无任何前置经验的情况下,这一步是需要配合专业的技术手段来实现的,接下来我们将对此进行介绍。
1.远程初始访问
在分析远程攻击条件后,我们可以进一步思考如何攻击汽车。针对这些初始条件,我们需要结合专业技术手段进行深入分析。以下是对几种可能的攻击方法的分析。
首先是无授权的远程攻击手段。这种攻击方式主要是指那些开放在公网上的云端平台,如车辆云端和App云端,以及远程的无线电(如GPS和Radio)等。这种攻击方式的特点是攻击者不需要获得授权就可以进行攻击。这种攻击方式的危害比较大,因为攻击者可以直接通过云端平台和无线电进行攻击。
其次是有授权的远程攻击手段。这种攻击方式主要是指那些能够获得授权访问云端平台的攻击方式,例如访问OEM后台、供应链后台、车辆云端账户、App账户等。相对于无授权的远程攻击方式来说,攻击者采用这种方式需要通过一定的授权手段获得权限,但一旦攻击成功,其危害也是很大的。
图11-1 整车攻击面分析
最后是One Click远程攻击手段。这种攻击方式主要针对车机上具有远程交互的App,例如浏览器和社交软件等。这种攻击方式的特点是攻击者可以通过车主的一次点击操作进行攻击,攻击成本低,危害也很大。
综上所述,针对这些初始条件,需要结合专业技术手段进行深入分析,以便有效地防范和应对各种远程攻击。
2.中程初始访问
与远程攻击类似,我们可以从车辆的功能入手来分析中程攻击的可能性。首先,有一些显而易见的中程功能,包括蓝牙钥匙控车、车载无线Wi-Fi热点、NFC卡片钥匙等。其次,通过查阅资料可以了解到自动驾驶主要依赖各种传感器,利用这些传感器也能实现中程攻击。最后,还有一些比较隐蔽的功能,例如TPMS(轮胎压力监测系统),则需要对车辆有一定了解才能知道。
具体来说,中程攻击可以分为以下几种类型。
❑无授权的中程攻击手段:主要指通过蓝牙、Wi-Fi、UWB、NFC以及其他无线电技术与车辆进行无授权的近程通信。
❑无授权的中程(传感器)攻击手段:指利用传感器实现近程通信,例如摄像头、激光雷达、超声波传感器等。
❑有授权的中程攻击手段:主要指通过蓝牙绑定、Wi-Fi密码等方式实现授权的车辆近程通信。
3.物理接触初始访问
物理接触攻击是一种通过直接接触车辆电子系统进行攻击的手段,其攻击手段主要包括车外和车内两种情况。在熟悉车辆后,几乎所有的电子插孔都可以成为物理接触攻击的目标。
车外物理接触攻击手段主要指利用车外的物理接口实现攻击,比如充电口等。而车内物理接触攻击手段则主要指利用车内的物理接口实现攻击,比如OBD、USB、CD、AUDIO等插孔,这些插孔都可以被攻击者利用来进行物理接触攻击,因此需要进行加固或者限制访问权限,以保障车辆安全。
4.ECU权限初始访问
在进行ECU权限初始访问时,需要先了解车辆的电子电气架构,包括各个ECU之间的通信方式和权限。一些公开的架构资料可以帮助我们了解这些信息,例如车辆的技术规范、用户手册、网络资料等。
在获得足够的信息后,攻击者可以尝试利用漏洞或弱点进行攻击。如果攻击者已经获得了车机的应用权限,那么可以通过车机的提权等方式进行后渗透。如果攻击者已经获得了其他ECU的权限,那么可以利用这些权限进行车内网络的横向渗透。
需要注意的是,在进行ECU权限初始访问时需要遵守法律法规,避免那些违反相关法律法规的行为。车辆制造商正在不断加强车辆的安全性能,例如增加加密措施、限制ECU之间的通信等。同时,攻击者也在更新攻击技术和手段,以适应不断提高的车辆安全性能。
5.拆车权限初始访问
拆车权限初始访问是指攻击者通过拆卸车辆内部电子设备进行攻击。攻击者可以使用这种方法来直接物理接触和操作车辆内部的电子设备,例如ECU和总线等。
为了获得这种权限,攻击者需要对车辆的电子电气架构有深入的了解,包括车内ECU的排布、总线的排布、ECU芯片类型等。攻击者需要对车辆内部进行分析,确定物理接触攻击的向量,例如车内物理接线攻击、PCB硬件攻击等。
由于这种攻击方式需要直接操作车辆内部的设备,攻击者必须拥有足够的专业知识和技能才能够顺利地进行攻击。此外,这种攻击方式可能会留下明显的物理痕迹,因此容易被发现。
有了攻击向量之后,我们就有了研究方向。当然,这些方向还不够明确,我们需要沿着攻击向量进一步细化要研究的目标。这一步会涉及很多技术和方法,这也是表现攻击者技术高低的重要步骤之一。一些非常严重的漏洞往往出现在一些未被人发现的攻击面中。我们将在后续的内容中逐一讲解探索各个攻击面的过程,本节只列举出我们的分析结果,主要攻击面如图11-2所示。
图11-2 汽车网络安全攻击面
主要攻击面及对应的攻击方式如下。
❑云端:OTA服务器、TSP服务器、手机应用程序登录、车辆钥匙分享API、远程控制API等。
❑蓝牙:协议栈、BLE(低功耗蓝牙)钥匙、经典蓝牙耳机、蓝牙通话等。
❑Wi-Fi:协议栈、开放端口、中间人攻击等。
❑传感器:欺骗、干扰、致盲、模型攻击等。
❑应用程序:JavaScript引擎、社交媒体消息解析、媒体文件解析、WebView攻击、中间人攻击等。
❑OBD:DoCAN、DoIP等协议攻击。
❑提权:用户态提权、内核态提权等。
经过攻击面的分析,我们基本确定了要攻击的目标,但每个目标都可能涉及很多内容,攻击点分析就是找到具体的点,也就是确定是哪个程序(Binary)、哪个进程、哪段代码等。在这个过程中我们很难全面列举,因为不同目标使用的代码不同,除非使用一些通用库,否则我们需要根据自己的目标进行研究。攻击点分析是整个攻击过程中非常重要且耗时的步骤。通常我们会使用抓包分析的方式进行初步信息收集,然后对感兴趣的报文进行逆向代码分析,查找相关线索。
实际上,在这个过程中,我们已经开始了漏洞挖掘。一旦发现一个新的攻击点,就需要构思该点的漏洞可能出现在哪里,并进一步编写代码、通信和逆向调试来验证。此外,如果该攻击点具有复杂的处理逻辑,那我们还可以使用Fuzz的方式来探索漏洞。
汽车网络安全集成了Web、逆向、物联网(IoT)、移动和无线等多个方面的综合安全性。对于汽车漏洞挖掘而言,硬件分析和固件分析是必备的能力,如果不具备这两种能力,则无法深入挖掘汽车漏洞。
硬件分析是一个汽车黑客需要掌握的通用前置技能。硬件分析的知识点如图11-3所示,它并不是一个针对汽车攻击面的研究。如果读者已经掌握该技能,可以跳过此部分。
图11-3 硬件分析的知识点
为何需要掌握硬件分析技能呢?因为对于黑盒汽车来说,我们无法获得它的固件、设计资料或系统权限,只能通过硬件这个最直接的入口去获取更多信息。该技能对于后续的攻击面分析、固件提取、逆向和调试都是不可或缺的帮手之一。
1.拆解ECU
拆解ECU需要由专业的车辆维修工程师来完成。一般来说,拆卸中央集成控制器的步骤包括打开前机舱盖、断开蓄电池负极电缆、拆卸仪表板左侧下护板、拆卸中央集成控制器固定螺丝、断开中央集成控制器控制插头、取出中央集成控制器总成、拆除模块外壳,如图11-4所示。
图11-4 拆解中央集成控制器的一般步骤
拆解ECU后,就要进行具体分析了。ECU板上包含了很多有价值的信息。例如,通过处理器芯片,我们可以了解到它的功能大致包括什么;存储芯片可以帮助我们提取固件;调试接口可以帮助我们动态分析ECU的功能;通信接口可以让我们了解与ECU通信的方式。
2.丝印查看
通常,我们需要依靠PCB板上的丝印信息进行分析。通过放大镜或显微镜可以查看芯片型号。如果板上的丝印信息不足,我们就需要使用万用表进行测量(测量方法在上册中介绍过)。不同的引脚有不同的测量方式。例如,我们可能通过丝印看到JTAG接口,如图11-5所示,各引脚的用途如下。
❑VCC(Voltage Common Collector):电源引脚。
❑TRST(Test Reset Input):对TAP(Test Access Port)进行复位,即初始化。
❑TDI(Test Data Input):测试数据输入。
❑TMS(Test Mode Select):测试模式选择引脚。
❑TCLK(Test Clock):时钟信号。
❑RTCK(Return Test Clock):将JTAG信号与内部时钟同步。
❑TDO(Test Data Out):测试数据输出。
❑RESET(Test Reset):直接对目标系统复位。
图11-5 JTAG接口
3.FCC ID查看
FCC ID是美国联邦通信委员会(FCC)注册的硬件产品唯一标识符,主要分配给在美国合法销售的无线设备。设备制造商需要向FCC提供设备的实验数据、产品手册、文档、照片等资料进行注册。大多数智能设备都有FCC ID,对于这些设备,可以通过FCC网站查询其详细信息,例如设备的内部照片、工作频率等。这些信息对于识别组件以及了解该设备使用的射频技术至关重要。
在针对车辆的无线钥匙的例子中,我们首先拆解钥匙并查看其FCC ID,如图11-6所示。
图11-6 FCC ID查看
然后去fccid.io上搜索这个编号——NBG009066T,可以得到公开的钥匙信息,包括照片、测试报告、用户手册等,如图11-7所示。
4.芯片DataSheet查看
当我们确定了PCB上的芯片型号后,可以查找芯片的手册,即DataSheet。常见的芯片在其官网上都能找到手册,里面明确定义了芯片的引脚、内存映射等信息。这些信息可以帮助我们了解硬件调试接口和固件的基址信息。
例如,图11-8显示的是英飞凌芯片TC37系列产品的手册中BGA封装的引脚定义,圈出的是JTAG引脚,我们已经介绍过它们的含义了。
图11-7 通过FCC ID获取无线钥匙的信息
图11-8 芯片引脚定义
图11-9显示的是英飞凌芯片TC3xx的内存映射表,我们可以从中看出不同的内存地址所存放的内容是什么,比如0x80000000是Flash的基地址。
图11-9 芯片内存映射表
5.硬件分析汇总
经过上述的丝印查看和芯片手册查看之后,可以得到一个ECU芯片的示意图,并从中了解到ECU上有哪些芯片。通过这些芯片的DataSheet上的相关描述,我们可以知道它们的功能,从而了解该ECU大致具备的功能。以T-BOX和IVI为例,我们可以标记板上所有芯片的型号,并调查它们所具备的功能。
1)T-BOX:包括一个英飞凌的MCU(微控制器)和一块LTE的SOC(System On Chip,系统级芯片),外置了一块Kioxia America eMMC 8GB 5.1 2D 15nm芯片用于存放日志信息等数据,如图11-10所示。
图11-10 T-BOX芯片标记(正面)
2)IVI:包括一个英飞凌的MCU(TriCore SAK-TC275TP-64 F200N)、一块高通8155芯片(PMM8996AU)以及一个东芝UFS存储芯片,如图11-11所示。
图11-11 IVI芯片标记(正面)
了解了芯片的功能后,如果想继续深入研究,就需要掌握固件逆向的技能。这是汽车黑客必须掌握的前置技能之一。如果读者已经掌握了该技能,可以跳过本节。本节知识点如图11-12所示。
图11-12 固件逆向的知识点
为何需要此项技能?因为对于攻击者来说,汽车是一个黑盒产品。在做完一些通用的基础黑盒测试后,攻击者势必要了解车载系统具体的代码实现,才能进一步发现更多漏洞,比如一些协议、功能的具体实现等都需要通过固件逆向来完成。
1.固件分类
固件是车辆控制器的软件。不同固件的复杂度不同,从MCU的Bare Metal(裸机)固件,到用于智能座舱、自动驾驶等更复杂模块的基于微处理器的成熟操作系统,如Linux、Android、QNX等。逆向是帮助我们对固件进行进一步分析的重要步骤,在本节中,我们将讨论如何使用各种工具对固件进行逆向工程。让我们先来看看什么是Bare Metal固件。
(1)Bare Metal固件
根据车辆ECU的功能选择技术栈。如果功能单一、不需要UI交互、不涉及复杂网络协议,则使用裸机(Bare Metal)固件,例如电池管理、车身控制、底盘管理等模块。那么什么是Bare Metal固件呢?简单来说,这类固件可以直接与硬件交互,不涉及驱动程序或内核。现在车内的Bare Metal通常都是基于Autosar CP实现的标准嵌入式系统。Bare Metal不会运行太多复杂任务,它的任务通常不超过3或4个,被安排在特定的条件下运行。Autosar供应商为这些功能提供了SDK,并提供了生命周期管理方法,使得这些程序员编写的功能在循环中运行。
基本的Bare Metal固件都是用C语言编写的,所以内存攻击也适用于这些固件,例如利用缓冲区溢出相关漏洞。这些ECU通常会使用CAN或其他总线与其他设备进行通信,因此可能会存在通信协议漏洞,例如不正确的数据包处理、密钥泄露、缓冲区溢出等。而发现这些漏洞,就需要固件逆向工程了。
首先,Bare Metal固件就是一个二进制文件,它的逆向与Windows EXE和Linux ELF文件不同,因为它没有预定义的文件结构。对于Bare Metal二进制代码,我们需要了解芯片手册,在反汇编工具(例如IDA、Ghidra等)中创建内存映射,以进行适当的反汇编。内存映射可以帮助我们了解MCU与哪些GPIO及其他外围设备交互,这些信息将有助于我们了解ECU的功能。
(2)成熟的操作系统固件
现代智能汽车的功能越来越复杂,例如Wi-Fi连接、蓝牙连接,以及娱乐、会议、防火墙等功能。要实现所有这些功能,需要一个成熟的操作系统来支持。
嵌入式系统有许多不同的操作系统选项,如Linux、Windows CE、Cisco IOS、Symbian、Android、VxWorks等。其中一些是专用的,如用于路由器的Cisco IOS,其他大多数则更加通用。在嵌入式系统产品中,最受欢迎的操作系统是Linux。这有很多原因,首先它是开源的、灵活的,最重要的是它是免费的。你可以在周围找到许多使用Linux的设备,例如互联网路由器、信息娱乐系统等。
这类固件通常至少包含3个组件:引导加载程序(BootLoader)、内核和文件系统。引导加载程序是用于加载操作系统、内核并传递各种信息需求的软件。一旦引导加载程序执行完毕,内核就会接管,并启动其他用户应用程序,以便最终用户可以使用操作系统。这通常涉及在后台启动各种应用程序和服务,一旦所有这些步骤都完成了,用户就可以与系统交互了。此外,所有用户应用程序和应用程序数据都存储在文件系统上。
2.固件解包
提取固件的方法在前文已经介绍过了。现在我们已经拿到了固件,接下来的步骤就是对固件进行解包,以便我们使用逆向工具进行分析。我们需要从Flash中提取固件并进行分解。
(1)清除OOB
在分析从Flash中提取的固件时,需要进行预处理。Out-Of-Band(OOB)“空闲”区块被插入到每页数据或者每个数据块的末端。这些空白的存储区块被主控用于跟踪坏块、擦除计数等。因此,当从整块芯片提取原始数据时,这些空白区块也会被一并提取。这意味着我们需要将所有这些空闲区块剔除,才能从芯片的固件中获得连续的真实文件。
如图11-13所示,有两种常见的存储数据方式。一种方式是每页大小为2112字节,其中512字节为数据区,16字节为OOB。另一种方式是首先存储4个512字节的数据区,其对应的每个16字节的OOB在页面尾部按顺序跟随。因此,我们可以编写一个脚本,将每个2048字节的页面中后面64字节的OOB数据删除,脚本如下。
图11-13 Flash芯片的OOB
(2)解包固件
Binwalk是最流行的固件解包工具之一,它试图从任何二进制BLOB中提取二进制文件。它通过搜索许多常见的二进制文件格式(如ZIP、TAR、EXE、ELF等)的签名来实现这一点。Binwalk具有一个二进制头签名数据库,签名匹配是针对该数据库进行的。使用该工具的主要目的是提取嵌入在固件二进制文件中的文件系统,如Squashfs、yaffs2、Cramfs、extfs、jffs2等。文件系统包含将在设备上运行的所有应用程序代码。该工具还有许多参数,我们可以调整这些参数以便更好地进行提取,比如使用binwalk-eM<filename>命令进行迭代解包,如图11-14所示。
图11-14 使用binwalk-eM<filename>命令进行迭代解包
更多使用方法请参考https://github.com/ReFirmLabs/binwalk/wiki/Usage。
(3)特殊文件系统解包
如果Binwalk无法提取相应的文件,则首先需要判断该固件是否包含文件系统。如果它是一个Bare Metal系统,则可以直接使用IDA等工具进行逆向分析。如果它包含文件系统,就需要确定它使用的是哪种文件系统。可以根据特定文件系统的定义编写自己的解包工具,或者寻找第三方工具来解包。以下是一些常用于解包QNX固件包和Android固件包的工具。
❑OS image(IFS)固件:dumpifs。
使用dumpifs解包IFS固件,如下所示。
❑Flash filesystem image(EFS)固件:dumpefs。
使用dumpefs解包EFS固件,如下所示。
❑system.img和vendor.img固件。
使用simg2img解包system固件,如下所示。
❑boot.img固件。
使用工具(https://github.com/osm0sis/mkbootimg)解包boot固件,如下所示。
❑ramdisk固件。
解包ramdisk固件,如下所示。
3.Bare Metal固件逆向
在逆向阶段,Bare Metal固件的逆向比较困难,因为它使用的指令集特殊,没有固定的文件格式,因此我们需要手动设置基地址、分配段等。而使用富操作系统的固件相对来说容易一些,因为这些系统的程序都有标准的文件格式,如ELF格式,其中定义了基地址、指令集、符号等各种信息。通常,类似IDA这样的工具都会很好地支持这些格式。
因此,本节将主要介绍Bare Metal固件的逆向方法,而不再介绍使用富操作系统的固件的逆向方法。
(1)指令集识别
只要知道芯片型号,就可以知道指令集。此外,Binwalk的参数-A可以自动识别指令集。如果Binwalk识别不出来,则可以尝试使用IDA进行穷举,因为常见的指令集并不多,例如ARM、PowerPC、Tricore、MIPS等。图11-15展示了IDA选择指令集的界面。
图11-15 IDA选择指令集界面
详细的IDA支持指令集列表请参考https://hex-rays.com/products/ida/processors/。
(2)获取固件基地址
Bare Metal固件是一个没有头格式的纯bin文件,因此固件中不包含基地址信息。所以,在进行逆向时,我们需要手动指定基地址。笔者根据个人经验,提供以下几种方法。
1)在有芯片手册的情况下可以查看DataSheet来确定基地址,如图11-16所示,0x80000000是基地址。
2)在没有手册的情况下可以通过一些工具获取,比如rbasefind、basefind.py、binbloom。笔者最推荐的是binbloom,它的原理如下。
❑利用熵值来确定代码段和数据段。
❑收集固件中数据段的兴趣点(比如字符串、数组)。
❑收集固件代码段中对兴趣点的指针引用,并生成候选基址列表。
❑对每个候选基址列表所包含的有效指针数量进行排序,最高的那个可能就是基地址。
图11-16 根据芯片手册获取基地址
3)switch定位法。该方法主要是先找到switch的跳转指令,然后看switch指令的跳转表地址,再通过该地址获取基地址。如图11-17所示,跳转表内的0x2XXX都是函数地址,它减去文件的偏移即基地址。
图11-17 switch跳转表
(3)固件符号恢复
从Flash提取的Bare Metal固件是一个纯bin文件,它没有调试符号,我们通过反编译工具所看到的都是一系列没有名字的函数和变量,如图11-18所示。
图11-18 没有符号的固件
因此,我们通常会想尽一切手段来恢复一些符号,常见的手段有以下几种。
1)log函数恢复法。这是一种常用的逆向方法。在固件中通常都会存在输出信息的函数,比如log和print函数,这些函数在输出时通常会带上函数名,这些函数名也是存在于固件中的。因此,我们可以定位到log函数,对它的所有引用进行搜索,在每个引用处检查log函数所输出的函数名,并将其作为引用log函数的未命名函数的名字。
下面是一个用于在IDA中通过log函数恢复函数名的脚本。
2)BSP(Board Support Package)重编译Diff法。如果我们恰好拥有与该芯片固件相对应的BSP软件包,就可以编译一个包含基础库的演示程序。例如,如果目标芯片是TICC2642,就可以使用TI官方提供的IDE(https://www.ti.com/tool/SMARTRFTM-STUDIO)编译一个演示程序。由于我们自己编译的程序带有符号,因此可以采用Diff法,即对比两个固件相似的函数,并对BSP函数名进行修复。如图11-19所示,使用了bindiff二进制对比工具恢复符号。左侧是原始的固件,没有函数名称;右侧是我们编译的BSP固件,包含符号。将它们进行对比后,我们可以找到相似的函数,并恢复左侧函数的名称。
图11-19 使用bindiff恢复符号
3)CMSIS-SVD符号恢复法。该方法主要用于修复固件访问外设寄存器的符号。对于Bare Metal固件,许多外设寄存器都是以内存地址的形式进行访问的。因此,在进行逆向工程时需要查阅芯片手册并逐个匹配寄存器地址及定义寄存器名称,这会带来很高的人力成本。为了减轻这一负担,可以使用SVD(System View Description)文件来加速这个过程。SVD是一种描述文件,IDA等工具都支持该文件,如图11-20所示。
图11-20 在IDA上使用SVD文件
CMSIS-SVD规范描述了基于ARM Cortex-M处理器的微控制器所包含的系统信息,特别是芯片外设的内存映射寄存器。SVD文件中有着与芯片手册相同的详细信息,包括了外设的高级功能描述,以及内存映射寄存器中单个位字段的定义和用途等。我们可以在https://github.com/posborne/cmsis-svd/tree/master/data上获取相应的SVD文件。
经过对SVD文件的解析,如图11-21所示,可以看到固件中的寄存器名称。
图11-21 经过SVD恢复符号后的结果
(4)固件代码定位
现在,我们已经完成了对固件的初步处理,可以开始逆向工程的操作了。Linux等成熟操作系统的固件逆向工程比较容易,因为它具有标准的库函数、系统调用等,并且具有动态链接机制(例如ELF),会将各个功能划分到不同的文件中,因此实施起来比较轻松。而Bare Metal固件的逆向工程则相对困难一些,因为它没有任何标准的API,底层操作仅限于硬件寄存器访问,并且对一些协议的使用也是自己实现或者通过静态链接完成的,最终编译后的结果是所有的代码都集成在一个大的二进制文件中。因此,对于攻击者来说,如何在Bare Metal的海量无符号汇编代码中找到感兴趣的关键代码尤为重要。接下来,我们将介绍一些在Bare Metal中定位关键函数的方法。
①固件解密流程定位
有些固件是被加密的,但它们无论如何都会有一段未加密的代码,用来解密并加载后续的程序,我们可以称之为init boot。通常,这段解密代码是固件头部的一部分。我们可以通过跟踪复位中断函数找到解密某些数据并将其复制到指定位置的代码,并最终跳转到该位置以执行代码。这段代码具有非常明显的特征,如下所示。
②常见压缩算法定位
在之前的讨论中,我们提到了固件解密的问题。然而,在固件中还有许多数据或代码是被压缩存储的。因此,如果我们想要还原这些数据或代码,就需要识别它们使用的压缩算法。
其中,GZIP是一种常见的压缩算法,经常被用于固件中。我们通常可以通过识别其magic number(魔数)——0x1F和0x8B来发现该算法,具体示例如下。
除了GZIP外,LZSS也是一种常见的压缩算法,它并没有magic number可用于识别。因此,我们需要通过分析其函数逻辑来判断固件中是否使用了该算法。
一般情况下,LZSS会定义一个WINDOW_SIZE,然后根据压缩与否决定是否进行解压。具体实现的示例可以参考图11-22。
除了软件解压外,有些压缩算法也可以通过硬件实现解压,这种方式通常是通过识别协处理器寄存器来实现的,例如下面的p15寄存器。
图11-22 LZSS解压算法逻辑
③任务定位
在实时操作系统(RTOS)中,任务(Task)通常作为一个运行单元,类似于Linux中的进程。因此,在逆向工程过程中,识别任务尤为重要。通过任务,我们可以分析出该固件包含哪些业务方向,还可以跟随任务的代码流程进一步分析其具体逻辑。
通常,任务包含以下几种属性。
其中,任务入口地址和任务名称非常重要,它们提供了关于任务的启动地址和名称的信息。如图11-23所示,这是一个任务结构的数组,包含了多个任务的信息。
图11-23 RTOS中的任务结构
一旦我们找到了这类结构体,就可以搜索其引用,进而找到创建任务的位置,如图11-24所示。
图11-24 在实时操作系统中找到创建任务的位置
④通信代码定位
在进行固件分析时,确定网络通信的代码段非常重要,因为通信与漏洞密切相关。以下是一些常见的通信代码段定位方法。
1)校验和定位法。通常,程序会使用某些校验和算法来验证数据包的完整性,例如CRC、SHA、ParityCheck、CheckSUM等。其中,奇偶校验是一种常见的校验和算法,如下所示,它通过计算数据位中的奇数和偶数个数来实现校验和。
2)寄存器设置定位法。一些底层的通信协议需要设置相应的硬件寄存器,我们可以通过对比芯片手册或者查找代码中设置寄存器的逻辑来判断当前代码是否反映了协议的收发过程。例如,I2C协议需要设置I2C相关的寄存器,如下所示。
首先要设置缓冲区大小。
然后将数据写入data register,即数据寄存器。
最后令寄存器写入1来启动该功能,与启动DMA类似。
(5)固件调试
对于漏洞研究来说,仅使用纯静态逆向是不够的。必须与动态调试相结合,才能更好地分析代码逻辑并发现其中的漏洞。我们通常使用的调试方法分为软件级和芯片级,它们的主要区别在于软件级调试与系统关联紧密,而芯片级调试则与芯片型号关联紧密。除了软件级调试和芯片级调试之外,还有Patch固件调试。下面是更详细的介绍。
①软件级调试
软件级调试指的是固件的操作系统本身支持调试,例如Linux的GDB。这样,我们就可以通过软件的方式对系统内的进程进行调试。首先需要进入目标系统获取shell,通常可以利用串口、USB口或网络等获取shell。获取shell之后,我们可以直接在目标设备上使用GDB进行调试,也可以开启GDB Server进行远程调试。如下是GDB调试进程。
关于更多GDB使用方法,请参考GDB手册:https://sourceware.org/gdb/current/onlinedocs/gdb.html/。
②芯片级调试
芯片级调试一般指MCU自带的调试功能,比如JTAG。Bare Metal系统不存在shell和软件调试机制,因此需要进行芯片级调试,这通常使用JTAG/SWD等方式,例如使用J-Link GDB Server软件进行调试。如图11-25所示,用J-Link连接了芯片的JTAG后,打开J-Link GDB Server调试软件,它会创建一个GDB Server。
图11-25 J-Link连接JTAG
配置好该软件后,我们就可以用GDB进行芯片级调试了。
③Patch固件调试
利用Patch固件调试法,通过在想要调试的位置添加hook代码,并将内容通过GPIO引脚输出,可以在没有软件和芯片级调试的情况下进行调试。首先需要找到一些GPIO引脚,可以通过芯片的DataSheet来查找,如图11-26所示。
图11-26 GPIO的寄存器地址
通过以上的DataSheet,我们可以看到一个芯片拥有非常多GPIO引脚,如图11-27所示,所有标记为GP的引脚都是GPIO引脚。
图11-27 GPIO引脚
然后我们需要修改固件,在想要查看信息的地方进行挂钩(hook),将寄存器等信息通过GPIO输出。下面是一个示例,演示如何在代码中添加hook跳转指令。
在hook点跳转后,需要编写一段shellcode来完成上述输出GPIO的过程。这通常需要以下几个步骤。
❑保存所有寄存器。
❑禁止中断。
❑将数据输出到GPIO。
❑恢复中断。
❑恢复所有寄存器。
❑返回原地址继续执行。
对应代码如下所示。
由于GPIO是原始引脚,为了方便地解析数据,我们可以通过GPIO来输出UART协议的数据。于是需要编写一段输出UART数据的GPIO shellcode,如下所示。
接下来,我们需要将已经修改好的固件刷写回去,并且使用UART转USB工具将其连接到GPIO口上,如图11-28所示。
图11-28 使用UART转USB工具
接下来运行程序,就可以在电脑上输出我们想要的内容了,如图11-29所示,我们得到了程序输出的数据。
图11-29 UART数据输出
至此,Patch固件调试的整个过程结束。如果想要查看其他的信息,重复上面的操作即可。