← Back to Writeups
CUSTOMEasyForensics

Hens can type?

XESXOR9/2/20262 min read
#forensics#custom#easy#scancode_mapping#usb_capdata_extraction#usb_hid_keyboard_decoding#linux

Hens can type?

Platform: bluehensctf | Category: Forensics | Type: Challenge | Difficulty: Easy | OS: Linux | Author: XESXOR | Date: 2026-04-21 | Status: Solved Techniques: scancode_mapping, usb_capdata_extraction, usb_hid_keyboard_decoding

Summary

Task: PCAPNG with USB traffic from usbmon0 containing HID keyboard data. Solution: Extract 8-byte HID reports from usb.capdata, decode USB keyboard scancodes to reconstruct typed text revealing the flag.

Hens can type? — BlueHens CTF 2026

Description

Analyze the USB traffic capture to find the flag.

Given a PCAPNG file challenge1.pcapng containing USB traffic captured from usbmon0.

Analysis

Initial reconnaissance with tshark/capinfos revealed USB interrupt traffic. The capture contains data from multiple USB devices, but the important packets came from USB source address 2.11.1 with endpoint 0x81.

The packets carry 8-byte payloads in the usb.capdata field — the standard format for USB HID boot keyboard reports: ‍​‌‌​​‌​​​‌‌​​‌​​​​‌‌​‌​​​​‌‌‌​​‌​​‌‌​​​‌​​‌‌‌​​​​​‌‌​‌‌​​​‌‌​​​​‍

Byte 0: Modifier keys (Shift, Ctrl, Alt, etc.)
Byte 1: Reserved (always 0x00)
Bytes 2-7: Up to 6 simultaneous key scancodes

Solution

  1. Extract USB HID keyboard data from the capture:
bash
tshark -r challenge1.pcapng -Y "usb.src == '2.11.1' && usb.capdata" -T fields -e usb.capdata
  1. Decode the HID keyboard scancodes. Each 8-byte report contains modifier flags and keycodes that map to characters:
python
#!/usr/bin/env python3
"""USB HID Keyboard decoder for CTF challenges"""

# USB HID keyboard scancode to character mapping
SCANCODES = {
    0x04: ('a', 'A'), 0x05: ('b', 'B'), 0x06: ('c', 'C'), 0x07: ('d', 'D'),
    0x08: ('e', 'E'), 0x09: ('f', 'F'), 0x0a: ('g', 'G'), 0x0b: ('h', 'H'),
    0x0c: ('i', 'I'), 0x0d: ('j', 'J'), 0x0e: ('k', 'K'), 0x0f: ('l', 'L'),
    0x10: ('m', 'M'), 0x11: ('n', 'N'), 0x12: ('o', 'O'), 0x13: ('p', 'P'),
    0x14: ('q', 'Q'), 0x15: ('r', 'R'), 0x16: ('s', 'S'), 0x17: ('t', 'T'),
    0x18: ('u', 'U'), 0x19: ('v', 'V'), 0x1a: ('w', 'W'), 0x1b: ('x', 'X'),
    0x1c: ('y', 'Y'), 0x1d: ('z', 'Z'),
    0x1e: ('1', '!'), 0x1f: ('2', '@'), 0x20: ('3', '#'), 0x21: ('4', '$'),
    0x22: ('5', '%'), 0x23: ('6', '^'), 0x24: ('7', '&'), 0x25: ('8', '*'),
    0x26: ('9', '('), 0x27: ('0', ')'),
    0x2c: (' ', ' '), 0x2d: ('-', '_'), 0x2e: ('=', '+'),
    0x2f: ('[', '{'), 0x30: (']', '}'), 0x33: (';', ':'),
    0x34: ("'", '"'), 0x36: (',', '<'), 0x37: ('.', '>'),
    0x38: ('/', '?'), 0x31: ('\\', '|'),
}
‍​‌‌​​‌​​​‌‌​​‌​​​​‌‌​‌​​​​‌‌‌​​‌​​‌‌​​​‌​​‌‌‌​​​​​‌‌​‌‌​​​‌‌​​​​‍

def decode_hid_report(data: bytes) -> str:
    """Decode a single 8-byte HID keyboard report"""
    if len(data) < 3:
        return ''
    
    modifier = data[0]
    shift = bool(modifier & 0x22)  # Left or Right Shift
    
    result = ''
    for keycode in data[2:8]:
        if keycode == 0:
            continue
        if keycode in SCANCODES:
            char = SCANCODES[keycode][1 if shift else 0]
            result += char
    return result

# Process extracted HID data
import subprocess
output = subprocess.check_output([
    'tshark', '-r', 'challenge1.pcapng',
    '-Y', "usb.src == '2.11.1' && usb.capdata",
    '-T', 'fields', '-e', 'usb.capdata'
]).decode()
‍​‌‌​​‌​​​‌‌​​‌​​​​‌‌​‌​​​​‌‌‌​​‌​​‌‌​​​‌​​‌‌‌​​​​​‌‌​‌‌​​​‌‌​​​​‍

typed_text = ''
for line in output.strip().split('\n'):
    if not line:
        continue
    data = bytes.fromhex(line.replace(':', ''))
    typed_text += decode_hid_report(data)

print(f"Typed: {typed_text}")
  1. The decoded keystrokes reveal the flag directly. ‍​‌‌​​‌​​​‌‌​​‌​​​​‌‌​‌​​​​‌‌‌​​‌​​‌‌​​​‌​​‌‌‌​​​​​‌‌​‌‌​​​‌‌​​​​‍