def _construct_ecrypt_array(self, array_start: int, count: int, \ cryptdll_types: interfaces.context.ModuleInterface) -> interfaces.context.ModuleInterface: """ Attempts to construct an array of _KERB_ECRYPT structures Args: array_start: starting virtual address of the array count: how many elements are in the array cryptdll_types: the reverse engineered types Returns: The instantiated array """ try: array = cryptdll_types.object( object_type="array", offset=array_start, subtype=cryptdll_types.get_type("_KERB_ECRYPT"), count=count, absolute=True) except exceptions.InvalidAddressException: vollog.debug( "Unable to construct cSystems array at given offset: {:x}". format(array_start)) array = None return array
def _find_csystems_with_scanning( self, proc_layer_name: str, cryptdll_types: interfaces.context.ModuleInterface, cryptdll_base: int, cryptdll_size: int) -> List[interfaces.context.ModuleInterface]: """ Performs scanning to find potential RC4 HMAC csystem instances This function may return several values as it cannot validate which is the active one Args: proc_layer_name: the lsass.exe process layer name cryptdll_types: the types from cryptdll binary analysis cryptdll_base: base address of cryptdll.dll inside of lsass.exe cryptdll_size: size of the VAD Returns: A list of csystem instances """ csystems = [] cryptdll_end = cryptdll_base + cryptdll_size proc_layer = self.context.layers[proc_layer_name] ecrypt_size = cryptdll_types.get_type("_KERB_ECRYPT").size # scan for potential instances of RC4 HMAC # the signature is based on the type being 0x17 # and the block size member being 1 in all test samples for address in proc_layer.scan( self.context, scanners.BytesScanner(b"\x17\x00\x00\x00\x01\x00\x00\x00"), sections=[(cryptdll_base, cryptdll_size)]): # this occurs across page boundaries if not proc_layer.is_valid(address, ecrypt_size): continue kerb = cryptdll_types.object("_KERB_ECRYPT", offset=address, absolute=True) # ensure the Encrypt and Finish pointers are inside the VAD # these are not manipulated in the attack if (cryptdll_base < kerb.Encrypt < cryptdll_end) and \ (cryptdll_base < kerb.Finish < cryptdll_end): csystems.append(kerb) return csystems
def lookup_module_address( cls, kernel_module: interfaces.context.ModuleInterface, handlers: List[Tuple[str, int, int]], target_address: int): """ Searches between the start and end address of the kernel module using target_address. Returns the module and symbol name of the address provided. """ mod_name = "UNKNOWN" symbol_name = "N/A" for name, start, end in handlers: if start <= target_address <= end: mod_name = name if name == constants.linux.KERNEL_NAME: symbols = list( kernel_module.get_symbols_by_absolute_location( target_address)) if len(symbols): symbol_name = symbols[0].split(constants.BANG)[1] if constants.BANG in symbols[0] else \ symbols[0] break return mod_name, symbol_name
def _find_array_with_pdb_symbols( self, cryptdll_symbols: str, cryptdll_types: interfaces.context.ModuleInterface, proc_layer_name: str, cryptdll_base: int ) -> Tuple[interfaces.objects.ObjectInterface, int, int, int]: """ Finds the CSystems array through use of PDB symbols Args: cryptdll_symbols: The symbols table from the PDB file cryptdll_types: The types from cryptdll binary analysis proc_layer_name: The lsass.exe process layer name cryptdll_base: Base address of cryptdll.dll inside of lsass.exe Returns: Tuple of: array: The cSystems array rc4HmacInitialize: The runtime address of the expected initialization function rc4HmacDecrypt: The runtime address of the expected decryption function """ cryptdll_module = self.context.module(cryptdll_symbols, layer_name=proc_layer_name, offset=cryptdll_base) rc4HmacInitialize = cryptdll_module.get_absolute_symbol_address( "rc4HmacInitialize") rc4HmacDecrypt = cryptdll_module.get_absolute_symbol_address( "rc4HmacDecrypt") count_address = cryptdll_module.get_symbol("cCSystems").address # we do not want to fail just because the count is not in memory # 16 was the size on samples I tested, so I chose it as the default try: count = cryptdll_types.object(object_type="unsigned long", offset=count_address) except exceptions.InvalidAddressException: count = 16 array_start = cryptdll_module.get_absolute_symbol_address("CSystems") array = self._construct_ecrypt_array(array_start, count, cryptdll_types) if array is None: vollog.debug( "The CSystem array is not present in memory. Stopping PDB based analysis." ) return array, rc4HmacInitialize, rc4HmacDecrypt
def _enumerate_system_va_type( cls, large_page_size: int, system_range_start: int, module: interfaces.context.ModuleInterface, type_array: interfaces.objects.ObjectInterface ) -> Dict[str, List[Tuple[int, int]]]: result: Dict[str, List[Tuple[int, int]]] = {} system_va_type = module.get_enumeration('_MI_SYSTEM_VA_TYPE') start = system_range_start prev_entry = -1 cur_size = large_page_size for entry in type_array: entry = system_va_type.lookup(entry) if entry != prev_entry: region_range = result.get(entry, []) region_range.append((start, cur_size)) result[entry] = region_range start = start + cur_size cur_size = large_page_size else: cur_size += large_page_size prev_entry = entry return result
def determine_extended_value( self, leaf_type: interfaces.objects.ObjectInterface, value: interfaces.objects.ObjectInterface, module: interfaces.context.ModuleInterface, length: int ) -> Tuple[str, interfaces.objects.ObjectInterface, int]: """Reads a value and potentially consumes more data to construct the value.""" excess = 0 if value >= leaf_type.LF_CHAR: sub_leaf_type = self.context.object( self.context.symbol_space.get_enumeration( leaf_type.vol.type_name), layer_name=leaf_type.vol.layer_name, offset=value.vol.offset) # Set the offset at just after the previous size type offset = value.vol.offset + value.vol.data_format.length if sub_leaf_type in [leaf_type.LF_CHAR]: value = module.object(object_type='char', offset=offset) elif sub_leaf_type in [leaf_type.LF_SHORT]: value = module.object(object_type='short', offset=offset) elif sub_leaf_type in [leaf_type.LF_USHORT]: value = module.object(object_type='unsigned short', offset=offset) elif sub_leaf_type in [leaf_type.LF_LONG]: value = module.object(object_type='long', offset=offset) elif sub_leaf_type in [leaf_type.LF_ULONG]: value = module.object(object_type='unsigned long', offset=offset) else: raise TypeError("Unexpected extended value type") excess = value.vol.data_format.length # Updated the consume/offset counters name = module.object(object_type="string", offset=value.vol.offset + value.vol.data_format.length) name_str = self.parse_string(name, leaf_type < leaf_type.LF_ST_MAX, size=length - excess) return name_str, value, excess
def consume_type( self, module: interfaces.context.ModuleInterface, offset: int, length: int ) -> Tuple[Tuple[ Optional[interfaces.objects.ObjectInterface], Optional[str], Union[ None, List, interfaces.objects.ObjectInterface]], int]: """Returns a (leaf_type, name, object) Tuple for a type, and the number of bytes consumed.""" leaf_type = self.context.object(module.get_enumeration("LEAF_TYPE"), layer_name=module._layer_name, offset=offset) consumed = leaf_type.vol.base_type.size remaining = length - consumed if leaf_type in [ leaf_type.LF_CLASS, leaf_type.LF_CLASS_ST, leaf_type.LF_STRUCTURE, leaf_type.LF_STRUCTURE_ST, leaf_type.LF_INTERFACE ]: structure = module.object(object_type="LF_STRUCTURE", offset=offset + consumed) name_offset = structure.name.vol.offset - structure.vol.offset name, value, excess = self.determine_extended_value( leaf_type, structure.size, module, remaining - name_offset) structure.size = value structure.name = name consumed += remaining result = leaf_type, name, structure elif leaf_type in [leaf_type.LF_MEMBER, leaf_type.LF_MEMBER_ST]: member = module.object(object_type="LF_MEMBER", offset=offset + consumed) name_offset = member.name.vol.offset - member.vol.offset name, value, excess = self.determine_extended_value( leaf_type, member.offset, module, remaining - name_offset) member.offset = value member.name = name result = leaf_type, name, member consumed += member.vol.size + len(name) + 1 + excess elif leaf_type in [ leaf_type.LF_ARRAY, leaf_type.LF_ARRAY_ST, leaf_type.LF_STRIDED_ARRAY ]: array = module.object(object_type="LF_ARRAY", offset=offset + consumed) name_offset = array.name.vol.offset - array.vol.offset name, value, excess = self.determine_extended_value( leaf_type, array.size, module, remaining - name_offset) array.size = value array.name = name result = leaf_type, name, array consumed += remaining elif leaf_type in [leaf_type.LF_ENUMERATE]: enum = module.object(object_type='LF_ENUMERATE', offset=offset + consumed) name_offset = enum.name.vol.offset - enum.vol.offset name, value, excess = self.determine_extended_value( leaf_type, enum.value, module, remaining - name_offset) enum.value = value enum.name = name result = leaf_type, name, enum consumed += enum.vol.size + len(name) + 1 + excess elif leaf_type in [leaf_type.LF_ARGLIST, leaf_type.LF_ENUM]: enum = module.object(object_type="LF_ENUM", offset=offset + consumed) name_offset = enum.name.vol.offset - enum.vol.offset name = self.parse_string(enum.name, leaf_type < leaf_type.LF_ST_MAX, size=remaining - name_offset) enum.name = name result = leaf_type, name, enum consumed += remaining elif leaf_type in [leaf_type.LF_UNION]: union = module.object(object_type="LF_UNION", offset=offset + consumed) name_offset = union.name.vol.offset - union.vol.offset name = self.parse_string(union.name, leaf_type < leaf_type.LF_ST_MAX, size=remaining - name_offset) result = leaf_type, name, union consumed += remaining elif leaf_type in [ leaf_type.LF_MODIFIER, leaf_type.LF_POINTER, leaf_type.LF_PROCEDURE ]: obj = module.object(object_type=leaf_type.lookup(), offset=offset + consumed) result = leaf_type, None, obj consumed += remaining elif leaf_type in [leaf_type.LF_FIELDLIST]: sub_length = remaining sub_offset = offset + consumed fields = [] while length > consumed: subfield, sub_consumed = self.consume_type( module, sub_offset, sub_length) sub_consumed += self.consume_padding(module.layer_name, sub_offset + sub_consumed) sub_length -= sub_consumed sub_offset += sub_consumed consumed += sub_consumed fields.append(subfield) result = leaf_type, None, fields elif leaf_type in [leaf_type.LF_BITFIELD]: bitfield = module.object(object_type="LF_BITFIELD", offset=offset + consumed) result = leaf_type, None, bitfield consumed += remaining elif leaf_type in [leaf_type.LF_STRING_ID, leaf_type.LF_FUNC_ID]: string_id = module.object(object_type=leaf_type.lookup(), offset=offset + consumed) name_offset = string_id.name.vol.offset - string_id.vol.offset name = self.parse_string(string_id.name, leaf_type < leaf_type.LF_ST_MAX, size=remaining - name_offset) result = leaf_type, name, string_id elif leaf_type in [ leaf_type.LF_UDT_SRC_LINE, leaf_type.LF_UDT_MOD_SRC_LINE ]: src_line = module.object(object_type=leaf_type.lookup(), offset=offset + consumed) result = leaf_type, None, src_line elif leaf_type in [leaf_type.LF_BUILDINFO]: buildinfo = module.object(object_type=leaf_type.lookup(), offset=offset + consumed) buildinfo.arguments.count = buildinfo.count consumed += buildinfo.arguments.vol.size result = leaf_type, None, buildinfo else: raise TypeError("Unhandled leaf_type: {}".format(leaf_type)) return result, consumed
def determine_map(cls, module: interfaces.context.ModuleInterface) -> \ Dict[str, List[Tuple[int, int]]]: """Returns the virtual map from a windows kernel module.""" layer = module.context.layers[module.layer_name] if not isinstance(layer, intel.Intel): raise result: Dict[str, List[Tuple[int, int]]] = {} system_va_type = module.get_enumeration('_MI_SYSTEM_VA_TYPE') large_page_size = (layer.page_size** 2) // module.get_type("_MMPTE").size if module.has_symbol('MiVisibleState'): symbol = module.get_symbol('MiVisibleState') visible_state = module.object( object_type='pointer', offset=symbol.address, subtype=module.get_type('_MI_VISIBLE_STATE')).dereference() if hasattr(visible_state, 'SystemVaRegions'): for i in range(visible_state.SystemVaRegions.count): lookup = system_va_type.lookup(i) region_range = result.get(lookup, []) region_range.append( (visible_state.SystemVaRegions[i].BaseAddress, visible_state.SystemVaRegions[i].NumberOfBytes)) result[lookup] = region_range elif hasattr(visible_state, 'SystemVaType'): system_range_start = module.object( object_type="pointer", offset=module.get_symbol("MmSystemRangeStart").address) result = cls._enumerate_system_va_type( large_page_size, system_range_start, module, visible_state.SystemVaType) else: raise exceptions.SymbolError(None, module.name, "Required structures not found") elif module.has_symbol('MiSystemVaType'): system_range_start = module.object( object_type="pointer", offset=module.get_symbol("MmSystemRangeStart").address) symbol = module.get_symbol('MiSystemVaType') array_count = (0xFFFFFFFF + 1 - system_range_start) // large_page_size type_array = module.object(object_type='array', offset=symbol.address, count=array_count, subtype=module.get_type('char')) result = cls._enumerate_system_va_type(large_page_size, system_range_start, module, type_array) else: raise exceptions.SymbolError(None, module.name, "Required structures not found") return result
def consume_type( self, module: interfaces.context.ModuleInterface, offset: int, length: int ) -> Tuple[Tuple[ Optional[interfaces.objects.ObjectInterface], Optional[str], Union[ None, List, interfaces.objects.ObjectInterface]], int]: """Returns a (leaf_type, name, object) Tuple for a type, and the number of bytes consumed.""" leaf_type = self.context.object(module.get_enumeration("LEAF_TYPE"), layer_name=module._layer_name, offset=offset) consumed = leaf_type.vol.base_type.size remaining = length - consumed type_handler, has_name, value_attribute = self.type_handlers.get( leaf_type.lookup(), ('LF_UNKNOWN', False, None)) if type_handler in ['LF_FIELDLIST']: sub_length = remaining sub_offset = offset + consumed fields = [] while length > consumed: subfield, sub_consumed = self.consume_type( module, sub_offset, sub_length) sub_consumed += self.consume_padding(module.layer_name, sub_offset + sub_consumed) sub_length -= sub_consumed sub_offset += sub_consumed consumed += sub_consumed fields.append(subfield) result = leaf_type, None, fields elif type_handler in ['LF_BUILDINFO']: parsed_obj = module.object(object_type=type_handler, offset=offset + consumed) parsed_obj.arguments.count = parsed_obj.count consumed += parsed_obj.arguments.vol.size result = leaf_type, None, parsed_obj elif type_handler in self.type_handlers: parsed_obj = module.object(object_type=type_handler, offset=offset + consumed) current_consumed = remaining if has_name: name_offset = parsed_obj.name.vol.offset - parsed_obj.vol.offset if value_attribute: name, value, excess = self.determine_extended_value( leaf_type, getattr(parsed_obj, value_attribute), module, remaining - name_offset) setattr(parsed_obj, value_attribute, value) current_consumed = parsed_obj.vol.size + len( name) + 1 + excess else: name = self.parse_string(parsed_obj.name, leaf_type < leaf_type.LF_ST_MAX, size=remaining - name_offset) parsed_obj.name = name else: name = None result = leaf_type, name, parsed_obj consumed += current_consumed else: raise TypeError(f"Unhandled leaf_type: {leaf_type}") return result, consumed