PI 1: Magic in the Air


We are investigating an individual we believe is connected to a group smuggling drugs into the country and selling them on social media. You have been posted on a stake out in the apartment above theirs and with the help of space-age eavesdropping technology have managed to extract some data from their computer. What is the phone number of the suspect’s criminal contact?

flag format includes country code so it should be in the format: rgbCTF{+00000000000}

Category: Forensics

First Blood: 3 hour, 4 minutes after release by team dcua

Attempt this challenge by downloading it from here

The other questions in this series: PI2 and PI3

PI 1: Solution

Unzipping the data reveals a file with no extension. We can check its type with the file command:

$ file data
data: BTSnoop version 1,

BTSnoop is a bluetooth packet capture and is handled well by Wireshark. Lets open it there to have a look.


The first part of the packet capture is littered with bluetooth handshake noise that we can ignore for now, what we want to find out is what kind of device we are eavesdropping on. One of the first packets to transmit data from the device after session is established is in the above picture. Observe 3 important fields in wireshark:

Starting from the bottom, whatever this Value field is, it occurrs in every packet sent from our remote bluetooth device to the PC we are eavesdropping from. The Expert Info: Undecoded is Wireshark telling us that this Value field was decoded automatically, as wireshark has detected the bluetooth key exchange caught earlier in the capture file. We know it is a Human Interface Device and after googling the device name, G613, we find out that it is in fact, a bluetooth keyboard that we are eavesdropping on.

Considering we are looking at bluetooth packets coming from a keyboard, and (excluding the Bluetooth pairing packets) our keyboard is sending data (the Value field) over and over again to our host computer, AND that Value field is changing every time, it is not a short logical jump to make to assume that each Value field represents some kind of keyboard event. For example a keypress.

There are many ways to proceed here, but I opt for exporting the packet capture as JSON (File -> export packet dissections -> as JSON) and processing it further in an IPython terminal.

First we need a function to strip out the btatt Value field:

def get_key_btatt_value(cap):
    """ Data in flight captured through btmon of a HID like a keyboard will use the btatt protocol's value field to transmit

    This functions gets only the btatt.value field as a list in order of packet time
    l = []
    for x in cap:
            btatt_value = x['_source']['layers']['btatt'].get('btatt.value')
            if btatt_value:
        except (AttributeError, KeyError):
    return l

Then we can run it on the json output from wireshark of our packet capture:

In [26]: import json                                                                  

In [27]: cap = json.load(open("out.json", "r"))                                       

In [28]: get_key_btatt_value(cap)                                            

Using some documentation online, we can begin to understand how this data is formatted. The second byte is the key value and the first byte indicates whether SHIFT is held down.

def is_key_press(p):
    """ Bluetooth keyboards will not only communicate OnKeyPress
    but also onKeyReleased. We need to filter that out.
    if len(p) == 20:
    	data = p.split(':')
    	is_keyval = int(data[1], 16) != 0
    	has_timestamp = int(data[2], 16) != 0 #I think this data value is a timestamp or velocity value?
    	return is_keyval and not has_timestamp
        return False

def get_key_value(l):
    out = ''

    translator = {i:chr(i+93) for i in range(4,30)}

    translator[42] = "<BKSP>"
    translator[44] = " "
    translator[40] = "\n"
    translator[56] = "?"
    translator[46] = "+"
    translator[52] = "'"
    translator[54] = ","

    translator[39] = "0"
    numbers = {i+29:str(i) for i in range(1,10)}

    for x in get_key_btatt_value(l):
        if is_key_press(x):
            data = x.split(':')
            i = int(data[1], 16)
            c = translator.get(i, "<?_{}_?>".format(i))
            # Check SHIFT key down in the first byte 
            if data[0] == "20":
                c = "<SHFT_{}>".format(c)
            out += c

    return out

We can filter out Value data that doesn’t pertain to an actual keypress as this won’t contribute to the readability of our evesdropped message. With that in mind, we can simply decode character by caracter as follows:

In [37]: print(get_key_value(cap))
yoo man
sorry for the delay lol

trying to get tyi<BKSP><BKSP><BKSP>this keyboard workinn

yea its new<?_55_?> wireless mang<BKSP><?_55_?> 

been moving product

speakin of you needed to contact my boy right<SHFT_?>


should be fine just say <SHFT_j>ohnny <SHFT_h> sent you

alright lemme get you the number

hold up <SHFT_i>'m looking for it

its his burner, got it written down somewhere

yeah got it


mind it is a swwedish number<?_55_?> he got it on holiday there few months back

yeah you can buy burners super easily there

alright g

yeah its <SHFT_ >donny l

remember to tell him i sent you



Awesome. We have intercepted one half of a conversation it seems. Our suspect has leaked several bits of personally indentifiable information. We have a phone number and we know its Swedish and we know the flag format uses the international country code format. Thus the flag is rgbCTF{+46736727859}

Find the Private Investigator 2 writeup here.