☰
CTF流量分析实战:USB键盘与鼠标流量提取与还原
2026/9/25 6:25:22 网站建设 项目流程

CTF流量分析做了几年,USB这个方向真的是“老面孔”了。从入门赛到省级决赛,USB流量题几乎成了标配,尤其是键盘流量,几乎人手一把梭。但是很多人卡在不知道USB流量到底在说什么、键盘映射怎么处理、鼠标坐标怎么还原,更别提遇到数据丢包、HID设备混杂的资产包。这篇内容我结合自己实际做题和出题的经验,把USB流量从原理到实操捋一遍,不讲空理论,全部是可复现的命令和脚本,希望能帮到正在啃流量分析的朋友。

1. 先说清楚USB流量分析到底在分析什么

1.1 USB协议里我们真正关心的层

USB协议本身是一套非常复杂的传输体系,分为物理层、总线层、传输层、协议层等等。但CTF赛题里我们真正拿到的,通常是一个pcap或pcapng文件,里面装着的是USB总线上的通信记录,用Wireshark打开后能看到URB(USB Request Block)结构。

CTF题目一般不会让你分析USB的控制传输、批量传输、同步传输那些底层业务,绝大多数情况下,我们面对的就是USB HID(Human Interface Device)类设备的数据。HID类设备最常见的三类是键盘、鼠标和触摸板/游戏手柄,它们通过中断传输方式周期性上报数据。这类数据的特征是:每个传输对应一个固定长度(比如键盘是8字节,鼠标是4字节或6字节),数据内容就是设备的输入状态。

从pcap里提取USB HID数据,通俗地说,就是把那些中断传输的URB信息里的“数据内容”抽出来,然后按设备协议解码成按键、坐标、按键值、手柄摇杆值等。CTF中USB流量分析题大部分答案就在这些解码后的内容里。

1.2 为什么USB流量题“既可以送分也可以防AK”

USB流量题在Misc里算是一个稳定输出点,但选项集约分明显。送分版只是键盘流量按固定映射表还原字符串,复杂版则会加干扰,比如:

  • 同一个pcap里混合了键盘和鼠标流量,需要按端点区分设备。
  • 多个键盘设备同时上报,需要定位目标设备的地址。
  • 出现了调制机制,比如键盘在普通键值上叠加了控制键(Shift、Ctrl等),必须考虑修饰键对大小写和符号的影响。
  • USB包数据存在丢包,中间缺少几个字节,直接拼接会错位。
  • 设备上报的其实不是单字符,而是扫描码,有些键盘扫描码在不同标准(USB HID Usage Table vs PS/2 Set 2)下含义不同。
  • 不只是键盘,也可能是鼠标映射成绘图轨迹、U盘读取文件内容、甚至HID伪造设备读取数据。

在这些干扰下,如果只会“键盘流量一把梭”脚本,稍微变换一下题目形式就废了。所以理解底层协议和提取方法,比死记脚本更重要。

2. 一步一步拆解USB协议:从抓包到HID事件

2.1 认识pcap里的USB数据包结构

在Wireshark中开启一个USB pcap,你会看到类似usb.urb_type、usb.transfer_type、usb.device_address、usb.endpoint_address这些字段。以一个键盘输入包为例,最关键的字段有:

  • usb.record_length:记录长度,一般8字节。
  • usb.data_present:是否包含数据。
  • usb.data_length:数据长度。
  • usb.endpoint_address:端点地址,用于区分IN/OUT方向。
  • usb.device_address:设备地址,用于区分不同USB设备。
  • usb.transfer_type:传输类型,HID中断传输类型一般是0x02。

在Wireshark中,USB协议的解析结果里能看到“Leftover Capture Data”字段,那个就是URB数据内容,也就是我们需要的关键字节。如果是8字节键盘数据,格式通常是:

Byte 0: 修饰键 Byte 1: 保留位 Byte 2~7: 当前按下的按键(最多6个按键的扫描码)

