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)
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
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])
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)
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
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
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
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)
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
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)
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
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
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)
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
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
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
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
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