One of the functions that that Yubikey can provide is the option to “store” a static password on the token which will be “typed” out on the host whenever you press the button. Having already done quite of a lot of work on the USB HID implementation, I was curious to know how Yubico had decided to emulate the keyboard functionality. Ultimately, I was hoping that I’d be able to set all kinds of different modifiers like Ctrl+Alt+Del and Super+R to have a little more fun with it (BadUSB/Rubber Ducky style).

A look at the source code

Luckily for us, the source for the Yubikey personalization tool has been open sourced and is on their Github page. The main file of interest here is this one. It defines what is basically a dictionary called keyMap in a function called UsScanEdit().

#define SHIFT = 0x80;

UsScanEdit::UsScanEdit() {
	keyMap = QMap<int, int>();
	keyMap[0x00] = 0;
	...
	keyMap[0x09] = 0x2b; /* \t */
	keyMap[0x0a] = 0x28; /* \n */
	...
	keyMap[0x2c] = 0x36; /* , */
	...
	keyMap[0x41] = 0x04 | SHIFT; /* A */
	...
	keyMap[0x61] = 0x04; /* a*/
	...
	keyMap[0x7f] = 0;
}

So, what’s going on here? Well, the binary exploitation fans among you would probably have spotted that A is commented next to the 0x41 entry of the dictionary keyMap. For those who aren’t aware, 0x41 is the ASCII for the uppercase letter A so we know that this is just a lookup table of keyboard characters. (In fact, it turns out at Yubico are using Latin1 rather that ASCII as can be seen on line 57 here, but it doesn’t really make any difference).

This is why most of keyMap is just set to 0 - they’re all the ASCII characters that don’t appear on a keyboard!

Keyboard Scan Codes

The question now is: if this is lookup table, what is it looking up? These are actually the keyboard “scan codes”. Taking a look at this beast of PDF from the USB Foundation, all the way down on page 53, we get the table of all the scan codes listed under “Usage ID (Hex)” and their “Usage Name"s (or the names of the keys).

