Writeup: Boston Key Party 2015 - Riverside

Information

  • Category: network
  • Points: 200

Description

A fellow hacker’s super secret login has been sniffed by the N$A. He provides the pcap file with USB traffic. He wants to know if he is screwed.

Writeup

What device?

The first step is obviously to find out what kind of device this traffic belongs to.
Since we are dealing with USB, we need to know that the host sends a packet of type GET_DESCRIPTOR Request DEVICE to the connected device, to request device informations. The device, in turn, responds with a GET_DESCRIPTOR Response DEVICE packet containing (among other informations) the idVendor and idProduct, which allow unique identification of the device.

Wireshark session

The initial part of the pcap has some GET_DESCRIPTOR Request DEVICE / Response DEVICE packets.
To list them all, apply the filter:

1
usb.bDescriptorType == 0x01

There are many devices connected, but a quick look at the whole capture reveals that all the traffic is generated by the device labeled 12.

The corrisponding GET_DESCRIPTOR Response DEVICE is:

./get_desc_0.png

With fields:

./get_desc_1.png

It’s a mouse.

Get a clue

The mouse traffic is a long sequence of URB_INTERRUPT in requests and responses (in URB slang URB_SUBMIT and URB_COMPLETE) between the host and the device, starting at packet #101:

./urb_flow.png

Long story short, on each interaction the host queries the mouse and the mouse replies with 4B of data, which can be seen in wireshark under the field leftover capture data. For example (packet #519):

./mouse_data.png

Get the flow

The payloads can be extracted easily with tshark:

1
2
3
4
5
6
$ tshark -Y "((usb.urb_type == 67) && (frame.number >= 101))" \
-r challenge.pcap -T fields -e usb.capdata > payload.txt
$ tail -n3 payload.txt
00:fa:fc:00
00:fa:fd:00
00:fe:ff:00

The result is a text file, so I set-up a ruby script to parse the packets:

Data processing

1
2
3
packets = File.readlines('payload.txt').collect do |l|
l.chomp!.gsub!(':', '').scan(/[a-z0-9]{2}/).collect { |b| b.to_i(16) }
end

I found somewhere (!) the meaning of the mouse data. The first byte carries the information about the mouse clicks, while the two bytes in the middle are
respectively the x-axis speed and the y-axis speed. The fourth byte is for wheel scroll speed.
Thus, while the first byte is a bit mask, while the others are signed bytes.

Detail of the click data (byte 0):

7 6 5 4 3 2 1 0
extra click side click wheel click middle click right click left click

On my script, I mapped the packets to actions, and derived the sequence of mouse coordinates integrating the speeds:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# Map actions.
actions = packets.map do |bytes|
{ :left => (bytes[0] & (0x01)) != 0,
:right => (bytes[0] & (0x02)) != 0,
:middle => (bytes[0] & (0x04)) != 0,
:extra => (bytes[0] & (0x10)) != 0,
:rel_x => bytes[1].chr.unpack("c").shift,
:rel_y => bytes[2].chr.unpack("c").shift,
:rel_wheel => bytes[3].chr.unpack("c").shift
}
end
# Add current x/y coordinates to each action.
actions.inject([0, 0]) do |(x, y), action|
action[:x] = x + action[:rel_x]
action[:y] = y + action[:rel_y]
[action[:x], action[:y]]
end

Visualization

To visualize the mouse movements and clicks i decided to build an svg image using the rasem library (the calculations involving x/y min/max are just
translations have positive coordinates)
:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
require 'rasem'

x_min = actions.map { |a| a[:x] }.min
x_max = actions.map { |a| a[:x] }.max
y_min = actions.map { |a| a[:y] }.min
y_max = actions.map { |a| a[:y] }.max

img = Rasem::SVGImage.new((x_max - x_min),
(y_max - y_min)) do

actions.inject([0, 0]) do |(prev_x, prev_y), action|
x = action[:x] - x_min
y = action[:y] - y_min
action[:img_x] = x
action[:img_y] = y

# Draw path.
line(prev_x, prev_y, x, y, :stroke => '#7f7f7f')

# Draw left clicks.
if action[:left]
circle(x, y, 5, :fill => 'red')
end

[x, y]
end
end

File.write('mouse.svg', img.output)

Which yields:

./path.png

./path_insight.png

Reading the flag

In the script, I picked the coordinates of each red hotspot, tagging them with the corresponding key. Then, I just matched each click with the nearest key:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
# Poor's man data clustering.
KB_MAP = {
'q' => {:x => 112, :y => 51},
'w' => {:x => 201, :y => 51},
'e' => {:x => 283, :y => 51},
'r' => {:x => 371, :y => 51},
't' => {:x => 451, :y => 51},
'y' => {:x => 553, :y => 51},
'u' => {:x => 618, :y => 51},
'i' => {:x => 707, :y => 51},
'o' => {:x => 793, :y => 51},
'p' => {:x => 876, :y => 51},

'a' => {:x => 156, :y => 134},
's' => {:x => 250, :y => 134},
'd' => {:x => 334, :y => 134},
'f' => {:x => 416, :y => 134},
'g' => {:x => 501, :y => 134},
'h' => {:x => 584, :y => 134},
'j' => {:x => 670, :y => 134},
'k' => {:x => 741, :y => 134},
'l' => {:x => 844, :y => 134},

'z' => {:x => 201, :y => 222},
'x' => {:x => 290, :y => 222},
'c' => {:x => 376, :y => 222},
'v' => {:x => 458, :y => 222},
'b' => {:x => 542, :y => 222},
'n' => {:x => 624, :y => 222},
'm' => {:x => 717, :y => 222},

' ' => {:x => 551, :y => 314}
}
def click_to_kb(click)
# Sort by euclidean distance.
KB_MAP.keys.min do |m1, m2|
x1, y1 = KB_MAP[m1][:x], KB_MAP[m1][:y]
x2, y2 = KB_MAP[m2][:x], KB_MAP[m2][:y]

dist1 = Math.sqrt(((click[:img_x] - x1)**2) + ((click[:img_y] - y1)**2))
dist2 = Math.sqrt(((click[:img_x] - x2)**2) + ((click[:img_y] - y2)**2))

dist1 <=> dist2
end
end

pass = actions.collect do |action|
next unless action[:left]
click_to_kb(action)
end.compact!.join
puts pass
# the quick brown fox jumps ovver the lazy dog thekeyisiheardyoulikedsketchyetchingglastyear

Note The repeated letters (double v, double g) are due to the fact that the mouse button pressure is registered for two near polling cycles.