Thursday, March 5, 2015

BkP15 - School Bus - Riverside

On the School Bus route of the Boston Key Party 2015 CTF, we found ourselves up against the Riverside challenge.  (For those who aren't familiar -- as I wasn't -- Riverside is the name of a station in Boston and likely goes with the theme of public transportation lines used to liven up the standard Jeopardy-style CTF.)



[screenshot of scoreboard]

The challenge itself was a PCAP file, so off to Wireshark we went.  (Note: Wireshark has been known to have vulnerabilities, so we were running within a VM.)

[screenshot of Wireshark]

Wireshark showed lots of USB data, so our first question was -- What are we looking at?  We need to filter out just the traffic that will tell us what devices are connected, so we used the display filter "usb.bDescriptorType==0x01" to see just the device descriptors.

[screenshot with filter applied and showing a device]

USB provides vendor and device codes which tell us the make and model of what's connected to the USB bus.  This includes the USB hubs that the devices are plugged into, as well as the devices themselves.  In this case, it looks like some USB 2.0 and 3.0 hubs, a mouse, an Intel something, an Acer something, and a "bluetooth programming interface".

Alright, let's turn our attention to the data.

[screenshot of a data packet, highlight the USB URB context]

As we started looking through the packets, we noticed that under the "USB URB" context, there's a "Device" field which appears to be a unique ID number.  Following all the interrupts (which are the bulk of the data), the device ID is 12, which pairs with one of the devices we saw above -- the mouse ("M-BJ58/M-BJ69 Optical Wheel Mouse (0xc00e)")

Alright, so this data is hopefully the mouse moving around.  Maybe it's drawing a picture or something.  Let's look at the interrupts, since they contain the data from the mouse moving around.

[screenshot of a data packet, highlight the Leftover Capture Data]

Assuming the "Leftover Capture Data" on each packet is the data from the mouse (telling us what the user just did), we can filter those out with the display filter:  usb.urb_type==URB_COMPLETE

[screenshot with display filter]

As we looked through packet after packet, we started to notice patterns.  For starters, the "Leftover Capture Data" appears to consistently be a 4-byte value.  Further, the first and fourth bytes are always zero.  We needed to start understanding how USB Mice work, so a Google search for "usb mouse
data" led us to: http://wiki.osdev.org/Mouse_Input

