通过网络了解汽车内部的物理构造

优质
小牛编辑
120浏览
2023-12-01

通过网络了解汽车内部的物理构造

现在,在远程攻击后,我们要开始发送CAN信息。为了弄明白要发送哪些CAN信息,我们需要搞清楚吉普切诺发送的信息有哪些独有的特性。这一过程需要不断地尝试和犯错,逆向机械工具,逆向ECU固件。接下来的这一章节,我们就要完成这些工作。

机械工具

和所有的安全研究一样,要想事半功倍,合适的工具很重要。不出意外,我们需要机械工具来处理这辆吉普车。这些机械工具可以在低层级上通过CAN与ECU交互。在这些工具中包含有攻击者可能感兴趣的安全访问秘钥和诊断测试功能。

不过,我们发现这些设备并不是具有软件功能的J2534 标准直通式设备,而是wiTECH生成的专用 软件/硬件系统,价格超过了6700.00美元(约合人民币42513.51元,超过了Tech Authority一年1800美元的订阅费用)。

{%}

图-wiTECH报价

虽然,某些研究可以在不是用诊断设备的情况下进行,但是很多主动测试和ECU解锁都需要分析这些机械工具。我们卖血卖了几周后,最终购买到了诊断这台吉普切诺(以及其他菲亚特-克莱斯勒车型)所需要的系统。

综述

wiTECH工具非常易用,可能是经过了重新设计。你可以观察汽车的各个方面,甚至是用图形来表示这台吉普的网络架构,这些在我们使用wiTECH设备以前是无法发现的。

{%}

图-WiTech软件中显示的2014年款吉普切诺示意图

wiTECH与其他我们以前见过的诊断程序还有另外一项差别,wiTECH系统是用Java写的,而不是C/C++。这样的话,逆向工程就更容易了,因为其友好的名称,并且能够把字节代码反编译成Java源。

{%}

图-wiTECH的重要文件

制造商预置的一项方法给反编译造成了困难,就是使用了字符串混淆,似乎是用Allatori混淆器生成的。如下,在java代码中搜索输出字符串并没有得到什么好结果,因为这些代码都是 “加密的”,并且只能在运行时 “解密”。

{%}

图-wiTECH的字符串混淆

我们在一开始分析一些java字节代码时发现,最简单的方法就是把需要的wiTECH JARs导入到一个java应用中,并使用库中的函数来解密。下面就是我们解密的字符串和打印的结果,正好是 “flash engine is invalidated”(flash引擎失效)。

{%}

图-Eclipse输出的去混淆文本

安全访问

虽然,wiTECH设备是用来收集主动测试,比如,用于启动雨刷器的CAN信息,但是最具吸引力的还是通过分析软件来搞明白其安全访问算法,用于 “解锁”一个ECU来进行再编程或其他权限操作。

再说一次,不同于我们以前研究过的任何诊断软件,wiTECH软件中似乎没有包含任何实际的代码会负责根据用于解锁ECU的种子来生成秘钥。最后,在‘jcanflash/Chrysler/dcx/securityunlock/’ 下的文件中,我们发现某些解锁函数被调用了,调用取决于要重刷的ECU类型。

在静态分析的最后,我们从中发现了一些代码‘/ngst/com/dcx/NGST/vehicle/services/security/SecurityUnlockManagerImp.java’ ,下面的代码就是来自这个位置:

