# EngraverEngraver was a hardware challenge on [Google CTF 2022](https://capturetheflag.withgoogle.com/) that was involved recovering movements of a robotic arm engraving the flag from a pcap dump of commands sent to the device.
For the challenge we were given the mentioned pcap containing usb traffic and two images of the robot arm (one of the robot arm idle, one during the engraving process).
## Taking a look at the PCAPThe provided PCAP file contains a dump of USB traffic from a specific usb device. We can see the typical usb configuration packets being sent at the beginning (host fetching the usb descriptors) and afterwards we see a lot of URB interrupts from the host with HID data attached.![USB traffic](https://i.imgur.com/O8Jz8CF.png)
## Figuring out the hardware partFrom the images, we know that this is some sort of a robotic arm, but we have no idea how it is controlled outside of the fact it communicates over USB HID so first order of business is identifying the device.![Robot arm](https://i.imgur.com/jGJaDvG.jpg)
A bit of googling of keywords like `robot arm` `blue` `arduino` yields some results that look very similar and have a common keyword in them: `xArm`.After researching the lead a bit more, xArm turns out to be a series of robot arm kits with a common controller. Then by searching github we can find [control scripts](https://gist.github.com/maximecb/7fd42439e8a28b9a74a4f7db68281071) that seem to communicate with the arm through USB HID commands.
## Recovering the instructions from PCWith a control script found, we can compare if the instructions it would send match the data we have in the pcap file:```python    def move_to(self, id, pos, time=0):        """        CMD_SERVO_MOVE        0x55 0x55 len 0x03 [time_lsb time_msb, id, pos_lsb pos_msb]        Servo position is in range [0, 1000]        """
        t_lsb, t_msb = itos(time)        p_lsb, p_msb = itos(pos)        self.dev.write([0x55, 0x55, 8, 0x03, 1, t_lsb, t_msb, id, p_lsb, p_msb])```
We can see that when sending a signal to move a servo the computer sends the following static data `55 55 08 03 01` followed by information about the servo and the move it should make.
This lines up with the data we can see in a sample interrupt packet:![Interrupt](https://i.imgur.com/3mTo5La.png)
With the data matching up, we can now write a script to parse the servo movements from the pcap with [scapy](https://scapy.net/).```pythonfrom scapy.utils import RawPcapReaderfrom scapy.layers.usb import USBpcap
for (pkt_data, pkt_metadata,) in RawPcapReader('./engraver.pcapng'):    pkt = USBpcap(pkt_data)    if pkt.function != 9:        continue # Not a USB interrupt    if pkt.dataLength == 0:        continue # Does not contain any data
    data = pkt_data[-pkt.dataLength:]
    length, cmd = data[2], data[3]
    if cmd == 3: # Servo move        t_lsb, t_msb, id, p_lsb, p_msb = data[5], data[6], data[7], data[8], data[9]        time = t_msb << 8 | t_lsb        pos = p_msb << 8 | p_lsb        print(f'[{id}] T:{time:04}; POS:{pos:04}')```
With the dumped moves we can notice that while servos 1, 2 and 3 perform movements, servos 4, 5 and 6 receive the same position data every time.
From this we can assume that they aren't used and we can cut down on the amount of data needed to be visualized.
Another thing to note is that servo 1 only oscillates between 2300 and 2400, a slight movement like that could possibly be used to turn on/off the laser pointer.
## Visualizing the dataNow that we know which servos move and how they are moving, we can try to visualize the data.
I opted for visualizing the data in the form of frames that would hopefully contain each character separately.
To separate the frames from each other we can use the servo 6's position setting as it only seems to get re-set after a couple of movements of servo's 1/2/3 have already been executed
```pythonfrom scapy.utils import RawPcapReaderfrom scapy.layers.usb import USBpcapfrom PIL import Image, ImageDraw
positions = {    2: 1000,    3: 1000,}
img = Image.new('RGB', (1500, 1500), 'white')imgd = ImageDraw.Draw(img)draw = Falseframe = 0
for (pkt_data, pkt_metadata,) in RawPcapReader('./engraver.pcapng'):    pkt = USBpcap(pkt_data)    if pkt.function != 9:        continue # Not a USB interrupt    if pkt.dataLength == 0:        continue # Does not contain any data
    data = pkt_data[-pkt.dataLength:]
    length, cmd = data[2], data[3]
    if cmd == 3: # Servo move        t_lsb, t_msb, id, p_lsb, p_msb = data[5], data[6], data[7], data[8], data[9]        time = t_msb << 8 | t_lsb        pos = p_msb << 8 | p_lsb
        # Skip servos we don't care about        if id not in [1, 2, 3, 6]:            continue
        print(f'[{id}] T:{time:04}; POS:{pos:04}')         # Determine if the laser is turned on        if id == 1:            draw = pos == 2400 # If the position is 2400 we're turning it on
        # If we're drawing, draw a line from the previously saved position to the new one        if draw:            if id == 2:                imgd.line([                    (positions[2], positions[3]),                    (pos - 1000, positions[3])                ], fill='red', width=20)            if id == 3:                imgd.line([                    (positions[2], positions[3]),                    (positions[2], pos - 1000)                ], fill='red', width=20)         print(positions)
        # Servo 6 was re-set so we can save the current frame and re-set the canvas        if id == 6:            draw = False            # Flip the image so it's readable            img = img.transpose(Image.FLIP_LEFT_RIGHT)            img = img.transpose(Image.FLIP_TOP_BOTTOM)            # Save the frame            img.save(f'./imgs/{frame:4}.jpg')
            # Initialize a new image for the next frame            img = Image.new('RGB', (1500, 1500), 'white')            imgd = ImageDraw.Draw(img)            frame += 1         # Save the position of the servos for the future - offset for drawing purposes        positions[id] = pos - 1000 ```
The following code then produces a series of images that contain the movements of the robot arm:![Generated frames](https://i.imgur.com/GGiMTbR.png)
From those, we can read the flag characters (some of the characters have parts split between two frames) remembering that the description mentioned all of them being uppercase: `CTF{6_D3GREES_OF_FR3EDOM}`