def __init__(self): super(AudioHandler, self).__init__() self.header_base = construct.BitStruct( 'ASTRMBaseHeader', construct.BitField('fmt', 3), construct.Bit('channel'), construct.Flag('vibrate'), construct.Bit('packet_type'), construct.BitField('seq_id', 10), construct.BitField('payload_size', 16)) self.header_aud = construct.Struct('ASTRMAudioHeader', construct.ULInt32('timestamp')) self.header_msg = construct.Struct( 'ASTRMMsgHeader', # This is kind of a hack, (there are two timestamp fields, which one is used # depends on packet_type construct.ULInt32('timestamp_audio'), construct.ULInt32('timestamp'), construct.Array(2, construct.ULInt32('freq_0')), # -> mc_video construct.Array(2, construct.ULInt32('freq_1')), # -> mc_sync construct.ULInt8('vid_format'), construct.Padding(3)) self.header = construct.Struct( 'ASTRMHeader', construct.Embed(self.header_base), construct.Switch('format_hdr', lambda ctx: ctx.packet_type, { 0: construct.Embed(self.header_aud), 1: construct.Embed(self.header_msg), }, default=construct.Pass))
def _struct(cls): return construct.Struct( "type" / construct.Enum(construct.Byte, ParamType), "value" / construct.Switch( construct.this.type, { "Int": construct.Int32sl, "Float": construct.ExprAdapter( construct.Bytes(10), lambda obj, ctx: numpy.frombuffer( obj.rjust(16, b"\x00"), dtype=numpy.longdouble), lambda obj, ctx: numpy.longdouble(obj).tobytes()[-10:], ), "Flag": construct.Byte, "Str": construct.PascalString(construct.Int32ul, "cp932"), }, # else 'Var' variable name type construct.Select( construct.PascalString(construct.Int32ul, "cp932"), ), ), )
class SwitchTest(cst.TContainerMixin): @dataclasses.dataclass class Case1(cst.TContainerMixin): case1_1: int = cst.sfield(cs.Int16sb) case1_2: int = cst.sfield(cs.Int16sb) @dataclasses.dataclass class Case2(cst.TContainerMixin): case2_1: int = cst.sfield(cs.Int8sb) case2_2: int = cst.sfield(cs.Int8sb) case2_3: int = cst.sfield(cs.Int8sb) case2_4: int = cst.sfield(cs.Int8sb) @dataclasses.dataclass class CaseDefault(cst.TContainerMixin): case_default_1: int = cst.sfield(cs.Int32sb) choice: int = cst.sfield(cs.Int8ub) switch: t.Union[Case1, Case2, CaseDefault] = cst.sfield( cs.Switch( cs.this.choice, { 1: cst.TStruct(Case1), 2: cst.TStruct(Case2), }, cst.TStruct(CaseDefault), ))
def _struct(cls): return construct.Struct( "type" / construct.Enum(construct.Byte, OpeDataType), "name" / construct.PascalString(construct.Int32ul, "cp932"), "count" / construct.Int32ul, "func" / construct.Switch( construct.this.type, {"Func": construct.Enum(construct.Byte, OpeFuncType)}), "operands" / construct.Array(construct.this.count, Param._struct()), )
def _struct(cls): return construct.Struct( 'type' / construct.Enum(construct.Byte, OpeDataType), 'name' / construct.PascalString(construct.Int32ul, 'cp932'), 'count' / construct.Int32ul, 'func' / construct.Switch( construct.this.type, { 'Func': construct.Enum(construct.Byte, OpeFuncType), }), 'operands' / construct.Array(construct.this.count, Param._struct()), )
def gate_factory(class_dict): normal_color_struct = cs.Struct(cs.Padding(2), 'category' / cs.Byte, 'color' / cs.Byte) offset_color_struct = cs.Struct('source' / cs.Int, cs.Const(class_dict['color_triplet']), 'hsv' / cs.Single[3]) color_entry_struct = cs.Struct( 'type' / cs.RawCopy(cs.Short), 'colors' / cs.Switch( cs.this.type.data, { class_dict['normal_color']: normal_color_struct, class_dict['offset_color']: offset_color_struct })) color_struct = cs.Struct( cs.Const(class_dict['colorization']), 'num_entries' / cs.Int, 'colorizations' / color_entry_struct[cs.this.num_entries]) level_struct = cs.Struct( cs.Const(class_dict['gate_level']), cs.Const(b'\x01'), 'level_name' / cs.PascalString(cs.Short, 'ascii'), cs.Const(b'\x01'), 'level_icon' / cs.PascalString(cs.Short, 'ascii'), 'colorization' / color_struct, cs.Const(b'\x01'), 'description' / cs.PascalString(cs.Short, 'ascii'), cs.Const(class_dict['level_type']), 'level_type' / cs.Byte, 'restricted' / cs.Byte) velocity_struct = cs.Struct('velocity' / cs.Single, cs.Const(b'\x01'), 'num_entries' / cs.Int, 'lengths' / cs.Single[cs.this.num_entries], 'start' / cs.Long) wheel_struct = cs.Struct( 'class_id' / cs.RawCopy(cs.Short), 'unknown' / cs.Byte[0x05], 'num_levels' / cs.Short, 'levels' / level_struct[cs.this.num_levels], 'velocity' / cs.If(cs.this.class_id.data != class_dict['random_depth'], velocity_struct)) gate_struct = cs.Struct( cs.Const(b'\x01'), 'gate_id' / cs.Int, cs.Const(b'\x01'), 'gate_name' / cs.PascalString(cs.Short, 'ascii'), cs.Const(b'\x01'), 'gate_icon' / cs.PascalString(cs.Short, 'ascii'), 'colorization' / color_struct, cs.Const(b'\x01'), 'description' / cs.PascalString(cs.Short, 'ascii'), 'unknown' / cs.Byte[0x16], 'num_wheels' / cs.Int, 'wheels' / wheel_struct[cs.this.num_wheels], 'class_id' / cs.Int16sb, cs.If(cs.this.class_id < 0, cs.PascalString(cs.Short, 'ascii')), 'themes' / cs.Struct('unknown' / cs.Byte[0x07], 'themes' / cs.Byte[0x06])) return gate_struct
def __init__(s): s.header_cmd0 = construct.Struct('CMD0Header', construct.UBInt8('magic'), construct.UBInt8('unk_0'), construct.UBInt8('unk_1'), construct.UBInt8('unk_2'), construct.UBInt8('unk_3'), construct.UBInt8('flags'), construct.UBInt8('id_primary'), construct.UBInt8('id_secondary'), construct.UBInt16('error_code'), construct.UBInt16('payload_size_cmd0') ) s.header_cmd1 = construct.Struct('CMD1Header', construct.Padding(48) ) s.header_cmd2 = construct.Struct('CMD2Header', construct.ULInt16('JDN_base'), construct.Padding(2), construct.ULInt32('seconds') ) s.header = construct.Struct('CMDHeader', construct.ULInt16('packet_type'), construct.ULInt16('cmd_id'), construct.ULInt16('payload_size'), construct.ULInt16('seq_id'), construct.Switch('cmd_hdr', lambda ctx: ctx.cmd_id, { 0 : construct.If(lambda ctx: ctx.payload_size >= s.header_cmd0.sizeof(), construct.Embed(s.header_cmd0)), 1 : construct.If(lambda ctx: ctx.payload_size == s.header_cmd1.sizeof(), construct.Embed(s.header_cmd1)), 2 : construct.If(lambda ctx: ctx.payload_size == s.header_cmd2.sizeof(), construct.Embed(s.header_cmd2)) }, default = construct.Pass ) ) s.cmd_handlers = { 0 : s.cmd0, 1 : s.cmd1, 2 : s.cmd2 } s.cmd0_handlers = { 5 : { 6 : s.cmd0_5_6 }, }
def __init__(s): super(ServiceASTRM, s).__init__() s.header_base = construct.BitStruct('ASTRMBaseHeader', construct.BitField('fmt', 3), construct.Bit('channel'), construct.Flag('vibrate'), construct.Bit('packet_type'), construct.BitField('seq_id', 10), construct.BitField('payload_size', 16) ) s.header_aud = construct.Struct('ASTRMAudioHeader', construct.ULInt32('timestamp'), # construct.Array(lambda ctx: ctx.payload_size, construct.UBInt8("data")) ) s.header_msg = construct.Struct('ASTRMMsgHeader', # This is kind of a hack, (there are two timestamp fields, which one is used depends on packet_type construct.ULInt32('timestamp_audio'), construct.ULInt32('timestamp'), construct.Array(2, construct.ULInt32('freq_0')), # -> mc_video construct.Array(2, construct.ULInt32('freq_1')), # -> mc_sync construct.ULInt8('vid_format'), construct.Padding(3) ) s.header = construct.Struct('ASTRMHeader', construct.Embed(s.header_base), construct.Switch('format_hdr', lambda ctx: ctx.packet_type, { 0 : construct.Embed(s.header_aud), 1 : construct.Embed(s.header_msg), }, default = construct.Pass ) ) s.is_streaming = False s.p = pyaudio.PyAudio() s.stream = None s.pa_num_bufs = 15 s.pa_ring = [array.array('H', '\0' * 416 * 2)] * s.pa_num_bufs s.pa_wpos = s.pa_rpos = 0
def _struct(cls): return construct.Struct( 'type' / construct.Enum(construct.Byte, ParamType), 'value' / construct.Switch( construct.this.type, { 'Int': construct.Int32ul, 'Float': construct.ExprAdapter( construct.Bytes(10), lambda obj, ctx: numpy.frombuffer(obj.rjust(16, b'\x00'), dtype=numpy.longdouble), lambda obj, ctx: numpy.longdouble(obj).tobytes()[-10:] ), 'Flag': construct.Byte, 'Str': construct.PascalString(construct.Int32ul, 'cp932'), }, # else 'Var' variable name type construct.Select( construct.PascalString(construct.Int32ul, 'cp932'), ), ), )
def DataEntry(type): return C.FocusedSeq( "values", "_count" / C.Rebuild(Int32ul, C.len_(this.values)), "values" / C.Switch( type, { 0: Pass, 1: ClassEntry(), 2: ClassEntry(), 3: Byte, #boolean 4: Int8ul, 5: Int16ul, 6: Int32ul, 7: Int64ul, 8: Int8sl, 9: Int16sl, 10: Int32sl, 11: Int64sl, 12: Float32l, 13: Float64l, 14: CString("utf8"), 15: RGBA(), 16: Int64ul, #pointer #17: Int32ul #size, potentially not a uint but that's probably the best option of it 20: Vector3(), 21: Vector4(), 22: Quat4(), 32: CString( 'utf8' ), #specifically a CString, while 14 is probably something like std::string 46: Capsule(), 64: Vector2() }, default=C.StopFieldError)[this._count], )
sys.stderr.write("This tool requires Construct. Install it with 'pip install Construct'.\n") sys.exit(1) from construct import this, len_ if os.isatty(sys.stdin.fileno()): tx_hex = input("Enter transaction in hex format: ") else: tx_hex = sys.stdin.read().strip() tx_bin = bytes.fromhex(tx_hex) CompactUintStruct = c.Struct( "base" / c.Int8ul, "ext" / c.Switch(this.base, {0xfd: c.Int16ul, 0xfe: c.Int32ul, 0xff: c.Int64ul}), ) class CompactUintAdapter(c.Adapter): def _encode(self, obj, context, path): if obj < 0xfd: return {"base": obj} if obj < 2 ** 16: return {"base": 0xfd, "ext": obj} if obj < 2 ** 32: return {"base": 0xfe, "ext": obj} if obj < 2 ** 64: return {"base": 0xff, "ext": obj} raise ValueError("Value too big for compact uint")
'status' / construct.Flag, ) class Socks5AddressType(Enum): ipv4_address = 0x01 domain_name = 0x03 ipv6_address = 0x06 Socks5Address = construct.Struct( 'type' / PackEnum(Socks5AddressType), 'address' / construct.Switch( construct.this.type, { Socks5AddressType.ipv4_address: IPv4Address, Socks5AddressType.domain_name: construct.PascalString(construct.Byte, 'utf-8'), Socks5AddressType.ipv6_address: IPv6Address, })) class Socks5Command(Enum): tcp_connect = 0x01 tcp_bind_port = 0x02 udp_bind_port = 0x03 ClientConnectionRequest = construct.Struct( 'version' / construct.Default(construct.Const(5, construct.Byte), 5), 'command' / PackEnum(Socks5Command),
def __init__(self, websocket, mode): self._websocket = websocket self._mode = mode self._inbound_packet = construct.Struct( "type" / construct.Enum( construct.Int8ul, setup=0, killed=1, kill=2, remove=3, sync=4, club_collision=5, wall_collision=6, set_leaderboard=7, set_target_dim=8 ), "payload" / construct.Switch( construct.this.type, { "setup": construct.Struct( "server_version" / construct.CString(encoding="utf8"), construct.Check(lambda ctx: ctx.server_version == CLIENT_VERSION), "syncer_value" / construct.Int32ul, "game_mode" / construct.Mapping( construct.Int8ul, { 0: Mode.ffa, 1: Mode.tdm }, dict() # This packet is only received ), "setup_value" / construct.Int32ul ), "killed": construct.Pass, "kill": construct.Struct("stamp" / construct.Int32ul), "remove": construct.Struct( "enqueue_param" / construct.Int32ul, # TODO: Figure out purpose "player_id" / construct.Int32ul ), "sync": construct.Struct( "timestamp" / construct.Int32ul, "remove_count" / construct.Int32ul, "removal_array" / construct.Array( construct.this.remove_count, construct.Int32ul ), "sync_count" / construct.Int32ul, "sync_array" / construct.Array( construct.this.sync_count, construct.Struct( "player_id" / construct.Int32ul, "player_state" / physical_state, "mace_state" / physical_state, "mace_radius" / construct.Float32l ) ) ), "club_collision": construct.Struct( "enqueue_param" / construct.Int32ul, # TODO: Figure out purpose "p" / vector2d, # TODO: Figure out purpose "i" / construct.Float32l, # TODO: Figure out purpose "first_id" / construct.Int32ul, "first_state" / physical_state, "second_id" / construct.Int32ul, "second_state" / physical_state ), "wall_collision": construct.Struct( "enqueue_param" / construct.Int32ul, # TODO: Figure out purpose "p" / vector2d, # TODO: Figure out purpose "i" / construct.Float32l, # TODO: Figure out purpose "player_id" / construct.Int32ul, "player_state" / physical_state, "mace_radius" / construct.Float32l ), "set_leaderboard": construct.Struct( "player_count" / construct.Int32ul, "total" / construct.Int32ul, # TODO: Figure out purpose, construct.IfThenElse( lambda ctx: self._mode == Mode.ffa, construct.Struct( # FFA "count" / construct.Int8ul, "first_entry_id" / construct.Int32ul, "leaderboard" / construct.Array( construct.this.count, construct.Struct( "name" / construct.CString(encoding="utf8"), "score" / construct.Int32ul, ) ), "king_name" / construct.CString(encoding="utf8"), "king_score" / construct.Int32ul, "place" / construct.Int32ul, "score" / construct.Int32ul ), construct.Array( 3, construct.Struct( "id" / construct.Int8ul, "score" / construct.Int32ul, "count" / construct.Int32ul ) ) ) ), "set_target_dim": construct.Struct("target_dim" / vector2d) } ) ) self._outbound_packet = construct.Struct( "type" / construct.Enum( construct.Int8ul, play=0, direction=1, move_up=2, move_down=3, move_left=4, move_right=5, stop_move_up=6, stop_move_down=7, stop_move_left=8, stop_move_right=9 ), "payload" / construct.Switch( construct.this.type, { "play": construct.Pass # TODO: Implement the rest of the packets } ) )
class BsmParser(interface.FileObjectParser): """Parser for BSM files.""" _INITIAL_FILE_OFFSET = None NAME = u'bsm_log' DESCRIPTION = u'Parser for BSM log files.' # BSM supported version (0x0b = 11). AUDIT_HEADER_VERSION = 11 # Magic Trail Header. BSM_TOKEN_TRAILER_MAGIC = b'b105' # IP Version constants. AU_IPv4 = 4 AU_IPv6 = 16 IPV4_STRUCT = construct.UBInt32(u'ipv4') IPV6_STRUCT = construct.Struct(u'ipv6', construct.UBInt64(u'high'), construct.UBInt64(u'low')) # Tested structures. # INFO: I have ommited the ID in the structures declaration. # I used the BSM_TYPE first to read the ID, and then, the structure. # Tokens always start with an ID value that identifies their token # type and subsequent structure. BSM_TYPE = construct.UBInt8(u'token_id') # Data type structures. BSM_TOKEN_DATA_CHAR = construct.String(u'value', 1) BSM_TOKEN_DATA_SHORT = construct.UBInt16(u'value') BSM_TOKEN_DATA_INTEGER = construct.UBInt32(u'value') # Common structure used by other structures. # audit_uid: integer, uid that generates the entry. # effective_uid: integer, the permission user used. # effective_gid: integer, the permission group used. # real_uid: integer, user id of the user that execute the process. # real_gid: integer, group id of the group that execute the process. # pid: integer, identification number of the process. # session_id: unknown, need research. BSM_TOKEN_SUBJECT_SHORT = construct.Struct( u'subject_data', construct.UBInt32(u'audit_uid'), construct.UBInt32(u'effective_uid'), construct.UBInt32(u'effective_gid'), construct.UBInt32(u'real_uid'), construct.UBInt32(u'real_gid'), construct.UBInt32(u'pid'), construct.UBInt32(u'session_id')) # Common structure used by other structures. # Identify the kind of inet (IPv4 or IPv6) # TODO: instead of 16, AU_IPv6 must be used. BSM_IP_TYPE_SHORT = construct.Struct( u'bsm_ip_type_short', construct.UBInt32(u'net_type'), construct.Switch(u'ip_addr', _BsmTokenGetNetType, {16: IPV6_STRUCT}, default=IPV4_STRUCT)) # Initial fields structure used by header structures. # length: integer, the length of the entry, equal to trailer (doc: length). # version: integer, version of BSM (AUDIT_HEADER_VERSION). # event_type: integer, the type of event (/etc/security/audit_event). # modifier: integer, unknown, need research (It is always 0). BSM_HEADER = construct.Struct(u'bsm_header', construct.UBInt32(u'length'), construct.UBInt8(u'version'), construct.UBInt16(u'event_type'), construct.UBInt16(u'modifier')) # First token of one entry. # timestamp: unsigned integer, number of seconds since # January 1, 1970 00:00:00 UTC. # microsecond: unsigned integer, number of micro seconds. BSM_HEADER32 = construct.Struct(u'bsm_header32', BSM_HEADER, construct.UBInt32(u'timestamp'), construct.UBInt32(u'microsecond')) BSM_HEADER64 = construct.Struct(u'bsm_header64', BSM_HEADER, construct.UBInt64(u'timestamp'), construct.UBInt64(u'microsecond')) BSM_HEADER32_EX = construct.Struct(u'bsm_header32_ex', BSM_HEADER, BSM_IP_TYPE_SHORT, construct.UBInt32(u'timestamp'), construct.UBInt32(u'microsecond')) # Token TEXT, provides extra information. BSM_TOKEN_TEXT = construct.Struct( u'bsm_token_text', construct.UBInt16(u'length'), construct.Array(_BsmTokenGetLength, construct.UBInt8(u'text'))) # Path of the executable. BSM_TOKEN_PATH = BSM_TOKEN_TEXT # Identified the end of the record (follow by TRAILER). # status: integer that identifies the status of the exit (BSM_ERRORS). # return: returned value from the operation. BSM_TOKEN_RETURN32 = construct.Struct(u'bsm_token_return32', construct.UBInt8(u'status'), construct.UBInt32(u'return_value')) BSM_TOKEN_RETURN64 = construct.Struct(u'bsm_token_return64', construct.UBInt8(u'status'), construct.UBInt64(u'return_value')) # Identified the number of bytes that was written. # magic: 2 bytes that identifies the TRAILER (BSM_TOKEN_TRAILER_MAGIC). # length: integer that has the number of bytes from the entry size. BSM_TOKEN_TRAILER = construct.Struct(u'bsm_token_trailer', construct.UBInt16(u'magic'), construct.UBInt32(u'record_length')) # A 32-bits argument. # num_arg: the number of the argument. # name_arg: the argument's name. # text: the string value of the argument. BSM_TOKEN_ARGUMENT32 = construct.Struct( u'bsm_token_argument32', construct.UBInt8(u'num_arg'), construct.UBInt32(u'name_arg'), construct.UBInt16(u'length'), construct.Array(_BsmTokenGetLength, construct.UBInt8(u'text'))) # A 64-bits argument. # num_arg: integer, the number of the argument. # name_arg: text, the argument's name. # text: the string value of the argument. BSM_TOKEN_ARGUMENT64 = construct.Struct( u'bsm_token_argument64', construct.UBInt8(u'num_arg'), construct.UBInt64(u'name_arg'), construct.UBInt16(u'length'), construct.Array(_BsmTokenGetLength, construct.UBInt8(u'text'))) # Identify an user. # terminal_id: unknown, research needed. # terminal_addr: unknown, research needed. BSM_TOKEN_SUBJECT32 = construct.Struct(u'bsm_token_subject32', BSM_TOKEN_SUBJECT_SHORT, construct.UBInt32(u'terminal_port'), IPV4_STRUCT) # Identify an user using a extended Token. # terminal_port: unknown, need research. # net_type: unknown, need research. BSM_TOKEN_SUBJECT32_EX = construct.Struct( u'bsm_token_subject32_ex', BSM_TOKEN_SUBJECT_SHORT, construct.UBInt32(u'terminal_port'), BSM_IP_TYPE_SHORT) # au_to_opaque // AUT_OPAQUE BSM_TOKEN_OPAQUE = BSM_TOKEN_TEXT # au_to_seq // AUT_SEQ BSM_TOKEN_SEQUENCE = BSM_TOKEN_DATA_INTEGER # Program execution with options. # For each argument we are going to have a string+ "\x00". # Example: [00 00 00 02][41 42 43 00 42 42 00] # 2 Arguments, Arg1: [414243] Arg2: [4242]. BSM_TOKEN_EXEC_ARGUMENTS = construct.UBInt32(u'number_arguments') BSM_TOKEN_EXEC_ARGUMENT = construct.Struct( u'bsm_token_exec_argument', construct.RepeatUntil(_BsmTokenIsEndOfString, construct.StaticField("text", 1))) # au_to_in_addr // AUT_IN_ADDR: BSM_TOKEN_ADDR = IPV4_STRUCT # au_to_in_addr_ext // AUT_IN_ADDR_EX: BSM_TOKEN_ADDR_EXT = construct.Struct(u'bsm_token_addr_ext', construct.UBInt32(u'net_type'), IPV6_STRUCT) # au_to_ip // AUT_IP: # TODO: parse this header in the correct way. BSM_TOKEN_IP = construct.String(u'binary_ipv4_add', 20) # au_to_ipc // AUT_IPC: BSM_TOKEN_IPC = construct.Struct(u'bsm_token_ipc', construct.UBInt8(u'object_type'), construct.UBInt32(u'object_id')) # au_to_ipc_perm // au_to_ipc_perm BSM_TOKEN_IPC_PERM = construct.Struct( u'bsm_token_ipc_perm', construct.UBInt32(u'user_id'), construct.UBInt32(u'group_id'), construct.UBInt32(u'creator_user_id'), construct.UBInt32(u'creator_group_id'), construct.UBInt32(u'access_mode'), construct.UBInt32(u'slot_seq'), construct.UBInt32(u'key')) # au_to_iport // AUT_IPORT: BSM_TOKEN_PORT = construct.UBInt16(u'port_number') # au_to_file // AUT_OTHER_FILE32: BSM_TOKEN_FILE = construct.Struct( u'bsm_token_file', construct.UBInt32(u'timestamp'), construct.UBInt32(u'microsecond'), construct.UBInt16(u'length'), construct.Array(_BsmTokenGetLength, construct.UBInt8(u'text'))) # au_to_subject64 // AUT_SUBJECT64: BSM_TOKEN_SUBJECT64 = construct.Struct(u'bsm_token_subject64', BSM_TOKEN_SUBJECT_SHORT, construct.UBInt64(u'terminal_port'), IPV4_STRUCT) # au_to_subject64_ex // AU_IPv4: BSM_TOKEN_SUBJECT64_EX = construct.Struct( u'bsm_token_subject64_ex', BSM_TOKEN_SUBJECT_SHORT, construct.UBInt32(u'terminal_port'), construct.UBInt32(u'terminal_type'), BSM_IP_TYPE_SHORT) # au_to_process32 // AUT_PROCESS32: BSM_TOKEN_PROCESS32 = construct.Struct(u'bsm_token_process32', BSM_TOKEN_SUBJECT_SHORT, construct.UBInt32(u'terminal_port'), IPV4_STRUCT) # au_to_process64 // AUT_PROCESS32: BSM_TOKEN_PROCESS64 = construct.Struct(u'bsm_token_process64', BSM_TOKEN_SUBJECT_SHORT, construct.UBInt64(u'terminal_port'), IPV4_STRUCT) # au_to_process32_ex // AUT_PROCESS32_EX: BSM_TOKEN_PROCESS32_EX = construct.Struct( u'bsm_token_process32_ex', BSM_TOKEN_SUBJECT_SHORT, construct.UBInt32(u'terminal_port'), BSM_IP_TYPE_SHORT) # au_to_process64_ex // AUT_PROCESS64_EX: BSM_TOKEN_PROCESS64_EX = construct.Struct( u'bsm_token_process64_ex', BSM_TOKEN_SUBJECT_SHORT, construct.UBInt64(u'terminal_port'), BSM_IP_TYPE_SHORT) # au_to_sock_inet32 // AUT_SOCKINET32: BSM_TOKEN_AUT_SOCKINET32 = construct.Struct( u'bsm_token_aut_sockinet32', construct.UBInt16(u'net_type'), construct.UBInt16(u'port_number'), IPV4_STRUCT) # Info: checked against the source code of XNU, but not against # real BSM file. BSM_TOKEN_AUT_SOCKINET128 = construct.Struct( u'bsm_token_aut_sockinet128', construct.UBInt16(u'net_type'), construct.UBInt16(u'port_number'), IPV6_STRUCT) INET6_ADDR_TYPE = construct.Struct(u'addr_type', construct.UBInt16(u'ip_type'), construct.UBInt16(u'source_port'), construct.UBInt64(u'saddr_high'), construct.UBInt64(u'saddr_low'), construct.UBInt16(u'destination_port'), construct.UBInt64(u'daddr_high'), construct.UBInt64(u'daddr_low')) INET4_ADDR_TYPE = construct.Struct( u'addr_type', construct.UBInt16(u'ip_type'), construct.UBInt16(u'source_port'), construct.UBInt32(u'source_address'), construct.UBInt16(u'destination_port'), construct.UBInt32(u'destination_address')) # au_to_socket_ex // AUT_SOCKET_EX # TODO: Change the 26 for unixbsm.BSM_PROTOCOLS.INET6. BSM_TOKEN_AUT_SOCKINET32_EX = construct.Struct( u'bsm_token_aut_sockinet32_ex', construct.UBInt16(u'socket_domain'), construct.UBInt16(u'socket_type'), construct.Switch(u'structure_addr_port', _BsmTokenGetSocketDomain, {26: INET6_ADDR_TYPE}, default=INET4_ADDR_TYPE)) # au_to_sock_unix // AUT_SOCKUNIX BSM_TOKEN_SOCKET_UNIX = construct.Struct( u'bsm_token_au_to_sock_unix', construct.UBInt16(u'family'), construct.RepeatUntil(_BsmTokenIsEndOfString, construct.StaticField("path", 1))) # au_to_data // au_to_data # how to print: bsmtoken.BSM_TOKEN_DATA_PRINT. # type: bsmtoken.BSM_TOKEN_DATA_TYPE. # unit_count: number of type values. # BSM_TOKEN_DATA has a end field = type * unit_count BSM_TOKEN_DATA = construct.Struct(u'bsm_token_data', construct.UBInt8(u'how_to_print'), construct.UBInt8(u'data_type'), construct.UBInt8(u'unit_count')) # au_to_attr32 // AUT_ATTR32 BSM_TOKEN_ATTR32 = construct.Struct( u'bsm_token_attr32', construct.UBInt32(u'file_mode'), construct.UBInt32(u'uid'), construct.UBInt32(u'gid'), construct.UBInt32(u'file_system_id'), construct.UBInt64(u'file_system_node_id'), construct.UBInt32(u'device')) # au_to_attr64 // AUT_ATTR64 BSM_TOKEN_ATTR64 = construct.Struct( u'bsm_token_attr64', construct.UBInt32(u'file_mode'), construct.UBInt32(u'uid'), construct.UBInt32(u'gid'), construct.UBInt32(u'file_system_id'), construct.UBInt64(u'file_system_node_id'), construct.UBInt64(u'device')) # au_to_exit // AUT_EXIT BSM_TOKEN_EXIT = construct.Struct(u'bsm_token_exit', construct.UBInt32(u'status'), construct.UBInt32(u'return_value')) # au_to_newgroups // AUT_NEWGROUPS # INFO: we must read BSM_TOKEN_DATA_INTEGER for each group. BSM_TOKEN_GROUPS = construct.UBInt16(u'group_number') # au_to_exec_env == au_to_exec_args BSM_TOKEN_EXEC_ENV = BSM_TOKEN_EXEC_ARGUMENTS # au_to_zonename //AUT_ZONENAME BSM_TOKEN_ZONENAME = BSM_TOKEN_TEXT # Token ID. # List of valid Token_ID. # Token_ID -> [NAME_STRUCTURE, STRUCTURE] # Only the checked structures are been added to the valid structures lists. BSM_TYPE_LIST = { 17: [u'BSM_TOKEN_FILE', BSM_TOKEN_FILE], 19: [u'BSM_TOKEN_TRAILER', BSM_TOKEN_TRAILER], 20: [u'BSM_HEADER32', BSM_HEADER32], 21: [u'BSM_HEADER64', BSM_HEADER64], 33: [u'BSM_TOKEN_DATA', BSM_TOKEN_DATA], 34: [u'BSM_TOKEN_IPC', BSM_TOKEN_IPC], 35: [u'BSM_TOKEN_PATH', BSM_TOKEN_PATH], 36: [u'BSM_TOKEN_SUBJECT32', BSM_TOKEN_SUBJECT32], 38: [u'BSM_TOKEN_PROCESS32', BSM_TOKEN_PROCESS32], 39: [u'BSM_TOKEN_RETURN32', BSM_TOKEN_RETURN32], 40: [u'BSM_TOKEN_TEXT', BSM_TOKEN_TEXT], 41: [u'BSM_TOKEN_OPAQUE', BSM_TOKEN_OPAQUE], 42: [u'BSM_TOKEN_ADDR', BSM_TOKEN_ADDR], 43: [u'BSM_TOKEN_IP', BSM_TOKEN_IP], 44: [u'BSM_TOKEN_PORT', BSM_TOKEN_PORT], 45: [u'BSM_TOKEN_ARGUMENT32', BSM_TOKEN_ARGUMENT32], 47: [u'BSM_TOKEN_SEQUENCE', BSM_TOKEN_SEQUENCE], 96: [u'BSM_TOKEN_ZONENAME', BSM_TOKEN_ZONENAME], 113: [u'BSM_TOKEN_ARGUMENT64', BSM_TOKEN_ARGUMENT64], 114: [u'BSM_TOKEN_RETURN64', BSM_TOKEN_RETURN64], 116: [u'BSM_HEADER32_EX', BSM_HEADER32_EX], 119: [u'BSM_TOKEN_PROCESS64', BSM_TOKEN_PROCESS64], 122: [u'BSM_TOKEN_SUBJECT32_EX', BSM_TOKEN_SUBJECT32_EX], 127: [u'BSM_TOKEN_AUT_SOCKINET32_EX', BSM_TOKEN_AUT_SOCKINET32_EX], 128: [u'BSM_TOKEN_AUT_SOCKINET32', BSM_TOKEN_AUT_SOCKINET32] } # Untested structures. # When not tested structure is found, we try to parse using also # these structures. BSM_TYPE_LIST_NOT_TESTED = { 49: [u'BSM_TOKEN_ATTR32', BSM_TOKEN_ATTR32], 50: [u'BSM_TOKEN_IPC_PERM', BSM_TOKEN_IPC_PERM], 52: [u'BSM_TOKEN_GROUPS', BSM_TOKEN_GROUPS], 59: [u'BSM_TOKEN_GROUPS', BSM_TOKEN_GROUPS], 60: [u'BSM_TOKEN_EXEC_ARGUMENTS', BSM_TOKEN_EXEC_ARGUMENTS], 61: [u'BSM_TOKEN_EXEC_ENV', BSM_TOKEN_EXEC_ENV], 62: [u'BSM_TOKEN_ATTR32', BSM_TOKEN_ATTR32], 82: [u'BSM_TOKEN_EXIT', BSM_TOKEN_EXIT], 115: [u'BSM_TOKEN_ATTR64', BSM_TOKEN_ATTR64], 117: [u'BSM_TOKEN_SUBJECT64', BSM_TOKEN_SUBJECT64], 123: [u'BSM_TOKEN_PROCESS32_EX', BSM_TOKEN_PROCESS32_EX], 124: [u'BSM_TOKEN_PROCESS64_EX', BSM_TOKEN_PROCESS64_EX], 125: [u'BSM_TOKEN_SUBJECT64_EX', BSM_TOKEN_SUBJECT64_EX], 126: [u'BSM_TOKEN_ADDR_EXT', BSM_TOKEN_ADDR_EXT], 129: [u'BSM_TOKEN_AUT_SOCKINET128', BSM_TOKEN_AUT_SOCKINET128], 130: [u'BSM_TOKEN_SOCKET_UNIX', BSM_TOKEN_SOCKET_UNIX] } def __init__(self): """Initializes a parser object.""" super(BsmParser, self).__init__() # Create the dictionary with all token IDs: tested and untested. self.bsm_type_list_all = self.BSM_TYPE_LIST.copy() self.bsm_type_list_all.update(self.BSM_TYPE_LIST_NOT_TESTED) def _CopyByteArrayToBase16String(self, byte_array): """Copies a byte array into a base-16 encoded Unicode string. Args: byte_array: A byte array. Returns: A base-16 encoded Unicode string. """ return u''.join([u'{0:02x}'.format(byte) for byte in byte_array]) def _CopyUtf8ByteArrayToString(self, byte_array): """Copies a UTF-8 encoded byte array into a Unicode string. Args: byte_array: A byte array containing an UTF-8 encoded string. Returns: A Unicode string. """ byte_stream = b''.join(map(chr, byte_array)) try: string = byte_stream.decode(u'utf-8') except UnicodeDecodeError: logging.warning(u'Unable to decode UTF-8 formatted byte array.') string = byte_stream.decode(u'utf-8', errors=u'ignore') string, _, _ = string.partition(b'\x00') return string def _IPv4Format(self, address): """Change an integer IPv4 address value for its 4 octets representation. Args: address: integer with the IPv4 address. Returns: IPv4 address in 4 octet representation (class A, B, C, D). """ ipv4_string = self.IPV4_STRUCT.build(address) return socket.inet_ntoa(ipv4_string) def _IPv6Format(self, high, low): """Provide a readable IPv6 IP having the high and low part in 2 integers. Args: high: 64 bits integers number with the high part of the IPv6. low: 64 bits integers number with the low part of the IPv6. Returns: String with a well represented IPv6. """ ipv6_string = self.IPV6_STRUCT.build( construct.Container(high=high, low=low)) # socket.inet_ntop not supported in Windows. if hasattr(socket, u'inet_ntop'): return socket.inet_ntop(socket.AF_INET6, ipv6_string) # TODO: this approach returns double "::", illegal IPv6 addr. str_address = binascii.hexlify(ipv6_string) address = [] blank = False for pos in range(0, len(str_address), 4): if str_address[pos:pos + 4] == u'0000': if not blank: address.append(u'') blank = True else: blank = False address.append(str_address[pos:pos + 4].lstrip(u'0')) return u':'.join(address) def _RawToUTF8(self, byte_stream): """Copies a UTF-8 byte stream into a Unicode string. Args: byte_stream: A byte stream containing an UTF-8 encoded string. Returns: A Unicode string. """ try: string = byte_stream.decode(u'utf-8') except UnicodeDecodeError: logging.warning( u'Decode UTF8 failed, the message string may be cut short.') string = byte_stream.decode(u'utf-8', errors=u'ignore') return string.partition(b'\x00')[0] def ParseFileObject(self, parser_mediator, file_object, **kwargs): """Parses a BSM file-like object. Args: parser_mediator: A parser mediator object (instance of ParserMediator). file_object: A file-like object. Raises: UnableToParseFile: when the file cannot be parsed. """ file_object.seek(0, os.SEEK_SET) try: is_bsm = self.VerifyFile(parser_mediator, file_object) except (IOError, construct.FieldError) as exception: raise errors.UnableToParseFile( u'Unable to parse BSM file with error: {0:s}'.format( exception)) if not is_bsm: raise errors.UnableToParseFile(u'Not a BSM File, unable to parse.') event_object = self.ReadBSMEvent(parser_mediator, file_object) while event_object: parser_mediator.ProduceEvent(event_object) event_object = self.ReadBSMEvent(parser_mediator, file_object) def ReadBSMEvent(self, parser_mediator, file_object): """Returns a BsmEvent from a single BSM entry. Args: parser_mediator: A parser mediator object (instance of ParserMediator). file_object: A file-like object. Returns: An event object. """ # A list of tokens that has the entry. extra_tokens = [] offset = file_object.tell() # Token header, first token for each entry. try: token_id = self.BSM_TYPE.parse_stream(file_object) except (IOError, construct.FieldError): return bsm_type, structure = self.BSM_TYPE_LIST.get(token_id, [u'', u'']) if bsm_type == u'BSM_HEADER32': token = structure.parse_stream(file_object) elif bsm_type == u'BSM_HEADER64': token = structure.parse_stream(file_object) elif bsm_type == u'BSM_HEADER32_EX': token = structure.parse_stream(file_object) else: logging.warning( u'Token ID Header {0} not expected at position 0x{1:X}.' u'The parsing of the file cannot be continued'.format( token_id, file_object.tell())) # TODO: if it is a Mac OS X, search for the trailer magic value # as a end of the entry can be a possibility to continue. return length = token.bsm_header.length event_type = u'{0} ({1})'.format( bsmtoken.BSM_AUDIT_EVENT.get(token.bsm_header.event_type, u'UNKNOWN'), token.bsm_header.event_type) timestamp = timelib.Timestamp.FromPosixTimeWithMicrosecond( token.timestamp, token.microsecond) # Read until we reach the end of the record. while file_object.tell() < (offset + length): # Check if it is a known token. try: token_id = self.BSM_TYPE.parse_stream(file_object) except (IOError, construct.FieldError): logging.warning( u'Unable to parse the Token ID at position: {0:d}'.format( file_object.tell())) return if not token_id in self.BSM_TYPE_LIST: pending = (offset + length) - file_object.tell() extra_tokens.extend( self.TryWithUntestedStructures(file_object, token_id, pending)) else: token = self.BSM_TYPE_LIST[token_id][1].parse_stream( file_object) extra_tokens.append( self.FormatToken(token_id, token, file_object)) if file_object.tell() > (offset + length): logging.warning(u'Token ID {0} not expected at position 0x{1:X}.' u'Jumping for the next entry.'.format( token_id, file_object.tell())) try: file_object.seek((offset + length) - file_object.tell(), os.SEEK_CUR) except (IOError, construct.FieldError) as exception: logging.warning( u'Unable to jump to next entry with error: {0:s}'.format( exception)) return # BSM can be in more than one OS: BSD, Solaris and Mac OS X. if parser_mediator.platform == u'MacOSX': # In Mac OS X the last two tokens are the return status and the trailer. if len(extra_tokens) >= 2: return_value = extra_tokens[-2:-1][0] if (return_value.startswith(u'[BSM_TOKEN_RETURN32') or return_value.startswith(u'[BSM_TOKEN_RETURN64')): _ = extra_tokens.pop(len(extra_tokens) - 2) else: return_value = u'Return unknown' else: return_value = u'Return unknown' if extra_tokens: trailer = extra_tokens[-1] if trailer.startswith(u'[BSM_TOKEN_TRAILER'): _ = extra_tokens.pop(len(extra_tokens) - 1) else: trailer = u'Trailer unknown' else: trailer = u'Trailer unknown' return MacBsmEvent(event_type, timestamp, u'. '.join(extra_tokens), return_value, trailer, offset) else: # Generic BSM format. if extra_tokens: trailer = extra_tokens[-1] if trailer.startswith(u'[BSM_TOKEN_TRAILER'): _ = extra_tokens.pop(len(extra_tokens) - 1) else: trailer = u'Trailer unknown' else: trailer = u'Trailer unknown' return BsmEvent(event_type, timestamp, u'. '.join(extra_tokens), trailer, offset) def VerifyFile(self, parser_mediator, file_object): """Check if the file is a BSM file. Args: parser_mediator: A parser mediator object (instance of ParserMediator). file_event: file that we want to check. Returns: True if this is a valid BSM file, otherwise False. """ if file_object.tell() != 0: file_object.seek(0) # First part of the entry is always a Header. try: token_id = self.BSM_TYPE.parse_stream(file_object) except (IOError, construct.FieldError): return False if token_id not in self.BSM_TYPE_LIST: return False bsm_type, structure = self.BSM_TYPE_LIST.get(token_id, [u'', u'']) try: if bsm_type == u'BSM_HEADER32': header = structure.parse_stream(file_object) elif bsm_type == u'BSM_HEADER64': header = structure.parse_stream(file_object) elif bsm_type == u'BSM_HEADER32_EX': header = structure.parse_stream(file_object) else: return False except (IOError, construct.FieldError): return False if header.bsm_header.version != self.AUDIT_HEADER_VERSION: return False try: token_id = self.BSM_TYPE.parse_stream(file_object) except (IOError, construct.FieldError): return False # If is Mac OS X BSM file, next entry is a text token indicating # if it is a normal start or it is a recovery track. if parser_mediator.platform == u'MacOSX': bsm_type_list = self.BSM_TYPE_LIST.get(token_id) if not bsm_type_list: return False if bsm_type_list[0] != u'BSM_TOKEN_TEXT': logging.warning( u'It is not a valid first entry for Mac OS X BSM.') return False try: token = self.BSM_TOKEN_TEXT.parse_stream(file_object) except (IOError, construct.FieldError): return text = self._CopyUtf8ByteArrayToString(token.text) if (text != u'launchctl::Audit startup' and text != u'launchctl::Audit recovery'): logging.warning( u'It is not a valid first entry for Mac OS X BSM.') return False file_object.seek(0) return True def TryWithUntestedStructures(self, file_object, token_id, pending): """Try to parse the pending part of the entry using untested structures. Args: file_object: BSM file. token_id: integer with the id that comes from the unknown token. pending: pending length of the entry. Returns: A list of extra tokens data that can be parsed using non-tested structures. A message indicating that a structure cannot be parsed is added for unparsed structures. """ # Data from the unknown structure. start_position = file_object.tell() start_token_id = token_id extra_tokens = [] # Read all the "pending" bytes. try: if token_id in self.bsm_type_list_all: token = self.bsm_type_list_all[token_id][1].parse_stream( file_object) extra_tokens.append( self.FormatToken(token_id, token, file_object)) while file_object.tell() < (start_position + pending): # Check if it is a known token. try: token_id = self.BSM_TYPE.parse_stream(file_object) except (IOError, construct.FieldError): logging.warning( u'Unable to parse the Token ID at position: {0:d}'. format(file_object.tell())) return if token_id not in self.bsm_type_list_all: break token = self.bsm_type_list_all[token_id][1].parse_stream( file_object) extra_tokens.append( self.FormatToken(token_id, token, file_object)) except (IOError, construct.FieldError): token_id = 255 next_entry = (start_position + pending) if file_object.tell() != next_entry: # Unknown Structure. logging.warning( u'Unknown Token at "0x{0:X}", ID: {1} (0x{2:X})'.format( start_position - 1, token_id, token_id)) # TODO: another way to save this information must be found. extra_tokens.append(u'Plaso: some tokens from this entry can ' u'not be saved. Entry at 0x{0:X} with unknown ' u'token id "0x{1:X}".'.format( start_position - 1, start_token_id)) # Move to next entry. file_object.seek(next_entry - file_object.tell(), os.SEEK_CUR) # It returns null list because it doesn't know witch structure was # the incorrect structure that makes that it can arrive to the spected # end of the entry. return [] return extra_tokens # TODO: instead of compare the text to know what structure was parsed # is better to compare directly the numeric number (token_id), # less readable, but better performance. def FormatToken(self, token_id, token, file_object): """Parse the Token depending of the type of the structure. Args: token_id: Identification integer of the token_type. token: Token struct to parse. file_object: BSM file. Returns: String with the parsed Token values. """ if token_id not in self.bsm_type_list_all: return u'Type Unknown: {0:d} (0x{0:X})'.format(token_id) bsm_type, _ = self.bsm_type_list_all.get(token_id, [u'', u'']) if bsm_type in [ u'BSM_TOKEN_TEXT', u'BSM_TOKEN_PATH', u'BSM_TOKEN_ZONENAME' ]: try: string = self._CopyUtf8ByteArrayToString(token.text) except TypeError: string = u'Unknown' return u'[{0}: {1:s}]'.format(bsm_type, string) elif bsm_type in [ u'BSM_TOKEN_RETURN32', u'BSM_TOKEN_RETURN64', u'BSM_TOKEN_EXIT' ]: return u'[{0}: {1} ({2}), System call status: {3}]'.format( bsm_type, bsmtoken.BSM_ERRORS.get(token.status, u'Unknown'), token.status, token.return_value) elif bsm_type in [u'BSM_TOKEN_SUBJECT32', u'BSM_TOKEN_SUBJECT64']: return ( u'[{0}: aid({1}), euid({2}), egid({3}), uid({4}), gid({5}), ' u'pid({6}), session_id({7}), terminal_port({8}), ' u'terminal_ip({9})]').format( bsm_type, token.subject_data.audit_uid, token.subject_data.effective_uid, token.subject_data.effective_gid, token.subject_data.real_uid, token.subject_data.real_gid, token.subject_data.pid, token.subject_data.session_id, token.terminal_port, self._IPv4Format(token.ipv4)) elif bsm_type in [ u'BSM_TOKEN_SUBJECT32_EX', u'BSM_TOKEN_SUBJECT64_EX' ]: if token.bsm_ip_type_short.net_type == self.AU_IPv6: ip = self._IPv6Format(token.bsm_ip_type_short.ip_addr.high, token.bsm_ip_type_short.ip_addr.low) elif token.bsm_ip_type_short.net_type == self.AU_IPv4: ip = self._IPv4Format(token.bsm_ip_type_short.ip_addr) else: ip = u'unknown' return ( u'[{0}: aid({1}), euid({2}), egid({3}), uid({4}), gid({5}), ' u'pid({6}), session_id({7}), terminal_port({8}), ' u'terminal_ip({9})]').format( bsm_type, token.subject_data.audit_uid, token.subject_data.effective_uid, token.subject_data.effective_gid, token.subject_data.real_uid, token.subject_data.real_gid, token.subject_data.pid, token.subject_data.session_id, token.terminal_port, ip) elif bsm_type in [u'BSM_TOKEN_ARGUMENT32', u'BSM_TOKEN_ARGUMENT64']: string = self._CopyUtf8ByteArrayToString(token.text) return u'[{0}: {1:s}({2}) is 0x{3:X}]'.format( bsm_type, string, token.num_arg, token.name_arg) elif bsm_type in [u'BSM_TOKEN_EXEC_ARGUMENTS', u'BSM_TOKEN_EXEC_ENV']: arguments = [] for _ in range(0, token): sub_token = self.BSM_TOKEN_EXEC_ARGUMENT.parse_stream( file_object) string = self._CopyUtf8ByteArrayToString(sub_token.text) arguments.append(string) return u'[{0}: {1:s}]'.format(bsm_type, u' '.join(arguments)) elif bsm_type == u'BSM_TOKEN_AUT_SOCKINET32': return (u'[{0}: {1} ({2}) open in port {3}. Address {4}]'.format( bsm_type, bsmtoken.BSM_PROTOCOLS.get(token.net_type, u'UNKNOWN'), token.net_type, token.port_number, self._IPv4Format(token.ipv4))) elif bsm_type == u'BSM_TOKEN_AUT_SOCKINET128': return u'[{0}: {1} ({2}) open in port {3}. Address {4}]'.format( bsm_type, bsmtoken.BSM_PROTOCOLS.get(token.net_type, u'UNKNOWN'), token.net_type, token.port_number, self._IPv6Format(token.ipv6.high, token.ipv6.low)) elif bsm_type == u'BSM_TOKEN_ADDR': return u'[{0}: {1}]'.format(bsm_type, self._IPv4Format(token)) elif bsm_type == u'BSM_TOKEN_IP': return u'[IPv4_Header: 0x{0:s}]'.format(token.encode(u'hex')) elif bsm_type == u'BSM_TOKEN_ADDR_EXT': return u'[{0}: {1} ({2}). Address {3}]'.format( bsm_type, bsmtoken.BSM_PROTOCOLS.get(token.net_type, u'UNKNOWN'), token.net_type, self._IPv6Format(token.ipv6.high, token.ipv6.low)) elif bsm_type == u'BSM_TOKEN_PORT': return u'[{0}: {1}]'.format(bsm_type, token) elif bsm_type == u'BSM_TOKEN_TRAILER': return u'[{0}: {1}]'.format(bsm_type, token.record_length) elif bsm_type == u'BSM_TOKEN_FILE': # TODO: if this timestamp is usefull, it must be extracted as a separate # event object. timestamp = timelib.Timestamp.FromPosixTimeWithMicrosecond( token.timestamp, token.microsecond) date_time = timelib.Timestamp.CopyToDatetime(timestamp, pytz.UTC) date_time_string = date_time.strftime(u'%Y-%m-%d %H:%M:%S') string = self._CopyUtf8ByteArrayToString(token.text) return u'[{0}: {1:s}, timestamp: {2:s}]'.format( bsm_type, string, date_time_string) elif bsm_type == u'BSM_TOKEN_IPC': return u'[{0}: object type {1}, object id {2}]'.format( bsm_type, token.object_type, token.object_id) elif bsm_type in [u'BSM_TOKEN_PROCESS32', u'BSM_TOKEN_PROCESS64']: return ( u'[{0}: aid({1}), euid({2}), egid({3}), uid({4}), gid({5}), ' u'pid({6}), session_id({7}), terminal_port({8}), ' u'terminal_ip({9})]').format( bsm_type, token.subject_data.audit_uid, token.subject_data.effective_uid, token.subject_data.effective_gid, token.subject_data.real_uid, token.subject_data.real_gid, token.subject_data.pid, token.subject_data.session_id, token.terminal_port, self._IPv4Format(token.ipv4)) elif bsm_type in [ u'BSM_TOKEN_PROCESS32_EX', u'BSM_TOKEN_PROCESS64_EX' ]: if token.bsm_ip_type_short.net_type == self.AU_IPv6: ip = self._IPv6Format(token.bsm_ip_type_short.ip_addr.high, token.bsm_ip_type_short.ip_addr.low) elif token.bsm_ip_type_short.net_type == self.AU_IPv4: ip = self._IPv4Format(token.bsm_ip_type_short.ip_addr) else: ip = u'unknown' return ( u'[{0}: aid({1}), euid({2}), egid({3}), uid({4}), gid({5}), ' u'pid({6}), session_id({7}), terminal_port({8}), ' u'terminal_ip({9})]').format( bsm_type, token.subject_data.audit_uid, token.subject_data.effective_uid, token.subject_data.effective_gid, token.subject_data.real_uid, token.subject_data.real_gid, token.subject_data.pid, token.subject_data.session_id, token.terminal_port, ip) elif bsm_type == u'BSM_TOKEN_DATA': data = [] data_type = bsmtoken.BSM_TOKEN_DATA_TYPE.get(token.data_type, u'') if data_type == u'AUR_CHAR': for _ in range(token.unit_count): data.append( self.BSM_TOKEN_DATA_CHAR.parse_stream(file_object)) elif data_type == u'AUR_SHORT': for _ in range(token.unit_count): data.append( self.BSM_TOKEN_DAT_SHORT.parse_stream(file_object)) elif data_type == u'AUR_INT32': for _ in range(token.unit_count): data.append( self.BSM_TOKEN_DATA_INTEGER.parse_stream(file_object)) else: data.append(u'Unknown type data') # TODO: the data when it is string ends with ".", HW a space is return # after uses the UTF-8 conversion. return u'[{0}: Format data: {1}, Data: {2}]'.format( bsm_type, bsmtoken.BSM_TOKEN_DATA_PRINT[token.how_to_print], self._RawToUTF8(u''.join(data))) elif bsm_type in [u'BSM_TOKEN_ATTR32', u'BSM_TOKEN_ATTR64']: return (u'[{0}: Mode: {1}, UID: {2}, GID: {3}, ' u'File system ID: {4}, Node ID: {5}, Device: {6}]').format( bsm_type, token.file_mode, token.uid, token.gid, token.file_system_id, token.file_system_node_id, token.device) elif bsm_type == u'BSM_TOKEN_GROUPS': arguments = [] for _ in range(token): arguments.append( self._RawToUTF8( self.BSM_TOKEN_DATA_INTEGER.parse_stream(file_object))) return u'[{0}: {1:s}]'.format(bsm_type, u','.join(arguments)) elif bsm_type == u'BSM_TOKEN_AUT_SOCKINET32_EX': if bsmtoken.BSM_PROTOCOLS.get(token.socket_domain, u'') == u'INET6': saddr = self._IPv6Format(token.structure_addr_port.saddr_high, token.structure_addr_port.saddr_low) daddr = self._IPv6Format(token.structure_addr_port.daddr_high, token.structure_addr_port.daddr_low) else: saddr = self._IPv4Format( token.structure_addr_port.source_address) daddr = self._IPv4Format( token.structure_addr_port.destination_address) return u'[{0}: from {1} port {2} to {3} port {4}]'.format( bsm_type, saddr, token.structure_addr_port.source_port, daddr, token.structure_addr_port.destination_port) elif bsm_type == u'BSM_TOKEN_IPC_PERM': return (u'[{0}: user id {1}, group id {2}, create user id {3}, ' u'create group id {4}, access {5}]').format( bsm_type, token.user_id, token.group_id, token.creator_user_id, token.creator_group_id, token.access_mode) elif bsm_type == u'BSM_TOKEN_SOCKET_UNIX': string = self._CopyUtf8ByteArrayToString(token.path) return u'[{0}: Family {1}, Path {2:s}]'.format( bsm_type, token.family, string) elif bsm_type == u'BSM_TOKEN_OPAQUE': string = self._CopyByteArrayToBase16String(token.text) return u'[{0}: {1:s}]'.format(bsm_type, string) elif bsm_type == u'BSM_TOKEN_SEQUENCE': return u'[{0}: {1}]'.format(bsm_type, token)
def EnumSwitch( type_field, type_enum, value_field, value_choices, # noqa default=construct.Switch.NoDefault): """ Maps the members of an :py:class:`enum.Enum` to arbitrary :py:func:`construct.Constructs`. It returns a tuple intended to be spliced into another :py:func:`construct.Construct`'s definition: >>> from tls._common._constructs import EnumSwitch >>> import construct, enum >>> class IntEnum(enum.Enum): ... VALUE = 1 ... >>> construct.Struct( ... "name", ... construct.UBInt8("an_integer"), ... *EnumSwitch(type_field=construct.UBInt8("type"), ... type_enum=IntEnum, ... value_field="value", ... value_choices={ ... IntEnum.VALUE: construct.UBInt8("first"), ... }) ... ) ... Struct('name') :param type_field: The construct that represents the enum's members. The type of this should correspond to the enum members' types, so an enum with a maximum value of 65535, for example, would use a :py:class:`construct.macros.UBInt16`. :type type_field: :py:class:`construct.Construct` :param type_enum: The enum to encode and decode. :type type_enum: :py:class:`enum.Enum` :param value_field: The attribute name under which this value will be accessible. :type value_field: :py:class:`str` :param value_choices: A dictionary that maps members of `type_enum` to subconstructs. This follows :py:func:`construct.core.Switch`'s API, so ``_default_`` will match any members without an explicit mapping. :type value_choices: :py:class:`dict` :param default: A default field to use when no explicit match is found for the key in the provided mapping. This follows :py:func:`construct.core.Switch`'s API, so if not supplied, an exception will be raised when the key is not found. :py:class:`construct.Pass` can be used for do-nothing. :type default: :py:class:`construct.Construct` :return: A :py:class:`tuple` of the form (:py:func:`EnumClass`, :py:func:`construct.core.Switch`) """ return (EnumClass(type_field, type_enum), construct.Switch(value_field, operator.attrgetter(type_field.name), value_choices, default=default))
class BSMParser(interface.FileObjectParser): """Parser for BSM files.""" NAME = 'bsm_log' DESCRIPTION = 'Parser for BSM log files.' # BSM supported version (0x0b = 11). AUDIT_HEADER_VERSION = 11 # Magic Trail Header. BSM_TOKEN_TRAILER_MAGIC = b'b105' # IP Version constants. AU_IPv4 = 4 AU_IPv6 = 16 IPV4_STRUCT = construct.UBInt32('ipv4') IPV6_STRUCT = construct.Struct( 'ipv6', construct.UBInt64('high'), construct.UBInt64('low')) # Tested structures. # INFO: I have ommited the ID in the structures declaration. # I used the BSM_TYPE first to read the ID, and then, the structure. # Tokens always start with an ID value that identifies their token # type and subsequent structure. _BSM_TOKEN = construct.UBInt8('token_id') # Data type structures. BSM_TOKEN_DATA_CHAR = construct.String('value', 1) BSM_TOKEN_DATA_SHORT = construct.UBInt16('value') BSM_TOKEN_DATA_INTEGER = construct.UBInt32('value') # Common structure used by other structures. # audit_uid: integer, uid that generates the entry. # effective_uid: integer, the permission user used. # effective_gid: integer, the permission group used. # real_uid: integer, user id of the user that execute the process. # real_gid: integer, group id of the group that execute the process. # pid: integer, identification number of the process. # session_id: unknown, need research. BSM_TOKEN_SUBJECT_SHORT = construct.Struct( 'subject_data', construct.UBInt32('audit_uid'), construct.UBInt32('effective_uid'), construct.UBInt32('effective_gid'), construct.UBInt32('real_uid'), construct.UBInt32('real_gid'), construct.UBInt32('pid'), construct.UBInt32('session_id')) # Common structure used by other structures. # Identify the kind of inet (IPv4 or IPv6) # TODO: instead of 16, AU_IPv6 must be used. BSM_IP_TYPE_SHORT = construct.Struct( 'bsm_ip_type_short', construct.UBInt32('net_type'), construct.Switch( 'ip_addr', _BSMTokenGetNetType, {16: IPV6_STRUCT}, default=IPV4_STRUCT)) # Initial fields structure used by header structures. # length: integer, the length of the entry, equal to trailer (doc: length). # version: integer, version of BSM (AUDIT_HEADER_VERSION). # event_type: integer, the type of event (/etc/security/audit_event). # modifier: integer, unknown, need research (It is always 0). BSM_HEADER = construct.Struct( 'bsm_header', construct.UBInt32('length'), construct.UBInt8('version'), construct.UBInt16('event_type'), construct.UBInt16('modifier')) # First token of one entry. # timestamp: unsigned integer, number of seconds since # January 1, 1970 00:00:00 UTC. # microseconds: unsigned integer, number of micro seconds. BSM_HEADER32 = construct.Struct( 'bsm_header32', BSM_HEADER, construct.UBInt32('timestamp'), construct.UBInt32('microseconds')) BSM_HEADER64 = construct.Struct( 'bsm_header64', BSM_HEADER, construct.UBInt64('timestamp'), construct.UBInt64('microseconds')) BSM_HEADER32_EX = construct.Struct( 'bsm_header32_ex', BSM_HEADER, BSM_IP_TYPE_SHORT, construct.UBInt32('timestamp'), construct.UBInt32('microseconds')) # Token TEXT, provides extra information. BSM_TOKEN_TEXT = construct.Struct( 'bsm_token_text', construct.UBInt16('length'), construct.Array(_BSMTokenGetLength, construct.UBInt8('text'))) # Path of the executable. BSM_TOKEN_PATH = BSM_TOKEN_TEXT # Identified the end of the record (follow by TRAILER). # status: integer that identifies the status of the exit (BSM_ERRORS). # return: returned value from the operation. BSM_TOKEN_RETURN32 = construct.Struct( 'bsm_token_return32', construct.UBInt8('status'), construct.UBInt32('return_value')) BSM_TOKEN_RETURN64 = construct.Struct( 'bsm_token_return64', construct.UBInt8('status'), construct.UBInt64('return_value')) # Identified the number of bytes that was written. # magic: 2 bytes that identifies the TRAILER (BSM_TOKEN_TRAILER_MAGIC). # length: integer that has the number of bytes from the entry size. BSM_TOKEN_TRAILER = construct.Struct( 'bsm_token_trailer', construct.UBInt16('magic'), construct.UBInt32('record_length')) # A 32-bits argument. # num_arg: the number of the argument. # name_arg: the argument's name. # text: the string value of the argument. BSM_TOKEN_ARGUMENT32 = construct.Struct( 'bsm_token_argument32', construct.UBInt8('num_arg'), construct.UBInt32('name_arg'), construct.UBInt16('length'), construct.Array(_BSMTokenGetLength, construct.UBInt8('text'))) # A 64-bits argument. # num_arg: integer, the number of the argument. # name_arg: text, the argument's name. # text: the string value of the argument. BSM_TOKEN_ARGUMENT64 = construct.Struct( 'bsm_token_argument64', construct.UBInt8('num_arg'), construct.UBInt64('name_arg'), construct.UBInt16('length'), construct.Array(_BSMTokenGetLength, construct.UBInt8('text'))) # Identify an user. # terminal_id: unknown, research needed. # terminal_addr: unknown, research needed. BSM_TOKEN_SUBJECT32 = construct.Struct( 'bsm_token_subject32', BSM_TOKEN_SUBJECT_SHORT, construct.UBInt32('terminal_port'), IPV4_STRUCT) # Identify an user using a extended Token. # terminal_port: unknown, need research. # net_type: unknown, need research. BSM_TOKEN_SUBJECT32_EX = construct.Struct( 'bsm_token_subject32_ex', BSM_TOKEN_SUBJECT_SHORT, construct.UBInt32('terminal_port'), BSM_IP_TYPE_SHORT) # au_to_opaque // AUT_OPAQUE BSM_TOKEN_OPAQUE = BSM_TOKEN_TEXT # au_to_seq // AUT_SEQ BSM_TOKEN_SEQUENCE = BSM_TOKEN_DATA_INTEGER # Program execution with options. # For each argument we are going to have a string+ "\x00". # Example: [00 00 00 02][41 42 43 00 42 42 00] # 2 Arguments, Arg1: [414243] Arg2: [4242]. BSM_TOKEN_EXEC_ARGUMENTS = construct.UBInt32('number_arguments') BSM_TOKEN_EXEC_ARGUMENT = construct.Struct( 'bsm_token_exec_argument', construct.RepeatUntil( _BSMTokenIsEndOfString, construct.StaticField("text", 1))) # au_to_in_addr // AUT_IN_ADDR: BSM_TOKEN_ADDR = IPV4_STRUCT # au_to_in_addr_ext // AUT_IN_ADDR_EX: BSM_TOKEN_ADDR_EXT = construct.Struct( 'bsm_token_addr_ext', construct.UBInt32('net_type'), IPV6_STRUCT) # au_to_ip // AUT_IP: # TODO: parse this header in the correct way. BSM_TOKEN_IP = construct.String('binary_ipv4_add', 20) # au_to_ipc // AUT_IPC: BSM_TOKEN_IPC = construct.Struct( 'bsm_token_ipc', construct.UBInt8('object_type'), construct.UBInt32('object_id')) # au_to_ipc_perm // au_to_ipc_perm BSM_TOKEN_IPC_PERM = construct.Struct( 'bsm_token_ipc_perm', construct.UBInt32('user_id'), construct.UBInt32('group_id'), construct.UBInt32('creator_user_id'), construct.UBInt32('creator_group_id'), construct.UBInt32('access_mode'), construct.UBInt32('slot_seq'), construct.UBInt32('key')) # au_to_iport // AUT_IPORT: BSM_TOKEN_PORT = construct.UBInt16('port_number') # au_to_file // AUT_OTHER_FILE32: BSM_TOKEN_FILE = construct.Struct( 'bsm_token_file', construct.UBInt32('timestamp'), construct.UBInt32('microseconds'), construct.UBInt16('length'), construct.Array(_BSMTokenGetLength, construct.UBInt8('text'))) # au_to_subject64 // AUT_SUBJECT64: BSM_TOKEN_SUBJECT64 = construct.Struct( 'bsm_token_subject64', BSM_TOKEN_SUBJECT_SHORT, construct.UBInt64('terminal_port'), IPV4_STRUCT) # au_to_subject64_ex // AU_IPv4: BSM_TOKEN_SUBJECT64_EX = construct.Struct( 'bsm_token_subject64_ex', BSM_TOKEN_SUBJECT_SHORT, construct.UBInt32('terminal_port'), construct.UBInt32('terminal_type'), BSM_IP_TYPE_SHORT) # au_to_process32 // AUT_PROCESS32: BSM_TOKEN_PROCESS32 = construct.Struct( 'bsm_token_process32', BSM_TOKEN_SUBJECT_SHORT, construct.UBInt32('terminal_port'), IPV4_STRUCT) # au_to_process64 // AUT_PROCESS32: BSM_TOKEN_PROCESS64 = construct.Struct( 'bsm_token_process64', BSM_TOKEN_SUBJECT_SHORT, construct.UBInt64('terminal_port'), IPV4_STRUCT) # au_to_process32_ex // AUT_PROCESS32_EX: BSM_TOKEN_PROCESS32_EX = construct.Struct( 'bsm_token_process32_ex', BSM_TOKEN_SUBJECT_SHORT, construct.UBInt32('terminal_port'), BSM_IP_TYPE_SHORT) # au_to_process64_ex // AUT_PROCESS64_EX: BSM_TOKEN_PROCESS64_EX = construct.Struct( 'bsm_token_process64_ex', BSM_TOKEN_SUBJECT_SHORT, construct.UBInt64('terminal_port'), BSM_IP_TYPE_SHORT) # au_to_sock_inet32 // AUT_SOCKINET32: BSM_TOKEN_AUT_SOCKINET32 = construct.Struct( 'bsm_token_aut_sockinet32', construct.UBInt16('net_type'), construct.UBInt16('port_number'), IPV4_STRUCT) # Info: checked against the source code of XNU, but not against # real BSM file. BSM_TOKEN_AUT_SOCKINET128 = construct.Struct( 'bsm_token_aut_sockinet128', construct.UBInt16('net_type'), construct.UBInt16('port_number'), IPV6_STRUCT) INET6_ADDR_TYPE = construct.Struct( 'addr_type', construct.UBInt16('ip_type'), construct.UBInt16('source_port'), construct.UBInt64('saddr_high'), construct.UBInt64('saddr_low'), construct.UBInt16('destination_port'), construct.UBInt64('daddr_high'), construct.UBInt64('daddr_low')) INET4_ADDR_TYPE = construct.Struct( 'addr_type', construct.UBInt16('ip_type'), construct.UBInt16('source_port'), construct.UBInt32('source_address'), construct.UBInt16('destination_port'), construct.UBInt32('destination_address')) # au_to_socket_ex // AUT_SOCKET_EX # TODO: Change the 26 for unixbsm.BSM_PROTOCOLS.INET6. BSM_TOKEN_AUT_SOCKINET32_EX = construct.Struct( 'bsm_token_aut_sockinet32_ex', construct.UBInt16('socket_domain'), construct.UBInt16('socket_type'), construct.Switch( 'structure_addr_port', _BSMTokenGetSocketDomain, {26: INET6_ADDR_TYPE}, default=INET4_ADDR_TYPE)) # au_to_sock_unix // AUT_SOCKUNIX BSM_TOKEN_SOCKET_UNIX = construct.Struct( 'bsm_token_au_to_sock_unix', construct.UBInt16('family'), construct.RepeatUntil( _BSMTokenIsEndOfString, construct.StaticField("path", 1))) # au_to_data // au_to_data # how to print: bsmtoken.BSM_TOKEN_DATA_PRINT. # type: bsmtoken.BSM_TOKEN_DATA_TYPE. # unit_count: number of type values. # BSM_TOKEN_DATA has a end field = type * unit_count BSM_TOKEN_DATA = construct.Struct( 'bsm_token_data', construct.UBInt8('how_to_print'), construct.UBInt8('data_type'), construct.UBInt8('unit_count')) # au_to_attr32 // AUT_ATTR32 BSM_TOKEN_ATTR32 = construct.Struct( 'bsm_token_attr32', construct.UBInt32('file_mode'), construct.UBInt32('uid'), construct.UBInt32('gid'), construct.UBInt32('file_system_id'), construct.UBInt64('file_system_node_id'), construct.UBInt32('device')) # au_to_attr64 // AUT_ATTR64 BSM_TOKEN_ATTR64 = construct.Struct( 'bsm_token_attr64', construct.UBInt32('file_mode'), construct.UBInt32('uid'), construct.UBInt32('gid'), construct.UBInt32('file_system_id'), construct.UBInt64('file_system_node_id'), construct.UBInt64('device')) # au_to_exit // AUT_EXIT BSM_TOKEN_EXIT = construct.Struct( 'bsm_token_exit', construct.UBInt32('status'), construct.UBInt32('return_value')) # au_to_newgroups // AUT_NEWGROUPS # INFO: we must read BSM_TOKEN_DATA_INTEGER for each group. BSM_TOKEN_GROUPS = construct.UBInt16('group_number') # au_to_exec_env == au_to_exec_args BSM_TOKEN_EXEC_ENV = BSM_TOKEN_EXEC_ARGUMENTS # au_to_zonename //AUT_ZONENAME BSM_TOKEN_ZONENAME = BSM_TOKEN_TEXT # Token ID. # List of valid Token_ID. # Token_ID -> (NAME_STRUCTURE, STRUCTURE) # Only the checked structures are been added to the valid structures lists. _BSM_TOKEN_TYPES = { 17: ('BSM_TOKEN_FILE', BSM_TOKEN_FILE), 19: ('BSM_TOKEN_TRAILER', BSM_TOKEN_TRAILER), 20: ('BSM_HEADER32', BSM_HEADER32), 21: ('BSM_HEADER64', BSM_HEADER64), 33: ('BSM_TOKEN_DATA', BSM_TOKEN_DATA), 34: ('BSM_TOKEN_IPC', BSM_TOKEN_IPC), 35: ('BSM_TOKEN_PATH', BSM_TOKEN_PATH), 36: ('BSM_TOKEN_SUBJECT32', BSM_TOKEN_SUBJECT32), 38: ('BSM_TOKEN_PROCESS32', BSM_TOKEN_PROCESS32), 39: ('BSM_TOKEN_RETURN32', BSM_TOKEN_RETURN32), 40: ('BSM_TOKEN_TEXT', BSM_TOKEN_TEXT), 41: ('BSM_TOKEN_OPAQUE', BSM_TOKEN_OPAQUE), 42: ('BSM_TOKEN_ADDR', BSM_TOKEN_ADDR), 43: ('BSM_TOKEN_IP', BSM_TOKEN_IP), 44: ('BSM_TOKEN_PORT', BSM_TOKEN_PORT), 45: ('BSM_TOKEN_ARGUMENT32', BSM_TOKEN_ARGUMENT32), 47: ('BSM_TOKEN_SEQUENCE', BSM_TOKEN_SEQUENCE), 96: ('BSM_TOKEN_ZONENAME', BSM_TOKEN_ZONENAME), 113: ('BSM_TOKEN_ARGUMENT64', BSM_TOKEN_ARGUMENT64), 114: ('BSM_TOKEN_RETURN64', BSM_TOKEN_RETURN64), 116: ('BSM_HEADER32_EX', BSM_HEADER32_EX), 119: ('BSM_TOKEN_PROCESS64', BSM_TOKEN_PROCESS64), 122: ('BSM_TOKEN_SUBJECT32_EX', BSM_TOKEN_SUBJECT32_EX), 127: ('BSM_TOKEN_AUT_SOCKINET32_EX', BSM_TOKEN_AUT_SOCKINET32_EX), 128: ('BSM_TOKEN_AUT_SOCKINET32', BSM_TOKEN_AUT_SOCKINET32)} # Untested structures. # When not tested structure is found, we try to parse using also # these structures. BSM_TYPE_LIST_NOT_TESTED = { 49: ('BSM_TOKEN_ATTR', BSM_TOKEN_ATTR32), 50: ('BSM_TOKEN_IPC_PERM', BSM_TOKEN_IPC_PERM), 52: ('BSM_TOKEN_GROUPS', BSM_TOKEN_GROUPS), 59: ('BSM_TOKEN_GROUPS', BSM_TOKEN_GROUPS), 60: ('BSM_TOKEN_EXEC_ARGUMENTS', BSM_TOKEN_EXEC_ARGUMENTS), 61: ('BSM_TOKEN_EXEC_ENV', BSM_TOKEN_EXEC_ENV), 62: ('BSM_TOKEN_ATTR32', BSM_TOKEN_ATTR32), 82: ('BSM_TOKEN_EXIT', BSM_TOKEN_EXIT), 115: ('BSM_TOKEN_ATTR64', BSM_TOKEN_ATTR64), 117: ('BSM_TOKEN_SUBJECT64', BSM_TOKEN_SUBJECT64), 123: ('BSM_TOKEN_PROCESS32_EX', BSM_TOKEN_PROCESS32_EX), 124: ('BSM_TOKEN_PROCESS64_EX', BSM_TOKEN_PROCESS64_EX), 125: ('BSM_TOKEN_SUBJECT64_EX', BSM_TOKEN_SUBJECT64_EX), 126: ('BSM_TOKEN_ADDR_EXT', BSM_TOKEN_ADDR_EXT), 129: ('BSM_TOKEN_AUT_SOCKINET128', BSM_TOKEN_AUT_SOCKINET128), 130: ('BSM_TOKEN_SOCKET_UNIX', BSM_TOKEN_SOCKET_UNIX)} MESSAGE_CAN_NOT_SAVE = ( 'Plaso: some tokens from this entry can not be saved. Entry at 0x{0:X} ' 'with unknown token id "0x{1:X}".') # BSM token types: # https://github.com/openbsm/openbsm/blob/master/sys/bsm/audit_record.h _BSM_TOKEN_TYPE_ARGUMENT32 = 45 _BSM_TOKEN_TYPE_ARGUMENT64 = 113 _BSM_TOKEN_TYPE_ATTR = 49 _BSM_TOKEN_TYPE_ATTR32 = 62 _BSM_TOKEN_TYPE_ATTR64 = 115 _BSM_TOKEN_TYPE_EXEC_ARGUMENTS = 60 _BSM_TOKEN_TYPE_EXEC_ENV = 61 _BSM_TOKEN_TYPE_EXIT = 82 _BSM_TOKEN_TYPE_HEADER32 = 20 _BSM_TOKEN_TYPE_HEADER32_EX = 116 _BSM_TOKEN_TYPE_HEADER64 = 21 _BSM_TOKEN_TYPE_PATH = 35 _BSM_TOKEN_TYPE_PROCESS32 = 38 _BSM_TOKEN_TYPE_PROCESS32_EX = 123 _BSM_TOKEN_TYPE_PROCESS64 = 119 _BSM_TOKEN_TYPE_PROCESS64_EX = 124 _BSM_TOKEN_TYPE_RETURN32 = 39 _BSM_TOKEN_TYPE_RETURN64 = 114 _BSM_TOKEN_TYPE_SUBJECT32 = 36 _BSM_TOKEN_TYPE_SUBJECT32_EX = 122 _BSM_TOKEN_TYPE_SUBJECT64 = 117 _BSM_TOKEN_TYPE_SUBJECT64_EX = 125 _BSM_TOKEN_TYPE_TEXT = 40 _BSM_TOKEN_TYPE_ZONENAME = 96 _BSM_ARGUMENT_TOKEN_TYPES = ( _BSM_TOKEN_TYPE_ARGUMENT32, _BSM_TOKEN_TYPE_ARGUMENT64) _BSM_ATTR_TOKEN_TYPES = ( _BSM_TOKEN_TYPE_ATTR, _BSM_TOKEN_TYPE_ATTR32, _BSM_TOKEN_TYPE_ATTR64) _BSM_EXEV_TOKEN_TYPES = ( _BSM_TOKEN_TYPE_EXEC_ARGUMENTS, _BSM_TOKEN_TYPE_EXEC_ENV) _BSM_HEADER_TOKEN_TYPES = ( _BSM_TOKEN_TYPE_HEADER32, _BSM_TOKEN_TYPE_HEADER32_EX, _BSM_TOKEN_TYPE_HEADER64) _BSM_PROCESS_TOKEN_TYPES = ( _BSM_TOKEN_TYPE_PROCESS32, _BSM_TOKEN_TYPE_PROCESS64) _BSM_PROCESS_EX_TOKEN_TYPES = ( _BSM_TOKEN_TYPE_PROCESS32_EX, _BSM_TOKEN_TYPE_PROCESS64_EX) _BSM_RETURN_TOKEN_TYPES = ( _BSM_TOKEN_TYPE_EXIT, _BSM_TOKEN_TYPE_RETURN32, _BSM_TOKEN_TYPE_RETURN64) _BSM_SUBJECT_TOKEN_TYPES = ( _BSM_TOKEN_TYPE_SUBJECT32, _BSM_TOKEN_TYPE_SUBJECT64) _BSM_SUBJECT_EX_TOKEN_TYPES = ( _BSM_TOKEN_TYPE_SUBJECT32_EX, _BSM_TOKEN_TYPE_SUBJECT64_EX) _BSM_UTF8_BYTE_ARRAY_TOKEN_TYPES = ( _BSM_TOKEN_TYPE_PATH, _BSM_TOKEN_TYPE_TEXT, _BSM_TOKEN_TYPE_ZONENAME) def __init__(self): """Initializes a parser object.""" super(BSMParser, self).__init__() # Create the dictionary with all token IDs: tested and untested. self._bsm_type_list_all = self._BSM_TOKEN_TYPES.copy() self._bsm_type_list_all.update(self.BSM_TYPE_LIST_NOT_TESTED) def _CopyByteArrayToBase16String(self, byte_array): """Copies a byte array into a base-16 encoded Unicode string. Args: byte_array (bytes): A byte array. Returns: str: a base-16 encoded Unicode string. """ return ''.join(['{0:02x}'.format(byte) for byte in byte_array]) def _CopyUtf8ByteArrayToString(self, byte_array): """Copies a UTF-8 encoded byte array into a Unicode string. Args: byte_array (bytes): A byte array containing an UTF-8 encoded string. Returns: str: A Unicode string. """ byte_stream = b''.join(map(chr, byte_array)) try: string = byte_stream.decode('utf-8') except UnicodeDecodeError: logging.warning('Unable to decode UTF-8 formatted byte array.') string = byte_stream.decode('utf-8', errors='ignore') string, _, _ = string.partition(b'\x00') return string def _IPv4Format(self, address): """Formats an IPv4 address as a human readable string. Args: address (int): IPv4 address. Returns: str: human readable string of IPv4 address in 4 octet representation: "1.2.3.4". """ ipv4_string = self.IPV4_STRUCT.build(address) return socket.inet_ntoa(ipv4_string) def _IPv6Format(self, high, low): """Formats an IPv6 address as a human readable string. Args: high (int): upper 64-bit part of the IPv6 address. low (int): lower 64-bit part of the IPv6 address. Returns: str: human readable string of IPv6 address. """ ipv6_string = self.IPV6_STRUCT.build( construct.Container(high=high, low=low)) # socket.inet_ntop not supported in Windows. if hasattr(socket, 'inet_ntop'): return socket.inet_ntop(socket.AF_INET6, ipv6_string) # TODO: this approach returns double "::", illegal IPv6 addr. str_address = binascii.hexlify(ipv6_string) address = [] blank = False for pos in range(0, len(str_address), 4): if str_address[pos:pos + 4] == '0000': if not blank: address.append('') blank = True else: blank = False address.append(str_address[pos:pos + 4].lstrip('0')) return ':'.join(address) def _ParseBSMEvent(self, parser_mediator, file_object): """Parses a BSM entry (BSMEvent) from the file-like object. Args: parser_mediator (ParserMediator): mediates interactions between parsers and other components, such as storage and dfvfs. file_object (dfvfs.FileIO): a file-like object. Returns: bool: True if the BSM entry was parsed. """ record_start_offset = file_object.tell() try: token_type = self._BSM_TOKEN.parse_stream(file_object) except (IOError, construct.FieldError) as exception: parser_mediator.ProduceExtractionError(( 'unable to parse BSM token type at offset: 0x{0:08x} with error: ' '{1:s}.').format(record_start_offset, exception)) return False if token_type not in self._BSM_HEADER_TOKEN_TYPES: parser_mediator.ProduceExtractionError( 'unsupported token type: {0:d} at offset: 0x{1:08x}.'.format( token_type, record_start_offset)) # TODO: if it is a Mac OS X, search for the trailer magic value # as a end of the entry can be a possibility to continue. return False _, record_structure = self._BSM_TOKEN_TYPES.get(token_type, ('', None)) try: token = record_structure.parse_stream(file_object) except (IOError, construct.FieldError) as exception: parser_mediator.ProduceExtractionError(( 'unable to parse BSM record at offset: 0x{0:08x} with error: ' '{1:s}.').format(record_start_offset, exception)) return False event_type = bsmtoken.BSM_AUDIT_EVENT.get( token.bsm_header.event_type, 'UNKNOWN') event_type = '{0:s} ({1:d})'.format( event_type, token.bsm_header.event_type) timestamp = (token.timestamp * 1000000) + token.microseconds date_time = dfdatetime_posix_time.PosixTimeInMicroseconds( timestamp=timestamp) record_length = token.bsm_header.length record_end_offset = record_start_offset + record_length # A dict of tokens that has the entry. extra_tokens = {} # Read until we reach the end of the record. while file_object.tell() < record_end_offset: # Check if it is a known token. try: token_type = self._BSM_TOKEN.parse_stream(file_object) except (IOError, construct.FieldError): logging.warning( 'Unable to parse the Token ID at position: {0:d}'.format( file_object.tell())) return False _, record_structure = self._BSM_TOKEN_TYPES.get(token_type, ('', None)) if not record_structure: pending = record_end_offset - file_object.tell() new_extra_tokens = self.TryWithUntestedStructures( file_object, token_type, pending) extra_tokens.update(new_extra_tokens) else: token = record_structure.parse_stream(file_object) new_extra_tokens = self.FormatToken(token_type, token, file_object) extra_tokens.update(new_extra_tokens) if file_object.tell() > record_end_offset: logging.warning( 'Token ID {0:d} not expected at position 0x{1:08x}.' 'Jumping for the next entry.'.format( token_type, file_object.tell())) try: file_object.seek( record_end_offset - file_object.tell(), os.SEEK_CUR) except (IOError, construct.FieldError) as exception: logging.warning( 'Unable to jump to next entry with error: {0:s}'.format(exception)) return False # BSM can be in more than one OS: BSD, Solaris and Mac OS X. if parser_mediator.platform != 'MacOSX': event_data = BSMEventData() else: event_data = MacBSMEventData() # In Mac OS X the last two tokens are the return status and the trailer. return_value = extra_tokens.get('BSM_TOKEN_RETURN32') if not return_value: return_value = extra_tokens.get('BSM_TOKEN_RETURN64') if not return_value: return_value = 'UNKNOWN' event_data.return_value = return_value event_data.event_type = event_type event_data.extra_tokens = extra_tokens event_data.offset = record_start_offset event_data.record_length = record_length # TODO: check why trailer was passed to event in original while # event was expecting record length. # if extra_tokens: # trailer = extra_tokens.get('BSM_TOKEN_TRAILER', 'unknown') event = time_events.DateTimeValuesEvent( date_time, definitions.TIME_DESCRIPTION_CREATION) parser_mediator.ProduceEventWithEventData(event, event_data) return True def _RawToUTF8(self, byte_stream): """Copies a UTF-8 byte stream into a Unicode string. Args: byte_stream (bytes): byte stream containing an UTF-8 encoded string. Returns: str: A Unicode string. """ try: string = byte_stream.decode('utf-8') except UnicodeDecodeError: logging.warning( 'Decode UTF8 failed, the message string may be cut short.') string = byte_stream.decode('utf-8', errors='ignore') return string.partition(b'\x00')[0] def ParseFileObject(self, parser_mediator, file_object, **kwargs): """Parses a BSM file-like object. Args: parser_mediator (ParserMediator): mediates interactions between parsers and other components, such as storage and dfvfs. file_object (dfvfs.FileIO): a file-like object. Raises: UnableToParseFile: when the file cannot be parsed. """ try: is_bsm = self.VerifyFile(parser_mediator, file_object) except (IOError, construct.FieldError) as exception: raise errors.UnableToParseFile( 'Unable to parse BSM file with error: {0:s}'.format(exception)) if not is_bsm: raise errors.UnableToParseFile('Not a BSM File, unable to parse.') file_object.seek(0, os.SEEK_SET) while self._ParseBSMEvent(parser_mediator, file_object): pass def VerifyFile(self, parser_mediator, file_object): """Check if the file is a BSM file. Args: parser_mediator (ParserMediator): mediates interactions between parsers and other components, such as storage and dfvfs. file_object (dfvfs.FileIO): a file-like object. Returns: bool: True if this is a valid BSM file, False otherwise. """ # First part of the entry is always a Header. try: token_type = self._BSM_TOKEN.parse_stream(file_object) except (IOError, construct.FieldError): return False if token_type not in self._BSM_HEADER_TOKEN_TYPES: return False _, record_structure = self._BSM_TOKEN_TYPES.get(token_type, ('', None)) try: header = record_structure.parse_stream(file_object) except (IOError, construct.FieldError): return False if header.bsm_header.version != self.AUDIT_HEADER_VERSION: return False try: token_identifier = self._BSM_TOKEN.parse_stream(file_object) except (IOError, construct.FieldError): return False # If is Mac OS X BSM file, next entry is a text token indicating # if it is a normal start or it is a recovery track. if parser_mediator.platform == 'MacOSX': token_type, record_structure = self._BSM_TOKEN_TYPES.get( token_identifier, ('', None)) if not record_structure: return False if token_type != 'BSM_TOKEN_TEXT': logging.warning('It is not a valid first entry for Mac OS X BSM.') return False try: token = record_structure.parse_stream(file_object) except (IOError, construct.FieldError): return text = self._CopyUtf8ByteArrayToString(token.text) if (text != 'launchctl::Audit startup' and text != 'launchctl::Audit recovery'): logging.warning('It is not a valid first entry for Mac OS X BSM.') return False return True def TryWithUntestedStructures(self, file_object, token_id, pending): """Try to parse the pending part of the entry using untested structures. Args: file_object: BSM file. token_id: integer with the id that comes from the unknown token. pending: pending length of the entry. Returns: A list of extra tokens data that can be parsed using non-tested structures. A message indicating that a structure cannot be parsed is added for unparsed structures. """ # Data from the unknown structure. start_position = file_object.tell() start_token_id = token_id extra_tokens = {} # Read all the "pending" bytes. try: if token_id in self._bsm_type_list_all: token = self._bsm_type_list_all[token_id][1].parse_stream(file_object) new_extra_tokens = self.FormatToken(token_id, token, file_object) extra_tokens.update(new_extra_tokens) while file_object.tell() < (start_position + pending): # Check if it is a known token. try: token_id = self._BSM_TOKEN.parse_stream(file_object) except (IOError, construct.FieldError): logging.warning( 'Unable to parse the Token ID at position: {0:d}'.format( file_object.tell())) return if token_id not in self._bsm_type_list_all: break token = self._bsm_type_list_all[token_id][1].parse_stream(file_object) new_extra_tokens = self.FormatToken(token_id, token, file_object) extra_tokens.update(new_extra_tokens) except (IOError, construct.FieldError): token_id = 255 next_entry = (start_position + pending) if file_object.tell() != next_entry: # Unknown Structure. logging.warning('Unknown Token at "0x{0:X}", ID: {1} (0x{2:X})'.format( start_position - 1, token_id, token_id)) # TODO: another way to save this information must be found. extra_tokens.update( {'message': self.MESSAGE_CAN_NOT_SAVE.format( start_position - 1, start_token_id)}) # Move to next entry. file_object.seek(next_entry - file_object.tell(), os.SEEK_CUR) # It returns null list because it doesn't know witch structure was # the incorrect structure that makes that it can arrive to the spected # end of the entry. return {} return extra_tokens def FormatToken(self, token_id, token, file_object): """Parse the Token depending of the type of the structure. Args: token_id (int): identification of the token_type. token (structure): token struct to parse. file_object: BSM file. Returns: (dict): parsed Token values. Keys for returned dictionary are token name like BSM_TOKEN_SUBJECT32. Values of this dictionary are key-value pairs like terminal_ip:127.0.0.1. """ if token_id not in self._bsm_type_list_all: return {} bsm_type, _ = self._bsm_type_list_all.get(token_id, ['', '']) if token_id in self._BSM_UTF8_BYTE_ARRAY_TOKEN_TYPES: try: string = self._CopyUtf8ByteArrayToString(token.text) except TypeError: string = 'Unknown' return {bsm_type: string} elif token_id in self._BSM_RETURN_TOKEN_TYPES: return {bsm_type: { 'error': bsmtoken.BSM_ERRORS.get(token.status, 'Unknown'), 'token_status': token.status, 'call_status': token.return_value }} elif token_id in self._BSM_SUBJECT_TOKEN_TYPES: return {bsm_type: { 'aid': token.subject_data.audit_uid, 'euid': token.subject_data.effective_uid, 'egid': token.subject_data.effective_gid, 'uid': token.subject_data.real_uid, 'gid': token.subject_data.real_gid, 'pid': token.subject_data.pid, 'session_id': token.subject_data.session_id, 'terminal_port': token.terminal_port, 'terminal_ip': self._IPv4Format(token.ipv4) }} elif token_id in self._BSM_SUBJECT_EX_TOKEN_TYPES: if token.bsm_ip_type_short.net_type == self.AU_IPv6: ip = self._IPv6Format( token.bsm_ip_type_short.ip_addr.high, token.bsm_ip_type_short.ip_addr.low) elif token.bsm_ip_type_short.net_type == self.AU_IPv4: ip = self._IPv4Format(token.bsm_ip_type_short.ip_addr) else: ip = 'unknown' return {bsm_type: { 'aid': token.subject_data.audit_uid, 'euid': token.subject_data.effective_uid, 'egid': token.subject_data.effective_gid, 'uid': token.subject_data.real_uid, 'gid': token.subject_data.real_gid, 'pid': token.subject_data.pid, 'session_id': token.subject_data.session_id, 'terminal_port': token.terminal_port, 'terminal_ip': ip }} elif token_id in self._BSM_ARGUMENT_TOKEN_TYPES: string = self._CopyUtf8ByteArrayToString(token.text) return {bsm_type: { 'string': string, 'num_arg': token.num_arg, 'is': token.name_arg}} elif token_id in self._BSM_EXEV_TOKEN_TYPES: arguments = [] for _ in range(0, token): sub_token = self.BSM_TOKEN_EXEC_ARGUMENT.parse_stream(file_object) string = self._CopyUtf8ByteArrayToString(sub_token.text) arguments.append(string) return {bsm_type: ' '.join(arguments)} elif bsm_type == 'BSM_TOKEN_AUT_SOCKINET32': return {bsm_type: { 'protocols': bsmtoken.BSM_PROTOCOLS.get(token.net_type, 'UNKNOWN'), 'net_type': token.net_type, 'port': token.port_number, 'address': self._IPv4Format(token.ipv4) }} elif bsm_type == 'BSM_TOKEN_AUT_SOCKINET128': return {bsm_type: { 'protocols': bsmtoken.BSM_PROTOCOLS.get(token.net_type, 'UNKNOWN'), 'net_type': token.net_type, 'port': token.port_number, 'address': self._IPv6Format(token.ipv6.high, token.ipv6.low) }} elif bsm_type == 'BSM_TOKEN_ADDR': return {bsm_type: self._IPv4Format(token)} elif bsm_type == 'BSM_TOKEN_IP': return {'IPv4_Header': '0x{0:s}]'.format(token.encode('hex'))} elif bsm_type == 'BSM_TOKEN_ADDR_EXT': return {bsm_type: { 'protocols': bsmtoken.BSM_PROTOCOLS.get(token.net_type, 'UNKNOWN'), 'net_type': token.net_type, 'address': self._IPv6Format(token.ipv6.high, token.ipv6.low) }} elif bsm_type == 'BSM_TOKEN_PORT': return {bsm_type: token} elif bsm_type == 'BSM_TOKEN_TRAILER': return {bsm_type: token.record_length} elif bsm_type == 'BSM_TOKEN_FILE': # TODO: if this timestamp is usefull, it must be extracted as a separate # event object. timestamp = timelib.Timestamp.FromPosixTimeWithMicrosecond( token.timestamp, token.microseconds) date_time = timelib.Timestamp.CopyToDatetime(timestamp, pytz.UTC) date_time_string = date_time.strftime('%Y-%m-%d %H:%M:%S') string = self._CopyUtf8ByteArrayToString(token.text) return {bsm_type: {'string': string, 'timestamp': date_time_string}} elif bsm_type == 'BSM_TOKEN_IPC': return {bsm_type: { 'object_type': token.object_type, 'object_id': token.object_id }} elif token_id in self._BSM_PROCESS_TOKEN_TYPES: return {bsm_type: { 'aid': token.subject_data.audit_uid, 'euid': token.subject_data.effective_uid, 'egid': token.subject_data.effective_gid, 'uid': token.subject_data.real_uid, 'gid': token.subject_data.real_gid, 'pid': token.subject_data.pid, 'session_id': token.subject_data.session_id, 'terminal_port': token.terminal_port, 'terminal_ip': self._IPv4Format(token.ipv4) }} elif token_id in self._BSM_PROCESS_EX_TOKEN_TYPES: if token.bsm_ip_type_short.net_type == self.AU_IPv6: ip = self._IPv6Format( token.bsm_ip_type_short.ip_addr.high, token.bsm_ip_type_short.ip_addr.low) elif token.bsm_ip_type_short.net_type == self.AU_IPv4: ip = self._IPv4Format(token.bsm_ip_type_short.ip_addr) else: ip = 'unknown' return {bsm_type: { 'aid': token.subject_data.audit_uid, 'euid': token.subject_data.effective_uid, 'egid': token.subject_data.effective_gid, 'uid': token.subject_data.real_uid, 'gid': token.subject_data.real_gid, 'pid': token.subject_data.pid, 'session_id': token.subject_data.session_id, 'terminal_port': token.terminal_port, 'terminal_ip': ip }} elif bsm_type == 'BSM_TOKEN_DATA': data = [] data_type = bsmtoken.BSM_TOKEN_DATA_TYPE.get(token.data_type, '') if data_type == 'AUR_CHAR': for _ in range(token.unit_count): data.append(self.BSM_TOKEN_DATA_CHAR.parse_stream(file_object)) elif data_type == 'AUR_SHORT': for _ in range(token.unit_count): data.append(self.BSM_TOKEN_DATA_SHORT.parse_stream(file_object)) elif data_type == 'AUR_INT32': for _ in range(token.unit_count): data.append(self.BSM_TOKEN_DATA_INTEGER.parse_stream(file_object)) else: data.append('Unknown type data') # TODO: the data when it is string ends with ".", HW a space is return # after uses the UTF-8 conversion. return {bsm_type: { 'format': bsmtoken.BSM_TOKEN_DATA_PRINT[token.how_to_print], 'data': '{0}'.format(self._RawToUTF8(''.join(map(str, data)))) }} elif token_id in self._BSM_ATTR_TOKEN_TYPES: return {bsm_type: { 'mode': token.file_mode, 'uid': token.uid, 'gid': token.gid, 'system_id': token.file_system_id, 'node_id': token.file_system_node_id, 'device': token.device}} elif bsm_type == 'BSM_TOKEN_GROUPS': arguments = [] for _ in range(token): arguments.append( self._RawToUTF8( self.BSM_TOKEN_DATA_INTEGER.parse_stream(file_object))) return {bsm_type: ','.join(arguments)} elif bsm_type == 'BSM_TOKEN_AUT_SOCKINET32_EX': if bsmtoken.BSM_PROTOCOLS.get(token.socket_domain, '') == 'INET6': saddr = self._IPv6Format( token.structure_addr_port.saddr_high, token.structure_addr_port.saddr_low) daddr = self._IPv6Format( token.structure_addr_port.daddr_high, token.structure_addr_port.daddr_low) else: saddr = self._IPv4Format(token.structure_addr_port.source_address) daddr = self._IPv4Format(token.structure_addr_port.destination_address) return {bsm_type:{ 'from': saddr, 'from_port': token.structure_addr_port.source_port, 'to': daddr, 'to_port': token.structure_addr_port.destination_port}} elif bsm_type == 'BSM_TOKEN_IPC_PERM': return {bsm_type: { 'user_id': token.user_id, 'group_id': token.group_id, 'creator_user_id': token.creator_user_id, 'creator_group_id': token.creator_group_id, 'access': token.access_mode}} elif bsm_type == 'BSM_TOKEN_SOCKET_UNIX': string = self._CopyUtf8ByteArrayToString(token.path) return {bsm_type: {'family': token.family, 'path': string}} elif bsm_type == 'BSM_TOKEN_OPAQUE': string = self._CopyByteArrayToBase16String(token.text) return {bsm_type: string} elif bsm_type == 'BSM_TOKEN_SEQUENCE': return {bsm_type: token}
# Generic RPC message 'union' RpcMessage = con.Switch( this.code, { RPC_TYPE.RPC_OK: con.Pass, RPC_TYPE.RPC_FAIL: RpcMessage_FAIL, RPC_TYPE.RPC_NOTIFY: RpcMessage_NOTIFY, RPC_TYPE.RPC_HELO: RpcMessage_HELO, RPC_TYPE.PULL_MD: RpcMessage_PULL_MD, RPC_TYPE.PULL_MD_RESULT: RpcMessage_PULL_MD_RESULT, RPC_TYPE.PUSH_MD: RpcMessage_PUSH_MD, RPC_TYPE.PUSH_MD_RESULT: RpcMessage_PUSH_MD_RESULT, #RPC_TYPE.GET_POP : RpcMessage_GET_POP, #RPC_TYPE.GET_POP_RESULT : RpcMessage_GET_POP_RESULT, #RPC_TYPE.LIST_PEERS : RpcMessage_LIST_PEERS, #RPC_TYPE.LIST_PEERS_RESULT : RpcMessage_LIST_PEERS_RESULT, #RPC_TYPE.KILL_SESSIONS : RpcMessage_KILL_SESSIONS, #RPC_TYPE.KILL_SESSIONS_RESULT : RpcMessage_KILL_SESSIONS_RESULT, #RPC_TYPE.DEL_ENTRIES : RpcMessage_DEL_ENTRIES, #RPC_TYPE.DEL_ENTRIES_RESULT : RpcMessage_DEL_ENTRIES_RESULT, #RPC_TYPE.SHOW_ENTRIES : RpcMessage_SHOW_ENTRIES, #RPC_TYPE.SHOW_ENTRIES_RESULT : RpcMessage_SHOW_ENTRIES_RESULT, #RPC_TYPE.DUMP_MD : RpcMessage_DUMP_MD, #RPC_TYPE.DUMP_MD_RESULT : RpcMessage_DUMP_MD_RESULT, #RPC_TYPE.CLEAN_DB : RpcMessage_CLEAN_DB, #RPC_TYPE.DEBUGCTL : RpcMessage_DEBUGCTL, }, default=None) # RPC packet common header
"sec" / ct.Flag, "frame_pending" / ct.Flag, "ack_requested" / ct.Flag, "pan_id_comp" / ct.Flag, "reserved" / LEBitsInteger(3), "dst_addressing_mode" / addressing_mode_t, "version" / LEBitsInteger(2), "src_addressing_mode" / addressing_mode_t, )) mac_header_t = ct.Struct( "fcf" / fcf_t, "seqnum" / ct.Hex(ct.Int8ul), "dst_addr" / ct.Switch( lambda ctx: int(ctx.fcf.dst_addressing_mode), { int(addressing_mode_t.SHORT): short_addr_t, int(addressing_mode_t.LONG): long_addr_t, }), "src_addr" / ct.If( lambda ctx: is_address_present(ctx.fcf.src_addressing_mode), ct.Struct( "pan_id" / ct.IfThenElse( lambda ctx: ctx._.fcf.pan_id_comp and is_address_present( ctx._.fcf.dst_addressing_mode), ct.Computed(ct.this._.dst_addr.pan_id), ct.Hex(ct.Int16ul)), "addr" / ct.Switch( lambda ctx: int(ctx._.fcf.src_addressing_mode), { int(addressing_mode_t.SHORT): ct.Hex(ct.Int16ul), int(addressing_mode_t.LONG): ct.Hex(ct.Int64ul) }))), )
'data' / construct.Switch( lambda ctx: int(ctx.event_type), { EventType.MAKECODE.value: construct.Struct('ea' / construct.Int32ub), EventType.MAKEDATA.value: construct.Struct( 'ea' / construct.Int32ub, 'flags' / construct.Int32ub, 'size' / construct.Int32ub, 'tid' / construct.Int32ub), EventType.RENAMED.value: construct.Struct( 'ea' / construct.Int32ub, 'new_name' / construct.PascalString(construct.Int16ub, 'utf-8'), 'local_name' / construct.Int32ub), EventType.FUNCADD.value: construct.Struct('start_ea' / construct.Int32ub, 'end_ea' / construct.Int32ub), EventType.FUNCREMOVE.value: construct.Struct('start_ea' / construct.Int32ub, ), EventType.SETFUNCSTART.value: construct.Struct('start_ea' / construct.Int32ub, 'new_start_ea' / construct.Int32ub), EventType.SETFUNCEND.value: construct.Struct('start_ea' / construct.Int32ub, ), EventType.FUNCTAILAPPENDED.value: construct.Struct('func_start_ea' / construct.Int32ub, 'tail_start_ea' / construct.Int32ub, 'tail_end_ea' / construct.Int32ub), EventType.FUNCTAILDELETED.value: construct.Struct('func_start_ea' / construct.Int32ub, 'tail_ea' / construct.Int32ub), EventType.TAILOWNERCHANGED.value: construct.Struct('tail_ea' / construct.Int32ub, 'owner_func' / construct.Int32ub), EventType.COMMENTCHANGED.value: construct.Struct( 'ea' / construct.Int32ub, 'comment' / construct.PascalString(construct.Int16ub, 'utf-8'), 'repeatable' / construct.Flag), EventType.RANGECOMMENTCHANGED.value: construct.Struct( 'kind' / construct.Int32ub, 'start_ea' / construct.Int32ub, 'comment' / construct.PascalString(construct.Int16ub, 'utf-8'), 'repeatable' / construct.Flag), EventType.EXTRACOMMENTCHANGED.value: construct.Struct( 'ea' / construct.Int32ub, 'line_idx' / construct.Int32ub, 'comment' / construct.PascalString(construct.Int16ub, 'utf-8')), EventType.OPTYPECHANGED.value: construct.Struct( 'ea' / construct.Int32ub, 'n' / construct.Int32ub, 'op' / construct.PascalString(construct.Int16ub, 'utf-8'), 'extra' / DictAdapter( construct.PascalString(construct.Int16ub, 'utf-8'))), }))
def _struct(cls): return construct.Struct( "version" / LsbVersionValidator(construct.Int32ul), "project_name" / construct.PascalString(construct.Int32ul, "cp932"), "unk1" / construct.Int64ul, "unk2" / construct.Int64ul, "init_lsb" / construct.PascalString(construct.Int32ul, "cp932"), "exit_lsb" / construct.If( construct.this.version > 0x6D, construct.PascalString(construct.Int32ul, "cp932"), ), "project_dir" / construct.PascalString(construct.Int32ul, "cp932"), "unk3" / construct.Int32ul, "bool1" / construct.Byte, "bool2" / construct.If( construct.this.version >= 0x6A, construct.Byte, ), "audio_formats" / construct.If( construct.this.version >= 0x6D, construct.PascalString(construct.Int32ul, "cp932"), ), "bool3" / construct.If( construct.this.version >= 0x71, construct.Byte, ), "bool4" / construct.If( construct.this.version >= 0x72, construct.Byte, ), "bool5" / construct.If( construct.this.version >= 0x74, construct.Byte, ), "insert_disk_prompt" / construct.PascalString(construct.Int32ul, "cp932"), "exit_prompt" / construct.PascalString(construct.Int32ul, "cp932"), "system_settings" / construct.PrefixedArray( construct.Int32ul, construct.Struct( "type" / construct.Enum(construct.Byte, ParamType), "name" / construct.PascalString(construct.Int32ul, "cp932"), "value" / construct.Switch( construct.this.type, { "Int": construct.Int32sl, "Float": construct.ExprAdapter( construct.Bytes(10), lambda obj, ctx: numpy.frombuffer( obj.rjust(16, b"\x00"), dtype=numpy.longdouble), lambda obj, ctx: numpy.longdouble(obj).tobytes( )[-10:], ), "Flag": construct.Byte, "Str": construct.PascalString(construct.Int32ul, "cp932"), }, ), ), ), )
"This tool requires Construct. Install it with 'pip install Construct'.\n" ) sys.exit(1) if os.isatty(sys.stdin.fileno()): tx_hex = input("Enter transaction in hex format: ") else: tx_hex = sys.stdin.read().strip() tx_bin = bytes.fromhex(tx_hex) CompactUintStruct = c.Struct( "base" / c.Int8ul, "ext" / c.Switch(this.base, { 0xFD: c.Int16ul, 0xFE: c.Int32ul, 0xFF: c.Int64ul }), ) class CompactUintAdapter(c.Adapter): def _encode(self, obj: int, context: Any, path: Any) -> dict: if obj < 0xFD: return {"base": obj} if obj < 2**16: return {"base": 0xFD, "ext": obj} if obj < 2**32: return {"base": 0xFE, "ext": obj} if obj < 2**64: return {"base": 0xFF, "ext": obj}
TaskSpecificStatusReportFormat = con.Switch( con.this.current_task_id, { "fault": con.Struct( "failed_task_id" / TaskIDFormat, "failed_task_exit_code" / U8, ), "run": con.Select( # New format starting from firmware v0.2 - added a new field 'torque' and one reserved four bytes long field con.Struct( "stall_count" / U32, "demand_factor" / F32, # Mechanical parameters "electrical_angular_velocity" / F32, "mechanical_angular_velocity" / F32, "torque" / F32, # Rotating system parameters "u_dq" / con.Array(2, F32), "i_dq" / con.Array(2, F32), # Control mode "mode" / ControlModeFormat, # State flags "spinup_in_progress" / con.Flag, "rotation_reversed" / con.Flag, "controller_saturated" / con.Flag, ), # An older format used in the firmware v0.1 - this one is shorter, hence it must be at the end of Select() con.Struct( "stall_count" / U32, "demand_factor" / F32, "electrical_angular_velocity" / F32, "mechanical_angular_velocity" / F32, "u_dq" / con.Array(2, F32), "i_dq" / con.Array(2, F32), "mode" / ControlModeFormat, "spinup_in_progress" / con.Flag, "rotation_reversed" / con.Flag, "controller_saturated" / con.Flag, ), ), "hardware_test": con.Struct("progress" / F32, ), "motor_identification": con.Struct("progress" / F32, ), "low_level_manipulation": con.Struct("mode" / LowLevelManipulationModeFormat, ), }, default=con.Padding(1), )
construct.Switch( 'block', lambda ctx: ctx.block_start, { 0x3B: construct.Struct( # workaround for Pass not working 'terminator', construct.Value('terminator', lambda ctx: 'terminator'), ), 0x2C: _image_block, 0x21: construct.Struct( 'ext', construct.ULInt8('ext_label'), construct.Embedded( construct.Switch( 'extension', lambda ctx: ctx.ext_label, { 0xFF: _application_extension, 0xFE: _comment_extension, 0xF9: _gce_extension, }, default=_unknown_extension, ), ), ), }, ), ),
'VAULT_VSCH', construct.ULInt32('version'), GUID('schema_guid'), construct.ULInt32('vault_vsch_unknown_1'), construct.ULInt32('count'), construct.Rename('schema_name', UNICODE_STRING_STRIP) ) VAULT_ATTRIBUTE_ITEM = construct.Struct( 'VAULT_ATTRIBUTE_ITEM', construct.ULInt32('id'), construct.Switch( 'item', lambda ctx: ctx.id, { 1: construct.Rename('resource', UNICODE_STRING_HEX), 2: construct.Rename('identity', UNICODE_STRING_HEX), 3: construct.Rename('authenticator', UNICODE_STRING_HEX), }, default = construct.Rename('generic', SIZED_DATA)) ) # Vault Generic Schema VAULT_SCHEMA_GENERIC = construct.Struct( 'VAULT_SCHEMA_GENERIC', construct.ULInt32('version'), construct.ULInt32('count'), construct.ULInt32('vault_schema_generic_unknown1'), construct.Array( lambda ctx: ctx.count, VAULT_ATTRIBUTE_ITEM)
class Game: command_id = namedtuple("packet_type", ("register", "error", "update", "start", "state"))( register=0, error=1, update=2, start=3, state=4) players_struct = construct.Struct("x" / construct.Int16ul, "y" / construct.Int16ul, "score" / construct.Int8ul, "active" / construct.Int8ul) command_state = construct.Struct( "puck_x" / construct.Int16ul, "puck_y" / construct.Int16ul, "players" / construct.Array(4, players_struct)) command_update = construct.Struct("x" / construct.Int16ul, "y" / construct.Int16ul) command_register = construct.Struct("id" / construct.String(36)) command = construct.Struct( "type" / construct.Int8ul, construct.Embedded( construct.Switch(lambda ctx: ctx.type, { command_id.update: command_update, command_id.register: command_register, command_id.state: command_state, }, default=construct.Pass))) def __init__(self): self.sockets = [] self.redis = rredis.from_url(os.environ.get("REDIS_URL")) def add_state_socket(self, ws): sock = ClientSocket(ws) data = sock.receive() if data: data = self.command.parse(data) if data and data.type == self.command_id.register: sock.set_id(data.id) self.sockets.append(sock) else: sock.close() def update(self): while True: games = ast.literal_eval(self.redis.get("games")) self.update_games(games) # Send states for ws in list(self.sockets): # Clear inactive sockets if ws.is_closed(): for game in list(games): for player in range(0, len(game["players"])): if game["players"][player]["id"] == ws.get_id(): game["players"][player]["id"] = None game["players"][player]["active"] = 0 if self.active_players(game["players"]) == 0: games.remove(game) self.sockets.remove(ws) continue # Send state for game in games: for player in game["players"]: if player["id"] == ws.get_id(): ws.send(self.build_state_packet(game), True) self.redis.set("games", games) gevent.sleep(0.05) @staticmethod def update_games(games): if len(games): games[0]["puck"]["x"] += 1 def start(self): self.redis.set("games", []) gevent.spawn(self.update) def add_request_socket(self, ws): sock = ClientSocket(ws) while not sock.is_closed(): data = sock.receive() if not data: sock.close() return data = self.command.parse(data) # Register if data and data.type == self.command_id.register and sock.get_id( ) is None: client_id = self.get_new_client_id() sock.set_id(client_id) if client_id: sock.send( self.command.build( dict(type=self.command_id.register, id=client_id)), True) else: sock.send( self.command.build(dict(type=self.command_id.error)), True) # Position Update elif data.type == self.command_id.update: self.update_player_position(sock.client_id, data.x, data.y) def get_new_client_id(self): games = ast.literal_eval(self.redis.get("games")) new_player_id = str(uuid.uuid4()) # Make sure the id is unique for game in games: for player in game["players"]: if player["id"] == new_player_id: return self.get_new_client_id() # Find an open game found_game = False for game in games: if self.active_players(game["players"]) < 4: found_game = True # Add player to game for player in range(0, len(game["players"])): if game["players"][player]["active"] == 0: game["players"][player]["id"] = new_player_id game["players"][player]["active"] = 1 break # Check if game can start if self.active_players(game["players"]) == 4: for player in game["players"]: for sock in self.sockets: if sock.get_id() == player["id"]: sock.send( self.command.build( dict(type=self.command_id.start)), True) # Create game if an open one was not found if not found_game: game = {"players": [], "puck": {"x": 0, "y": 0}} for player_num in range(0, 4): game["players"].append({ "id": None, "x": 0, "y": 0, "score": 0, "active": 0 }) game["players"][0]["id"] = new_player_id game["players"][0]["active"] = 1 games.append(game) self.redis.set("games", games) return new_player_id def build_state_packet(self, game): state = dict(type=self.command_id.state, puck_x=game["puck"]["x"], puck_y=game["puck"]["y"], players=game["players"]) return self.command.build(state) def update_player_position(self, client_id, x, y): games = ast.literal_eval(self.redis.get("games")) for game in games: for player in game["players"]: if player["id"] == client_id: player["x"] = x player["y"] = y self.redis.set("games", games) @staticmethod def active_players(players): count = 0 for player in players: if player["active"] == 1: count += 1 return count
construct.Int16ul, "major_subsystem_version" / construct.Int16ul, "minor_subsystem_version" / construct.Int16ul, "win32_version_value" / construct.Int32ul, "image_size" / construct.Int32ul, "headers_size" / construct.Int32ul, "checksum" / construct.Int32ul, "subsystem" / construct.Int16ul, "dll_characteristics" / construct.Int16ul, "stack_reserve_size" / construct.Int64ul, "stack_commit_size" / construct.Int64ul, "heap_reserve_size" / construct.Int64ul, "heap_commit_size" / construct.Int64ul, "loader_flags" / construct.Int32ul, "rva_and_sizes_count" / construct.Int32ul, "data_directory" / construct.Array(construct.this.rva_and_sizes_count, ImageDataDirectory)) # TODO: When building this struct, the optional_header_size in ImageFileHeader should be fixed ImageOptionalHeader = construct.Struct( "signature" / construct.Enum(construct.Int16ul, PE32=267, PE64=523), "header" / construct.Switch(construct.this.signature, { "PE32": ImageOptionalHeader32, "PE64": ImageOptionalHeader64 })) """ typedef struct _IMAGE_SECTION_HEADER { BYTE Name[IMAGE_SIZEOF_SHORT_NAME]; union { DWORD PhysicalAddress; DWORD VirtualSize; } Misc; DWORD VirtualAddress; DWORD SizeOfRawData; DWORD PointerToRawData; DWORD PointerToRelocations; DWORD PointerToLinenumbers; WORD NumberOfRelocations; WORD NumberOfLinenumbers;
"cam_multiplier_limit" / construct.Int16ub, construct.Padding(2) ) header_cmd2 = construct.Struct( 'JDN_base' / construct.Int16ul, construct.Padding(2), 'seconds' / construct.Int32ul ) header = construct.Struct( 'packet_type' / construct.Int16ul, 'cmd_id' / construct.Int16ul, 'payload_size' / construct.Int16ul, 'seq_id' / construct.Int16ul, construct.Embedded( construct.Switch(lambda ctx: ctx.cmd_id, { 0: construct.If( lambda ctx: ctx.payload_size >= header_cmd0.sizeof(), header_cmd0), 1: construct.If( lambda ctx: ctx.payload_size == header_cmd1.sizeof(), header_cmd1), 2: construct.If( lambda ctx: ctx.payload_size == header_cmd2.sizeof(), header_cmd2) }, default=construct.Pass ) ) )
'accelerations' / JointData(10), ) JointFeedback = c2.Struct('Header' / Header, 'body' / c2.Renamed(JointFeedbackBody), c2.Terminated) msg_type_body_map = { smt.PING: PingBody, smt.JOINT_POSITION: JointPositionBody, #smt.JOINT : , #smt.READ_INPUT : , #smt.WRITE_OUTPUT : , smt.JOINT_TRAJ_PT: JointTrajectoryPointBody, #smt.JOINT_TRAJ : , smt.STATUS: RobotStatusBody, smt.JOINT_TRAJ_PT_FULL: JointTrajectoryPointFullBody, smt.JOINT_FEEDBACK: JointFeedbackBody, } SimpleMessage = c2.Struct( 'header' / Header, 'data' / c2.Switch(lambda ctx: ctx.header.msg_type, msg_type_body_map, default=GenericBody), c2.Terminated)
TaskSpecificStatusReportFormat = con.Switch( con.this.current_task_id, { 'fault': con.Struct( 'failed_task_id' / TaskIDFormat, 'failed_task_exit_code' / U8, ), 'run': con.Select( # New format starting from firmware v0.2 - added a new field 'torque' and one reserved four bytes long field con.Struct( 'stall_count' / U32, 'demand_factor' / F32, # Mechanical parameters 'electrical_angular_velocity' / F32, 'mechanical_angular_velocity' / F32, 'torque' / F32, # Rotating system parameters 'u_dq' / con.Array(2, F32), 'i_dq' / con.Array(2, F32), # Control mode 'mode' / ControlModeFormat, # State flags 'spinup_in_progress' / con.Flag, 'rotation_reversed' / con.Flag, 'controller_saturated' / con.Flag, ), # An older format used in the firmware v0.1 - this one is shorter, hence it must be at the end of Select() con.Struct( 'stall_count' / U32, 'demand_factor' / F32, 'electrical_angular_velocity' / F32, 'mechanical_angular_velocity' / F32, 'u_dq' / con.Array(2, F32), 'i_dq' / con.Array(2, F32), 'mode' / ControlModeFormat, 'spinup_in_progress' / con.Flag, 'rotation_reversed' / con.Flag, 'controller_saturated' / con.Flag, ), ), 'hardware_test': con.Struct('progress' / F32, ), 'motor_identification': con.Struct('progress' / F32, ), 'low_level_manipulation': con.Struct('mode' / LowLevelManipulationModeFormat, ), }, default=con.Padding(1))
IMAGE_NT_OPTIONAL_HDR32_MAGIC = 0x10b, IMAGE_NT_OPTIONAL_HDR64_MAGIC = 0x20b ), construct.ULInt8('MajorLinkerVersion'), construct.ULInt8('MinorLinkerVersion'), construct.ULInt32('SizeOfCode'), construct.ULInt32('SizeOfInitializedData'), construct.ULInt32('SizeOfUninitializedData'), construct.ULInt32('AddressOfEntryPoint'), construct.ULInt32('BaseOfCode'), construct.If(lambda ctx: ctx.Magic == 'IMAGE_NT_OPTIONAL_HDR32_MAGIC', construct.ULInt32('BaseOfData') ), construct.Switch('ImageBase', lambda ctx: ctx.Magic, { 'IMAGE_NT_OPTIONAL_HDR32_MAGIC' : construct.ULInt32('ImageBase_'), 'IMAGE_NT_OPTIONAL_HDR64_MAGIC' : construct.ULInt64('ImageBase_') } ), construct.ULInt32('SectionAlignment'), construct.ULInt32('FileAlignment'), construct.ULInt16('MajorOperatingSystemVersion'), construct.ULInt16('MinorOperatingSystemVersion'), construct.ULInt16('MajorImageVersion'), construct.ULInt16('MinorImageVersion'), construct.ULInt16('MajorSubsystemVersion'), construct.ULInt16('MinorSubsystemVersion'), construct.ULInt32('Win32VersionValue'), construct.ULInt32('SizeOfImage'), construct.ULInt32('SizeOfHeaders'), construct.ULInt32('CheckSum'), construct.ULInt16('Subsystem'),