Tags: laser wireshark hid 

Rating: 5.0

## Official description

> You can see pictures of a robot arm laser engraver attached.
> Can you figure out what it is engraving?
>
> Note: the flag should be entered all in upper case. It contains underscores but does not contain dashes.
>
> Good luck!

We are given a ZIP file containing
[engraver.pcapng](https://github.com/google/google-ctf/raw/66de2426aaf3e37e4314714d1eb588d5804c62d6/2022/hardware-engraver/attachments/engraver.pcapng),
[robot.jpg](https://raw.githubusercontent.com/google/google-ctf/66de2426aaf3e37e4314714d1eb588d5804c62d6/2022/hardware-engraver/attachments/robot.jpg)
and
[robot_engraving.jpg](https://raw.githubusercontent.com/google/google-ctf/66de2426aaf3e37e4314714d1eb588d5804c62d6/2022/hardware-engraver/attachments/robot_engraving.jpg)

![robot_engraving.jpg showing a 6-axis robot drawing G letter with a laser pointer](https://blog.nanax.fr/assets/images/hardware-engraver/robot_engraving.jpg)
robot_engraving.jpg showing a 6-axis robot drawing G letter with a laser pointer

## 1. Exploration

### USB capture

Let's start by opening `engraver.pcapng` in Wireshark. We discover USB traffic.
As the capture was started before connecting the device, it contains the USB
device initialization:
- `GET DESCRIPTOR DEVICE` response indicates a [STMicroelectronics LED badge, mini LED display, 11x44](https://linux-hardware.org/index.php?id=usb:0483-5750) (ID `0x0483:0x5750`).
- `GET DESCRIPTOR CONFIGURATION` response indicates that this device only has one [USB HID interface](https://en.wikipedia.org/wiki/USB_human_interface_device_class) with no standard subclass (`0x00`).
- Then the host fetches some USB descriptors string:
- `0x01`: MindMotion SOC Solutions
- `0x02`: Hiwonder
- `0x03`: MM32F103RB

The remaining packets of the capture correspond to HID data transfers as
Wireshark does not include a dissector for this device.

### Robot arm identification

The USB capture indicates `MindMotion SOC Solutions Hiwonder MM32F103RB`.
The pictures [robot.jpg](https://raw.githubusercontent.com/google/google-ctf/66de2426aaf3e37e4314714d1eb588d5804c62d6/2022/hardware-engraver/attachments/robot.jpg)
and
[robot_engraving.jpg](https://raw.githubusercontent.com/google/google-ctf/66de2426aaf3e37e4314714d1eb588d5804c62d6/2022/hardware-engraver/attachments/robot_engraving.jpg)
show a 6-axis blue robot holding a laser pointer.

With a bit of online search, we find that this robot is the
[LeArm by Hiwonder](https://www.hiwonder.com/store/learn/2.html).
More search indicates that we can interface with robot with
<https://github.com/ccourson/xArmServoController> library.

## 2. Proposed solution

We extract the HID data from the USB capture and dissect it by analysing
[xArmServoController](https://github.com/ccourson/xArmServoController)
source-code.
We notice that only 3 servomotors are used to draw, which means we don't need
to compute a reverse kinematic model of the arm. We finally plot the letters
the robot was drawing during the USB capture and get the flag.

### HID data dissection

We write a Python script to extract HID data using Scapy.
We skip a fixed header defined in [xArmServoController/xarm/controller.py](https://github.com/ccourson/xArmServoController/blob/33cd7a0bd924c60a758ed69e85294620e72abc5b/Python/xarm/controller.py#L143).

```Python
from scapy.all import rdpcap

p = rdpcap("engraver.pcapng")
for pkt in p:
l = pkt.fields["load"]
if l[14] != 0x09 or l[15] != 0x00 or l[16] != 0x00:
continue

# Skip 5555080301 header (SIGNATURE, SIGNATURE, length, CMD_SERVO_MOVE, 1)
c = l[27+5:27+5+5]
if not c:
continue # ignore empty

duration = c[0] + (c[1] << 8)
servo = c[2]
position = c[3] + (c[4] << 8)
print(duration, servo, position)
```

We get 418 combinations of durations, servomotor identifier and position:
```
500 1 2300
1500 2 1300
1500 3 1700
[...]
1500 4 2500
1500 5 1600
1500 6 1500
```

### (Not) computing inverse kinematic

A first look at the previous data can be scary as we notice that all 6
servomotors are being driven. This means that we might need to compute a inverse
kinematic model of the arm to get the pointer position and orientation from
this data.
After looking more closely, we notice 43 repetitions of a position reset
pattern:
```
500 1 2300
1500 2 1300
1500 3 1700
1500 4 2500
1500 5 1600
1500 6 1500
```

Let's replace this pattern by `0`.
**We now notice that only servomotors 1, 2 and 3 are being driven.**

Servomotor 1 is always moving between position `2300` and `2400`. A look at
the LeArm documentation reveals that it is the gripper. On the provided pictures
this gripper is positioned on the laser pointer button.
**Servomotor 1 turns on and off the laser pointer.**

We can consider a 2-axis drawing robot using the pan and tilt of the gripper.

### Plotting the letters

We write the following Python script that computes the arm state after each
command and then plot each succession of movements between resets:
```Python
import matplotlib.pyplot as plt

def plot_data(states, n, laser_btn=True):
# Remove points when laser is off
for i in range(len(states[1])):
if states[1][i] == 2300 and laser_btn:
states[2].pop(i)
states[3].pop(i)

# Inverse X axis
states[2] = [-x for x in states[2]]

# Plot
plt.figure()
plt.ylim(1500 - 50, 1700 + 50)
plt.xlim(-1500 - 50, -1300 + 50)
plt.axis('off')
plt.plot(states[2], states[3], linewidth=50)
plt.savefig(f"out/{n}.png")
plt.close()

# Build arm state after each command
states = {}
states[1] = [2300]
states[2] = [1300]
states[3] = [1700]
n = 0
with open("engraver_data_decoded", "r") as f:
for line in f.readlines():
c = line.split()

# Reset between letters
if len(c) < 2:
n += 1
if len(states[1]) > 1:
plot_data(states, n)
states[1] = [2300]
states[2] = [1300]
states[3] = [1700]
continue

duration, servo, position = map(int, c)
if servo != 1:
states[1].append(states[1][-1])
if servo != 2:
states[2].append(states[2][-1])
if servo != 3:
states[3].append(states[3][-1])
states[servo].append(position)
```

This script is imperfect as it does not consider timing and
persistence of vision, but it was enough to flag this challenge.

![Script output with laser_btn=True (top) and laser_btn=False (bottom)](https://blog.nanax.fr/assets/images/hardware-engraver/flag.jpg)

Script output with laser_btn=True (top) and laser_btn=False (bottom)

We recognize "fr3edom" at the end of the flag, which after a bit of thinking led
to `CTF{6_D3GREES_OF_FR3EDOM}`.

Original writeup (https://blog.nanax.fr/post/2022-07-02-hardware-engraver/).