-
Notifications
You must be signed in to change notification settings - Fork 55
Expand file tree
/
Copy pathusb-mouse-pcap-visualizer.py
More file actions
153 lines (117 loc) · 4.39 KB
/
Copy pathusb-mouse-pcap-visualizer.py
File metadata and controls
153 lines (117 loc) · 4.39 KB
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
150
151
152
153
#!/usr/bin/env python
import csv
import enum
import json
import struct
import pyshark
import structlog
import tqdm
import typer
logger = structlog.get_logger()
class Opcode(enum.Enum):
LEFT_BUTTON_HOLDING = 0b00000001
RIGHT_BUTTON_HOLDING = 0b00000010
class MouseSnapshot():
def __init__(self, timestamp: float, x: int, y: int, left_button_holding: bool, right_button_holding: bool):
self.timestamp = timestamp
self.x = x
self.y = y
self.left_button_holding = left_button_holding
self.right_button_holding = right_button_holding
def __repr__(self):
return f"MouseSnapshot(timestamp={self.timestamp}, x={self.x}, y={self.y}, left_button_holding={self.left_button_holding}, right_button_holding={self.right_button_holding})"
class MouseSnapshotEncoder(json.JSONEncoder):
def default(self, obj: object | None):
if isinstance(obj, MouseSnapshot):
return obj.__dict__
return json.JSONEncoder.default(self, obj)
class MouseEmulator:
def __init__(self):
self.x = 0
self.y = 0
self.left_button_holding = False
self.right_button_holding = False
def move(self, x: int, y: int):
self.x += x
self.y -= y
def set_left_button(self, state: bool):
self.left_button_holding = state
def set_right_button(self, state: bool):
self.right_button_holding = state
def snapshot(self, timestamp: float):
return MouseSnapshot(timestamp=timestamp, x=self.x, y=self.y, left_button_holding=self.left_button_holding, right_button_holding=self.right_button_holding)
class MouseTracer:
def __init__(self):
self.snapshots: list[MouseSnapshot] = []
def add(self, snapshot: MouseSnapshot):
self.snapshots.append(snapshot)
def save(self, path: str):
with open(path, 'w', newline='') as f:
writer = csv.writer(f)
writer.writerow(
['timestamp', 'x', 'y', 'left_button_holding', 'right_button_holding'],
)
for snapshot in self.snapshots:
writer.writerow([
snapshot.timestamp, snapshot.x, snapshot.y,
snapshot.left_button_holding, snapshot.right_button_holding,
])
def load_pcap(filepath: str):
cap = pyshark.FileCapture(filepath)
for packet in tqdm.tqdm(cap):
if hasattr(packet, 'DATA'):
usbhid_data = packet.DATA.get_field('usbhid_data')
usb_capdata = packet.DATA.get_field('usb_capdata')
timestamp = float(packet.sniff_timestamp)
for data in [usbhid_data, usb_capdata]:
if data:
yield (timestamp, data)
def parse_packet(payload: str):
items = [
struct.unpack('b', bytes.fromhex(i))[0]
for i in payload.split(':')
]
state, movement_x, movement_y = 0, 0, 0
if len(items) == 4:
state, movement_x, movement_y, _ = items
if len(items) == 8:
state, _, movement_x, _, movement_y, _, _, _ = items
left_button_holding = state & Opcode.LEFT_BUTTON_HOLDING.value != 0
right_button_holding = state & Opcode.RIGHT_BUTTON_HOLDING.value != 0
return movement_x, movement_y, left_button_holding, right_button_holding
def snapshot_mouse(filepath: str):
mouse_emulator = MouseEmulator()
for timestamp, data in load_pcap(filepath):
mx, my, lbh, rbh = parse_packet(data)
mouse_emulator.move(mx, my)
mouse_emulator.set_left_button(lbh)
mouse_emulator.set_right_button(rbh)
yield mouse_emulator.snapshot(timestamp)
app = typer.Typer()
@app.command()
def main(
input_file: str = typer.Option(
..., '-i', '--input-file',
help='Path to the input pcap file.',
),
output_file: str = typer.Option(
..., '-o', '--output-file',
help='Path to the output csv file.',
),
):
logger.info(
'Starting processing', input_file=input_file,
output_file=output_file,
)
mt = MouseTracer()
for snapshot in snapshot_mouse(input_file):
mt.add(snapshot)
logger.info('Snapshots loaded', count=len(mt.snapshots))
mt.save(output_file)
logger.info('Mouse snapshots saved', output_file=output_file)
logger.info(
'Visualization ready',
message='visualize the data by opening the assets/index.html file in your browser.',
)
if __name__ == '__main__':
app()