修饰键的位含义如下(0x00表示无修饰键):

  • 0x01:左Ctrl
  • 0x02:左Shift
  • 0x04:左Alt
  • 0x08:左GUI(Win/Cmd)
  • 0x10:右Ctrl
  • 0x20:右Shift
  • 0x40:右Alt
  • 0x80:右GUI

通常我们只关心第0字节和第2~7字节,第1字节在标准协议里固定为0。

鼠标数据的常见格式是4字节:

Byte 0: 按键状态(bit0左键、bit1右键、bit2中键) Byte 1: X位移(有符号) Byte 2: Y位移(有符号) Byte 3: 滚轮位移

也有6字节的鼠标报告,多出来的是额外的坐标字段,但CTF里最常见的是4字节。

2.2 用Wireshark过滤器快速定位HID传输

不是所有URB都是我们要的,建议先用显示过滤器筛出中断传输入方向(设备→主机)的数据,同时排除掉系统无意义的URB:

usb.transfer_type == 0x02 && usb.endpoint_address == 0x81 && usb.data_present == 1

端点0x81一般默认是HID设备的IN端点。如果设备有多个端点,可以先用统计查看所有端点:

在Wireshark中:统计 -> 端点,可以查看USB设备地址和端点分布;或者使用tshark直接列出:

tshark -r usb.pcap -Y "usb.transfer_type == 0x02 && usb.data_present == 1" -T fields -e usb.device_address -e usb.endpoint_address -e usb.data_len

这样你能看到有哪些设备在发送中断数据,以及每种数据包的长度。键盘数据一般是8字节,鼠标一般是4字节,如果看到长度不规律,可能是其他HID设备或混合流量。

2.3 URB里的数据不等于“报文”

这一步我见到很多人踩坑。Wireshark抓到的USB数据里,除了我们关心的HID报告(report),还可能出现URB围绕的setup包、status包、间隔符等。很多CTF搬运工直接用tshark的-T fields -e usb.capdata提取usb.capdata,这个字段其实是由Wireshark解析后的“USB数据”,它已经去掉了URB头部,剩下的才是HID报告数据。

用tshark提取鼠标和键盘数据的常用命令如下:

tshark -r usb.pcap -Y "usb.capdata && usb.transfer_type == 0x02 && usb.endpoint_address == 0x81" -T fields -e usb.capdata

注意usb.capdata是一个可选字段,只有Wireshark能识别出设备时才会显示。如果提不出来,可以用-e usb.data.data或者提取data.data字段,但那样会带上更多嵌套数据,需要手动处理。我习惯先看usb.capdata,不行再降级处理原始数据。

3. 键盘流量:基础映射与数据还原

3.1 按键码与字母的映射表

USB HID规范定义了一套Usage ID,每个数字对应一个键位。CTF里常见的字母、数字、标点映射关系如下(十进制表示Usage ID):

  • a-z:0x04~0x1D
  • 1-9:0x1E~0x26,0是0x27
  • 回车:0x28
  • 空格:0x2C
  • 逗号:0x36
  • 句点:0x37
  • 斜杠:0x38
  • 分号:0x33
  • 引号:0x34
  • 左方括号:0x2F
  • 右方括号:0x30
  • 反斜杠:0x31
  • 减号/下划线:0x2D
  • 等号/加号:0x2E
  • 上标/波浪号:0x35
  • Tab:0x2B
  • 退格:0x2A
  • 删除:0x4C
  • Home:0x4A
  • End:0x4D
  • 左右方向键:0x50/0x51/0x52/0x4F

实际做题时最常用到的是全部HID Usage ID到ASCII的映射表。我建议整理一份完整的表,而不是零散记忆。网络上有很多开源键盘映射表,比如usb_hid_keys.h,可以转成Python字典。以下是经典的映射逻辑:

没有Shift修饰键时,按下0x1E输出数字“1”;按下Shift修饰键时,同样0x1E输出字符“!”。

有Shift时数字与符号切换:

  • 1 -> !
  • 2 -> @
  • 3 -> #
  • 4 -> $
  • 5 -> %
  • 6 -> ^
  • 7 -> &
  • 8 -> *
  • 9 -> (
  • 0 -> )

