Ejemplo n.º 1
0
    def rename(self, name=None):
        """
        Renames (and comments) the string variable in IDA.

        :param str name: New name to given encoded string. (defaults to decoded_string)
        """
        name = name or self.display_name
        if not name:
            append_debug(
                'Unable to rename encoded string due to no decoded string: {!r}'.format(self),
                log_token='[!]')

        # Set name and comment in stack variable.
        comment = '"{}"'.format(name[:self._MAX_COMMENT_LENGTH])
        if len(name) > self._MAX_COMMENT_LENGTH:
            comment += ' (truncated)'
        if self.frame_id and self.stack_offset:
            idc.SetMemberComment(self.frame_id, self.stack_offset, comment, repeatable=1)
            var_name = re.sub('[^_$?@0-9A-Za-z]', '_', name[:self._MAX_NAME_LENGTH])  # Replace invalid characters
            if not var_name:
                raise ValueError('Unable to calculate var_name for : {!r}'.format(self))
            var_name = 'a' + var_name.capitalize()
            idc.SetMemberName(self.frame_id, self.stack_offset, var_name)

        # Add a comment where the string is being used.
        if self.string_reference:
            idc.MakeRptCmt(self.string_reference, comment)
Ejemplo n.º 2
0
def ida_make_function(location):
    '''
    Description:
        From the first non-function byte, attempt to make a function.

    Input:
        location - The EA at which IDA should attempt to make a function.

    Output:
        True if it succeeded, False otherwise.
    '''
    function_start = location
    ea = location
    while not (idaapi.get_func(ea) or idc.isAlign(idc.GetFlags(ea))):
        function_start = ea
        ea = idc.PrevHead(ea)
    function_start = _un_nop(function_start, idc.NextHead)

    if idc.MakeFunction(function_start):
        last_mnem = idc.GetMnem(
            idc.ItemHead(idaapi.get_func(function_start).endEA - 1))
        if 'ret' not in last_mnem and 'jmp' not in last_mnem:
            idc.DelFunction(function_start)
            append_debug(
                'Created a function at 0x%X, but there wasn\'t a jmp or ret at the end.'
                % function_start)
            return False
        else:
            append_debug('Created a function 0x%X.' % function_start)
            return True
    else:
        return False
Ejemplo n.º 3
0
    def rename(self, name=None):
        """
        Renames (and comments) the string variable in IDA.

        :param str name: New name to given encoded string. (defaults to decoded_string)
        """
        name = name or self.display_name
        if not name:
            append_debug(
                'Unable to rename encoded string due to no decoded string: {!r}'.format(self),
                log_token='[!]')
            return

        # Add comment
        comment = '"{}"'.format(name[:self._MAX_COMMENT_LENGTH])
        if len(name) > self._MAX_COMMENT_LENGTH:
            comment += ' (truncated)'
        if self.string_location not in (INVALID, UNUSED):
            idc.MakeRptCmt(self.string_location, comment)
        if self.string_reference not in (INVALID, UNUSED):
            idc.MakeRptCmt(self.string_reference, comment)

        # Set variable name
        if self.string_location not in (INVALID, UNUSED):
            idaapi.do_name_anyway(self.string_location, name[:self._MAX_NAME_LENGTH])
Ejemplo n.º 4
0
 def define(self):
     """
     Defines the string in the IDB.
     """
     try:
         idc.MakeUnknown(self.startEA, self.byte_length, idc.DOUNK_SIMPLE)
         idaapi.make_ascii_string(self.startEA, self.byte_length, self.string_type)
     except:
         append_debug('Unable to define string at 0x%X' % self.startEA)
