[HITCON CTF 2018] – EV3 Series

I got few hours on Sunday 21st to play HITCON CTF 2018. I enjoyed the challenges I attempted. Presenting write-up for EV3 Series.

EV3-Basic:

No time for write-up, read my code and have fun!

import json
import operator

'''
Have to read documentation and see how opUI_DRAW worked
840501xxxxyyyyff
opcode text black xcord ycord char
Communication happens from localhost ethernet -> ev3
'''


# Taken from https://stackoverflow.com/questions/10664856/make-dictionary-with-duplicate-keys-in-python
class DictList(dict):
    def __setitem__(self, key, value):
        try:
            # Assumes there is a list on the key
            self[key].append(value) 
        except KeyError: # if fails because there is no key
            super(DictList, self).__setitem__(key, value)
        except AttributeError: # if fails because it is not a list
            super(DictList, self).__setitem__(key, [self[key], value])

blk1=""
blk2=""
blk3=""
blk4=""
dict1 = DictList()
dict2 = DictList()
dict3 = DictList()
dict4 = DictList()

with open('data_ev3_1.1') as f:
    data = json.load(f)

for j in xrange(0, 4):
    data_dump=""
    stack=""
    for i in xrange(0, len(data)):
        tmp = "".join(data[i]["_source"]["layers"]["data"]["data.data"].split(":"))
        if j ==0:
            #print "[+] Retrieving block 1"
            if '2884' in tmp:
                meh = tmp.find('2884')
                beep = tmp[meh+4:][:2]
                xcord = tmp[meh-4:][:4]
                if '00' in xcord[:2]:
                    xcord = tmp[meh-6:][:6]
                xcord_int = int(xcord, 16)
                beep_nice = beep.decode('hex')
                if beep_nice in stack:
                    beep_nice = beep_nice+"#"
                stack +=beep_nice
                dict1[beep_nice] = xcord_int
        elif j == 1:
            #print "[+] Retrieving block 2"
            if '3684' in tmp:
                meh = tmp.find('3684')
                beep = tmp[meh+4:][:2]
                xcord = tmp[meh-4:][:4]
                if '00' in xcord[:2]:
                    xcord = tmp[meh-6:][:6]
                xcord_int = int(xcord, 16)
                beep_nice = beep.decode('hex')
                if beep_nice in stack:
                    beep_nice = beep_nice+"#"
                stack +=beep_nice
                dict2[beep_nice] = xcord_int
                #print data_dump
        elif j == 2:
            #print "[+] Retrieving block 3"
            if '4484' in tmp:
                meh = tmp.find('4484')
                beep = tmp[meh+4:][:2]
                xcord = tmp[meh-4:][:4]
                if '00' in xcord[:2]:
                    xcord = tmp[meh-6:][:6]
                xcord_int = int(xcord, 16)
                beep_nice = beep.decode('hex')
                if beep_nice in stack:
                    beep_nice = beep_nice+"#"
                    count +=1
                stack +=beep_nice
                dict3[beep_nice] = xcord_int
                #print data_dump
        elif j == 3:
            #print "[+] Retrieving block 4"
            if '5284' in tmp:
                meh = tmp.find('5284')
                beep = tmp[meh+4:][:2]
                xcord = tmp[meh-4:][:4]
                if '00' in xcord[:2]:
                    xcord = tmp[meh-6:][:6]
                xcord_int = int(xcord, 16)
                beep_nice = beep.decode('hex')
                if beep_nice in stack:
                    beep_nice = beep_nice+"#"
                stack +=beep_nice
                dict4[beep_nice] = xcord_int
                #print data_dump

print dict1
sorted_dict1 = sorted(dict1.items(), key=operator.itemgetter(1))
for i in xrange(0, len(sorted_dict1)):
    blk1 += sorted_dict1[i][0]

sorted_dict2 = sorted(dict2.items(), key=operator.itemgetter(1))
for i in xrange(0, len(sorted_dict2)):
    blk2 += sorted_dict2[i][0]

sorted_dict3 = sorted(dict3.items(), key=operator.itemgetter(1))
for i in xrange(0, len(sorted_dict3)):
    blk3 += sorted_dict3[i][0]

sorted_dict4 = sorted(dict4.items(), key=operator.itemgetter(1))
for i in xrange(0, len(sorted_dict4)):
    blk4 += sorted_dict4[i][0]


x = blk1.replace("#", '')
y = blk2.replace("#", '')
z = blk3.replace("#", '')
zz = blk4.replace("#", '')

print x+y+z+zz
#hitcon{m1nd5t0rm_communication_and_firmware_developer_kit}
#Human interaction was to include 'e' in firmwar due to 3 duplication issue. Didn't have time during CTF to optimize solver

FLAG: hitcon{m1nd5t0rm_communication_and_firmware_developer_kit}

EV3-Scanner:

We first of all have to read the firmware documentation here and here.

The image makes it evident that Gyro sensor is attached to Lego EV3 and it runs on a white mat with flag written in black.

To identify the communication, we will analyse the documentation. Part 4.2.3 in this document page 23 says that the op-code will be opINPUT_DEVICE. We will find that in the other documentation which will provide us breakdown.

Instruction opInput_Device (CMD, …)
Opcode 0x99

Example payload: 99 1d 00 02 00 02 01 60
length = 8 Bytes
99 = Opcode
1d = READY_SI
00 = Layer number 0
02 = Port Number of Sensor
00 = Type (default)
02 = Mode (default)
01 = Returned values

It looks like this payload is sent as a request to read the values from sensor I think and the EV3 will respond it.

Now here we are looking at the returned values from the sensor sent from EV3 to localhost ethernet. It seems using # for black and <space> for white will recreate the image in ASCII Art.