字母键按下Shift时切换大小写。符号键也会切换成上档符号,比如分号键0x33在普通模式输出;,在Shift模式下输出:。

3.2 用Python还原键盘输入流程

拿到一组键盘URB数据后,第一步是解析每个包的修饰键和按键码,然后按时间顺序拼接。先提一个最常见的还原思路:每个包中如果第2~7字节里有值,就代表这一时刻有键按着或按住;当按键释放时,这些字节会变成0。美中不足的是,USB HID只报告当前时刻被按下的键,不直接区分“按下”和“释放”,所以我们需要通过状态变化推导。

实际CTF过程里,大多数USB键盘流量还原都是看“出现非0值”的包,将其中的键位输出,不考虑重复按住的延迟。比如一串连续的:

00 00 1E 00 00 00 00 00 00 00 1E 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 17 00 00 00 00 00 00 00 17 00 00 00 00 00

那么看到0x1E就输出 “1”,然后重复的0x1E在上一键释放后再次按下,按一次算一次(或者只算一次),接着0x17输出“t”。一般题目作者会用自动输入方式生成流量,每个字符的按下和释放之间都有间隔,所以“每个非零包输出一次字符”基本是可行的。

更稳妥的做法是记录状态变化,当按键从无到有时输出对应字符,当同一时刻有多个键被按下时,全部输出。下面是我常用的基础还原脚本框架:

from capstone import * # 这里不需要 import usb.core # 也不是必须 # 实际直接读取tshark输出的字符串列表即可 hid_map = { 0x04: 'a', 0x05: 'b', 0x06: 'c', 0x07: 'd', # ... 完整映射略 0x1E: '1', 0x1F: '2', 0x20: '3', 0x21: '4', # ... } SHIFT_MAP = { '1': '!', '2': '@', '3': '#', '4': '$', '5': '%', '6': '^', '7': '&', '8': '*', '9': '(', '0': ')', '-': '_', '=': '+', '[': '{', ']': '}', '\\': '|', ';': ':', '\'': '"', ',': '<', '.': '>', '/': '?', '`': '~', } def parse_capdata(capdata_str): # capdata_str 形如 "00:00:1e:00:00:00:00:00" data = bytes.fromhex(capdata_str.replace(':', '')) mod = data[0] keys = data[2:] return mod, keys lines = open('capdata.txt').read().strip().splitlines() result = '' last_keys = set() for line in lines: mod, keys = parse_capdata(line) pressed = set(k for k in keys if k != 0) for k in pressed: if k in hid_map: ch = hid_map[k] if mod & 0x02 or mod & 0x20: # 左右Shift ch = SHIFT_MAP.get(ch, ch.upper()) result += ch print(result)

实际使用中,我发现这个框架有一个问题:同一个按键按住不动,USB会不断刷出相同的非零数据,在还原时如果每个包都输出字符会导致一个字符重复很多次。正常键盘输入发布时间很短,重复周期很短,但肉眼难以分辨。更准确的做法是记录上一次的按键集合,只有当前包出现了上一次没有的键时才输出。这样按住不动的重复键不会重复输出,新按下的键才会输出。如果你发现在自己的数据里还原时字符重复,可以按这个思路改进。

3.3 Shift处理与控制键带来的符号陷阱

这是USB键盘流量里最容易被扣分的点。很多人在还原时只处理了普通字母和数字,遇到Shift组合就出错。

我遇到过一个题目,抓到的流量只包含键盘敲击,但结果需要还原出一整段英文句子,里面夹着特殊符号@,%,_等。如果忽略Shift修饰键,你只能得到“1”“5”“-”这些下档字符,完全对不上明文。

处理Shift有两种策略:

  1. 如果题目明文是小写字母和大小写混合,按0x02或0x20修饰键将字母a变成A。
  2. 如果符号键被按下,同时Shift被按下,直接通过SHIFT_MAP转换成上档符号。

还有一种情况是键盘锁CapsLock,有些流量里会出现CapsLock键的按下记录(0x39),如果你不做状态处理,后面的字母大小写全反。可以在脚本里维护一个caps_lock状态,遇到0x39取反。实际CTF中出现CapsLock的很少,但一旦出现就是坑。

