.
675 |
--------------------------------------------------------------------------------
/Pipfile:
--------------------------------------------------------------------------------
1 | [[source]]
2 | url = "https://pypi.org/simple"
3 | verify_ssl = true
4 | name = "pypi"
5 |
6 | [dev-packages]
7 |
8 | [packages]
9 | termcolor = "*"
10 | terminaltables = "*"
11 | tqdm = "*"
12 |
13 | [requires]
14 | python_version = "3.6"
15 |
--------------------------------------------------------------------------------
/Pipfile.lock:
--------------------------------------------------------------------------------
1 | {
2 | "_meta": {
3 | "hash": {
4 | "sha256": "454595540faf1bdad184687d2565d4d1bf40fd021f93fb4cc17a1430b9d88854"
5 | },
6 | "pipfile-spec": 6,
7 | "requires": {
8 | "python_version": "3.6"
9 | },
10 | "sources": [
11 | {
12 | "name": "pypi",
13 | "url": "https://pypi.org/simple",
14 | "verify_ssl": true
15 | }
16 | ]
17 | },
18 | "default": {
19 | "termcolor": {
20 | "hashes": [
21 | "sha256:1d6d69ce66211143803fbc56652b41d73b4a400a2891d7bf7a1cdf4c02de613b"
22 | ],
23 | "index": "pypi",
24 | "version": "==1.1.0"
25 | },
26 | "terminaltables": {
27 | "hashes": [
28 | "sha256:f3eb0eb92e3833972ac36796293ca0906e998dc3be91fbe1f8615b331b853b81"
29 | ],
30 | "index": "pypi",
31 | "version": "==3.1.0"
32 | }
33 | },
34 | "develop": {}
35 | }
36 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | ----------
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 | **usbrip** (inherited from "USB Ripper", not "USB R.I.P.") is a simple forensics tool with command line interface that lets you keep track of USB device artifacts (i.e., USB event history) on Linux machines.
16 |
17 | Table of Contents:
18 |
19 | * [**Description**](#description)
20 | * [**Quick Start**](#quick-start)
21 | * [**Showcase**](#showcase)
22 | * [**System Log Structure**](#system-log-structure)
23 | * [**Dependencies**](#dependencies)
24 | - [deb](#deb)
25 | - [pip](#pip)
26 | * [**Manual installation**](#manual-installation)
27 | - [Git Clone](#git-clone)
28 | - [`install.sh`](#installsh)
29 | * [Paths](#paths)
30 | * [Cron](#cron)
31 | * [`uninstall.sh`](#uninstallsh)
32 | * [**Usage**](#usage)
33 | - [Synopsis](#synopsis)
34 | - [Help](#help)
35 | * [**Examples**](#examples)
36 | * [**Credits & References**](#credits--references)
37 | * [**Stargazers Chart**](#stargazers-chart)
38 | * [**Post Scriptum**](#post-scriptum)
39 |
40 | Description
41 | ==========
42 |
43 | **usbrip** is a small piece of software which analyzes Linux log data: journalctl output or contents of `/var/log/syslog*` (or `/var/log/messages*`) files. Based on the collected data usbrip can build USB event history tables with the following columns:
44 |
45 | * Connected (date & time)
46 | * Host
47 | * VID (vendor ID)
48 | * PID (product ID)
49 | * Product
50 | * Manufacturer
51 | * Serial Number
52 | * Port
53 | * Disconnected (date & time)
54 |
55 | Besides, it also can:
56 |
57 | * Export collected data as a JSON dump for later use.
58 | * Generate a list of authorized (trusted) USB devices as a JSON file (call it auth.json).
59 | * Search for "violation events" based on auth.json: discover USB devices that do appear in history **and** do NOT appear in the auth.json file.
60 | * *\*when installed with `-s` flag\** Create protected storages (7-Zip archives) to automatically backup and accumulate USB events with the help of cron scheduler.
61 | * Search additional details about a specific USB device based on its VID and/or PID.
62 |
63 | Quick Start
64 | ==========
65 |
66 | **Way 1.** Install with pip:
67 |
68 | ```console
69 | ~$ sudo -H python3 -m pip install -U usbrip
70 | ~$ usbrip -h
71 | ```
72 |
73 | **Way 2.** Install bleeding-edge with [`install.sh`](#manual-installation) (recommended, extra features available):
74 |
75 | ```console
76 | ~$ sudo apt install python3-venv p7zip-full -y
77 | ~$ git clone https://github.com/snovvcrash/usbrip && cd usbrip
78 | ~/usbrip$ sudo -H installers/install.sh
79 | ~/usbrip$ cd
80 | ~$ usbrip -h
81 | ```
82 |
83 | Showcase
84 | ==========
85 |
86 | 
87 |
88 | [**Docker**](https://hub.docker.com/r/snovvcrash/usbrip) (\*DEMO ONLY!\*)
89 |
90 | ```console
91 | ~$ docker run --rm -it snovvcrash/usbrip
92 | ```
93 |
94 | System Log Structure
95 | ==========
96 |
97 | usbrip supports two types of timestamps to parse within system log files:
98 |
99 | 1. **Non-modified** – standard syslog structure for GNU/Linux ([`"%b %d %H:%M:%S"`](http://strftime.org/), ex. `"Jan 1 00:00:00"`). This type of timestamp does not provide the information about the year.
100 | 2. **Modified** (recommended) – better syslog structure which provides high precision timestamps including years ([`"%Y-%m-%dT%H:%M:%S.%f%z"`](http://strftime.org/), ex. `"1970-01-01T00:00:00.000000-00:00"`).
101 |
102 | If you do have `journalctl` installed, then there's nothing to worry about as it can convert timestamps on the fly. Otherwise, the desired syslog structure can be achieved by setting `RSYSLOG_FileFormat` format in rsyslog configuration.
103 |
104 | 1. Comment out the following line in `/etc/rsyslog.conf`:
105 |
106 | ```console
107 | $ActionFileDefaultTemplate RSYSLOG_TraditionalFileFormat
108 | ```
109 |
110 | 2. Add custom `.conf` file for usbrip:
111 |
112 | ```console
113 | ~$ echo '$ActionFileDefaultTemplate RSYSLOG_FileFormat' | sudo tee /etc/rsyslog.d/usbrip.conf
114 | ```
115 |
116 | 3. *\*optional\** Delete existing log files:
117 |
118 | ```console
119 | ~$ sudo rm -f /var/log/syslog* /var/log/messages*
120 | ```
121 |
122 | 4. Restart the service:
123 |
124 | ```console
125 | ~$ sudo systemctl restart rsyslog
126 | ```
127 |
128 | Firstly, usbrip will check if there is a chance to dump system events using journalctl as the most portable option. If not – it will search for and parse `/var/log/syslog*` or `/var/log/messages*` system log files.
129 |
130 | Dependencies
131 | ==========
132 |
133 | ## deb
134 |
135 | * python3.6 interpreter (or newer)
136 | * python3-venv
137 | * p7zip-full (used by `storage` module)
138 |
139 | ## pip
140 |
141 | * [terminaltables](https://github.com/Robpol86/terminaltables)
142 | * [termcolor](https://pypi.org/project/termcolor)
143 | * [tqdm](https://github.com/tqdm/tqdm)
144 |
145 | Manual installation
146 | ==========
147 |
148 | ## Git Clone
149 |
150 | For simplicity, lets agree that all the commands where `~/usbrip$` prefix is appeared are executed in the `~/usbrip` directory which is created as a result of a git clone:
151 |
152 | ```console
153 | ~$ git clone https://github.com/snovvcrash/usbrip
154 | ~$ cd usbrip
155 | ~/usbrip$ pwd
156 | ```
157 |
158 | ## `install.sh`
159 |
160 | Besides installing with pip, usbrip can also be installed with custom [`installers/install.sh`](https://github.com/snovvcrash/usbrip/blob/master/installers/install.sh) script.
161 |
162 | When using `install.sh` some extra features become available:
163 |
164 | * The virtual environment is created automatically.
165 | * You can use the `storage` module – set a cron job to backup USB events on a schedule (example of a cron job can be found in [`usbrip/cron/usbrip.cron`](https://github.com/snovvcrash/usbrip/blob/master/usbrip/cron/usbrip.cron)).
166 |
167 | :warning: **Warning:** if you are using cron scheduling, you want to configure the crontab with `sudo crontab -e` in order to force the `storage update` submodule run as root. The storage passwords are kept in `/var/opt/usbrip/usbrip.ini` and accessible by root only by default.
168 |
169 | To install usbrip with `install.sh` use:
170 |
171 | ```console
172 | ~/usbrip$ sudo -H installers/install.sh [-l/--local] [-s/--storages]
173 | ~/usbrip$ cd
174 | ~$ usbrip -h
175 | ```
176 |
177 | * When `-l` switch is enabled, Python dependencies are resolved from local `.tar` packages ([3rdPartyTools](https://github.com/snovvcrash/usbrip/tree/master/3rdPartyTools) directory) instead of PyPI.
178 | * When `-s` switch is enabled, not only the usbrip project is installed but also the list of trusted USB devices, history and violations storages are created.
179 |
180 | After the installation completes feel free to remove the `~/usbrip` directory.
181 |
182 | ### Paths
183 |
184 | When installed with `install.sh`, usbrip uses the following paths:
185 |
186 | * `/opt/usbrip/` – project's main directory.
187 | * `/var/opt/usbrip/log/` – usbrip cron logs.
188 | * `/var/opt/usbrip/storage/` – USB event storages (`history.7z` and `violations.7z`, created during the installation process).
189 | * `/var/opt/usbrip/trusted/` – lists of trusted USB devices (`auth.json`, created during the installation process).
190 | * `/var/opt/usbrip/usbrip.ini` – usbrip configuration file (contains passwords for 7-Zip storages).
191 | * `/usr/local/bin/usbrip` – symlink to the `/opt/usbrip/venv/bin/usbrip` script.
192 |
193 | ### Cron
194 |
195 | Cron jobs can be set as follows:
196 |
197 | ```console
198 | ~/usbrip$ sudo crontab -l > tmpcron && echo "" >> tmpcron
199 | ~/usbrip$ cat usbrip/cron/usbrip.cron | tee -a tmpcron
200 | ~/usbrip$ sudo crontab tmpcron
201 | ~/usbrip$ rm tmpcron
202 | ```
203 |
204 | ### `uninstall.sh`
205 |
206 | The [`installers/uninstall.sh`](https://github.com/snovvcrash/usbrip/blob/master/installers/uninstall.sh) script removes usbrip and all the installation artifacts from your system.
207 |
208 | To uninstall usbrip use:
209 |
210 | ```console
211 | ~/usbrip$ sudo installers/uninstall.sh [-a/--all]
212 | ```
213 |
214 | * When `-a` switch is enabled, not only the usbrip project directory is deleted but also all the storages and usbrip logs are deleted too.
215 |
216 | Don't forget to remove the cron job if you had set up one.
217 |
218 | Usage
219 | ==========
220 |
221 | ## Synopsis
222 |
223 | ```console
224 | # ---------- BANNER ----------
225 |
226 | ~$ usbrip banner
227 | Get usbrip banner.
228 |
229 | # ---------- EVENTS ----------
230 |
231 | ~$ usbrip events history [-t | -l] [-e] [-n ] [-d [ ...]] [--host [ ...]] [--vid [ ...]] [--pid [ ...]] [--prod [ ...]] [--manufact [ ...]] [--serial [ ...]] [--port [ ...]] [-c [ ...]] [-f [ ...]] [-q] [--debug]
232 | Get USB event history.
233 |
234 | ~$ usbrip events open [-t | -l] [-e] [-n ] [-d [ ...]] [--host [ ...]] [--vid [ ...]] [--pid [ ...]] [--prod [ ...]] [--manufact [ ...]] [--serial [ ...]] [--port [ ...]] [-c [ ...]] [-q] [--debug]
235 | Open USB event dump.
236 |
237 | ~$ sudo usbrip events genauth [-a [ ...]] [-e] [-n ] [-d [ ...]] [--host [ ...]] [--vid [ ...]] [--pid [ ...]] [--prod [ ...]] [--manufact [ ...]] [--serial [ ...]] [--port [ ...]] [-f [ ...]] [-q] [--debug]
238 | Generate a list of trusted (authorized) USB devices.
239 |
240 | ~$ sudo usbrip events violations [-a [ ...]] [-t | -l] [-e] [-n ] [-d [ ...]] [--host [ ...]] [--vid [ ...]] [--pid [ ...]] [--prod [ ...]] [--manufact [ ...]] [--serial [ ...]] [--port [ ...]] [-c [ ...]] [-f [ ...]] [-q] [--debug]
241 | Get USB violation events based on the list of trusted devices.
242 |
243 | # ---------- STORAGE ----------
244 |
245 | ~$ sudo usbrip storage list [-q] [--debug]
246 | List contents of the selected storage. STORAGE_TYPE is either "history" or "violations".
247 |
248 | ~$ sudo usbrip storage open [-t | -l] [-e] [-n ] [-d [ ...]] [--host [ ...]] [--vid [ ...]] [--pid [ ...]] [--prod [ ...]] [--manufact [ ...]] [--serial [ ...]] [--port [ ...]] [-c [ ...]] [-q] [--debug]
249 | Open selected storage. Behaves similarly to the EVENTS OPEN submodule.
250 |
251 | ~$ sudo usbrip storage update [IN_AUTH.JSON] [-a [ ...]] [-e] [-n ] [-d [ ...]] [--host [ ...]] [--vid [ ...]] [--pid [ ...]] [--prod [ ...]] [--manufact [ ...]] [--serial [ ...]] [--port [ ...]] [--lvl ] [-q] [--debug]
252 | Update storage -- add USB events to the existing storage. COMPRESSION_LEVEL is a number in [0..9].
253 |
254 | ~$ sudo usbrip storage create [IN_AUTH.JSON] [-a [ ...]] [-e] [-n ] [-d [ ...]] [--host [ ...]] [--vid [ ...]] [--pid [ ...]] [--prod [ ...]] [--manufact [ ...]] [--serial [ ...]] [--port [ ...]] [--lvl ] [-q] [--debug]
255 | Create storage -- create 7-Zip archive and add USB events to it according to the selected options.
256 |
257 | ~$ sudo usbrip storage passwd [--lvl ] [-q] [--debug]
258 | Change password of the existing storage.
259 |
260 | # ---------- IDs ----------
261 |
262 | ~$ usbrip ids search [--vid ] [--pid ] [--offline] [-q] [--debug]
263 | Get extra details about a specific USB device by its and/or from the USB ID database.
264 |
265 | ~$ usbrip ids download [-q] [--debug]
266 | Update (download) the USB ID database.
267 | ```
268 |
269 | ## Help
270 |
271 | To get a list of module names use:
272 |
273 | ```console
274 | ~$ usbrip -h
275 | ```
276 |
277 | To get a list of submodule names for a specific module use:
278 |
279 | ```console
280 | ~$ usbrip -h
281 | ```
282 |
283 | To get a list of all switches for a specific submodule use:
284 |
285 | ```console
286 | ~$ usbrip -h
287 | ```
288 |
289 | Examples
290 | ==========
291 |
292 | * Show the event history of all USB devices, suppressing banner output, info messages and user interaction (`-q`, `--quiet`), represented as a list (`-l`, `--list`) with latest 100 entries (`-n NUMBER`, `--number NUMBER`):
293 |
294 | ```console
295 | ~$ usbrip events history -ql -n 100
296 | ```
297 |
298 | * Show the event history of the external USB devices (`-e`, `--external`, which were *actually* disconnected) represented as a table (`-t`, `--table`) containing Connected, VID, PID, Disconnected and Serial Number columns (`-c COLUMN [COLUMN ...]`, `--column COLUMN [COLUMN ...]`) filtered by date (`-d DATE [DATE ...]`, `--date DATE [DATE ...]`) and PID (`--pid [ ...]`) with logs taken from outer files (`-f FILE [FILE ...]`, `--file FILE [FILE ...]`):
299 |
300 | ```console
301 | ~$ usbrip events history -et -c conn vid pid disconn serial -d '1995-09-15' '2018-07-01' --pid 1337 -f /var/log/syslog.1 /var/log/syslog.2.gz
302 | ```
303 |
304 | :alien: **Note:** there is a thing to remember when working with filters. There are 4 types of filtering available: only *external* USB events (devices that can be pulled out easily, `-e`), *by date* (`-d`), *by fields* (`--host`, `--vid`, `--pid`, `--product`, `--manufact`, `--serial`, `--port`) and *by number of entries* you get as the output (`-n`). When applying different filters simultaneously, you will get the following behavior: firstly, *external* and *by date* filters are applied, then usbrip will search for specified *field* values in the intersection of the last two filters, and in the end it will cut the output to the *number* you defined with the `-n` option. So think of it as an **intersection** for *external* and *by date* filtering and **union** for *by fields* filtering. Hope it makes sense.
305 |
306 | * Build the event history of all USB devices and redirect the output to a file for further analysis. When the output stream is NOT terminal stdout (`|` or `>` for example) there would be no ANSI escape characters (color) in the output so feel free to use it that way. Also notice that usbrip uses some UNICODE symbols so it would be nice to convert the resulting file to UTF-8 encoding (with `encov` for example) as well as change newline characters to Windows style for portability (with `awk` for example):
307 |
308 | ```console
309 | ~$ usbrip events history -t | awk '{ sub("$", "\r"); print }' > usbrip.out && enconv -x UTF8 usbrip.out
310 | ```
311 |
312 | :alien: **Note:** you can always get rid of the escape characters by yourself even if you have already got the output to stdout. To do that just copy the output data to `usbrip.out` and apply one more `awk` instruction:
313 |
314 | ```console
315 | ~$ awk '{ sub("$", "\r"); gsub("\\x1B\\[[0-?]*[ -/]*[@-~]", ""); print }' usbrip.out && enconv -x UTF8 usbrip.out
316 | ```
317 |
318 | * Generate a list of trusted USB devices as a JSON file (`trusted/auth.json`) with VID and PID attributes containing the first *three* devices connected on November 30, 1984:
319 |
320 | ```console
321 | ~$ sudo usbrip events genauth trusted/auth.json -a vid pid -n 3 -d '1984-11-30'
322 | ```
323 |
324 | :warning: **Warning:** there are cases when different USB flash drives might have identical serial numbers. This could happen as a result of a [manufacturing error](https://forums.anandtech.com/threads/changing-creating-a-custom-serial-id-on-a-flash-drive-low-level-blocks.2099116/) or just some black hats were able to rewrite the drive's memory chip which turned out to be non-one-time programmable and so on... Anyways, *"no system is safe"*. usbrip **does not** handle such cases in a smart way so far, namely it will treat a pair of devices with identical SNs (if there exists one) as the same device regarding to the trusted device list and `genauth` module.
325 |
326 | * Search the event history of the external USB devices for violations based on the list of trusted USB devices (`trusted/auth.json`) by PID attribute, restrict resulting events to those which have Bob-PC as a hostname, EvilUSBManufacturer as a manufacturer, 0123456789 as a serial number and represent the output as a table with Connected, VID and PID columns:
327 |
328 | ```console
329 | ~$ sudo usbrip events violations trusted/auth.json -a pid -et --host Bob-PC --manufact EvilUSBManufacturer --serial 0123456789 -c conn vid pid
330 | ```
331 |
332 | * Search for details about a specific USB device by its VID (`--vid VID`) and PID (`--pid PID`):
333 |
334 | ```console
335 | ~$ usbrip ids search --vid 0781 --pid 5580
336 | ```
337 |
338 | * Download the latest version of `usb.ids` [database](http://www.linux-usb.org/usb.ids "List of USB ID's"):
339 |
340 | ```console
341 | ~$ usbrip ids download
342 | ```
343 |
344 | Credits & References
345 | ==========
346 |
347 | * [usbrip / Инструменты Kali Linux](https://kali.tools/?p=4873)
348 | * [Как узнать, какие USB устройства подключались к Linux / HackWare.ru](https://hackware.ru/?p=9703)
349 | * [usbrip: USB-форензика для Линуксов, или Как Алиса стала Евой / Форум информационной безопасности Codeby.net](https://codeby.net/threads/usbrip-usb-forenzika-dlja-linuksov-ili-kak-alisa-stala-evoj.63644/)
350 |
351 |
352 |
353 |
354 |
355 | Stargazers Chart
356 | ==========
357 |
358 |
359 |
360 |
361 |
--------------------------------------------------------------------------------
/gen-demo-syslog.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python3
2 | # -*- coding: utf-8 -*-
3 |
4 | import os
5 | import time
6 | import string
7 | from glob import glob
8 | from datetime import datetime
9 | from random import random, randint, choices
10 |
11 | CHARSET = string.digits + 'ABCDEF'
12 |
13 |
14 | def strftime_prop(start, end, prop, fmt):
15 | stime = time.mktime(time.strptime(start, fmt))
16 | etime = time.mktime(time.strptime(end, fmt))
17 | ptime = stime + prop * (etime - stime)
18 | return time.strftime(fmt, time.localtime(ptime))
19 |
20 |
21 | def random_date(start, end, prop, fmt='%Y %b %d %H:%M:%S'):
22 | return strftime_prop(start, end, prop, fmt)
23 |
24 |
25 | def date_offset(date, fmt='%Y %b %d %H:%M:%S'):
26 | return time.strftime(fmt, time.localtime(time.mktime(time.strptime(date, fmt)) + randint(1, 60)))
27 |
28 |
29 | if __name__ == '__main__':
30 | for f in glob('/var/log/syslog*'):
31 | os.remove(f)
32 |
33 | with open('/var/log/syslog', 'w', encoding='utf-8') as f:
34 | events = [f"""\
35 | 1970-01-01T00:00:00.000000-0000 THESE kernel: [ 0.000000] usb 0-0: new high-speed USB device number 0 using ehci-pci
36 | 1970-01-01T00:00:00.000000-0000 THESE kernel: [ 0.000000] usb 0-0: New USB device found, idVendor=ARE, idProduct=JUST
37 | 1970-01-01T00:00:00.000000-0000 THESE kernel: [ 0.000000] usb 0-0: New USB device strings: Mfr=1, Product=2, SerialNumber=3
38 | 1970-01-01T00:00:00.000000-0000 THESE kernel: [ 0.000000] usb 0-0: Product: RANDOMLY
39 | 1970-01-01T00:00:00.000000-0000 THESE kernel: [ 0.000000] usb 0-0: Manufacturer: GENERATED
40 | 1970-01-01T00:00:00.000000-0000 THESE kernel: [ 0.000000] usb 0-0: SerialNumber: DEMO EVENTS
41 | 1970-01-01T00:00:00.000000-0000 THESE kernel: [ 0.000000] usb-storage 0-0: USB Mass Storage device detected
42 | 1970-01-01T00:00:00.000000-0000 THESE kernel: [ 0.000000] scsi host3: usb-storage 0-0
43 | 1970-01-01T00:00:01.000000-0000 THESE kernel: [ 0.000000] usb 0-0: USB disconnect, device number 0
44 | """.replace('\t', '')]
45 |
46 | for _ in range(10):
47 | connected = random_date(
48 | '1970-01-01T00:00:00',
49 | datetime.now().strftime('%Y-%m-%dT%H:%M:%S'),
50 | prop=random(),
51 | fmt='%Y-%m-%dT%H:%M:%S'
52 | )
53 |
54 | disconnected = date_offset(connected, fmt='%Y-%m-%dT%H:%M:%S')
55 | device_num = randint(1, 12)
56 |
57 | connected += '.000000-0000'
58 | host = 'usbrip'
59 | vid = str(randint(1, 9999)).rjust(4, '0')
60 | pid = str(randint(1, 9999)).rjust(4, '0')
61 | product = ''.join(choices(CHARSET, k=8))
62 | manufacturer = ''.join(choices(CHARSET, k=12))
63 | serial_number = ''.join(choices(CHARSET, k=13))
64 | port = randint(1, 3)
65 | disconnected += '.000000-0000'
66 |
67 | events.append(f"""\
68 | {connected} {host} kernel: [ 0.000000] usb {port}-1: new high-speed USB device number {device_num} using ehci-pci
69 | {connected} {host} kernel: [ 0.000000] usb {port}-1: New USB device found, idVendor={vid}, idProduct={pid}
70 | {connected} {host} kernel: [ 0.000000] usb {port}-1: New USB device strings: Mfr=1, Product=2, SerialNumber=3
71 | {connected} {host} kernel: [ 0.000000] usb {port}-1: Product: {product}
72 | {connected} {host} kernel: [ 0.000000] usb {port}-1: Manufacturer: {manufacturer}
73 | {connected} {host} kernel: [ 0.000000] usb {port}-1: SerialNumber: {serial_number}
74 | {connected} {host} kernel: [ 0.000000] usb-storage {port}-1:1.0: USB Mass Storage device detected
75 | {connected} {host} kernel: [ 0.000000] scsi host3: usb-storage {port}-1:1.0
76 | {disconnected} {host} kernel: [ 0.000000] usb {port}-1: USB disconnect, device number {device_num}
77 | """.replace('\t', ''))
78 |
79 | events.sort(key=lambda x: datetime.strptime(x[:31], '%Y-%m-%dT%H:%M:%S.%f%z'))
80 | f.writelines(events)
81 |
--------------------------------------------------------------------------------
/installers/install.sh:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 |
3 | : '
4 | %file install.sh
5 | %author Sam Freeside (@snovvcrash)
6 | %date 2018-05-28
7 |
8 | %brief usbrip installer.
9 |
10 | %license
11 | Copyright (C) 2020 Sam Freeside
12 |
13 | This file is part of usbrip.
14 |
15 | usbrip is free software: you can redistribute it and/or modify
16 | it under the terms of the GNU General Public License as published by
17 | the Free Software Foundation, either version 3 of the License, or
18 | (at your option) any later version.
19 |
20 | usbrip is distributed in the hope that it will be useful,
21 | but WITHOUT ANY WARRANTY; without even the implied warranty of
22 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
23 | GNU General Public License for more details.
24 |
25 | You should have received a copy of the GNU General Public License
26 | along with usbrip. If not, see .
27 | %endlicense
28 | '
29 |
30 | # Usage: sudo -H bash installers/install.sh [-l/--local] [-s/--storages]
31 |
32 | shopt -s expand_aliases
33 |
34 | # --------------- Check for root privileges ----------------
35 |
36 | if [[ "${EUID}" -ne 0 ]]; then
37 | /usr/bin/printf "${R}>>>>${NC} Please run as root:\nsudo -H %s [-l/--local] [-s/--storages]\n" "${0}"
38 | exit 1
39 | fi
40 |
41 | # ----------------------- Constants ------------------------
42 |
43 | if [[ -z "${SUDO_USER}" ]]; then
44 | SUDO_USER="root"
45 | fi
46 |
47 | USER_HOME=`getent passwd ${SUDO_USER} | cut -d: -f6`
48 | CONFIG="${USER_HOME}/.config/usbrip"
49 |
50 | OPT="/opt/usbrip"
51 | VAR_OPT="/var/opt/usbrip"
52 | SYMLINK="/usr/local/bin/usbrip"
53 |
54 | G="\033[1;32m" # GREEN
55 | Y="\033[1;33m" # YELLOW
56 | R="\033[1;31m" # RED
57 | W="\033[1;37m" # WHITE
58 | NC="\033[0m" # NO COLOR
59 |
60 | # ----------------------- Functions ------------------------
61 |
62 | create_directory() {
63 | /usr/bin/printf "${W}>>>>${NC} Creating directory: '$1'\n"
64 |
65 | if [[ -d "$1" ]]; then
66 | /usr/bin/printf "${R}>>>>${NC} $1 already exists. First run:\n%s\n" \
67 | "sudo uninstall.sh --all"
68 | exit 1
69 | fi
70 |
71 | if /bin/mkdir -p "$1"; then
72 | /usr/bin/printf "${G}>>>>${NC} Successfully created directory: '$1'\n\n"
73 | else
74 | /usr/bin/printf "${R}>>>>${NC} Failed to create directory: '$1'\n"
75 | exit 1
76 | fi
77 | }
78 |
79 | # --------------------- usbrip aliases ---------------------
80 |
81 | alias createHistoryStorage="${OPT}/venv/bin/usbrip storage create history -e"
82 |
83 | alias generateAuthorizedDeviceList="${OPT}/venv/bin/usbrip events genauth /var/opt/usbrip/trusted/auth.json -e -a vid pid"
84 |
85 | alias createViolationsStorage="${OPT}/venv/bin/usbrip storage create violations /var/opt/usbrip/trusted/auth.json -e -a vid pid"
86 |
87 | # -------------------- Handle switches ---------------------
88 |
89 | LOCAL=false
90 | STORAGES=false
91 |
92 | if [[ "$1" == "-l" ]] || [[ "$1" == "--local" ]]; then
93 | LOCAL=true
94 | elif [[ "$1" == "-s" ]] || [[ "$1" == "--storages" ]]; then
95 | STORAGES=true
96 | fi
97 |
98 | if [[ "$2" == "-l" ]] || [[ "$2" == "--local" ]]; then
99 | LOCAL=true
100 | elif [[ "$2" == "-s" ]] || [[ "$2" == "--storages" ]]; then
101 | STORAGES=true
102 | fi
103 |
104 | # -------------- Check for required packages ---------------
105 |
106 | # python3-venv
107 |
108 | if ! /usr/bin/dpkg-query -W -f='${Status}' python3-venv 2>&1 | /bin/grep "ok installed" > /dev/null; then
109 | /usr/bin/printf "${Y}>>>>${NC} Unresolved dependency: python3-venv. Do you want to install this package as:\n%s\n" \
110 | "\"sudo apt install python3-venv -y\"?"
111 | select YN in "Yes" "No"; do
112 | case ${YN} in
113 | "Yes" )
114 | apt install "python3-venv" -y
115 | break
116 | ;;
117 | "No" )
118 | exit 1
119 | ;;
120 | esac
121 | done
122 | fi
123 |
124 | # p7zip-full
125 |
126 | if ! /usr/bin/dpkg-query -W -f='${Status}' p7zip-full 2>&1 | /bin/grep "ok installed" > /dev/null && ${STORAGES}; then
127 | /usr/bin/printf "${Y}>>>>${NC} Unresolved dependency: p7zip-full. Do you want to install this package as:\n%s\n" \
128 | "\"sudo apt install p7zip-full -y\"?"
129 | select YN in "Yes" "No"; do
130 | case ${YN} in
131 | "Yes" )
132 | apt install "p7zip-full" -y
133 | break
134 | ;;
135 | "No" )
136 | exit 1
137 | ;;
138 | esac
139 | done
140 | fi
141 |
142 | # ------------------- Create directories -------------------
143 |
144 | # OPT
145 |
146 | create_directory "${OPT}"
147 |
148 | # LOG
149 |
150 | create_directory "${VAR_OPT}/log"
151 |
152 | # STORAGE
153 |
154 | create_directory "${VAR_OPT}/storage"
155 |
156 | # CONFIG
157 |
158 | create_directory "${CONFIG}"
159 |
160 | # -------------------- Fix permissions ---------------------
161 |
162 | chmod -R 600 "${VAR_OPT}/log"
163 |
164 | # ------------ Build Python virtual environment ------------
165 |
166 | /usr/bin/printf "${W}>>>>${NC} Building Python virtual environment\n"
167 |
168 | if /usr/bin/python3 -m venv "${OPT}/venv"; then
169 | /usr/bin/printf "${G}>>>>${NC} Successfully built Python virtual environment\n\n"
170 | else
171 | /usr/bin/printf "${R}>>>>${NC} Failed to build Python virtual environment\n"
172 | exit 1
173 | fi
174 |
175 | # ------------------------ Install -------------------------
176 |
177 | /usr/bin/printf "${W}>>>>${NC} Installing usbrip\n"
178 |
179 | if $LOCAL; then
180 | if ${OPT}/venv/bin/python "${PWD}/setup.py" install; then
181 | /usr/bin/printf "${G}>>>>${NC} Successfully installed usbrip using local dependencies\n\n"
182 | else
183 | /usr/bin/printf "${R}>>>>${NC} Failed to install usbrip using local dependencies\n"
184 | exit 1
185 | fi
186 | else
187 | if ${OPT}/venv/bin/pip install "${PWD}"; then
188 | /usr/bin/printf "${G}>>>>${NC} Successfully installed usbrip using PyPI dependencies\n\n"
189 | else
190 | /usr/bin/printf "${R}>>>>${NC} Failed to install usbrip using PyPI dependencies\n"
191 | exit 1
192 | fi
193 | fi
194 |
195 | ${OPT}/venv/bin/python "${PWD}/setup.py" clean
196 | echo
197 |
198 | # ----------------------- Copy files -----------------------
199 |
200 | if cp "${PWD}/usbrip/usb_ids/usb.ids" "${USER_HOME}/.config/usbrip/usb.ids"; then
201 | /usr/bin/printf "${G}>>>>${NC} Successfully copied usb.ids database\n\n"
202 | else
203 | /usr/bin/printf "${R}>>>>${NC} Failed to copy usb.ids database\n"
204 | exit 1
205 | fi
206 |
207 | if cp "${PWD}/usbrip/cron/usbrip.cron" "${USER_HOME}/.config/usbrip/usbrip.cron"; then
208 | /usr/bin/printf "${G}>>>>${NC} Successfully copied usbrip cron job example\n\n"
209 | else
210 | /usr/bin/printf "${R}>>>>${NC} Failed to copy usbrip cron job example\n"
211 | exit 1
212 | fi
213 |
214 | chown -R ${SUDO_USER} ${USER_HOME}/.config
215 |
216 | # --------------------- Create symlink ---------------------
217 |
218 | if [[ -e "${SYMLINK}" ]]; then
219 | /bin/rm "${SYMLINK}"
220 | fi
221 |
222 | if /bin/ln -s "${OPT}/venv/bin/usbrip" "${SYMLINK}"; then
223 | /usr/bin/printf "${G}>>>>${NC} Created symlink: '${SYMLINK}'\n"
224 | fi
225 |
226 | # ----------------- Create usbrip storages -----------------
227 |
228 | if ${STORAGES}; then
229 | # History
230 |
231 | /usr/bin/printf "${W}>>>>${NC} Creating usbrip history storage\n"
232 |
233 | if createHistoryStorage; then
234 | /usr/bin/printf "${G}>>>>${NC} Successfully created usbrip history storage\n\n"
235 | else
236 | /usr/bin/printf "${R}>>>>${NC} Failed to create usbrip history storage\n"
237 | exit 1
238 | fi
239 |
240 | # GenAuth
241 |
242 | /usr/bin/printf "${W}>>>>${NC} Generating authorized device list\n"
243 |
244 | if generateAuthorizedDeviceList; then
245 | /usr/bin/printf "${G}>>>>${NC} Successfully generated authorized device list\n\n"
246 | else
247 | /usr/bin/printf "${R}>>>>${NC} Failed to generate authorized device list\n"
248 | exit 1
249 | fi
250 |
251 | # Violations
252 |
253 | /usr/bin/printf "${W}>>>>${NC} Creating usbrip violations storage\n"
254 |
255 | if createViolationsStorage; then
256 | /usr/bin/printf "${G}>>>>${NC} Successfully created usbrip violations storage\n\n"
257 | else
258 | /usr/bin/printf "${R}>>>>${NC} Failed to create usbrip violations storage\n"
259 | exit 1
260 | fi
261 | fi
262 |
263 | # -------------------------- Done --------------------------
264 |
265 | /usr/bin/printf "${G}>>>>${NC} Done.\n"
266 |
--------------------------------------------------------------------------------
/installers/uninstall.sh:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 |
3 | : '
4 | %file uninstall.sh
5 | %author Sam Freeside (@snovvcrash)
6 | %date 2018-05-28
7 |
8 | %brief usbrip uninstaller.
9 |
10 | %license
11 | Copyright (C) 2020 Sam Freeside
12 |
13 | This file is part of usbrip.
14 |
15 | usbrip is free software: you can redistribute it and/or modify
16 | it under the terms of the GNU General Public License as published by
17 | the Free Software Foundation, either version 3 of the License, or
18 | (at your option) any later version.
19 |
20 | usbrip is distributed in the hope that it will be useful,
21 | but WITHOUT ANY WARRANTY; without even the implied warranty of
22 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
23 | GNU General Public License for more details.
24 |
25 | You should have received a copy of the GNU General Public License
26 | along with usbrip. If not, see .
27 | %endlicense
28 | '
29 |
30 | # Usage: sudo bash installers/uninstall.sh [-a/--all]
31 |
32 | # --------------- Check for root privileges ----------------
33 |
34 | if [[ $EUID -ne 0 ]]; then
35 | /usr/bin/printf "${R}>>>>${NC} Please run as root:\nsudo %s [-a/--all]\n" "${0}"
36 | exit 1
37 | fi
38 |
39 | # ----------------------- Constants ------------------------
40 |
41 | if [[ -z "${SUDO_USER}" ]]; then
42 | SUDO_USER="root"
43 | fi
44 |
45 | USER_HOME=`getent passwd ${SUDO_USER} | cut -d: -f6`
46 | CONFIG="${USER_HOME}/.config/usbrip"
47 |
48 | OPT="/opt/usbrip"
49 | VAR_OPT="/var/opt/usbrip"
50 | SYMLINK="/usr/local/bin/usbrip"
51 |
52 | G="\033[1;32m" # GREEN
53 | R="\033[1;31m" # RED
54 | NC="\033[0m" # NO COLOR
55 |
56 | # ----------------------- Functions ------------------------
57 |
58 | remove_directory() {
59 | if /bin/rm -r "$1" 2> /dev/null; then
60 | /usr/bin/printf "${G}>>>>${NC} Removed directory: '$1'\n"
61 | fi
62 | }
63 |
64 | # -------------------- Handle switches ---------------------
65 |
66 | if [[ "$1" == "-a" ]] || [[ "$1" == "--all" ]]; then
67 | ALL=true
68 | else
69 | ALL=false
70 | fi
71 |
72 | # ------------------- Remove directories -------------------
73 |
74 | # OPT
75 |
76 | remove_directory "${OPT}"
77 |
78 | # VAR_OPT
79 |
80 | remove_directory "${VAR_OPT}"
81 |
82 | # CONFIG
83 |
84 | remove_directory "${CONFIG}"
85 |
86 | # --------------------- Remove symlink ---------------------
87 |
88 | if /bin/rm "${SYMLINK}" 2> /dev/null; then
89 | /usr/bin/printf "${G}>>>>${NC} Removed symlink: '${SYMLINK}'\n"
90 | fi
91 |
92 | # -------------------------- Done --------------------------
93 |
94 | /usr/bin/printf "${G}>>>>${NC} Done.\n"
95 |
--------------------------------------------------------------------------------
/quick/README.md:
--------------------------------------------------------------------------------
1 | ### Quick `journalctl` output analysis with bash
2 |
3 | ```
4 | $ curl -s https://raw.githubusercontent.com/snovvcrash/usbrip/master/quick/a.sh | bash
5 | $ curl -sL https://git.io/Jvf60 | bash
6 | $ wget -qO- https://raw.githubusercontent.com/snovvcrash/usbrip/master/quick/a.sh | bash
7 | $ wget -qO- https://git.io/Jvf60 | bash
8 | ```
9 |
--------------------------------------------------------------------------------
/quick/a.sh:
--------------------------------------------------------------------------------
1 | o=`journalctl -o short-iso-precise|grep -iw usb|grep -iwe 'Product:\|Manufacturer:\|SerialNumber:\|USB disconnect'|awk '{$3=" ";print $0}'|sed s/" "/" "/`;p=`echo "$o"|cut -d'.' -f1`;echo "$o"|while read -r line;do c=`echo "$line"|awk '{print $1}'|cut -d'.' -f1`;if [[ "$c" != "$p" ]];then p="$c";echo;fi;echo "$c `echo $line|cut -d' ' -f2-100`";done
--------------------------------------------------------------------------------
/requirements-dev.txt:
--------------------------------------------------------------------------------
1 | twine # PyPI
2 | grip # Markdown to PDF
3 |
--------------------------------------------------------------------------------
/requirements.txt:
--------------------------------------------------------------------------------
1 | termcolor
2 | terminaltables
3 | tqdm
4 |
--------------------------------------------------------------------------------
/setup.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python3
2 | # -*- coding: utf-8 -*-
3 |
4 | """LICENSE
5 |
6 | Copyright (C) 2020 Sam Freeside
7 |
8 | This file is part of usbrip.
9 |
10 | usbrip is free software: you can redistribute it and/or modify
11 | it under the terms of the GNU General Public License as published by
12 | the Free Software Foundation, either version 3 of the License, or
13 | (at your option) any later version.
14 |
15 | usbrip is distributed in the hope that it will be useful,
16 | but WITHOUT ANY WARRANTY; without even the implied warranty of
17 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18 | GNU General Public License for more details.
19 |
20 | You should have received a copy of the GNU General Public License
21 | along with usbrip. If not, see .
22 | """
23 |
24 | __author__ = 'Sam Freeside (@snovvcrash)'
25 | __email__ = 'snovvcrash@protonmail.ch'
26 | __license__ = 'GPL-3.0'
27 | __site__ = 'https://github.com/snovvcrash/usbrip'
28 | __brief__ = 'USB device artifacts tracker'
29 |
30 | import glob
31 | import shutil
32 | import sys
33 | import os
34 | from subprocess import check_output
35 |
36 | from setuptools import setup, find_packages, Command
37 | from setuptools.command.install import install
38 |
39 | from usbrip import __version__
40 |
41 |
42 | class LocalInstallCommand(install):
43 | """Custom install command to install local Python dependencies."""
44 | def run(self):
45 | install.run(self)
46 | tools_dir = os.path.join(os.path.abspath(os.path.dirname(__file__)), '3rdPartyTools')
47 |
48 | deps = []
49 | for dep in os.listdir(tools_dir):
50 | if dep.startswith('wheel-'):
51 | wheel = dep
52 | else:
53 | deps.append(dep)
54 |
55 | resolve(wheel, tools_dir)
56 |
57 | for dep in deps:
58 | resolve(dep, tools_dir)
59 |
60 |
61 | class CleanCommand(Command):
62 | """Custom clean command to tidy up the project root."""
63 | CLEAN_FILES = './build ./dist ./*.pyc ./*.tgz ./*.egg-info'.split(' ')
64 |
65 | user_options = []
66 |
67 | def initialize_options(self):
68 | pass
69 |
70 | def finalize_options(self):
71 | pass
72 |
73 | def run(self):
74 | here = os.path.abspath(os.path.dirname(__file__))
75 |
76 | for path_spec in self.CLEAN_FILES:
77 | abs_paths = glob.glob(os.path.normpath(os.path.join(here, path_spec)))
78 | for path in abs_paths:
79 | if not path.startswith(here):
80 | # Die if path in CLEAN_FILES is absolute + outside this directory
81 | raise ValueError(f'{path} is not a path inside {here}')
82 | print(f'[*] Removing {os.path.relpath(path)}')
83 | shutil.rmtree(path)
84 |
85 |
86 | def resolve(dep, path=None):
87 | pip = os.path.join(sys.executable.rsplit('/', 1)[0], 'pip')
88 |
89 | if path:
90 | args = [pip, 'install', os.path.join(path, dep)]
91 | dep_type = 'local'
92 | else:
93 | args = [pip, 'install', dep]
94 | dep_type = 'remote'
95 |
96 | if 'Successfully installed' in check_output(args).decode('utf-8'):
97 | print(f'[*] Resolved ({dep_type}) dependency: {dep}')
98 |
99 |
100 | def parse_requirements(file):
101 | required = []
102 | with open(file, 'r') as f:
103 | for req in f:
104 | if not req.strip().startswith('#'):
105 | required.append(req)
106 |
107 | return required
108 |
109 |
110 | long_description = '''\
111 | Simple CLI forensics tool for tracking \
112 | USB device artifacts (history of USB events) \
113 | on GNU/Linux.\
114 | '''.replace('\t', '')
115 |
116 | keywords = 'forensics cybersecurity infosec usb-history usb-devices'
117 |
118 | resolve('wheel')
119 |
120 | setup(
121 | name='usbrip',
122 | version=__version__,
123 | url=__site__,
124 | author=__author__,
125 | author_email=__email__,
126 | license=__license__,
127 | description=__brief__,
128 | long_description=long_description,
129 | long_description_content_type='text/markdown',
130 | keywords=keywords,
131 | packages=find_packages(),
132 | zip_safe=False,
133 |
134 | python_requires='>=3.6',
135 | install_requires=parse_requirements('requirements.txt'),
136 |
137 | cmdclass={
138 | 'install': LocalInstallCommand,
139 | 'clean': CleanCommand
140 | },
141 |
142 | entry_points={
143 | 'console_scripts': [
144 | 'usbrip=usbrip.__main__:main'
145 | ]
146 | }
147 | )
148 |
--------------------------------------------------------------------------------
/usbrip/__init__.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python3
2 | # -*- coding: utf-8 -*-
3 |
4 | """LICENSE
5 |
6 | Copyright (C) 2020 Sam Freeside
7 |
8 | This file is part of usbrip.
9 |
10 | usbrip is free software: you can redistribute it and/or modify
11 | it under the terms of the GNU General Public License as published by
12 | the Free Software Foundation, either version 3 of the License, or
13 | (at your option) any later version.
14 |
15 | usbrip is distributed in the hope that it will be useful,
16 | but WITHOUT ANY WARRANTY; without even the implied warranty of
17 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18 | GNU General Public License for more details.
19 |
20 | You should have received a copy of the GNU General Public License
21 | along with usbrip. If not, see .
22 | """
23 |
24 | __version__ = '2.2.2-1'
25 |
--------------------------------------------------------------------------------
/usbrip/__main__.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python3
2 | # -*- coding: utf-8 -*-
3 |
4 | # Usage: python3 -m usbrip [-h]
5 |
6 | """LICENSE
7 |
8 | Copyright (C) 2020 Sam Freeside
9 |
10 | This file is part of usbrip.
11 |
12 | usbrip is free software: you can redistribute it and/or modify
13 | it under the terms of the GNU General Public License as published by
14 | the Free Software Foundation, either version 3 of the License, or
15 | (at your option) any later version.
16 |
17 | usbrip is distributed in the hope that it will be useful,
18 | but WITHOUT ANY WARRANTY; without even the implied warranty of
19 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
20 | GNU General Public License for more details.
21 |
22 | You should have received a copy of the GNU General Public License
23 | along with usbrip. If not, see .
24 | """
25 |
26 | __author__ = 'Sam Freeside (@snovvcrash)'
27 | __email__ = 'snovvcrash@protonmail[.]ch'
28 | __date__ = '2018-03-21'
29 | __site__ = 'https://github.com/snovvcrash/usbrip'
30 | __brief__ = 'usbrip project\'s driver program'
31 |
32 | import os
33 | import sys
34 |
35 | import usbrip.lib.core.config as cfg; cfg.DEBUG = '--debug' in sys.argv
36 | import usbrip.lib.utils.timing as timing
37 | from usbrip.lib.core.usbevents import USBEvents
38 | from usbrip.lib.core.usbstorage import USBStorage
39 | from usbrip.lib.core.usbids import USBIDs
40 | from usbrip.lib.core.common import BANNER
41 | from usbrip.lib.core.common import COLUMN_NAMES
42 | from usbrip.lib.core.common import print_critical
43 | from usbrip.lib.core.common import USBRipError
44 | from usbrip.lib.parse.argparser import get_arg_parser
45 | from usbrip.lib.parse.configparser import get_config_parser
46 |
47 |
48 | # ----------------------------------------------------------
49 | # -------------------------- Main --------------------------
50 | # ----------------------------------------------------------
51 |
52 |
53 | def main():
54 | if not len(sys.argv) > 1:
55 | print(BANNER + '\n')
56 | usbrip_arg_error()
57 |
58 | arg_parser = get_arg_parser()
59 | args = arg_parser.parse_args()
60 |
61 | if hasattr(args, 'quiet') and not args.quiet:
62 | if cfg.ISATTY:
63 | print(BANNER + '\n')
64 | else:
65 | print(f'# Done as: usbrip {" ".join(sys.argv[1:])}')
66 | else:
67 | cfg.QUIET = True
68 |
69 | # ----------------------------------------------------------
70 | # ------------------------- Banner -------------------------
71 | # ----------------------------------------------------------
72 |
73 | if args.subparser == 'banner':
74 | print(BANNER)
75 |
76 | # ----------------------------------------------------------
77 | # ----------------------- USB Events -----------------------
78 | # ----------------------------------------------------------
79 |
80 | elif args.subparser == 'events' and args.ue_subparser:
81 | if (args.ue_subparser == 'genauth' or args.ue_subparser == 'violations') and os.geteuid() != 0:
82 | sys.exit('Permission denied. Retry with sudo')
83 |
84 | sieve, repres = validate_ue_args(args)
85 |
86 | # ------------------- USB Events History -------------------
87 |
88 | if args.ue_subparser == 'history':
89 | timing.begin()
90 | ue = USBEvents(args.file)
91 | if ue:
92 | ue.event_history(
93 | args.column,
94 | sieve=sieve,
95 | repres=repres
96 | )
97 |
98 | # -------------------- USB Events Open ---------------------
99 |
100 | elif args.ue_subparser == 'open':
101 | timing.begin()
102 | USBEvents.open_dump(
103 | args.input,
104 | args.column,
105 | sieve=sieve,
106 | repres=repres
107 | )
108 |
109 | # ------------------ USB Events GenAuth -------------------
110 |
111 | elif args.ue_subparser == 'genauth':
112 | timing.begin()
113 | ue = USBEvents(args.file)
114 | if ue:
115 | if ue.generate_auth_json(
116 | args.output,
117 | args.attribute,
118 | sieve=sieve
119 | ):
120 | usbrip_internal_error()
121 | else:
122 | usbrip_internal_error()
123 |
124 | # ----------------- USB Events Violations ------------------
125 |
126 | elif args.ue_subparser == 'violations':
127 | timing.begin()
128 | ue = USBEvents(args.file)
129 | if ue:
130 | ue.search_violations(
131 | args.input,
132 | args.attribute,
133 | args.column,
134 | sieve=sieve,
135 | repres=repres
136 | )
137 |
138 | # ----------------------------------------------------------
139 | # ---------------------- USB Storage -----------------------
140 | # ----------------------------------------------------------
141 |
142 | elif args.subparser == 'storage' and args.us_subparser:
143 | if os.geteuid() != 0:
144 | sys.exit('Permission denied. Retry with sudo')
145 |
146 | if any (not os.path.exists(p) for p in ('/opt/usbrip/', '/var/opt/usbrip', '/usr/local/bin/usbrip')):
147 | sys.exit('The "storage" module can only be used when usbrip is installed via "install.sh" - https://git.io/JJfJq')
148 |
149 | sieve, repres = validate_us_args(args)
150 | timing.begin()
151 | config_parser = get_config_parser()
152 | us = USBStorage()
153 |
154 | # -------------------- USB Storage List --------------------
155 |
156 | if args.us_subparser == 'list':
157 | us.list_storage(
158 | args.storage_type,
159 | config_parser[args.storage_type]['password']
160 | )
161 |
162 | # -------------------- USB Storage Open --------------------
163 |
164 | elif args.us_subparser == 'open':
165 | us.open_storage(
166 | args.storage_type,
167 | config_parser[args.storage_type]['password'],
168 | args.column,
169 | sieve=sieve,
170 | repres=repres
171 | )
172 |
173 | # ------------------- USB Storage Update -------------------
174 |
175 | elif args.us_subparser == 'update':
176 | if us.update_storage(
177 | args.storage_type,
178 | config_parser[args.storage_type]['password'],
179 | input_auth=args.input,
180 | attributes=args.attribute,
181 | compression_level=args.lvl,
182 | sieve=sieve
183 | ):
184 | usbrip_internal_error()
185 |
186 | # ------------------- USB Storage Create -------------------
187 |
188 | elif args.us_subparser == 'create':
189 | if us.create_storage(
190 | args.storage_type,
191 | config_parser[args.storage_type]['password'],
192 | input_auth=args.input,
193 | attributes=args.attribute,
194 | compression_level=args.lvl,
195 | sieve=sieve
196 | ):
197 | usbrip_internal_error()
198 |
199 | # ------------------- USB Storage Passwd -------------------
200 |
201 | elif args.us_subparser == 'passwd':
202 | us.change_password(
203 | args.storage_type,
204 | compression_level=args.lvl
205 | )
206 |
207 | # ----------------------------------------------------------
208 | # ------------------------ USB IDs -------------------------
209 | # ----------------------------------------------------------
210 |
211 | elif args.subparser == 'ids' and args.ui_subparser:
212 | validate_ui_args(args)
213 | timing.begin()
214 | ui = USBIDs()
215 |
216 | # --------------------- USB IDs Search ---------------------
217 |
218 | if args.ui_subparser == 'search':
219 | ui.search_ids(
220 | args.vid,
221 | args.pid,
222 | offline=args.offline
223 | )
224 |
225 | # -------------------- USB IDs Download --------------------
226 |
227 | elif args.ui_subparser == 'download':
228 | try:
229 | usb_ids = ui.prepare_database(offline=False)
230 | except USBRipError as e:
231 | print_critical(str(e), errcode=e.errors['errcode'], initial_error=e.errors['initial_error'])
232 | else:
233 | usb_ids.close()
234 |
235 | else:
236 | subparser = ' ' + args.subparser + ' '
237 | usbrip_arg_error(f'Choose one of the usbrip {args.subparser} actions', subparser=subparser)
238 |
239 |
240 | # ----------------------------------------------------------
241 | # ------------------ Argument validation -------------------
242 | # ----------------------------------------------------------
243 |
244 | # ----------------------- USB Events -----------------------
245 |
246 |
247 | def validate_ue_args(args):
248 | _validate_column_args(args)
249 | _validate_attribute_args(args)
250 | _validate_io_args(args)
251 | _validate_file_args(args)
252 |
253 | return (_validate_sieve_args(args), _validate_repres_args(args))
254 |
255 |
256 | # ---------------------- USB Storage -----------------------
257 |
258 |
259 | def validate_us_args(args):
260 | _validate_storage_type_args(args)
261 | _validate_compression_level_args(args)
262 | _validate_io_args(args)
263 | _validate_attribute_args(args)
264 |
265 | return (_validate_sieve_args(args), _validate_repres_args(args))
266 |
267 |
268 | # ------------------------ USB IDs -------------------------
269 |
270 |
271 | def validate_ui_args(args):
272 | _validate_vid_pid_args(args)
273 |
274 |
275 | # ----------------------------------------------------------
276 | # ---------------- Error Message Generators ----------------
277 | # ----------------------------------------------------------
278 |
279 |
280 | def usbrip_arg_error(message=None, *, subparser=' '):
281 | if message:
282 | print(f'Usage: {sys.argv[0]}{subparser}[-h]\n')
283 | print(sys.argv[0].rsplit('/', 1)[-1] + ': argument error: ' + message, file=sys.stderr)
284 | else:
285 | print(f'Usage: {sys.argv[0]} [-h]')
286 |
287 | sys.exit(1)
288 |
289 |
290 | def usbrip_internal_error():
291 | print(sys.argv[0].rsplit('/', 1)[-1] + ': Internal error occurred', file=sys.stderr)
292 | sys.exit(1)
293 |
294 |
295 | # ----------------------------------------------------------
296 | # ----------------------- Utilities ------------------------
297 | # ----------------------------------------------------------
298 |
299 |
300 | def _validate_column_args(args):
301 | if hasattr(args, 'column') and args.column:
302 | for column in args.column:
303 | if column not in COLUMN_NAMES.keys():
304 | usbrip_arg_error(column + ': Invalid column name')
305 |
306 |
307 | def _validate_sieve_args(args):
308 | if 'external' in args:
309 | sieve = dict(
310 | zip(('external', 'number', 'dates', 'fields'),
311 | (args.external, args.number, args.date, {}))
312 | )
313 |
314 | if args.host:
315 | sieve['fields']['host'] = args.host
316 | if args.vid:
317 | sieve['fields']['vid'] = args.vid
318 | if args.pid:
319 | sieve['fields']['pid'] = args.pid
320 | if args.prod:
321 | sieve['fields']['prod'] = args.prod
322 | if args.manufact:
323 | sieve['fields']['manufact'] = args.manufact
324 | if args.serial:
325 | sieve['fields']['serial'] = args.serial
326 | if args.port:
327 | sieve['fields']['port'] = args.port
328 |
329 | return sieve
330 |
331 | return None
332 |
333 |
334 | def _validate_repres_args(args):
335 | if hasattr(args, 'table') or hasattr(args, 'list'):
336 | repres = dict.fromkeys(('table', 'list', 'smart'), False)
337 |
338 | if hasattr(args, 'table') and args.table:
339 | repres['table'] = True
340 | elif hasattr(args, 'list') and args.list:
341 | repres['list'] = True
342 | else:
343 | repres['smart'] = True
344 |
345 | return repres
346 |
347 | return None
348 |
349 |
350 | def _validate_io_args(args):
351 | if hasattr(args, 'input') and args.input and args.input != '/var/opt/usbrip/trusted/auth.json':
352 | if not os.path.exists(args.input):
353 | usbrip_arg_error(args.input + ': Path does not exist')
354 | elif not os.path.isfile(args.input):
355 | usbrip_arg_error(args.input + ': Not a regular file')
356 |
357 | if hasattr(args, 'output') and os.path.exists(args.output):
358 | usbrip_arg_error(args.output + ': Path already exists')
359 |
360 |
361 | def _validate_attribute_args(args):
362 | if hasattr(args, 'attribute') and args.attribute:
363 | for attribute in args.attribute:
364 | if attribute not in ('vid', 'pid', 'prod', 'manufact', 'serial'):
365 | usbrip_arg_error(attribute + ': Invalid attribute name')
366 |
367 |
368 | def _validate_storage_type_args(args):
369 | if args.storage_type not in ('history', 'violations'):
370 | usbrip_arg_error(args.storage_type + ': Invalid storage type')
371 |
372 | if args.storage_type == 'history':
373 | if hasattr(args, 'input') and args.input and args.input != '/var/opt/usbrip/trusted/auth.json':
374 | usbrip_arg_error('Cannot use "--input" switch with history storage')
375 | if hasattr(args, 'attribute') and args.attribute:
376 | usbrip_arg_error('Cannot use "--attribute" switch with history storage')
377 | elif args.storage_type == 'violations':
378 | if hasattr(args, 'input') and args.input is None:
379 | usbrip_arg_error('Please specify input path for the list of authorized devices (-i)')
380 |
381 |
382 | def _validate_compression_level_args(args):
383 | if hasattr(args, 'lvl') and args.lvl and (len(args.lvl) > 1 or args.lvl not in '0123456789'):
384 | usbrip_arg_error(args.lvl + ': Invalid compression level')
385 |
386 |
387 | def _validate_file_args(args):
388 | if hasattr(args, 'file') and args.file:
389 | for file in args.file:
390 | if not os.path.exists(file):
391 | usbrip_arg_error(file + ': Path does not exist')
392 | elif not os.path.isfile(file):
393 | usbrip_arg_error(file + ': Not a regular file')
394 |
395 |
396 | def _validate_vid_pid_args(args):
397 | if hasattr(args, 'vid') and hasattr(args, 'pid') and not args.vid and not args.pid:
398 | usbrip_arg_error('At least one of --vid/--pid or --download option should be specified')
399 |
400 |
401 | # ----------------------------------------------------------
402 | # -------------------------- RUN ---------------------------
403 | # ----------------------------------------------------------
404 |
405 |
406 | if __name__ == '__main__':
407 | main()
408 |
--------------------------------------------------------------------------------
/usbrip/cron/usbrip.cron:
--------------------------------------------------------------------------------
1 | # Update USB event history every 4 hours
2 | 0 */4 * * * usbrip storage update history -e 2>&1 | tee /var/opt/usbrip/log/$(date "+\%FT\%H\%M\%S").log > /dev/null 2>&1
3 |
4 | # Update USB violation events every 4 hours
5 | 0 */4 * * * usbrip storage update violations /var/opt/usbrip/trusted/auth.json -a vid pid -e 2>&1 | tee /var/opt/usbrip/log/$(date "+\%FT\%H\%M\%S").log > /dev/null 2>&1
6 |
--------------------------------------------------------------------------------
/usbrip/lib/__init__.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python3
2 | # -*- coding: utf-8 -*-
3 |
4 | """LICENSE
5 |
6 | Copyright (C) 2020 Sam Freeside
7 |
8 | This file is part of usbrip.
9 |
10 | usbrip is free software: you can redistribute it and/or modify
11 | it under the terms of the GNU General Public License as published by
12 | the Free Software Foundation, either version 3 of the License, or
13 | (at your option) any later version.
14 |
15 | usbrip is distributed in the hope that it will be useful,
16 | but WITHOUT ANY WARRANTY; without even the implied warranty of
17 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18 | GNU General Public License for more details.
19 |
20 | You should have received a copy of the GNU General Public License
21 | along with usbrip. If not, see .
22 | """
23 |
24 | pass
25 |
--------------------------------------------------------------------------------
/usbrip/lib/core/__init__.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python3
2 | # -*- coding: utf-8 -*-
3 |
4 | """LICENSE
5 |
6 | Copyright (C) 2020 Sam Freeside
7 |
8 | This file is part of usbrip.
9 |
10 | usbrip is free software: you can redistribute it and/or modify
11 | it under the terms of the GNU General Public License as published by
12 | the Free Software Foundation, either version 3 of the License, or
13 | (at your option) any later version.
14 |
15 | usbrip is distributed in the hope that it will be useful,
16 | but WITHOUT ANY WARRANTY; without even the implied warranty of
17 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18 | GNU General Public License for more details.
19 |
20 | You should have received a copy of the GNU General Public License
21 | along with usbrip. If not, see .
22 | """
23 |
24 | pass
25 |
--------------------------------------------------------------------------------
/usbrip/lib/core/common.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python3
2 | # -*- coding: utf-8 -*-
3 |
4 | """LICENSE
5 |
6 | Copyright (C) 2020 Sam Freeside
7 |
8 | This file is part of usbrip.
9 |
10 | usbrip is free software: you can redistribute it and/or modify
11 | it under the terms of the GNU General Public License as published by
12 | the Free Software Foundation, either version 3 of the License, or
13 | (at your option) any later version.
14 |
15 | usbrip is distributed in the hope that it will be useful,
16 | but WITHOUT ANY WARRANTY; without even the implied warranty of
17 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18 | GNU General Public License for more details.
19 |
20 | You should have received a copy of the GNU General Public License
21 | along with usbrip. If not, see .
22 | """
23 |
24 | __author__ = 'Sam Freeside (@snovvcrash)'
25 | __email__ = 'snovvcrash@protonmail[.]ch'
26 | __site__ = 'https://github.com/snovvcrash/usbrip'
27 | __brief__ = 'Common items'
28 |
29 | import os
30 | import sys
31 | import json
32 | import time
33 | import random
34 | from collections import OrderedDict
35 |
36 | from termcolor import colored, cprint
37 |
38 | import usbrip.lib.core.config as cfg
39 | from usbrip import __version__
40 |
41 |
42 | # ----------------------------------------------------------
43 | # ---------------- Configuration file path -----------------
44 | # ----------------------------------------------------------
45 |
46 |
47 | CONFIG_FILE = '/var/opt/usbrip/usbrip.ini'
48 |
49 |
50 | # ----------------------------------------------------------
51 | # ------------------- Unicode constants --------------------
52 | # ----------------------------------------------------------
53 |
54 |
55 | BULLET = '\u2022' # '•', U_BULLET
56 | ABSENCE = '\u2205' # '∅', U_EMPTY_SET
57 | SEPARATOR = '\u2212' # '−', U_MINUS_SIGN
58 |
59 |
60 | # ----------------------------------------------------------
61 | # ------------------------- Banner -------------------------
62 | # ----------------------------------------------------------
63 |
64 |
65 | VERSION = __version__
66 | SITE = __site__
67 |
68 | VERSION_FORMATTED = '\033[0m\033[1;37m{\033[1;34mv%s\033[1;37m}\033[0m' % VERSION
69 | SITE_FORMATTED = '\033[0m\033[4;37m%s\033[0m' % SITE
70 |
71 | BANNER = '''\033[1;33m\
72 |
73 | _ {{4}} %s\033[1;33m
74 | _ _ ___| |_ ___[+]___
75 | | | |_ -| . | _[*] . |
76 | |___|___|___|_| [?] _|
77 | x[^]_| %s
78 | \
79 | ''' % (VERSION_FORMATTED, SITE_FORMATTED)
80 |
81 | E = ('E', 'e', '3')
82 | N = ('N', 'n')
83 | S = ('S', 's', '5')
84 | I = ('I', 'i', '1', '!')
85 |
86 | E,N,S,I = list(map(lambda x: random.choice(x), (E,N,S,I)))
87 | E,N,S,I = list(map(lambda x: colored(x, 'green', 'on_blue') + '\033[1;33m', (E,N,S,I)))
88 |
89 | BANNER = BANNER.replace('+', E, 1)
90 | BANNER = BANNER.replace('*', N, 1)
91 | BANNER = BANNER.replace('?', S, 1)
92 | BANNER = BANNER.replace('^', I, 1)
93 |
94 |
95 | # ----------------------------------------------------------
96 | # -------------------- Exception class ---------------------
97 | # ----------------------------------------------------------
98 |
99 |
100 | class USBRipError(Exception):
101 | def __init__(self, message, *, errors=None):
102 | super().__init__(message)
103 | if not errors:
104 | errors = {}
105 | self.errors = errors
106 | self.errors.setdefault('errcode', 0)
107 | self.errors.setdefault('initial_error', '')
108 |
109 |
110 | # ----------------------------------------------------------
111 | # ----------------------- USB Events -----------------------
112 | # ----------------------------------------------------------
113 |
114 |
115 | COLUMN_NAMES = OrderedDict()
116 |
117 | if cfg.ISATTY:
118 | COLUMN_NAMES['conn'] = colored('Connected', 'magenta', attrs=['bold'])
119 | COLUMN_NAMES['host'] = colored('Host', 'magenta', attrs=['bold'])
120 | COLUMN_NAMES['vid'] = colored('VID', 'magenta', attrs=['bold'])
121 | COLUMN_NAMES['pid'] = colored('PID', 'magenta', attrs=['bold'])
122 | COLUMN_NAMES['prod'] = colored('Product', 'magenta', attrs=['bold'])
123 | COLUMN_NAMES['manufact'] = colored('Manufacturer', 'magenta', attrs=['bold'])
124 | COLUMN_NAMES['serial'] = colored('Serial Number', 'magenta', attrs=['bold'])
125 | COLUMN_NAMES['port'] = colored('Port', 'magenta', attrs=['bold'])
126 | COLUMN_NAMES['disconn'] = colored('Disconnected', 'magenta', attrs=['bold'])
127 | else:
128 | COLUMN_NAMES['conn'] = 'Connected'
129 | COLUMN_NAMES['host'] = 'Host'
130 | COLUMN_NAMES['vid'] = 'VID'
131 | COLUMN_NAMES['pid'] = 'PID'
132 | COLUMN_NAMES['prod'] = 'Product'
133 | COLUMN_NAMES['manufact'] = 'Manufacturer'
134 | COLUMN_NAMES['serial'] = 'Serial Number'
135 | COLUMN_NAMES['port'] = 'Port'
136 | COLUMN_NAMES['disconn'] = 'Disconnected'
137 |
138 |
139 | # ----------------------------------------------------------
140 | # ----------------------- Event Sets -----------------------
141 | # ----------------------------------------------------------
142 |
143 |
144 | def intersect_event_sets(event_set_one, event_set_two):
145 | event_dumped_set = {json.dumps(event) for event in event_set_one}
146 | event_intersection_set = event_dumped_set.intersection([json.dumps(event) for event in event_set_two])
147 | event_intersection = [json.loads(event) for event in event_intersection_set]
148 | event_intersection_sorted = sorted(event_intersection, key=lambda i: i['conn'])
149 |
150 | return event_intersection_sorted
151 |
152 |
153 | def union_event_sets(event_set_one, event_set_two):
154 | event_dumped_set = {json.dumps(event) for event in event_set_one}
155 | event_union_set = event_dumped_set.union([json.dumps(event) for event in event_set_two])
156 | event_union = [json.loads(event) for event in event_union_set]
157 | event_union_sorted = sorted(event_union, key=lambda i: i['conn'])
158 |
159 | return event_union_sorted
160 |
161 |
162 | # ----------------------------------------------------------
163 | # ----------------------- Utilities ------------------------
164 | # ----------------------------------------------------------
165 |
166 |
167 | def os_makedirs(dirname):
168 | try:
169 | os.makedirs(dirname)
170 | except PermissionError as e:
171 | raise USBRipError(
172 | f'Permission denied: "{dirname}"',
173 | errors={'initial_error': str(e)}
174 | )
175 | except OSError as e: # exists
176 | if not os.path.isdir(dirname):
177 | raise USBRipError(
178 | f'Path exists and it is not a directory: "{dirname}"',
179 | errors={'initial_error': str(e)}
180 | )
181 |
182 |
183 | def traverse_dir(source_dir):
184 | return [os.path.join(root, filename)
185 | for root, dirnames, filenames in os.walk(source_dir)
186 | for filename in filenames]
187 |
188 |
189 | def list_files(source_dir):
190 | return [os.path.join(source_dir, filename)
191 | for filename in os.listdir(source_dir)
192 | if os.path.isfile(os.path.join(source_dir, filename))]
193 |
194 |
195 | # ----------------------------------------------------------
196 | # ------------------------ Messages ------------------------
197 | # ----------------------------------------------------------
198 |
199 |
200 | def _get_time(fmt='%H:%M:%S'):
201 | return time.strftime(fmt, time.localtime())
202 |
203 |
204 | def print_info(message):
205 | if cfg.QUIET:
206 | return
207 |
208 | if cfg.ISATTY:
209 | cprint(f'[{_get_time()}] [INFO] {message}', 'green')
210 | else:
211 | print(f'[{_get_time("%Y-%m-%d %H:%M:%S")}] [INFO] {message}')
212 |
213 |
214 | def print_warning(message, *, errcode=0, initial_error=''):
215 | if cfg.QUIET:
216 | return
217 |
218 | if cfg.DEBUG:
219 | if errcode:
220 | print(f'[DEBUG] ERRCODE: {errcode}', file=sys.stderr)
221 | if initial_error:
222 | print(f'[DEBUG] {initial_error}', file=sys.stderr)
223 |
224 | if cfg.ISATTY:
225 | cprint(f'[{_get_time()}] [WARNING] {message}', 'yellow')
226 | else:
227 | print(f'[{_get_time("%Y-%m-%d %H:%M:%S")}] [WARNING] {message}')
228 |
229 |
230 | def print_critical(message, *, errcode=0, initial_error=''):
231 | if cfg.DEBUG:
232 | if errcode:
233 | print(f'[DEBUG] ERRCODE: {errcode}', file=sys.stderr)
234 | if initial_error:
235 | print(f'[DEBUG] {initial_error}', file=sys.stderr)
236 |
237 | if cfg.ISATTY:
238 | cprint(f'[{_get_time()}] [CRITICAL] {message}', 'white', 'on_red', attrs=['bold'])
239 | else:
240 | print(f'[{_get_time("%Y-%m-%d %H:%M:%S")}] [CRITICAL] {message}')
241 |
242 |
243 | def print_secret(message, *, secret=''):
244 | if cfg.ISATTY:
245 | cprint(
246 | '[{}] [SECRET] {} "{}"'.format(
247 | _get_time(),
248 | colored(message, 'white', attrs=['bold']),
249 | colored(secret, 'white', 'on_grey', attrs=['bold'])
250 | ),
251 | 'white', attrs=['bold']
252 | )
253 | else:
254 | print(f'[{_get_time("%Y-%m-%d %H:%M:%S")}] [SECRET] {message} {secret}')
255 |
--------------------------------------------------------------------------------
/usbrip/lib/core/config.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python3
2 | # -*- coding: utf-8 -*-
3 |
4 | """LICENSE
5 |
6 | Copyright (C) 2020 Sam Freeside
7 |
8 | This file is part of usbrip.
9 |
10 | usbrip is free software: you can redistribute it and/or modify
11 | it under the terms of the GNU General Public License as published by
12 | the Free Software Foundation, either version 3 of the License, or
13 | (at your option) any later version.
14 |
15 | usbrip is distributed in the hope that it will be useful,
16 | but WITHOUT ANY WARRANTY; without even the implied warranty of
17 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18 | GNU General Public License for more details.
19 |
20 | You should have received a copy of the GNU General Public License
21 | along with usbrip. If not, see .
22 | """
23 |
24 | __author__ = 'Sam Freeside (@snovvcrash)'
25 | __email__ = 'snovvcrash@protonmail[.]ch'
26 | __site__ = 'https://github.com/snovvcrash/usbrip'
27 | __brief__ = 'Config file containing cross-module variables'
28 |
29 | import sys
30 |
31 | DEBUG = False
32 | QUIET = False
33 |
34 | ISATTY = True if sys.stdout.isatty() else False # enable colored text when terminal output (True), else (| or > for example) no color (False)
35 | ISUTF8 = True if sys.stdout.encoding.upper() == 'UTF-8' else False
36 |
--------------------------------------------------------------------------------
/usbrip/lib/core/usbevents.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python3
2 | # -*- coding: utf-8 -*-
3 |
4 | """LICENSE
5 |
6 | Copyright (C) 2020 Sam Freeside
7 |
8 | This file is part of usbrip.
9 |
10 | usbrip is free software: you can redistribute it and/or modify
11 | it under the terms of the GNU General Public License as published by
12 | the Free Software Foundation, either version 3 of the License, or
13 | (at your option) any later version.
14 |
15 | usbrip is distributed in the hope that it will be useful,
16 | but WITHOUT ANY WARRANTY; without even the implied warranty of
17 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18 | GNU General Public License for more details.
19 |
20 | You should have received a copy of the GNU General Public License
21 | along with usbrip. If not, see .
22 | """
23 |
24 | '''
25 | mount - Does Ubuntu log when USB devices are connected? - Ask Ubuntu
26 | https://askubuntu.com/questions/142050/does-ubuntu-log-when-usb-devices-are-connected
27 | ubuntu 14.04 - method by which I can track down a list of flash drives - Super User
28 | https://superuser.com/questions/1041548/method-by-which-i-can-track-down-a-list-of-flash-drives
29 | monitoring - Monitor history of USB flash drives - Unix & Linux Stack Exchange
30 | https://unix.stackexchange.com/questions/152240/monitor-history-of-usb-flash-drives
31 | '''
32 |
33 | __author__ = 'Sam Freeside (@snovvcrash)'
34 | __email__ = 'snovvcrash@protonmail[.]ch'
35 | __site__ = 'https://github.com/snovvcrash/usbrip'
36 | __brief__ = 'USB events handler'
37 |
38 | import re
39 | import codecs
40 | import gzip
41 | import json
42 | import itertools
43 | import operator
44 | import os
45 | import stat
46 | from datetime import datetime
47 | from collections import OrderedDict, defaultdict
48 | from string import printable
49 | from subprocess import check_output
50 | from io import StringIO
51 | from random import randint
52 |
53 | from terminaltables import AsciiTable, SingleTable
54 | from termcolor import colored, cprint
55 | from tqdm import tqdm
56 |
57 | import usbrip.lib.core.config as cfg
58 | from usbrip.lib.core.common import BULLET
59 | from usbrip.lib.core.common import ABSENCE
60 | from usbrip.lib.core.common import SEPARATOR
61 | from usbrip.lib.core.common import COLUMN_NAMES
62 | from usbrip.lib.core.common import intersect_event_sets
63 | from usbrip.lib.core.common import os_makedirs
64 | from usbrip.lib.core.common import list_files
65 | from usbrip.lib.core.common import print_info
66 | from usbrip.lib.core.common import print_warning
67 | from usbrip.lib.core.common import print_critical
68 | from usbrip.lib.core.common import USBRipError
69 | from usbrip.lib.utils.debug import time_it
70 | from usbrip.lib.utils.debug import time_it_if_debug
71 |
72 |
73 | # ----------------------------------------------------------
74 | # ----------------------- USB Events -----------------------
75 | # ----------------------------------------------------------
76 |
77 |
78 | class USBEvents:
79 |
80 | # SingleTable (uses ANSI escape codes) when termianl output, else (| or > for example) AsciiTable (only ASCII)
81 | TableClass = SingleTable if cfg.ISATTY and cfg.ISUTF8 else AsciiTable
82 |
83 | @time_it_if_debug(cfg.DEBUG, time_it)
84 | def __new__(cls, files=None):
85 | try:
86 | if files:
87 | filtered_history = []
88 | for file in files:
89 | filtered_history.extend(_read_log_file(file))
90 |
91 | else:
92 | print_info('Trying to run journalctl...')
93 |
94 | # child_env = os.environ.copy()
95 | # child_env['LANG'] = 'en_US.utf-8'
96 | # journalctl_out = check_output(['journalctl'], env=child_env).decode('utf-8')
97 |
98 | try:
99 | journalctl_out = check_output([
100 | 'journalctl',
101 | '-o',
102 | 'short-iso-precise'
103 | ]).decode('utf-8')
104 |
105 | except Exception as e:
106 | print_warning(f'Failed to run journalctl: {str(e)}')
107 | filtered_history = _get_filtered_history()
108 |
109 | else:
110 | if '-- Logs begin at' in journalctl_out:
111 | print_info('Successfully ran journalctl')
112 |
113 | filtered_history = _read_log_file(
114 | None,
115 | log=StringIO(journalctl_out),
116 | total=journalctl_out.count('\n')
117 | )
118 |
119 | else:
120 | print_warning(f'An error occurred when running journalctl: {journalctl_out}')
121 | filtered_history = _get_filtered_history()
122 |
123 | except USBRipError as e:
124 | print_critical(str(e), initial_error=e.errors['initial_error'])
125 | return None
126 |
127 | all_events = _parse_history(filtered_history)
128 |
129 | instance = super().__new__(cls)
130 | instance._all_events = all_events # self._all_events
131 | instance._violations = [] # self._violations
132 | instance._events_to_show = None # self._events_to_show
133 | return instance
134 |
135 | # ------------------- USB Events History -------------------
136 |
137 | @time_it_if_debug(cfg.DEBUG, time_it)
138 | def event_history(self, columns, *, indent=4, sieve=None, repres=None):
139 | self._events_to_show = _filter_events(self._all_events, sieve)
140 | if not self._events_to_show:
141 | print_info('No USB events found!')
142 | return
143 |
144 | if not cfg.QUIET and cfg.ISATTY:
145 | choice, abs_filename = _output_choice('event history', 'history.json')
146 | if choice == '2':
147 | try:
148 | _dump_events(self._events_to_show, 'event history', abs_filename, indent)
149 | except USBRipError as e:
150 | print_critical(str(e), initial_error=e.errors['initial_error'])
151 | return
152 |
153 | # elif choice == '1' or choice == '':
154 |
155 | if columns:
156 | table_data = [[COLUMN_NAMES[name] for name in columns]]
157 | else:
158 | columns = [key for key in COLUMN_NAMES.keys()]
159 | table_data = [[val for val in COLUMN_NAMES.values()]]
160 |
161 | _represent_events(self._events_to_show, columns, table_data, 'USB-History-Events', repres)
162 |
163 | # -------------------- USB Events Open ---------------------
164 |
165 | @staticmethod
166 | @time_it_if_debug(cfg.DEBUG, time_it)
167 | def open_dump(input_dump, columns, *, sieve=None, repres=None):
168 | abs_input_dump = os.path.abspath(input_dump)
169 |
170 | print_info(f'Opening USB event dump: "{abs_input_dump}"')
171 |
172 | try:
173 | with open(abs_input_dump, 'r', encoding='utf-8') as dump:
174 | events_dumped = json.load(dump)
175 | except json.decoder.JSONDecodeError as e:
176 | print_critical('Failed to decode event dump (JSON)', initial_error=str(e))
177 | return
178 | except PermissionError as e:
179 | print_critical(
180 | f'Permission denied: "{abs_input_dump}". Retry with sudo',
181 | initial_error=str(e)
182 | )
183 | return
184 |
185 | if not events_dumped:
186 | print_critical('This dump is empty!')
187 | return
188 |
189 | events_to_show = _filter_events(events_dumped, sieve)
190 | if not events_to_show:
191 | print_info('No USB events found!')
192 | return
193 |
194 | if columns:
195 | table_data = [[COLUMN_NAMES[name] for name in columns]]
196 | else:
197 | columns = [key for key in COLUMN_NAMES.keys()]
198 | table_data = [[val for val in COLUMN_NAMES.values()]]
199 |
200 | _represent_events(events_to_show, columns, table_data, 'USB-Event-Dump', repres)
201 |
202 | # ------------------ USB Events GenAuth -------------------
203 |
204 | @time_it_if_debug(cfg.DEBUG, time_it)
205 | def generate_auth_json(self, output_auth, attributes, *, indent=4, sieve=None):
206 | self._events_to_show = _filter_events(self._all_events, sieve)
207 | if not self._events_to_show:
208 | print_info('No USB devices found!')
209 |
210 | rand_id = f'usbrip-{randint(1000, 9999)}'
211 | self._events_to_show += [{
212 | 'conn': rand_id,
213 | 'host': rand_id,
214 | 'vid': rand_id,
215 | 'pid': rand_id,
216 | 'prod': rand_id,
217 | 'manufact': rand_id,
218 | 'serial': rand_id,
219 | 'port': rand_id,
220 | 'disconn': rand_id
221 | }]
222 |
223 | abs_output_auth = os.path.abspath(output_auth)
224 |
225 | try:
226 | dirname = os.path.dirname(abs_output_auth)
227 | os_makedirs(dirname)
228 | except USBRipError as e:
229 | print_critical(str(e), initial_error=e.errors['initial_error'])
230 | return 1
231 | else:
232 | print_info(f'Created directory "{dirname}/"')
233 |
234 | try:
235 | auth_json = open(abs_output_auth, 'w', encoding='utf-8')
236 | except PermissionError as e:
237 | print_critical(f'Permission denied: "{abs_output_auth}". Retry with sudo', initial_error=str(e))
238 | return 1
239 |
240 | print_info('Generating authorized device list (JSON)')
241 |
242 | if not attributes:
243 | attributes = ('vid', 'pid', 'prod', 'manufact', 'serial')
244 |
245 | auth = defaultdict(set)
246 | for event in tqdm(self._events_to_show, ncols=80, unit='dev'):
247 | for key, val in event.items():
248 | if key in attributes and val is not None:
249 | auth[key].add(val)
250 |
251 | auth = {key: list(vals) for key, vals in auth.items()}
252 |
253 | for key in auth.keys():
254 | auth[key].sort()
255 |
256 | json.dump(auth, auth_json, sort_keys=True, indent=indent)
257 | auth_json.close()
258 |
259 | os.chmod(abs_output_auth, stat.S_IRUSR | stat.S_IWUSR) # 600
260 |
261 | print_info(f'New authorized device list: "{abs_output_auth}"')
262 |
263 | # ----------------- USB Events Violations ------------------
264 |
265 | @time_it_if_debug(cfg.DEBUG, time_it)
266 | def search_violations(self, input_auth, attributes, columns, *, indent=4, sieve=None, repres=None):
267 | abs_input_auth = os.path.abspath(input_auth)
268 |
269 | print_info(f'Opening authorized device list: "{abs_input_auth}"')
270 |
271 | try:
272 | auth = _process_auth_list(abs_input_auth, indent)
273 | except json.decoder.JSONDecodeError as e:
274 | print_critical('Failed to decode authorized device list (JSON)', initial_error=str(e))
275 | return
276 |
277 | print_info('Searching for violations')
278 |
279 | if not attributes:
280 | attributes = auth.keys()
281 |
282 | auth_sets = [set(auth[attr]) for attr in attributes]
283 |
284 | for event in tqdm(self._all_events, ncols=80, unit='dev'):
285 | try:
286 | if any(
287 | event[key] is not None and
288 | event[key] not in vals
289 | for key, vals in zip(attributes, auth_sets)
290 | ):
291 | self._violations.append(event)
292 | except KeyError as e:
293 | print_critical('No such attribute in authorized device list', initial_error=str(e))
294 | return
295 |
296 | self._events_to_show = _filter_events(self._violations, sieve)
297 | if not self._events_to_show:
298 | print_info('No USB violation events found!')
299 | return
300 |
301 | if not cfg.QUIET and cfg.ISATTY:
302 | choice, abs_filename = _output_choice('violation', 'viol.json')
303 | if choice == '2':
304 | try:
305 | _dump_events(self._events_to_show, 'violations', abs_filename, indent)
306 | except USBRipError as e:
307 | print_critical(str(e), initial_error=e.errors['initial_error'])
308 | return
309 |
310 | # elif choice == '1' or choice == '':
311 |
312 | if columns:
313 | table_data = [[COLUMN_NAMES[name] for name in columns]]
314 | else:
315 | columns = [key for key in COLUMN_NAMES.keys()]
316 | table_data = [[val for val in COLUMN_NAMES.values()]]
317 |
318 | _represent_events(self._events_to_show, columns, table_data, 'USB-Violation-Events', repres)
319 |
320 |
321 | # ----------------------------------------------------------
322 | # ----------------------- Utilities ------------------------
323 | # ----------------------------------------------------------
324 |
325 |
326 | def _get_filtered_history():
327 | filtered_history = []
328 |
329 | print_info('Searching for log files: "/var/log/syslog*" or "/var/log/messages*"')
330 |
331 | syslog_files = sorted([
332 | filename
333 | for filename in list_files('/var/log/')
334 | if filename.rsplit('/', 1)[1].startswith('syslog')
335 | ])
336 |
337 | if syslog_files:
338 | for syslog in syslog_files:
339 | filtered_history.extend(_read_log_file(syslog))
340 | else:
341 | messages_files = sorted([
342 | filename
343 | for filename in list_files('/var/log/')
344 | if filename.rsplit('/', 1)[1].startswith('messages')
345 | ])
346 |
347 | if messages_files:
348 | for messages in messages_files:
349 | filtered_history.extend(_read_log_file(messages))
350 | else:
351 | raise USBRipError('None of log file types was found!')
352 |
353 | return filtered_history
354 |
355 |
356 | def _read_log_file(filename, log=None, total=None):
357 | filtered = []
358 |
359 | if log is None:
360 | abs_filename = os.path.abspath(filename)
361 |
362 | if abs_filename.endswith('.gz'):
363 | print_info(f'Unpacking "{abs_filename}"')
364 |
365 | try:
366 | log = gzip.open(abs_filename, 'rb')
367 | except PermissionError as e:
368 | print_warning(
369 | f'Permission denied: "{abs_filename}". Retry with sudo',
370 | initial_error=str(e)
371 | )
372 | return filtered
373 | else:
374 | end_of_file = b''
375 | abs_filename = os.path.splitext(abs_filename)
376 |
377 | else:
378 | try:
379 | log = codecs.open(abs_filename, 'r', encoding='utf-8', errors='ignore')
380 | except PermissionError as e:
381 | print_warning(
382 | f'Permission denied: "{abs_filename}". Retry with sudo',
383 | initial_error=str(e)
384 | )
385 | return filtered
386 | else:
387 | end_of_file = ''
388 |
389 | total = sum(1 for line in log)
390 | log.seek(0)
391 |
392 | print_info(f'Reading "{abs_filename}"')
393 |
394 | else:
395 | abs_filename = 'journalctl output'
396 | end_of_file = ''
397 | print_info(f'Reading journalctl output')
398 |
399 | regex = re.compile(r'(?:]|:) usb (.*?): ')
400 | for line in tqdm(iter(log.readline, end_of_file), ncols=80, unit='line', total=total):
401 | if isinstance(line, bytes):
402 | line = line.decode('utf-8', errors='ignore')
403 |
404 | if regex.search(line):
405 | # Case 1 -- Modified Timestamp ("%Y-%m-%dT%H:%M:%S.%f%z")
406 |
407 | date = line[:32].strip()
408 | if date.count(':') == 3:
409 | date = ''.join(line[:32].rsplit(':', 1)) # rreplace(':', '', 1) to remove the last ':' from "1970-01-01T00:00:00.000000-00:00" timestamp if there is one
410 |
411 | try:
412 | date = datetime.strptime(date, '%Y-%m-%dT%H:%M:%S.%f%z') # ex. "1970-01-01T00:00:00.000000-0000"
413 |
414 | except ValueError:
415 | # Case 2 -- Non-Modified Timestamp ("%b %d %H:%M:%S")
416 |
417 | date = line[:15].strip()
418 | if ' ' in date:
419 | date = date.replace(' ', ' 0', 1) # pad day of the week with zero
420 |
421 | try:
422 | date = datetime.strptime(date, '%b %d %H:%M:%S') # ex. "Mar 18 13:56:07"
423 | except ValueError as e:
424 | raise USBRipError(f'Wrong timestamp format found in "{abs_filename}"', errors={'initial_error': str(e)})
425 | else:
426 | date = date.strftime('????-%m-%d %H:%M:%S')
427 | logline = line[15:].strip()
428 |
429 | else:
430 | date = date.strftime('%Y-%m-%d %H:%M:%S')
431 | logline = line[32:].strip()
432 |
433 | if any(pat in line for pat in ('New USB device found, ', 'Product: ', 'Manufacturer: ', 'SerialNumber: ')):
434 | filtered.append((date, 'c', logline))
435 | elif 'disconnect' in line:
436 | filtered.append((date, 'd', logline))
437 |
438 | log.close()
439 | return filtered
440 |
441 |
442 | def _parse_history(filtered_history):
443 | re_vid = re.compile(r'idVendor=(\w+)')
444 | re_pid = re.compile(r'idProduct=(\w+)')
445 | re_prod = re.compile(r'Product: (.*?$)')
446 | re_manufact = re.compile(r'Manufacturer: (.*?$)')
447 | re_serial = re.compile(r'SerialNumber: (.*?$)')
448 | re_port = re.compile(r'usb (.*[0-9]):')
449 |
450 | all_events, curr, link, interrupted = [], -1, 1, False
451 | for date, action, logline in filtered_history:
452 | if action == 'c':
453 | if 'New USB device found, ' in logline:
454 | host = logline.split(' ', 1)[0] # logline -> ' '
455 |
456 | try:
457 | vid = re_vid.search(logline).group(1)
458 | except AttributeError:
459 | vid = None
460 | try:
461 | pid = re_pid.search(logline).group(1)
462 | except AttributeError:
463 | pid = None
464 | try:
465 | port = re_port.search(logline).group(1)
466 | except AttributeError:
467 | port = None
468 |
469 | event = {
470 | 'conn': date,
471 | 'host': host,
472 | 'vid': vid,
473 | 'pid': pid,
474 | 'prod': None,
475 | 'manufact': None,
476 | 'serial': None,
477 | 'port': port,
478 | 'disconn': None
479 | }
480 |
481 | all_events.append(event)
482 | curr += 1
483 | link = 2
484 | interrupted = False
485 |
486 | elif not interrupted:
487 | if link == 2:
488 | try: # if 'Product: ' in logline
489 | prod = re_prod.search(logline).group(1)
490 | except AttributeError:
491 | interrupted = True
492 | else:
493 | all_events[curr]['prod'] = prod
494 | link = 3
495 | elif link == 3:
496 | try: # if 'Manufacturer: ' in logline
497 | manufact = re_manufact.search(logline).group(1)
498 | except AttributeError:
499 | interrupted = True
500 | else:
501 | all_events[curr]['manufact'] = manufact
502 | link = 4
503 | elif link == 4:
504 | try: # if 'SerialNumber: ' in logline
505 | serial = re_serial.search(logline).group(1)
506 | except AttributeError:
507 | pass
508 | else:
509 | all_events[curr]['serial'] = serial
510 | finally:
511 | interrupted = True
512 |
513 | else:
514 | continue
515 |
516 | elif action == 'd':
517 | try:
518 | port = re_port.search(logline).group(1)
519 | except AttributeError:
520 | pass
521 | else:
522 | for i in range(len(all_events)-1, -1, -1):
523 | if all_events[i]['port'] == port:
524 | all_events[i]['disconn'] = date
525 | break
526 |
527 | return all_events
528 |
529 |
530 | '''
531 | def _sort_by_date(unsorted_log):
532 | """For old syslog format."""
533 | # "usorted_log" is a list of ( ('Mon dd hh:mm:ss', 'EVENT'), ['LOG_DATA'] )
534 | return sorted(unsorted_log, key=lambda i: MONTH_ENUM[i[0][0][:3]] + i[0][0][3:])
535 | '''
536 |
537 |
538 | def _process_auth_list(input_auth, indent):
539 | with open(input_auth, 'r+', encoding='utf-8') as auth_json:
540 | #auth = json.load(auth_json, object_pairs_hook=OrderedDict)
541 | auth = json.load(auth_json)
542 | auth_json.seek(0)
543 | for key, vals in auth.items():
544 | auth[key] = list(filter(None, vals))
545 | if not _is_sorted(vals):
546 | auth[key].sort()
547 | json.dump(auth, auth_json, sort_keys=True, indent=indent)
548 | auth_json.truncate()
549 |
550 | return auth
551 |
552 |
553 | def _is_sorted(iterable, reverse=False):
554 | def pairwise(iterable):
555 | a, b = itertools.tee(iterable)
556 | next(b, None)
557 | return zip(a, b)
558 |
559 | compare = operator.ge if reverse else operator.le
560 |
561 | return all(
562 | compare(current_element, next_element)
563 | for current_element, next_element
564 | in pairwise(iterable)
565 | )
566 |
567 |
568 | def _filter_events(all_events, sieve):
569 | # default_sieve = {
570 | # 'external': False,
571 | # 'dates': [],
572 | # 'fields': {},
573 | # 'number': -1
574 | # }
575 |
576 | if sieve is None or sieve == {'external': False, 'dates': [], 'fields': {}, 'number': -1}:
577 | return all_events
578 |
579 | else:
580 | print_info('Filtering events')
581 |
582 | events_by_external = []
583 | if sieve['external']:
584 | for event in all_events:
585 | if event['disconn'] is not None:
586 | events_by_external.append(event)
587 | continue
588 | else:
589 | events_by_external = all_events
590 |
591 | events_by_date = []
592 | if sieve['dates']:
593 | for event in all_events:
594 | for date in sieve['dates']:
595 | if event['conn'].startswith(date):
596 | events_by_date.append(event)
597 | break
598 | continue
599 | else:
600 | events_by_date = all_events
601 |
602 | event_intersection = intersect_event_sets(events_by_external, events_by_date)
603 |
604 | events_to_show = []
605 | if sieve['fields']:
606 | for event in event_intersection:
607 | for key, vals in sieve['fields'].items():
608 | if any(event[key] == val for val in vals):
609 | events_to_show.append(event)
610 | break
611 | else:
612 | events_to_show = event_intersection
613 |
614 | if not events_to_show:
615 | return []
616 |
617 | SIZE = len(events_to_show)
618 | if sieve['number'] <= -1 or sieve['number'] > SIZE:
619 | if sieve['number'] < -1:
620 | print_warning(
621 | f'usbrip can\'t handle dark matter \"--number={sieve["number"]}\", so it will show '
622 | f'all {SIZE} USB history entries available'
623 | )
624 |
625 | elif sieve['number'] > SIZE:
626 | print_warning(
627 | f'USB history has only {SIZE} entries instead of requested {sieve["number"]}, '
628 | f'displaying all of them...'
629 | )
630 |
631 | sieve['number'] = SIZE
632 |
633 | return [events_to_show[SIZE-i] for i in range(sieve['number'], 0, -1)]
634 |
635 |
636 | def _represent_events(events_to_show, columns, table_data, title, repres):
637 | print_info('Preparing collected events')
638 |
639 | if repres is None:
640 | repres = {
641 | 'table': False,
642 | 'list': False,
643 | 'smart': True
644 | }
645 |
646 | max_len = {
647 | 'conn': 19,
648 | 'host': max(max(len(event['host']) for event in events_to_show), len('Host')),
649 | 'vid': 4,
650 | 'pid': 4,
651 | 'prod': max(max(len(str(event['prod'])) for event in events_to_show), len('Product')),
652 | 'manufact': max(max(len(str(event['manufact'])) for event in events_to_show), len('Manufacturer')),
653 | 'serial': max(max(len(str(event['serial'])) for event in events_to_show), len('Serial Number')),
654 | 'port': max(max(len(event['port']) for event in events_to_show), len('Port')),
655 | 'disconn': 19
656 | }
657 |
658 | prev_cday = ''
659 | for event in events_to_show:
660 | if 'conn' in columns:
661 | curr_cday = event['conn'][:10]
662 | if prev_cday != curr_cday:
663 | cday = [f'{curr_cday} {BULLET * (len(event["conn"])-len(curr_cday)-1)}']
664 | table_data.append(cday + [SEPARATOR*max_len[name] for name in columns if name != 'conn'])
665 | prev_cday = curr_cday
666 |
667 | row = []
668 | for name in columns:
669 | if event[name] is None:
670 | event[name] = ABSENCE
671 |
672 | item = event[name]
673 | if name == 'conn' and cfg.ISATTY:
674 | item = colored(item, 'green')
675 | elif name == 'disconn' and cfg.ISATTY:
676 | item = colored(item, 'red')
677 |
678 | row.append(item)
679 |
680 | table_data.append(row)
681 |
682 | event_table = _build_single_table(USBEvents.TableClass, table_data, colored(title, 'white', attrs=['bold']))
683 |
684 | # Display as table
685 | if cfg.ISATTY and (repres['smart'] and event_table.ok or repres['table']):
686 | print_info('Representation: table')
687 | print('\n' + event_table.table)
688 |
689 | # Display as list
690 | elif not cfg.ISATTY or (repres['smart'] and not event_table.ok or repres['list']):
691 | if not event_table.ok:
692 | print_warning('Terminal window is too small to display table properly')
693 | print_warning('Representation: list')
694 | else:
695 | print_info('Representation: list')
696 |
697 | max_len = max(len(str(val)) for event in events_to_show for val in event.values()) + len('Serial Number: ') # max length string
698 | if not max_len // 2:
699 | max_len += 1
700 |
701 | if cfg.ISATTY:
702 | cprint('\n' + title, 'white', attrs=['bold'])
703 | else:
704 | print('\n' + title)
705 |
706 | print(SEPARATOR * max_len)
707 |
708 | for event in events_to_show:
709 | if cfg.ISATTY:
710 | print(colored('Connected: ', 'magenta', attrs=['bold']) + colored(event['conn'], 'green'))
711 | print(colored('Host: ', 'magenta', attrs=['bold']) + event['host'])
712 | print(colored('VID: ', 'magenta', attrs=['bold']) + event['vid'])
713 | print(colored('PID: ', 'magenta', attrs=['bold']) + event['pid'])
714 | print(colored('Product: ', 'magenta', attrs=['bold']) + str(event['prod']))
715 | print(colored('Manufacturer: ', 'magenta', attrs=['bold']) + str(event['manufact']))
716 | print(colored('Serial Number: ', 'magenta', attrs=['bold']) + str(event['serial']))
717 | print(colored('Bus-Port: ', 'magenta', attrs=['bold']) + event['port'])
718 | print(colored('Disconnected: ', 'magenta', attrs=['bold']) + colored(event['disconn'], 'red'))
719 | else:
720 | print('Connected: ' + event['conn'])
721 | print('Host: ' + event['host'])
722 | print('VID: ' + event['vid'])
723 | print('PID: ' + event['pid'])
724 | print('Product: ' + str(event['prod']))
725 | print('Manufacturer: ' + str(event['manufact']))
726 | print('Serial Number: ' + str(event['serial']))
727 | print('Bus-Port: ' + event['port'])
728 | print('Disconnected: ' + event['disconn'])
729 |
730 | print(SEPARATOR * max_len)
731 |
732 |
733 | def _build_single_table(TableClass, table_data, title, align='right', inner_row_border=False):
734 | single_table = TableClass(table_data)
735 | single_table.title = title
736 |
737 | for i in range(len(table_data[0])):
738 | single_table.justify_columns[i] = align
739 |
740 | if inner_row_border:
741 | single_table.inner_row_border = True
742 |
743 | return single_table
744 |
745 |
746 | def _dump_events(events_to_show, list_name, abs_filename, indent):
747 | print_info(f'Generating {list_name} list (JSON)')
748 |
749 | out = []
750 | for event in events_to_show:
751 | tmp_event_dict = OrderedDict()
752 |
753 | for key in ('conn', 'host', 'vid', 'pid', 'prod', 'manufact', 'serial', 'port', 'disconn'):
754 | tmp_event_dict[key] = event[key]
755 |
756 | out.append(tmp_event_dict)
757 |
758 | try:
759 | with open(abs_filename, 'w', encoding='utf-8') as out_json:
760 | json.dump(out, out_json, indent=indent)
761 |
762 | except PermissionError as e:
763 | raise USBRipError(
764 | f'Permission denied: "{abs_filename}". Retry with sudo',
765 | errors={'initial_error': str(e)}
766 | )
767 |
768 | os.chmod(abs_filename, stat.S_IRUSR | stat.S_IWUSR) # 600
769 |
770 | print_info(f'New {list_name} list: "{abs_filename}"')
771 |
772 |
773 | def _output_choice(list_name, default_filename):
774 | while True:
775 | print(f'[?] How would you like your {list_name} list to be generated?\n')
776 |
777 | print(' 1. Terminal stdout')
778 | print(' 2. JSON-file')
779 |
780 | choice = input('\n[>] Please enter the number of your choice (default 1): ')
781 |
782 | if choice == '1' or choice == '':
783 | return (choice, '')
784 |
785 | elif choice == '2':
786 | while True:
787 | filename = input(
788 | f'[>] Please enter the output file name '
789 | f'(default is "{default_filename}"): '
790 | )
791 |
792 | if all(c in printable for c in filename) and len(filename) < 256:
793 | if not filename:
794 | filename = default_filename
795 | elif os.path.splitext(filename)[-1] != '.json':
796 | filename = filename + '.json'
797 |
798 | abs_filename = os.path.join(os.path.abspath(os.getcwd()), filename)
799 |
800 | overwrite = True
801 | if os.path.isfile(abs_filename):
802 | while True:
803 | overwrite = input('[?] File exists. Would you like to overwrite it? [Y/n]: ')
804 | if len(overwrite) == 1 and overwrite in 'Yy' or overwrite == '':
805 | overwrite = True
806 | break
807 | elif len(overwrite) == 1 and overwrite in 'Nn':
808 | overwrite = False
809 | break
810 |
811 | if overwrite:
812 | return (choice, abs_filename)
813 |
--------------------------------------------------------------------------------
/usbrip/lib/core/usbids.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python3
2 | # -*- coding: utf-8 -*-
3 |
4 | """LICENSE
5 |
6 | Copyright (C) 2020 Sam Freeside
7 |
8 | This file is part of usbrip.
9 |
10 | usbrip is free software: you can redistribute it and/or modify
11 | it under the terms of the GNU General Public License as published by
12 | the Free Software Foundation, either version 3 of the License, or
13 | (at your option) any later version.
14 |
15 | usbrip is distributed in the hope that it will be useful,
16 | but WITHOUT ANY WARRANTY; without even the implied warranty of
17 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18 | GNU General Public License for more details.
19 |
20 | You should have received a copy of the GNU General Public License
21 | along with usbrip. If not, see .
22 | """
23 |
24 | '''
25 | USB Vendor/Device IDs Database - Linux-USB.org
26 | http://www.linux-usb.org/usb.ids
27 | '''
28 |
29 | __author__ = 'Sam Freeside (@snovvcrash)'
30 | __email__ = 'snovvcrash@protonmail[.]ch'
31 | __site__ = 'https://github.com/snovvcrash/usbrip'
32 | __brief__ = 'USB IDs handler'
33 |
34 | import re
35 | import socket
36 | import os
37 | from pathlib import Path
38 |
39 | from urllib.request import urlopen
40 |
41 | import usbrip.lib.core.config as cfg
42 | from usbrip.lib.core.common import os_makedirs
43 | from usbrip.lib.core.common import print_info
44 | from usbrip.lib.core.common import print_warning
45 | from usbrip.lib.core.common import print_critical
46 | from usbrip.lib.core.common import USBRipError
47 | from usbrip.lib.utils.debug import time_it
48 | from usbrip.lib.utils.debug import time_it_if_debug
49 |
50 |
51 | # ----------------------------------------------------------
52 | # ------------------------ USB IDs -------------------------
53 | # ----------------------------------------------------------
54 |
55 |
56 | class USBIDs:
57 |
58 | _INTERNET_CONNECTION_ERROR = -1
59 | _SERVER_TIMEOUT_ERROR = -2
60 | _SERVER_CONTENT_ERROR = -3
61 |
62 | @staticmethod
63 | @time_it_if_debug(cfg.DEBUG, time_it)
64 | def search_ids(vid, pid, *, offline=True):
65 | if offline:
66 | print_warning('Offline mode')
67 |
68 | try:
69 | usb_ids = USBIDs.prepare_database(offline=offline)
70 | except USBRipError as e:
71 | print_critical(str(e), errcode=e.errors['errcode'], initial_error=e.errors['initial_error'])
72 | else:
73 | _search_ids_helper(usb_ids, vid, pid)
74 | usb_ids.close()
75 |
76 | @staticmethod
77 | @time_it_if_debug(cfg.DEBUG, time_it)
78 | def prepare_database(*, offline=True):
79 | filename = f'{os.path.abspath(str(Path.home()))}/.config/usbrip/usb.ids'
80 | file_exists = os.path.isfile(filename)
81 |
82 | if file_exists and offline:
83 | usb_ids = open(filename, 'r', encoding='utf-8')
84 | elif file_exists and not offline:
85 | usb_ids = _update_database(filename)
86 | elif not file_exists and not offline:
87 | print_warning('No local database found, trying to download')
88 | usb_ids = _download_database(filename)
89 | elif not file_exists and offline:
90 | raise USBRipError('No local database found')
91 |
92 | return usb_ids
93 |
94 |
95 | # ----------------------------------------------------------
96 | # ----------------------- Utilities ------------------------
97 | # ----------------------------------------------------------
98 |
99 |
100 | def _update_database(filename):
101 | try:
102 | usb_ids = open(filename, 'r+', encoding='utf-8')
103 | except PermissionError as e:
104 | raise USBRipError(
105 | f'Permission denied: "{filename}"',
106 | errors={'initial_error': str(e)}
107 | )
108 |
109 | print_info('Getting current database version')
110 | curr_ver, curr_date = _get_current_version(usb_ids)
111 | print(f'Version: {curr_ver}')
112 | print(f'Date: {curr_date}')
113 |
114 | print_info('Checking local database for update')
115 | db, latest_ver, latest_date, errcode, e = _get_latest_version()
116 |
117 | if errcode:
118 | if errcode == USBIDs._INTERNET_CONNECTION_ERROR:
119 | print_warning(
120 | 'No internet connection, using current version',
121 | errcode=errcode
122 | )
123 |
124 | elif errcode == USBIDs._SERVER_TIMEOUT_ERROR:
125 | print_warning(
126 | 'Server timeout, using current version',
127 | errcode=errcode,
128 | initial_error=e
129 | )
130 |
131 | elif errcode == USBIDs._SERVER_CONTENT_ERROR:
132 | print_warning(
133 | 'Server error, using current version',
134 | errcode=errcode,
135 | initial_error=e
136 | )
137 |
138 | return usb_ids
139 |
140 | if curr_ver != latest_ver and curr_date != latest_date: # if there's newer database version
141 | print('Updating database... ', end='')
142 |
143 | usb_ids.write(db)
144 | usb_ids.truncate()
145 | usb_ids.seek(0)
146 |
147 | print('Done\n')
148 |
149 | print(f'Version: {latest_ver}')
150 | print(f'Date: {latest_date}')
151 |
152 | print_info('Local database is up-to-date')
153 |
154 | return usb_ids
155 |
156 |
157 | def _download_database(filename):
158 | try:
159 | dirname = os.path.dirname(filename)
160 | os_makedirs(dirname)
161 | except USBRipError as e:
162 | raise USBRipError(str(e), errors={'initial_error': e.errors['initial_error']})
163 | else:
164 | print_info(f'Created directory "{dirname}/"')
165 |
166 | try:
167 | usb_ids = open(filename, 'w+', encoding='utf-8')
168 | except PermissionError as e:
169 | raise USBRipError(
170 | f'Permission denied: "{filename}"',
171 | errors={'initial_error': str(e)}
172 | )
173 |
174 | db, latest_ver, latest_date, errcode, e = _get_latest_version()
175 |
176 | if errcode:
177 | usb_ids.close()
178 | os.remove(filename)
179 |
180 | if errcode == USBIDs._INTERNET_CONNECTION_ERROR:
181 | errmsg = 'No internet connection'
182 | elif errcode == USBIDs._SERVER_TIMEOUT_ERROR:
183 | errmsg = 'Server timeout'
184 | elif errcode == USBIDs._SERVER_CONTENT_ERROR:
185 | errmsg = 'Server content error: no version or date found'
186 |
187 | raise USBRipError(errmsg, errors={'errcode': errcode, 'initial_error': e})
188 |
189 | usb_ids.write(db)
190 | usb_ids.seek(0)
191 |
192 | print_info('Database downloaded')
193 |
194 | print(f'Version: {latest_ver}')
195 | print(f'Date: {latest_date}')
196 |
197 | return usb_ids
198 |
199 |
200 | def _get_current_version(usb_ids):
201 | db = usb_ids.read()
202 | usb_ids.seek(0)
203 |
204 | try:
205 | curr_ver = re.search(r'^# Version:\s*(.*?$)', db, re.MULTILINE).group(1)
206 | curr_date = re.search(r'^# Date:\s*(.*?$)', db, re.MULTILINE).group(1)
207 | except AttributeError as e:
208 | raise USBRipError(
209 | 'Invalid database content structure: no version or date found',
210 | errors={'initial_error': str(e)}
211 | )
212 |
213 | return (curr_ver, curr_date)
214 |
215 |
216 | def _get_latest_version():
217 | connected, errcode, e = _check_connection('www.google.com')
218 | if not connected:
219 | return (None, -1, -1, errcode, e)
220 |
221 | print_info('Getting latest version and date')
222 |
223 | try:
224 | html = urlopen('http://www.linux-usb.org/usb.ids', timeout=10).read()
225 | #from requests import get
226 | #resp = get('http://www.linux-usb.org/usb.ids', timeout=10)
227 | except socket.timeout as e:
228 | #except requests.exceptions.Timeout as e:
229 | return (None, -1, -1, USBIDs._SERVER_TIMEOUT_ERROR, str(e))
230 |
231 | db = html.decode('cp1252')
232 | #soup = BeautifulSoup(resp.text, 'html.parser')
233 | #db = soup.text
234 |
235 | try:
236 | latest_ver = re.search(r'^# Version:\s*(.*?$)', db, re.MULTILINE).group(1)
237 | latest_date = re.search(r'^# Date:\s*(.*?$)', db, re.MULTILINE).group(1)
238 | except AttributeError as e:
239 | return (None, -1, -1, USBIDs._SERVER_CONTENT_ERROR, str(e))
240 |
241 | return (db, latest_ver, latest_date, 0, '')
242 |
243 |
244 | def _check_connection(hostname):
245 | try:
246 | host = socket.gethostbyname(hostname)
247 | socket.create_connection((host, 80), 2)
248 | return (True, 0, '')
249 | except Exception as e:
250 | return (False, USBIDs._INTERNET_CONNECTION_ERROR, str(e))
251 |
252 |
253 | def _search_ids_helper(usb_ids, vid, pid):
254 | print('Searching for matches... ', end='')
255 |
256 | if vid and pid:
257 | re_vid = re.compile(rf'^{vid} (.*?$)')
258 | re_pid = re.compile(rf'^\t{pid} (.*?$)')
259 |
260 | for line in iter(usb_ids.readline, ''):
261 | vid_match = re_vid.match(line)
262 | if vid_match:
263 | for subline in iter(usb_ids.readline, ''):
264 | if subline[0] == '\t':
265 | pid_match = re_pid.match(subline)
266 | if pid_match:
267 | print('Done\n')
268 | print(f'Vendor: {vid_match.group(1)}')
269 | print(f'Product: {pid_match.group(1)}')
270 | break
271 | else:
272 | print('Done\n')
273 | print('No such pair of (vendor, product) found')
274 | break
275 | print()
276 | return
277 |
278 | print('Done\n')
279 | print('No such vendor found')
280 |
281 | else: # if (vid and not pid) or (pid and not vid):
282 | if vid and not pid:
283 | matches = re.findall(rf'^{vid} (.*?$)', usb_ids.read(), re.MULTILINE)
284 | else: # if pid and not vid
285 | matches = re.findall(rf'^\t{pid} (.*?$)', usb_ids.read(), re.MULTILINE)
286 |
287 | print('Done\n')
288 | print('| Possible products:')
289 |
290 | if not matches:
291 | print('|_ no results')
292 | else:
293 | for i, match in enumerate(matches):
294 | if i != len(matches)-1:
295 | print(f'| {match}')
296 | else:
297 | print(f'|_ {match}')
298 |
299 | print()
300 |
--------------------------------------------------------------------------------
/usbrip/lib/core/usbstorage.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python3
2 | # -*- coding: utf-8 -*-
3 |
4 | """LICENSE
5 |
6 | Copyright (C) 2020 Sam Freeside
7 |
8 | This file is part of usbrip.
9 |
10 | usbrip is free software: you can redistribute it and/or modify
11 | it under the terms of the GNU General Public License as published by
12 | the Free Software Foundation, either version 3 of the License, or
13 | (at your option) any later version.
14 |
15 | usbrip is distributed in the hope that it will be useful,
16 | but WITHOUT ANY WARRANTY; without even the implied warranty of
17 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18 | GNU General Public License for more details.
19 |
20 | You should have received a copy of the GNU General Public License
21 | along with usbrip. If not, see .
22 | """
23 |
24 | __author__ = 'Sam Freeside (@snovvcrash)'
25 | __email__ = 'snovvcrash@protonmail[.]ch'
26 | __site__ = 'https://github.com/snovvcrash/usbrip'
27 | __brief__ = 'USB Storage handler'
28 |
29 | import re
30 | import json
31 | import subprocess
32 | import os
33 | import stat
34 | from datetime import datetime
35 | from getpass import getpass
36 | from configparser import ConfigParser
37 |
38 | import usbrip.lib.core.config as cfg
39 | from usbrip.lib.core.usbevents import USBEvents
40 | from usbrip.lib.core.usbevents import _filter_events
41 | from usbrip.lib.core.usbevents import _dump_events
42 | from usbrip.lib.core.usbevents import _process_auth_list
43 | from usbrip.lib.core.common import CONFIG_FILE
44 | from usbrip.lib.core.common import USBRipError
45 | from usbrip.lib.core.common import union_event_sets
46 | from usbrip.lib.core.common import print_info
47 | from usbrip.lib.core.common import print_warning
48 | from usbrip.lib.core.common import print_critical
49 | from usbrip.lib.core.common import print_secret
50 | from usbrip.lib.utils.debug import time_it
51 | from usbrip.lib.utils.debug import time_it_if_debug
52 |
53 |
54 | # ----------------------------------------------------------
55 | # ---------------------- USB Storage -----------------------
56 | # ----------------------------------------------------------
57 |
58 |
59 | class USBStorage:
60 |
61 | _STORAGE_BASE = '/var/opt/usbrip/storage'
62 |
63 | _7Z_WRONG_PASSWORD_ERROR = -1
64 | _7Z_PERMISSION_ERROR = -2
65 | _7Z_UNKNOWN_ERROR = -3
66 |
67 | # -------------------- USB Storage List --------------------
68 |
69 | @staticmethod
70 | @time_it_if_debug(cfg.DEBUG, time_it)
71 | def list_storage(storage_type, password):
72 | storage_full_path = os.path.join(USBStorage._STORAGE_BASE, f'{storage_type}.7z')
73 | if not os.path.isfile(storage_full_path):
74 | print_critical(f'Storage not found: "{storage_full_path}"')
75 | return
76 |
77 | try:
78 | out = _7zip_list(storage_full_path, password)
79 | except USBRipError as e:
80 | print_critical(str(e), errcode=e.errors['errcode'], initial_error=e.errors['initial_error'])
81 | return
82 |
83 | if '--' in out:
84 | print(out[out.index('--'):] + '--')
85 | else:
86 | print_critical('Undefined behaviour while listing storage contents', initial_error=out)
87 |
88 | # -------------------- USB Storage Open --------------------
89 |
90 | @staticmethod
91 | @time_it_if_debug(cfg.DEBUG, time_it)
92 | def open_storage(storage_type, password, columns, *, sieve=None, repres=None):
93 | storage_full_path = os.path.join(USBStorage._STORAGE_BASE, f'{storage_type}.7z')
94 | if not os.path.isfile(storage_full_path):
95 | print_critical(f'Storage not found: "{storage_full_path}"')
96 | return
97 |
98 | try:
99 | out = _7zip_unpack(storage_full_path, password)
100 | except USBRipError as e:
101 | print_critical(str(e), errcode=e.errors['errcode'], initial_error=e.errors['initial_error'])
102 | return
103 |
104 | if 'Everything is Ok' in out:
105 | base_filename = re.search(r'([^\n ]*\.json)', _7zip_list(storage_full_path, password), re.MULTILINE).group(1)
106 | json_file = os.path.join(USBStorage._STORAGE_BASE, base_filename)
107 | USBEvents.open_dump(json_file, columns, sieve=sieve, repres=repres)
108 | _shred(json_file)
109 | else:
110 | print_critical('Undefined behaviour while unpacking storage', initial_error=out)
111 |
112 | # ------------------- USB Storage Update -------------------
113 |
114 | @staticmethod
115 | @time_it_if_debug(cfg.DEBUG, time_it)
116 | def update_storage(
117 | storage_type,
118 | password,
119 | *,
120 | input_auth=None,
121 | attributes=None,
122 | compression_level='5',
123 | indent=4,
124 | sieve=None
125 | ):
126 | if storage_type == 'history':
127 | events_to_show = _get_history_events(sieve)
128 | elif storage_type == 'violations':
129 | try:
130 | events_to_show = _get_violation_events(sieve, input_auth, attributes, indent)
131 | except USBRipError as e:
132 | print_critical(str(e), initial_error=e.errors['initial_error'])
133 | return 1
134 |
135 | if events_to_show is None:
136 | return 1
137 |
138 | if events_to_show:
139 | min_date, max_date = _get_dates(events_to_show)
140 | else:
141 | print_info('No events to append')
142 | return 1
143 |
144 | storage_full_path = os.path.join(USBStorage._STORAGE_BASE, f'{storage_type}.7z')
145 | if not os.path.isfile(storage_full_path):
146 | print_critical(f'Storage not found: "{storage_full_path}"')
147 | return 1
148 |
149 | print_info(f'Updating storage: "{storage_full_path}"')
150 |
151 | try:
152 | out = _7zip_unpack(storage_full_path, password)
153 | except USBRipError as e:
154 | print_critical(str(e), errcode=e.errors['errcode'], initial_error=e.errors['initial_error'])
155 | return 1
156 |
157 | if 'Everything is Ok' in out:
158 | base_filename = re.search(r'([^\n ]*\.json)', _7zip_list(storage_full_path, password), re.MULTILINE).group(1)
159 | json_file = os.path.join(USBStorage._STORAGE_BASE, base_filename)
160 | _shred(storage_full_path)
161 |
162 | with open(json_file, 'r', encoding='utf-8') as dump:
163 | events_dumped = json.load(dump)
164 | _shred(json_file)
165 |
166 | merged_events = union_event_sets(events_dumped, events_to_show)
167 |
168 | if len(base_filename) > 20: # len('%Y%m%dT%H%M%S') -> 20
169 | min_date = base_filename[:15]
170 |
171 | new_json_file = os.path.join(USBStorage._STORAGE_BASE, f'{min_date}-{max_date}.json')
172 | _dump_events(merged_events, storage_type, new_json_file, indent)
173 |
174 | try:
175 | out = _7zip_pack(storage_full_path, new_json_file, password, compression_level)
176 | except USBRipError as e:
177 | _shred(new_json_file)
178 | print_critical(str(e), errcode=e.errors['errcode'], initial_error=e.errors['initial_error'])
179 | return 1
180 |
181 | if 'Everything is Ok' in out:
182 | print_info('Storage was successfully updated')
183 | else:
184 | print_critical('Undefined behaviour while creating storage', initial_error=out)
185 |
186 | _shred(new_json_file)
187 |
188 | else:
189 | print_critical('Undefined behaviour while unpacking storage', initial_error=out)
190 |
191 | # ------------------- USB Storage Create -------------------
192 |
193 | @staticmethod
194 | @time_it_if_debug(cfg.DEBUG, time_it)
195 | def create_storage(
196 | storage_type,
197 | password,
198 | *,
199 | input_auth=None,
200 | attributes=None,
201 | compression_level='5',
202 | indent=4,
203 | sieve=None
204 | ):
205 | if storage_type == 'history':
206 | events_to_show = _get_history_events(sieve)
207 | elif storage_type == 'violations':
208 | try:
209 | events_to_show = _get_violation_events(sieve, input_auth, attributes, indent)
210 | except USBRipError as e:
211 | print_critical(str(e), initial_error=e.errors['initial_error'])
212 | return 1
213 |
214 | if events_to_show is None:
215 | return 1
216 |
217 | if events_to_show:
218 | min_date, max_date = _get_dates(events_to_show)
219 | json_file = os.path.join(USBStorage._STORAGE_BASE, f'{min_date}-{max_date}.json')
220 | else:
221 | json_file = os.path.join(USBStorage._STORAGE_BASE, f'{datetime.now().strftime("%Y%m%dT%H%M%S")}.json')
222 |
223 | try:
224 | _dump_events(events_to_show, storage_type, json_file, indent)
225 | except USBRipError as e:
226 | print_critical(str(e), initial_error=e.errors['initial_error'])
227 | return 1
228 |
229 | storage_full_path = os.path.join(USBStorage._STORAGE_BASE, f'{storage_type}.7z')
230 | if os.path.exists(storage_full_path):
231 | _shred(storage_full_path)
232 |
233 | try:
234 | out = _7zip_pack(storage_full_path, json_file, password, compression_level)
235 | except USBRipError as e:
236 | _shred(json_file)
237 | print_critical(str(e), errcode=e.errors['errcode'], initial_error=e.errors['initial_error'])
238 | return 1
239 |
240 | if 'Everything is Ok' in out:
241 | print_info(f'New {storage_type} storage: "{storage_full_path}"')
242 | print_secret('Your password is', secret=password)
243 | _shred(json_file)
244 | else:
245 | print_critical('Undefined behaviour while creating storage', initial_error=out)
246 |
247 | # ------------------- USB Storage Passwd -------------------
248 |
249 | @staticmethod
250 | @time_it_if_debug(cfg.DEBUG, time_it)
251 | def change_password(storage_type, *, compression_level='5'):
252 | storage_full_path = os.path.join(USBStorage._STORAGE_BASE, f'{storage_type}.7z')
253 | if not os.path.isfile(storage_full_path):
254 | print_critical(f'Storage not found: "{storage_full_path}"')
255 | return
256 |
257 | old_password = getpass('Old password: ')
258 | new_password = getpass('New password: ')
259 | confirm_new_password = getpass('Confirm new password: ')
260 |
261 | if new_password != confirm_new_password:
262 | print_critical('Passwords does not match, try again')
263 | return
264 |
265 | try:
266 | out = _7zip_unpack(storage_full_path, old_password)
267 | if 'Everything is Ok' in out:
268 | base_filename = re.search(r'([^\n ]*\.json)', _7zip_list(storage_full_path, old_password), re.MULTILINE).group(1)
269 | json_file = os.path.join(USBStorage._STORAGE_BASE, f'{base_filename}')
270 | _shred(storage_full_path)
271 |
272 | out = _7zip_pack(storage_full_path, json_file, new_password, compression_level)
273 |
274 | if 'Everything is Ok' in out:
275 | print_info('Password was successfully changed')
276 |
277 | conf_parser = ConfigParser(allow_no_value=True)
278 | conf_parser.optionxform = str
279 | conf_parser.read(CONFIG_FILE, encoding='utf-8')
280 | conf_parser.set(storage_type, 'password', new_password)
281 |
282 | with open(CONFIG_FILE, 'w', encoding='utf-8') as f:
283 | conf_parser.write(f)
284 |
285 | os.chmod(CONFIG_FILE, stat.S_IRUSR | stat.S_IWUSR) # 600
286 |
287 | print_info('Configuration file updated')
288 |
289 | else:
290 | print_critical('Undefined behaviour while creating storage', initial_error=out)
291 |
292 | _shred(json_file)
293 |
294 | else:
295 | print_critical('Undefined behaviour while unpacking storage', initial_error=out)
296 |
297 | except USBRipError as e:
298 | print_critical(str(e), errcode=e.errors['errcode'], initial_error=e.errors['initial_error'])
299 | return
300 |
301 |
302 | # ----------------------------------------------------------
303 | # ----------------------- Utilities ------------------------
304 | # ----------------------------------------------------------
305 |
306 |
307 | def _get_history_events(sieve):
308 | ue = USBEvents()
309 | if not ue:
310 | return None
311 |
312 | return _filter_events(ue._all_events, sieve)
313 |
314 |
315 | def _get_violation_events(sieve, input_auth, attributes, indent):
316 | try:
317 | auth = _process_auth_list(input_auth, indent)
318 | except json.decoder.JSONDecodeError as e:
319 | raise USBRipError(
320 | 'Failed to decode authorized device list',
321 | errors={'initial_error': str(e)}
322 | )
323 |
324 | if not attributes:
325 | attributes = auth.keys()
326 |
327 | ue = USBEvents()
328 | if not ue:
329 | return None
330 |
331 | for event in ue._all_events:
332 | try:
333 | if any(
334 | event[key] not in vals and
335 | event[key] is not None
336 | for key, vals in zip(attributes, auth.values())
337 | ):
338 | ue._violations.append(event)
339 | except KeyError as e:
340 | raise USBRipError(
341 | 'No such attribute in authorized device list',
342 | errors={'initial_error': str(e)}
343 | )
344 |
345 | return _filter_events(ue._violations, sieve)
346 |
347 |
348 | def _get_dates(events_to_show):
349 | dates = {event['conn'].replace(' ', 'T').replace('-', '').replace(':', '') for event in events_to_show}
350 | return (min(dates), max(dates))
351 |
352 |
353 | '''
354 | def _create_shadow(password, rounds):
355 | from bcrypt import hashpw, gensalt
356 | hashed = hashpw(password.encode('utf-8'), gensalt(rounds))
357 | with open('/var/opt/usbrip/shadow', 'wb') as f:
358 | f.write(hashed)
359 | '''
360 |
361 |
362 | def _shred(filename):
363 | cmd = [
364 | 'shred',
365 | '-z',
366 | '-u',
367 | '-n',
368 | '7',
369 | filename
370 | ]
371 |
372 | try:
373 | out = subprocess.check_output(cmd).decode('utf-8')
374 | except subprocess.CalledProcessError as e:
375 | print_warning(f'Failed to shred {filename}, using standard rm instead: {str(e)}')
376 | os.remove(filename)
377 |
378 |
379 | def _7zip_list(archive, password):
380 | print_info(f'Listing archive: "{archive}"')
381 |
382 | cmd = [
383 | '7z',
384 | 'l',
385 | archive,
386 | '-p' + password
387 | ]
388 |
389 | out, errcode, errmsg, e = _7zip_subprocess_handler(cmd)
390 | if errcode:
391 | raise USBRipError(errmsg, errors={'errcode': errcode, 'initial_error': e})
392 |
393 | return out
394 |
395 |
396 | def _7zip_unpack(archive, password):
397 | print_info(f'Unpacking archive: "{archive}"')
398 |
399 | cmd = [
400 | '7z',
401 | 'e',
402 | archive,
403 | '-p' + password,
404 | '-o' + USBStorage._STORAGE_BASE,
405 | '-y'
406 | ]
407 |
408 | out, errcode, errmsg, e = _7zip_subprocess_handler(cmd)
409 | if errcode:
410 | raise USBRipError(errmsg, errors={'errcode': errcode, 'initial_error': e})
411 |
412 | return out
413 |
414 |
415 | def _7zip_pack(archive, file, password, compression_level):
416 | print_info(f'Creating storage (7-Zip): "{archive}"')
417 |
418 | cmd = [
419 | '7z',
420 | 'a',
421 | archive,
422 | file,
423 | '-mhe=on',
424 | '-p' + password,
425 | '-mx=' + compression_level
426 | ]
427 |
428 | out, errcode, errmsg, e = _7zip_subprocess_handler(cmd)
429 | if errcode:
430 | raise USBRipError(errmsg, errors={'errcode': errcode, 'initial_error': e})
431 |
432 | os.chmod(archive, stat.S_IRUSR | stat.S_IWUSR) # 600
433 |
434 | return out
435 |
436 |
437 | def _7zip_subprocess_handler(cmd):
438 | try:
439 | out = subprocess.check_output(cmd).decode('utf-8')
440 | except subprocess.CalledProcessError as e:
441 | initial_error = e.output.decode('utf-8')
442 |
443 | if 'Wrong password?' in initial_error:
444 | errmsg = 'Can not open encrypted archive. Wrong password?'
445 | errcode = USBStorage._7Z_WRONG_PASSWORD_ERROR
446 | elif 'can not open output file' in initial_error:
447 | errmsg = 'Permission denied. Retry with sudo'
448 | errcode = USBStorage._7Z_PERMISSION_ERROR
449 | else:
450 | errmsg = 'Something went wrong while working with 7-Zip archive'
451 | errcode = USBStorage._7Z_UNKNOWN_ERROR
452 |
453 | return ('', errcode, errmsg, initial_error)
454 |
455 | return (out, 0, '', '')
456 |
--------------------------------------------------------------------------------
/usbrip/lib/parse/__init__.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python3
2 | # -*- coding: utf-8 -*-
3 |
4 | """LICENSE
5 |
6 | Copyright (C) 2020 Sam Freeside
7 |
8 | This file is part of usbrip.
9 |
10 | usbrip is free software: you can redistribute it and/or modify
11 | it under the terms of the GNU General Public License as published by
12 | the Free Software Foundation, either version 3 of the License, or
13 | (at your option) any later version.
14 |
15 | usbrip is distributed in the hope that it will be useful,
16 | but WITHOUT ANY WARRANTY; without even the implied warranty of
17 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18 | GNU General Public License for more details.
19 |
20 | You should have received a copy of the GNU General Public License
21 | along with usbrip. If not, see .
22 | """
23 |
24 | pass
25 |
--------------------------------------------------------------------------------
/usbrip/lib/parse/argparser.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python3
2 | # -*- coding: utf-8 -*-
3 |
4 | """LICENSE
5 |
6 | Copyright (C) 2020 Sam Freeside
7 |
8 | This file is part of usbrip.
9 |
10 | usbrip is free software: you can redistribute it and/or modify
11 | it under the terms of the GNU General Public License as published by
12 | the Free Software Foundation, either version 3 of the License, or
13 | (at your option) any later version.
14 |
15 | usbrip is distributed in the hope that it will be useful,
16 | but WITHOUT ANY WARRANTY; without even the implied warranty of
17 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18 | GNU General Public License for more details.
19 |
20 | You should have received a copy of the GNU General Public License
21 | along with usbrip. If not, see .
22 | """
23 |
24 | __author__ = 'Sam Freeside (@snovvcrash)'
25 | __email__ = 'snovvcrash@protonmail[.]ch'
26 | __site__ = 'https://github.com/snovvcrash/usbrip'
27 | __brief__ = 'Command line option parser'
28 |
29 | import os
30 | from pathlib import Path
31 | from argparse import ArgumentParser
32 |
33 | from usbrip.lib.core.usbstorage import USBStorage
34 |
35 |
36 | def get_arg_parser():
37 | arg_parser = ArgumentParser()
38 | subparsers = arg_parser.add_subparsers(dest='subparser')
39 |
40 | # ----------------------------------------------------------
41 | # ------------------------- Banner -------------------------
42 | # ----------------------------------------------------------
43 |
44 | build_ub_parser(subparsers)
45 |
46 | # ----------------------------------------------------------
47 | # ----------------------- USB Events -----------------------
48 | # ----------------------------------------------------------
49 |
50 | build_ue_parser(subparsers)
51 |
52 | # ----------------------------------------------------------
53 | # ---------------------- USB Storage -----------------------
54 | # ----------------------------------------------------------
55 |
56 | build_us_parser(subparsers)
57 |
58 | # ----------------------------------------------------------
59 | # ------------------------ USB IDs -------------------------
60 | # ----------------------------------------------------------
61 |
62 | build_ui_parser(subparsers)
63 |
64 | return arg_parser
65 |
66 |
67 | # ----------------------------------------------------------
68 | # ------------------------- Banner -------------------------
69 | # ----------------------------------------------------------
70 |
71 |
72 | def build_ub_parser(subparsers):
73 | subparsers.add_parser(
74 | 'banner',
75 | help='show tool banner'
76 | )
77 |
78 |
79 | # ----------------------------------------------------------
80 | # ----------------------- USB Events -----------------------
81 | # ----------------------------------------------------------
82 |
83 |
84 | def build_ue_parser(subparsers):
85 | ue_parser = subparsers.add_parser(
86 | 'events',
87 | help='work with USB events'
88 | )
89 |
90 | ue_subparsers = ue_parser.add_subparsers(dest='ue_subparser')
91 |
92 | build_ueh_parser(ue_subparsers)
93 | build_ueo_parser(ue_subparsers)
94 | build_ueg_parser(ue_subparsers)
95 | build_uev_parser(ue_subparsers)
96 |
97 |
98 | # ------------------- USB Events History -------------------
99 |
100 |
101 | def build_ueh_parser(subparsers):
102 | ueh_parser = subparsers.add_parser(
103 | 'history',
104 | help='show USB event history'
105 | )
106 |
107 | _parse_debug_args(ueh_parser)
108 | _parse_quiet_args(ueh_parser)
109 | _parse_column_args(ueh_parser)
110 | _parse_sieve_args(ueh_parser)
111 | _parse_repres_args(ueh_parser)
112 | _parse_file_args(ueh_parser)
113 |
114 |
115 | # -------------------- USB Events Open ---------------------
116 |
117 |
118 | def build_ueo_parser(subparsers):
119 | ueo_parser = subparsers.add_parser(
120 | 'open',
121 | help='open USB event dump'
122 | )
123 |
124 | ueo_parser.add_argument(
125 | 'input',
126 | type=str,
127 | help='input path for the event dump'
128 | )
129 |
130 | _parse_debug_args(ueo_parser)
131 | _parse_quiet_args(ueo_parser)
132 | _parse_column_args(ueo_parser)
133 | _parse_sieve_args(ueo_parser)
134 | _parse_repres_args(ueo_parser)
135 | _parse_file_args(ueo_parser)
136 |
137 |
138 | # ------------------ USB Events GenAuth -------------------
139 |
140 |
141 | def build_ueg_parser(subparsers):
142 | ueg_parser = subparsers.add_parser(
143 | 'genauth',
144 | help='generate authorized device list (JSON)'
145 | )
146 |
147 | ueg_parser.add_argument(
148 | 'output',
149 | type=str,
150 | nargs='?',
151 | default='/var/opt/usbrip/trusted/auth.json',
152 | help='output path for the list of authorized devices'
153 | )
154 |
155 | _parse_debug_args(ueg_parser)
156 | _parse_quiet_args(ueg_parser)
157 | _parse_sieve_args(ueg_parser)
158 | _parse_file_args(ueg_parser)
159 |
160 | _parse_attribute_args(
161 | ueg_parser,
162 | help_msg='attributes to include in authorized device list '
163 | '(options: "vid", '
164 | '"pid", '
165 | '"prod", '
166 | '"manufact", '
167 | '"serial")'
168 | )
169 |
170 |
171 | # ----------------- USB Events Violations ------------------
172 |
173 |
174 | def build_uev_parser(subparsers):
175 | uev_parser = subparsers.add_parser(
176 | 'violations',
177 | help='search USB event history for violations '
178 | '(show USB devices that do appear in hist'
179 | 'ory and do NOT appear in authorized devi'
180 | 'ce list)'
181 | )
182 |
183 | uev_parser.add_argument(
184 | 'input',
185 | type=str,
186 | nargs='?',
187 | default='/var/opt/usbrip/trusted/auth.json',
188 | help='input path for the list of authorized devices'
189 | )
190 |
191 | _parse_debug_args(uev_parser)
192 | _parse_quiet_args(uev_parser)
193 | _parse_column_args(uev_parser)
194 | _parse_sieve_args(uev_parser)
195 | _parse_repres_args(uev_parser)
196 | _parse_file_args(uev_parser)
197 |
198 | _parse_attribute_args(
199 | uev_parser,
200 | help_msg='attributes to look through when searching for USB violation events '
201 | '(options: "vid", '
202 | '"pid", '
203 | '"prod", '
204 | '"manufact", '
205 | '"serial")'
206 | )
207 |
208 |
209 | # ----------------------------------------------------------
210 | # ---------------------- USB Storage -----------------------
211 | # ----------------------------------------------------------
212 |
213 |
214 | def build_us_parser(subparsers):
215 | us_parser = subparsers.add_parser(
216 | 'storage',
217 | help='work with USB event storage'
218 | )
219 |
220 | us_subparsers = us_parser.add_subparsers(dest='us_subparser')
221 |
222 | build_usl_parser(us_subparsers)
223 | build_uso_parser(us_subparsers)
224 | build_usu_parser(us_subparsers)
225 | build_usc_parser(us_subparsers)
226 | build_usp_parser(us_subparsers)
227 |
228 |
229 | # -------------------- USB Storage List --------------------
230 |
231 |
232 | def build_usl_parser(subparsers):
233 | usl_parser = subparsers.add_parser(
234 | 'list',
235 | help='list storage contents'
236 | )
237 |
238 | _parse_debug_args(usl_parser)
239 | _parse_quiet_args(usl_parser)
240 | _parse_storage_type_args(usl_parser)
241 |
242 |
243 | # -------------------- USB Storage Open --------------------
244 |
245 |
246 | def build_uso_parser(subparsers):
247 | uso_parser = subparsers.add_parser(
248 | 'open',
249 | help='open storage contents'
250 | )
251 |
252 | _parse_debug_args(uso_parser)
253 | _parse_quiet_args(uso_parser)
254 | _parse_storage_type_args(uso_parser)
255 | _parse_column_args(uso_parser)
256 | _parse_sieve_args(uso_parser)
257 | _parse_repres_args(uso_parser)
258 |
259 |
260 | # ------------------- USB Storage Update -------------------
261 |
262 |
263 | def build_usu_parser(subparsers):
264 | usu_parser = subparsers.add_parser(
265 | 'update',
266 | help='update current storage'
267 | )
268 |
269 | _parse_debug_args(usu_parser)
270 | _parse_quiet_args(usu_parser)
271 | _parse_storage_type_args(usu_parser)
272 | _parse_comperssion_level_args(usu_parser)
273 | _parse_sieve_args(usu_parser)
274 |
275 | _parse_attribute_args(
276 | usu_parser,
277 | help_msg='attributes to look through when searching for USB violation events '
278 | '(options: "vid", '
279 | '"pid", '
280 | '"prod", '
281 | '"manufact", '
282 | '"serial")'
283 | )
284 |
285 | usu_parser.add_argument(
286 | 'input',
287 | type=str,
288 | nargs='?',
289 | default='/var/opt/usbrip/trusted/auth.json',
290 | help='input path for the list of authorized devices'
291 | )
292 |
293 |
294 | # ------------------- USB Storage Create -------------------
295 |
296 |
297 | def build_usc_parser(subparsers):
298 | usc_parser = subparsers.add_parser(
299 | 'create',
300 | help=f'create initial history/violations storage; '
301 | f'storage path is "{USBStorage._STORAGE_BASE}"'
302 | )
303 |
304 | _parse_debug_args(usc_parser)
305 | _parse_quiet_args(usc_parser)
306 | _parse_storage_type_args(usc_parser)
307 | _parse_comperssion_level_args(usc_parser)
308 | _parse_sieve_args(usc_parser)
309 |
310 | _parse_attribute_args(
311 | usc_parser,
312 | help_msg='attributes to look through when searching for USB violation events '
313 | '(options: "vid", '
314 | '"pid", '
315 | '"prod", '
316 | '"manufact", '
317 | '"serial")'
318 | )
319 |
320 | usc_parser.add_argument(
321 | 'input',
322 | type=str,
323 | nargs='?',
324 | default='/var/opt/usbrip/trusted/auth.json',
325 | help='input path for the list of authorized devices'
326 | )
327 |
328 |
329 | # ------------------- USB Storage Passwd -------------------
330 |
331 |
332 | def build_usp_parser(subparsers):
333 | usp_parser = subparsers.add_parser(
334 | 'passwd',
335 | help='change storage password'
336 | )
337 |
338 | _parse_debug_args(usp_parser)
339 | _parse_quiet_args(usp_parser)
340 | _parse_storage_type_args(usp_parser)
341 | _parse_comperssion_level_args(usp_parser)
342 |
343 |
344 | # ----------------------------------------------------------
345 | # ------------------------ USB IDs -------------------------
346 | # ----------------------------------------------------------
347 |
348 |
349 | def build_ui_parser(subparsers):
350 | ui_parser = subparsers.add_parser(
351 | 'ids',
352 | help='work with USB IDs'
353 | )
354 |
355 | ui_subparsers = ui_parser.add_subparsers(dest='ui_subparser')
356 |
357 | build_uis_parser(ui_subparsers)
358 | build_uid_parser(ui_subparsers)
359 |
360 |
361 | # --------------------- USB IDs Search ---------------------
362 |
363 |
364 | def build_uis_parser(subparsers):
365 | uis_parser = subparsers.add_parser(
366 | 'search',
367 | help=f'search by VID and/or PID; '
368 | f'ids database path is "{os.path.abspath(str(Path.home()))}/.config/usbrip/usb.ids"'
369 | )
370 |
371 | _parse_debug_args(uis_parser)
372 | _parse_quiet_args(uis_parser)
373 |
374 | uis_parser.add_argument(
375 | '--vid',
376 | type=str,
377 | default=None,
378 | help='vendor ID'
379 | )
380 |
381 | uis_parser.add_argument(
382 | '--pid',
383 | type=str,
384 | default=None,
385 | help='product ID'
386 | )
387 |
388 | uis_parser.add_argument(
389 | '--offline',
390 | action='store_true',
391 | help='offline mode (no database download/update)'
392 | )
393 |
394 |
395 | # -------------------- USB IDs Download --------------------
396 |
397 |
398 | def build_uid_parser(subparsers):
399 | uid_parser = subparsers.add_parser(
400 | 'download',
401 | help=f'download/update database; '
402 | f'ids database path is "{os.path.abspath(str(Path.home()))}/.config/usbrip/usb.ids"'
403 | )
404 |
405 | _parse_debug_args(uid_parser)
406 | _parse_quiet_args(uid_parser)
407 |
408 |
409 | # ----------------------------------------------------------
410 | # ----------------------- Utilities ------------------------
411 | # ----------------------------------------------------------
412 |
413 |
414 | def _parse_debug_args(parser):
415 | parser.add_argument(
416 | '--debug',
417 | action='store_true',
418 | help='DEBUG mode'
419 | )
420 |
421 |
422 | def _parse_quiet_args(parser):
423 | parser.add_argument(
424 | '-q',
425 | '--quiet',
426 | action='store_true',
427 | help='supress banner, some info messages, '
428 | 'time capture and user iteraction'
429 | )
430 |
431 |
432 | def _parse_column_args(parser):
433 | parser.add_argument(
434 | '-c',
435 | '--column',
436 | nargs='+',
437 | type=str,
438 | default=[],
439 | help='columns to show (options: "conn", '
440 | '"host", '
441 | '"vid", '
442 | '"pid", '
443 | '"prod", '
444 | '"manufact", '
445 | '"serial", '
446 | '"port", '
447 | '"disconn")'
448 | )
449 |
450 |
451 | def _parse_sieve_args(parser):
452 | parser.add_argument(
453 | '-e',
454 | '--external',
455 | action='store_true',
456 | help='show only those devices which have "disconnect" date'
457 | )
458 |
459 | parser.add_argument(
460 | '-n',
461 | '--number',
462 | type=int,
463 | default=-1,
464 | help='number of events to show'
465 | )
466 |
467 | parser.add_argument(
468 | '-d',
469 | '--date',
470 | nargs='+',
471 | type=str,
472 | default=[],
473 | help='filter by dates'
474 | )
475 |
476 | parser.add_argument(
477 | '--host',
478 | nargs='+',
479 | type=str,
480 | default=[],
481 | help='search by hostname'
482 | )
483 |
484 | parser.add_argument(
485 | '--vid',
486 | nargs='+',
487 | type=str,
488 | default=[],
489 | help='search by VIDs'
490 | )
491 |
492 | parser.add_argument(
493 | '--pid',
494 | nargs='+',
495 | type=str,
496 | default=[],
497 | help='search by PIDs'
498 | )
499 |
500 | parser.add_argument(
501 | '--prod',
502 | nargs='+',
503 | type=str,
504 | default=[],
505 | help='search by products'
506 | )
507 |
508 | parser.add_argument(
509 | '--manufact',
510 | nargs='+',
511 | type=str,
512 | default=[],
513 | help='search by manufacturers'
514 | )
515 |
516 | parser.add_argument(
517 | '--serial',
518 | nargs='+',
519 | type=str,
520 | default=[],
521 | help='search by serial numbers'
522 | )
523 |
524 | parser.add_argument(
525 | '--port',
526 | nargs='+',
527 | type=str,
528 | default=[],
529 | help='search by ports'
530 | )
531 |
532 |
533 | def _parse_repres_args(parser):
534 | group_table_list = parser.add_mutually_exclusive_group()
535 |
536 | group_table_list.add_argument(
537 | '-t',
538 | '--table',
539 | action='store_true',
540 | help='represent as table (not list)'
541 | )
542 |
543 | group_table_list.add_argument(
544 | '-l',
545 | '--list',
546 | action='store_true',
547 | help='represent as list (not table)'
548 | )
549 |
550 |
551 | def _parse_attribute_args(parser, *, help_msg):
552 | parser.add_argument(
553 | '-a',
554 | '--attribute',
555 | nargs='+',
556 | type=str,
557 | default=[],
558 | help=help_msg
559 | )
560 |
561 |
562 | def _parse_storage_type_args(parser):
563 | parser.add_argument(
564 | 'storage_type',
565 | type=str,
566 | help='storage type (options: "history", "violations")'
567 | )
568 |
569 |
570 | def _parse_comperssion_level_args(parser):
571 | parser.add_argument(
572 | '--lvl',
573 | type=str,
574 | default='5',
575 | help='compression level (from 0 to 9, default is 0 = no compression)'
576 | )
577 |
578 |
579 | def _parse_file_args(parser):
580 | parser.add_argument(
581 | '-f',
582 | '--file',
583 | nargs='+',
584 | type=str,
585 | default=[],
586 | help='obtain log from the outer files'
587 | )
588 |
--------------------------------------------------------------------------------
/usbrip/lib/parse/configparser.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python3
2 | # -*- coding: utf-8 -*-
3 |
4 | """LICENSE
5 |
6 | Copyright (C) 2020 Sam Freeside
7 |
8 | This file is part of usbrip.
9 |
10 | usbrip is free software: you can redistribute it and/or modify
11 | it under the terms of the GNU General Public License as published by
12 | the Free Software Foundation, either version 3 of the License, or
13 | (at your option) any later version.
14 |
15 | usbrip is distributed in the hope that it will be useful,
16 | but WITHOUT ANY WARRANTY; without even the implied warranty of
17 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18 | GNU General Public License for more details.
19 |
20 | You should have received a copy of the GNU General Public License
21 | along with usbrip. If not, see .
22 | """
23 |
24 | __author__ = 'Sam Freeside (@snovvcrash)'
25 | __email__ = 'snovvcrash@protonmail[.]ch'
26 | __site__ = 'https://github.com/snovvcrash/usbrip'
27 | __brief__ = 'Configuration file parser'
28 |
29 | import os
30 | import stat
31 | from configparser import ConfigParser
32 |
33 | from usbrip.lib.core.common import CONFIG_FILE
34 | from usbrip.lib.core.common import print_info
35 | from usbrip.lib.core.common import print_warning
36 |
37 |
38 | def get_config_parser():
39 | config_parser = ConfigParser(allow_no_value=True)
40 | config_parser.optionxform = str
41 |
42 | os.makedirs(CONFIG_FILE.rsplit('/', 1)[0], exist_ok=True)
43 |
44 | if os.path.isfile(CONFIG_FILE):
45 | config_parser.read(CONFIG_FILE, encoding='utf-8')
46 | print_info(f'Configuration loaded: "{CONFIG_FILE}"')
47 |
48 | else:
49 | print_warning('No configuration file found, creating new one...')
50 |
51 | config_parser.add_section('history')
52 | config_parser.set('history', 'password', 'R1pp3r!')
53 |
54 | config_parser.add_section('violations')
55 | config_parser.set('violations', 'password', 'R1pp3r!')
56 |
57 | print_info(f'New configuration file: "{CONFIG_FILE}"')
58 |
59 | with open(CONFIG_FILE, 'w', encoding='utf-8') as f:
60 | config_parser.write(f)
61 |
62 | os.chmod(CONFIG_FILE, stat.S_IRUSR | stat.S_IWUSR) # 600
63 |
64 | return config_parser
65 |
--------------------------------------------------------------------------------
/usbrip/lib/utils/__init__.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python3
2 | # -*- coding: utf-8 -*-
3 |
4 | """LICENSE
5 |
6 | Copyright (C) 2020 Sam Freeside
7 |
8 | This file is part of usbrip.
9 |
10 | usbrip is free software: you can redistribute it and/or modify
11 | it under the terms of the GNU General Public License as published by
12 | the Free Software Foundation, either version 3 of the License, or
13 | (at your option) any later version.
14 |
15 | usbrip is distributed in the hope that it will be useful,
16 | but WITHOUT ANY WARRANTY; without even the implied warranty of
17 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18 | GNU General Public License for more details.
19 |
20 | You should have received a copy of the GNU General Public License
21 | along with usbrip. If not, see .
22 | """
23 |
24 | pass
25 |
--------------------------------------------------------------------------------
/usbrip/lib/utils/debug.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python3
2 | # -*- coding: utf-8 -*-
3 |
4 | """LICENSE
5 |
6 | Copyright (C) 2020 Sam Freeside
7 |
8 | This file is part of usbrip.
9 |
10 | usbrip is free software: you can redistribute it and/or modify
11 | it under the terms of the GNU General Public License as published by
12 | the Free Software Foundation, either version 3 of the License, or
13 | (at your option) any later version.
14 |
15 | usbrip is distributed in the hope that it will be useful,
16 | but WITHOUT ANY WARRANTY; without even the implied warranty of
17 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18 | GNU General Public License for more details.
19 |
20 | You should have received a copy of the GNU General Public License
21 | along with usbrip. If not, see .
22 | """
23 |
24 | __author__ = 'Sam Freeside (@snovvcrash)'
25 | __email__ = 'snovvcrash@protonmail[.]ch'
26 | __site__ = 'https://github.com/snovvcrash/usbrip'
27 | __brief__ = 'Debug utils'
28 |
29 | import functools
30 | import time
31 |
32 | import usbrip.lib.core.config as cfg
33 |
34 |
35 | def time_it(func):
36 | @functools.wraps(func)
37 | def wrapper(*args, **kwargs):
38 | start = time.time()
39 | result = func(*args, **kwargs)
40 | end = time.time()
41 | print(f'{func.__name__}: {end-start:.3f} seconds')
42 | return result
43 |
44 | return wrapper
45 |
46 |
47 | class time_it_if_debug:
48 | def __init__(self, condition, decorator):
49 | self._condition = cfg.DEBUG
50 | self._decorator = decorator
51 |
52 | def __call__(self, func):
53 | if not self._condition:
54 | return func
55 |
56 | return self._decorator(func)
57 |
--------------------------------------------------------------------------------
/usbrip/lib/utils/timing.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python3
2 | # -*- coding: utf-8 -*-
3 |
4 | """LICENSE
5 |
6 | Copyright (C) 2020 Sam Freeside
7 |
8 | This file is part of usbrip.
9 |
10 | usbrip is free software: you can redistribute it and/or modify
11 | it under the terms of the GNU General Public License as published by
12 | the Free Software Foundation, either version 3 of the License, or
13 | (at your option) any later version.
14 |
15 | usbrip is distributed in the hope that it will be useful,
16 | but WITHOUT ANY WARRANTY; without even the implied warranty of
17 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18 | GNU General Public License for more details.
19 |
20 | You should have received a copy of the GNU General Public License
21 | along with usbrip. If not, see .
22 | """
23 |
24 | __author__ = 'Sam Freeside (@snovvcrash)'
25 | __email__ = 'snovvcrash@protonmail[.]ch'
26 | __site__ = 'https://github.com/snovvcrash/usbrip'
27 | __brief__ = 'Program runtime meter'
28 |
29 | import atexit
30 | import time
31 | import datetime
32 |
33 | import usbrip.lib.core.config as cfg
34 |
35 | START = time.time()
36 |
37 |
38 | def tick(msg, fmt='%H:%M:%S', taken=None):
39 | now = time.strftime(fmt, time.localtime())
40 | print('%s %s' % (msg, now))
41 | if taken:
42 | taken = datetime.timedelta(seconds=taken)
43 | print('[*] Time taken: %s' % taken)
44 |
45 |
46 | def final():
47 | end = time.time()
48 | taken = end - START
49 | tick('[*] Shut down at', fmt='%Y-%m-%d %H:%M:%S', taken=taken)
50 |
51 |
52 | def begin():
53 | if not cfg.QUIET:
54 | atexit.register(final)
55 | tick('[*] Started at', fmt='%Y-%m-%d %H:%M:%S')
56 |
--------------------------------------------------------------------------------