Tags: misc 

Rating: 3.0

You're given a picture of a Lego Mindstorms EV3 display block with the flag partially displayed on it. You're also given a macOS PacketLogger file.

If you open the PacketLogger file, you'll find a capture of packets as they were being sent back and forth to the device. You can export that to a format that's easier to write a parser for via File -> Export -> Payload as Text...

Once you do that, you can use the [EV3 developer kit documentation](https://education.lego.com/en-us/support/mindstorms-ev3/developer-kits) to find the specifications for the protocol used. To get the flag, you only need to parse outgoing packets, and you only really need to pay attention to one thing: the `TEXT` command of the `opUI_DRAW` instruction.

We can write a crude Go parser like so:

```go
package main

import (
"bufio"
"encoding/binary"
"encoding/hex"
"fmt"
"os"
"sort"
"strings"
)

func parseParameter(b *[]byte) interface{} {
switch (*b)[0] & 0xc0 {
case 0:
// short constant
var ret int
if (*b)[0]&0x20 == 0 {
// positive
ret = int((*b)[0] & 0x1f)
} else {
// negative
ret = -int((*b)[0] & 0x1f)
}
*b = (*b)[1:]
return ret
case 0x80:
// long constant
if (*b)[0]&0xf0 == 0x80 {
// value
switch (*b)[0] & 0x07 {
case 0, 4:
// null-terminated
s := ""
*b = (*b)[1:]
for (*b)[0] != 0 {
s += string(rune((*b)[0]))
*b = (*b)[1:]
}
*b = (*b)[1:]
return s
case 1:
// 1 byte
ret := (*b)[1]
*b = (*b)[2:]
return int(ret)
case 2:
// 2 bytes
ret := binary.LittleEndian.Uint16((*b)[1:])
*b = (*b)[3:]
return int(ret)
case 3:
// 4 bytes
ret := binary.LittleEndian.Uint32((*b)[1:])
*b = (*b)[5:]
return int(ret)
}
}
}
return nil
}

func main() {
f, err := os.Open("Extracted Data/RFCOMM-Oct 18 16_06_54.995.txt")
if err != nil {
panic(err)
}
defer f.Close()

type Text struct {
X int
Y int
S string
}
var texts []Text

scanner := bufio.NewScanner(f)
for scanner.Scan() {
line := scanner.Text()
if !strings.HasPrefix(line, "0B EF") {
continue
}
line = strings.Replace(line[9:], " ", "", -1)
b, err := hex.DecodeString(line)
if err != nil {
panic(err)
}
byteCodes := b[7:]
byteCodes = byteCodes[:len(byteCodes)-2]

for len(byteCodes) > 0 {
switch byteCodes[0] {
case 0x80:
// FLUSH
byteCodes = byteCodes[1:]
case 0x84:
// DRAW
switch byteCodes[1] {
case 0x00:
// UPDATE
byteCodes = byteCodes[2:]
case 0x05:
// TEXT
byteCodes = byteCodes[2:]
parseParameter(&byteCodes)
x := parseParameter(&byteCodes).(int)
y := parseParameter(&byteCodes).(int)
s := parseParameter(&byteCodes).(string)
texts = append(texts, Text{x, y, s})
case 0x12:
// TOPLINE
byteCodes = byteCodes[3:]
case 0x13:
// FILLWINDOW
byteCodes = byteCodes[4:]
default:
panic(fmt.Errorf("unexpected byte code: %x", byteCodes[1]))
}
default:
panic(fmt.Errorf("unexpected byte code: %x", byteCodes[0]))
}
}
}

if err := scanner.Err(); err != nil {
panic(err)
}

sort.Slice(texts, func(i, j int) bool {
if texts[i].Y < texts[j].Y {
return true
}
if texts[i].Y == texts[j].Y {
return texts[i].X < texts[j].X
}
return false
})

y := 0
for _, t := range texts {
if t.Y != y {
fmt.Printf("\n")
y = t.Y
}
fmt.Printf("%s", t.S)
}
fmt.Printf("\n")
}
```

The result:

```
hitcon{m1nd5t0rm
_communication_a
nd_firmware_deve
loper_kit}
```