3.4 数据丢包下的容错处理

USB流量在抓包时也有丢失情况,特别是题目构造时使用了不完整的URB记录。举例说,正常键盘数据包是8字节,但pcap中有些包的usb.capdata只有6字节,或者连续缺失了中间的按键值。如果直接按“每包第一位输出”的逻辑去拼接,可能错位。

比较好的容错方式是在提取数据前先观察所有usb.capdata的长度分布。如果发现大部分是8字节,少数是6字节或4字节,那么说明要么是不同端点混入(鼠标),要么是数据截断。需要按端点分离后再分别处理。如果是键盘数据包内部缺失,那基本无法完全恢复,但可以尝试通过上下文猜测缺失字符,多见于英文语境下,结合单词补全。

另一个更隐蔽的问题是“伪数据包”,也就是URB类型是中断,但data字段并不是真正的HID报告,而是别的类。比如有的题目会在流量里夹杂U盘SCSI命令,和键盘流量混在一起。这时一定要先用端点号过滤设备,再单独解析。

4. 鼠标流量:坐标还原与轨迹图像

4.1 鼠标数据包结构

鼠标协议最常见的是4字节:

Byte 0: 按键 Byte 1: X相对位移(有符号) Byte 2: Y相对位移(有符号) Byte 3: 滚轮

有时是5字节,多出来的可能是额外的坐标值;还有高精度鼠标会输出6字节或8字节。CTF里4字节居多。

X、Y是“相对位移”,不是绝对坐标。这意味着我们要根据上一包的坐标累加当前包的位移,才能得到当前绝对坐标。比如:

00 02 01 00 00 00 FF 00 00 FC 01 00

第一包X=2,Y=1,如果初始坐标为(0,0),则移动后坐标为(2,1)。第二包X=0,Y=-1(0xFF即-1),绝对位置变成(2,0)。第三包X=-4,Y=1,绝对位置变成(-2,1)。如果需要画图,坐标可能有负数,处理时要平移归一化。

鼠标流量题通常的玩法是:鼠标在屏幕上画了一幅画,可能是手写签名、曲线、二维码轮廓,或者直接是笔迹。解题基本步骤是:

  1. 解析所有URB数据,提取X和Y位移。
  2. 累加得到绝对坐标数组。
  3. 将坐标点绘制成图像,观察内容。

4.2 用Python从鼠标流量绘制轨迹

处理鼠标流量时,可以用tshark提取所有鼠标数据包,然后逐包解析。下面这段是我常用的绘制脚本:

import re import matplotlib.pyplot as plt x, y = 0, 0 points_x, points_y = [], [] with open('mouse.txt', 'r') as f: for line in f: data = line.strip().replace(':', '') if len(data) < 8: continue # 假设标准4字节偏移从byte1开始 dx = int(data[2:4], 16) dy = int(data[4:6], 16) if dx >= 128: dx -= 256 if dy >= 128: dy -= 256 x += dx y += dy points_x.append(x) points_y.append(y) plt.plot(points_x, points_y, linewidth=1) plt.gca().invert_yaxis() # 有些绘制方向可能需要翻转 plt.axis('equal') plt.show()

需要注意,位移字节是有符号的8位整数,取值范围-128~127。遇到0x80以上要按补码处理成负数,否则轨迹会乱。

还有一个坐标系方向问题。大多数鼠标报告里的Y正方向是向上还是向下取决于驱动和坐标空间约定。在Windows GDI坐标里,Y向下为正;但在很多HID报告中,Y正方向是向上的。画图时如果发现轨迹上下颠倒,可以plt.gca().invert_yaxis()或者把y = -y再累加。另外如果画出来像是镜像,可以考虑X轴反转。

4.3 鼠标按键与点击事件

鼠标流量有时不只是画轨迹,还会记录左键点击和右键点击。当Byte 0的bit0被置为1,表示左键按下,这时代表一次点击。如果把点击位置也画出来,可以还原“鼠标点过的位置”,常见于模拟用户打开文件、选择菜单等场景。