localObject = new ScriptedSecurityAlgorithm(new
EncryptedSecurityU(((ScriptedSecurityMetaData)paramSecurityLevelMetaData
.getScript()));

不过,我们在检查了‘EncryptedSecurityUnlock’ 之后,并没有发现更多关于秘钥生成算法的信息。

{%}

图-加密安全解锁java代码

通过跟踪安全解锁使用的方法,我们找到了位于‘\jcanflash\com\dcx\NGST\jCanFlash\flashfile\odx\data\scripts\unlock’ 中的一个目录,在这里有很多以‘.esu’ 结尾的文件(后来我们才知道.esu代表的是加密安全解锁)。当我们在十六进制编辑器中检查这些文件时,并没有发现任何可读的字符串或内容,对此我们并不惊讶。

{%}

图-wiTECH的加密安全解锁文件

虽然我们没有解锁算法,但是我们却很清楚整个运作过程是什么样的。wiTECH应用会请求ECU来获取种子,在获得种子后,应用会判断ECU的类型,并解密解锁文件,我们认为秘钥的生成算法就在这个解锁文件中。

我们再次检查了“EncryptedSecurityUnlock”构造函数,并发现了下面的信息:

    UC localUC = new UC();
    SecurityUnlockFactoryImp localSecurityUnlockFactoryImp =
          new  SecurityUnlockFactoryImp();
    try
    {
      byte[] arrayOfByte = localUC.d(a);

传递到‘d’函数的字节流和上面的加密数据非常类似,我们去混淆了这个构造函数,得到了满意的结果。你可以看到他们非常精通l33t语言,因为解锁秘钥似乎是“G3n3r@ti0n”。向wiTECH脱帽致礼。

Uc.init(“G3n3r@ti0n”, “MD5”, “”, “BC”, “AES”, new String[] 
{“com.chrysler.lx.UnlockCryptographerTest”, 
"com.dcx.securityunlock.encrypted.EncryptedSecurityUnlock", “”, 
“com.dcx.NGST.jCanFlash.flashfile.efd2.SecurityUnlockBuilderImpTest”});

在运行了“00A6.esu” (如上)上的解密例程后,现在我们能看到这确实是一个用于根据种子生成秘钥的JavaScript。

{%}

图-解密后的javascript解锁文件

在解密了用于解锁ECU的文件后,我们就能够看到javascript脚本了,并可以把脚本的功能移植到Python。不出意外的话,这个算法中还涉及到了一些秘密和简单的位元操作,因为在自动化行业中,这些技术几乎无处不在。在下面的截图中,是我们用来解锁吉普切诺中各种ECU的Python代码,但是很多其他的车型可能也应用了相同的算法。完整的代码可以在’JeepUnlock.py’的内容数据包中找到。

{%}

图-吉普切诺的ECU解锁算法

应该注意,与我们之前研究的福特和丰田车不同,我们实际上不需要安全访问秘钥就可以执行攻击。安全访问算法的唯一作用就是用来重刷ECU,而我们并没有对此进行探索。

PAM ECU逆向

通过利用机械工具,我们能够执行主动测试,并嗅探测试结果。另外,我们还知道了安全算法和秘钥,允许我们执行权限操作。但是,机械工具发送的信息是固定的,也没有使用校验和。我们检查发现ECU之间的流量经常会使用校验和。如果,我们想要自己制作CAN信息(不是简单的回复现有的信息),我们需要理解这些校验和。为此,我们必须观察执行校验和的代码,而这些代码只会出现在ECU本身中。

很多时候,通过观察嗅探得到的CAN流量就足够判断车速、刹车比率和其他的情况。另外,这些CAN信息中最后的数据字节就是校验和。例如,下面的信息就来自一款2010年的丰田普锐斯,这款车就使用了车道维持辅助系统(LKA)。

IDH: 02, IDL: E4, Len: 05, Data: 98 00 00 00 83
IDH: 02, IDL: E4, Len: 05, Data: 9A 00 00 00 85
IDH: 02, IDL: E4, Len: 05, Data: 9E 00 00 00 89

在每条信息中,最后的字节是CAN ID、数据长度和数据字节的一个整数加法校验和(限制为1字节),通过分析几条信息就能想到这一点。我们发现大多数信息不是纵向冗余检查(异或校验和)就是整数加法校验和,但是泊车辅助模块(PAM)使用的校验和与我们之前看到的都不一样。下面的信息就是2014款吉普切诺的PAM模块发送的。

IDH: 02, IDL: 0C, Len: 04, Data: 80 00 06 7F
IDH: 02, IDL: 0C, Len: 04, Data: 80 00 08 D9
IDH: 02, IDL: 0C, Len: 04, Data: 80 00 19 09

PAM信息使用的校验和算法不仅与我们知道的不同,而且也不同于库普曼在论文中介绍的校验和技术和CRC数据完整性技术。我们认为,如果我们能获取到固件并逆向其代码,我们就能识别出校验和算法,这样我们就可以制作任意的信息,让监听CAN总线的ECU认为信息是有效的。

很幸运,wiTECH软件为我们提供了所有必要的信息来网购一个PAM模块,序列号:56038998AJ;我们可以从任何销售MOPAR部件的销售商那里下单。

{%}

图-2014年吉普车的泊车辅助模块

wiTECH工具还能够更新PAM,也提示我们固件可以从网上下载并本地储存到计算机上来执行更新。这点很明确,在调查了运行wiTECH笔记本上的文件系统,我们找到了目录:‘%PROGRAMDATA%\wiTECH\jserver\userData\file\flashfiles’。这个目录下包含着固件缓存,这样软件就不需要在每次刷新事件时,重新下载副本。

我们还不确定哪个文件是哪个,这些文件是如何编码的,所以在重刷两个ECU的过程中,我们捕捉了CAN流量。再对比在文件重刷过程中的数据,我们可以推断出其中一个文件就是泊车辅助模块的更新。我们在文件5603899ah.efd上运行了字符串来查找 “PAM”字符串,结果表明,这个固件更新就是我们要获取的固件。

C:\Jeep\pam>strings 56038998ah.efd | grep PAM
PAM
PAM_CUSW SU 
.\PAM_DSW\GEN\DSW09_PROJECT_gen\api\DTC_Mapping_MID_DTCID_PROJECT.h 
.\PAM_DSW\GEN\DSW09_PROJECT_gen\api\DTC_Mapping_MID_DTCID_PROJECT.h 
.\PAM_DSW\DSW_Adapter\src\DSW4BSW_PDM2NVM.c

注意:你会注意到我们还没有聪明到能根据EFD文件的名称,也就是2014年吉普切诺泊车辅助模块的序列号,来推断出路径是不是正确的。

这个文件本身并不只是一个固件镜像,而是包含了wiTECH软件使用的元数据,其目的并不单一。幸运的是,我们可以通过wiTECH软件中提供的JAR文件来实现特定的方法调用,从而发现真正的字符串偏移和固件的大小。

在导入了合适的类后,下面的调用链会披露真正的起始偏移和固件大小。

String user_file = "C:/Jeep/pam/56038998ah.efd"; 
UserFileImp ufi = new UserFileImp(user_file);
ff.load(ufi);

Microprocessor mps[] = ff.getMicroprocessors(); 
StandardMicroprocessor smp = (StandardMicroprocessor)mps[0];

LogicalBlock lb = smp.getLogicalBlocks()[0];

PhysicalBlockImp pb = (PhysicalBlockImp)lb.getPhysicalBlocks()[0];

System.out.println("Block Len: " + pb.getBlockLength()); 
System.out.println("Block len (uncomp): " + pb.getUncompressedBlockLength()); 
System.out.println("File Offset: " + pb.getFileOffset()); 
System.out.println("Start Address: " + pb.getStartAddress());

上面的输出代码如下:

Block Len: 733184
Block len (uncomp): 733184
File Offset: 3363
Start Address: 8192

现在,我们已经掌握了所有需要的信息来写一个Python脚本,提取固件并开始逆向。

还遗留下的一个主要问题是,我们仍然无法完全确定PAM模块中使用的CPU是什么架构的。最佳的行动方案就是打开PAM模块,通过观察主板上的标志来判断。如果我们能确定芯片标志,那么很可能我们就能够判断出使用的是哪个处理器,并在IDA Pro中开始反汇编固件。

{%}

图-PAM PCB

虽然不容易发现,主要MCU上的标志是D70F3634,我们通过谷歌搜到这是Renesas v850芯片。很幸运,信息娱乐系统上使用的也是这个芯片,所以先前的逆向脚本,技术和工具都可以再用。

现在,我们已经从更新中提取出了固件,并知道了其架构,我们可以通过逆向这个二进制,找到用于计算校验和的函数。在经过了一些讨论后,我们认为其中的一个常量可能经过了异或,从而导致校验和虽然有类似的有效载荷但是区别很大。快速地进行搜索后,我们发现了一个函数异或了一些值,并且似乎具有某些循环。这个函数是一个完美的逆向候选。

{%}

图-PAM校验和算法

我们首先把反汇编语言逆向成了C语言,因为本文的作者之一就是个神经病。这时候,C函数就可以移植到Python进行测试。下面的代码是从反汇编中得到的Python代码。

def calc_checksum(data, length):
    end_index = length - 1
    index = 0
    checksum = 0xFF
    temp_chk = 0;
    bit_sum = 0;

    if(end_index <= index):
        return False

    for index in range(0, end_index):
        shift = 0x80
        curr = data[index]
        iterate = 8

        while(iterate > 0):
            iterate -= 1

            bit_sum = curr & shift;
            temp_chk = checksum & 0x80

            if (bit_sum != 0):
                bit_sum = 0x1C

                if (temp_chk != 0):
                    bit_sum = 1

                checksum = checksum << 1
                temp_chk = checksum | 1
                bit_sum ^= temp_chk
            else:
                if (temp_chk != 0):
                    bit_sum = 0x1D
                checksum = checksum << 1
                    bit_sum ^= checksum

                checksum = bit_sum
                shift = shift >> 1

        return ~checksum & 0xFF

如果你通过“calc_checksum”函数运行从上面PAM信息获取的3字节数据。更重要的是,我们在吉普车上看到所有包含了1字节校验和的CAN总线都是使用了相同的函数。所以,我们获取到的校验和算法适用于所有感兴趣的信息。这个校验和与我们前面遇到的那个相比,更加复杂。

注意:我们还发现了另外两个校验和,并逆向到了C语言,但是任何我们感兴趣的信息都没有使用这两个校验和。这两个算法很类似但是字节长度不同。