from DIE.Lib.DataPluginBase import DataPluginBase import logging import idaapi import idc import sys try: # TODO: Is singleton really required here? python modules are basically singleton by design from yapsy.PluginManager import PluginManagerSingleton except ImportError, err: idaapi.msg("Yapsy not installed (please use 'pip install yapsy' or equivalent : %s\n", err) # TODO: does this not kill IDA? Instead, the error should be propagated to the plugin initialization. sys.exit(1) # TODO: better use new style classes class DataParser(): """ Data parser is a class for parsing raw runtime values. """ def __init__(self): self.logger = logging.getLogger(__name__) # type_parsers is a dictionary that maps type names to the parsers that support them. # this is done in order to speedup parser lookups and avoid iterating the entire parser list self.type_parsers = {} self.pManager = PluginManagerSingleton.get() # Plugin manager def set_plugin_path(self, plugin_path): """ Set the data parser plugin path @param plugin_path: full path of data-parser root directory @return: """ self.pluginLocation = plugin_path self.pManager.setPluginPlaces([self.pluginLocation]) # Set plugin directory self.pManager.setCategoriesFilter({"ParserPlugins": DataPluginBase}) self.logger.info("Plugin path is set to %s", plugin_path) def loadPlugins(self): """ Load\Reload all plugins found in the plugin location. """ self.logger.info("Loading Plugins from %s", self.pluginLocation) self.pManager.collectPlugins() all_plugins = self.pManager.getAllPlugins() if len(all_plugins) == 0: idaapi.msg("Warning - No Plugins were loaded!\n") self.logger.error("No plugins were loaded") for pluginInfo in all_plugins: # TODO: Validate plugins! self.logger.info("Loading plugin %s", pluginInfo.name) if pluginInfo.name == "headers": # headers is an illegal plugin name (see get_parser_list) continue # Set a type name normalizing function pluginInfo.plugin_object.initPlugin(self.typeName_norm) self.pManager.activatePluginByName(pluginInfo.name) # Add type to type_parser dict for quick lookups suported_types = pluginInfo.plugin_object.getSupportedTypes() if suported_types is not None: self.addTypeParser(suported_types, pluginInfo.plugin_object) def deactivatePlugin(self, pluginInfo): """ Deactivate a plugin @param pluginInfo: deactivated plugin plugininfo object @return: """ # Deactivate plugin self.pManager.deactivatePluginByName(pluginInfo.name) # Remove from type_parsers for stype in self.type_parsers: if pluginInfo.plugin_object in self.type_parsers[stype]: self.type_parsers[stype].remove(pluginInfo.plugin_object) def activatePlugin(self, pluginInfo): """ Activate a plugin @param pluginInfo: activated plugin plugininfo object @return: """ # Run plugin initialization pluginInfo.plugin_object.initPlugin(self.typeName_norm) # Activate Plugin self.pManager.activatePluginByName(pluginInfo.name) def get_parser_list(self): """ Query available parsers @return: Returns a dictionary of all available parsers and their data. The dictionary key is the parser name, and value is a list of available data in the following format: Plugin1 -> [Plugin1 Description, Plugin1 Version, Plugin2 -> [Plugin2 Description, Plugin2 Version, ...] A special key named "headers" represents the type names of the returned columns """ parser_list = {} # TODO: use classes or named tuples parser_list["headers"] = ["Description", "Version", "State", "Author"] for plugin in self.pManager.getAllPlugins(): parser_list[plugin.name] = [plugin.description, plugin.version, plugin.is_activated, plugin.author] return parser_list def addTypeParser(self, supported_types, parser_plugin): """ Add an entry to the type_parser dictionary @param supported_types: a list of supported type strings @param parser_plugin: parser plugin object """ for stype, sparams in supported_types: if stype in self.type_parsers: self.type_parsers[stype].append(parser_plugin) else: self.type_parsers[stype] = [parser_plugin] def ParseData(self, rawData, type=None, loc=None, custom_parser=None): """ Parse Data @param rawData: The raw data to be parsed @param type: The data type (If unknown should be None) @param loc: raw value (memory) location @param custom_parser: A custom parser to use. @return: A list of ParsedValue objects (containing the guessed\exact parsed values) """ parsedValues = [] try: # If custom parser was defined if custom_parser is not None: custom_parser.run(rawData, type, match_override=True) ret_vals = custom_parser.getParsedValues() parsedValues.extend(ret_vals) return parsedValues # if type is known, try to look it up in the parser_type dict if type is not None: type_name = idaapi.print_tinfo('', 0, 0, idaapi.PRTYPE_1LINE, type, '', '') type_name = self.typeName_norm(type_name) if type_name in self.type_parsers: for parser_plugin in self.type_parsers[type_name]: parser_plugin.run(rawData, type) ret_vals = parser_plugin.getParsedValues() parsedValues.extend(ret_vals) return parsedValues # Otherwise, the entire plugin list has to be iterated for pluginInfo in self.pManager.getAllPlugins(): if pluginInfo.is_activated: pluginInfo.plugin_object.run(rawData, type) ret_vals = pluginInfo.plugin_object.getParsedValues() parsedValues.extend(ret_vals) return parsedValues except Exception as ex: self.logger.exception("Error while parsing data: %s", ex) def typeName_norm(self, type_name): """ Builds and returns a normalized type string. Normalization deletes all space characters and changes to uppercase. @param type_name: Type name string (e.g "CONST CHAR *") @return: a normalized type name """ if not type_name: return None type_name = type_name.upper() type_name = type_name.replace(" ", "") return type_name ### a global dataParser object. ### This should basically be enough in order to create a singleton object, since of the way Python modules are ### loaded (reloading of a module should never be preformed..) # TODO: Read from configuration file #config = DieConfig.get_config() idaapi.msg("[2] Loading data parsers\n") #_dataParser = DataParser("C:\Users\yanivb\Desktop\Workspace\Projects\DIE\Plugins\DataParsers") #_dataParser = DataParser(config.data_parser_path) _dataParser = DataParser() # Just in case this will someday be a full singleton implementation def getParser(): """ Get a parser instance @return: DataParser instance """ return _dataParser