Ejemplo n.º 5
0
def create_function_precise(location, require_term=True, start_mnem_bytes=None, end_mnem_bytes=None):
    """
    Description:
        Attempt to make a function containing <location> and only that function.
        First tries to let IDA find the end of the calculated start EA.
        If that fails, try to calculate the end ourselves.

    Input:
        location - An address that should be within a function
        require_term - When True, requires the last instruction in all defined functions to be retn or jmp
        start_mnem_bytes - Try to start functions on a particular instruction
                           Instructions are entered as space separated bytes (i.e. '55' for 'push ebp')
                           The specified pattern will be used first, then the defaults will be used
                           If no pattern is specified, the defaults will be used, which prefers 'push ebp'
        end_mnem_bytes - Try to end functions on a particular instruction
                         Instructions are entered as space separated bytes (i.e. 'C2' for 'retn')
                         The specified pattern will be used first, then the defaults will be used
                         If no pattern is specified, the defaults will be used, which prefers 'retn'

    Output:
        True if it made a function or a function was already present, False otherwise.
    """
    sanity = sanity_checks(location)
    if sanity is None:  # There was already a function
        return True
    elif sanity is False:  # There was something preventing function creation
        return False

    append_debug('Trying to make function for 0x%X' % location)
    function_starts = find_function_starts_near(location, start_mnem_bytes)
    if not function_starts:
        return False  # If we don't have any start points, we're up a creek

    # Don't populate function_ends at this point to avoid the tracing we aren't sure we need yet
    # This will cause two repeats in the last section if we get that far, but that's an acceptable trade-off

    # Try to make a function at the most likely start point letting IDA calculate the end
    if try_make_function(function_starts[0], target_location=location, require_term=require_term,
                         end_mnem_bytes=end_mnem_bytes):
        return True
    else:  # If that fails, try to make a function at that point with the most likely end
        function_ends = find_function_ends_near(location, end_mnem_bytes)
        # Only try the first end here. This guarantees that one of the lower tier starts won't work with idc.BADADDR before we try this end
        if function_ends and try_make_function(function_starts[0], function_ends[0], location, require_term,
                                               end_mnem_bytes):
            return True

    # Always let IDA have the first shot at finding the end for each start
    function_ends.insert(0, idc.BADADDR)
    # For each end, try each start, that way each start gets a shot at the most likely end before we try the next most likely one
    for function_end in function_ends:
        # For each start, try to make a function with the current end
        for function_start in function_starts:
            if try_make_function(function_start, function_end, location, require_term, end_mnem_bytes):
                return True
    return False
Ejemplo n.º 6
0
def create_function(location, find_start=True):
    '''
    Description:
        Attempts to create a function using IDA's builtin functionality. If that fails build a
        assuming a start instruction of "push ebp", "push esp", "push esi", or "push edi" and an
        end instruction of "retn" (C2 or C3), excluding aligns and nops.

    Input:
        location - An address that should be within a function
        find_start - When False, assume location is the start of the function

    Output:
        True if it made a function, False otherwise.
    '''
    # Do a couple sanity checks.
    if idaapi.get_func(location):
        append_debug('There\'s already a function here! (0x%X)' % location)
        return False
    elif idc.isAlign(idc.GetFlags(location)) or idc.GetMnem(location) == 'nop' or \
            (idaapi.isData(idc.GetFlags(location)) and idc.Byte(location) == 0x90):
        append_debug('Can\'t make a function out of aligns and/or nops!')
        return False

    # Trace up as far as possible and have IDA do its thing.
    if ida_make_function(location):
        return True

    # Attempt to find the function ourselves.
    function_starts = _find_function_start(location) if find_start else [
        location
    ]
    function_ends = _find_function_end(location)

    found_func = None
    if function_ends and function_starts:
        for function_start, function_end in itertools.product(
                function_starts, function_ends):
            if function_start < function_end:
                if idc.MakeFunction(function_start, function_end):
                    append_debug('Created a function 0x%X - 0x%X.' %
                                 (function_start, function_end))
                    found_func = (function_start, function_end)
                    break  # Don't return here in case we have to split it yet.
                else:
                    append_debug(
                        'Tried to create a function 0x%X - 0x%X, but IDA wouldn\'t do it.'
                        % (function_start, function_end))

    if found_func:
        split_funcs(*found_func)
        return True

    append_debug('Failed to find function based on location 0x%X.' % location)
    return False
Ejemplo n.º 7
0
def force_create_function(loc):
    """
    Similar to create_function above, but a little more hackish (maybe). Makes a lot of assumptions about there
    being defined code, i.e. not obfsucated code. However, won't create a function that does not include the
    desired location, which will need to be fixed at a later date.

    :param loc: Location a function is needed at

    :return: True if function is created, False otherwise
    """
    # Do a couple sanity checks.
    if idaapi.get_func(loc):
        append_debug('There\'s already a function here!')
        return False
    elif idc.isAlign(idc.GetFlags(loc)) or idc.GetMnem(loc) == 'nop' or \
            (idaapi.isData(idc.GetFlags(loc)) and idc.Byte(loc) == 0x90):
        append_debug('Can\'t make a function out of aligns and/or nops!')
        return False

    start = _force_find_start(loc)
    end = _find_force_end(loc)
    if idc.MakeFunction(start, end):
        append_debug('Created a function 0x%X - 0x%X.' % (start, end))
        return True
    else:
        append_debug('Failed to create a function 0x%X - 0x%X.' % (start, end))
        return False
Ejemplo n.º 8
0
    def _obtain_targeted_apis_by_name(self, api_names):
        """
        Given a list of api_names attempt to locate them in the IDA database by name. If located add to the
        self.api_addrs dictionary.

        :param api_names: List of API names to locate by name

        :return:
        """
        for api_name in api_names:
            addr = obtain_function_by_name(api_name)
            if addr != idc.BADADDR:
                self.api_addrs[api_name] = addr
            else:
                append_debug('Address for %s was not located by name.' %
                             api_name)
def define_string(decoded_string):
    '''
    Defines a string object in the IDB for the provided string.

    Input:
        decoded_string - The EncodedString object to define in IDA
    '''
    try:
        idc.MakeUnknown(decoded_string.startEA, decoded_string.byte_length,
                        idc.DOUNK_SIMPLE)
        idaapi.make_ascii_string(decoded_string.startEA,
                                 decoded_string.byte_length,
                                 decoded_string.string_type)
    except:
        append_debug('Unable to define string at 0x%X' %
                     decoded_string.startEA)
Ejemplo n.º 10
0
def create_functions(location, require_term=True, start_mnem_bytes=None, end_mnem_bytes=None):
    """
    Description:
        Attempts to create functions based on the assumption that there should be continuous contiguous
        functions defined since the previous function or align. Stops creating functions once a function
        containing <location> is created or the next created function would be past <location>.
        Finds both start and end EAs, not using on IDA's algorithms.

    Input:
        location - An address that should be within a function
        require_term - When True, requires the last instruction in all defined functions to be retn or jmp
        start_mnem_bytes - Try to start functions on a particular instruction
                           Instructions are entered as space separated bytes (i.e. '55' for 'push ebp')
                           The specified pattern will be used first, then the defaults will be used
                           If no pattern is specified, the defaults will be used, which prefers 'push ebp'
        end_mnem_bytes - Try to end functions on a particular instruction
                         Instructions are entered as space separated bytes (i.e. 'C2' for 'retn')
                         The specified pattern will be used first, then the defaults will be used
                         If no pattern is specified, the defaults will be used, which prefers 'retn'

    Output:
        True if it made a function or a function was already present, False otherwise.
    """
    sanity = sanity_checks(location)
    if sanity is None:  # There was already a function
        return True
    elif sanity is False:  # There was something preventing function creation
        return False

    # Attempt to find the function ourselves.
    function_starts = find_function_starts(location, start_mnem_bytes)
    function_ends = find_function_ends(location, end_mnem_bytes)

    found_func = None
    for function_start, function_end in itertools.product(function_starts, function_ends):
        if function_start < function_end:
            if try_make_function(function_start, function_end, require_term=require_term) \
                    and function_start <= location < idaapi.get_func(function_start).endEA:
                found_func = (function_start, function_end)
                break  # Don't return here in case we have to split it yet.

    if found_func:
        return True
    else:
        append_debug('Failed to find function based on location 0x%X.' % location)
        return False
Ejemplo n.º 11
0
def try_make_function(function_start, function_end=idc.BADADDR, target_location=None, require_term=True,
                      end_mnem_bytes=None):
    """
    Description:
        Given a function location, attempt to create a function.
        If function creation fails, delete any partially created functions.
        If function creation succeeds, ensure all of the function's bytes are analyzed as code.

    Input:
        function_start - The startEA of the function to create
        function_end - The endEA of the function to create. IDA will calculate if not provided.
        target_location - If provided, fail function creation if it does not include this EA
        require_term - If provided, fail function creation if the last instruction is not a ret or jmp
        end_mnem_bytes - If provided, fail function creation if the last instruction is not the provided bytes
                         Instructions are entered as space separated bytes (i.e. '55' for 'push ebp')

    Output:
        Returns a tuple (function_start, function_end) for the created function if successful, None otherwise
    """
    if function_start <= function_end:
        if idc.MakeFunction(function_start, function_end):
            append_debug('Created a function 0x%X - 0x%X.' % (function_start, function_end))
            if require_term:
                last_mnem_ea = idc.ItemHead(idaapi.get_func(function_start).endEA - 1)
                last_mnem = idc.GetMnem(last_mnem_ea)
                if (end_mnem_bytes is None and 'ret' not in last_mnem and 'jmp' not in last_mnem) or \
                        (end_mnem_bytes and idaapi.get_many_bytes(last_mnem_ea, idc.ItemSize(last_mnem_ea)).encode('hex').upper() != end_mnem_bytes.upper()):
                    idc.DelFunction(function_start)
                    append_debug(
                        'Deleted function at 0x%X - the function didn\'t end with the correct mnem/bytes.' % function_start)
                    return
            if target_location is not None:
                if function_start <= target_location < idaapi.get_func(function_start).endEA:
                    idc.AnalyzeArea(function_start, idaapi.get_func(function_start).endEA)
                    return function_start, function_end
                else:
                    idc.DelFunction(function_start)
                    append_debug(
                        'Deleted function at 0x%X - the function didn\'t contain the target location.' % function_start)
                    return
        else:
            append_debug(
                'Tried to create a function 0x%X - 0x%X, but IDA wouldn\'t do it.' % (function_start, function_end))
    else:
        append_debug('The end address was not greater than the start address!')
def run_yara_on_segment(rule_text,
                        name=None,
                        start_ea=None,
                        callback_func=_yara_callback):
    '''
    Description:
        Applies yara rule to the bytes in the specified segment and returns raw results.
        Segments may be specified by name or start EA, but one or the other is required.
        Clears the matches each time to prevent duplicates.

    Input:
        name - The name of the target segment
        start_ea - The start EA of the target segment
        callback_func - A pointer to the callback function for YARA's matching to use

    Output:
        Returns a list of YARA's match results with items (location, description)
    '''
    global _YARA_MATCHES, FROM_FILE
    _YARA_MATCHES = []
    FROM_FILE = False

    if name is None and start_ea is None:
        raise Exception(
            "Either a segment name or start EA are required to YARA scan a specific segment."
        )

    rule = yara.compile(source=rule_text)
    found_segment = False
    for seg in map(idaapi.getseg, idautils.Segments()):
        if seg.startEA == start_ea or idaapi.get_segm_name(
                seg.startEA) == name:
            found_segment = True
            for bites in _read_bytes(seg.startEA, seg.endEA):
                rule.match(data=bites, callback=callback_func)

    if not found_segment:
        append_debug("Failed to find segment \"" + name + "\"")

    return _YARA_MATCHES
    def rename(self, new_name):
        '''
        Description:
            Attempts to apply new_name to the object at <ea>. If more than one object starts at <ea>, the
            largest object will be renamed. If that name already exists, let IDA resolve the collission
            and then return that name. If new_name is "", reset the name to IDA's default.

        Input:
            new_name - The desired new name for the function.

        Output:
            The name that ended up getting set (unless no name was set, then return None).
        '''
        if new_name == '':
            if idaapi.set_name(self.function_obj.startEA, new_name):
                return idaapi.get_name(self.function_obj.startEA,
                                       self.function_obj.startEA)
            else:
                append_debug('Failed to reset name at 0x%X' %
                             self.function_obj.startEA)
        elif idaapi.do_name_anyway(self.function_obj.startEA, new_name):
            self.name = idaapi.get_name(self.function_obj.startEA,
                                        self.function_obj.startEA)
            if self.name != new_name:
                append_debug('IDA changed name "%s" to "%s"' %
                             (new_name, self.name))
            return self.name
        else:
            append_debug('Failed to rename at 0x%X' %
                         self.function_obj.startEA)
Ejemplo n.º 14
0
def find_encoded_strings(funcs, Tracer, **kwargs):
    """
    Description:
        For each ref for each function, attempt to find encoded strings.

    Input:
        funcs - A list of functions. Must have an xrefs_to field.
        Tracer - A pointer to an implementation of StringTracer.
        **kwargs - kwargs to be passed to Tracer's constructor.

    Output:
        A list of EncodedStrings.
    """
    encoded_strings = []
    for func in funcs:
        for ref in func.xrefs_to:
            if idc.SegName(ref) == '.pdata':
                append_debug('Segment .pdata for ref 0x%08x is not a relevant code segment and will be skipped' % ref)
            else:
                try:
                    tracer = Tracer(ref, func.identifier, **kwargs)
                    if tracer.search():
                        encoded_strings.extend(tracer.encoded_strings)
                    else:
                        append_debug('Failed to find strings at 0x%X' % ref)
                except AttributeError:
                    append_debug(
                        'No function exists at 0x%X. Create a function at this location to obtain strings.' % ref)
    return encoded_strings
Ejemplo n.º 15
0
 def __init__(self, ea, identifier=UNUSED, create_if_not_exists=True):
     super(SuperFunc_t, self).__init__()
     self.origin_ea = ea
     self.identifier = identifier
     self.function_obj = idaapi.get_func(ea)
     if not self.function_obj:
         if create_if_not_exists:
             if create_function_precise(ea, False):
                 self.function_obj = idaapi.get_func(ea)
                 append_debug("Created function at 0x%X" % self.function_obj.startEA)
             else:
                 raise AttributeError("No function at 0x%X" % ea)
         else:
             raise AttributeError("No function at 0x%X" % ea)
     if self.function_obj:
         self.startEA = self.function_obj.startEA
         self.endEA = self.function_obj.endEA
     self.name = idaapi.get_func_name(self.function_obj.startEA)
     self.xrefs_to = [ref.frm for ref in idautils.XrefsTo(self.function_obj.startEA)
                      if idaapi.get_func_name(ref.frm) != self.name]
     self.xref_count = len(self.xrefs_to)
     self._flowchart = None
Ejemplo n.º 16
0
def split_funcs(startEA, endEA):
    '''
    Description:
        Attempt to split the function we created into a bunch of smaller functions based on
        aligns we find in the middle of the func. If we do successfully split, recurse on
        the remainder of the original function.

    Input:
        startEA - The beginning of the function
        endEA - The end of the function

    Output:
        The IDB is updated with the resulting functions
    '''
    ea = startEA
    while ea < endEA:
        # We found an align so delete the function and try to make 2 new ones in its place.
        if idaapi.isAlign(idc.GetFlags(ea)) and idc.DelFunction(startEA):
            # Make the first function.
            if idc.MakeFunction(startEA, _un_nop(ea, idc.NextHead)):
                # We found an align, now get past them.
                while idaapi.isAlign(idc.GetFlags(ea)):
                    ea += idc.ItemSize(ea)

                # Make the second function and recurse to ensure it doesn't need split too.
                if idc.MakeFunction(_un_nop(ea, idc.PrevHead), endEA):
                    append_debug('Split 0x%X - 0x%X at 0x%X.' %
                                 (startEA, endEA, ea))
                    split_funcs(ea, endEA)
                    return
                else:  # We failed to make the second function, so delete the first.
                    idc.DelFunction(startEA)

            # Splitting failed - rebuild the original function.
            idc.MakeFunction(startEA, endEA)
            append_debug('Almost split 0x%X - 0x%X at 0x%X.' %
                         (startEA, endEA, ea))

        ea += idc.ItemSize(ea)
Ejemplo n.º 17
0
    def patch(self, fill_char=None, define=True):
        """
        Patches the original encoded string with the decoded string.

        :param str fill_char:
            Character to use to fill left over space if decoded data
            is shorter than its encoded data. (defaults to leaving the original data)
        :param bool define: Whether to define the string after patching.
        """
        if self.decoded_data in (INVALID, UNUSED):
            return
        if self.string_location in (INVALID, UNUSED, idc.BADADDR):
            return
        decoded_data = self.as_bytes
        if fill_char:
            decoded_data += fill_char * (len(self.encoded_data) - len(decoded_data))
        try:
            idaapi.patch_many_bytes(self.startEA, decoded_data)
            if define:
                self.define()
        except TypeError:
            append_debug("String type for decoded string from location 0x{:08x}.".format(self.startEA))
