def iterateList(self, mappings): """ iterate forward, then backward, until null or duplicate """ done = [0] obj = self # print 'going forward ' for fieldname in [forward, backward]: link = getattr(obj, fieldname) addr = utils.get_pointee_address(link) # print fieldname,addr,hex(addr) log.debug("iterateList got a <%s>/0x%x" % (link.__class__.__name__, addr)) nb = 0 while addr not in done: # print '%x %s'%(addr, addr in done) done.append(addr) memoryMap = mappings.is_valid_address_value(addr, structType) if memoryMap == False: log.error("ValueError: 'the link of this linked list has a bad value'") raise StopIteration st = memoryMap.readStruct(addr, structType) st._orig_address_ = addr mappings.keepRef(st, structType, addr) log.debug("keepRefx2 %s.%s: @%x" % (structType.__name__, fieldname, addr)) yield addr # next link = getattr(st, fieldname) addr = utils.get_pointee_address(link) # print 'going backward after %x'%(addr) raise StopIteration
def loadMembers(self, mappings, maxDepth): if not ctypes.Structure.loadMembers(self, mappings, maxDepth): return False # Load and memcopy key and iv log.debug('Enc Memcopying a Key with %d bytes'%self.key_len) attr_obj_address = get_pointee_address(self.key) log.debug('got key @%x '%(attr_obj_address)) memoryMap = mappings.is_valid_address_value( attr_obj_address) # DEBUG - I do question the buffer_copy. log.debug('memoryMap is %s - \nmake array '%(memoryMap)) array=(ctypes.c_ubyte*self.key_len).from_buffer_copy(memoryMap.readArray(attr_obj_address, ctypes.c_ubyte, self.key_len)) # save key as bitstream ##key_contents = ctypes.c_ubyte.from_buffer(array) key_contents = array log.debug('keep ref ') mappings.keepRef(key_contents, utils.get_subtype(self.key), attr_obj_address) log.debug('Enc Memcopying a IV with %d bytes'%( self.block_size) ) attr_obj_address=get_pointee_address(self.iv) memoryMap = mappings.is_valid_address_value(attr_obj_address) log.debug('make array ') array=(ctypes.c_ubyte*self.block_size).from_buffer_copy(memoryMap.readArray(attr_obj_address, ctypes.c_ubyte,self.block_size)) # save iv contents as bitstream ##iv_contents = ctypes.c_ubyte.from_buffer(array) iv_contents = array log.debug('keep ref') mappings.keepRef(iv_contents, utils.get_subtype(self.iv), attr_obj_address) log.debug('ENC KEY(%d bytes) and IV(%d bytes) acquired'%(self.key_len,self.block_size)) return True
def __str__(self): """Print the direct members values. Never tries to recurse.""" import ctypes if hasattr(self, '_orig_address_'): s = "# <%s at @%x>\n" % ( self.__class__.__name__, self._orig_address_) else: s = "# <%s at @???>\n" % (self.__class__.__name__) # we need to ensure _mappings_ is defined in all children. for field, attrtype in self.getFields(): attr = getattr(self, field) if ctypes.is_basic_type(attrtype): # basic type, ctypes or python s += '%s : %s, \n' % (field, repr(attr)) elif (ctypes.is_struct_type(attrtype) or ctypes.is_union_type(attrtype)): # you can print a inner struct content s += '%s (@0x%lx) : {\t%s}\n' % (field, ctypes.addressof(attr), attr) elif ctypes.is_function_type(attrtype): # only print address in target space s += '%s (@0x%lx) : 0x%lx (FIELD NOT LOADED: function type)\n' % ( field, ctypes.addressof(attr), utils.get_pointee_address(attr)) elif ctypes.is_array_of_basic_type(attrtype): try: s += '%s (@0x%lx) : %s\n' % (field, ctypes.addressof(attr), repr(utils.array2bytes(attr))) except IndexError as e: log.error('error while reading %s %s' % (repr(attr), type(attr))) # FIXME elif ctypes.is_array_type(attrtype): # array of something else than int s += '%s (@0x%lx) :[' % (field, ctypes.addressof(attr)) s += ','.join(["%s" % (val) for val in attr]) s += '],\n' elif ctypes.is_cstring_type(attrtype): # only print address/null s += '%s (@0x%lx) : 0x%lx\n' % (field, ctypes.addressof(attr), utils.get_pointee_address(attr.ptr)) elif ctypes.is_pointer_type(attrtype): # and # not ctypes.is_pointer_to_void_type(attrtype)): # do not recurse. if attr is None: attr = 0 s += '%s (@????) : 0x0\n' % (field) else: s += '%s (@0x%lx) : 0x%lx\n' % (field, ctypes.addressof(attr), utils.get_pointee_address(attr)) elif (isinstance(attr, long)) or (isinstance(attr, int)): s += '%s : %s\n' % (field, hex(attr)) else: s += '%s : %s\n' % (field, repr(attr)) return s
def HEAP_get_segment_list(self, mappings): """returns a list of all segment attached to one Heap structure.""" segments = list() for segment in self.iterateListField(mappings, 'SegmentList'): segment_addr = segment._orig_address_ first_addr = utils.get_pointee_address(segment.FirstEntry) last_addr = utils.get_pointee_address(segment.LastValidEntry) log.debug( 'Heap.Segment: 0x%0.8x FirstEntry: 0x%0.8x LastValidEntry: 0x%0.8x' % (segment_addr, first_addr, last_addr)) segments.append(segment) return segments
def test_get_pointee_address(self): """tests get_pointee_address on host ctypes POINTER and haystack POINTER""" ctypes = types.reload_ctypes(8, 8, 16) class X(ctypes.Structure): _pack_ = True _fields_ = [('a', ctypes.c_long), ('p', ctypes.POINTER(ctypes.c_int)), ('b', ctypes.c_ubyte)] self.assertEquals(ctypes.sizeof(X), 17) i = X.from_buffer_copy( b'\xAA\xAA\xBB\xBB' + 4 * '\xBB' + 8 * '\x11' + '\xCC') a = utils.get_pointee_address(i.p) self.assertEquals(ctypes.sizeof(i.p), 8) self.assertNotEquals(a, 0) self.assertEquals(a, 0x1111111111111111) # 8*'\x11' # null pointer i = X.from_buffer_copy( b'\xAA\xAA\xBB\xBB' + 4 * '\xBB' + 8 * '\x00' + '\xCC') pnull = utils.get_pointee_address(i.p) self.assertEquals(utils.get_pointee_address(pnull), 0) # change arch, and retry ctypes = types.reload_ctypes(4, 4, 8) class Y(ctypes.Structure): _pack_ = True _fields_ = [('a', ctypes.c_long), ('p', ctypes.POINTER(ctypes.c_int)), ('b', ctypes.c_ubyte)] self.assertEquals(ctypes.sizeof(Y), 9) i = Y.from_buffer_copy(b'\xAA\xAA\xBB\xBB' + 4 * '\x11' + '\xCC') a = utils.get_pointee_address(i.p) self.assertEquals(ctypes.sizeof(i.p), 4) self.assertNotEquals(a, 0) self.assertEquals(a, 0x11111111) # 4*'\x11' # null pointer i = Y.from_buffer_copy(b'\xAA\xAA\xBB\xBB' + 4 * '\x00' + '\xCC') pnull = utils.get_pointee_address(i.p) self.assertEquals(utils.get_pointee_address(pnull), 0) # non-pointer, and void null pointer ctypes = types.load_ctypes_default() i = ctypes.c_int(69) self.assertEquals(utils.get_pointee_address(i), 0) pnull = ctypes.c_void_p(0) self.assertEquals(utils.get_pointee_address(pnull), 0) pass
def loadMembers(self, mappings, maxDepth): if not ctypes.Structure.loadMembers(self, mappings, maxDepth): return False #log.debug('evp app_data attr_obj_address=0x%lx'%(self.evp.app_data) ) #log.debug('evp cipher_data attr_obj_address=0x%lx'%(self.evp.cipher_data) ) ##none cipher = mappings.getRef(Cipher, get_pointee_address(self.cipher)) ciphername = mappings.getRef(ctypes.CString, get_pointee_address(cipher.name.ptr)) # cast evp.app_data into a valid struct if ciphername in self.cipherContexts: # evp.cipher.nid should be 0 struct = self.cipherContexts[ciphername] if (struct is None): log.warning("Unsupported cipher %s" % (ciphername)) return True attr_obj_address = get_pointee_address(self.evp.app_data) memoryMap = mappings.is_valid_address_value( attr_obj_address, struct) log.debug("CipherContext CAST app_data into : %s " % (struct)) if not memoryMap: log.warning( 'On second toughts, app_data seems to be at an invalid address. That should not happen (often).' ) log.warning('%s addr:0x%lx size:0x%lx addr+size:0x%lx ' % (mappings.is_valid_address_value(attr_obj_address), attr_obj_address, ctypes.sizeof(struct), attr_obj_address + ctypes.sizeof(struct))) return False # DEBUG kill it # read the void * and keep a ref st = memoryMap.readStruct(attr_obj_address, struct) mappings.keepRef(st, struct, attr_obj_address) # yeah... no. "self.evp.app_data = xx" means SEGFAULT. address_evp_app_data = ctypes.addressof(st) log.debug('Copied 0x%lx into app_data (0x%lx)' % (attr_obj_address, address_evp_app_data)) log.debug('LOADED app_data as %s from 0x%lx (%s) into 0x%lx' % (struct, attr_obj_address, mappings.is_valid_address_value( attr_obj_address, struct), address_evp_app_data)) from haystack.outputters import text parser = text.RecursiveTextOutputter(mappings) log.debug('\t\t---------\n%s\t\t---------' % (parser.parse(st))) else: log.debug( "Unknown cipher %s, can't load a data struct for the EVP_CIPHER_CTX->app_data" % (ciphername)) return True
def HEAP_SUBSEGMENT_get_freeblocks(self): """ Use AggregateExchg.Depth and NextFreeoffset to fetch the head, then traverse the links """ userblocks_addr = utils.get_pointee_address(self.UserBlocks) if not bool(userblocks_addr): return [] # structure is in a structure in an union # struct_c__S__INTERLOCK_SEQ_Ua_Sa_0 aggExchange = self.AggregateExchg._0._0 if aggExchange.FreeEntryOffset == 0x2: log.debug(' * FirstFreeOffset==0x2 Depth==%d' % (aggExchange.Depth)) # self.AggregateExchg.Depth the size of UserBlock divided by the HeapBucket size # self.AggregateExchg.FreeEntryOffset starts at 0x2 (blocks), which means 0x10 bytes after UserBlocks # see Understanding LFH page 14 # nextoffset of user data is at current + offset*8 + len(HEAP_ENTRY) # the structure is astructure in an unnamed union of self st = self._3._0 freeblocks = [(userblocks_addr + (aggExchange.FreeEntryOffset * 8) + st.BlockSize * 8 * i, st.BlockSize * 8) for i in range(aggExchange.Depth)] return freeblocks
def HEAP_SUBSEGMENT_get_userblocks(self): """ AggregateExchg contains info on userblocks, number left, depth """ userblocks_addr = utils.get_pointee_address(self.UserBlocks) if not bool(userblocks_addr): log.debug('Userblocks is null') return [] # the structure is astructure in an unnamed union of self st = self._3._0 # its basically an array of self.BlockCount blocks of self.BlockSize*8 # bytes. log.debug( 'fetching %d blocks of %d bytes' % (st.BlockCount, st.BlockSize * 8)) # UserBlocks points to HEAP_USERDATA_HEADER. Real user data blocks will starts after sizeof( HEAP_USERDATA_HEADER ) = 0x10 # each chunk starts with a 8 byte header + n user-writeable data # user writable chunk starts with 2 bytes for next offset # basically, first committed block is first. # ( page 38 ) userblocks = [ (userblocks_addr + 0x10 + st.BlockSize * 8 * i, st.BlockSize * 8) for i in range( st.BlockCount)] # # we need to substract non allocated blocks # self.AggregateExchg.Depth counts how many blocks are remaining free # if self.AggregateExchg.FreeEntryOffset == 0x2, there a are no commited # blocks return userblocks
def HEAP_get_chunks(self, mappings): """Returns a list of tuple(address,size) for all chunks in the backend allocator.""" allocated = list() free = list() for segment in self.get_segment_list(mappings): first_addr = utils.get_pointee_address(segment.FirstEntry) last_addr = utils.get_pointee_address(segment.LastValidEntry) # create the skip list for each segment. skiplist = dict() for ucr in segment.get_UCR_segment_list(mappings): ucr_addr = utils.get_pointee_address(ucr.Address) # UCR.Size are not chunks sizes. NOT *8 skiplist[ucr_addr] = ucr.Size # chunk_addr = first_addr while (chunk_addr < last_addr): if chunk_addr in skiplist: size = skiplist[chunk_addr] log.debug( 'Skipping 0x%0.8x - skip %0.5x bytes to 0x%0.8x' % (chunk_addr, size, chunk_addr + size)) chunk_addr += size continue chunk_header = mappings.getRef(HEAP_ENTRY, chunk_addr) if chunk_header is None: # force read it chunk_header = _get_chunk(mappings, self, chunk_addr) if self.EncodeFlagMask: # heap.EncodeFlagMask chunk_header = HEAP_ENTRY_decode(chunk_header, self) #log.debug('\t\tEntry: 0x%0.8x\n%s'%( chunk_addr, chunk_header)) if ((chunk_header.Flags & 1) == 1): log.debug( 'Chunk 0x%0.8x is in use size: %0.5x' % (chunk_addr, chunk_header.Size * 8)) allocated.append((chunk_addr, chunk_header.Size * 8)) else: log.debug('Chunk 0x%0.8x is FREE' % (chunk_addr)) free.append((chunk_addr, chunk_header.Size * 8)) pass chunk_addr += chunk_header.Size * 8 return (allocated, free)
def EVP_CIPHER_CTX_toPyObject(self): d = super(EVP_CIPHER_CTX,self).toPyObject() log.debug('Cast a EVP_CIPHER_CTX into PyObj') # cast app_data or cipher_data to right struct if bool(self.cipher_data): cipher = self._mappings_.getRef( evp_cipher_st, get_pointee_address(self.cipher) ) struct = getCipherDataType( cipher.nid) if struct is not None: # CAST c_void_p to struct d.cipher_data = struct.from_address(self.cipher_data).toPyObject() return d
def HEAP_getFreeLists_by_blocksindex(self, mappings): """ Understanding_the_LFH.pdf page 21 Not Implemented yet """ freeList = [] # 128 blocks start = ctypes.addressof(self.BlocksIndex) bi_addr = utils.get_pointee_address(self.BlocksIndex) # enumerate BlocksIndex recursively on ExtendedLookup param while bi_addr != 0: log.debug('BLocksIndex is at %x' % (bi_addr)) m = mappings.get_mapping_for_address(bi_addr) bi = m.readStruct(bi_addr, HEAP_LIST_LOOKUP) """ ('ExtendedLookup', POINTER(HEAP_LIST_LOOKUP)), ('ArraySize', __uint32_t), ('ExtraItem', __uint32_t), ('ItemCount', __uint32_t), ('OutOfRangeItems', __uint32_t), ('BaseIndex', __uint32_t), ('ListHead', POINTER(LIST_ENTRY)), ('ListsInUseUlong', POINTER(__uint32_t)), ('ListHints', POINTER(POINTER(LIST_ENTRY))), """ log.debug('ArraySize is %d' % (bi.ArraySize)) log.debug('BlocksIndex: %s' % (bi.toString())) hints_addr = utils.get_pointee_address(bi.ListHints) log.debug('ListHints is pointing to %x' % (hints_addr)) extlookup_addr = utils.get_pointee_address(bi.ExtendedLookup) log.debug('ExtendedLookup is pointing to %x' % (extlookup_addr)) if extlookup_addr == 0: """ all chunks of size greater than or equal to BlocksIndex->ArraySize - 1 will be stored in ascending order in FreeList[ArraySize-BaseIndex – 1] """ log.debug( 'Free chunks >= %d stored at FreeList[ArraySize(%d)-BaseIndex(%d) – 1]' % (bi.ArraySize - 1, bi.ArraySize, bi.BaseIndex)) #raise NotImplementedError() log.debug('-' * 80) bi_addr = extlookup_addr # raise NotImplementedError('NOT FINISHED')
def EVP_CIPHER_CTX_toPyObject(self): d = super(EVP_CIPHER_CTX, self).toPyObject() log.debug('Cast a EVP_CIPHER_CTX into PyObj') # cast app_data or cipher_data to right struct if bool(self.cipher_data): cipher = self._mappings_.getRef(evp_cipher_st, get_pointee_address(self.cipher)) struct = getCipherDataType(cipher.nid) if struct is not None: # CAST c_void_p to struct d.cipher_data = struct.from_address(self.cipher_data).toPyObject() return d
def EVP_CIPHER_CTX_loadMembers(self, mappings, maxDepth): if not super(EVP_CIPHER_CTX, self).loadMembers(mappings, maxDepth): return False log.debug('trying to load cipher_data Structs.') ''' if bool(cipher) and bool(self.cipher.nid) and mappings.is_valid_address(cipher_data): memcopy( self.cipher_data, cipher_data_addr, self.cipher.ctx_size) # cast possible on cipher.nid -> cipherType ''' cipher = mappings.getRef(evp_cipher_st, get_pointee_address(self.cipher)) if cipher.nid == 0: # NID_undef, not openssl doing log.info( 'The cipher is home made - the cipher context data should be application dependant (app_data)' ) return True struct = getCipherDataType(cipher.nid) log.debug('cipher type is %s - loading %s' % (getCipherName(cipher.nid), struct)) if (struct is None): log.warning("Unsupported cipher %s" % (cipher.nid)) return True # c_void_p is a basic type. attr_obj_address = self.cipher_data memoryMap = mappings.is_valid_address_value(attr_obj_address, struct) log.debug("cipher_data CAST into : %s " % (struct)) if not memoryMap: log.warning( 'in CTX On second toughts, cipher_data seems to be at an invalid address. That should not happen (often).' ) log.warning('%s addr:0x%lx size:0x%lx addr+size:0x%lx ' % (mappings.is_valid_address_value(attr_obj_address), attr_obj_address, ctypes.sizeof(struct), attr_obj_address + ctypes.sizeof(struct))) return True #ok st = memoryMap.readStruct(attr_obj_address, struct) mappings.keepRef(st, struct, attr_obj_address) self.cipher_data = ctypes.c_void_p(ctypes.addressof(st)) ###print 'self.cipher_data in loadmembers',self.cipher_data # check debug attr = getattr(self, 'cipher_data') log.debug('Copied 0x%lx into %s (0x%lx)' % (ctypes.addressof(st), 'cipher_data', attr)) log.debug( 'LOADED cipher_data as %s from 0x%lx (%s) into 0x%lx' % (struct, attr_obj_address, mappings.is_valid_address_value(attr_obj_address, struct), attr)) from haystack.outputters import text parser = text.RecursiveTextOutputter(mappings) log.debug('\t\t---------\n%s\t\t---------' % (parser.parse(st))) return True
def loadMembers(self, mappings, maxDepth): if not ctypes.Structure.loadMembers(self, mappings, maxDepth): return False # Load and memcopy key log.debug('Memcopying a Key with %d bytes'%self.key_len) attr_obj_address=get_pointee_address(self.key) memoryMap = mappings.is_valid_address_value( attr_obj_address) array=(ctypes.c_ubyte*self.key_len).from_buffer_copy(memoryMap.readArray(attr_obj_address, ctypes.c_ubyte, self.key_len)) mappings.keepRef(array, utils.get_subtype(self.key), attr_obj_address) log.debug('unmac_ctx has been nulled and ignored. its not often used by any ssh impl. Not useful for us anyway.') log.debug('MAC KEY(%d bytes) acquired'%(self.key_len)) return True
def HEAP_SEGMENT_get_UCR_segment_list(self, mappings): """Returns a list of UCR segments for this segment. HEAP_SEGMENT.UCRSegmentList is a linked list to UCRs for this segment. Some may have Size == 0. """ ucrs = list() for ucr in self.iterateListField(mappings, 'UCRSegmentList'): ucr_struct_addr = ucr._orig_address_ ucr_addr = utils.get_pointee_address(ucr.Address) # UCR.Size are not chunks sizes. NOT *8 log.debug("Segment.UCRSegmentList: 0x%0.8x addr: 0x%0.8x size: 0x%0.5x" % ( ucr_struct_addr, ucr_addr, ucr.Size)) ucrs.append(ucr) return ucrs
def loadMembers(self, mappings, maxDepth): if not ctypes.Structure.loadMembers(self, mappings, maxDepth): return False #log.debug('evp app_data attr_obj_address=0x%lx'%(self.evp.app_data) ) #log.debug('evp cipher_data attr_obj_address=0x%lx'%(self.evp.cipher_data) ) ##none cipher = mappings.getRef( Cipher, get_pointee_address(self.cipher) ) ciphername = mappings.getRef(ctypes.CString, get_pointee_address(cipher.name.ptr) ) # cast evp.app_data into a valid struct if ciphername in self.cipherContexts: # evp.cipher.nid should be 0 struct = self.cipherContexts[ciphername] if (struct is None): log.warning("Unsupported cipher %s"%(ciphername)) return True attr_obj_address = get_pointee_address(self.evp.app_data) memoryMap = mappings.is_valid_address_value(attr_obj_address, struct) log.debug( "CipherContext CAST app_data into : %s "%( struct) ) if not memoryMap: log.warning('On second toughts, app_data seems to be at an invalid address. That should not happen (often).') log.warning('%s addr:0x%lx size:0x%lx addr+size:0x%lx '%(mappings.is_valid_address_value( attr_obj_address), attr_obj_address, ctypes.sizeof(struct), attr_obj_address+ctypes.sizeof(struct))) return False # DEBUG kill it # read the void * and keep a ref st = memoryMap.readStruct(attr_obj_address, struct ) mappings.keepRef(st, struct, attr_obj_address) # yeah... no. "self.evp.app_data = xx" means SEGFAULT. address_evp_app_data = ctypes.addressof(st) log.debug('Copied 0x%lx into app_data (0x%lx)'%(attr_obj_address, address_evp_app_data) ) log.debug('LOADED app_data as %s from 0x%lx (%s) into 0x%lx'%(struct, attr_obj_address, mappings.is_valid_address_value(attr_obj_address,struct), address_evp_app_data)) from haystack.outputters import text parser = text.RecursiveTextOutputter(mappings) log.debug('\t\t---------\n%s\t\t---------'%(parser.parse(st))) else: log.debug("Unknown cipher %s, can't load a data struct for the EVP_CIPHER_CTX->app_data"%(ciphername)) return True
def loadMembers(self, mappings, maxDepth): if not ctypes.Structure.loadMembers(self, mappings, maxDepth): return False # Load and memcopy key and iv log.debug('Enc Memcopying a Key with %d bytes' % self.key_len) attr_obj_address = get_pointee_address(self.key) log.debug('got key @%x ' % (attr_obj_address)) memoryMap = mappings.is_valid_address_value(attr_obj_address) # DEBUG - I do question the buffer_copy. log.debug('memoryMap is %s - \nmake array ' % (memoryMap)) array = (ctypes.c_ubyte * self.key_len).from_buffer_copy( memoryMap.readArray(attr_obj_address, ctypes.c_ubyte, self.key_len)) # save key as bitstream ##key_contents = ctypes.c_ubyte.from_buffer(array) key_contents = array log.debug('keep ref ') mappings.keepRef(key_contents, utils.get_subtype(self.key), attr_obj_address) log.debug('Enc Memcopying a IV with %d bytes' % (self.block_size)) attr_obj_address = get_pointee_address(self.iv) memoryMap = mappings.is_valid_address_value(attr_obj_address) log.debug('make array ') array = (ctypes.c_ubyte * self.block_size).from_buffer_copy( memoryMap.readArray(attr_obj_address, ctypes.c_ubyte, self.block_size)) # save iv contents as bitstream ##iv_contents = ctypes.c_ubyte.from_buffer(array) iv_contents = array log.debug('keep ref') mappings.keepRef(iv_contents, utils.get_subtype(self.iv), attr_obj_address) log.debug('ENC KEY(%d bytes) and IV(%d bytes) acquired' % (self.key_len, self.block_size)) return True
def HEAP_get_free_UCR_segment_list(self, mappings): """Returns a list of available UCR segments for this heap. HEAP.UCRList is a linked list to all UCRSegments """ # TODO: exclude UCR segment from valid pointer values in mappings. ucrs = list() for ucr in self.iterateListField(mappings, 'UCRList'): ucr_struct_addr = ucr._orig_address_ ucr_addr = utils.get_pointee_address(ucr.Address) # UCR.Size are not chunks sizes. NOT *8 log.debug("Heap.UCRList: 0x%0.8x addr: 0x%0.8x size: 0x%0.5x" % ( ucr_struct_addr, ucr_addr, ucr.Size)) ucrs.append(ucr) return ucrs
def is_valid_address(self, obj, structType=None): # FIXME is valid pointer """ :param obj: the obj to evaluate. :param structType: the object's type, so the size could be taken in consideration. Returns False if the object address is NULL. Returns False if the object address is not in a mapping. Returns the mapping in which the object stands otherwise. """ # check for null pointers addr = utils.get_pointee_address(obj) if addr == 0: return False return self.is_valid_address_value(addr, structType)
def loadMembers(self, mappings, maxDepth): if not ctypes.Structure.loadMembers(self, mappings, maxDepth): return False # Load and memcopy key log.debug('Memcopying a Key with %d bytes' % self.key_len) attr_obj_address = get_pointee_address(self.key) memoryMap = mappings.is_valid_address_value(attr_obj_address) array = (ctypes.c_ubyte * self.key_len).from_buffer_copy( memoryMap.readArray(attr_obj_address, ctypes.c_ubyte, self.key_len)) mappings.keepRef(array, utils.get_subtype(self.key), attr_obj_address) log.debug( 'unmac_ctx has been nulled and ignored. its not often used by any ssh impl. Not useful for us anyway.' ) log.debug('MAC KEY(%d bytes) acquired' % (self.key_len)) return True
def getEvpAppData(self, mappings): cipher = self.getCipher(mappings) ciphername = cipher.getName(mappings) if ciphername in self.cipherContexts: struct = self.cipherContexts[ciphername] from haystack.outputters import text parser = text.RecursiveTextOutputter(mappings) if (struct is None): log.warning("Unsupported cipher %s" % (ciphername)) log.warning("%s" % (parser.parse(cipher))) return None log.debug('CAST evp.app_data Into %s' % (struct)) attr_obj_address = get_pointee_address(self.evp.app_data) st = mappings.getRef(struct, attr_obj_address) #st = struct.from_address(attr) log.debug('app_data value is : 0x%lx' % (attr_obj_address)) log.debug(parser.parse(st)) return st return None
def getEvpAppData(self, mappings): cipher = self.getCipher(mappings) ciphername = cipher.getName(mappings) if ciphername in self.cipherContexts: struct = self.cipherContexts[ciphername] from haystack.outputters import text parser = text.RecursiveTextOutputter(mappings) if(struct is None): log.warning("Unsupported cipher %s"%(ciphername)) log.warning("%s"%(parser.parse(cipher))) return None log.debug('CAST evp.app_data Into %s'%(struct)) attr_obj_address = get_pointee_address(self.evp.app_data) st = mappings.getRef(struct, attr_obj_address) #st = struct.from_address(attr) log.debug('app_data value is : 0x%lx'%(attr_obj_address)) log.debug(parser.parse(st)) return st return None
def EVP_CIPHER_CTX_loadMembers(self, mappings, maxDepth): if not super(EVP_CIPHER_CTX,self).loadMembers(mappings, maxDepth): return False log.debug('trying to load cipher_data Structs.') ''' if bool(cipher) and bool(self.cipher.nid) and mappings.is_valid_address(cipher_data): memcopy( self.cipher_data, cipher_data_addr, self.cipher.ctx_size) # cast possible on cipher.nid -> cipherType ''' cipher = mappings.getRef( evp_cipher_st, get_pointee_address(self.cipher) ) if cipher.nid == 0: # NID_undef, not openssl doing log.info('The cipher is home made - the cipher context data should be application dependant (app_data)') return True struct = getCipherDataType( cipher.nid) log.debug('cipher type is %s - loading %s'%( getCipherName(cipher.nid), struct )) if(struct is None): log.warning("Unsupported cipher %s"%(cipher.nid)) return True # c_void_p is a basic type. attr_obj_address = self.cipher_data memoryMap = mappings.is_valid_address_value( attr_obj_address, struct) log.debug( "cipher_data CAST into : %s "%(struct) ) if not memoryMap: log.warning('in CTX On second toughts, cipher_data seems to be at an invalid address. That should not happen (often).') log.warning('%s addr:0x%lx size:0x%lx addr+size:0x%lx '%(mappings.is_valid_address_value(attr_obj_address), attr_obj_address, ctypes.sizeof(struct), attr_obj_address+ctypes.sizeof(struct))) return True #ok st = memoryMap.readStruct(attr_obj_address, struct ) mappings.keepRef(st, struct, attr_obj_address) self.cipher_data = ctypes.c_void_p(ctypes.addressof(st)) ###print 'self.cipher_data in loadmembers',self.cipher_data # check debug attr=getattr(self, 'cipher_data') log.debug('Copied 0x%lx into %s (0x%lx)'%(ctypes.addressof(st), 'cipher_data', attr)) log.debug('LOADED cipher_data as %s from 0x%lx (%s) into 0x%lx'%(struct, attr_obj_address, mappings.is_valid_address_value(attr_obj_address, struct), attr )) from haystack.outputters import text parser = text.RecursiveTextOutputter(mappings) log.debug('\t\t---------\n%s\t\t---------'%(parser.parse(st))) return True
def BIGNUM_loadMembers(self, mappings, maxDepth): ''' #self._d = process.readArray(attr_obj_address, ctypes.c_ulong, self.top) ## or #ulong_array= (ctypes.c_ulong * self.top) ''' if not self.isValid(mappings): log.debug('BigNUm tries to load members when its not validated') return False # Load and memcopy d / BN_ULONG * attr_obj_address = get_pointee_address(self.d) if not bool(self.d): log.debug('BIGNUM has a Null pointer d') return True memoryMap = mappings.is_valid_address_value(attr_obj_address) # TODO - challenge buffer_copy use, contents=(BN_ULONG*self.top).from_buffer_copy(memoryMap.readArray(attr_obj_address, BN_ULONG, self.top)) mappings.keepRef( contents, model.getSubtype(self.d), attr_obj_address ) log.debug('contents acquired %d'%ctypes.sizeof(contents)) return True
def BIGNUM_loadMembers(self, mappings, maxDepth): ''' #self._d = process.readArray(attr_obj_address, ctypes.c_ulong, self.top) ## or #ulong_array= (ctypes.c_ulong * self.top) ''' if not self.isValid(mappings): log.debug('BigNUm tries to load members when its not validated') return False # Load and memcopy d / BN_ULONG * attr_obj_address = get_pointee_address(self.d) if not bool(self.d): log.debug('BIGNUM has a Null pointer d') return True memoryMap = mappings.is_valid_address_value(attr_obj_address) # TODO - challenge buffer_copy use, contents = (BN_ULONG * self.top).from_buffer_copy( memoryMap.readArray(attr_obj_address, BN_ULONG, self.top)) mappings.keepRef(contents, model.getSubtype(self.d), attr_obj_address) log.debug('contents acquired %d' % ctypes.sizeof(contents)) return True
def getKey(self): #return pointer2bytes(self.key,self.key_len) return utils.array2bytes( mappings.getRef(utils.get_subtype(self.key), get_pointee_address(self.key)))
def getIV(self, mappings): #return pointer2bytes(mappings.getRef(ctypes.Array, get_pointee_address(self.iv)), self.block_size) return mappings.array2bytes( mappings.getRef(mappings.get_subtype(self.iv), get_pointee_address(self.iv)))
def _loadMember(self, attr, attrname, attrtype, mappings, maxDepth): ctypes = self._mappings_.config.ctypes # skip static void_p data members if not self._isLoadableMember(attr, attrname, attrtype): log.debug("%s %s not loadable bool(attr) = %s" % (attrname, attrtype, bool(attr))) return True # load it, fields are valid elif ctypes.is_struct_type(attrtype) or ctypes.is_union_type(attrtype): # its an embedded record. Bytes are already loaded. offset = utils.offsetof(type(self), attrname) log.debug('st: %s %s is STRUCT at @%x' % (attrname, attrtype, self._orig_address_ + offset)) # TODO pydoc for impl. attr._orig_address_ = self._orig_address_ + offset attr._mappings = mappings if not attr.loadMembers(mappings, maxDepth - 1): log.debug( "st: %s %s not valid, error while loading inner struct" % (attrname, attrtype)) return False log.debug("st: %s %s inner struct LOADED " % (attrname, attrtype)) return True elif ctypes.is_array_of_basic_type(attrtype): return True elif ctypes.is_array_type(attrtype): log.debug('a: %s is arraytype %s recurse load' % (attrname, repr(attr))) attrLen = len(attr) if attrLen == 0: return True elType = type(attr[0]) for i in range(0, attrLen): # FIXME BUG DOES NOT WORK # offsetof("%s[%d]") is called, and %s exists, not %s[%d] # if not self._loadMember(attr[i], "%s[%d]"%(attrname,i), # elType, mappings, maxDepth): if not self._loadMember( attr[i], attrname, elType, mappings, maxDepth): return False return True # we have PointerType here . Basic or complex # exception cases elif ctypes.is_function_type(attrtype): pass # FIXME elif ctypes.is_cstring_type(attrtype): # can't use basic c_char_p because we can't load in foreign memory # FIXME, you need to keep a ref to this ctring if # your want _mappings_ to exists # or just mandate mappings in toString attr_obj_address = utils.get_pointee_address(attr.ptr) if not bool(attr_obj_address): log.debug('%s %s is a CString, the pointer is null (validation ' 'must have occurred earlier)' % (attrname, attr)) return True memoryMap = mappings.is_valid_address_value(attr_obj_address) if not memoryMap: log.warning('Error on addr while fetching a CString.' 'should not happen') return False ref = mappings.getRef(ctypes.CString, attr_obj_address) if ref: log.debug("%s %s loading from references cache %s/0x%lx" % (attrname, attr, ctypes.CString, attr_obj_address)) return True max_size = min( self.MAX_CSTRING_SIZE, memoryMap.end - attr_obj_address) log.debug('%s %s is defined as a CString, loading %d bytes from 0x%lx ' 'is_valid_address %s' % (attrname, attr, max_size, attr_obj_address, mappings.is_valid_address_value(attr_obj_address))) txt, truncated = memoryMap.readCString(attr_obj_address, max_size) if truncated: log.warning( 'buffer size was too small for this CString: %d' % (max_size)) # that will SEGFAULT attr.string = txt - instead keepRef to String mappings.keepRef(txt, ctypes.CString, attr_obj_address) log.debug( 'kept CString ref for "%s" at @%x' % (txt, attr_obj_address)) return True # not functionType, it's not loadable elif ctypes.is_pointer_type(attrtype): _attrType = get_subtype(attrtype) attr_obj_address = utils.get_pointee_address(attr) #### # memcpy and save objet ref + pointer in attr # we know the field is considered valid, so if it's not in # memory_space, we can ignore it memoryMap = mappings.is_valid_address(attr, _attrType) if(not memoryMap): # big BUG Badaboum, why did pointer changed validity/value ? log.warning( "%s %s not loadable 0x%lx but VALID " % (attrname, attr, attr_obj_address)) return True ref = mappings.getRef(_attrType, attr_obj_address) if ref: log.debug( "%s %s loading from references cache %s/0x%lx" % (attrname, attr, _attrType, attr_obj_address)) # DO NOT CHANGE STUFF SOUPID attr.contents = ref. attr.contents # will SEGFAULT return True log.debug( "%s %s loading from 0x%lx (is_valid_address: %s)" % (attrname, attr, attr_obj_address, memoryMap)) # Read the struct in memory and make a copy to play with. # DO NOT COPY THE STRUCT, we have a working readStruct for that... # ERRROR # attr.contents=_attrType.from_buffer_copy(memoryMap.readStruct(attr_obj_address, # _attrType )) contents = memoryMap.readStruct(attr_obj_address, _attrType) # save that validated and loaded ref and original addr so we dont # need to recopy it later mappings.keepRef(contents, _attrType, attr_obj_address) log.debug( "keepRef %s.%s @%x" % (_attrType, attrname, attr_obj_address)) log.debug( "%s %s loaded memcopy from 0x%lx to 0x%lx" % (attrname, attr, attr_obj_address, (utils.get_pointee_address(attr)))) # recursive validation checks on new struct if not bool(attr): log.warning( 'Member %s is null after copy: %s' % (attrname, attr)) return True # go and load the pointed struct members recursively subtype = utils.get_subtype(attrtype) if (ctypes.is_basic_type(subtype) or ctypes.is_array_of_basic_type(subtype)): # do nothing return True elif (ctypes.is_array_type(subtype) or ctypes.is_pointer_type(subtype)): return self._loadMember( contents, 'pointee', subtype, mappings, maxDepth - 1) if not contents.loadMembers(mappings, maxDepth - 1): log.debug('member %s was not loaded' % (attrname)) # invalidate the cache ref. mappings.delRef(_attrType, attr_obj_address) return False return True # TATAFN return True
def getCipher(self, mappings): cipher = mappings.getRef(Cipher, get_pointee_address(self.cipher)) return cipher
def HEAP_get_frontend_chunks(self, mappings): """ windows xp ? the list of chunks from the frontend are deleted from the segment chunk list. Functionnaly, (page 28) LFH_HEAP should be fetched by HEAP_BUCKET calcul //position 0x7 in the header denotes //whether the chunk was allocated via //the front-end or the back-end (non-encoded ;) ) if(ChunkHeader->UnusedBytes & 0x80) RtlpLowFragHeapFree else BackEndHeapFree """ res = list() all_free = list() all_committed = list() log.debug('HEAP_get_frontend_chunks') ptr = self.FrontEndHeap addr = utils.get_pointee_address(ptr) if self.FrontEndHeapType == 1: # windows XP per default # TODO delete this ptr from the heap-segment entries chunks for x in range(128): log.debug('finding lookaside %d at @%x' % (x, addr)) m = mappings.get_mapping_for_address(addr) st = m.readStruct(addr, HEAP_LOOKASIDE) # load members on self.FrontEndHeap car c'est un void * for free in st.iterateList('ListHead'): # single link list. # TODO delete this free from the heap-segment entries chunks log.debug('free') res.append(free) # ??? pass addr += ctypes.sizeof(HEAP_LOOKASIDE) elif self.FrontEndHeapType == 2: # win7 per default log.debug('finding frontend at @%x' % (addr)) m = mappings.get_mapping_for_address(addr) st = m.readStruct(addr, LFH_HEAP) # LFH is a big chunk allocated by the backend allocator, called subsegment # but rechopped as small chunks of a heapbin. # Active subsegment hold that big chunk. # # # load members on self.FrontEndHeap car c'est un void * if not st.loadMembers(mappings, 1): log.error('Error on loading frontend') raise model.NotValid('Frontend load at @%x is not valid' % (addr)) # log.debug(st.LocalData[0].toString()) # # 128 HEAP_LOCAL_SEGMENT_INFO for sinfo in st.LocalData[0].SegmentInfo: # TODO , what about ActiveSubsegment ? for items_ptr in sinfo.CachedItems: # 16 caches items max items_addr = utils.get_pointee_address(items_ptr) if not bool(items_addr): #log.debug('NULL pointer items') continue m = mappings.get_mapping_for_address(items_addr) subsegment = m.readStruct(items_addr, HEAP_SUBSEGMENT) # log.debug(subsegment) # TODO current subsegment.SFreeListEntry is on error at some depth. # bad pointer value on the second subsegment chunks = subsegment.get_userblocks() free = subsegment.get_freeblocks() committed = set(chunks) - set(free) all_free.extend(free) all_committed.extend(committed) log.debug( 'subseg: 0x%0.8x, commit: %d chunks free: %d chunks' % (items_addr, len(committed), len(free))) else: # print 'FrontEndHeapType == %d'%(self.FrontEndHeapType) #raise StopIteration pass return all_committed, all_free
def BIGNUM___str__(self): d= get_pointee_address(self.d) return ("BN { d=0x%lx, top=%d, dmax=%d, neg=%d, flags=%d }"% (d, self.top, self.dmax, self.neg, self.flags) )
def BIGNUM_get_d(self): return self._mapping_.getRef( model.getSubtype(self.d), get_pointee_address(self.d))
def getKey(self): #return pointer2bytes(self.key,self.key_len) return utils.array2bytes( mappings.getRef( utils.get_subtype(self.key), get_pointee_address(self.key)) )
def getName(self, mappings): ciphername = mappings.getRef(ctypes.CString, get_pointee_address(self.name.ptr)) if ciphername is None: raise ValueError('ciphername not in cache') return ciphername
def getIV(self, mappings): #return pointer2bytes(mappings.getRef(ctypes.Array, get_pointee_address(self.iv)), self.block_size) return mappings.array2bytes(mappings.getRef(mappings.get_subtype(self.iv), get_pointee_address(self.iv)) )
def _attrToPyObject(self, attr, field, attrtype): import ctypes if ctypes.is_basic_type(attrtype): if ctypes.is_basic_ctype(type(attr)): obj = attr.value else: obj = attr elif ctypes.is_struct_type(attrtype) or ctypes.is_union_type(attrtype): attr._mappings_ = self.mappings obj = self.parse(attr) elif ctypes.is_array_of_basic_type(attrtype): # return a list of int, float, or a char[] to str obj = utils.ctypes_to_python_array(attr) elif ctypes.is_array_type(attrtype): # array of something else than int/byte obj = [] eltyp = type(attr[0]) for i in range(0, len(attr)): obj.append(self._attrToPyObject(attr[i], i, eltyp)) elif ctypes.is_cstring_type(attrtype): obj = self.mappings.getRef( ctypes.CString, utils.get_pointee_address( attr.ptr)) elif ctypes.is_function_type(attrtype): obj = repr(attr) elif ctypes.is_pointer_type(attrtype): # get the cached Value of the LP. _subtype = get_subtype(attrtype) _address = utils.get_pointee_address(attr) if _address == 0: # Null pointer obj = None elif ctypes.is_pointer_to_void_type(attrtype): # TODO: make a prototype for c_void_p loading # void types a rereturned as None obj = None elif ctypes.is_array_of_basic_type(attrtype): log.error('basic Type array - %s' % (field)) obj = 'BasicType array' else: # get the cached Value of the LP. _subtype = get_subtype(attrtype) cache = self.mappings.getRef(_subtype, _address) if cache is not None: # struct, union... obj = self._attrToPyObject(cache, field, _subtype) else: # you got here because your pointer is not loaded: # did you ignore it in expectedValues ? # is it in the middle of a struct ? # is that a linked list ? # is it a invalid instance ? log.debug('Pointer for field:%s %s/%s not in cache ' '0x%x' % (field, attrtype, get_subtype(attrtype), _address)) return (None, None) elif isinstance(attr, numbers.Number): # case for int, long. But needs to be after c_void_p pointers case obj = attr else: log.error('toPyObj default to return attr %s' % (type(attr))) obj = attr return obj
def _isValidAttr(self, attr, attrname, attrtype, mappings): """ Validation of a single member """ import ctypes # a) log.debug('valid: %s, %s' % (attrname, attrtype)) if ctypes.is_basic_type(attrtype): if attrname in self.expectedValues: if attr not in self.expectedValues[attrname]: log.debug( 'basicType: %s %s %s bad value not in self.expectedValues[attrname]:' % (attrname, attrtype, repr(attr))) return False log.debug( 'basicType: %s %s %s ok' % (attrname, attrtype, repr(attr))) return True # b) elif ctypes.is_struct_type(attrtype) or ctypes.is_union_type(attrtype): # do i need to load it first ? becaus it should be memcopied with # the super().. if not attr.isValid(mappings): log.debug( 'structType: %s %s %s isValid FALSE' % (attrname, attrtype, repr(attr))) return False log.debug( 'structType: %s %s %s isValid TRUE' % (attrname, attrtype, repr(attr))) return True # c) elif ctypes.is_array_of_basic_type(attrtype): if attrname in self.expectedValues: if attr not in self.expectedValues[attrname]: log.debug( 'basicArray: %s %s %s - bad value not in self.expectedValues[attrname]:' % (attrname, attrtype, type(attr))) return False log.debug( 'basicArray: %s is arraytype %s we decided it was valid', attrname, type(attr)) return True # d) elif ctypes.is_array_type(attrtype): log.debug('array: %s is arraytype %s recurse validate' % (attrname, repr(attr))) attrLen = len(attr) if attrLen == 0: return True elType = type(attr[0]) for i in range(0, attrLen): # FIXME BUG DOES NOT WORK - offsetof("%s[%d]") is called, # and %s exists, not %s[%d] if not self._isValidAttr(attr[i], "%s[%d]" % (attrname, i), elType, mappings): return False return True # e) elif ctypes.is_cstring_type(attrtype): myaddress = utils.get_pointee_address(attr.ptr) if attrname in self.expectedValues: # test if NULL is an option if not bool(myaddress): if not ((None in self.expectedValues[attrname]) or (0 in self.expectedValues[attrname])): log.debug('str: %s %s %s isNULL - NOT EXPECTED' % ( attrname, attrtype, repr(attr))) return False log.debug('str: %s %s %s isNULL - OK' % (attrname, attrtype, repr(attr))) # e.1) return True if (myaddress != 0 and not mappings.is_valid_address_value(myaddress)): log.debug('str: %s %s %s 0x%lx INVALID' % (attrname, attrtype, repr(attr), myaddress)) # e.2) return False log.debug('str: %s %s %s is at 0x%lx OK' % (attrname, attrtype, repr(attr), myaddress)) # e.3) return True # f) elif ctypes.is_pointer_type(attrtype): myaddress = utils.get_pointee_address(attr) if attrname in self.expectedValues: # test if NULL is an option log.debug('ctypes.is_pointer_type: bool(attr):%s attr:%s' % ( bool(attr), attr)) if not bool(myaddress): if not ((None in self.expectedValues[attrname]) or (0 in self.expectedValues[attrname])): log.debug('ptr: %s %s %s isNULL - NOT EXPECTED' % ( attrname, attrtype, repr(attr))) # f.1) expectedValues specifies NULL to be invalid return False log.debug('ptr: %s %s %s isNULL - OK' % (attrname, attrtype, repr(attr))) # f.2) expectedValues specifies NULL to be valid return True _attrType = None if (ctypes.is_pointer_to_void_type(attrtype) or ctypes.is_function_type(attrtype)): log.debug( 'Its a simple type. Checking mappings only. attr=%s' % (attr)) if (myaddress != 0 and not mappings.is_valid_address_value(myaddress)): log.debug('voidptr: %s %s %s 0x%lx INVALID simple pointer' % ( attrname, attrtype, repr(attr), myaddress)) # f.3) address must be valid, no type requirement return False else: # test valid address mapping _attrType = get_subtype(attrtype) if (myaddress != 0 and not mappings.is_valid_address(attr, _attrType)): log.debug('ptr: %s %s %s 0x%lx INVALID' % (attrname, attrtype, repr(attr), utils.get_pointee_address(attr))) # f.4) its a pointer, but not valid in our mappings for this # pointee type. return False log.debug('ptr: name:%s repr:%s address:0x%lx OK' % (attrname, repr(attr), utils.get_pointee_address(attr))) # f.5) null is accepted by default return True # g) log.error('What type are You ?: %s/%s' % (attrname, attrtype)) return True
def getCipher(self, mappings): cipher = mappings.getRef( Cipher, get_pointee_address(self.cipher)) return cipher
def BIGNUM_get_d(self): return self._mapping_.getRef(model.getSubtype(self.d), get_pointee_address(self.d))
def BIGNUM___str__(self): d = get_pointee_address(self.d) return ("BN { d=0x%lx, top=%d, dmax=%d, neg=%d, flags=%d }" % (d, self.top, self.dmax, self.neg, self.flags))
def getName(self, mappings): ciphername = mappings.getRef(ctypes.CString, get_pointee_address(self.name.ptr) ) if ciphername is None: raise ValueError('ciphername not in cache') return ciphername