解析时,把当前绝对坐标记录下来作为点击点即可。如果题目中需要还原点击顺序,还要按时间顺序排列这些点。必要时可以输出坐标列表,再用画图工具连点,形成操作轨迹。

5. 实战题目与工具链:从零到拿Flag的完整路径

5.1 常见题型分类与识别技巧

我把CTF里的USB流量题大致分成四类,每类的解法侧重点不同:

题型数据特征解决路径
键盘流量8字节报告,大量非零键值按HID映射表还原字符串
鼠标轨迹4/6字节报告,位移为主还原坐标后绘图
键盘+鼠标混合存在多个端点或不同长度数据按端点分离后分别还原
特殊HID(U盘、手柄)数据长度不规则,SCSI/Bulk传输提取文件、解析报告描述符

识别技巧是看tshark导出的usb.capdata的长度分布。如果绝大多数长度为8,几乎可以认定是键盘;如果长度为4,基本是鼠标;如果还有其他长度,就要检查是否为复合HID设备。

有些题目会在描述符里做文章,比如把键盘报告长度改成其他值。这时直接提取usb.capdata可能什么都提不到,需要查看usbhid.data或解析HID Report Descriptor。这种情况极少出现,但如果出现,说明题目考察的是HID报告描述符解析,而非简单按键映射。

5.2 工具链推荐:tshark、Wireshark、Python生态

我平时处理USB流量,核心工具链是:

  • Wireshark:用于快速浏览数据包,分析端点、协议细节。
  • tshark:命令行提取字段,适合批量处理。
  • Python3:还原脚本,处理逻辑。
  • scapy:用于重新构造或深入解析USB数据包,不过USB流量在scapy里通常只是RawPcap,大多数场景用tshark提取完就够了。
  • usbhid或hid解析库:用于复杂HID报告描述符解析,不是必须。

网上有一定流传度的“一把梭”工具,比如“随波逐流”这类集成工具,内置键盘流量解析。但我建议只把它当辅助验证手段,不要依赖。因为题目变异很多,集成工具往往只处理标准场景,遇到Shift、多设备、丢包就抓瞎。亲手做一遍解析脚本,才是真正牢固的掌握方式。

5.3 真题模拟:一份带有残缺数据的键盘捕获

题目设定:下载到一个keyboard.pcapng,里面是有人用键盘敲了一封“信”。由于抓包问题,部分数据包的存储不完整,也就是说存在URB丢失。我们需要还原出明文。

我的实操步骤是:

第一步,用tshark先看基本信息:

tshark -r keyboard.pcapng -2 -R "usb.transfer_type == 0x02 && usb.data_present == 1" -T fields -e usb.endpoint_address -e usb.data_len

如果数据长度不全为8,记录一下异常长度。假设我们看到大部分8字节,少数6字节,说明有些键值可能被截断。这类截断常见于URB报文没被完整抓取,缺掉的部分通常是尾部,也就是后面几个0或后一个按键值。如果我们只关心第一个有效按键,前面还是能满足。如果缺掉的是中间的某个键,只能通过语义推断。

第二步,提取数据到文件:

tshark -r keyboard.pcapng -Y "usb.transfer_type == 0x02 && usb.endpoint_address == 0x81" -T fields -e usb.capdata > capdata.txt

第三步,写脚本解析。注意先去重00:00:00:00:00:00:00:00这类空包,只处理有键值的包。解码后发现是一段英文句子。如果其中有几个字符因为丢包缺失,比如句子是“Hello World”,缺失e,变成“Hllo World”,可根据上下文补全。

第四步,如果数据包中出现了多个USB设备地址,先对usb.device_address做分组,每个设备单独还原,再按时间戳合并。时间戳是判断输入顺序的关键,如果只用文本顺序,有可能因为抓包时不同设备交错而乱序。

5.4 利用HID报告描述符做深入分析

HID Report Descriptor是一段二进制描述符,定义设备上报的数据格式。在更复杂的USB流量题中,单纯映射键值不够,需要解析报告描述符来知道哪些bit是键盘、哪些bit是鼠标、或者哪个字节是特定传感器的数据。

