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: