Skip to content

Commit

Permalink
Added DisasVerifier and an init logger
Browse files Browse the repository at this point in the history
Added DisasVerifier and an init logger
  • Loading branch information
chkp-eyalit authored Apr 20, 2020
2 parents 4f8ae71 + 7653947 commit a77f80d
Show file tree
Hide file tree
Showing 10 changed files with 152 additions and 33 deletions.
12 changes: 9 additions & 3 deletions src/analyze_src_file.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,10 +29,16 @@ def analyzeFile():
logger.info("Finished Successfully")


# create a logger
# Create a basic logger for the init phase
init_logger = Logger(LIBRARY_NAME)
init_logger.linkHandler(logging.FileHandler(constructInitLogPath(), "w"))
disas = createDisassemblerHandler(init_logger)
# In case of a dependency issue, disas will be None
if disas is None:
exit()
# Always init the utils before we start (now with the real logger too)
logger = Logger(LIBRARY_NAME, use_stdout=False)
# Always init the utils before we start
initUtils(logger, createDisassemblerHandler(logger))
initUtils(logger, disas)
# Register our contexts
registerContexts(SourceContext, BinaryContext, IslandContext)
# Start to analyze the file
Expand Down
12 changes: 12 additions & 0 deletions src/config/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -192,6 +192,18 @@ def getContextsStats():
"""
return src_seen_strings, src_seen_consts, src_functions_list

def constructInitLogPath(bin_path=None):
"""Generate the name for the init log file of the currently analyzed binary file.
Args:
bin_path (str, Optional): path to the compiled binary file (None by default)
Return value:
file name for the log file
"""
prefix = '' if bin_path is None else os.path.split(bin_path)[0] + os.path.sep
return prefix + LIBRARY_NAME + ".log"

def constructLogPath(bin_path=None):
"""Generate the name for the log file of the currently analyzed binary file.
Expand Down
6 changes: 1 addition & 5 deletions src/disassembler/IDA/__init__.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,2 @@
# Used to check if we are running inside IDA Pro
try:
from .ida_api import *
except ImportError:
pass
from .ida_verifier_api import *
from .ida_cmd_api import *
10 changes: 4 additions & 6 deletions src/disassembler/IDA/ida_api.py
Original file line number Diff line number Diff line change
@@ -1,14 +1,16 @@
# Dependencies that only exist inside IDA
import idautils
import idaapi
import idc
import ida_pro
import ida_search
import ida_nalt
# Dependencies with heavy setup
import sark
from .ida_analysis_api import AnalyzerIDA
# Basic dependencies (only basic python packages)
from config.utils import *
from disassembler.disas_api import DisasAPI
from disassembler.factory import registerDisassembler
from .ida_analysis_api import AnalyzerIDA
import logging

class IdaLogHandler(logging.Handler):
Expand Down Expand Up @@ -718,7 +720,3 @@ def showExternalsForm(self, prepared_entries):
"""
view = ExternalsChooseForm(prepared_entries)
view.show()


# Don't forget to register at the factory
registerDisassembler("IDA", IDA)
47 changes: 47 additions & 0 deletions src/disassembler/IDA/ida_verifier_api.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
from disassembler.disas_api import DisasVerifier
from disassembler.factory import registerDisassembler
from .ida_cmd_api import IdaCMD

class IdaVerifier(DisasVerifier):
"""DisasVerifier implementation for the IDA disassembler."""

# Overridden base function
@staticmethod
def identify():
"""Check if we are being executed inside our matching disassembler.
Return Value:
True iff the environment matches our program
"""
try:
import idaapi
# Silence the tests
cond = idaapi.open_form != idaapi.open_frame_window
return True or cond
except ImportError:
return False

# Overridden base function
@staticmethod
def name():
"""Get the program's name (used mainly for bug fixes in our code...).
Return Value:
String name of the disassembler program
"""
return IdaCMD.name()

# Overridden base function
@staticmethod
def disas():
"""Create a disassembler class instance.
Return Value:
Created disassembler instance
"""
from .ida_api import IDA
return IDA()


# Don't forget to register at the factory
registerDisassembler(IdaVerifier)
35 changes: 35 additions & 0 deletions src/disassembler/disas_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -570,3 +570,38 @@ def executeScript(self, database, script):
script (path): python script to be executed once the database is loaded
"""
raise NotImplementedError("Subclasses should implement this!")

class DisasVerifier(object):
"""Abstract class that verifies that we are running inside our matching disassembler.
Attributes
----------
(none)
"""

@staticmethod
def identify():
"""Check if we are being executed inside our matching disassembler.
Return Value:
True iff the environment matches our program
"""
raise NotImplementedError("Subclasses should implement this!")

@staticmethod
def name():
"""Get the program's name (used mainly for bug fixes in our code...).
Return Value:
String name of the disassembler program
"""
raise NotImplementedError("Subclasses should implement this!")

@staticmethod
def disas():
"""Create a disassembler class instance.
Return Value:
Created disassembler instance
"""
raise NotImplementedError("Subclasses should implement this!")
33 changes: 20 additions & 13 deletions src/disassembler/factory.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import collections
import traceback

######################################################################################################################
## Note: This factory is an indirection point, so we could (maybe) add support for more disassemblers in the future ##
Expand All @@ -8,19 +9,18 @@
## Global Variables ##
######################

