How I Hacked my Car Part 4: CAN Bus/Micom Access
If you haven’t read the earlier parts, please do so: Part 1, Part 2, Part 3.
Why⌗
Some time after hacking my head unit I came up with an idea. I wanted to create an application which added more features to the standard key fob. By this I mean by entering a certain code of locks, unlocks, or other button presses on the fob, would do something else in the vehicle like start the engine. This would make the research and work I’ve done legitimately useful to the common user as it would add practical functionality to their vehicle.
Where I Ended Up⌗
While theorizing the best way to do this, I decided it would be beneficial to figure out how the various applications in the system communicate with the rest of the car. I knew this would eventually lead me into how I can read and write to at least one of the vehicle’s CAN buses. If I could access this data I could potentially see the button presses from the fob, or at least see the consequences of the button presses.
Starting Off⌗
To figure out how an app evenutally sends a signal into the CAN bus, I needed to find at least one good example of a function where the CAN bus would be needed so I could trace it back. I decided on ccOS’s HBody library.
As I mentioned in Part 3, ccOS is a joint project made by Hyundai and Nvidia to make a new car operating system which is highly integrated into the vehicle. Some early work of the core components of ccOS, including a couple of libraries is available in D-Audio 2V. These libraries offer numerous functions which can query certain statuses of the car like checking if a door is open, or perform certain actions like setting the temperature of the A/C. For the library to do these things, it inevitably needs to access the CAN bus. So, I started by reverse engineering the HVehicle library found at /usr/lib/libHVehicle.so.1.0.0.
The method I chose to investigate was requestDoorLock() on HBody. I found it’s implementation method called “ccos::vehicle::general::HBody::HBodyImpl::requestDoorLock” in the HVehicle library.
This method called another function named “requestRemoteControlVehicle”, which eventually called a method on the same name on the HBodyGDBus.
GDBus?⌗
Ok, what is a “GDBus”?
I Googled the term and it looks like “GDBus” is “a specific implementation of D-Bus”.
Great that clears things up.
What’s a “D-Bus”?
One more Google later and I learned D-Bus is short for “Desktop Bus” and is a messaged based communication mechanism that allows different processes on a computer to talk to each other. Neat.
So it looks like some other process must be receiving this “requestRemoteControlVehicle” D-Bus method call and that process communicates with the CAN Bus.
I pulled out one of my favorite tools, Agent Ransack(A super cool and fast file search tool for Windows) and searched for the string “requestRemoteControlVehicle” in my head unit’s firmware.
Agent Ransack found 3 files which contained the method name, automotivefw, HBody.h, and libHVehicle.so.1.0.0. I already knew of the header file itself and it’s library, so it must be in automotivefw!
I searched for the method name and found HBodyStubImpl::requestRemoteControlVehicle(). Within it, I found multiple calls to the HBodyStubImpl::sendPacket() method. I guessed that this is what sends data to the CAN bus.
I went into that function and found it called MicomService::sendPacket(), which called into MicomPacketRunner::sendPacket(), which called into MicomPacketRunner::rawsend(), which finally used the native send() function to actually send the data. I love programming and its consequences.
The method send() is used to send some buffer through a socket. This meant the CAN bus is either directly or indirectly accessable through some socket that is open in the system. All I had to do is find out which socket.
Since the send function requires a socket to be opened using the socket() function, I searched for uses of it. There was only a single call to socket() in automotivefw, the constructor for MicomPacketRunner:
The socket() method has 3 parameters: domain, type, & protocol. The domain was set to 1 which indicates it is a AF_UNIX/Unix domain socket. Based on the string “micom_mux” in the _socket_make_sockaddr_un() call I guessed that the socket I was looking for was a Unix domain socket named “micom_mux”.
When I was first playing around with my backdoor that I created in Part 2, I saved the output of the netstat command. I looked through it and found multiple connections to the “@micom_mux” socket. The “@” in the name means the socket is held in an abstract namespace which isn’t in the filesystem.
So now I needed to access that socket, what better way to do that then using socat? socat is a powerful relay utility built into linux which allows you to relay data between two connections. I decided the easiest way to get the data would be to pipe the output from the @micom_mux socket into my flash drive, which I could copy and analyse later.
I “cd"ed into my flash drive and I ran the command:
socat ABSTRACT-CLIENT:micom_mux STDIO > micomOutput
and waited for a bit. After a minute or so I killed the process and pulled out my flash drive to look at the file. I opened it up and quickly found it was completely empty. I retried the command and tried a few things in the car like opening and locking the doors, then stopped socat and running “sync”. I looked at the file and yet again it was completely empty.
The socket must not automatically send the CAN bus data through it. Maybe it needed some kind of starting packet or secret to start listening in on the data?
The Magic Packet⌗
I started search for this magic packet by looking at every application which talked to the “micom_mux” socket. I found that every app sent a specific packet once it connected to the micom_mux socket.
I found at least 6 apps/libraries that used micom_mux:
- app-logic-nmode:
- Used for N Mode which is an app which allow users to monitor and tune certain advanced settings like the Launch RPM or traction control in Hyundai N vehicles.
- CANManager:
- Used by BlueLink to talk to the CAN bus.
- RDOPacketRunner:
- Used by the radio app.
- HevService:
- Used to monitor hybrid vehicle stats.
- EvService:
- Used to monitor electric vehicle stats.
- Automotivefw:
- The core framework that most apps talk to to control the vehicle.
Each of these apps had different Magic packets:
App | Magic Packet |
---|---|
app-logic-nmode | FF8AFFF3FFFF00038ACA00 |
CANManager | FF8AFFF3FFFF00028E00 |
RDOPacketRunner | FF8AFFF3FFFF0003879000 |
HevService | FF8AFFF3FFFF00028800 |
EvService | FF8AFFF3FFFF000388C200 |
Automotivefw | FF8AFFF1FFFF00018A |
Now I just had to figure out what these meant. By comparing these packets I did notice a few patterns:
- They all started with FF8AFF
- Which is followed by either a F1 or F3
- Then FFFF
- A length (In this case 0001, 0002, or 0003)
- n amount of bytes of the length previously, most end with 00
I wasn’t entirely sure on what to make of this information, so I decided to try to find what application is actually receiving these packets to find out more.
micomd⌗
I found an application called “micomd” in /usr/bin/ which appeared to be the source of the socket. After reverse engineering it I found out the app followed this flow:
- Connects to “/dev/tcc_ipc”. (Where the micom data comes from)
- Creates the “micom_mux” abstract unix socket.
- Waits for a new client.
- Once a client joins it will start reading a packet.
- It will check if it is a Magic Packet by seeing if the 2nd byte is 8A and the 3rd byte is FF.
- Then it checks the 4th byte
- F1: It is an AutomotiveFW packet, it reads the rest of the packet and sends out a special hardcoded packet to the CAN bus.
- F2: It is an AutomotiveFW packet, it reads the rest of the packet
- F3: It is a different app’s packet, it reads the rest of the packet
This explained a couple of things, but I still didn’t know what the entire packet meant, especially the variable length part. But, while looking at micomd I saw that it is heavily logged, so I decided to check that out.
While looking at micom related logs from my previous log dumps I found this:
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
Hmm, it looked like the last log entry here was a micom packet and the logs above it had it parted out with names.
This might have been what I needed. It looks like the packet format is:
- Always FF (Byte)
- SID (Byte)
- RID (Byte)
- Type: (Byte)
- Function (Int16, Big Endian)
- Payload Length (Int16, Big Endian)
- Payload (Array of {Payload Length} Bytes)
I also found some more logs related to the micom:
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
It looks like a function called checkDiagState() sends out a packet with a SID of 87 and a RID of 03 and receives a packet back with the SID and RID reversed. I guessed SID was a Sender ID and RID was a Receiver ID.
While looking at all of the logs I noticed that every RID I saw was in the payload of the magic packets I found earlier:
Example Packet:
MicomD : ReceivedData[SUCCESS]: m => c : : ff 03 87 01 83 5b 00 05 01 22 2e 00 aa
App | Magic Packet |
---|---|
RDOPacketRunner | FF8AFFF3FFFF0003879000 |
Which might mean the magic packet is used to subscribe to packets on from the micom which have a ceratin RID set.
Ok, since I probably knew what the magic packet does, I could now use socat to try to read some real data. I used the following command to use socat to open the micom socket, send the magic packet from CANManager (FF8AFFF3FFFF00028E00) and then write everything it receives to a file on my flash drive:
printf "\xFF\x8A\xFF\xF3\xFF\xFF\x00\x02\x8E\x00" | socat ABSTRACT-CLIENT:micom_mux STDIO > micomOutput
After letting it run for a minute I closed the socat process, ran sync, and pulled out my flash drive. This time I had some data, now I just had to read it some way.
Reading The Data⌗
To get a basic layout of the data I used a the hex editor 010 to view the data. The first packet I captured was:
- Packet Start: FF
- SID: 0B
- RID: 8E
- Type: 01
- Function: 8BC7
- Payload Length: 0100 (256)
I guess I captured some large packets, which might make it hard to see and highlight where one ends and another begins. But luckily, 010 has a super handy tool which helped me with my problem: Templates.
Templates are a super cool feature which allow you to write a C/C++ struct like layout and have 010 turn it into more readable/parsable data.
//------------------------------------------------
//--- 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;
This template I created does this: Declares the file represents a struct called MicomDataStream. MicomDataStream contains many MicomPackets and it will keep reading MicomPackets until it reaches the end of the file (!FEof()). MicomPacket contains 7 fields: FF, sid, rid, type, func, payloadLength, and payload.
Because 010 was able to parse every packet I received I could see that I received 1070 packets in total, and all of them had a 256 byte payload.
The Numbers Mason, What do they Mean‽⌗
I could now tell where one packet ends and another begins, but the data inside of them was still unknown to me.
I am a programmer at heart so I decided that I would write a program which takes in a data stream of these packets and then logs/decodes them live.
I ended up creating a C# program which listens on a TCP port for this data stream and parses it.
I used the following command to relay the traffic from @micom_mux to my packet decoder server which was running on my laptop:
socat ABSTRACT-CLIENT:micom_mux TCP4:192.168.0.3:6999
Then I started to play around with it. I used my program to send a magic packet with every possible RID so I could listen to everything, then I figured out the RIDS that didn’t seem like noisy “garbage” and then isolated a couple of useful packets.
One of the ones I found were the packets which indicated if the driver door opened or closed:
- Packet Start: FF
- SID: 44
- RID: C1
- Type: 00
- Function: C403
- PayloadLength: 02
- Payload: (00 if closed 01 if open) ?? (Random byte? Checksum? Timing?)
I continued dumping the micom data while trying to do various actions. Slowly but surely I figured out a few more packets.
I even made a little UI to show the status of the doors:
I may release this application sometime in the future once I get a chance to clean it up and add some more packet types to it.
Once I got the hang of reading this data I went to see if I could get any data from my key fob, unfortunately I could not. I could not read button presses from the fob or see if the fob was within range. The only thing I could read was the status of the door’s locks if they changed. This severely limited my ability to add new functionality to the fob.
You can’t win every time⌗
While my inital research into the micom wasn’t very fruitful for my intended application, it was still nice to get a breif glimspe into how the head unit, and the car itself worked. For now I will stop looking into the micom system. Besides, there is a new firmware update to hack!
Further Reading⌗
If this post did not quite scratch the itch of reading a deep dive into the micom system, try reading Reversing Kia Motors Head Unit to discover and exploit software vulnerabilities by Gianpiero Costantino & Ilaria Matteucci. The paper goes into a vulnerability Gianpiero & Ilaria found in Kia head units that exploit a similar micom process. They go pretty deep into their findings and it is overall a good read.