# File from polymorph project # Copyright (C) 2018 Santiago Hernandez Ramos <shramos@protonmail.com> # For more information about the project: https://github.com/shramos/polymorph from polymorph.UI.interface import Interface from prompt_toolkit import PromptSession from prompt_toolkit import HTML from collections import OrderedDict from polymorph.UI.command_parser import CommandParser from termcolor import colored from polymorph.tfield import TField from polymorph.UI.fieldinterface import FieldInterface import hexdump from prompt_toolkit.history import FileHistory from prompt_toolkit.completion import WordCompleter from prompt_toolkit.auto_suggest import AutoSuggestFromHistory from prompt_toolkit.shortcuts import CompleteStyle import construct class LayerInterface(Interface): """This class is responsible for parsing and respond to user commands in the tlist interface, when the user already has a packet capture.""" def __init__(self, layer, tindex, poisoner=None): """Initialization method of the class. Parameters ---------- layer : obj:`TLayer` The layer object. tindex : int The index of the `Template` of the `TLayer`. poisoner : obj:`ARPpoisones` The poisoner object, if exist """ # Constructor of the parent class super(LayerInterface, self).__init__() # Class Attributes self._l = layer self._tindex = tindex self._poisoner = poisoner def run(self): """Runs the interface and waits for user input commands.""" completer = WordCompleter(['show', 'name', 'field', 'fields', 'dump', 'recalculate', 'clear', 'back']) history = FileHistory(self._polym_path + '/.linterface_history') session = PromptSession(history=history) while True: try: command = session.prompt(HTML("<bold>PH:cap/t%d/<red>%s</red> > </bold>" % (self._tindex, self._l.name)), completer=completer, complete_style=CompleteStyle.READLINE_LIKE, auto_suggest=AutoSuggestFromHistory(), enable_history_search=True) except KeyboardInterrupt: self.exit_program() continue # Argument parsing command = command.split(" ") if command[0] in self.EXIT: self.exit_program() elif command[0] in self.RET: break elif command[0] in ["s", "show"]: self._show(command) elif command[0] == "name": self._name(command) elif command[0] in ["field", "f"]: self._field(command) elif command[0] in ["fields", "fs"]: self._fields(command) elif command[0] in ["dump", "d"]: self._dump(command) elif command[0] in ["recalculate", "r"]: self._recalculate(command) elif command[0] == "clear": Interface._clear() elif command[0] == "": continue else: Interface._wrong_command() def _show(self, command): """Shows the contents of the `TLayer`""" if len(command) == 1: self._l.show() return # Parsing the arguemnts cp = CommandParser(LayerInterface._show_opts()) args = cp.parse(command) if not args: Interface._argument_error() return # Print the help if args["-h"]: Interface.print_help(LayerInterface._show_help()) # Shows a particular field elif args["-f"]: self._l.getlayer(args["-f"]).show() @staticmethod def _show_help(): """Builds the help for the show command.""" options = OrderedDict([ ("-h", "prints the help."), ("-f", "shows a particular field.") ]) return OrderedDict([ ("name", "show"), ("usage", "show [-option]"), ("description", "Prints information about the layer."), ("options", options) ]) @staticmethod def _show_opts(): """Returns command options in a form that can be handled by the command parser.""" opts = {"-h": {"type": bool, "default": False}, "-f": {"type": str, "default": None}} return opts def _name(self, command): """Manages the name of the `TLayer`.""" if len(command) == 1: print(self._l.name, '\n') return # Parsing the arguments cp = CommandParser(LayerInterface._name_opts()) args = cp.parse(command) if not args: Interface._argument_error() return # Print the help if args["-h"]: Interface.print_help(LayerInterface._name_help()) # Add a new name elif args["-n"]: self._l.name = args["-n"] Interface._print_info("New name %s added" % args['-n']) @staticmethod def _name_help(): """Builds the help for the name command.""" options = OrderedDict([ ("-h", "prints the help."), ("-n", "set a new name to the layer.") ]) return OrderedDict([ ("name", "name"), ("usage", "name [-option]"), ("description", "Manage the name of the layer."), ("options", options) ]) @staticmethod def _name_opts(): """Returns command options in a form that can be handled by the command parser.""" opts = {"-h": {"type": bool, "default": False}, "-n": {"type": str, "default": None}} return opts def _fields(self, command): """Show all the fields in the layer.""" if len(command) == 1: print(colored("\n".join(self._l.fieldnames()), 'cyan'), "\n") return # Parsing arguments cp = CommandParser(LayerInterface._fields_opts()) args = cp.parse(command) if not args: Interface._argument_error() return # Print the help if args["-h"]: Interface.print_help(LayerInterface._fields_help()) # Prints the custom fields elif args["-c"]: cfields = [f.name for f in self._l.customfields()] print(colored("\n".join(cfields), 'cyan'), "\n") @staticmethod def _fields_help(): """Builds the help for the fields command.""" options = OrderedDict([ ("-h", "prints the help."), ("-c", "prints the custom fields.") ]) return OrderedDict([ ("name", "fields"), ("usage", "fields [-option]"), ("description", "Prints the fields of the layer."), ("options", options) ]) @staticmethod def _fields_opts(): """Returns command options in a form that can be handled by the command parser.""" opts = {"-h": {"type": bool, "default": False}, "-c": {"type": bool, "default": False}} return opts def _field(self, command): """Manages the access an creation of `TField`.""" if len(command) == 1: Interface.print_help(LayerInterface._field_help()) elif len(command) == 2 and command[1].lower() in self._l.fieldnames(): fi = FieldInterface(self._l.getfield( command[1].lower()), self._tindex, self._l.name, self._poisoner) fi.run() else: cp = CommandParser(LayerInterface._field_opts()) args = cp.parse(command) if not args: Interface._argument_error() return # Print the help if args["-h"]: Interface.print_help(LayerInterface._field_help()) # Adds a new field elif args["-a"]: Interface.color_dump(self._l.raw, self._l.slice.start) start = input("Start byte of the custom field: ") end = input("End byte of the custom field: ") if not start.isdecimal() or not end.isdecimal(): Interface._print_error( "The start or end byte is not a number") return else: fslice = slice(int(start), int(end)) fvalue = self._l.raw[fslice] fsize = len(fvalue) new_field = TField(name=args["-a"], value=fvalue, tslice=str(fslice).encode().hex(), custom=True, size=fsize, raw=self._l.raw.hex(), layer=self._l) # Set the type ftype = input("Field type [int/str/bytes/hex]: ") if ftype == "int": new_field.to_int() elif ftype == "str": new_field.to_str() elif ftype == "hex": new_field.to_hex() elif ftype == "bytes": new_field.to_bytes() # Add the field to the layer self._l.addfield(new_field) Interface._print_info( "Field %s added to the layer" % args['-a']) # Deletes a field from the layer elif args["-d"]: del_field = self._l.getfield(args["-d"]) if del_field: self._l.delfield(del_field) Interface._print_info( "Field %s deleted from the layer" % args["-d"]) else: Interface._print_error( "The field %s is not in the layer" % args["-d"]) @staticmethod def _field_help(): """Builds the help for the field command.""" options = OrderedDict([ ("-h", "prints the help."), ("-a", "adds a new field to the layer."), ("-d", "deletes a custom field from the layer.") ]) return OrderedDict([ ("name", "field"), ("usage", "field <name> [-option]"), ("description", "access and manage the fields of the layer."), ("options", options) ]) @staticmethod def _field_opts(): """Returns command options in a form that can be handled by the command parser.""" opts = {"-h": {"type": bool, "default": False}, "-a": {"type": str, "default": None}, "-d": {"type": str, "default": None}} return opts def _dump(self, command): """Dumps the layer bytes in different formats.""" if len(command) == 1: Interface.color_dump(self._l.raw, self._l.slice.start) return # Parsing the arguments cp = CommandParser(LayerInterface._dump_opts()) args = cp.parse(command) if not args: Interface._argument_error() return if args["-hex"]: Interface.color_dump(self._l.raw, self._l.slice.start) elif args["-b"]: print(str(self._l.raw[self._l.slice.start:]), '\n') elif args["-hexstr"]: d = hexdump.dump(self._l.raw).split(" ") print(" ".join(d[self._l.slice.start:]), '\n') elif args["-h"]: Interface.print_help(LayerInterface._dump_help()) @staticmethod def _dump_help(): """Builds the help for the dump command.""" options = OrderedDict([ ("-h", "prints the help."), ("-hex", "dump the packet bytes encoded in hexadecimal."), ("-b", "dump the packet bytes without encoding."), ("-hexstr", "dump the packet bytes as an hexadecimal stream.") ]) return OrderedDict([ ("name", "dump"), ("usage", "dump [-option]"), ("description", "Dumps the layer bytes in different formats."), ("options", options) ]) @staticmethod def _dump_opts(): """Returns command options in a form that can be handled by the command parser.""" opts = {"-h": {"type": bool, "default": False}, "-hex": {"type": bool, "default": False}, "-b": {"type": bool, "default": False}, "-hexstr": {"type": bool, "default": False}} return opts def _recalculate(self, command): """Manages all the recalculate options for the fields of the layer.""" if len(command) == 1: Interface.print_help(LayerInterface._recalculate_help()) return # Parsing the arguments cp = CommandParser(LayerInterface._recalculate_opts()) args = cp.parse(command) if not args: Interface._argument_error() return # Print the help if args["-h"]: Interface.print_help(LayerInterface._recalculate_help()) # Adds a new recalculate expression elif args["-f"] and args["-sb"] and args["-e"]: fields = LayerInterface._extrac_deps(args["-sb"], args["-e"]) if fields: try: self._l.add_struct( args["-f"], fields, args["-sb"], args["-e"]) Interface._print_info( "Struct added to field %s" % args["-f"]) except: Interface._print_error( "Wrong fields or wrong syntax referring to the fields") return else: Interface._print_error( "Wrong syntax for referring to the fields. Please use 'this.field' syntax") # Tests a created struct elif args["-t"] and len(command) == 3: if self._l.is_struct(args["-t"]): try: print(self._l.test_struct(args["-t"]), "\n") except construct.core.StreamError as e: Interface._print_error( "The Struct is not well formed. Please check the fields and their type.\n%s" % str(e)) else: Interface._print_error( "The Struct %s is not in the layer" % args['-t']) # Show the struct for a particular field elif args["-s"] and len(command) == 3: self._l.show_structs(args["-s"]) # Deletes a struct for a field elif args["-d"] and len(command) == 3: if self._l.is_struct(args["-d"]): self._l.del_struct(args["-d"]) Interface._print_info("Struct deleted for %s" % args["-d"]) else: self._print_error( "The Struct %s is not in the layer" % args["-d"]) @staticmethod def _extrac_deps(start_byte, expression): patterns = start_byte.split(" ") + expression.split(" ") fields = [] for p in patterns: if 'this.' in p and p[5:] not in fields: fields.append(p[5:]) return fields @staticmethod def _recalculate_help(): """Builds the help for the recalculate command.""" options = OrderedDict([ ("-h", "prints the help."), ("-f", "field to be recalculated"), ("-sb", "start byte of the field that you want to recalculate."), ("-e", "expression that recalculates the field"), ("-t", "tests a previously created structure"), ("-s", "shows the struct for a particular field"), ("-d", "deletes a struct from a field.") ]) return OrderedDict([ ("name", "recalculate"), ("usage", "recalculate <-option>"), ("description", "Creates a structure that relates a field to other fields of the layer, so that its value " "can be calculated dynamically at run time."), ("options", options) ]) @staticmethod def _recalculate_opts(): """Returns command options in a form that can be handled by the command parser.""" opts = {"-h": {"type": bool, "default": False}, "-s": {"type": str, "default": None}, "-f": {"type": str, "default": None}, "-sb": {"type": str, "default": None}, "-e": {"type": str, "default": None}, "-d": {"type": str, "default": None}, "-t": {"type": str, "default": None}} return opts