[ misc 知识点 ] USB 键鼠流量
2026-08-22 20:05:38

捋一下知识点

🏷️ 例题:
| 20260202SHCTF(USB+鼠标流量)
| 20260202hgame(NewKeyboard)
| 更多 USB 流量案例见下方 [CISCN 2022] ez_usb 等

一、USB 协议基础概念

概念 说明
Host 主机(PC),发起请求的一方
USB Device 客户端设备(如键盘、鼠标、U盘)
Endpoint 设备上的通信端点,分为 IN(设备→主机)和 OUT(主机→设备)
Interface Class 设备类型,常见有:
HID:Human Interface Device(键盘/鼠标)
Mass Storage:U盘/移动硬盘
Printer:打印机

二、USB HID报文结构

一个典型的HID键盘报告是8字节的数据包

1
2
3
Byte 0: Modifier Keys (Ctrl, Shift,Alt...)
Byte 1: Reserved
Bytes 2-7: Key Codes (最多 6 个按键)
1
00 00 04 00 00 00 00 00

Byte0=0x00 无修饰键,Byte1=0x00 保留,Byte2=0x04 按 ‘A’,Bytes3~7 全 0 只按了一键。只有当字节不为 0 时才表示有效按键

三、关键字段

1. Modifier Keys 映射表(第 0 字节)

Hex Meaning
0x01 Ctrl
0x02 Shift
0x04 Alt
0x08 Win
0x10 Menu
0x20 Num Lock

某位被置位即该修饰键被按住(例如 0x02 表示 Shift 被按住)

2. Key Code 映射表(第 2~7 字节)

最核心的部分,用于还原用户输入。

Key Code (Hex) Character / Action
0x04 A
0x05 B
0x06 C
0x1F 2
0x20 3
0x21 4
0x28 n
0x2a [DEL]
0x2B 空格
0x32 ~
0x33 ;
0x34
0x36 ,
0x37 .

注意:不是所有键都有映射,有些是特殊功能键(如 F1-F12)

四、Wireshark流量分析

1.定位USB HID

protocol列可以找USB``USBHID

过滤器可以用usbhid.data

2.根据设备地址分离流量

每个usb设备有一个唯一地址 格式是x.x.x

例如2.8.1``2.4.1``2.10.1

过滤器:usb.addr=="2.8.1"

或者usb.data_len == 8 and usb.src =="1.6.1"

有一个很奇怪的点 我在导出的时候必须导出到一个文件夹里 否则还是原始数据 试了好几次都这样

3.导出原始数据 tshark

