├── core ├── __init__.py ├── mouse.py ├── transfer_type.py ├── pcap_types.py ├── parseutils.py ├── keyboard.py └── usbutils.py ├── assets ├── bitsctf.pcap ├── example.pcap ├── output.pcap ├── sha2017.pcap ├── img │ └── image.png ├── icectf2016.pcapng ├── picoctf2017.pcap ├── test_keyboard.pcap ├── bsidesfThekey.pcapng └── test_keyboard.pcapng ├── tests └── testing.py ├── README.md ├── main.py └── Usb_Keyboard_Parser.py /core/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /core/mouse.py: -------------------------------------------------------------------------------- 1 | class USBMouse(object): 2 | # TODO: 3 | pass 4 | -------------------------------------------------------------------------------- /assets/bitsctf.pcap: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shark-asmx/CTF-Usb_Keyboard_Parser/HEAD/assets/bitsctf.pcap -------------------------------------------------------------------------------- /assets/example.pcap: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shark-asmx/CTF-Usb_Keyboard_Parser/HEAD/assets/example.pcap -------------------------------------------------------------------------------- /assets/output.pcap: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shark-asmx/CTF-Usb_Keyboard_Parser/HEAD/assets/output.pcap -------------------------------------------------------------------------------- /assets/sha2017.pcap: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shark-asmx/CTF-Usb_Keyboard_Parser/HEAD/assets/sha2017.pcap -------------------------------------------------------------------------------- /assets/img/image.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shark-asmx/CTF-Usb_Keyboard_Parser/HEAD/assets/img/image.png -------------------------------------------------------------------------------- /assets/icectf2016.pcapng: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shark-asmx/CTF-Usb_Keyboard_Parser/HEAD/assets/icectf2016.pcapng -------------------------------------------------------------------------------- /assets/picoctf2017.pcap: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shark-asmx/CTF-Usb_Keyboard_Parser/HEAD/assets/picoctf2017.pcap -------------------------------------------------------------------------------- /assets/test_keyboard.pcap: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shark-asmx/CTF-Usb_Keyboard_Parser/HEAD/assets/test_keyboard.pcap -------------------------------------------------------------------------------- /assets/bsidesfThekey.pcapng: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shark-asmx/CTF-Usb_Keyboard_Parser/HEAD/assets/bsidesfThekey.pcapng -------------------------------------------------------------------------------- /assets/test_keyboard.pcapng: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shark-asmx/CTF-Usb_Keyboard_Parser/HEAD/assets/test_keyboard.pcapng -------------------------------------------------------------------------------- /tests/testing.py: -------------------------------------------------------------------------------- 1 | from core.keyboard import USBKeyboard 2 | import glob, os 3 | 4 | class TestPassed: 5 | def __init__(self, filename): 6 | self.filename = filename 7 | def __str__(self): 8 | return f"Test Passed {self.filename}" 9 | 10 | class TestFailed: 11 | def __init__(self, filename): 12 | self.filename = filename 13 | def __str__(self): 14 | return f"Test Failed {self.filename}" 15 | 16 | if __name__ == "__main__": 17 | files = [file for file in glob.glob("assets/*") if os.path.isfile(file)] 18 | for filepath in files: 19 | try: 20 | keyboard = USBKeyboard(filepath) 21 | _ = keyboard.decode() 22 | print(TestPassed(filename=filepath)) 23 | except: 24 | print(TestFailed(filename=filepath)) 25 | 26 | 27 | ## TO RUN THE TEST CASES 28 | ## python3 -m tests.testing -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # USB Keyboard Parser 2 | 3 | **USB Keyboard Parser** The USB Keyboard Parser is an automated tool designed to extract and decode Human Interface Device (HID) data from pcap files. It works with two types of USB link types: 4 | 5 | - `LinkTypeUsbLinuxMmapped`: Captured USB traffic from Linux systems using memory-mapped I/O. 6 | 7 | - `LinkTypeUsbPcap`: Standard USB link type used in regular pcap files for capturing USB traffic. 8 | 9 | **Features** 10 | 11 | Two scripts are currently available: 12 | 13 | - `main.py`: Displays output based on Device Id, providing accurate results compared to `Usb_Keyboard_Parser.py`. It directly parses USB structures without relying on external dependencies and supports both pcap and pcapng formats. 14 | 15 | - `Usb_Keyboard_Parser.py`: Relies on `tshark` to extract the HID payload from packet captures and then decodes the data. 16 | 17 | 18 | #### TODO 19 | 20 | - [ ] Add support for Darwin USB link types. 21 | 22 | #### Bugs 23 | 24 | - Feel free to create issues. 25 | 26 | # Usage : 27 | 28 | **New Script** 29 | 30 | ```bash 31 | python3 main.py 32 | ``` 33 | 34 | ![alt text](assets/img/image.png) 35 | 36 | **Old Script** 37 | 38 | ```bash 39 | python3 Usb_Keyboard_Parser.py 40 | ``` 41 | 42 | 43 | ![image](https://user-images.githubusercontent.com/89577007/236658068-01990364-e717-4a37-9893-9594910c7ee0.png) 44 | -------------------------------------------------------------------------------- /main.py: -------------------------------------------------------------------------------- 1 | from core.keyboard import * 2 | import sys 3 | import os 4 | import shutil 5 | import textwrap 6 | 7 | # Check if terminal supports ANSI colors 8 | USE_COLOR = sys.stdout.isatty() and os.name != 'nt' 9 | 10 | GREEN = "\033[92m" if USE_COLOR else "" 11 | RESET = "\033[0m" if USE_COLOR else "" 12 | 13 | if __name__ == "__main__": 14 | if len(sys.argv) < 2: 15 | sys.stderr.write("Usage: python main.py \n") 16 | sys.exit(1) 17 | 18 | file_path = sys.argv[1] 19 | 20 | if os.path.exists(file_path): 21 | usb_keyboard = USBKeyboard(file_path) 22 | out = usb_keyboard.decode() 23 | terminal_width = shutil.get_terminal_size().columns 24 | max_width = min(terminal_width, 120) 25 | 26 | for ip, data in out.items(): 27 | file_name = ip + " => Host" 28 | decoded_text = ''.join(data) 29 | 30 | print(GREEN + "╔" + "═" * (max_width - 2) + "╗") 31 | print(f"║ {file_name[:max_width - 4]:<{max_width - 4}} ║") 32 | print("╟" + "─" * (max_width - 2) + "╢") 33 | 34 | for line in decoded_text.split("\n"): 35 | wrapped = textwrap.wrap(line, width=max_width - 4) or [""] 36 | for wline in wrapped: 37 | sys.stdout.write("║ ") 38 | for char in wline: 39 | sys.stdout.write(char) 40 | sys.stdout.flush() 41 | sys.stdout.write(" " * (max_width - 4 - len(wline))) 42 | sys.stdout.write(" ║\n") 43 | print("╚" + "═" * (max_width - 2) + "╝" + RESET) 44 | 45 | else: 46 | sys.stderr.write(f"File does not exist at path: {file_path}\n") 47 | sys.stderr.flush() 48 | -------------------------------------------------------------------------------- /core/transfer_type.py: -------------------------------------------------------------------------------- 1 | from enum import Enum 2 | 3 | class URBType(Enum): 4 | URB_COMPLETE = 0x43 5 | URB_SUBMIT = 0x53 6 | 7 | class URBTransferType(Enum): 8 | Iso = 0x0 9 | Interrupt = 0x1 10 | Control = 0x2 11 | Bulk = 0x3 12 | 13 | class URBFunction(Enum): 14 | URB_FUNCTION_CONTROL_TRANSFER = 0x0008 15 | URB_FUNCTION_BULK_OR_INTERRUPT_TRANSFER = 0x0009 16 | URB_FUNCTION_ISO_TRANSFER = 0x000A 17 | URB_FUNCTION_GET_DESCRIPTOR_FROM_DEVICE = 0x000B 18 | URB_FUNCTION_SET_DESCRIPTOR_TO_DEVICE = 0x000C 19 | URB_FUNCTION_SET_FEATURE_TO_DEVICE = 0x000D 20 | URB_FUNCTION_SET_FEATURE_TO_INTERFACE = 0x000E 21 | URB_FUNCTION_SET_FEATURE_TO_ENDPOINT = 0x000F 22 | URB_FUNCTION_CLEAR_FEATURE_TO_DEVICE = 0x0010 23 | URB_FUNCTION_CLEAR_FEATURE_TO_INTERFACE = 0x0011 24 | URB_FUNCTION_CLEAR_FEATURE_TO_ENDPOINT = 0x0012 25 | URB_FUNCTION_GET_STATUS_FROM_DEVICE = 0x0013 26 | URB_FUNCTION_GET_STATUS_FROM_INTERFACE = 0x0014 27 | URB_FUNCTION_GET_STATUS_FROM_ENDPOINT = 0x0015 28 | URB_FUNCTION_VENDOR_DEVICE = 0x0017 29 | URB_FUNCTION_VENDOR_INTERFACE = 0x0018 30 | URB_FUNCTION_VENDOR_ENDPOINT = 0x0019 31 | URB_FUNCTION_CLASS_DEVICE = 0x001A 32 | URB_FUNCTION_CLASS_INTERFACE = 0x001B 33 | URB_FUNCTION_CLASS_ENDPOINT = 0x001C 34 | URB_FUNCTION_CLASS_OTHER = 0x001F 35 | URB_FUNCTION_VENDOR_OTHER = 0x0020 36 | URB_FUNCTION_GET_STATUS_FROM_OTHER = 0x0021 37 | URB_FUNCTION_CLEAR_FEATURE_TO_OTHER = 0x0022 38 | URB_FUNCTION_SET_FEATURE_TO_OTHER = 0x0023 39 | URB_FUNCTION_GET_DESCRIPTOR_FROM_ENDPOINT = 0x0024 40 | URB_FUNCTION_SET_DESCRIPTOR_TO_ENDPOINT = 0x0025 41 | URB_FUNCTION_GET_CONFIGURATION = 0x0026 42 | URB_FUNCTION_GET_INTERFACE = 0x0027 43 | URB_FUNCTION_GET_DESCRIPTOR_FROM_INTERFACE = 0x0028 44 | URB_FUNCTION_SET_DESCRIPTOR_TO_INTERFACE = 0x0029 45 | URB_FUNCTION_GET_MS_FEATURE_DESCRIPTOR = 0x002A 46 | URB_FUNCTION_CONTROL_TRANSFER_EX = 0x0032 47 | URB_FUNCTION_BULK_OR_INTERRUPT_TRANSFER_USING_CHAINED_MDL = 0x0037 48 | URB_FUNCTION_ABORT_PIPE = 0x0002 49 | URB_FUNCTION_SYNC_RESET_PIPE_AND_CLEAR_STALL = 0x001E 50 | URB_FUNCTION_SYNC_RESET_PIPE = 0x0030 51 | URB_FUNCTION_SYNC_CLEAR_STALL = 0x0031 52 | 53 | 54 | 55 | -------------------------------------------------------------------------------- /Usb_Keyboard_Parser.py: -------------------------------------------------------------------------------- 1 | import subprocess,sys,os 2 | import shlex,string 3 | usb_codes = { 4 | "0x04":['a','A'],"0x05":['b','B'], "0x06":['c','C'], "0x07":['d','D'], "0x08":['e','E'], "0x09":['f','F'],"0x0A":['g','G'],"0x0B":['h','H'], "0x0C":['i','I'], "0x0D":['j','J'], "0x0E":['k','K'], "0x0F":['l','L'],"0x10":['m','M'], "0x11":['n','N'], "0x12":['o','O'], "0x13":['p','P'], "0x14":['q','Q'], "0x15":['r','R'],"0x16":['s','S'], "0x17":['t','T'], "0x18":['u','U'], "0x19":['v','V'], "0x1A":['w','W'], "0x1B":['x','X'],"0x1C":['y','Y'], "0x1D":['z','Z'], "0x1E":['1','!'], "0x1F":['2','@'], "0x20":['3','#'], "0x21":['4','$'],"0x22":['5','%'], "0x23":['6','^'], "0x24":['7','&'], "0x25":['8','*'], "0x26":['9','('], "0x27":['0',')'],"0x28":['\n','\n'], "0x29":['[ESC]','[ESC]'], "0x2A":['[BACKSPACE]','[BACKSPACE]'], "0x2B":['\t','\t'],"0x2C":[' ',' '], "0x2D":['-','_'], "0x2E":['=','+'], "0x2F":['[','{'], "0x30":[']','}'], "0x31":['\',"|'],"0x32":['#','~'], "0x33":";:", "0x34":"'\"", "0x36":",<", "0x37":".>", "0x38":"/?","0x39":['[CAPSLOCK]','[CAPSLOCK]'], "0x3A":['F1'], "0x3B":['F2'], "0x3C":['F3'], "0x3D":['F4'], "0x3E":['F5'], "0x3F":['F6'], "0x41":['F7'], "0x42":['F8'], "0x43":['F9'], "0x44":['F10'], "0x45":['F11'],"0x46":['F12'], "0x4F":[u'→',u'→'], "0x50":[u'←',u'←'], "0x51":[u'↓',u'↓'], "0x52":[u'↑',u'↑'] 5 | } 6 | data = "usb.capdata" 7 | filepath = sys.argv[1] 8 | 9 | def keystroke_decoder(filepath,data): 10 | out = subprocess.run(shlex.split("tshark -r %s -Y \"%s\" -T fields -e %s"%(filepath,data,data)),capture_output=True) 11 | output = out.stdout.split() # Last 8 bytes of URB_INTERPRUT_IN 12 | message = [] 13 | modifier =0 14 | count =0 15 | for i in range(len(output)): 16 | buffer = str(output[i])[2:-1] 17 | if (buffer)[:2] == "02" or (buffer)[:2] == "20": 18 | for j in range(1): 19 | count +=1 20 | m ="0x" + buffer[4:6].upper() 21 | if m in usb_codes and m == "0x2A": message.pop(len(message)-1) 22 | elif m in usb_codes: message.append(usb_codes.get(m)[1]) 23 | else: break 24 | else: 25 | if buffer[:2] == "01": 26 | modifier +=1 27 | continue 28 | for j in range(1): 29 | count +=1 30 | m = "0x" + buffer[4:6].upper() 31 | if m in usb_codes and m == "0x2A": message.pop(len(message)-1) 32 | elif m in usb_codes : message.append(usb_codes.get(m)[0]) 33 | else: break 34 | 35 | if modifier != 0: 36 | print(f'[-] Found Modifier in {modifier} packets [-]') 37 | return message 38 | 39 | if len(sys.argv) != 2 or os.path.exists(filepath) != 1: 40 | print("\nUsage : ") 41 | print("\npython Usb_Keyboard_Parser.py ") 42 | print("Created by \t\t\t Sabhya str: 52 | """ 53 | Extracts a null-terminated string from the stream. 54 | """ 55 | start_pos = self.pos 56 | while self.pos < len(stream) and stream[self.pos] != 0x00: 57 | self.pos += 1 58 | result = stream[start_pos:self.pos].decode('utf-8', errors='ignore') 59 | self.pos += 1 # Skip the null terminator 60 | return result 61 | 62 | def unwrap(self,stream, endian=LE): 63 | self.pos += len(stream) 64 | return int.from_bytes(stream, endian) 65 | 66 | @staticmethod 67 | def read_byte(file): 68 | return file.read(BYTE_SIZE) 69 | 70 | def read_word(self, file): 71 | return self.unwrap(file.read(WORD_SIZE)) 72 | 73 | def read_dword(self, file): 74 | return self.unwrap(file.read(DWORD_SIZE)) 75 | 76 | def read_qword(self, file): 77 | return self.unwrap(file.read(QWORD_SIZE)) 78 | 79 | @staticmethod 80 | def read_bytes(file, length): 81 | # NOTE: For marking EOF 82 | if length < 0: return None 83 | return file.read(length) 84 | 85 | def skip_bytes(self,file, pos): 86 | self.pos += pos 87 | file.read(pos) 88 | 89 | def skip_pos(self, file): 90 | pass 91 | 92 | def reset_count(self): 93 | self.count = 0x0 94 | 95 | def get_byte(self,stream): 96 | self.count += 1 97 | return stream[self.count - 1] 98 | 99 | def get_word(self,stream, endian=LE): # 2 bytes 100 | self.count += 2 101 | return int.from_bytes(stream[self.count - 2:self.count], endian) 102 | 103 | def get_dword(self,stream, endian=LE): # 4 bytes 104 | self.count += 4 105 | return int.from_bytes(stream[self.count - 4:self.count], endian) 106 | 107 | def get_qword(self,stream, endian=LE): # 8 bytes 108 | self.count += 8 109 | return int.from_bytes(stream[self.count - 8 :self.count], endian) 110 | 111 | def get_bytes(self,stream, length): 112 | return stream[self.count : self.count + length] 113 | 114 | def _le_to_be(num): return ((num >> 24) & 0xFF) | ((num >> 8) & 0xFF00) | ((num << 8) & 0xFF0000) | ((num << 24) & 0xFF000000) 115 | 116 | def read_file(name): 117 | """Returns the address of file where it being loaded""" 118 | return open(name, 'rb') -------------------------------------------------------------------------------- /core/keyboard.py: -------------------------------------------------------------------------------- 1 | from core.usbutils import * 2 | from core.transfer_type import * 3 | from enum import Enum 4 | 5 | class USBDeviceType(Enum): 6 | KEYBOARD=0x0 7 | MOUSE=0x1 8 | 9 | class KeyMapping: 10 | usb_codes = { 11 | "0x04": ['a', 'A'], "0x05": ['b', 'B'], "0x06": ['c', 'C'], "0x07": ['d', 'D'], "0x08": ['e', 'E'], 12 | "0x09": ['f', 'F'], "0x0A": ['g', 'G'], "0x0B": ['h', 'H'], "0x0C": ['i', 'I'], "0x0D": ['j', 'J'], 13 | "0x0E": ['k', 'K'], "0x0F": ['l', 'L'], "0x10": ['m', 'M'], "0x11": ['n', 'N'], "0x12": ['o', 'O'], 14 | "0x13": ['p', 'P'], "0x14": ['q', 'Q'], "0x15": ['r', 'R'], "0x16": ['s', 'S'], "0x17": ['t', 'T'], 15 | "0x18": ['u', 'U'], "0x19": ['v', 'V'], "0x1A": ['w', 'W'], "0x1B": ['x', 'X'], "0x1C": ['y', 'Y'], 16 | "0x1D": ['z', 'Z'], "0x1E": ['1', '!'], "0x1F": ['2', '@'], "0x20": ['3', '#'], "0x21": ['4', '$'], 17 | "0x22": ['5', '%'], "0x23": ['6', '^'], "0x24": ['7', '&'], "0x25": ['8', '*'], "0x26": ['9', '('], 18 | "0x27": ['0', ')'], "0x28": ['\n', '\n'], "0x29": ['[ESC]', '[ESC]'], "0x2A": ['[BACKSPACE]', '[BACKSPACE]'], 19 | "0x2B": ['\t', '\t'], "0x2C": [' ', ' '], "0x2D": ['-', '_'], "0x2E": ['=', '+'], "0x2F": ['[', '{'], 20 | "0x30": [']', '}'], "0x31": ['\',"|'], "0x32": ['#', '~'], "0x33": ";:", "0x34": "'\"", "0x36": ",<", 21 | "0x37": ".>", "0x38": "/?", "0x39": ['[CAPSLOCK]', '[CAPSLOCK]'], "0x3A": ['F1'], "0x3B": ['F2'], 22 | "0x3C": ['F3'], "0x3D": ['F4'], "0x3E": ['F5'], "0x3F": ['F6'], "0x41": ['F7'], "0x42": ['F8'], "0x43": ['F9'], 23 | "0x44": ['F10'], "0x45": ['F11'], "0x46": ['F12'], "0x4F": [u'→', u'→'], "0x50": [u'←', u'←'], 24 | "0x51": [u'↓', u'↓'], "0x52": [u'↑', u'↑'] 25 | } 26 | 27 | class Result(KeyMapping): 28 | def __init__(self): 29 | self.devices = dict() 30 | self.type = None 31 | 32 | def register_new_device(self, ip): 33 | self.devices[ip] = list() 34 | 35 | @staticmethod 36 | def _special_keypress(val): 37 | if val[0x0] == 0x20 or val[0x0] == 0x02: 38 | return True 39 | else: 40 | return False 41 | 42 | def _decode_the_strokes(self, val, _id): 43 | tmp = self.usb_codes.get( 44 | "0x" + str(hex(val[0x2]))[2:].zfill(2).upper() 45 | ) 46 | if tmp is not None: 47 | if tmp[0] == self.usb_codes.get('0x2A')[0]: 48 | # print(self.devices[_id]) 49 | self.devices[_id].pop( ) # Remove if backspace is pressed 50 | return None 51 | if self._special_keypress(val): 52 | return tmp[1] 53 | else: return tmp[0] 54 | else: return None 55 | 56 | def push_result(self, out): 57 | if out['id'] not in self.devices: 58 | self.register_new_device(out['id']) 59 | tmp = self._decode_the_strokes(out['data'], out['id']) 60 | if tmp is not None: 61 | self.devices[out['id']].append(tmp) 62 | 63 | def dump_result(self): 64 | return self.devices 65 | # result = "" 66 | # for _, val in self.devices.items(): 67 | # result += (''.join(val)) 68 | # return _, result 69 | 70 | class USBKeyboard(Packet, PCAPng): 71 | def __init__(self, filename): 72 | super().__init__(filename) 73 | self.enhanced_packet_block = None 74 | self.urb_fields = None 75 | self.linux_mapped_urb_fields = None 76 | self.pcap_record_fields = None 77 | self.result = Result() 78 | 79 | def _is_towards_host(self): 80 | q_value = self.urb_fields.get('urb_type') 81 | if q_value is not None: 82 | return True if URBType(q_value) == URBType(0x43) else False 83 | q_value = self.urb_fields.get('irp_info') 84 | if q_value is not None: 85 | return True if q_value == 1 else False 86 | 87 | def _is_urb_interrupt(self): 88 | # print("URB TRANSFER TYPE :: ", self.urb_fields['urb_transfer_type']) 89 | if URBTransferType(self.urb_fields['urb_transfer_type']) == URBTransferType(0x1): 90 | return True 91 | return False 92 | 93 | def _is_urb_bulk_interrupt(self): 94 | if ((URBFunction(self.urb_fields['urb_function']) == URBFunction(0x9) ) or 95 | (URBFunction(self.urb_fields['urb_function']) == URBFunction(0x37) )): 96 | return True 97 | return False 98 | 99 | def is_eof(self): 100 | return True if self.pcap_record_fields['frame_len'] == 0x0 else False 101 | 102 | def is_interrupt(self): 103 | return self._is_urb_interrupt() 104 | 105 | def _process_parsing(self): 106 | if NetworkType(self.get_network_type()) == NetworkType(0xdc): 107 | # NOTE: Assign to pcap_record_fields to avoid multiple checks 108 | if self.is_pcapng: 109 | return self._parse_linux_mapped_urb_fields(self.enhanced_packet_block["frame_buff"]) 110 | else: return self._parse_linux_mapped_urb_fields(self.pcap_record_fields['frame_buff']) 111 | # TODO: Abstract it 112 | elif NetworkType(self.get_network_type()) == NetworkType(0xf9): 113 | if self.is_pcapng: 114 | return self.read_usb(self.enhanced_packet_block["frame_buff"]) 115 | else: return self.read_usb(self.pcap_record_fields['frame_buff']) 116 | 117 | def _check_network_type(self): 118 | if self.is_pcapng: 119 | if NetworkType(self.get_network_type()) == NetworkType(0xdc): 120 | return self.parse_enhanced_block(self.pkt) 121 | elif NetworkType(self.get_network_type()) == NetworkType(0xf9): 122 | # return self.read_usb(self.parse_enhanced_block(self.pkt)) 123 | return self.parse_enhanced_block(self.pkt) 124 | 125 | # Parses the PCAP structure !! 126 | # if NetworkType(self.get_network_type()) == NetworkType(0xdc): 127 | # return self._parse_linux_mapped_urb_fields(self.pcap_record_fields['frame_buff']) 128 | # # TODO: Abstract it 129 | # elif NetworkType(self.get_network_type()) == NetworkType(0xf9): 130 | # return self.read_usb(self.pcap_record_fields['frame_buff']) 131 | return self._process_parsing() 132 | 133 | def decode_pcapng(self): 134 | while True: 135 | self.enhanced_packet_block = self._check_network_type() 136 | if self.enhanced_packet_block is None: # Marks end 137 | break 138 | self.urb_fields = self._process_parsing() 139 | if self.urb_fields is None: continue 140 | if self.is_interrupt() and self._is_towards_host() and len(self.urb_fields.get('pkt_data')) ==0x8: 141 | # print(self.urb_fields['pkt_data']) 142 | self.result.push_result( 143 | out={"id": self.urb_fields['pseudo_ip'], "data": self.urb_fields['pkt_data']} 144 | ) 145 | 146 | 147 | def decode_pcap(self): 148 | while True: 149 | self.pcap_record_fields = self._parse_pcap_record(self.pkt) 150 | if self.is_eof(): 151 | break 152 | 153 | self.urb_fields = self._check_network_type() 154 | # When frame is either CONTROL or ISO CHRONOUS, returns None 155 | if self.urb_fields is None: 156 | # skipped the current frame 157 | continue 158 | 159 | # TODO: AVOID CHECK ON DATA LENGTH 160 | if self.is_interrupt() and self._is_towards_host() and len(self.urb_fields.get('pkt_data')) ==0x8: 161 | # print(self.urb_fields['pkt_data']) 162 | self.result.push_result( 163 | out={"id": self.urb_fields['pseudo_ip'], "data": self.urb_fields['pkt_data']} 164 | ) 165 | 166 | def decode(self): 167 | self.read_pcap() 168 | if self.is_pcapng: 169 | self.decode_pcapng() 170 | else: 171 | self.decode_pcap() 172 | 173 | # # When frame is either CONTROL or ISO CHRONOUS, returns None 174 | # if self.urb_fields is None: 175 | # # skipped the current frame 176 | # continue 177 | # 178 | # # TODO: AVOID CHECK ON DATA LENGTH 179 | # if self.is_interrupt() and self._is_towards_host() and len(self.urb_fields.get('pkt_data')) ==0x8: 180 | # # print(self.urb_fields['pkt_data']) 181 | # self.result.push_result( 182 | # out={"id": self.urb_fields['pseudo_ip'], "data": self.urb_fields['pkt_data']} 183 | # ) 184 | return self.result.dump_result() 185 | 186 | -------------------------------------------------------------------------------- /core/usbutils.py: -------------------------------------------------------------------------------- 1 | BIG_ENDIAN_PCAP_HEADER = 0xA1B2C3D4 2 | LITTLE_ENDIAN_PCAP_HEADER = 0xD4C3B2A1 3 | PCAPNG_HEADER=0xa0d0d0a 4 | 5 | # Imports the enum class from pcap_types.py 6 | from core.pcap_types import * 7 | from core.parseutils import * 8 | from core.transfer_type import * 9 | 10 | class USBParser(ParseUtils): 11 | def __init__(self): 12 | super().__init__() 13 | 14 | self._pcap_record_fields = { 15 | "timestamp" : self.read_dword, 16 | "time_microseconds" : self.read_dword, 17 | "octet_len": self.read_dword, 18 | "frame_len" : self.read_dword, 19 | "frame_buff" : self.read_bytes, 20 | } 21 | 22 | self._urb_fields = { 23 | "pseudo_header": self.get_word, 24 | "irp_id": self.get_qword, 25 | "status": self.get_dword, 26 | "urb_function": self.get_word, 27 | "irp_info": self.get_byte, 28 | "usb_bus_id": self.get_word, 29 | "device_address": self.get_word, 30 | "endpoint": self.get_byte, 31 | "urb_transfer_type": self.get_byte, 32 | "pkt_data_len": self.get_dword, 33 | "pkt_data": self.get_bytes, 34 | "pseudo_ip": self._format_pseudo_ip, 35 | } 36 | 37 | self._linux_mapped_urb_fields = { 38 | "urb_id": self.get_qword, 39 | "urb_type": self.get_byte, 40 | "urb_transfer_type": self.get_byte, 41 | "endpoint": self.get_byte, 42 | "device_address": self.get_byte, 43 | "usb_bus_id": self.get_word, 44 | "device_setup_req": self.get_byte, 45 | "data_present": self.get_byte, 46 | "urb_seconds": self.get_qword, 47 | "urb_useconds": self.get_dword, 48 | "status": self.get_dword, 49 | "urb_data_len": self.get_dword, 50 | "pkt_data_len": self.get_dword, 51 | "reserved": self.get_qword, 52 | "interval": self.get_dword, 53 | "sof": self.get_dword, 54 | "transfer_flags": self.get_dword, 55 | "num_iso_desc": self.get_dword, 56 | "pkt_data": self.get_bytes, 57 | "pseudo_ip": self._format_pseudo_ip, 58 | } 59 | 60 | @staticmethod 61 | def init_linux_mapped_urb_fields(): 62 | return {key: None for key in [ 63 | "urb_id", "urb_type", "urb_transfer_type", "endpoint", "device_address", "usb_bus_id", "device_setup_req", "data_present", 64 | "urb_seconds", "urb_useconds", "status", "urb_data_len", "pkt_data_len", 65 | "reserved", "interval", "sof", "transfer_flags", "num_iso_desc", "pkt_data", "pseudo_ip" 66 | ]} 67 | 68 | def _skip_frame(self): 69 | pass 70 | 71 | def _retrieve_linux_mapped_function(self, method: str): 72 | return self._linux_mapped_urb_fields.get(method) 73 | 74 | def _parse_linux_mapped_urb_fields(self, stream): 75 | linux_mapped_urb_fields = self.init_linux_mapped_urb_fields() 76 | for k, v in linux_mapped_urb_fields.items(): 77 | if k == "pkt_data": 78 | # Read the leftover data 79 | linux_mapped_urb_fields[k] = self._retrieve_linux_mapped_function(k)(stream, linux_mapped_urb_fields['pkt_data_len']) 80 | elif k == "urb_transfer_type": 81 | linux_mapped_urb_fields[k] = self._retrieve_linux_mapped_function(k)(stream) 82 | # print("Transfer Type::", linux_mapped_urb_fields['urb_transfer_type']) 83 | # if URBTransferType(linux_mapped_urb_fields['urb_transfer_type']) != URBTransferType(0x1): 84 | if linux_mapped_urb_fields['urb_transfer_type'] != 0x1: 85 | # Skipping the frame 86 | # Resetting the counter in ParseUtils 87 | self._skip_frame() 88 | self.reset_count() 89 | return None 90 | 91 | elif k == "pseudo_ip": 92 | linux_mapped_urb_fields[k] = self._retrieve_linux_mapped_function(k)(linux_mapped_urb_fields) 93 | else: 94 | linux_mapped_urb_fields[k] = self._retrieve_linux_mapped_function(k)(stream) 95 | # Decode the data accordingly 96 | linux_mapped_urb_fields['urb_id'] = hex(linux_mapped_urb_fields['urb_id']) 97 | linux_mapped_urb_fields['reserved'] = hex(linux_mapped_urb_fields['reserved']) 98 | linux_mapped_urb_fields['status'] = 2**32 - (linux_mapped_urb_fields['status']) 99 | 100 | # print(linux_mapped_urb_fields) 101 | self.reset_count() 102 | return linux_mapped_urb_fields 103 | 104 | @staticmethod 105 | def init_urb_fields(): 106 | return {key: None for key in 107 | ["pseudo_header", "irp_id", "status", "urb_function", "irp_info", "usb_bus_id", 108 | "device_address", "endpoint", "urb_transfer_type", "pkt_data_len", "pkt_data", "pseudo_ip"]} 109 | 110 | def _retrieve_function(self, method: str): 111 | return self._urb_fields.get(method) 112 | 113 | def _parse_usb_fields(self, stream: bytes): 114 | urb_field = self.init_urb_fields() 115 | for k, v in urb_field.items(): 116 | if k == "pkt_data": 117 | length = urb_field['pkt_data_len'] 118 | urb_field[k] = self._retrieve_function(k)(stream, length) 119 | elif k == "pseudo_ip": 120 | urb_field[k] = self._retrieve_function(k)(urb_field) 121 | else: 122 | urb_field[k] = self._retrieve_function(k)(stream) 123 | urb_field['irp_id'] = hex(urb_field['irp_id']) 124 | # print(urb_field) 125 | self.reset_count() 126 | return urb_field 127 | 128 | def read_usb(self, stream): 129 | return self._parse_usb_fields(stream) 130 | 131 | @staticmethod 132 | def _format_pseudo_ip(fields): 133 | """ 134 | IP = bus_id + device_address + ((endpoint >> 4) & 0x0f) 135 | """ 136 | ip = str(fields['usb_bus_id']) + "." + str(fields['device_address']) + "." + str(((int(fields['endpoint'])) & 0xf)) 137 | return ip 138 | 139 | class PCAPng(USBParser): 140 | def __init__(self): 141 | super().__init__() 142 | 143 | self._section_hdr_block = { 144 | # TODO: For PCAPng Structure (https://pcapng.com/) 145 | "block_type": self.read_dword, 146 | "block_len1": self.read_dword, 147 | "signature": self.read_dword, 148 | "major": self.read_word, 149 | "minor": self.read_word, 150 | "section_len": self.read_qword, 151 | "options": self.read_bytes, 152 | "block_len2": self.read_dword, 153 | } 154 | 155 | self._interface_desc_block = { 156 | "block_type": self.read_dword, 157 | "block_len1": self.read_dword, 158 | "network_type": self.read_word, # LINK TYPE 159 | "reserved": self.read_word, 160 | "snap_len": self.read_dword, 161 | "options": self.read_bytes, 162 | "block_len2": self.read_dword, 163 | } 164 | 165 | self._enhanced_packet_block = { 166 | "block_type": self.read_dword, # 0x00000006 167 | "block_len1": self.read_dword, 168 | "interface_id": self.read_dword, 169 | "timestamp_lower": self.read_dword, 170 | "timestamp_upper": self.read_dword, 171 | "cap_pkt_len": self.read_dword, 172 | "orig_pkt_len": self.read_dword, 173 | "frame_buff": self.read_bytes, 174 | "block_len2": self.read_dword, 175 | } 176 | 177 | @staticmethod 178 | def get_interface_options_size(val): return val - (0x10 +0x4 ) 179 | @staticmethod 180 | def get_section_options_size(val): return val - (0x10 + 0x8 + 0x4) 181 | @staticmethod 182 | def get_enhanced_buff_size(block_len, pkt_len): return block_len - (pkt_len + 0x20) # 0x20: Remaining struct size (except frame_buff) 183 | 184 | @staticmethod 185 | def init_the_blocks(name): 186 | if name == "section": 187 | return {key:None for key in ['block_type', 'block_len1', 'signature', 'major', 'minor', 'section_len', 'options', 'block_len2'] } 188 | elif name == "interface": 189 | return {key:None for key in ['block_type', 'block_len1', 'network_type', 'reserved', 'snap_len', 'options', 'block_len2'] } 190 | else: 191 | return {key:None for key in ['block_type', 'block_len1', 'interface_id', 'timestamp_lower', 'timestamp_upper', 'cap_pkt_len', 'orig_pkt_len', 'frame_buff', 'block_len2'] } 192 | 193 | def parse_enhanced_block(self, stream): 194 | enhanced_block = self.init_the_blocks("enhanced") 195 | for k, v in enhanced_block.items(): 196 | if k == "frame_buff": 197 | enhanced_block[k] = self._retrieve_pcapng_function("enhanced", k)( 198 | stream, 199 | (enhanced_block['orig_pkt_len'] + 200 | self.get_enhanced_buff_size( 201 | enhanced_block['block_len1'], 202 | enhanced_block['orig_pkt_len'] 203 | )) 204 | ) 205 | else: 206 | enhanced_block[k] = self._retrieve_pcapng_function("enhanced", k)(stream) 207 | if enhanced_block[k] is None: return None 208 | # print(self.pos,enhanced_block) 209 | return enhanced_block 210 | 211 | def _retrieve_pcapng_function(self, _name, _method): 212 | if _name == "section": 213 | return self._section_hdr_block.get(_method) 214 | elif _name == "interface": 215 | return self._interface_desc_block.get(_method) 216 | else: 217 | return self._enhanced_packet_block.get(_method) 218 | 219 | def parse_section_block(self, stream): 220 | section_hdr_block = self.init_the_blocks("section") 221 | for k, v in section_hdr_block.items(): 222 | if k == "options": 223 | # Subtracting 0x10 (compare struct size of section_hdr and interface_block) 224 | section_hdr_block[k] = self._retrieve_pcapng_function("section", k)(stream, self.get_section_options_size(section_hdr_block['block_len1'])) 225 | else: 226 | section_hdr_block[k] = (self._retrieve_pcapng_function("section", k)(stream)) 227 | # print(section_hdr_block) 228 | return section_hdr_block 229 | 230 | def parse_interface_block(self, stream): 231 | # Ignoring the SECTION BLOCK HEADER 232 | # UTILIZE IT TO DUMP METADATA, NO USE HERE FOR NOW 233 | _ = self.parse_section_block(stream) 234 | interface_block = self.init_the_blocks("interface") 235 | for k, v in interface_block.items(): 236 | if k == "options": 237 | interface_block[k] = self._retrieve_pcapng_function("interface", k)(stream, (self.get_interface_options_size(interface_block['block_len1']) )) 238 | else: 239 | interface_block[k] = self._retrieve_pcapng_function("interface", k)(stream) 240 | # print("Interface", interface_block) 241 | return interface_block 242 | 243 | class Packet(USBParser): 244 | def __init__(self, filename): 245 | super().__init__() 246 | 247 | self.pkt = read_file(filename) 248 | self.is_pcapng = None 249 | self.pcapng = PCAPng() 250 | self._headers = { 251 | "magic_header": None, 252 | } 253 | 254 | self._pcap_header_fields = { 255 | "major_version": self.read_word, 256 | "minor_version": self.read_word, 257 | "zone_info": self.read_dword, 258 | "sig_flags": self.read_dword, 259 | "max_len": self.read_dword, 260 | "network_type": self.read_dword, 261 | } 262 | 263 | @staticmethod 264 | def init_record_fields(): 265 | return {key: None for key in ["timestamp", "time_microseconds", "octet_len", "frame_len", "frame_buff"]} 266 | 267 | def error(self, value): 268 | raise Exception(f"Pcap is not USB-TYPE. Pcap TYPE :: {NetworkType(value)}") 269 | 270 | def pcap_error(self): 271 | raise Exception("Invalid Pcap File") 272 | 273 | def _retrieve_pcap_hdr_function(self, method: str): 274 | return self._pcap_header_fields.get(method) 275 | 276 | def _parse_pcap_header(self, file, sig): 277 | if sig == PCAPNG_HEADER: 278 | self.is_pcapng = True 279 | self._pcap_header_fields = self.pcapng.parse_interface_block(self.pkt) 280 | else: 281 | for k, v in self._pcap_header_fields.items(): 282 | self._pcap_header_fields[k] = self._retrieve_pcap_hdr_function(k)(file) 283 | return self._pcap_header_fields 284 | 285 | def _retrieve_pcap_record_function(self, method: str): 286 | return self._pcap_record_fields.get(method) 287 | 288 | def _parse_pcap_record(self, file): 289 | pcap_record = self.init_record_fields() 290 | for k, v in pcap_record.items(): 291 | if k == "frame_buff": 292 | pcap_record[k] = self._retrieve_pcap_record_function(k)(file=file,length=pcap_record['frame_len']) 293 | else: 294 | pcap_record[k] = self._retrieve_pcap_record_function(k)(file) 295 | # print(pcap_record) 296 | return pcap_record 297 | 298 | def _check_valid_usb_pcap(self) -> bool: 299 | """ 300 | Checks the PCAP Header, also Parse PCAP Headers and checks the USB_PCAP Enum Value 301 | """ 302 | self._headers['magic_headers'] = self.read_dword(self.pkt) 303 | # print(hex(self._headers['magic_headers'])) 304 | if self._headers['magic_headers'] == BIG_ENDIAN_PCAP_HEADER or self._headers['magic_headers'] == LITTLE_ENDIAN_PCAP_HEADER: 305 | return True 306 | # PCAPNG Headers Metadata 307 | # https://pcapng.com/ 308 | # Read the 4 bytes and seek it to align the structs 309 | elif self._headers['magic_headers'] == PCAPNG_HEADER: 310 | self.pkt.seek(0x0) # Go back to starting 311 | return True 312 | else: 313 | self.pcap_error() 314 | return False 315 | 316 | def get_network_type(self): 317 | return self._pcap_header_fields['network_type'] 318 | 319 | def read_pcap(self): 320 | if self._check_valid_usb_pcap(): 321 | self._parse_pcap_header(self.pkt, self._headers['magic_headers']) 322 | # Check the network type is TYPE_USB 323 | # print("Network Type :: ", NetworkType(self._pcap_header_fields['network_type'])) 324 | # if self._pcap_header_fields['block_type'] == PCAPNG_HEADER: 325 | # pass 326 | # else: 327 | assert (NetworkType(self._pcap_header_fields['network_type']) == NetworkType(0xf9) or 328 | NetworkType(self._pcap_header_fields['network_type']) == NetworkType(0xdc)) 329 | 330 | 331 | # def __str__(self): 332 | # return (f"Headers(\n" 333 | # f"Signature={hex(self._headers['magic_headers'])}\n" 334 | # f"{self._pcap_header_fields}\n" 335 | # f"{self._pcap_record_fields}\n" 336 | # f"{self._urb_fields}\n") 337 | 338 | --------------------------------------------------------------------------------