123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743 |
- # Copyright (c) 2003-2016 CORE Security Technologies
- #
- # This software is provided under under a slightly modified version
- # of the Apache Software License. See the accompanying LICENSE file
- # for more information.
- #
- from struct import pack, unpack, calcsize
- class Structure:
- """ sublcasses can define commonHdr and/or structure.
- each of them is an tuple of either two: (fieldName, format) or three: (fieldName, ':', class) fields.
- [it can't be a dictionary, because order is important]
-
- where format specifies how the data in the field will be converted to/from bytes (string)
- class is the class to use when unpacking ':' fields.
- each field can only contain one value (or an array of values for *)
- i.e. struct.pack('Hl',1,2) is valid, but format specifier 'Hl' is not (you must use 2 dfferent fields)
- format specifiers:
- specifiers from module pack can be used with the same format
- see struct.__doc__ (pack/unpack is finally called)
- x [padding byte]
- c [character]
- b [signed byte]
- B [unsigned byte]
- h [signed short]
- H [unsigned short]
- l [signed long]
- L [unsigned long]
- i [signed integer]
- I [unsigned integer]
- q [signed long long (quad)]
- Q [unsigned long long (quad)]
- s [string (array of chars), must be preceded with length in format specifier, padded with zeros]
- p [pascal string (includes byte count), must be preceded with length in format specifier, padded with zeros]
- f [float]
- d [double]
- = [native byte ordering, size and alignment]
- @ [native byte ordering, standard size and alignment]
- ! [network byte ordering]
- < [little endian]
- > [big endian]
- usual printf like specifiers can be used (if started with %)
- [not recommeneded, there is no why to unpack this]
- %08x will output an 8 bytes hex
- %s will output a string
- %s\\x00 will output a NUL terminated string
- %d%d will output 2 decimal digits (against the very same specification of Structure)
- ...
- some additional format specifiers:
- : just copy the bytes from the field into the output string (input may be string, other structure, or anything responding to __str__()) (for unpacking, all what's left is returned)
- z same as :, but adds a NUL byte at the end (asciiz) (for unpacking the first NUL byte is used as terminator) [asciiz string]
- u same as z, but adds two NUL bytes at the end (after padding to an even size with NULs). (same for unpacking) [unicode string]
- w DCE-RPC/NDR string (it's a macro for [ '<L=(len(field)+1)/2','"\\x00\\x00\\x00\\x00','<L=(len(field)+1)/2',':' ]
- ?-field length of field named 'field', formated as specified with ? ('?' may be '!H' for example). The input value overrides the real length
- ?1*?2 array of elements. Each formated as '?2', the number of elements in the array is stored as specified by '?1' (?1 is optional, or can also be a constant (number), for unpacking)
- 'xxxx literal xxxx (field's value doesn't change the output. quotes must not be closed or escaped)
- "xxxx literal xxxx (field's value doesn't change the output. quotes must not be closed or escaped)
- _ will not pack the field. Accepts a third argument, which is an unpack code. See _Test_UnpackCode for an example
- ?=packcode will evaluate packcode in the context of the structure, and pack the result as specified by ?. Unpacking is made plain
- ?&fieldname "Address of field fieldname".
- For packing it will simply pack the id() of fieldname. Or use 0 if fieldname doesn't exists.
- For unpacking, it's used to know weather fieldname has to be unpacked or not, i.e. by adding a & field you turn another field (fieldname) in an optional field.
-
- """
- commonHdr = ()
- structure = ()
- debug = 0
- def __init__(self, data = None, alignment = 0):
- if not hasattr(self, 'alignment'):
- self.alignment = alignment
- self.fields = {}
- self.rawData = data
- if data is not None:
- self.fromString(data)
- else:
- self.data = None
- @classmethod
- def fromFile(self, file):
- answer = self()
- answer.fromString(file.read(len(answer)))
- return answer
- def setAlignment(self, alignment):
- self.alignment = alignment
- def setData(self, data):
- self.data = data
- def packField(self, fieldName, format = None):
- if self.debug:
- print "packField( %s | %s )" % (fieldName, format)
- if format is None:
- format = self.formatForField(fieldName)
- if self.fields.has_key(fieldName):
- ans = self.pack(format, self.fields[fieldName], field = fieldName)
- else:
- ans = self.pack(format, None, field = fieldName)
- if self.debug:
- print "\tanswer %r" % ans
- return ans
- def getData(self):
- if self.data is not None:
- return self.data
- data = ''
- for field in self.commonHdr+self.structure:
- try:
- data += self.packField(field[0], field[1])
- except Exception, e:
- if self.fields.has_key(field[0]):
- e.args += ("When packing field '%s | %s | %r' in %s" % (field[0], field[1], self[field[0]], self.__class__),)
- else:
- e.args += ("When packing field '%s | %s' in %s" % (field[0], field[1], self.__class__),)
- raise
- if self.alignment:
- if len(data) % self.alignment:
- data += ('\x00'*self.alignment)[:-(len(data) % self.alignment)]
-
- #if len(data) % self.alignment: data += ('\x00'*self.alignment)[:-(len(data) % self.alignment)]
- return data
- def fromString(self, data):
- self.rawData = data
- for field in self.commonHdr+self.structure:
- if self.debug:
- print "fromString( %s | %s | %r )" % (field[0], field[1], data)
- size = self.calcUnpackSize(field[1], data, field[0])
- if self.debug:
- print " size = %d" % size
- dataClassOrCode = str
- if len(field) > 2:
- dataClassOrCode = field[2]
- try:
- self[field[0]] = self.unpack(field[1], data[:size], dataClassOrCode = dataClassOrCode, field = field[0])
- except Exception,e:
- e.args += ("When unpacking field '%s | %s | %r[:%d]'" % (field[0], field[1], data, size),)
- raise
- size = self.calcPackSize(field[1], self[field[0]], field[0])
- if self.alignment and size % self.alignment:
- size += self.alignment - (size % self.alignment)
- data = data[size:]
- return self
-
- def __setitem__(self, key, value):
- self.fields[key] = value
- self.data = None # force recompute
- def __getitem__(self, key):
- return self.fields[key]
- def __delitem__(self, key):
- del self.fields[key]
-
- def __str__(self):
- return self.getData()
- def __len__(self):
- # XXX: improve
- return len(self.getData())
- def pack(self, format, data, field = None):
- if self.debug:
- print " pack( %s | %r | %s)" % (format, data, field)
- if field:
- addressField = self.findAddressFieldFor(field)
- if (addressField is not None) and (data is None):
- return ''
- # void specifier
- if format[:1] == '_':
- return ''
- # quote specifier
- if format[:1] == "'" or format[:1] == '"':
- return format[1:]
- # code specifier
- two = format.split('=')
- if len(two) >= 2:
- try:
- return self.pack(two[0], data)
- except:
- fields = {'self':self}
- fields.update(self.fields)
- return self.pack(two[0], eval(two[1], {}, fields))
- # address specifier
- two = format.split('&')
- if len(two) == 2:
- try:
- return self.pack(two[0], data)
- except:
- if (self.fields.has_key(two[1])) and (self[two[1]] is not None):
- return self.pack(two[0], id(self[two[1]]) & ((1<<(calcsize(two[0])*8))-1) )
- else:
- return self.pack(two[0], 0)
- # length specifier
- two = format.split('-')
- if len(two) == 2:
- try:
- return self.pack(two[0],data)
- except:
- return self.pack(two[0], self.calcPackFieldSize(two[1]))
- # array specifier
- two = format.split('*')
- if len(two) == 2:
- answer = ''
- for each in data:
- answer += self.pack(two[1], each)
- if two[0]:
- if two[0].isdigit():
- if int(two[0]) != len(data):
- raise Exception, "Array field has a constant size, and it doesn't match the actual value"
- else:
- return self.pack(two[0], len(data))+answer
- return answer
- # "printf" string specifier
- if format[:1] == '%':
- # format string like specifier
- return format % data
- # asciiz specifier
- if format[:1] == 'z':
- return str(data)+'\0'
- # unicode specifier
- if format[:1] == 'u':
- return str(data)+'\0\0' + (len(data) & 1 and '\0' or '')
- # DCE-RPC/NDR string specifier
- if format[:1] == 'w':
- if len(data) == 0:
- data = '\0\0'
- elif len(data) % 2:
- data += '\0'
- l = pack('<L', len(data)/2)
- return '%s\0\0\0\0%s%s' % (l,l,data)
-
- if data is None:
- raise Exception, "Trying to pack None"
-
- # literal specifier
- if format[:1] == ':':
- return str(data)
- # struct like specifier
- return pack(format, data)
- def unpack(self, format, data, dataClassOrCode = str, field = None):
- if self.debug:
- print " unpack( %s | %r )" % (format, data)
- if field:
- addressField = self.findAddressFieldFor(field)
- if addressField is not None:
- if not self[addressField]:
- return
- # void specifier
- if format[:1] == '_':
- if dataClassOrCode != str:
- fields = {'self':self, 'inputDataLeft':data}
- fields.update(self.fields)
- return eval(dataClassOrCode, {}, fields)
- else:
- return None
- # quote specifier
- if format[:1] == "'" or format[:1] == '"':
- answer = format[1:]
- if answer != data:
- raise Exception, "Unpacked data doesn't match constant value '%r' should be '%r'" % (data, answer)
- return answer
- # address specifier
- two = format.split('&')
- if len(two) == 2:
- return self.unpack(two[0],data)
- # code specifier
- two = format.split('=')
- if len(two) >= 2:
- return self.unpack(two[0],data)
- # length specifier
- two = format.split('-')
- if len(two) == 2:
- return self.unpack(two[0],data)
- # array specifier
- two = format.split('*')
- if len(two) == 2:
- answer = []
- sofar = 0
- if two[0].isdigit():
- number = int(two[0])
- elif two[0]:
- sofar += self.calcUnpackSize(two[0], data)
- number = self.unpack(two[0], data[:sofar])
- else:
- number = -1
- while number and sofar < len(data):
- nsofar = sofar + self.calcUnpackSize(two[1],data[sofar:])
- answer.append(self.unpack(two[1], data[sofar:nsofar], dataClassOrCode))
- number -= 1
- sofar = nsofar
- return answer
- # "printf" string specifier
- if format[:1] == '%':
- # format string like specifier
- return format % data
- # asciiz specifier
- if format == 'z':
- if data[-1] != '\x00':
- raise Exception, ("%s 'z' field is not NUL terminated: %r" % (field, data))
- return data[:-1] # remove trailing NUL
- # unicode specifier
- if format == 'u':
- if data[-2:] != '\x00\x00':
- raise Exception, ("%s 'u' field is not NUL-NUL terminated: %r" % (field, data))
- return data[:-2] # remove trailing NUL
- # DCE-RPC/NDR string specifier
- if format == 'w':
- l = unpack('<L', data[:4])[0]
- return data[12:12+l*2]
- # literal specifier
- if format == ':':
- return dataClassOrCode(data)
- # struct like specifier
- return unpack(format, data)[0]
- def calcPackSize(self, format, data, field = None):
- # # print " calcPackSize %s:%r" % (format, data)
- if field:
- addressField = self.findAddressFieldFor(field)
- if addressField is not None:
- if not self[addressField]:
- return 0
- # void specifier
- if format[:1] == '_':
- return 0
- # quote specifier
- if format[:1] == "'" or format[:1] == '"':
- return len(format)-1
- # address specifier
- two = format.split('&')
- if len(two) == 2:
- return self.calcPackSize(two[0], data)
- # code specifier
- two = format.split('=')
- if len(two) >= 2:
- return self.calcPackSize(two[0], data)
- # length specifier
- two = format.split('-')
- if len(two) == 2:
- return self.calcPackSize(two[0], data)
- # array specifier
- two = format.split('*')
- if len(two) == 2:
- answer = 0
- if two[0].isdigit():
- if int(two[0]) != len(data):
- raise Exception, "Array field has a constant size, and it doesn't match the actual value"
- elif two[0]:
- answer += self.calcPackSize(two[0], len(data))
- for each in data:
- answer += self.calcPackSize(two[1], each)
- return answer
- # "printf" string specifier
- if format[:1] == '%':
- # format string like specifier
- return len(format % data)
- # asciiz specifier
- if format[:1] == 'z':
- return len(data)+1
- # asciiz specifier
- if format[:1] == 'u':
- l = len(data)
- return l + (l & 1 and 3 or 2)
- # DCE-RPC/NDR string specifier
- if format[:1] == 'w':
- l = len(data)
- return 12+l+l % 2
- # literal specifier
- if format[:1] == ':':
- return len(data)
- # struct like specifier
- return calcsize(format)
- def calcUnpackSize(self, format, data, field = None):
- if self.debug:
- print " calcUnpackSize( %s | %s | %r)" % (field, format, data)
- # void specifier
- if format[:1] == '_':
- return 0
- addressField = self.findAddressFieldFor(field)
- if addressField is not None:
- if not self[addressField]:
- return 0
- try:
- lengthField = self.findLengthFieldFor(field)
- return self[lengthField]
- except:
- pass
- # XXX: Try to match to actual values, raise if no match
-
- # quote specifier
- if format[:1] == "'" or format[:1] == '"':
- return len(format)-1
- # address specifier
- two = format.split('&')
- if len(two) == 2:
- return self.calcUnpackSize(two[0], data)
- # code specifier
- two = format.split('=')
- if len(two) >= 2:
- return self.calcUnpackSize(two[0], data)
- # length specifier
- two = format.split('-')
- if len(two) == 2:
- return self.calcUnpackSize(two[0], data)
- # array specifier
- two = format.split('*')
- if len(two) == 2:
- answer = 0
- if two[0]:
- if two[0].isdigit():
- number = int(two[0])
- else:
- answer += self.calcUnpackSize(two[0], data)
- number = self.unpack(two[0], data[:answer])
- while number:
- number -= 1
- answer += self.calcUnpackSize(two[1], data[answer:])
- else:
- while answer < len(data):
- answer += self.calcUnpackSize(two[1], data[answer:])
- return answer
- # "printf" string specifier
- if format[:1] == '%':
- raise Exception, "Can't guess the size of a printf like specifier for unpacking"
- # asciiz specifier
- if format[:1] == 'z':
- return data.index('\x00')+1
- # asciiz specifier
- if format[:1] == 'u':
- l = data.index('\x00\x00')
- return l + (l & 1 and 3 or 2)
- # DCE-RPC/NDR string specifier
- if format[:1] == 'w':
- l = unpack('<L', data[:4])[0]
- return 12+l*2
- # literal specifier
- if format[:1] == ':':
- return len(data)
- # struct like specifier
- return calcsize(format)
- def calcPackFieldSize(self, fieldName, format = None):
- if format is None:
- format = self.formatForField(fieldName)
- return self.calcPackSize(format, self[fieldName])
- def formatForField(self, fieldName):
- for field in self.commonHdr+self.structure:
- if field[0] == fieldName:
- return field[1]
- raise Exception, ("Field %s not found" % fieldName)
- def findAddressFieldFor(self, fieldName):
- descriptor = '&%s' % fieldName
- l = len(descriptor)
- for field in self.commonHdr+self.structure:
- if field[1][-l:] == descriptor:
- return field[0]
- return None
-
- def findLengthFieldFor(self, fieldName):
- descriptor = '-%s' % fieldName
- l = len(descriptor)
- for field in self.commonHdr+self.structure:
- if field[1][-l:] == descriptor:
- return field[0]
- return None
-
- def zeroValue(self, format):
- two = format.split('*')
- if len(two) == 2:
- if two[0].isdigit():
- return (self.zeroValue(two[1]),)*int(two[0])
-
- if not format.find('*') == -1: return ()
- if 's' in format: return ''
- if format in ['z',':','u']: return ''
- if format == 'w': return '\x00\x00'
- return 0
- def clear(self):
- for field in self.commonHdr + self.structure:
- self[field[0]] = self.zeroValue(field[1])
- def dump(self, msg = None, indent = 0):
- if msg is None: msg = self.__class__.__name__
- ind = ' '*indent
- print "\n%s" % msg
- fixedFields = []
- for field in self.commonHdr+self.structure:
- i = field[0]
- if i in self.fields:
- fixedFields.append(i)
- if isinstance(self[i], Structure):
- self[i].dump('%s%s:{' % (ind,i), indent = indent + 4)
- print "%s}" % ind
- else:
- print "%s%s: {%r}" % (ind,i,self[i])
- # Do we have remaining fields not defined in the structures? let's
- # print them
- remainingFields = list(set(self.fields) - set(fixedFields))
- for i in remainingFields:
- if isinstance(self[i], Structure):
- self[i].dump('%s%s:{' % (ind,i), indent = indent + 4)
- print "%s}" % ind
- else:
- print "%s%s: {%r}" % (ind,i,self[i])
- class _StructureTest:
- alignment = 0
- def create(self,data = None):
- if data is not None:
- return self.theClass(data, alignment = self.alignment)
- else:
- return self.theClass(alignment = self.alignment)
- def run(self):
- print
- print "-"*70
- testName = self.__class__.__name__
- print "starting test: %s....." % testName
- a = self.create()
- self.populate(a)
- a.dump("packing.....")
- a_str = str(a)
- print "packed: %r" % a_str
- print "unpacking....."
- b = self.create(a_str)
- b.dump("unpacked.....")
- print "repacking....."
- b_str = str(b)
- if b_str != a_str:
- print "ERROR: original packed and repacked don't match"
- print "packed: %r" % b_str
- class _Test_simple(_StructureTest):
- class theClass(Structure):
- commonHdr = ()
- structure = (
- ('int1', '!L'),
- ('len1','!L-z1'),
- ('arr1','B*<L'),
- ('z1', 'z'),
- ('u1','u'),
- ('', '"COCA'),
- ('len2','!H-:1'),
- ('', '"COCA'),
- (':1', ':'),
- ('int3','>L'),
- ('code1','>L=len(arr1)*2+0x1000'),
- )
- def populate(self, a):
- a['default'] = 'hola'
- a['int1'] = 0x3131
- a['int3'] = 0x45444342
- a['z1'] = 'hola'
- a['u1'] = 'hola'.encode('utf_16_le')
- a[':1'] = ':1234:'
- a['arr1'] = (0x12341234,0x88990077,0x41414141)
- # a['len1'] = 0x42424242
- class _Test_fixedLength(_Test_simple):
- def populate(self, a):
- _Test_simple.populate(self, a)
- a['len1'] = 0x42424242
- class _Test_simple_aligned4(_Test_simple):
- alignment = 4
- class _Test_nested(_StructureTest):
- class theClass(Structure):
- class _Inner(Structure):
- structure = (('data', 'z'),)
- structure = (
- ('nest1', ':', _Inner),
- ('nest2', ':', _Inner),
- ('int', '<L'),
- )
- def populate(self, a):
- a['nest1'] = _Test_nested.theClass._Inner()
- a['nest2'] = _Test_nested.theClass._Inner()
- a['nest1']['data'] = 'hola manola'
- a['nest2']['data'] = 'chau loco'
- a['int'] = 0x12345678
-
- class _Test_Optional(_StructureTest):
- class theClass(Structure):
- structure = (
- ('pName','<L&Name'),
- ('pList','<L&List'),
- ('Name','w'),
- ('List','<H*<L'),
- )
-
- def populate(self, a):
- a['Name'] = 'Optional test'
- a['List'] = (1,2,3,4)
-
- class _Test_Optional_sparse(_Test_Optional):
- def populate(self, a):
- _Test_Optional.populate(self, a)
- del a['Name']
- class _Test_AsciiZArray(_StructureTest):
- class theClass(Structure):
- structure = (
- ('head','<L'),
- ('array','B*z'),
- ('tail','<L'),
- )
- def populate(self, a):
- a['head'] = 0x1234
- a['tail'] = 0xabcd
- a['array'] = ('hola','manola','te traje')
-
- class _Test_UnpackCode(_StructureTest):
- class theClass(Structure):
- structure = (
- ('leni','<L=len(uno)*2'),
- ('cuchi','_-uno','leni/2'),
- ('uno',':'),
- ('dos',':'),
- )
- def populate(self, a):
- a['uno'] = 'soy un loco!'
- a['dos'] = 'que haces fiera'
- class _Test_AAA(_StructureTest):
- class theClass(Structure):
- commonHdr = ()
- structure = (
- ('iv', '!L=((init_vector & 0xFFFFFF) << 8) | ((pad & 0x3f) << 2) | (keyid & 3)'),
- ('init_vector', '_','(iv >> 8)'),
- ('pad', '_','((iv >>2) & 0x3F)'),
- ('keyid', '_','( iv & 0x03 )'),
- ('dataLen', '_-data', 'len(inputDataLeft)-4'),
- ('data',':'),
- ('icv','>L'),
- )
- def populate(self, a):
- a['init_vector']=0x01020304
- #a['pad']=int('01010101',2)
- a['pad']=int('010101',2)
- a['keyid']=0x07
- a['data']="\xA0\xA1\xA2\xA3\xA4\xA5\xA6\xA7\xA8\xA9"
- a['icv'] = 0x05060708
- #a['iv'] = 0x01020304
-
- if __name__ == '__main__':
- _Test_simple().run()
- try:
- _Test_fixedLength().run()
- except:
- print "cannot repack because length is bogus"
- _Test_simple_aligned4().run()
- _Test_nested().run()
- _Test_Optional().run()
- _Test_Optional_sparse().run()
- _Test_AsciiZArray().run()
- _Test_UnpackCode().run()
- _Test_AAA().run()
|