disassembler_factory = collections.OrderedDict() # Mapping from disassembler name => init function for the respective API object
disassembler_factory = collections.OrderedDict() # Mapping from disassembler name => DisasVerifier of the respective API object
disassembler_cmd_factory = [] # list of couples of the form (identifier_handler, class initializer)

def registerDisassembler(name, init_fn):
def registerDisassembler(disas_verifier):
"""Register the disassembler in the overall factory.
Args:
name (str): name of the supported disassembler program (used as a unique identifier for it)
init_fn (function): init function for the class instance
disas_verifier (DisasVerifier): verifier for the wanted disassembler layer
"""
global disassembler_factory

disassembler_factory[name] = init_fn
disassembler_factory[disas_verifier.name()] = disas_verifier

def registerDisassemblerCMD(identifier_fn, init_fn):
"""Register the disassembler's command-line identifier in the overall factory.
Expand All @@ -37,21 +37,28 @@ def createDisassemblerHandler(logger):
"""Create the disassembler handler according to the host program.
Args:
logger (logger): logger instance (can be None sometimes)
logger (logger): logger instance (Must NOT be None)
Return Value:
disassembler handler that implements the declared API
"""
for disas_name in disassembler_factory:
verifier = disassembler_factory[disas_name]
try:
handler = disassembler_factory[disas_name]
if logger is not None and len(logger.handlers) > 0:
logger.debug("Chose the %s handler", disas_name)
return handler()
except Exception:
if not verifier.identify():
continue
except Exception as err:
logger.error("Failed to identify disassembler \"%s\": %s", disas_name, err)
logger.error(traceback.format_exc())
continue
if logger is not None and len(logger.handlers) > 0:
logger.error("Failed to create a disassembler handler!")
logger.info("Chose the %s handler", disas_name)
try:
return verifier.disas()
except Exception as err:
logger.error("Failed to create disassembler handler \"%s\": %s", disas_name, err)
logger.error(traceback.format_exc())
return None
logger.error("Failed to find a matching disassembler handler!")
return None

def identifyDisassemblerHandler(program_path, logger):
Expand Down
9 changes: 7 additions & 2 deletions src/karta_analyze_src.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
from config.utils import *
from elementals import Prompter, ProgressBar
from disassembler.factory import identifyDisassemblerHandler
from disassembler.IDA import ida_cmd_api
from function_context import SourceContext, BinaryContext, IslandContext
import config.anchor as anchor

Expand Down Expand Up @@ -85,7 +86,7 @@ def analyzeLibrary(config_name, bin_dirs, compiled_ars, prompter):
compiled_ars = range(len(bin_dirs))

# ida has severe bugs, make sure to warn the user in advance
if disas_cmd.name() == "IDA" and ' ' in SCRIPT_PATH:
if disas_cmd.name() == ida_cmd_api.IdaCMD.name() and ' ' in SCRIPT_PATH:
prompter.error("IDA does not support spaces (' ') in the script's path. Please move %s's directory accordingly (I feel your pain)", (LIBRARY_NAME))
prompter.removeIndent()
return
Expand Down Expand Up @@ -129,7 +130,11 @@ def analyzeLibrary(config_name, bin_dirs, compiled_ars, prompter):
fd = open(full_file_path + STATE_FILE_SUFFIX, 'r')
except IOError:
prompter.error("Failed to create the .JSON file for file: %s" % (compiled_file))
prompter.error("Read the log file for more information: %s" % (constructLogPath(full_file_path)))
prompter.error("Read the log file for more information:")
prompter.addIndent()
prompter.error(constructInitLogPath(full_file_path))
prompter.error(constructLogPath(full_file_path))
prompter.removeIndent()
prompter.removeIndent()
prompter.removeIndent()
prompter.error("Encountered an error, exiting")
Expand Down
12 changes: 10 additions & 2 deletions src/karta_identifier.py
Original file line number Diff line number Diff line change
Expand Up @@ -157,9 +157,17 @@ def pluginMain():
"""Run the Karta (identifier) plugin."""
global logger, disas

# Use the basic logger on the init phase
init_logger = Logger(LIBRARY_NAME)
init_logger.linkHandler(logging.FileHandler(constructInitLogPath(), "w"))
disas = createDisassemblerHandler(init_logger)
# In case of a dependency issue, disas will be None
if disas is None:
return

# Can now safely continue on
logger = Logger(LIBRARY_NAME, [], use_stdout=False, min_log_level=logging.INFO)
initUtils(logger, createDisassemblerHandler(logger))
disas = getDisas()
initUtils(logger, disas)
logger.info("Started the Script")

# Init the strings list (Only once, because it's heavy to calculate)
Expand Down
9 changes: 7 additions & 2 deletions src/karta_matcher.py
Original file line number Diff line number Diff line change
Expand Up @@ -149,8 +149,13 @@ def pluginMain():
"""Run the Karta (matcher) plugin."""
global disas, logger, config_path

# init our disassembler handler
disas = createDisassemblerHandler(None)
# Use the basic logger on the init phase
init_logger = Logger(LIBRARY_NAME)
init_logger.linkHandler(logging.FileHandler(constructInitLogPath(), "w"))
disas = createDisassemblerHandler(init_logger)
# In case of a dependency issue, disas will be None
if disas is None:
return

# Get the configuration values from the user
config_values = disas.configForm()
Expand Down

0 comments on commit a77f80d

Please sign in to comment.