1cf3c1e67SAndrew Jeffery#!/usr/bin/python3 2cf3c1e67SAndrew Jeffery 3cf3c1e67SAndrew Jeffery# SPDX-License-Identifier: Apache-2.0 4cf3c1e67SAndrew Jeffery# Copyright 2019 IBM Corp. 5cf3c1e67SAndrew Jeffery 6a3db66b3SPatrick Williamsimport json 7a3db66b3SPatrick Williamsimport struct 8a3db66b3SPatrick Williamsimport sys 9cf3c1e67SAndrew Jefferyfrom argparse import ArgumentParser 10cf3c1e67SAndrew Jefferyfrom collections import namedtuple 11cf3c1e67SAndrew Jefferyfrom enum import Enum 12a3db66b3SPatrick Williamsfrom itertools import islice 13a3db66b3SPatrick Williams 14cf3c1e67SAndrew Jefferyfrom scapy.all import rdpcap 15cf3c1e67SAndrew Jeffery 16cf3c1e67SAndrew JefferyRawMessage = namedtuple("RawMessage", "endian, header, data") 17a3db66b3SPatrick WilliamsFixedHeader = namedtuple( 18a3db66b3SPatrick Williams "FixedHeader", "endian, type, flags, version, length, cookie" 19a3db66b3SPatrick Williams) 20cf3c1e67SAndrew JefferyCookedHeader = namedtuple("CookedHeader", "fixed, fields") 21cf3c1e67SAndrew JefferyCookedMessage = namedtuple("CookedMessage", "header, body") 22cf3c1e67SAndrew JefferyTypeProperty = namedtuple("TypeProperty", "field, type, nature") 23cf3c1e67SAndrew JefferyTypeContainer = namedtuple("TypeContainer", "type, members") 24cf3c1e67SAndrew JefferyField = namedtuple("Field", "type, data") 25cf3c1e67SAndrew Jeffery 26a3db66b3SPatrick Williams 27cf3c1e67SAndrew Jefferyclass MessageEndian(Enum): 28a3db66b3SPatrick Williams LITTLE = ord("l") 29a3db66b3SPatrick Williams BIG = ord("B") 30a3db66b3SPatrick Williams 31cf3c1e67SAndrew Jeffery 32cf3c1e67SAndrew JefferyStructEndianLookup = { 33cf3c1e67SAndrew Jeffery MessageEndian.LITTLE.value: "<", 34a3db66b3SPatrick Williams MessageEndian.BIG.value: ">", 35cf3c1e67SAndrew Jeffery} 36cf3c1e67SAndrew Jeffery 37a3db66b3SPatrick Williams 38cf3c1e67SAndrew Jefferyclass MessageType(Enum): 39cf3c1e67SAndrew Jeffery INVALID = 0 40cf3c1e67SAndrew Jeffery METHOD_CALL = 1 41cf3c1e67SAndrew Jeffery METHOD_RETURN = 2 42cf3c1e67SAndrew Jeffery ERROR = 3 43cf3c1e67SAndrew Jeffery SIGNAL = 4 44cf3c1e67SAndrew Jeffery 45a3db66b3SPatrick Williams 46cf3c1e67SAndrew Jefferyclass MessageFlags(Enum): 47cf3c1e67SAndrew Jeffery NO_REPLY_EXPECTED = 0x01 48cf3c1e67SAndrew Jeffery NO_AUTO_START = 0x02 49cf3c1e67SAndrew Jeffery ALLOW_INTERACTIVE_AUTHORIZATION = 0x04 50cf3c1e67SAndrew Jeffery 51a3db66b3SPatrick Williams 52cf3c1e67SAndrew Jefferyclass MessageFieldType(Enum): 53cf3c1e67SAndrew Jeffery INVALID = 0 54cf3c1e67SAndrew Jeffery PATH = 1 55cf3c1e67SAndrew Jeffery INTERFACE = 2 56cf3c1e67SAndrew Jeffery MEMBER = 3 57cf3c1e67SAndrew Jeffery ERROR_NAME = 4 58cf3c1e67SAndrew Jeffery REPLY_SERIAL = 5 59cf3c1e67SAndrew Jeffery DESTINATION = 6 60cf3c1e67SAndrew Jeffery SENDER = 7 61cf3c1e67SAndrew Jeffery SIGNATURE = 8 62cf3c1e67SAndrew Jeffery UNIX_FDS = 9 63cf3c1e67SAndrew Jeffery 64a3db66b3SPatrick Williams 65cf3c1e67SAndrew Jefferyclass DBusType(Enum): 66cf3c1e67SAndrew Jeffery INVALID = 0 67a3db66b3SPatrick Williams BYTE = ord("y") 68a3db66b3SPatrick Williams BOOLEAN = ord("b") 69a3db66b3SPatrick Williams INT16 = ord("n") 70a3db66b3SPatrick Williams UINT16 = ord("q") 71a3db66b3SPatrick Williams INT32 = ord("i") 72a3db66b3SPatrick Williams UINT32 = ord("u") 73a3db66b3SPatrick Williams INT64 = ord("x") 74a3db66b3SPatrick Williams UINT64 = ord("t") 75a3db66b3SPatrick Williams DOUBLE = ord("d") 76a3db66b3SPatrick Williams STRING = ord("s") 77a3db66b3SPatrick Williams OBJECT_PATH = ord("o") 78a3db66b3SPatrick Williams SIGNATURE = ord("g") 79a3db66b3SPatrick Williams ARRAY = ord("a") 80a3db66b3SPatrick Williams STRUCT = ord("(") 81a3db66b3SPatrick Williams VARIANT = ord("v") 82a3db66b3SPatrick Williams DICT_ENTRY = ord("{") 83a3db66b3SPatrick Williams UNIX_FD = ord("h") 84a3db66b3SPatrick Williams 85cf3c1e67SAndrew Jeffery 86cf3c1e67SAndrew JefferyDBusContainerTerminatorLookup = { 87a3db66b3SPatrick Williams chr(DBusType.STRUCT.value): ")", 88a3db66b3SPatrick Williams chr(DBusType.DICT_ENTRY.value): "}", 89cf3c1e67SAndrew Jeffery} 90cf3c1e67SAndrew Jeffery 91a3db66b3SPatrick Williams 92cf3c1e67SAndrew Jefferyclass DBusTypeCategory(Enum): 93cf3c1e67SAndrew Jeffery FIXED = { 94cf3c1e67SAndrew Jeffery DBusType.BYTE.value, 95cf3c1e67SAndrew Jeffery DBusType.BOOLEAN.value, 96cf3c1e67SAndrew Jeffery DBusType.INT16.value, 97cf3c1e67SAndrew Jeffery DBusType.UINT16.value, 98cf3c1e67SAndrew Jeffery DBusType.INT32.value, 99cf3c1e67SAndrew Jeffery DBusType.UINT32.value, 100cf3c1e67SAndrew Jeffery DBusType.INT64.value, 101cf3c1e67SAndrew Jeffery DBusType.UINT64.value, 102cf3c1e67SAndrew Jeffery DBusType.DOUBLE.value, 103a3db66b3SPatrick Williams DBusType.UNIX_FD.value, 104cf3c1e67SAndrew Jeffery } 105cf3c1e67SAndrew Jeffery STRING = { 106cf3c1e67SAndrew Jeffery DBusType.STRING.value, 107cf3c1e67SAndrew Jeffery DBusType.OBJECT_PATH.value, 108cf3c1e67SAndrew Jeffery DBusType.SIGNATURE.value, 109cf3c1e67SAndrew Jeffery } 110cf3c1e67SAndrew Jeffery CONTAINER = { 111cf3c1e67SAndrew Jeffery DBusType.ARRAY.value, 112cf3c1e67SAndrew Jeffery DBusType.STRUCT.value, 113cf3c1e67SAndrew Jeffery DBusType.VARIANT.value, 114cf3c1e67SAndrew Jeffery DBusType.DICT_ENTRY.value, 115cf3c1e67SAndrew Jeffery } 116cf3c1e67SAndrew Jeffery RESERVED = { 117cf3c1e67SAndrew Jeffery DBusType.INVALID.value, 118cf3c1e67SAndrew Jeffery } 119cf3c1e67SAndrew Jeffery 120a3db66b3SPatrick Williams 121cf3c1e67SAndrew JefferyTypePropertyLookup = { 122a3db66b3SPatrick Williams DBusType.BYTE.value: TypeProperty(DBusType.BYTE, "B", 1), 123cf3c1e67SAndrew Jeffery # DBus booleans are 32 bit, with only the LSB used. Extract as 'I'. 124a3db66b3SPatrick Williams DBusType.BOOLEAN.value: TypeProperty(DBusType.BOOLEAN, "I", 4), 125a3db66b3SPatrick Williams DBusType.INT16.value: TypeProperty(DBusType.INT16, "h", 2), 126a3db66b3SPatrick Williams DBusType.UINT16.value: TypeProperty(DBusType.UINT16, "H", 2), 127a3db66b3SPatrick Williams DBusType.INT32.value: TypeProperty(DBusType.INT32, "i", 4), 128a3db66b3SPatrick Williams DBusType.UINT32.value: TypeProperty(DBusType.UINT32, "I", 4), 129a3db66b3SPatrick Williams DBusType.INT64.value: TypeProperty(DBusType.INT64, "q", 8), 130a3db66b3SPatrick Williams DBusType.UINT64.value: TypeProperty(DBusType.UINT64, "Q", 8), 131a3db66b3SPatrick Williams DBusType.DOUBLE.value: TypeProperty(DBusType.DOUBLE, "d", 8), 132a3db66b3SPatrick Williams DBusType.STRING.value: TypeProperty(DBusType.STRING, "s", DBusType.UINT32), 133a3db66b3SPatrick Williams DBusType.OBJECT_PATH.value: TypeProperty( 134a3db66b3SPatrick Williams DBusType.OBJECT_PATH, "s", DBusType.UINT32 135a3db66b3SPatrick Williams ), 136a3db66b3SPatrick Williams DBusType.SIGNATURE.value: TypeProperty( 137a3db66b3SPatrick Williams DBusType.SIGNATURE, "s", DBusType.BYTE 138a3db66b3SPatrick Williams ), 139cf3c1e67SAndrew Jeffery DBusType.ARRAY.value: TypeProperty(DBusType.ARRAY, None, DBusType.UINT32), 140cf3c1e67SAndrew Jeffery DBusType.STRUCT.value: TypeProperty(DBusType.STRUCT, None, 8), 141cf3c1e67SAndrew Jeffery DBusType.VARIANT.value: TypeProperty(DBusType.VARIANT, None, 1), 142cf3c1e67SAndrew Jeffery DBusType.DICT_ENTRY.value: TypeProperty(DBusType.DICT_ENTRY, None, 8), 143b25389e5SSui Chen DBusType.UNIX_FD.value: TypeProperty(DBusType.UINT32, None, 8), 144cf3c1e67SAndrew Jeffery} 145cf3c1e67SAndrew Jeffery 146a3db66b3SPatrick Williams 147cf3c1e67SAndrew Jefferydef parse_signature(sigstream): 148cf3c1e67SAndrew Jeffery sig = ord(next(sigstream)) 149cf3c1e67SAndrew Jeffery assert sig not in DBusTypeCategory.RESERVED.value 150cf3c1e67SAndrew Jeffery if sig in DBusTypeCategory.FIXED.value: 151cf3c1e67SAndrew Jeffery ty = TypePropertyLookup[sig].field, None 152cf3c1e67SAndrew Jeffery elif sig in DBusTypeCategory.STRING.value: 153cf3c1e67SAndrew Jeffery ty = TypePropertyLookup[sig].field, None 154cf3c1e67SAndrew Jeffery elif sig in DBusTypeCategory.CONTAINER.value: 155cf3c1e67SAndrew Jeffery if sig == DBusType.ARRAY.value: 156cf3c1e67SAndrew Jeffery ty = DBusType.ARRAY, parse_signature(sigstream) 157cf3c1e67SAndrew Jeffery elif sig == DBusType.STRUCT.value or sig == DBusType.DICT_ENTRY.value: 158cf3c1e67SAndrew Jeffery collected = list() 159cf3c1e67SAndrew Jeffery ty = parse_signature(sigstream) 160cf3c1e67SAndrew Jeffery while ty is not StopIteration: 161cf3c1e67SAndrew Jeffery collected.append(ty) 162cf3c1e67SAndrew Jeffery ty = parse_signature(sigstream) 163cf3c1e67SAndrew Jeffery ty = DBusType.STRUCT, collected 164cf3c1e67SAndrew Jeffery elif sig == DBusType.VARIANT.value: 165cf3c1e67SAndrew Jeffery ty = TypePropertyLookup[sig].field, None 166cf3c1e67SAndrew Jeffery else: 167cf3c1e67SAndrew Jeffery assert False 168cf3c1e67SAndrew Jeffery else: 169cf3c1e67SAndrew Jeffery assert chr(sig) in DBusContainerTerminatorLookup.values() 170cf3c1e67SAndrew Jeffery return StopIteration 171cf3c1e67SAndrew Jeffery 172cf3c1e67SAndrew Jeffery return TypeContainer._make(ty) 173cf3c1e67SAndrew Jeffery 174a3db66b3SPatrick Williams 175cf3c1e67SAndrew Jefferyclass AlignedStream(object): 176cf3c1e67SAndrew Jeffery def __init__(self, buf, offset=0): 177cf3c1e67SAndrew Jeffery self.stash = (buf, offset) 178cf3c1e67SAndrew Jeffery self.stream = iter(buf) 179cf3c1e67SAndrew Jeffery self.offset = offset 180cf3c1e67SAndrew Jeffery 181cf3c1e67SAndrew Jeffery def align(self, tc): 182cf3c1e67SAndrew Jeffery assert tc.type.value in TypePropertyLookup 183cf3c1e67SAndrew Jeffery prop = TypePropertyLookup[tc.type.value] 184cf3c1e67SAndrew Jeffery if prop.field.value in DBusTypeCategory.STRING.value: 185cf3c1e67SAndrew Jeffery prop = TypePropertyLookup[prop.nature.value] 186b25389e5SSui Chen if prop.nature == DBusType.UINT32: 187b25389e5SSui Chen prop = TypePropertyLookup[prop.nature.value] 188a3db66b3SPatrick Williams advance = ( 189a3db66b3SPatrick Williams prop.nature - (self.offset & (prop.nature - 1)) 190a3db66b3SPatrick Williams ) % prop.nature 191cf3c1e67SAndrew Jeffery _ = bytes(islice(self.stream, advance)) 19208ce0a5bSAndrew Jeffery self.offset += len(_) 193cf3c1e67SAndrew Jeffery 194cf3c1e67SAndrew Jeffery def take(self, size): 195cf3c1e67SAndrew Jeffery val = islice(self.stream, size) 196cf3c1e67SAndrew Jeffery self.offset += size 197cf3c1e67SAndrew Jeffery return val 198cf3c1e67SAndrew Jeffery 199cf3c1e67SAndrew Jeffery def autotake(self, tc): 200cf3c1e67SAndrew Jeffery assert tc.type.value in DBusTypeCategory.FIXED.value 201cf3c1e67SAndrew Jeffery assert tc.type.value in TypePropertyLookup 202cf3c1e67SAndrew Jeffery self.align(tc) 203cf3c1e67SAndrew Jeffery prop = TypePropertyLookup[tc.type.value] 204cf3c1e67SAndrew Jeffery return self.take(prop.nature) 205cf3c1e67SAndrew Jeffery 206cf3c1e67SAndrew Jeffery def drain(self): 207cf3c1e67SAndrew Jeffery remaining = bytes(self.stream) 208cf3c1e67SAndrew Jeffery offset = self.offset 209cf3c1e67SAndrew Jeffery self.offset += len(remaining) 210a03cdf2dSAndrew Jeffery if self.offset - self.stash[1] != len(self.stash[0]): 211a3db66b3SPatrick Williams print( 212a3db66b3SPatrick Williams "(self.offset - self.stash[1]): %d, len(self.stash[0]): %d" 213a3db66b3SPatrick Williams % (self.offset - self.stash[1], len(self.stash[0])), 214a3db66b3SPatrick Williams file=sys.stderr, 215a3db66b3SPatrick Williams ) 216a03cdf2dSAndrew Jeffery raise MalformedPacketError 217cf3c1e67SAndrew Jeffery return remaining, offset 218cf3c1e67SAndrew Jeffery 219cf3c1e67SAndrew Jeffery def dump(self): 220a3db66b3SPatrick Williams print( 221a3db66b3SPatrick Williams "AlignedStream: absolute offset: {}".format(self.offset), 222a3db66b3SPatrick Williams file=sys.stderr, 223a3db66b3SPatrick Williams ) 224a3db66b3SPatrick Williams print( 225a3db66b3SPatrick Williams "AlignedStream: relative offset: {}".format( 226a3db66b3SPatrick Williams self.offset - self.stash[1] 227a3db66b3SPatrick Williams ), 228a3db66b3SPatrick Williams file=sys.stderr, 229a3db66b3SPatrick Williams ) 230a3db66b3SPatrick Williams print( 231a3db66b3SPatrick Williams "AlignedStream: remaining buffer:\n{}".format(self.drain()[0]), 232a3db66b3SPatrick Williams file=sys.stderr, 233a3db66b3SPatrick Williams ) 234a3db66b3SPatrick Williams print( 235a3db66b3SPatrick Williams "AlignedStream: provided buffer:\n{}".format(self.stash[0]), 236a3db66b3SPatrick Williams file=sys.stderr, 237a3db66b3SPatrick Williams ) 238cf3c1e67SAndrew Jeffery 239cf3c1e67SAndrew Jeffery def dump_assert(self, condition): 240cf3c1e67SAndrew Jeffery if condition: 241cf3c1e67SAndrew Jeffery return 242cf3c1e67SAndrew Jeffery self.dump() 243cf3c1e67SAndrew Jeffery assert condition 244cf3c1e67SAndrew Jeffery 245a3db66b3SPatrick Williams 246cf3c1e67SAndrew Jefferydef parse_fixed(endian, stream, tc): 247cf3c1e67SAndrew Jeffery assert tc.type.value in TypePropertyLookup 248cf3c1e67SAndrew Jeffery prop = TypePropertyLookup[tc.type.value] 249cf3c1e67SAndrew Jeffery val = bytes(stream.autotake(tc)) 250cf3c1e67SAndrew Jeffery try: 251cf3c1e67SAndrew Jeffery val = struct.unpack("{}{}".format(endian, prop.type), val)[0] 252cf3c1e67SAndrew Jeffery return bool(val) if prop.type == DBusType.BOOLEAN else val 253cf3c1e67SAndrew Jeffery except struct.error as e: 254855a2af4SAndrew Jeffery print(e, file=sys.stderr) 255855a2af4SAndrew Jeffery print("parse_fixed: Error unpacking {}".format(val), file=sys.stderr) 256a3db66b3SPatrick Williams print( 257a3db66b3SPatrick Williams ( 258a3db66b3SPatrick Williams "parse_fixed: Attempting to unpack type {} " 259a3db66b3SPatrick Williams + "with properties {}" 260a3db66b3SPatrick Williams ).format(tc.type, prop), 261a3db66b3SPatrick Williams file=sys.stderr, 262a3db66b3SPatrick Williams ) 263cf3c1e67SAndrew Jeffery stream.dump_assert(False) 264cf3c1e67SAndrew Jeffery 265a3db66b3SPatrick Williams 266cf3c1e67SAndrew Jefferydef parse_string(endian, stream, tc): 267cf3c1e67SAndrew Jeffery assert tc.type.value in TypePropertyLookup 268cf3c1e67SAndrew Jeffery prop = TypePropertyLookup[tc.type.value] 269cf3c1e67SAndrew Jeffery size = parse_fixed(endian, stream, TypeContainer(prop.nature, None)) 270cf3c1e67SAndrew Jeffery # Empty DBus strings have no NUL-terminator 271cf3c1e67SAndrew Jeffery if size == 0: 272cf3c1e67SAndrew Jeffery return "" 273cf3c1e67SAndrew Jeffery # stream.dump_assert(size > 0) 274cf3c1e67SAndrew Jeffery val = bytes(stream.take(size + 1)) 275cf3c1e67SAndrew Jeffery try: 276cf3c1e67SAndrew Jeffery stream.dump_assert(len(val) == size + 1) 277cf3c1e67SAndrew Jeffery try: 278a3db66b3SPatrick Williams return struct.unpack("{}{}".format(size, prop.type), val[:size])[ 279a3db66b3SPatrick Williams 0 280a3db66b3SPatrick Williams ].decode() 281cf3c1e67SAndrew Jeffery except struct.error as e: 282cf3c1e67SAndrew Jeffery stream.dump() 283cf3c1e67SAndrew Jeffery raise AssertionError(e) 284cf3c1e67SAndrew Jeffery except AssertionError as e: 285a3db66b3SPatrick Williams print( 286a3db66b3SPatrick Williams "parse_string: Error unpacking string of length {} from {}".format( 287a3db66b3SPatrick Williams size, val 288a3db66b3SPatrick Williams ), 289a3db66b3SPatrick Williams file=sys.stderr, 290a3db66b3SPatrick Williams ) 291cf3c1e67SAndrew Jeffery raise e 292cf3c1e67SAndrew Jeffery 293a3db66b3SPatrick Williams 294cf3c1e67SAndrew Jefferydef parse_type(endian, stream, tc): 295cf3c1e67SAndrew Jeffery if tc.type.value in DBusTypeCategory.FIXED.value: 296cf3c1e67SAndrew Jeffery val = parse_fixed(endian, stream, tc) 297cf3c1e67SAndrew Jeffery elif tc.type.value in DBusTypeCategory.STRING.value: 298cf3c1e67SAndrew Jeffery val = parse_string(endian, stream, tc) 299cf3c1e67SAndrew Jeffery elif tc.type.value in DBusTypeCategory.CONTAINER.value: 300cf3c1e67SAndrew Jeffery val = parse_container(endian, stream, tc) 301cf3c1e67SAndrew Jeffery else: 302cf3c1e67SAndrew Jeffery stream.dump_assert(False) 303cf3c1e67SAndrew Jeffery 304cf3c1e67SAndrew Jeffery return val 305cf3c1e67SAndrew Jeffery 306a3db66b3SPatrick Williams 307cf3c1e67SAndrew Jefferydef parse_array(endian, stream, tc): 308cf3c1e67SAndrew Jeffery arr = list() 309cf3c1e67SAndrew Jeffery length = parse_fixed(endian, stream, TypeContainer(DBusType.UINT32, None)) 310cf3c1e67SAndrew Jeffery stream.align(tc) 311cf3c1e67SAndrew Jeffery offset = stream.offset 312cf3c1e67SAndrew Jeffery while (stream.offset - offset) < length: 313cf3c1e67SAndrew Jeffery elem = parse_type(endian, stream, tc) 314cf3c1e67SAndrew Jeffery arr.append(elem) 315cf3c1e67SAndrew Jeffery if (stream.offset - offset) < length: 316cf3c1e67SAndrew Jeffery stream.align(tc) 317cf3c1e67SAndrew Jeffery return arr 318cf3c1e67SAndrew Jeffery 319a3db66b3SPatrick Williams 320cf3c1e67SAndrew Jefferydef parse_struct(endian, stream, tcs): 321cf3c1e67SAndrew Jeffery arr = list() 322cf3c1e67SAndrew Jeffery stream.align(TypeContainer(DBusType.STRUCT, None)) 323cf3c1e67SAndrew Jeffery for tc in tcs: 324cf3c1e67SAndrew Jeffery arr.append(parse_type(endian, stream, tc)) 325cf3c1e67SAndrew Jeffery return arr 326cf3c1e67SAndrew Jeffery 327a3db66b3SPatrick Williams 328cf3c1e67SAndrew Jefferydef parse_variant(endian, stream): 329cf3c1e67SAndrew Jeffery sig = parse_string(endian, stream, TypeContainer(DBusType.SIGNATURE, None)) 330cf3c1e67SAndrew Jeffery tc = parse_signature(iter(sig)) 331cf3c1e67SAndrew Jeffery return parse_type(endian, stream, tc) 332cf3c1e67SAndrew Jeffery 333a3db66b3SPatrick Williams 334cf3c1e67SAndrew Jefferydef parse_container(endian, stream, tc): 335cf3c1e67SAndrew Jeffery if tc.type == DBusType.ARRAY: 336cf3c1e67SAndrew Jeffery return parse_array(endian, stream, tc.members) 337cf3c1e67SAndrew Jeffery elif tc.type in (DBusType.STRUCT, DBusType.DICT_ENTRY): 338cf3c1e67SAndrew Jeffery return parse_struct(endian, stream, tc.members) 339cf3c1e67SAndrew Jeffery elif tc.type == DBusType.VARIANT: 340cf3c1e67SAndrew Jeffery return parse_variant(endian, stream) 341cf3c1e67SAndrew Jeffery else: 342cf3c1e67SAndrew Jeffery stream.dump_assert(False) 343cf3c1e67SAndrew Jeffery 344a3db66b3SPatrick Williams 345cf3c1e67SAndrew Jefferydef parse_fields(endian, stream): 346cf3c1e67SAndrew Jeffery sig = parse_signature(iter("a(yv)")) 347cf3c1e67SAndrew Jeffery fields = parse_container(endian, stream, sig) 348cf3c1e67SAndrew Jeffery # The header ends after its alignment padding to an 8-boundary. 349cf3c1e67SAndrew Jeffery # https://dbus.freedesktop.org/doc/dbus-specification.html#message-protocol-messages 350cf3c1e67SAndrew Jeffery stream.align(TypeContainer(DBusType.STRUCT, None)) 351cf3c1e67SAndrew Jeffery return list(map(lambda v: Field(MessageFieldType(v[0]), v[1]), fields)) 352cf3c1e67SAndrew Jeffery 353a3db66b3SPatrick Williams 354cf3c1e67SAndrew Jefferyclass MalformedPacketError(Exception): 355cf3c1e67SAndrew Jeffery pass 356cf3c1e67SAndrew Jeffery 357a3db66b3SPatrick Williams 35854e0c9aeSSui Chendef parse_header(raw, ignore_error): 359cf3c1e67SAndrew Jeffery assert raw.endian in StructEndianLookup.keys() 360cf3c1e67SAndrew Jeffery endian = StructEndianLookup[raw.endian] 361a3db66b3SPatrick Williams fixed = FixedHeader._make( 362a3db66b3SPatrick Williams struct.unpack("{}BBBBLL".format(endian), raw.header) 363a3db66b3SPatrick Williams ) 364cf3c1e67SAndrew Jeffery astream = AlignedStream(raw.data, len(raw.header)) 365cf3c1e67SAndrew Jeffery fields = parse_fields(endian, astream) 366cf3c1e67SAndrew Jeffery data, offset = astream.drain() 367a3db66b3SPatrick Williams if (not ignore_error) and fixed.length > len(data): 368cf3c1e67SAndrew Jeffery raise MalformedPacketError 369cf3c1e67SAndrew Jeffery return CookedHeader(fixed, fields), AlignedStream(data, offset) 370cf3c1e67SAndrew Jeffery 371a3db66b3SPatrick Williams 372cf3c1e67SAndrew Jefferydef parse_body(header, stream): 373cf3c1e67SAndrew Jeffery assert header.fixed.endian in StructEndianLookup 374cf3c1e67SAndrew Jeffery endian = StructEndianLookup[header.fixed.endian] 375cf3c1e67SAndrew Jeffery body = list() 376cf3c1e67SAndrew Jeffery for field in header.fields: 377cf3c1e67SAndrew Jeffery if field.type == MessageFieldType.SIGNATURE: 378cf3c1e67SAndrew Jeffery sigstream = iter(field.data) 379cf3c1e67SAndrew Jeffery try: 380cf3c1e67SAndrew Jeffery while True: 381cf3c1e67SAndrew Jeffery tc = parse_signature(sigstream) 382cf3c1e67SAndrew Jeffery val = parse_type(endian, stream, tc) 383cf3c1e67SAndrew Jeffery body.append(val) 384cf3c1e67SAndrew Jeffery except StopIteration: 385cf3c1e67SAndrew Jeffery pass 386cf3c1e67SAndrew Jeffery break 387cf3c1e67SAndrew Jeffery return body 388cf3c1e67SAndrew Jeffery 389a3db66b3SPatrick Williams 390cf3c1e67SAndrew Jefferydef parse_message(raw): 391cf3c1e67SAndrew Jeffery try: 39254e0c9aeSSui Chen header, data = parse_header(raw, False) 393cf3c1e67SAndrew Jeffery try: 394cf3c1e67SAndrew Jeffery body = parse_body(header, data) 395cf3c1e67SAndrew Jeffery return CookedMessage(header, body) 396cf3c1e67SAndrew Jeffery except AssertionError as e: 397855a2af4SAndrew Jeffery print(header, file=sys.stderr) 398cf3c1e67SAndrew Jeffery raise e 399cf3c1e67SAndrew Jeffery except AssertionError as e: 400855a2af4SAndrew Jeffery print(raw, file=sys.stderr) 401cf3c1e67SAndrew Jeffery raise e 402cf3c1e67SAndrew Jeffery 403a3db66b3SPatrick Williams 404cf3c1e67SAndrew Jefferydef parse_packet(packet): 405cf3c1e67SAndrew Jeffery data = bytes(packet) 40654e0c9aeSSui Chen raw = RawMessage(data[0], data[:12], data[12:]) 40754e0c9aeSSui Chen try: 40854e0c9aeSSui Chen msg = parse_message(raw) 409a3db66b3SPatrick Williams except MalformedPacketError: 410855a2af4SAndrew Jeffery print("Got malformed packet: {}".format(raw), file=sys.stderr) 411a3db66b3SPatrick Williams # For a message that is so large that its payload data could not be 412a3db66b3SPatrick Williams # parsed, just parse its header, then set its data field to empty. 41354e0c9aeSSui Chen header, data = parse_header(raw, True) 41454e0c9aeSSui Chen msg = CookedMessage(header, []) 415cf3c1e67SAndrew Jeffery return msg 416cf3c1e67SAndrew Jeffery 417a3db66b3SPatrick Williams 418cf3c1e67SAndrew JefferyCallEnvelope = namedtuple("CallEnvelope", "cookie, origin") 419a3db66b3SPatrick Williams 420a3db66b3SPatrick Williams 421cf3c1e67SAndrew Jefferydef parse_session(session, matchers, track_calls): 422cf3c1e67SAndrew Jeffery calls = set() 423cf3c1e67SAndrew Jeffery for packet in session: 424cf3c1e67SAndrew Jeffery try: 425cf3c1e67SAndrew Jeffery cooked = parse_packet(packet) 426cf3c1e67SAndrew Jeffery if not matchers: 427cf3c1e67SAndrew Jeffery yield packet.time, cooked 428cf3c1e67SAndrew Jeffery elif any(all(r(cooked) for r in m) for m in matchers): 429cf3c1e67SAndrew Jeffery if cooked.header.fixed.type == MessageType.METHOD_CALL.value: 430a3db66b3SPatrick Williams s = [ 431a3db66b3SPatrick Williams f 432a3db66b3SPatrick Williams for f in cooked.header.fields 433a3db66b3SPatrick Williams if f.type == MessageFieldType.SENDER 434a3db66b3SPatrick Williams ][0] 435cf3c1e67SAndrew Jeffery calls.add(CallEnvelope(cooked.header.fixed.cookie, s.data)) 436cf3c1e67SAndrew Jeffery yield packet.time, cooked 437cf3c1e67SAndrew Jeffery elif track_calls: 438*96ebc4e7SAndrew Jeffery responseTypes = { 439*96ebc4e7SAndrew Jeffery MessageType.METHOD_RETURN.value, 440*96ebc4e7SAndrew Jeffery MessageType.ERROR.value, 441*96ebc4e7SAndrew Jeffery } 442*96ebc4e7SAndrew Jeffery if cooked.header.fixed.type not in responseTypes: 443cf3c1e67SAndrew Jeffery continue 444a3db66b3SPatrick Williams rs = [ 445a3db66b3SPatrick Williams f 446a3db66b3SPatrick Williams for f in cooked.header.fields 447a3db66b3SPatrick Williams if f.type == MessageFieldType.REPLY_SERIAL 448a3db66b3SPatrick Williams ][0] 449a3db66b3SPatrick Williams d = [ 450a3db66b3SPatrick Williams f 451a3db66b3SPatrick Williams for f in cooked.header.fields 452a3db66b3SPatrick Williams if f.type == MessageFieldType.DESTINATION 453a3db66b3SPatrick Williams ][0] 454cf3c1e67SAndrew Jeffery ce = CallEnvelope(rs.data, d.data) 455cf3c1e67SAndrew Jeffery if ce in calls: 456cf3c1e67SAndrew Jeffery calls.remove(ce) 457cf3c1e67SAndrew Jeffery yield packet.time, cooked 458a3db66b3SPatrick Williams except MalformedPacketError: 459cf3c1e67SAndrew Jeffery pass 460cf3c1e67SAndrew Jeffery 461a3db66b3SPatrick Williams 462cf3c1e67SAndrew Jefferydef gen_match_type(rule): 463cf3c1e67SAndrew Jeffery mt = MessageType.__members__[rule.value.upper()] 464cf3c1e67SAndrew Jeffery return lambda p: p.header.fixed.type == mt.value 465cf3c1e67SAndrew Jeffery 466a3db66b3SPatrick Williams 467cf3c1e67SAndrew Jefferydef gen_match_sender(rule): 468cf3c1e67SAndrew Jeffery mf = Field(MessageFieldType.SENDER, rule.value) 469cf3c1e67SAndrew Jeffery return lambda p: any(f == mf for f in p.header.fields) 470cf3c1e67SAndrew Jeffery 471a3db66b3SPatrick Williams 472cf3c1e67SAndrew Jefferydef gen_match_interface(rule): 473cf3c1e67SAndrew Jeffery mf = Field(MessageFieldType.INTERFACE, rule.value) 474cf3c1e67SAndrew Jeffery return lambda p: any(f == mf for f in p.header.fields) 475cf3c1e67SAndrew Jeffery 476a3db66b3SPatrick Williams 477cf3c1e67SAndrew Jefferydef gen_match_member(rule): 478cf3c1e67SAndrew Jeffery mf = Field(MessageFieldType.MEMBER, rule.value) 479cf3c1e67SAndrew Jeffery return lambda p: any(f == mf for f in p.header.fields) 480cf3c1e67SAndrew Jeffery 481a3db66b3SPatrick Williams 482cf3c1e67SAndrew Jefferydef gen_match_path(rule): 483cf3c1e67SAndrew Jeffery mf = Field(MessageFieldType.PATH, rule.value) 484cf3c1e67SAndrew Jeffery return lambda p: any(f == mf for f in p.header.fields) 485cf3c1e67SAndrew Jeffery 486a3db66b3SPatrick Williams 487cf3c1e67SAndrew Jefferydef gen_match_destination(rule): 488cf3c1e67SAndrew Jeffery mf = Field(MessageFieldType.DESTINATION, rule.value) 489cf3c1e67SAndrew Jeffery return lambda p: any(f == mf for f in p.header.fields) 490cf3c1e67SAndrew Jeffery 491a3db66b3SPatrick Williams 492cf3c1e67SAndrew JefferyValidMatchKeys = { 493a3db66b3SPatrick Williams "type", 494a3db66b3SPatrick Williams "sender", 495a3db66b3SPatrick Williams "interface", 496a3db66b3SPatrick Williams "member", 497a3db66b3SPatrick Williams "path", 498a3db66b3SPatrick Williams "destination", 499cf3c1e67SAndrew Jeffery} 500cf3c1e67SAndrew JefferyMatchRule = namedtuple("MatchExpression", "key, value") 501cf3c1e67SAndrew Jeffery 502a3db66b3SPatrick Williams 503cf3c1e67SAndrew Jeffery# https://dbus.freedesktop.org/doc/dbus-specification.html#message-bus-routing-match-rules 504cf3c1e67SAndrew Jefferydef parse_match_rules(exprs): 505cf3c1e67SAndrew Jeffery matchers = list() 506cf3c1e67SAndrew Jeffery for mexpr in exprs: 507cf3c1e67SAndrew Jeffery rules = list() 508cf3c1e67SAndrew Jeffery for rexpr in mexpr.split(","): 509a3db66b3SPatrick Williams rule = MatchRule._make( 510a3db66b3SPatrick Williams map(lambda s: str.strip(s, "'"), rexpr.split("=")) 511a3db66b3SPatrick Williams ) 512a3db66b3SPatrick Williams assert rule.key in ValidMatchKeys, f"Invalid expression: {rule}" 513cf3c1e67SAndrew Jeffery rules.append(globals()["gen_match_{}".format(rule.key)](rule)) 514cf3c1e67SAndrew Jeffery matchers.append(rules) 515cf3c1e67SAndrew Jeffery return matchers 516cf3c1e67SAndrew Jeffery 517a3db66b3SPatrick Williams 518cf3c1e67SAndrew Jefferydef packetconv(obj): 519cf3c1e67SAndrew Jeffery if isinstance(obj, Enum): 520cf3c1e67SAndrew Jeffery return obj.value 521cf3c1e67SAndrew Jeffery raise TypeError 522cf3c1e67SAndrew Jeffery 523a3db66b3SPatrick Williams 524cf3c1e67SAndrew Jefferydef main(): 525cf3c1e67SAndrew Jeffery parser = ArgumentParser() 526a3db66b3SPatrick Williams parser.add_argument( 527a3db66b3SPatrick Williams "--json", 528a3db66b3SPatrick Williams action="store_true", 529a3db66b3SPatrick Williams help="Emit a JSON representation of the messages", 530a3db66b3SPatrick Williams ) 531a3db66b3SPatrick Williams parser.add_argument( 532a3db66b3SPatrick Williams "--no-track-calls", 533a3db66b3SPatrick Williams action="store_true", 534a3db66b3SPatrick Williams default=False, 535a3db66b3SPatrick Williams help="Make a call response pass filters", 536a3db66b3SPatrick Williams ) 537cf3c1e67SAndrew Jeffery parser.add_argument("file", help="The pcap file") 538a3db66b3SPatrick Williams parser.add_argument( 539a3db66b3SPatrick Williams "expressions", nargs="*", help="DBus message match expressions" 540a3db66b3SPatrick Williams ) 541cf3c1e67SAndrew Jeffery args = parser.parse_args() 542cf3c1e67SAndrew Jeffery stream = rdpcap(args.file) 543cf3c1e67SAndrew Jeffery matchers = parse_match_rules(args.expressions) 544cf3c1e67SAndrew Jeffery try: 545cf3c1e67SAndrew Jeffery if args.json: 546e310dd91SPatrick Williams for _, msg in parse_session( 547a3db66b3SPatrick Williams stream, matchers, not args.no_track_calls 548a3db66b3SPatrick Williams ): 549cf3c1e67SAndrew Jeffery print("{}".format(json.dumps(msg, default=packetconv))) 550cf3c1e67SAndrew Jeffery else: 551e310dd91SPatrick Williams for time, msg in parse_session( 552a3db66b3SPatrick Williams stream, matchers, not args.no_track_calls 553a3db66b3SPatrick Williams ): 554cf3c1e67SAndrew Jeffery print("{}: {}".format(time, msg)) 555cf3c1e67SAndrew Jeffery print() 556cf3c1e67SAndrew Jeffery except BrokenPipeError: 557cf3c1e67SAndrew Jeffery pass 558cf3c1e67SAndrew Jeffery 559a3db66b3SPatrick Williams 560cf3c1e67SAndrew Jefferyif __name__ == "__main__": 561cf3c1e67SAndrew Jeffery main() 562