Skip to content

Instantly share code, notes, and snippets.

@lposadskov
Last active July 18, 2022 13:37
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save lposadskov/901ccbb0ba63ffb066be58d939433026 to your computer and use it in GitHub Desktop.
Save lposadskov/901ccbb0ba63ffb066be58d939433026 to your computer and use it in GitHub Desktop.
Engraver Solution

ENGRAVER Writeup

GoogleCTF2022 - 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:

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:

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:

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:

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')

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}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment