Rating:
**ENGRAVER Writeup**
[GoogleCTF2022](https://capturetheflag.withgoogle.com/) - Hardware - Engraver
First of all lets look at what we have in our task archive. There are 3 files, two images and a pcapng file. Images show a robotic arm doing engraving. Let's leave the images for now and look at the pcapng file. We open it with wireshark and start looking through the content. Let's check below packages first:
![](https://gist.githubusercontent.com/lposadskov/901ccbb0ba63ffb066be58d939433026/raw/9d3fd67f2327e73cfa8a9f802b06a07a023d9d22/Engraver_solution2.JPG)
-----
Inside we can see several strings that can help us determine what to look for to solve the challenge. We have the following information available:
* MM32F103RB
* MindMotion SOC Solutions
* Hiwonder
After some googleing we found a similar product to the one we have images of. The product is located on Hiwonder website and is called LeArm. We downloaded the software to control the arm and install it to see what we can find out. Let's look at the interface:
![](https://gist.githubusercontent.com/lposadskov/901ccbb0ba63ffb066be58d939433026/raw/9d3fd67f2327e73cfa8a9f802b06a07a023d9d22/Engraver_solution4.JPG)
-----
As you can see the arm has 6 servomotors (we will need this information later) and we can set a position of each of them. We found an option to save command as an action file. We wrote a couple of actions and saved them to a file to see how it will look like. We opened the resulting .rob file with hex editor and looked at the content:
![](https://gist.githubusercontent.com/lposadskov/901ccbb0ba63ffb066be58d939433026/raw/9d3fd67f2327e73cfa8a9f802b06a07a023d9d22/Engraver_solution6.JPG)
-----
As you can see, command values got encoded as hexedimal values. We can see arm name as first several bytes and then we have our time offset and values for each of the 6 servos. Now let's look again at our pcap file and see if we can find something there that can be similar to this:
![](https://gist.githubusercontent.com/lposadskov/901ccbb0ba63ffb066be58d939433026/raw/9d3fd67f2327e73cfa8a9f802b06a07a023d9d22/Engraver_solution8.JPG)
-----
As you can see we have several packets of length 91 byte with data in them. If we look at HID data of several of those packets we can notice there is a static part and a dynamic part. Let's discard the static part and work with dynamic one. For example f40101fc08 can be decoded as following:
* 01f4 - time offset (we can discard it for this task, since it doesn't influence the result)
* 01 - ID of the servomotor
* 08fc - servomotor current position
Let's decode it and write out first 15 commands:
| Time Offset | Servo ID | Servo Position |
| -------- | -------- | -------- |
| 500 | 1 | 2300 |
| 1500 | 2 | 1300 |
| 1500 | 3 | 1700 |
| 1500 | 4 | 2500 |
| 1500 | 5 | 1600 |
| 1500 | 6 | 1500 |
| 500 | 1 | 2400 |
| 1000 | 2 | 1500 |
| 500 | 3 | 1500 |
| 1000 | 2 | 1300 |
| 500 | 1 | 2300 |
| 1500 | 2 | 1300 |
| 1500 | 3 | 1700 |
| 1500 | 4 | 2500 |
| 1500 | 5 | 1600 |
| 1500 | 6 | 1500 |
As you can see there are 6 servomotors with ID and possition appearing in this list. We can remove motors 4,5,6 since they have constant position through the whole file. For the sake of simplicity let's assume they are a character break where our arm switches to writing next character. This leaves us with motors 1,2,3. Motor 1 has two possible values([2300,2400]). If we look at the arm scheme and arm images we can see that motor 1 closes and releses the clamp, so, probably, it controlls button on the laser engraver. So 2300 will be "laser off" mode and 2400 will be "laser on" mode. Motor 2 controls horizontal movement and motor 3 controlls vertical movement. Knowing all of this let's write a program that will draw our flag:
```
from scapy.all import rdpcap
import matplotlib.pyplot as plt\
import sys,os
from PIL import Image
command_list=[]
image_list=[]
pcap_f = rdpcap(r"./engraver.pcapng")
for p in pcap_f:
load = p.fields["load"]
if load[14] != 0x09 or load[15] != 0x00 or load[16] != 0x00:
continue
c = load[34:37]
if not c:
continue
motor_num = c[0]
offset = c[1] + (c[2] << 8)
if(motor_num==5) or (motor_num==6):
continue
command_list.append(str(motor_num) + " " + str(offset))
plt.figure()
plt.axis('off')
plt.plot([-50,250],[1750,1750],'b', linewidth=5)
plt.plot([-50,250],[1450,1450],'b', linewidth=5)
plt.plot([-50,-50],[1750,1450],'b', linewidth=5)
plt.plot([250,250],[1750,1450],'b', linewidth=5)
laser_on=False
offset_flag=False
cur_coord_x=130
cur_coord_y=170
n=0
drawing=False
for coords in command_list:
if coords[0]=="1":
if coords[2:]=="2400":
laser_on=True
else:
laser_on=False
if coords[0]=="4":
if drawing:
plt.savefig(f"./out{n}.png")
n=n+1
drawing=False
plt.close()
plt.figure()
plt.axis('off')
plt.plot([-50,250],[1750,1750],'b', linewidth=5)
plt.plot([-50,250],[1450,1450],'b', linewidth=5)
plt.plot([-50,-50],[1750,1450],'b', linewidth=5)
plt.plot([250,250],[1750,1450],'b', linewidth=5)
if coords[0]=="2":
new_coord=(1500-int(coords[2:]))
if laser_on:
drawing=True
plt.plot([cur_coord_x,new_coord],[cur_coord_y,cur_coord_y],'r', linewidth=5)
cur_coord_x=new_coord
if coords[0]=="3":
new_coord=int(coords[2:])
if laser_on:
drawing=True
plt.plot([cur_coord_x,cur_coord_x],[cur_coord_y,new_coord],'r', linewidth=5)
cur_coord_y=new_coord
images = [Image.open(f"./out{x}.png") for x in range(0,n)]
widths, heights = zip(*(i.size for i in images))
total_width = sum(widths)
max_height = max(heights)
new_im = Image.new('RGB', (total_width, max_height))
x_offset = 0
for im in images:
new_im.paste(im, (x_offset,0))
x_offset += im.size[0]
new_im.save('combine.png')
```
-----
![](https://gist.githubusercontent.com/lposadskov/901ccbb0ba63ffb066be58d939433026/raw/9d3fd67f2327e73cfa8a9f802b06a07a023d9d22/Engraver_solution_result.png)
Final image is not perfect but we can clearly see the flag. Knowing that the flag should be entered all in upper case and that it contains underscores but does not contain dashes we can combine the final flag. Some letters had to be replaced with numbers to get the final flag. The final flag is CTF{6_D3GREES_OF_FR3EDOM}