Wireshark可以解析HID报告描述符,在解析树中展开“HID Protocol”层可以看到每个字段的offset和size。还有一种方式是提取描述符的原始字节,用Python的hidparse库解析。不过CTF一般用不到这么深,简单了解即可。

5.5 如何构造自己的USB流量样例

出题或练习时,可以用HID工具构造模拟流量。最简单的方法是在真实机器上用usbmon(Linux)或者Wireshark抓取真实键盘的USB流量,然后自行标注数据段。但很多时候我们并没有真实设备,也可以用虚拟USB设备模拟,比如用Python的pyusb构造模拟键盘设备发送报告,但这需要内核驱动支持。

如果没有硬件条件,我建议直接在已有公开数据集或网上例题的pcap上做变体练习。比如把正常键盘流量随机删除几个非零包,重新保存为pcap,就能模拟丢包场景;把不同设备的流量合并到一个文件里,练习多设备分辨。这些做法对提升实战判断力很有帮助。

6. 进阶实战:混合设备流量与特殊HID设备如何搞定

6.1 从键盘和鼠标混合流量中分离数据

很多赛题会把键盘和鼠标流量塞进同一个pcap,这个时候如果直接统一按键盘解析,会解出一堆垃圾。我的经验是先分组:

tshark -r mixed.pcap -Y "usb.transfer_type == 0x02 && usb.data_present == 1" -T fields -e usb.device_address -e usb.endpoint_address -e usb.capdata

输出里第一列是设备地址,第二列是端点地址,第三列是数据。按“设备地址+端点地址”分组,查看各组数据长度分布:

  • 组A:地址1.1.0,端点0x81,长度8字节,是键盘。
  • 组B:地址1.1.1,端点0x82,长度4字节,是鼠标。

分别处理这两组数据即可。有时Wireshark显示的设备地址不是简单的1.1.0,而是用usb.device_address和usb.bus_id组合,这种组合值也能区分设备。

6.2 识别无线鼠标和特有协议

无线鼠标接收器通常也是USB HID设备,但上报的数据格式可能不是标准的4字节。我曾遇到一个逻辑无线鼠标接收器,数据长度是6字节,前两个字节是设备ID,后四个才是位移和按键。这种情况下直接解析byte1和byte2可能会错。需要先观察数据中哪个字节随鼠标移动而变化。我一般采用“差分法”:移动鼠标时看哪些字节在变化,固定下来的是设备信息,变化的才是坐标字段。

还有一种游戏鼠标上报1000Hz,数据量特别大,且报文里有时间戳、任务ID等额外字段。CTF很少考这种,但若遇到,可以用Wireshark的柱状图查看不同数据的重复模式,先对数据做字段判断。

6.3 U盘流量与文件提取

U盘流量不是HID中断传输,而是Bulk传输,使用SCSI命令读写数据。当题目中出现U盘读取文件,我们可以从SCSI Read命令的返回值里还原出文件内容。这类题目比较硬核,通常需要熟悉SCSI命令格式和USB Mass Storage协议。

具体步骤是:

  1. 过滤usb.transfer_type == 0x03(批量传输)。
  2. 找到CBW(Command Block Wrapper)包,里面有SCSI操作码,比如0x28表示Read(10)。
  3. 对应的CSW和Data段包含文件数据。
  4. 提取所有Data段并按LBA逻辑块地址拼接成文件。

这已经超出了一般HID流量题的范畴,但作为进阶内容值得了解。我个人碰到过两三道这类题,都是提取出一个小文件然后做隐写。

6.4 游戏手柄流量解析

游戏手柄属于HID,通常上报结构和键盘鼠标不同,比如包含摇杆模拟值、按键位图等。CTF里游戏手柄题很少见,但一旦出现,通常是把按键序列转换为一串二进制,然后转字符串。解析时先看HID描述,找到哪个字节对应哪个按键。如果直接拿到的是“按键状态数组”,可以按时间顺序把所有按键状态拼起来,再转成ASCII。

7. 常见问题与排查技巧实录

7.1 为什么tshark提取不到usb.capdata

