来源:programmingwithstyle.com,感谢greenluigi1
如果您还没有阅读前几部分,请先阅读第一部分,第二部分,第三部分。
Why
在黑掉我的车载主机之后的某个时候,我想到了一个主意。我想要创建一个应用程序,为钥匙增加更多功能,在车辆上执行其他操作,比如启动引擎。这将使我所做的研究和工作对普通用户真正有用。
在思考最佳实现方式时,我决定弄清楚系统中的各种应用程序如何与汽车的其余部分通信。我知道这最终将引导我了解如何至少读取和写入汽车的CAN总线。如果我能访问这些数据,我可能会看到钥匙扣上的按钮按压,或者至少看到按钮按压的后果。
Starting Off
为了弄清楚应用程序最终如何将信号发送到CAN总线,我需要找到至少一个CAN总线的函数示例,这样我就可以从源头追踪回去。我决定选择ccOS的HBody库。
正如我在第三部分中提到的,ccOS是由现代和Nvidia联合开发的一个新汽车操作系统项目,它与汽车高度集成。一些ccOS的核心组件的早期工作,包括一些库在D-Audio 2V中是可用的。这些库提供了许多可以查询汽车某些状态的函数,比如检查门是否打开,或者执行某些操作,比如设置空调的温度。为了让库执行这些事情,它不可避免地需要访问CAN总线。所以我首先通过逆向工程来研究在/usr/lib/libHVehicle.so.1.0.0中发现的HVehicle库。
我选择的是HBody上的requestDoorLock()。我发现它的实现方法叫做“ccos::vehicle::general::HBody::HBodyImpl::requestDoorLock”在HVehicle库中。
这个方法调用了另一个名为“requestRemoteControlVehicle”的函数,最终调用了同名的HBodyGDBus上的方法。
我谷歌了一下这个词,看起来“GDBus”是“D-Bus的特定实现”。
又谷歌了一下后,我了解到D-Bus是“Desktop Bus”的缩写,是一种基于消息的通信机制,允许计算机上的不同进程相互通信。真棒。
所以看起来肯定有另一个进程接收这个“requestRemoteControlVehicle” D-Bus方法调用,而那个进程与CAN总线通信。
我拿出了我最喜欢的工具之一,Agent Ransack(一个超酷且快速的Windows文件搜索工具)在我的车载主机固件中搜索了“requestRemoteControlVehicle”字符串。
Agent Ransack发现了3个包含方法名称的文件,automotivefw,HBody.h和libHVehicle.so.1.0.0。我已经知道了头文件本身和它的库,所以它一定在automotivefw中!
我搜索了方法名称,找到了HBodyStubImpl::requestRemoteControlVehicle()。在其中,我发现了多次调用HBodyStubImpl::sendPacket()方法。我猜测这就是向CAN总线发送数据的方法。
我进入那个函数,发现它调用了MicomService::sendPacket(),调用了MicomPacketRunner::sendPacket(),调用了MicomPacketRunner::rawsend(),最后使用了原生的send()函数来实际发送数据。
send()方法被用来通过套接字发送一些缓冲区。这意味着CAN总线直接或间接地可以通过系统中打开的某个套接字访问。我所要做的就是找出哪个套接字。
由于send函数需要使用socket()函数打开一个套接字,我搜索了它的使用情况。在automotivefw中只有一个对socket()的调用,MicomPacketRunner的构造函数:
socket()方法有三个参数:domain, type, & protocol。domain被设置为1,表示它是AF_UNIX/Unix domain socket。基于在_socket_make_sockaddr_un()调用中的“micom_mux”字符串,我猜测我要找的套接字是一个名为“micom_mux”的Unix domain socket。
当我最初尝试在第二部分中创建的backdoor时,我保存了netstat命令的输出。我查看了它,并发现多个连接到“@micom_mux”套接字。名称中的“@”意味着套接字被保存在一个不在文件系统中的抽象命名空间中。
所以现在我需要访问那个套接字,有什么比使用socat更好的方法呢?socat是一个强大的中继实用程序,内置于linux中,允许你在两个连接之间中继数据。我决定最简单的方法是将@micom_mux套接字的输出管道到我的闪存驱动器,我可以稍后复制并分析。
我“cd”进入我的闪存驱动器,然后运行命令:
socat ABSTRACT-CLIENT:micom_mux STDIO > micomOutput
等待了一会儿。大约一分钟后,我终止了进程并拔出闪存驱动器查看文件。我打开它,很快发现它完全是空的。我重试了命令,并在车上尝试了一些操作,比如打开和锁定车门,然后停止socat并运行“sync”。我查看文件,再次发现它完全是空的。
套接字可能没有自动通过它发送CAN总线数据。也许它需要某种起始数据包或秘密才能开始监听数据?
我开始寻找这个魔法数据包,通过查看每个与“micom_mux”套接字通信的应用程序。我发现每个应用程序一旦连接到micom_mux套接字就会发送一个特定的数据包。
我发现至少有6个app/库使用micom_mux:
– 用于N模式,这是一个允许用户监视和调整某些高级设置的应用程序,如启动RPM或现代N车辆的牵引力控制。
每个应用程序都有不同的 Magic packets:
App |
Magic Packet |
app-logic-nmode
|
FF8AFFF3FFFF00038ACA00
|
CANManager
|
FF8AFFF3FFFF00028E00
|
RDOPacketRunner
|
FF8AFFF3FFFF0003879000
|
HevService
|
FF8AFFF3FFFF00028800
|
EvService
|
FF8AFFF3FFFF000388C200
|
Automotivefw
|
FF8AFFF1FFFF00018A
|
现在我只是需要弄清楚这些意味着什么。通过比较这些数据包,我确实注意到了一些模式:
– 长度(在这种情况下是0001、0002或0003)
我不太确定如何处理这些信息,所以我决定尝试找出哪个应用程序实际上正在接收这些数据包,以了解更多信息。
我发现/usr/bin/中有一个名为“micomd”的应用程序,它似乎是套接字的来源。通过逆向工程,我发现应用程序遵循了这个流程:
– 连接到“/dev/tcc_ipc”。(micom数据来自哪里)
– 创建“micom_mux”抽象unix socket。
– 等待新客户端。
– 一旦客户端加入,它将开始读取数据包。
– 它将检查是否是Magic Packet,方法是查看第二个字节是否为8A,第三个字节是否为FF。
– 然后它检查第四个字节
– F1:它是AutomotiveFW数据包,它读取数据包的其余部分,并向CAN总线发送一个特殊的硬编码数据包。
– F2:它是AutomotiveFW数据包,它读取数据包的其余部分
– F3:它是不同应用程序的数据包,它读取数据包的其余部分
这解释了一些事情,但我仍然不知道整个数据包的含义,特别是可变长度部分。但是,我在查看micomd时发现它被大量记录,所以我决定查看一下。
在查看我之前的日志转储中与micom相关的日志时,我发现了这个:
Hev_Packet: sendPacket:0550:send sid:88, rid:0c, type:01, Func:0x0c03, paylL:0, FuncName:HEV_RESET_GRAPH_C
Hev_Packet: sendPacket:0556:send sid:88, rid:0c, type:01, Func:0x0c03, paylL:1, FuncName:HEV_RESET_GRAPH_C
Hev_Packet: send sid:88, rid:0c, type:01, Func:0x0c03, paylL:1, FuncName:HEV_RESET_GRAPH_C
Hev_Packet: S => D micom :ff880c010c030001:74
嗯,看起来这里的最后一个日志条目是一个micom数据包,上面的日志条目有它的名称。
– Function (Int16, Big Endian)
– Payload Length (Int16, Big Endian)
– Payload (Array of {Payload Length} Bytes)
src/VRM/Service/DiagnosticUtils/DiagMainUtil.cpp checkDiagState 00120 checkDiagState(): diag type[3], state[0]
MicomD : send_data: c => m : ff 87 03 01 03 5b 00 02 01 21
MicomD : ReceivedData[SUCCESS]: m => c : : ff 03 87 01 83 5b 00 05 01 28 46 90 58
看起来调用checkDiagState()函数发送了一个SID为87和RID为03的数据包,并接收了一个SID和RID相反的回复数据包。我猜测SID是发送者ID,RID是接收者ID。
在查看所有日志时,我注意到我之前发现的魔法数据包的有效载荷中都有每一个RID:
MicomD : ReceivedData[SUCCESS]: m => c : : ff 03 87 01 83 5b 00 05 01 22 2e 00 aa
有一个RID为87,在我的RDOPacketRunner中
App |
Magic Packet |
RDOPacketRunner
|
FF8AFFF3FFFF0003879000
|
这可能意味着magic packet用于订阅来自micom的数据包,这些数据包设置了特定的RID。
好的,既然我可能知道魔法数据包的作用了,现在我可以使用socat尝试读取一些真正的数据。我使用以下命令使用socat打开micom套接字,发送CANManager的魔法数据包(FF8AFFF3FFFF00028E00),然后将它接收到的所有内容写入我的闪存驱动器上的文件:
printf "xFFx8AxFFxF3xFFxFFx00x02x8Ex00" | socat ABSTRACT-CLIENT:micom_mux STDIO > micomOutput
运行了大约一分钟,我关闭了socat进程,运行了sync,然后拔出了我的闪存驱动器。这一次我有了数据,现在我只需要某种方式来读取它。
为了获得数据的基本布局,我使用了十六进制编辑器010来查看数据。
我猜我捕获了一些大的数据包,这可能会使查看和突出显示一个数据包在哪里结束和另一个数据包在哪里开始变得困难。但幸运的是,010有一个非常方便的工具帮助我解决了这个问题:Templates。
Templates是一个超级酷的功能,允许您编写类似C/C++结构体的布局,并让010将其转换为更易于阅读/解析的数据。
//------------------------------------------------
//--- 010 Editor v12.0.1 Binary Template
//
// File: MicomPacketTemplate
// Authors: greenluigi1
// Version: 1.0
// Purpose: Decode raw data stream from micomd process on D-Audio 2V systems
// Category:
// File Mask:
// ID Bytes:
// History:
//------------------------------------------------
BigEndian();
struct
{
while(!FEof())
{
struct
{
byte FF;
byte sid;
byte rid;
byte type;
ushort func;
ushort payloadLength;
byte payload[payloadLength];
} MicomPacket;
}
} MicomDataStream;
这个我创建的模板这样做:声明文件代表一个名为MicomDataStream的结构体。MicomDataStream包含许多MicomPackets,它将继续读取MicomPackets,直到到达文件末尾(!FEof)。MicomPacket包含7个字段:FF,sid,rid,type,func,payloadLength和payload。
因为010能够解析我收到的每个数据包,我可以看到我总共收到了1070个数据包,它们都有256字节的有效载荷。
The Numbers Mason,What do they Mean‽
我现在可以告诉一个数据包在哪里结束和另一个数据包在哪里开始,但数据包里面的数据对我来说仍然是未知的。
我本质上是一个程序员,所以我决定编写一个程序,它接受这些数据包的数据流,然后实时记录/解码它们。
最终我创建了一个C#程序,它在一个TCP端口上监听这个数据流并解析它。
我使用以下命令将@micom_mux的流量转发到我的包解码服务器,该服务器运行在我的笔记本电脑上:
socat ABSTRACT-CLIENT:micom_mux TCP4:192.168.0.3:6999
然后我开始摆弄它。我使用我的程序发送一个带有每个可能RID的魔法数据包,这样我可以监听所有内容,然后我弄清楚了哪些RID看起来不像嘈杂的“垃圾”,然后隔离了几个有用的数据包。
我找到的一个是我找到的数据包指示驾驶员车门是打开还是关闭的:
– Payload: (00 if closed 01 if open) ?? (Random byte? Checksum? Timing?)
我继续转储micom数据,同时尝试执行各种操作。慢慢地,我弄清楚了一些更多的数据包。
一旦我有机会清理它并添加一些更多的数据包类型,我可能会在未来的某个时候发布这个应用程序。
一旦我掌握了读取这些数据的技巧,我就去看看是否能够从我的钥匙扣获取任何数据,不幸的是,我不能。我无法读取钥匙扣上的按钮按压,也看不到钥匙扣是否在范围内。我只能读取如果车门锁的状态发生变化,这严重限制了我为钥匙扣添加新功能的能力。
虽然我对micom的初步研究并不是很有成效,但能够短暂地窥见车载主机以及汽车本身的工作原理还是很好的。现在,我将停止研究micom系统。此外,还有一个新的固件更新要分析!
原文始发于微信公众号(安全脉脉):HowIHackedMyCar 2021款 现代IONIQ (五)CAN Bus分析