def read(self, addr, length): first_block = 0x1000 - addr % 0x1000 full_blocks = ((length + (addr % 0x1000)) / 0x1000) - 1 left_over = (length + addr) % 0x1000 baddr = self.get_addr(addr) if baddr == None: return obj.NoneObject("Could not get base address at " + str(addr)) if length < first_block: stuff_read = self.base.read(baddr, length) return stuff_read stuff_read = self.base.read(baddr, first_block) new_addr = addr + first_block for _i in range(0, full_blocks): baddr = self.get_addr(new_addr) if baddr == None: return obj.NoneObject("Could not get base address at " + str(new_addr)) stuff_read = stuff_read + self.base.read(baddr, 0x1000) new_addr = new_addr + 0x1000 if left_over > 0: baddr = self.get_addr(new_addr) if baddr == None: return obj.NoneObject("Could not get base address at " + str(new_addr)) stuff_read = stuff_read + self.base.read(baddr, left_over) return stuff_read
def dump_hashes(sysaddr, samaddr): if sysaddr == None: yield obj.NoneObject("SYSTEM address is None: Did you use the correct profile?") if samaddr == None: yield obj.NoneObject("SAM address is None: Did you use the correct profile?") bootkey = get_bootkey(sysaddr) hbootkey = get_hbootkey(samaddr, bootkey) if hbootkey: for user in get_user_keys(samaddr): ret = get_user_hashes(user, hbootkey) if not ret: yield obj.NoneObject("Cannot get user hashes for {0}".format(user)) else: lmhash, nthash = ret if not lmhash: lmhash = empty_lm if not nthash: nthash = empty_nt ## temporary fix to prevent UnicodeDecodeError backtraces ## however this can cause truncated user names as a result name = get_user_name(user).encode('ascii', 'ignore') yield "{0}:{1}:{2}:{3}:::".format(name, int(str(user.Name), 16), lmhash.encode('hex'), nthash.encode('hex')) else: yield obj.NoneObject("Hbootkey is not valid")
def dump_hashes(sysaddr, samaddr): if sysaddr == None: yield obj.NoneObject( "SYSTEM address is None: Did you use the correct profile?") if samaddr == None: yield obj.NoneObject( "SAM address is None: Did you use the correct profile?") bootkey = get_bootkey(sysaddr) hbootkey = get_hbootkey(samaddr, bootkey) if hbootkey: for user in get_user_keys(samaddr): ret = get_user_hashes(user, hbootkey) if not ret: yield obj.NoneObject(f"Cannot get user hashes for {user}") else: lmhash, nthash = ret if not lmhash: lmhash = empty_lm if not nthash: nthash = empty_nt ## temporary fix to prevent UnicodeDecodeError backtraces ## however this can cause truncated user names as a result name = get_user_name(user) if name is not None: name = name.encode('ascii', 'ignore') else: name = "(unavailable)" yield f"{name}:{int(str(user.Name), 16)}:{lmhash.hex()}:{nthash.hex()}:::" else: yield obj.NoneObject("Hbootkey is not valid")
def __new__(cls, theType, offset, vm, parent = None, **args): # Don't waste time if we're based on a NULL pointer # I can't think of a better check than this... if offset < 4: return obj.NoneObject("MMVAD probably instantiated from a NULL pointer, there is no tag to read") if not vm: return obj.NoneObject("Could not find address space for _MMVAD object") ## Note that since we were called from __new__ we can return a ## completely different object here (including ## NoneObject). This also means that we can not add any ## specialist methods to the _MMVAD class. ## We must not polute Object's constructor by providing the ## members or struct_size we were instantiated with args.pop('struct_size', None) args.pop('members', None) # Start off with an _MMVAD_LONG result = obj.Object('_MMVAD_LONG', offset = offset, vm = vm, parent = parent, **args) # Get the tag and change the vad type if necessary real_type = cls.tag_map.get(str(result.Tag), None) if not real_type: return obj.NoneObject("Tag {0} not known".format(str(result.Tag))) if result.__class__.__name__ != real_type: result = obj.Object(real_type, offset = offset, vm = vm, parent = parent, **args) return result
def _imported_functions(self): """ Generator for imported functions. @return: tuple (Ordinal, FunctionVA, Name) If the function is imported by ordinal, then Ordinal is the ordinal value and Name is None. If the function is imported by name, then Ordinal is the hint and Name is the imported function name (or None if its paged). FunctionVA is the virtual address of the imported function, as applied to the IAT by the Windows loader. If the FirstThunk is paged, then FunctionVA will be None. """ i = 0 while 1: thunk = obj.Object( '_IMAGE_THUNK_DATA', offset=self.obj_parent.DllBase + self.OriginalFirstThunk + i * self.obj_vm.profile.get_obj_size('_IMAGE_THUNK_DATA'), vm=self.obj_native_vm) # We've reached the end when the element is zero if thunk == None or thunk.AddressOfData == 0: break o = obj.NoneObject("Ordinal not accessible?") n = obj.NoneObject("Imported by ordinal?") f = obj.NoneObject("FirstThunk not accessible") # If the highest bit (32 for x86 and 64 for x64) is set, the function is # imported by ordinal and the lowest 16-bits contain the ordinal value. # Otherwise, the lowest bits (0-31 for x86 and 0-63 for x64) contain an # RVA to an _IMAGE_IMPORT_BY_NAME struct. if thunk.OrdinalBit == 1: o = thunk.Ordinal & 0xFFFF else: iibn = obj.Object("_IMAGE_IMPORT_BY_NAME", offset=self.obj_parent.DllBase + thunk.AddressOfData, vm=self.obj_native_vm) o = iibn.Hint n = iibn.Name # See if the import is bound (i.e. resolved) first_thunk = obj.Object( '_IMAGE_THUNK_DATA', offset=self.obj_parent.DllBase + self.FirstThunk + i * self.obj_vm.profile.get_obj_size('_IMAGE_THUNK_DATA'), vm=self.obj_native_vm) if first_thunk: f = first_thunk.Function.v() yield o, f, n i += 1
def value_data(val): inline = val.DataLength & 0x80000000 if inline: inline_len = val.DataLength & 0x7FFFFFFF if inline_len == 0 or inline_len > 4: valdata = None else: valdata = val.obj_vm.read(val.Data.obj_offset, inline_len) elif val.obj_vm.hive.Version == 5 and val.DataLength > 0x4000: # Value is a BIG_DATA block, stored in chunked format datalen = val.DataLength big_data = obj.Object("_CM_BIG_DATA", val.Data, val.obj_vm) valdata = "" thelist = [] if not big_data.Count or big_data.Count > 0x80000000: thelist = [] else: for i in range(big_data.Count): ptr_off = big_data.List + (i * 4) chunk_addr = obj.Object("unsigned int", ptr_off, val.obj_vm) if not val.obj_vm.is_valid_address(chunk_addr): continue thelist.append(chunk_addr) for chunk in thelist: amount_to_read = min(BIG_DATA_MAGIC, datalen) chunk_data = val.obj_vm.read(chunk, amount_to_read) if not chunk_data: valdata = None break valdata += chunk_data datalen -= amount_to_read else: valdata = val.obj_vm.read(val.Data, val.DataLength) valtype = VALUE_TYPES.get(val.Type.v(), "REG_UNKNOWN") if valdata == None: return (valtype, obj.NoneObject("Value data is unreadable")) if valtype in ["REG_DWORD", "REG_DWORD_BIG_ENDIAN", "REG_QWORD"]: if len(valdata) != struct.calcsize(value_formats[valtype]): return ( valtype, obj.NoneObject( f"Value data did not match the expected data size for a {valtype}" ), ) if valtype in ["REG_SZ", "REG_EXPAND_SZ", "REG_LINK"]: valdata = valdata.decode('utf-16-le', "ignore") elif valtype == "REG_MULTI_SZ": valdata = valdata.decode('utf-16-le', "ignore").split(b'\x00') elif valtype in ["REG_DWORD", "REG_DWORD_BIG_ENDIAN", "REG_QWORD"]: valdata = struct.unpack(value_formats[valtype], valdata)[0] return (valtype, valdata)
def render_text(self, outfd, data): self.table_header(outfd, [("Offset", "[addrpad]"), ("Name", "20"), ("DTB", "[addrpad]")]) for task in data: if isinstance(task, task_struct): dtb = obj.NoneObject() if mm_struct.is_offset_defined('pgd'): pgd = task.mm.pgd dtb = self.addr_space.vtop(pgd) if pgd else pgd self.table_row(outfd, task.obj_offset, task.comm, dtb) else: # dtblist self.table_row(outfd, obj.NoneObject(), obj.NoneObject(), task)
def _read_long_long_phys(self, addr): if not addr: return obj.NoneObject("Unable to read None") try: string = self.base.read(addr, 8) except IOError: string = None if not string: return obj.NoneObject("Unable to read base AS at " + hex(addr)) longlongval, = self._longlong_struct.unpack(string) return longlongval
def _nt_header(self): """Return the _IMAGE_NT_HEADERS object""" try: dos_header = obj.Object("_IMAGE_DOS_HEADER", offset = self.DllBase, vm = self.obj_native_vm) return dos_header.get_nt_header() except ValueError: return obj.NoneObject("Failed initial sanity checks") except exceptions.SanityCheckException: return obj.NoneObject("Failed initial sanity checks. Try -u or --unsafe")
def get_version_info(self, addr_space, offset): """Accepts an address space and an executable image offset Returns a VS_VERSION_INFO object of NoneObject """ if not addr_space.is_valid_address(offset): return obj.NoneObject("Disk image not resident in memory") try: nt_header = self.get_nt_header(addr_space=addr_space, base_addr=offset) except ValueError, ve: return obj.NoneObject( "PE file failed initial sanity checks: {0}".format(ve))
def __init__(self, base, config, **kwargs): self.as_assert(base, "No base Address Space") standard.FileAddressSpace.__init__(self, base, config, layered=True, **kwargs) self.runs = [] self.PageDict = {} self.HighestPage = 0 self.PageIndex = 0 self.AddressList = [] self.LookupCache = {} self.PageCache = Store(50) self.MemRangeCnt = 0 self.offset = 0 self.entry_count = 0xFF # Extract header information self.as_assert(self.profile.has_type("PO_MEMORY_IMAGE"), "PO_MEMORY_IMAGE is not available in profile") self.header = obj.Object('PO_MEMORY_IMAGE', 0, base) ## Is the signature right? if self.header.Signature.lower() not in ['hibr', 'wake']: self.header = obj.NoneObject("Invalid hibernation header") volmag = obj.NoneObject("Invalid hibernation header") else: volmag = obj.VolMagic(base) self.entry_count = volmag.HibrEntryCount.v() PROC_PAGE = volmag.HibrProcPage.v() # Check it's definitely a hibernation file self.as_assert(self._get_first_table_page() is not None, "No xpress signature found") # Extract processor state self.ProcState = obj.Object("_KPROCESSOR_STATE", PROC_PAGE * 4096, base) ## This is a pointer to the page table - any ASs above us dont ## need to search for it. self.dtb = self.ProcState.SpecialRegisters.Cr3.v() # This is a lengthy process, it was cached, but it may be best to delay this # until it's absolutely necessary and/or convert it into a generator... self.build_page_cache()
def _read_long_long_phys_cached(self, addr, cache={}): """ This is an optimized version of read_long_long_phys that memoizes the return values in the cache dictionary. When working on memory dumps on disk, this helps speed things up for a few read-intensive plugins (mac_compressed_swap for example). cache IS NOT a parameter. It's a local object that persists across calls and the appropriate return values are stored there for fast retrieval. """ try: return cache[addr] except KeyError: try: string = self.base.read(addr, 8) except IOError: string = None if not string: val = obj.NoneObject("Unable to read_long_long_phys at " + hex(addr)) cache[addr] = val return val longlongval, = self._longlong_struct.unpack(string) cache[addr] = longlongval return longlongval
def dump_memory_hashes(addr_space, config, syshive, samhive): if syshive != None and samhive != None: sysaddr = hive.HiveAddressSpace(addr_space, config, syshive) samaddr = hive.HiveAddressSpace(addr_space, config, samhive) return dump_hashes(sysaddr, samaddr) return obj.NoneObject( "SYSTEM or SAM address is None: Did you use the correct profile?")
def dbgkd_version64(self): """Scan backwards from the base of KDBG to find the _DBGKD_GET_VERSION64. We have a winner when kernel base addresses and process list head match.""" # Account for address masking differences in x86 and x64 memory_model = self.obj_vm.profile.metadata.get('memory_model', '32bit') dbgkd_off = self.obj_offset & 0xFFFFFFFFFFFFF000 dbgkd_end = dbgkd_off + 0x1000 # The _DBGKD_GET_VERSION64 structure is autogenerated, so # this value should be correct for each profile dbgkd_size = self.obj_vm.profile.get_obj_size("_DBGKD_GET_VERSION64") while dbgkd_off <= (dbgkd_end - dbgkd_size): dbgkd = obj.Object("_DBGKD_GET_VERSION64", offset = dbgkd_off, vm = self.obj_vm) if memory_model == "32bit": KernBase = dbgkd.KernBase & 0xFFFFFFFF PsLoadedModuleList = dbgkd.PsLoadedModuleList & 0xFFFFFFFF else: KernBase = dbgkd.KernBase PsLoadedModuleList = dbgkd.PsLoadedModuleList if ((KernBase == self.KernBase) and (PsLoadedModuleList == self.PsLoadedModuleList)): return dbgkd dbgkd_off += 1 return obj.NoneObject("Cannot find _DBGKD_GET_VERSION64")
def find_shared_info(self): """The way we find win32k!gSharedInfo on Windows 7 is different than before. For each DWORD in the win32k.sys module's .data section (DWORD-aligned) we check if its the HeEntrySize member of a possible tagSHAREDINFO structure. This should equal the size of a _HANDLEENTRY. The HeEntrySize member didn't exist before Windows 7 thus the need for separate methods.""" handle_table_size = self.obj_vm.profile.\ get_obj_size("_HANDLEENTRY") handle_entry_offset = self.obj_vm.profile.\ get_obj_offset("tagSHAREDINFO", "HeEntrySize") for chunk in self._section_chunks(".data"): if chunk != handle_table_size: continue shared_info = obj.Object("tagSHAREDINFO", offset=chunk.obj_offset - handle_entry_offset, vm=self.obj_vm) if shared_info.is_valid(): return shared_info return obj.NoneObject("Cannot find win32k!gSharedInfo")
def read_long(self, addr): _baseaddr = self.get_addr(addr) string = self.read(addr, 4) if not string: return obj.NoneObject("Could not read long at " + str(addr)) longval, = self._long_struct.unpack(string) return longval
def __read_bytes(self, vaddr, length, pad): """ Read 'length' bytes from the virtual address 'vaddr'. The 'pad' parameter controls whether unavailable bytes are padded with zeros. """ vaddr, length = int(vaddr), int(length) ret = '' while length > 0: chunk_len = min(length, 0x1000 - (vaddr % 0x1000)) buf = self.__read_chunk(vaddr, chunk_len) if not buf: if pad: buf = '\x00' * chunk_len else: return obj.NoneObject("Could not read_chunks from addr " + hex(vaddr) + " of size " + hex(chunk_len)) ret += buf vaddr += chunk_len length -= chunk_len return ret
def calculate(self): """Determines the address space""" addr_space = utils.load_as(self._config) result = None adrs = addr_space while adrs: if adrs.__class__.__name__ == 'WindowsHiberFileSpace32': sr = adrs.ProcState.SpecialRegisters peb = obj.NoneObject("Cannot locate a valid PEB") # Find the PEB by cycling through processes. This method works # on all versions of Windows x86 and x64. for task in tasks.pslist(addr_space): if task.Peb: peb = task.Peb break result = {'header': adrs.get_header(), 'sr': sr, 'peb': peb, 'adrs': adrs } adrs = adrs.base if result == None: debug.error("Memory Image could not be identified or did not contain hiberation information") return result
def get_version_info(self): """Get the _VS_VERSION_INFO structure""" try: nt_header = self.get_nt_header() except ValueError, ve: return obj.NoneObject("PE file failed initial sanity checks: {0}".format(ve))
def get_kdbg(addr_space): """A function designed to return the KDBG structure from an address space. First we try scanning for KDBG and if that fails, we try scanning for KPCR and bouncing back to KDBG from there. Also note, both the primary and backup methods rely on the 4-byte KDBG.Header.OwnerTag. If someone overwrites this value, then neither method will succeed. The same is true even if a user specifies --kdbg, because we check for the OwnerTag even in that case. """ kdbgo = obj.VolMagic(addr_space).KDBG.v() kdbg = obj.Object("_KDDEBUGGER_DATA64", offset=kdbgo, vm=addr_space) if kdbg.is_valid(): return kdbg # Fall back to finding it via the KPCR. We cannot # accept the first/best suggestion, because only # the KPCR for the first CPU allows us to find KDBG. for kpcr_off in obj.VolMagic(addr_space).KPCR.generate_suggestions(): kpcr = obj.Object("_KPCR", offset=kpcr_off, vm=addr_space) kdbg = kpcr.get_kdbg() if kdbg.is_valid(): return kdbg return obj.NoneObject( "KDDEBUGGER structure not found using either KDBG signature or KPCR pointer" )
def Thread(self): """Return the ETHREAD if its thread owned""" if self.ThreadOwned: return self.pOwner.\ dereference_as("tagTHREADINFO").\ pEThread.dereference() return obj.NoneObject("Cannot find thread")
def read_long(self, addr: int) -> int: _baseaddr = self.get_addr(addr) string = self.read(addr, 4) if not string: return obj.NoneObject(f"Could not read long at {addr}") (longval, ) = self._long_struct.unpack(string) return longval
def get_debug_directory(self): """Return the debug directory object for this PE""" try: data_dir = self.debug_dir() except ValueError, why: return obj.NoneObject(str(why))
def Peb32(self): """ Returns a _PEB object which is using the process address space. The PEB structure is referencing back into the process address space so we need to switch address spaces when we look at it. This method ensures this happens automatically. """ wow64process = self.Wow64Process if wow64process.is_valid(): process_ad = self.get_process_address_space() if process_ad: # starting with windows 10 the Wow64Process member # points to an _EWOW64PROCESS with a Peb try: offset = wow64process.Peb except AttributeError: offset = wow64process peb32 = obj.Object("_PEB32", offset = offset, vm = process_ad, name = "Peb32", parent = self) if peb32.is_valid(): return peb32 return obj.NoneObject("Peb32 not found")
def dereference(self): length = self.Length.v() if length > 0 and length <= 1024: data = self.Buffer.dereference_as('String', encoding = 'utf16', length = length) return data else: return obj.NoneObject("Buffer length {0} for _UNICODE_STRING not within bounds".format(length))
def get_object_bottom_up(self, struct_name, object_type, skip_type_check): """Get the windows object contained within this pool by using the bottom-up approach to finding the object """ if not object_type: return obj.Object(struct_name, vm = self.obj_vm, offset = self.obj_offset + self.obj_vm.profile.get_obj_size("_POOL_HEADER"), native_vm = self.obj_native_vm) pool_alignment = obj.VolMagic(self.obj_vm).PoolAlignment.v() the_object = obj.Object(struct_name, vm = self.obj_vm, offset = (self.obj_offset + self.BlockSize * pool_alignment - common.pool_align(self.obj_vm, struct_name, pool_alignment)), native_vm = self.obj_native_vm) header = the_object.get_object_header() if (skip_type_check or header.get_object_type() == object_type): return the_object else: return obj.NoneObject("Cannot find the object")
def find_gahti(self): """Find this session's gahti. This can potentially be much faster by searching for '\0' * sizeof(tagHANDLETYPEINFO) instead of moving on a dword aligned boundary through the section. """ for chunk in self._section_chunks(".rdata"): if not chunk.is_valid(): continue gahti = obj.Object("gahti", offset=chunk.obj_offset, vm=self.obj_vm) ## The sanity check here is based on the fact that the first entry ## in the gahti is always for TYPE_FREE. The fnDestroy pointer will ## be NULL, the alloc tag will be an empty string, and the creation ## flags will be zero. We also then check the alloc tag of the first ## USER handle type which should be Uswd (TYPE_WINDOW). ## Update: fnDestroy is no longer NULL for TYPE_FREE on Win8/2012. if (str(gahti.types[0].dwAllocTag) == '' and gahti.types[0].bObjectCreateFlags == 0 and str(gahti.types[1].dwAllocTag) == "Uswd"): return gahti return obj.NoneObject("Cannot find win32k!_gahti")
def find_shared_info(self): """Find this session's tagSHAREDINFO structure. This structure is embedded in win32k's .data section, (i.e. not in dynamically allocated memory). Thus we iterate over each DWORD-aligned possibility and treat it as a tagSHAREDINFO until the sanity checks are met. """ for chunk in self._section_chunks(".data"): # If the base of the value is paged if not chunk.is_valid(): continue # Treat it as a shared info struct shared_info = obj.Object("tagSHAREDINFO", offset=chunk.obj_offset, vm=self.obj_vm) # Sanity check it try: if shared_info.is_valid(): return shared_info except obj.InvalidOffsetError: pass return obj.NoneObject("Cannot find win32k!gSharedInfo")
def read_long(self, addr): _baseaddr = self.translate(addr) string = self.read(addr, 4) if not string: return obj.NoneObject("Could not read data at " + str(addr)) (longval, ) = struct.unpack('=I', string) return longval
def virtual_process_from_physical_offset(addr_space, offset): """ Returns a virtual process from a physical offset in memory """ # Since this is a physical offset, we find the process flat_addr_space = utils.load_as(addr_space.get_config(), astype = 'physical') flateproc = obj.Object("_EPROCESS", offset, flat_addr_space) # then use the virtual address of its first thread to get into virtual land # (Note: the addr_space and flat_addr_space use the same config, so should have the same profile) tleoffset = addr_space.profile.get_obj_offset("_ETHREAD", "ThreadListEntry") # start out with the member offset given to us from the profile offsets = [tleoffset] # if (and only if) we're dealing with 64-bit Windows 7 SP1 # then add the other commonly seen member offset to the list meta = addr_space.profile.metadata major = meta.get("major", 0) minor = meta.get("minor", 0) build = meta.get("build", 0) version = (major, minor, build) if meta.get("memory_model") == "64bit" and version == (6, 1, 7601): offsets.append(tleoffset + 8) ## use the member offset from the profile for ofs in offsets: ethread = obj.Object("_ETHREAD", offset = flateproc.ThreadListHead.Flink.v() - ofs, vm = addr_space) # and ask for the thread's process to get an _EPROCESS with a virtual address space virtual_process = ethread.owning_process() # Sanity check the bounce. See Issue 154. if virtual_process and offset == addr_space.vtop(virtual_process.obj_offset): return virtual_process return obj.NoneObject("Unable to bounce back from virtual _ETHREAD to virtual _EPROCESS")