[screenshot of  http://wiki.osdev.org/Mouse_Input]

The protocol description on that page matched what we were seeing -- four-byte values, with the first and fourth often being zeroes.  Reading further, the second byte is the X movement and the third byte is the Y movement.  The first byte contains two bits to tell me whether the values are signed (aka negative) and three bits that tell me if any of the buttons were pressed.


So, we're starting to understand the data, but to decode it, we'll need to extract it from Wireshark.  Really, we just wanted the textual output that I could start to parse via a spreadsheet or a script.   So, let's use the textual cousin of Wireshark: tcpdump:

[screenshot of tcpdump with the data]

There's a lot of control traffic in the beginning (100 packets, conveniently).  Those 100 packets are is just the devices being connected and other uninteresting traffic we don't care about that.  We only want the mouse movements.  We could have filtered it out in Wireshark (which would have been faster and easier), but we chose to do it the hard way: just redirect the output to a text file, open it in a text editor, and remove the first 100 lines.

[screenshot of tcpdump | grep of the 0x00 header > redirecting to text file]

Let's break this step apart.  We piped the tcpdump output to grep to filter down to only the lines containing a "0x00".  The 0x00 header is placed on every line containing our data bytes, so filtering on that string means we'll only get the data.  The next step was to remove the 0x00 header itself, since it's meaningless.  We just want the data.  Awk to the rescue!  We'll extract out just the hex of the data.  So, after redirecting to our output file (????), we have something that looks like this:

[screenshot of text data]

Finally!  Just the data of the USB mouse.  We can feed this into a spreadsheet, a Python script, or whatever tool we'd like to start analyzing it.  In our case, our hammer of choice is Python, so that's exactly what we did.

Tracking mouse movement with Python

With the data starting to make sense, we needed to begin tracking a cursor around the screen.  We used two variables to track the X,Y coordinates of cursor:


[Python script without clicks or keys]


Here's the output we started, but you'll notice it's a little meaningless as text:

[Python output without clicks or keys]

We need a graphical representation of this.  In vaguely similar challenges, we'd used a scatter plot on a Google Doc, so that's the tool we reached for next.

[screenshot of Google Doc]

Wow.  This pretty much looks like gibberish.  We played with this for a bit...  Maybe there was some hidden message that someone left us before scribbling it out?  We found some interesting patterns, but nothing that would give us a flag.  But what if they were clicking on or dragging something?  Right now, we're only tracking the position.  Let's see if there were any clicks...

BACK TO THE PROTOCOL!  The guide we found a while back (http://wiki.osdev.org/Mouse_Input) told us about button presses.  Let's read that more closely.

[screenshot of buttons info -- http://wiki.osdev.org/Mouse_Input]

Let's modify the Python script to check for button clicks:

[Python code with button checks]

And let's see how that changes the output:

[Python output with button checks]

Looks like we've got quite a few left clicks.  Let's throw that into our Google Doc and focus only on the points where they're clicking on something.

[screenshot of Google doc with clicks only]

That pattern is interesting.  That diagonal stairstepping looks like a on-screen keyboard.  But what's with that extra area along the top?

[screenshot of Google doc with clicks only -- arrow pointing to space bar]

After researching weird keyboard layouts (including Dvorak), we were left with one conclusion: the keyboard is upside down.  We don't know why, but it's the only thing that makes sense to us.  Also problematically, even if we flip the standard QWERTY layout, the bottom row is off by a column.  Either they didn't like the letter 'Z' and really liked the comma, or we're off by a key.  (I'm betting on the latter.)

Extracting clicks on a virtual keyboard

Alright, let's assume this is an upside down keyboard.  Using the Google Docs' filters, we started to identify ranges of X,Y coordinates for button presses.  The goal was to come up with values for the X and Y coordinates that we could use to programatically identify which keys were being pressed.  Here's what we came up with:

 [paste of X,Y coordinate breakdown]

Back to the Python script to implement some of this logic.  We assumed we had a 25x25 pixel area for each key, since reviewing the data showed us that the clicks were fairly accurate and precise.  When we got to the mystery key (hopefully, the spacebar), we just assumed anything on that row was a space.  Adding a function to our existing code to implement this logic, we were left with the following:

[Python code with key tracker -- without 'Z' correction]

Fingers crossed!  Let's run it and see what happens:

[Python output with key tracker -- without 'Z' correction]

That's close, but some of the keys are off.  Wait a second, those are all keys that are on the bottom row.  Let's modify our script to push the keys over a column and see what the results look like:

 [Python code with key tracker -- with 'Z' correction]

And now let's run it and see what happens:

  [Python output with key tracker -- with 'Z' correction]

Lookin' sharp!  Let's make that a little more readable by commenting out our unnecessary prints.
the quicj brown fox jumps ovver the lazy dog thekeyisiheardyoulikedsketchyetchingglastyear
That's definitely starting to look like a flag.  They've given us common test sentence that contains every character, which was mighty considerate.  Unfortunately, there are a few typos -- that's odd:
the quicj brown fox jumps ovver the lazy dog thekeyisiheardyoulikedsketchyetchingglastyear
They tell us the key, but it took us a few tries to figure out that we needed to clean up the typoes:
the quicj brown fox jumps ovver the lazy dog thekeyisiheardyoulikedsketchyetchingglastyear
DENIED!
thekeyisiheardyoulikedsketchyetchingglastyear
DENIED!

iheardyoulikedsketchyetchingglastyear
DENIED!

thekeyisiheardyoulikedsketchyetchinglastyear
DENIED!
iheardyoulikedsketchyetchinglastyear
SUCCESS!

To conclude, here's the link to our Google Doc, a full-scale screenshot, and our Python code.  It's all released under the MIT license, so feel free to use it for yourself!
  • [ link to Google Doc ]
  • [ link to keyboard screenshot ]
  • [ link to Python script ]
All-in-all, this was a great challenge.  It wasn't a stumper, but it was unique and fun.  We especially enjoy reading how other teams solved this challenge.  If you haven't already, check out the BKP github where they're linking to various teams' writeups.  There are quite a few good ones and everyone did it a little differently.

Thanks for reading!

No comments:

Post a Comment