def decode_strings(encoded_strings, decode):
    '''
    Description:
        For each encoded_string entry in encoded_strings, decode the data using the provided
        decode function.

    Input:
        encoded_strings - A list of EncodedStrings.
        decode - A pointer to a function that handles decoding.

    Output:
        A list of successfully decoded EncodedStrings.
    '''
    decoded_strings = []
    for encoded_string in encoded_strings:
        if encoded_string.encoded_data == INVALID:
            append_debug('Unable to find string at: 0x%X' %
                         encoded_string.string_location)
            continue
        encoded_string.decoded_string = decode(encoded_string)
        if encoded_string.decoded_string:  # Allow decoders to abort/fail quietly
            decoded_strings.append(encoded_string)
    return decoded_strings
Ejemplo n.º 19
0
def sanity_checks(location):
    """
    Description:
        Do some basic checks to see if a function can be created containing the provided EA.

    Input:
        location - The EA to evaluate

    Output:
        True if a function can be created containing the provided EA
        False if a the provided EA was a nop or Align
        None if there is already a function containing the provided EA
    """
    if idaapi.get_func(location):
        append_debug('There\'s already a function here! (0x%X)' % location)
        return None
    elif idc.isAlign(idc.GetFlags(location)) or idc.GetMnem(location) == 'nop' or \
            (idaapi.isData(idc.GetFlags(location)) and idc.Byte(location) == 0x90):
        # Yes, the nop bit may be incorrect, but it's gonna be a very special case that needs a function with nops
        append_debug('Can\'t make a function including aligns and/or nops!')
        return False
    else:
        return True
Ejemplo n.º 20
0
def find_encoded_strings_inline(matches, Tracer, **kwargs):
    """
    For each yara match, attempt to find encoded strings.

    Input:
        matches - A list of yara matches (ea, identifier)
        Tracer - A pointer to an implementation of StringTracer.
        **kwargs - kwargs to be passed to Tracer's constructor.

    Output:
        A list of EncodedStrings.
    """
    encoded_strings = []
    for ea, identifier in matches:
        try:
            tracer = Tracer(ea, identifier, **kwargs)
            if tracer.search():
                encoded_strings.extend(tracer.encoded_strings)
            else:
                append_debug('Failed to find strings at 0x%X' % ea)
        except AttributeError:
            append_debug('Error tracing at 0x%X' % ea)
    return encoded_strings
Ejemplo n.º 21
0
def find_input_file():
    """
    Description:
        Check whether or not IDA knows where the original file used to create the IDB is.
        If IDA doesn't know, check the IDA's directory for the file.

    Output:
        Returns True if the input file was located, False if it was not.
    """
    global INPUT_FILE_PATH
    ida_path = INPUT_FILE_PATH
    if not os.path.exists(ida_path):
        # If IDA does not know, check if the (correct) file is sitting next to the IDB.
        local_path = os.path.join(idautils.GetIdbDir(), idc.GetInputFile())
        if os.path.exists(local_path) and \
                hashlib.md5(open(local_path, 'rb').read()).hexdigest().upper() == idc.GetInputMD5():
            INPUT_FILE_PATH = local_path
            append_debug('Guessed the input file path: ' + INPUT_FILE_PATH)
            append_debug('IDA thought it was:          ' + ida_path)
            return True
        else:
            return False
    else:
        return True