注意要在官网下载wireshark然后要配一下PATH (之前不知道在哪下的,,没有tshark

"D:\Wireshark\tshark.exe" -r 流量包地址 -Y "usb.capdata" -T fields -e usb.capdata > 导出文件地址

如:"D:\Wireshark\tshark.exe" -r D:\luxluusb\2.4.1.pcapng -Y "usb.capdata" -T fields -e usb.capdata > D:\luxluusb\2.4.1.txt

-r 读pcap;-Y 过滤取 usb.capdata;-T fields 字段输出;-e usb.capdata 提取原始数据;> 导出到文件

五、py脚本

通用脚本

每行是标准USB HID报告描述符格式 共6字节(16hex字符)

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
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
# -*- coding: utf-8 -*-
"""
健全的 USB HID 键盘流量解析器(CTF 专用)
支持:
- 大小写自动识别(基于 Shift/Caps)
- Backspace 删除
- Shift+数字/符号映射(!@#$%...)
- 保留 ← → 方向键(用于某些 CTF 题目)
- 多种输入格式(00:00:04... 或 000004...)
- 跳过无效/空包
"""

import sys

# ===== 标准美式键盘映射(无 Shift)=====
KEYMAP_NORMAL = {
0x04: 'a', 0x05: 'b', 0x06: 'c', 0x07: 'd', 0x08: 'e', 0x09: 'f',
0x0A: 'g', 0x0B: 'h', 0x0C: 'i', 0x0D: 'j', 0x0E: 'k', 0x0F: 'l',
0x10: 'm', 0x11: 'n', 0x12: 'o', 0x13: 'p', 0x14: 'q', 0x15: 'r',
0x16: 's', 0x17: 't', 0x18: 'u', 0x19: 'v', 0x1A: 'w', 0x1B: 'x',
0x1C: 'y', 0x1D: 'z',

0x1E: '1', 0x1F: '2', 0x20: '3', 0x21: '4', 0x22: '5',
0x23: '6', 0x24: '7', 0x25: '8', 0x26: '9', 0x27: '0',

0x28: '\n', # Enter
0x29: '<ESC>',
0x2A: '[BACKSPACE]',
0x2B: '\t', # Tab
0x2C: ' ', # Space
0x2D: '-', 0x2E: '=', 0x2F: '[', 0x30: ']', 0x31: '\\',
0x32: '`', # Non-US #
0x33: ';', 0x34: "'",
0x35: '~', # Often used as backtick alternative
0x36: ',', 0x37: '.', 0x38: '/',
}

# ===== Shift 激活时的符号映射 =====
KEYMAP_SHIFT = {
0x1E: '!', 0x1F: '@', 0x20: '#', 0x21: '$', 0x22: '%',
0x23: '^', 0x24: '&', 0x25: '*', 0x26: '(', 0x27: ')',
0x2D: '_', 0x2E: '+', 0x2F: '{', 0x30: '}', 0x31: '|',
0x32: '~',
0x33: ':', 0x34: '"',
0x35: '`',
0x36: '<', 0x37: '>', 0x38: '?',
}

# ===== 功能键定义 =====
FUNCTION_KEYS = {
0x39: '<CAPS>', 0x3A: '<F1>', 0x3B: '<F2>', 0x3C: '<F3>', 0x3D: '<F4>',
0x3E: '<F5>', 0x3F: '<F6>', 0x40: '<F7>', 0x41: '<F8>', 0x42: '<F9>',
0x43: '<F10>', 0x44: '<F11>', 0x45: '<F12>',
0x46: '<PRTSCR>', 0x47: '<SCROLLLOCK>', 0x48: '<PAUSE>',
0x49: '<INSERT>', 0x4A: '<HOME>', 0x4B: '<PGUP>',
0x4C: '<DELETE>', # Delete 键
0x4D: '<END>', 0x4E: '<PGDN>',
0x4F: '<RIGHT>', # ← 保留!
0x50: '<LEFT>', # ← 保留!
0x51: '<DOWN>', 0x52: '<UP>',
}

def clean_hex_line(line):
"""清理一行,只保留十六进制字符"""
return ''.join(c for c in line.strip() if c.lower() in '0123456789abcdef')

def parse_hid_packet(hex_str):
if len(hex_str) < 8:
return None, None
try:
mod = int(hex_str[0:2], 16)
key = int(hex_str[4:6], 16)
return mod, key
except ValueError:
return None, None

def is_shift_pressed(modifier):
return bool(modifier & 0x22)

def get_char(keycode, shift_on):
if keycode == 0:
return None

if keycode in FUNCTION_KEYS:
return FUNCTION_KEYS[keycode]

if keycode == 0x2A:
return '[BACKSPACE]'

if 0x04 <= keycode <= 0x1D:
base_char = KEYMAP_NORMAL.get(keycode, f'[unknown:0x{keycode:02X}]')
return base_char.upper() if shift_on else base_char

if shift_on and keycode in KEYMAP_SHIFT:
return KEYMAP_SHIFT[keycode]
elif keycode in KEYMAP_NORMAL:
return KEYMAP_NORMAL[keycode]
else:
return f'[unknown:0x{keycode:02X}]'

def main(input_file='usb.txt'):
output_chars = []

try:
with open(input_file, 'r', encoding='utf-8', errors='ignore') as f:
for line_num, line in enumerate(f, 1):
cleaned = clean_hex_line(line)
if not cleaned:
continue

mod, key = parse_hid_packet(cleaned)
if mod is None or key is None:
continue

char = get_char(key, is_shift_pressed(mod))
if char is None:
continue

# 处理 Backspace
if char == '[BACKSPACE]':
if output_chars:
output_chars.pop()
# 保留 LEFT 和 RIGHT,跳过其他功能键
elif char in ('<LEFT>', '<RIGHT>'):
output_chars.append(char)
# 跳过其余功能键(<F1>, <CAPS>, <ENTER> 等)
elif char.startswith('<') and char.endswith('>'):
continue
else:
output_chars.append(char)

except FileNotFoundError:
print(f"错误:找不到文件 {input_file}", file=sys.stderr)
sys.exit(1)
except Exception as e:
print(f"解析出错: {e}", file=sys.stderr)
sys.exit(1)

result = ''.join(output_chars)
print("Recovered keystrokes:")
print("=" * 50)
print(result)
print("=" * 50)

if __name__ == "__main__":
if len(sys.argv) > 1:
main(sys.argv[1])
else:
main('usb.txt')

-- coding: utf-8 --

“””
USB Keyboard HID 报文解析脚本
支持格式:

  • 无分隔符:如 “0000120000000000”
  • 冒号分隔:如 “00:00:12:00:00:00:00”
  • 空格或其他分隔符(自动清理)
    只提取主按键码(第3字节,偏移4-6),并严格忽略无效行。
    未知键用 [unknown:0xXX] 标记,便于调试。
    “””

USB HID Usage ID 到字符的映射(主键盘区,标准美式布局)

mappings = {
# 字母 A-Z
0x04: “A”, 0x05: “B”, 0x06: “C”, 0x07: “D”, 0x08: “E”, 0x09: “F”, 0x0A: “G”, 0x0B: “H”,
0x0C: “I”, 0x0D: “J”, 0x0E: “K”, 0x0F: “L”, 0x10: “M”, 0x11: “N”, 0x12: “O”, 0x13: “P”,
0x14: “Q”, 0x15: “R”, 0x16: “S”, 0x17: “T”, 0x18: “U”, 0x19: “V”, 0x1A: “W”, 0x1B: “X”,
0x1C: “Y”, 0x1D: “Z”,

# 数字 1-0
0x1E: "1", 0x1F: "2", 0x20: "3", 0x21: "4", 0x22: "5",
0x23: "6", 0x24: "7", 0x25: "8", 0x26: "9", 0x27: "0",

# 符号与功能键
0x28: "\n",           # Enter
0x29: "<ESC>",
0x2A: "[DEL]",        # Backspace
0x2B: "\t",           # Tab
0x2C: " ",            # Space
0x2D: "-", 0x2E: "=", 0x2F: "[", 0x30: "]", 0x31: "\\", 
0x32: "~",            # Non-US # (常作 `)
0x33: ";", 0x34: "'", 
0x35: "`",            # 部分系统中为 Non-US backslash,此处按常见用法设为 `
0x36: ",", 0x37: ".", 0x38: "/",

# 功能键(可选扩展)
0x39: "<CAPS>", 0x3A: "<F1>", 0x3B: "<F2>", 0x3C: "<F3>", 0x3D: "<F4>",
0x3E: "<F5>", 0x3F: "<F6>", 0x40: "<F7>", 0x41: "<F8>", 0x42: "<F9>",
0x43: "<F10>", 0x44: "<F11>", 0x45: "<F12>",
0x46: "<PRTSCR>", 0x47: "<SCROLLLOCK>", 0x48: "<PAUSE>",
0x49: "<INSERT>", 0x4A: "<HOME>", 0x4B: "<PGUP>",
0x4C: "<DEL>",        # Delete
0x4D: "<END>", 0x4E: "<PGDN>",
0x4F: "<RIGHT>", 0x50: "<LEFT>", 0x51: "<DOWN>", 0x52: "<UP>",

}

def clean_and_extract_keycode(line):
“””
清理输入行,提取第3字节(USB HID 报文的主按键码)
支持格式:
- “0000120000000000”
- “00:00:12:00:00:00:00”
- “00 00 12 00 00 00 00”
返回 keycode (int) 或 None(无效行)
“””
line = line.strip()
if not line:
return None

# 移除所有非十六进制字符(保留0-9, a-f, A-F)
cleaned = ''.join(ch for ch in line if ch.lower() in '0123456789abcdef')

# 至少需要8个字符(4字节)才能取到第3字节(索引4-6)
if len(cleaned) < 8:
    return None

try:
    # 第3字节:从索引4开始取2个字符
    key_hex = cleaned[4:6]
    keycode = int(key_hex, 16)
    return keycode
except ValueError:
    return None

def main(input_file=’usbdata.txt’):
output = “”
with open(input_file, ‘r’, encoding=’utf-8’) as f:
for line_num, line in enumerate(f, 1):
keycode = clean_and_extract_keycode(line)
if keycode is None or keycode == 0:
continue # 跳过无效行或无按键

        if keycode in mappings:
            output += mappings[keycode]
        else:
            # 保留 unknown 并显示原始值,便于调试
            output += f"[unknown:0x{keycode:02X}]"

print("Recovered keystrokes:")
print("-" * 40)
print(output)
print("-" * 40)

if name == “main“:
# 默认读取 ‘usbdata.txt’,你也可以改成 ‘2.8.1.txt’ 或其他
main(‘222.txt’)

1

output : 35C535765E50074A

1

output:526172211A0700[unknown]C[unknown]F907300000D00000000000000C4527424943500300000002[unknown]A000000[unknown]02B9F9B0530778B5541D33080020000000666C61672[unknown]E[unknown]747874[unknown]B9B[unknown]A013242F3A[unknown]FC[unknown]000B092C229D6E994167C05[unknown]A7[unknown]8708B271F[unknown]FC[unknown]042AE3D251E65536[unknown]F9A[unknown]DA87C77406B67D0[unknown]E6316684766[unknown]A86E844D[unknown]C81AA2[unknown]C72C71348D10C4[unknown]C[DEL]3D7B[unknown]00400700

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

删除`[unknown]`和`C[DEL]`

![6.2USB 键鼠流量-01](/img/obsidian/usb-keyboard-mouse-traffic/6.2USB 键鼠流量-01.png)

是一个`rar`文件 用winrar打开 密码就是另一个output 但是改成小写 因为实际上没有按`cap lock`键 仍然是小写 嗯嗯!

`NSSCTF{20de17cc-d2c1-4b61-bebd-41159ed7172d}`

# mouse_parser_for_your_data.py
with open("usbnew.txt", "r") as f_in:
posx = 0
posy = 0
click_points = []

for line in f_in:
line = line.strip()
# 清理非十六进制字符(如冒号、空格)
clean = ''.join(c for c in line if c.lower() in '0123456789abcdef')
if len(clean) < 10: # 至少需要 5 字节 (0-4)
continue

try:
# 提取关键字节(每个字节2字符)
btn = int(clean[0:2], 16) # Byte 0: 按钮
x = int(clean[4:6], 16) # Byte 2: X 增量 ← 注意是 [4:6]
y = int(clean[8:10], 16) # Byte 4: Y 增量 ← 注意是 [8:10]

# 转为有符号 byte (-128 ~ 127)
if x > 127:
x -= 256
if y > 127:
y -= 256

posx += x
posy += y

# 记录左键点击(btn == 1)或左右键(btn == 3)
if btn in (1, 3):
click_points.append((posx, -posy)) # Y 取反:图像坐标系

except Exception as e:
continue # 跳过异常行

# 自动写入 out.txt
with open("out.txt", "w") as f_out:
for x, y in click_points:
f_out.write(f"{x} {y}\n")

print(f"✅ 成功提取 {len(click_points)} 个点击点,已保存到 out.txt")

提取后得到鼠标位移状态 进行绘图

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
import matplotlib.pyplot as plt

# 读取 out.txt 文件中的数据
try:
with open('out.txt', 'r') as file:
lines = file.readlines()
except FileNotFoundError:
print("❌ 错误:找不到 out.txt 文件!请先运行鼠标解析脚本。")
exit()

if not lines:
print("❌ out.txt 为空,没有点击点数据。")
exit()

x_values = []
y_values = []

for line in lines:
line = line.strip()
if not line:
continue
try:
x, y = line.split()
x_values.append(float(x))
y_values.append(float(y))
except ValueError:
continue # 跳过格式错误的行

if not x_values:
print("❌ 没有有效坐标数据。")
exit()

# 创建散点图
plt.figure(figsize=(10, 8)) # 设置画布大小
plt.scatter(x_values, y_values, s=1) # s=1 让点更小,适合密集轨迹
plt.xlabel('X')
plt.ylabel('Y')
plt.title('Mouse Click Trace')
plt.grid(True)

# 保持纵横比一致(防止图像变形)
plt.axis('equal')

# 保存图像
plt.savefig('out.png', dpi=300, bbox_inches='tight')
print("✅ 图像已保存为 out.png")

# (可选)显示图像(在本地运行时可用)
# plt.show()

![6.2USB 键鼠流量-02](/img/obsidian/usb-keyboard-mouse-traffic/6.2USB 键鼠流量-02.png)

7724774CTFF1111AG.jpg*的密码
![6.2USB 键鼠流量-03](/img/obsidian/usb-keyboard-mouse-traffic/6.2USB 键鼠流量-03.jpg)

010末尾获得flag

![6.2USB 键鼠流量-04](/img/obsidian/usb-keyboard-mouse-traffic/6.2USB 键鼠流量-04.png)

NSSCTF{this_is_rea1_fllllll11ag}

🏷️ 更多例题:
| 例题-USB键鼠流量
| 例题-USB键鼠流量
| 例题-USB键鼠流量
| 例题-USB键鼠流量
| 例题-USB键鼠流量
| 例题-USB键鼠流量
| 例题-USB键鼠流量
| 例题-USB键鼠流量

上一页
2026-08-22 20:05:38
下一页