Scan Code Keyboard Key
00 Reserved
01 ErrorRollOver
02 POSTFail
03 ErrorUndefined
04-1D a/A … z/Z
1E-27 1/!, 2/@, 3/#, 4/$, 5/%, 6/^, 7/&, 8/*, 9/(, 0/)
28 Return
29 Escape
2A Delete
2B Tab
2C Space
2D-38 -/_, =/+, [/{, ]/}, \/|, ;/:, ‘/”, `/~, ,,<, .,>, //?
39 CapsLock
3A-45 F1-F12
E0 Left Ctrl
E1 Left Shift
E2 Left Alt
E3 Left Super
E4 Right Control
E5 Right Shift
E6 Right Alt
E7 Right Super
E8-FFFF Reserved

You get the idea. What’s important here is that every possible keypress corresponds to a two byte scan code which is what actually gets sent to the host to be interpreted as input. The Yubico personalization tool is taking our password as input, looking up each character’s scan code and configuring the Yubikey to spit this string of bytes back at the host whenever the button is pressed.

However, there’s still one thing we haven’t explained: modifiers. What happens if I want to send an input that requires multiple keys to be pressed at once, like a capital A (Shift+a) or Ctrl+r? It turns out that it’s got something to do with the #DEFINE SHIFT=0x80; from the source code extract further back. To understand what’s going on, we need to know a little bit about HID packets.

HID Packets

If we restrict our attention to USB keyboards, then the HID protocol is surprisingly simple. Ignoring the USB layer stuff which initiates the connection by sending across a bunch of identifying information like VID/PIDs, serial numbers, etc (plug in a USB and check the output of dmesg to get an idea), let’s assume that you’ve plugged a USB keyboard into your computer and the correct drivers have been loaded.

(Note for the curious: Part of the initialization involves sending across a HID descriptor along with all the other USB descriptors (like VID/PID). This HID descriptor lays out how it’s packets should be interpreted by the host. It’s not really important for our present discussion though as it’s essentially just another hard-coded value.)

What happens when you push a key and release it? The microcontroller that handles the bus, sends a HID packet down the wire. Unlike TCP or one of the other common network protocols that you may be more familiar with, there are no chunks or encapsulation going on - each packet simply contains the information about a single complete keyboard instruction. The key here is “complete”, so holding Ctrl and then tapping a results in a single packet being send across (again, this is a slight simplification. Today, modern keyboards sometimes do some funky things like sending incomplete reports across like just holding down Ctrl, but is all to do with what report descriptor is sendt across (see the comment above)).

A HID packet is 8 bytes long, but we actually only care about 2 of those bytes.

00 02 04 06 08 0A 0C 0E
Modifier Reserved Scan Code Reserved Reserved Reserved Reserved Reserved

The third byte is pretty obvious - it just contains the scan code of the key that we’ve pressed. But what about the first byte - labelled “modifier”. It’s defined (again by the report descriptor) to be an 8-bit deep bitfield where each bit corresponds to one of the four modifiers; Ctrl, Shift, Alt, Super.

0/1 0/1 0/1 0/1
Ctrl Shift Alt Super

For example, holding Ctrl and Alt would give 0b1010=0x10, so the modifier byte would simply be 0x10. It’s as simple as that! So, if we wanted to send the complete instruction Ctrl+Alt+Del, our final HID packet would look like

10 00 2A 00 00 00 00 00
Ctrl+Alt Del

If we were the microcontroller, we just have to “say” \x10\x00\x2A\x00\x00\x00\x00\x00 to the host, and it would react as if someone had pressed Ctrl+Alt+Del!

What does all this have to do with the Yubikey? Remember the keyMap dictionary from earlier? It only maps things up to 0x7F - but our table from the HID specification goes up to 0xE8? It turns out that Yubico have only implemented support for the first 127 (0x00-0x7F) scan codes and mapped the second 127 (0x80-0xFF) to the exact same scan codes as the first.

Whenever a scan code greater than 0x80 is sent, the modifier bit gets set to 0x02. This simply corresponds to holding down the shift key. It appears to be a bit of a cheeky hack on Yubico’s part to get what should be 4 bytes compressed down to 2 when a character is stored as a static password on the Yubikey. It also means less logic is required by the Yubikey to handle constructing and sending HID packets. It’s probably just a simple switch case in the onboard firmware.

This theory is supported by going back to the source code.

#define SHIFT = 0x80;

UsScanEdit::UsScanEdit() {
	keyMap = QMap<int, int>();
	keyMap[0x00] = 0;
	...
	keyMap[0x41] = 0x04 | SHIFT; /* A */
	...
	keyMap[0x61] = 0x04; /* a*/
	...
	keyMap[0x7f] = 0;
}

Notice how for keyMap[0x41] (corresponding to A), we have the scan code 0x04 | SHIFT. Well, at the top, SHIFT is pre-defined to be 0x80, so this is just 0x84. i.e. the Shifted characters are just mapped to the 0x80-0xFF region. The true scan code for 0x84 is Scroll Lock, so we know that the Yubikey must be ORing any scan code higher than 0x80 with 0x80 before setting the modifer key and sending it down the wire. It might look something like this…

int * craftHIDPacket(int scanCode)
{
	int packet[8] = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};

	if ( scanCode >= 0x80 )
	{
		modifierBit = 0x20;
		keyCode = scanCode ^ 0x80;
	}
	else
	{
		modifierBit = 0x00;
		keyCode = scanCode;
	}

	packet[0] = modifierBit;
	packet[2] = keyCode;

	return packet;
}

Excusing my probably dodgy-looking C code, hopefully you get the idea as to what’s going on. Unfortunately, all this means that it looks like there isn’t a way to turn a Yubikey into a fully-fledged HID injection tool without rewriting the on-board firmware (which I’m not aware of a way to do). All in all, I hit a bit of a brick wall, but perhaps this read will be useful for someone who is looking to the USB HID implementation. My (perhaps choppy) implementation in Go can be found on Github here.

Until next time…