Ejemplo n.º 22
0
def string_decoder_main(yara_rule, Tracer, decode, patch=True, func_name='string_decode_function', inline=False):
    """
    Description:
        If you are going to use this file's workflow as is, this is the entry point. This supports the majority
        of decode function xref based decoding algorithms (the 'older' way) and inline decoding algorithms
        (the 'newer' way).

    Input:
        First, you'll need to provide a YARA rule. The rule can either be passed as a string or in a file.
        If it is in a file, be sure to set is_yara_file = True. Also note that when YARA finds the decoder
        function, it will attempt to rename it:
            func_name = '' will reset the name to the default name it had when IDA built the IDB
            func_name = None will prevent this script from renaming it

        Second, you need to implement the StringTracer class above to handle your malware's particular
        way of loading the encrypted string. Pass a pointer to this class as the second parameter.

        Third, you need to implement the decoding algorithm and pass a pointer to the entry function
        as the third parameter.

        It is also important to have the original file in the correct location for YARA rule application.
        The IDB contains the path of original file from when the IDB was created, but if the file isn't there,
        this script will check the IDB's current directory for a file with the same name and same md5.

        This script will also patch the encoded string with the decoded one unless patch = False.

    Throws:
        AttributeError - see StringTracer, SuperFunc_t
        OSError - The provided YARA file could not be opened.
        RuntimeError - see _yara_find_decode_functions
        TypeError - The provided Tracer does not extend StringTracer.
        ValueError - Could not find the file used to create the IDB.
                   - see EncodedString.get_bytes
    """
    global ENCODED_STRINGS
    global DECODED_STRINGS

    # We could just check if there is a 'search' method, but handle it this way to enforce conventions.
    if not issubclass(Tracer, StringTracer):
        append_debug("Tracer does not extend StringTracer!")
        return

    # Check that IDA actually knows where the original input file is.
    if not find_input_file():
        append_debug("Unable to locate the file used to create the IDB: " + INPUT_FILE_PATH)
        return

    # Do the decoding.
    try:
        if inline:
            matches = generic_run_yara(yara_rule)
            ENCODED_STRINGS = find_encoded_strings_inline(matches, Tracer)
        else:
            decode_functions = yara_find_decode_functions(yara_rule, func_name)
            ENCODED_STRINGS = find_encoded_strings(decode_functions, Tracer)
        ENCODED_STRINGS = decode_strings(ENCODED_STRINGS, decode)
        string_list = output_strings(ENCODED_STRINGS)
        if patch:
            patch_decoded(ENCODED_STRINGS)
        return string_list
    except RuntimeError:
        append_debug("The provided YARA rule failed to match. No strings can be decrypted for this YARA rule.")
        return
def output_strings(decoded_strings, size_in_comment=False):
    '''
    Description:
        Outputs the decoded string data to the console and as a comment at the
        reference location. Duplicate strings (based on decoded_string and string_location)
        are only operated on once.

    Input:
        decoded_strings - The list of decoded EncodedStrings
        size_in_comment - When True AND strings have string_reference populated,
                          the size of the decoded string will be added to the ref's comment.

    Output:
        Returns a list of the decoded string values in utf-8.
        Prints decoded string info to the console.
        Comments the decoded string values to their reference EAs (where applicable).
    '''
    deduped_decoded_strings = {(string.decoded_string, string.string_location):
                               string
                               for string in decoded_strings}.values()
    deduped_decoded_strings.sort(key=lambda string: string.string_location)

    string_list = []
    for decoded_string in deduped_decoded_strings:
        try:
            escaped_string = decoded_string.decode_unknown_charset().rstrip(
                '\x00').encode('unicode-escape')
        except UnicodeDecodeError:  # Well, we tried...
            escaped_string = decoded_string.decoded_string.decode(
                'utf-8', 'replace').rstrip('\x00').encode('unicode-escape')
        string_list.append(escaped_string)
        append_string(escaped_string)

        try:
            print decoded_string
        except:
            append_debug('IDA failed to print this string correctly!')
            if decoded_string.string_location not in [INVALID, UNUSED]:
                print 'EA:  0x%X' % decoded_string.string_location
            if decoded_string.string_reference not in [INVALID, UNUSED]:
                print 'Ref: 0x%X' % decoded_string.string_reference
            if decoded_string.decoded_string is not None:
                try:
                    print 'Dec: ' + decoded_string.decoded_string.rstrip('\x00') + '\t (' + \
                                    decoded_string.decoded_string.rstrip('\x00').encode('unicode-escape') + ')'
                except UnicodeDecodeError:
                    print 'Dec: ' + decoded_string.decoded_string.rstrip(
                        '\x00')
        if decoded_string.string_reference not in [INVALID, UNUSED]:
            if size_in_comment and decoded_string.size not in [
                    INVALID, UNUSED
            ]:
                idc.MakeComm(
                    decoded_string.string_reference,
                    escaped_string + '\nSize: %i' % decoded_string.size)
            else:
                idc.MakeComm(decoded_string.string_reference, escaped_string)
        if decoded_string.string_location not in [INVALID, UNUSED]:
            for ref in idautils.XrefsTo(decoded_string.string_location):
                if ref.frm != decoded_string.string_reference:
                    idc.MakeComm(ref.frm, escaped_string)

    return string_list