这是个高频问题。有以下可能:

  • 过滤器条件过严,导致没匹配到数据。可以先去掉usb.transfer_type条件,只留usb.data_present == 1,看看到底有哪些USB数据。
  • Wireshark版本不同,有些版本的usb协议解析不输出usb.capdata,而是输出usb.data.data。
  • 数据包本身不是USB HID,而是别的协议,需要先检查包类型。

我的排查习惯是先用Wireshark GUI打开pcap,点开一个有数据的包,看解析树中是否有“Leftover Capture Data”或“Payload”。如果能看到数据,再去看tshark对应的字段名。

如果实在提不到usb.capdata,可以用下面方式提取原始字节:

tshark -r usb.pcap -Y "frame.protocols == usb" -T fields -e data.data

这种提取会把USB链路层数据也带进来,需要手动剥离往返头。但至少不会丢数据。

7.2 数据错位:按键还原结果乱码

还原出的字符串乱码通常有三个原因:

  1. 把鼠标数据当键盘解析了,键值对应不到字母。
  2. 键盘映射表用错了,比如用了PS/2映射表而不是USB HID映射表。
  3. 数据提取时多了一个字节前缀,比如把usb.capdata当data.data提取,导致整个数据流偏移。

处理方法是先看一包原始数据,人工确认字节含义。比如看到00 00 04 00 00 00 00 00,如果设备是键盘,04应该是a。如果此时脚本输出的是其他字符,那大概率映射表错位。

7.3 鼠标轨迹左右镜像或上下翻转

我遇到过题目中鼠标轨迹是镜像的,比如正常绘制是“S”,还原出来是反“S”。这个时候检查X位移的符号位处理是否有误。USB鼠标的X正方向一般向右,Y正方向向上;但在很多绘图环境里,Y正方向向下。若显示上下翻转,可以在累加时对Y取反。左右镜像则把X累加方向反向。

还有一次,我发现同样是鼠标数据,水平方向位移值总是乘以某个系数,比如1.25倍,导致画的图横向拉伸。如果画出来的图明显变形,可以调整坐标缩放系数,或者用aspect='equal'强制等比例。这种情况一般是因为设备DPI不同没必要深究。

7.4 多个键盘设备同时上报

多设备流量里,如果错误合并了解析,会产生乱序字符串。正确方法是要按照时间戳排序,并且只保留目标设备的包。时间戳字段可以用frame.time_epoch提取。tshark在输出usb.capdata时加上-e frame.time_epoch,写入文件后按时间排序。

7.5 从空包到字符:重复按键问题

对于按住不动的重复包,正确逻辑是“状态变化时输出”,而不是“每个包都输出”。下面这个改进版解析片段可以处理重复键:

last_keys = set() result = '' for line in lines: mod, keys = parse_capdata(line) cur_keys = set(k for k in keys if k != 0) new_keys = cur_keys - last_keys for k in new_keys: if k in hid_map: ch = hid_map[k] if mod & 0x02 or mod & 0x20: ch = SHIFT_MAP.get(ch, ch.upper()) result += ch last_keys = cur_keys

使用这个逻辑,同一键重复上报不会重复输出,但新按键按下和旧按键释放时都会触发识别。唯一要注意的是当两个键连续快速切换时,新键输出会正常,不会漏。

8. 个人经验与沉淀

USB流量这类题给我最大的感触是:它不是考你背了多少脚本,而是考你有没有真正理解HID上报机制。我见过太多人拿到的映射表能跑通普通题,但一改Shift、一改多设备、一改鼠标轨迹就卡住。所以平时做练习,不要只满足于拿flag,要把每一道的pcap都拆开看,对照Wireshark解析树亲手确认字段是什么,端点是什么,设备地址是什么,数据长度为什么是8或4。这样拆过十道题之后,再遇到新题目,大概率扫一眼数据就能判断出解法。另外,保存几个自己的工具脚本,比如tshark提取命令、键盘还原脚本、鼠标绘图脚本,比赛时能省一半时间。别贪心去背一份万能脚本,猜一个完整的工具链,然后把数据流喂进去输出结果,是最容易被坑的。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询