For reading response we can load the pklg into wireshark and use the following filter:

btrfcomm && packetlogger.type==0x03 && data

Select all packets (CTRL+SHIFT+M) and dump it using 'Export Packet Dissection -> As JSON -> ev3-scanner.json'. Hence, the communication will be from LegoSyst -> localhost Ethernet this case as values taken from robot will be sent of.
The response data variations are minute, I mapped it like this:

00 c0 80 
00 80 3f
+ + 45 (1st + is faster, 2nd is increment)
00 c0 80
00 80 3f
- - 45
00 c0 40
00 80 3f
+ + 45 (1st + is faster, 2nd is increment)

This looks like the ++45 means robot is making 180 degree U-Turn towards Right hand side.  -- 45 is to nullify, making 180 degree U-Turn towards Left hand side and c0 80 , c0 40 means White color read whereas 80 3f means black color read

>>> int('c0', 16), int('80',16)
(192, 128)
>>> int('80', 16), int('3f',16)
(128, 63)

So, if the color is black then seems sensor value will be down, where intensity of reflected light will be higher on white surface.
—-> 1
<—– 2
——-> 3
…………
——–> 11
So, robot will traverse 11 times on the mattress.

import json

'''
A pretty hacky solution written during the ctf, warning - debug comments and prints are present 
'''

with open('ev3-scanner.json') as f:
    data = json.load(f)

alert = 0
total_turn = 0

first_round = ""
second_round = ""
third_round = ""
fourth_round =""
fifth_round = ""
six_round =""
seven_round=""
eight_round = ""
nine_round = ""
ten_round = ""
eleven_round = ""

for i in xrange(0, len(data)):
    tmp = "".join(data[i]["_source"]["layers"]["data"]["data.data"].split(":"))
    if len(tmp) == 18:
        i = 1
        identifier1 = tmp[12:][0:2]
        identifier2 = tmp[12:][2:4]
        identifier3 = tmp[12:][4:6]

        if identifier3 == '45':      # Likely 180 U Turn
            alert = 1
            continue

        elif identifier2 == 'c0' or identifier3 == '40' and identifier1 == '00': # Likely white
            if alert == 1:      # Turn has been taken
                print "[+] Turn was taken"
                alert = 0
                total_turn += 1
                if total_turn == 0:
                    first_round += " "
                elif total_turn == 1:
                    second_round += " "
                elif total_turn == 2:
                    third_round += " "
                elif total_turn == 3:
                    fourth_round += " "
                elif total_turn == 4:
                    fifth_round += " "
                elif total_turn == 5:
                    six_round += " "
                elif total_turn == 6:
                    seven_round += " "
                elif total_turn == 7:
                    eight_round += " "
                elif total_turn == 8:
                    nine_round += " "
                elif total_turn == 9:
                    ten_round += " "   
                elif total_turn == 10:
                    eleven_round += " "                                                                                                                                                                                
            if total_turn == 0:
                first_round += " "
            elif total_turn == 1:
                second_round += " "
            elif total_turn == 2:
                third_round += " "
            elif total_turn == 3:
                fourth_round += " "
            elif total_turn == 4:
                fifth_round += " "
            elif total_turn == 5:
                six_round += " "
            elif total_turn == 6:
                seven_round += " "
            elif total_turn == 7:
                eight_round += " "
            elif total_turn == 8:
                nine_round += " "
            elif total_turn == 9:
                ten_round += " "
            elif total_turn == 10:
                eleven_round += " "                

        elif identifier2 == '80' and identifier1 == '00':    # Likely black 
            if alert == 1:
                print "[+] Turn was taken"
                alert = 0
                total_turn += 1
                if total_turn == 0:
                    first_round += "#"
                elif total_turn == 1:
                    second_round += "#"
                elif total_turn == 2:
                    third_round += "#"
                elif total_turn == 3:
                    fourth_round += "#"
                elif total_turn == 4:
                    fifth_round += "#"
                elif total_turn == 5:
                    six_round += "#"
                elif total_turn == 6:
                    seven_round += "#"
                elif total_turn == 7:
                    eight_round += "#"
                elif total_turn == 8:
                    nine_round += "#"
                elif total_turn == 9:
                    ten_round += "#"
                elif total_turn == 10:
                    eleven_round += " "            
            if total_turn == 0:
                first_round += "#"
            elif total_turn == 1:
                second_round += "#"
            elif total_turn == 2:
                third_round += "#"
            elif total_turn == 3:
                fourth_round += "#"
            elif total_turn == 4:
                fifth_round += "#"
            elif total_turn == 5:
                six_round += "#"
            elif total_turn == 6:
                seven_round += "#"
            elif total_turn == 7:
                eight_round += "#"
            elif total_turn == 8:
                nine_round += "#"
            elif total_turn == 9:
                ten_round += "#"
            elif total_turn == 10:
                eleven_round +="#"         


print "[+] Total turn taken was "+str(total_turn)
print first_round
print second_round[::-1]
print third_round
print fourth_round[::-1]
print fifth_round
print six_round[::-1]
print seven_round
print eight_round[::-1]
print nine_round
print ten_round[::-1]
print eleven_round

print "*************************************************************************************"
print len(first_round)
print len(second_round)
print len(third_round)
print len(fourth_round)
print len(fifth_round)
print len(six_round)
print len(seven_round)
print len(eight_round)
print len(nine_round)
print len(ten_round)
print len(eleven_round)
#hitcon{EV3GYROSUCKS}
#The ascii art isn't that proper but readable.

 

Output ASCII Art:

FLAG: hitcon{EV3GYROSUCKS}

Twitter

Leave a comment