import struct from copy import deepcopy from inspect import isclass import vstruct.primitives as vs_prims class MemObjFile: """ A file like object that wraps a MemoryObject (envi) compatable object with a file-like object where seek == VA. """ def __init__(self, memobj, baseaddr): self.baseaddr = baseaddr self.offset = baseaddr self.memobj = memobj def seek(self, offset): self.offset = self.baseaddr + offset def flush(self): pass def tell(self): return self.offset - self.baseaddr def read(self, size=None): if size is None: # end of map for now, but perhaps this should be end of contiguous maps _, size, _, _ = self.memobj.getMemoryMap(self.offset) size -= self.offset ret = self.memobj.readMemory(self.offset, size) self.offset += size return ret def write(self, bytes): self.memobj.writeMemory(self.offset, bytes) self.offset += len(bytes) def isVstructType(x): return isinstance(x, vs_prims.v_base) class VStruct(vs_prims.v_base): ''' The VStruct class is the bases for all groups of primitive fields which define a "structure". Fields may be added with vsAddField() or simply added as attributes (provided you use a VStruct or one of the vstruct.primitives in the initial assignment.) Example: import vstruct from vstruct.primitives import * vs = vstruct.VStruct() vs.fieldone = v_uint32() vs.fieldtwo = v_str(size=30) bytes = vs.vsEmit() ''' def __init__(self, bigend=False): # A tiny bit of evil... object.__setattr__(self, '_vs_values', {}) vs_prims.v_base.__init__(self, bigend) self._vs_name = self.__class__.__name__ self._vs_fields = [] self._vs_field_align = False # To toggle visual studio style packing self._vs_padnum = 0 self._vs_pcallbacks = {} self._vs_fastfields = None def __mul__(self, x): # build a list of instances of this vstruct return [ deepcopy(self) for i in range(x) ] def vsAddParseCallback(self, fieldname, callback): ''' Register a callback which will be triggered when the field with the given name is set by the parser. This can be used to simplify auto-parsing to change fields sizes or whatnot during parsing. (You may also name a method pcb_ to get a callback for your struct.) Example: def updateLengthTarget(vs): dostuff() v.vsAddParseCallback('lenfield', updateLengthTarget) ''' if self._vs_values.get(fieldname) is None: raise Exception('Invalid Field: %s' % fieldname) cblist = self._vs_pcallbacks.get(fieldname) if cblist is None: cblist = [] self._vs_pcallbacks[fieldname] = cblist cblist.append(callback) def vsGetClassPath(self): ''' Return the entire class name (including module path). ''' return '%s.%s' % (self.__module__, self._vs_name) def _vsFireCallbacks(self, fname): callback = getattr(self, 'pcb_%s' % fname, None) if callback is not None: callback() cblist = self._vs_pcallbacks.get(fname) if cblist is not None: for callback in cblist: callback(self) def vsSetEndian(self, bigend): self._vs_bigend = bigend for fldname, vsfield in list(self._vs_values.items()): vsfield.vsSetEndian(bigend) @classmethod def vsFromFd(cls, fd, fast=True): v = cls() v.vsParseFd(fd,fast=fast) return v def vsParseFd(self, fd, fast=False): ''' Parse from the given file like object as input. ''' if fast: b = fd.read( len(self) ) self.vsParse(b,fast=True) return for fname in self._vs_fields: fobj = self._vs_values.get(fname) fobj.vsParseFd(fd) self._vsFireCallbacks(fname) def _vsInitFastFields(self): fields = self.vsGetFastParseFields() self._vs_fastfields = fields fmt = ''.join([ f._vs_fmt for f in fields ]) endian = '<' if fmt.find('>') != -1: endian = '>' # Strip all in-band endian specifiers fmt = fmt.replace('<','') fmt = fmt.replace('>','') self._vs_fastfmt = endian + fmt self._vs_fastlen = struct.calcsize( self._vs_fastfmt ) def vsParse(self, sbytes, offset=0, fast=False): """ For all the primitives contained within, allow them an opportunity to parse the given data and return the total offset... Any method named pcb_ will be called back when the specified field is set by the parser. the "fast" option enables fastparse which will *not* call any callbacks can may not be compatible with some structure defs. ( eg mixed endian ) """ if fast: if self._vs_fastfields is None: self._vsInitFastFields() values = struct.unpack_from( self._vs_fastfmt, sbytes, offset ) # Ephemeral list comprehension for speed [ self._vs_fastfields[i].vsSetValue( values[i] ) for i in range(len(values)) ] return offset + self._vs_fastlen # In order for callbacks to change fields, we can't use vsGetFields() for fname in self._vs_fields: fobj = self._vs_values.get(fname) offset = fobj.vsParse(sbytes, offset=offset) self._vsFireCallbacks(fname) return offset def vsGetFastParseFields(self): fields = [] for fname in self._vs_fields: fobj = self._vs_values.get(fname) if fobj.vsIsPrim(): fields.append( fobj ) continue fields.extend( fobj.vsGetFastParseFields() ) return fields def vsEmit(self, fast=False): """ Get back the byte sequence associated with this structure. """ if fast: if self._vs_fastfields is None: self._vsInitFastFields() ffvals = [ ff.vsGetValue() for ff in self._vs_fastfields ] return struct.pack(self._vs_fastfmt, *ffvals) ret = b'' for fname, fobj in self.vsGetFields(): ret += fobj.vsEmit() return ret def vsCalculate(self): ''' Calculate fields which need correction before emitting bytes etc... (VStruct extenders may call this, then modify fields internally) ''' for fname, fobj in self.vsGetFields(): fobj.vsCalculate() def vsIsPrim(self): return False def vsGetFields(self): ''' Get a list of (fieldname, fieldobj) tuples for all the kids in this VStruct (non-recursive) Example: for kidname, kidobj in x.vsGetFields(): print(kidname) ''' # This yield generator allows field list changes # during iteration... i = 0 while i < len(self._vs_fields): fname = self._vs_fields[i] fobj = self._vs_values.get(fname) yield fname,fobj i += 1 def vsGetField(self, name): x = self._vs_values.get(name) if x is None: raise Exception("Invalid field: %s" % name) return x def vsHasField(self, name): ''' Test whether this structure contains a field with the given name.... Example: if x.vsHasField('woot'): print('STRUCT HAS WOOT FIELD!') ''' return self._vs_values.get(name) is not None def vsSetField(self, name, value): ''' Mostly for internal use... ''' if isVstructType(value): self._vs_values[name] = value return x = self._vs_values.get(name) return x.vsSetValue(value) # FIXME implement more arithmetic for structs... def __ixor__(self, other): for name,value in other._vs_values.items(): self._vs_values[name] ^= value return self def vsClearFields(self): ''' Clear all fields from the current vstruct object. This may be useful in specialized parsers which populate their structure on vsParse() ''' self.__init__() def vsGetFirstPrim(self): fname = self._vs_fields[0] field = self._vs_values.get(fname) if not field.vsIsPrim(): return field.vsGetFirstPrim() return field def vsAddField(self, name, value): if not isVstructType(value): raise Exception('Added fields MUST be vstruct types!') # Do optional field alignment... if self._vs_field_align: # If it's a primitive, all is well, if not, pad to size of # the first element of the VStruct/VArray... if value.vsIsPrim(): align = value._vs_align if align is None: align = len(value) else: field = value.vsGetFirstPrim() align = field._vs_align if align is None: align = len(field) delta = len(self) % align if delta != 0: pname = "_pad%d" % self._vs_padnum self._vs_padnum += 1 self._vs_fields.append(pname) self._vs_values[pname] = vs_prims.v_bytes(align-delta) self._vs_fields.append(name) self._vs_values[name] = value def vsDelField(self, name): ''' Remove a field from the VStruct definition ''' field = self._vs_values.pop(name,None) if field is None: raise Exception('Invalid Field Name: %s' % name) self._vs_fields.remove(name) def vsInsertField(self, name, value, befname): ''' WARNING: vsInsertField does NOT honor field alignment! # FIXME (AND CAN MESS UP OTHER FIELDS ALIGNMENT!) ''' if not isVstructType(value): raise Exception("Added fields MUST be vstruct types!") idx = self._vs_fields.index(befname) self._vs_fields.insert(idx, name) self._vs_values[name] = value def vsGetPrims(self): """ return an order'd list of the primitive fields in this structure definition. This is recursive and will return the sub fields of all nested structures. """ ret = [] for name, field in self.vsGetFields(): if field.vsIsPrim(): ret.append(field) else: ret.extend(field.vsGetPrims()) return ret def vsGetTypeName(self): return self._vs_name def vsGetOffset(self, name, offset=0): """ Return the offset of a member (by name). This is recursive and will descend into nested structures to return the offset of a sub field. """ nameparts = name.split('.') namedepth = len(nameparts) - 1 depth = 0 for fname,field in self.vsGetFields(): if nameparts[depth] == fname: if depth == namedepth: return offset depth += 1 return field.vsGetOffset('.'.join(nameparts[depth:]), offset=offset) offset += len(field) raise Exception("Invalid Field Specified!") def vsGetFieldByOffset(self, offset, names=None, coffset=0): ''' Return a tuple of (name, field) for the field at the specified offset. ''' nparts = names if nparts is None: nparts = [] off = coffset for fname, field in self.vsGetFields(): flen = len(field) if offset < off or offset >= off + flen: off += flen continue nparts.append(fname) if isinstance(field, VStruct): return field.vsGetFieldByOffset(offset, names=nparts, coffset=off) break if len(nparts) == 0: raise Exception('Invalid Offset Specified!') return '.'.join(nparts), field def vsGetPrintInfo(self, offset=0, indent=0, top=True): ret = [] if top: ret.append((offset, indent, self._vs_name, self)) off = offset indent += 1 for fname in self._vs_fields: x = self._vs_values.get(fname) #off = offset + self.vsGetOffset(fname) if isinstance(x, VStruct): ret.append((off, indent, fname, x)) ret.extend(x.vsGetPrintInfo(offset=off, indent=indent, top=False)) else: ret.append((off, indent, fname, x)) off += len(x) # returns (offset, indent, fieldname, field_instance) return ret def __len__(self): ret = 0 for fname, fobj in self.vsGetFields(): ret += len(fobj) return ret def __getattr__(self, name): # Gotta do this for pickle issues... vsvals = self.__dict__.get("_vs_values") if vsvals is None: vsvals = {} self.__dict__["_vs_values"] = vsvals r = vsvals.get(name) if r is None: raise AttributeError(name) if isinstance(r, vs_prims.v_prim): return r.vsGetValue() return r def __setattr__(self, name, value): # If we have this field, asign to it x = self._vs_values.get(name, None) if x is not None: return self.vsSetField(name, value) # If it's a vstruct type, create a new field if isVstructType(value): return self.vsAddField(name, value) # Fail over to standard object attribute behavior return object.__setattr__(self, name, value) def __iter__(self): # Our iteration returns name,field pairs ret = [ (name, self._vs_values.get(name)) for name in self._vs_fields ] return iter(ret) def __repr__(self): return self._vs_name def __getitem__(self, name): return self.vsGetField(name) def __setitem__(self, name, valu): return self.vsSetField(name,valu) def tree(self, va=0, reprmax=None): ret = "" for off, indent, name, field in self.vsGetPrintInfo(): rstr = repr(field) #field.vsGetTypeName() if isinstance(field, vs_prims.v_number): if field.vsGetEnum() is not None: rstr = '%s (0x%.8x)' % (str(field), field.vsGetValue()) else: val = field.vsGetValue() rstr = '0x%.8x (%d)' % (val,val) elif isinstance(field, vs_prims.v_prim): rstr = repr(field) if reprmax is not None and len(rstr) > reprmax: rstr = rstr[:reprmax] + '...' ret += "%.8x (%.2d)%s %s: %s\n" % (va+off, len(field), " "*(indent*2), name, rstr) return ret class VArray(VStruct): def __init__(self, elems=()): VStruct.__init__(self) for e in elems: self.vsAddElement(e) def vsAddElement(self, elem): """ Used to add elements to an array """ idx = len(self._vs_fields) self.vsAddField("%d" % idx, elem) def vsAddElements(self, count, eclass): for i in range( count ): self.vsAddElement( eclass() ) def __getitem__(self, index): return self.vsGetField("%d" % index) #FIXME slice asignment class VUnion(VStruct): def vsEmit(self): raise Exception('VUnion is only for parse right now!') def vsParse(self, sbytes, offset=0): """ For all the primitives contained within, allow them an opportunity to parse the given data and return the total offset... Any method named pcb_ will be called back when the specified field is set by the parser. """ ret = offset for fname,fobj in self.vsGetFields(): ret = max(offset, fobj.vsParse(sbytes, offset=offset)) callback = getattr(self, 'pcb_%s' % fname, None) if callback is not None: callback() cblist = self._vs_pcallbacks.get(fname) if cblist is not None: for callback in cblist: callback(self) return ret def __len__(self): ret = 0 for fname, fobj in self.vsGetFields(): ret = max(ret, len(fobj)) return ret def vsGetPrintInfo(self, offset=0, indent=0, top=True): ret = [] if top: ret.append((offset, indent, self._vs_name, self)) indent += 1 for fname in self._vs_fields: x = self._vs_values.get(fname) if isinstance(x, VStruct): ret.append((offset, indent, fname, x)) ret.extend(x.vsGetPrintInfo(offset=offset, indent=indent, top=False)) else: ret.append((offset, indent, fname, x)) return ret def resolve(impmod, nameparts): """ Resolve the given (potentially nested) object from within a module. """ if not nameparts: return None m = impmod for nname in nameparts: m = getattr(m, nname, None) if m is None: break return m def resolvepath(impmod, pathstr): ''' Resolve an object/module from within the given module by path name (ie. 'foo.bar.baz') Example: x = resolvepath(vstruct.defs, 'win32.SEH_SCOPETABLE') ''' nameparts = pathstr.split('.') return resolve(impmod, nameparts) # NOTE: Gotta import this *after* VStruct/VSArray defined import vstruct.defs as vs_defs def getStructure(sname): """ Return an instance of the specified structure. The structure name may be a definition that was added with addStructure() or a python path (ie. win32.TEB) of a definition from within vstruct.defs. """ x = resolve(vs_defs, sname.split(".")) if x is not None: return x() return None def getModuleNames(): return [x for x in dir(vs_defs) if not x.startswith("__")] def getStructNames(modname): ret = [] mod = resolve(vs_defs, modname) if mod is None: return ret for n in dir(mod): x = getattr(mod, n) if isclass(x) and issubclass(x, VStruct): ret.append(n) return ret