├── vstruct2 ├── tests │ ├── __init__.py │ ├── test_compat.py │ ├── test_sizes.py │ └── test_types.py ├── __init__.py ├── compat.py ├── bases.py └── types.py ├── .gitignore ├── .travis.yml ├── .appveyor.yml ├── setup.py ├── README.rst └── LICENSE /vstruct2/tests/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.pyc 2 | *.swp 3 | *.py~ 4 | .hg* 5 | __pycache__ 6 | build 7 | dist 8 | vstruct2.egg-info 9 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | os: 2 | - linux 3 | language: python 4 | python: 5 | - "2.7" 6 | - "3.4" 7 | script: py.test 8 | -------------------------------------------------------------------------------- /vstruct2/__init__.py: -------------------------------------------------------------------------------- 1 | ''' 2 | Vivisect Structure Definition/Parsing Library 3 | 4 | https://github.com/vivisect/vstruct 5 | 6 | ''' 7 | version = (2,0,2) 8 | -------------------------------------------------------------------------------- /.appveyor.yml: -------------------------------------------------------------------------------- 1 | environment: 2 | 3 | matrix: 4 | 5 | - PYTHON: "C:\\Python27" 6 | 7 | - PYTHON: "C:\\Python27-x64" 8 | 9 | - PYTHON: "C:\\Python34" 10 | 11 | - PYTHON: "C:\\Python34-x64" 12 | 13 | install: 14 | # Check that we have the expected version and architecture for Python 15 | - "%PYTHON%\\python.exe --version" 16 | - "%PYTHON%\\python.exe -c \"import struct; print(struct.calcsize('P') * 8)\"" 17 | 18 | # Upgrade to the latest version of pip to avoid it displaying warnings 19 | # about it being out of date. 20 | - "%PYTHON%\\Scripts\\pip.exe install --disable-pip-version-check --user --upgrade pip" 21 | - "%PYTHON%\\Scripts\\pip.exe install --upgrade setuptools" 22 | 23 | # for now... should somehow be managed via setup.py 24 | - "%PYTHON%\\Scripts\\pip.exe install msgpack-python" 25 | 26 | # no msbuild phase in the project 27 | build: off 28 | 29 | test_script: 30 | # Run the project tests 31 | - "%CMD_IN_ENV% %PYTHON%\\python.exe -m unittest discover -v --fail" 32 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | #from distutils.core import setup 3 | 4 | import io 5 | import os 6 | 7 | from setuptools import setup,find_packages 8 | 9 | # For Testing: 10 | # 11 | # python3.4 setup.py register -r https://testpypi.python.org/pypi 12 | # python3.4 setup.py bdist_wheel upload -r https://testpypi.python.org/pypi 13 | # python3.4 -m pip install -i https://testpypi.python.org/pypi 14 | # 15 | # For Realz: 16 | # 17 | # python3.4 setup.py register 18 | # python3.4 setup.py bdist_wheel upload 19 | # python3.4 -m pip install 20 | 21 | here = os.path.dirname(__file__) 22 | 23 | def read(fname, encoding='utf-8'): 24 | with io.open(os.path.join(here, fname), encoding=encoding) as f: 25 | return f.read() 26 | 27 | setup( 28 | name='vstruct2', 29 | version='2.0.2', 30 | description='Vivisect Structure Definition/Parsing Library', 31 | long_description=read('README.rst'), 32 | author='Invisigoth Kenshoto', 33 | author_email='visi@vertex.link', 34 | url='https://github.com/vivisect/vstruct2', 35 | license='Apache License 2.0', 36 | 37 | packages=find_packages(exclude=['*.tests','*.tests.*']), 38 | 39 | classifiers=[ 40 | 'Development Status :: 5 - Production/Stable', 41 | 'License :: OSI Approved :: Apache Software License', 42 | 'Programming Language :: Python :: 3.4', 43 | 'Programming Language :: Python :: 2.7', 44 | ], 45 | 46 | ) 47 | -------------------------------------------------------------------------------- /README.rst: -------------------------------------------------------------------------------- 1 | vstruct2 ( Mark II ) 2 | =================== 3 | 4 | Vivisect Structure Definition/Parsing Library 5 | |Build Status| 6 | 7 | Installing 8 | ========== 9 | 10 | .. code:: 11 | 12 | python3.4 -m pip install vstruct2 13 | 14 | vstruct2 can now be installed via pip! 15 | 16 | Additionally, a repository of existing structure definitions 17 | is available as a seperate package named fracture. 18 | 19 | Examples 20 | ======== 21 | 22 | Basic Parsing 23 | ------------- 24 | 25 | Simple vstruct2 byte parsing: 26 | 27 | .. code:: python 28 | 29 | from vstruct2.types import * 30 | 31 | class Foo(VStruct): 32 | 33 | def __init__(self): 34 | VStruct.__init__(self) 35 | self.bar = uint32() 36 | self.baz = vbytes(20) 37 | 38 | 39 | foo = Foo() 40 | 41 | # read in byts from somewhere... 42 | foo.vsParse(byts) 43 | 44 | # access struct fields by name 45 | if foo.bar == 30: 46 | print('bar == 30!') 47 | 48 | # assign fields by name 49 | foo.bar = 90 50 | 51 | # emit modified bytes back out 52 | byts = bytes(foo) # same as foo.vsEmit() 53 | 54 | Parser Callbacks 55 | ---------------- 56 | 57 | WriteBack Bytes/Files 58 | --------------------- 59 | 60 | vstruct2 supports "writeback" functionality for both files and mutable 61 | bytearray types, allowing field assignments to change the underlying file 62 | or bytearray immediately. 63 | 64 | .. code:: python 65 | 66 | class Foo(VStruct): 67 | 68 | def __init__(self): 69 | VStruct.__init__(self) 70 | self.bar = uint32() 71 | self.baz = uint32() 72 | 73 | 74 | foo = Foo() 75 | 76 | # ba is a bytearray 77 | foo.vsParse(ba, writeback=True) 78 | 79 | # if bar is 30, set baz to 99 80 | if foo.bar == 30: 81 | foo.baz = 99 82 | 83 | # ba bytearray has now been modified 84 | 85 | Enum Types 86 | ---------- 87 | 88 | .. |Build Status| image:: https://travis-ci.org/vivisect/vstruct2.svg 89 | :target: https://travis-ci.org/vivisect/vstruct2 90 | -------------------------------------------------------------------------------- /vstruct2/tests/test_compat.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | 3 | from vstruct2.compat import * 4 | 5 | class CompatTest(unittest.TestCase): 6 | 7 | def test_compat_b2i(self): 8 | 9 | self.assertEqual( bytes2int(b'\xff\x56', 2, byteorder='big'), 0xff56 ) 10 | self.assertEqual( bytes2int(b'\xff\x56', 2, byteorder='little'), 0x56ff ) 11 | 12 | self.assertEqual( bytes2int(b'\xff\xff\xff\xff', 4, signed=True), -1 ) 13 | self.assertEqual( bytes2int(b'\xff\xff\xff\xff', 4, signed=False), 0xffffffff ) 14 | 15 | self.assertEqual( bytes2int(b'\xff\xff\xff\xff\xff\xff\xff\xff', 8, signed=True), -1 ) 16 | self.assertEqual( bytes2int(b'\xff\xff\xff\xff\xff\xff\xff\xff', 8, signed=False), 0xffffffffffffffff ) 17 | 18 | bytes16 = b'\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff' 19 | self.assertEqual( bytes2int(bytes16, 16, signed=True), -1 ) 20 | self.assertEqual( bytes2int(bytes16, 16, byteorder='big', signed=True), -1 ) 21 | self.assertEqual( bytes2int(bytes16, 16), 0xffffffffffffffffffffffffffffffff ) 22 | 23 | bytes16_2 = b'\xff\xff\xff\xff\xff\xff\xff\xff\xf0\xf0\xf0\xf0\xf0\xf0\xf0\xf0' 24 | self.assertEqual( bytes2int(bytes16_2, 16, byteorder='big'), 0xfffffffffffffffff0f0f0f0f0f0f0f0 ) 25 | self.assertEqual( bytes2int(bytes16_2, 16), 0xf0f0f0f0f0f0f0f0ffffffffffffffff ) 26 | 27 | def test_compat_i2b(self): 28 | 29 | self.assertEqual( int2bytes(-1, 2, signed=True), b'\xff\xff' ) 30 | 31 | self.assertEqual( int2bytes(0x102030, 3, byteorder='big'), b'\x10\x20\x30') 32 | self.assertEqual( int2bytes(0x102030, 3, byteorder='little'), b'\x30\x20\x10') 33 | 34 | self.assertEqual( int2bytes(0x0080, 2, byteorder='big'), b'\x00\x80' ) 35 | self.assertEqual( int2bytes(0x0080, 2, byteorder='little'), b'\x80\x00' ) 36 | 37 | self.assertEqual( int2bytes(0x008000800080008000800080, 12, byteorder='big'), b'\x00\x80\x00\x80\x00\x80\x00\x80\x00\x80\x00\x80' ) 38 | self.assertEqual( int2bytes(0x008000800080008000800080, 12, byteorder='little'), b'\x80\x00\x80\x00\x80\x00\x80\x00\x80\x00\x80\x00' ) 39 | -------------------------------------------------------------------------------- /vstruct2/tests/test_sizes.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | 3 | from vstruct2.bases import * 4 | from vstruct2.types import * 5 | 6 | class SizesTest(unittest.TestCase): 7 | 8 | def test_int8(self): 9 | i = int8() 10 | self.assertEqual(len(i.vsEmit()), 1) 11 | i.vsParse(b"\x00" * 8) 12 | self.assertEqual(len(i.vsEmit()), 1) 13 | 14 | def test_int16(self): 15 | i = int16() 16 | self.assertEqual(len(i.vsEmit()), 2) 17 | i.vsParse(b"\x00" * 8) 18 | self.assertEqual(len(i.vsEmit()), 2) 19 | 20 | def test_int32(self): 21 | i = int32() 22 | self.assertEqual(len(i.vsEmit()), 4) 23 | i.vsParse(b"\x00" * 8) 24 | self.assertEqual(len(i.vsEmit()), 4) 25 | 26 | def test_int64(self): 27 | i = int64() 28 | self.assertEqual(len(i.vsEmit()), 8) 29 | i.vsParse(b"\x00" * 8) 30 | self.assertEqual(len(i.vsEmit()), 8) 31 | 32 | def test_vbytes(self): 33 | i = vbytes(size=4) 34 | self.assertEqual(len(i.vsEmit()), 4) 35 | i.vsParse(b"\x00" * 8) 36 | self.assertEqual(len(i.vsEmit()), 4) 37 | 38 | def test_cstr(self): 39 | i = cstr(size=4) 40 | self.assertEqual(len(i.vsEmit()), 4) 41 | i.vsParse(b"\x00" * 8) 42 | self.assertEqual(len(i.vsEmit()), 4) 43 | 44 | def test_zstr(self): 45 | # zstr automatic resizing is documented, 46 | # so we'll allow it to not emit `size` bytes 47 | i = zstr(size=4) 48 | self.assertEqual(len(i.vsEmit()), 1) 49 | i.vsParse(b"\x00" * 8) 50 | self.assertEqual(len(i.vsEmit()), 1) 51 | 52 | def test_varray_int8(self): 53 | i = varray(4, int8)() 54 | self.assertEqual(len(i.vsEmit()), 4) 55 | i.vsParse(b"\x00" * 8) 56 | self.assertEqual(len(i.vsEmit()), 4) 57 | 58 | def test_varray_vbytes(self): 59 | i = VArray(fields=[vbytes(size=1) for _ in range(4)]) 60 | self.assertEqual(len(i.vsEmit()), 4) 61 | i.vsParse(b"\x00" * 8) 62 | self.assertEqual(len(i.vsEmit()), 4) 63 | 64 | 65 | def test(): 66 | try: 67 | unittest.main() 68 | except SystemExit: 69 | pass 70 | 71 | 72 | if __name__ == '__main__': 73 | test() 74 | -------------------------------------------------------------------------------- /vstruct2/compat.py: -------------------------------------------------------------------------------- 1 | ''' 2 | A place to isolate the py27 compat filth. 3 | ''' 4 | import sys 5 | 6 | major = sys.version_info.major 7 | minor = sys.version_info.minor 8 | micro = sys.version_info.micro 9 | 10 | version = (major,minor,micro) 11 | 12 | if version < (3,0,0): 13 | 14 | import struct 15 | fmtchars = { 16 | (1,'big',False):'B', 17 | (1,'little',False):'B', 18 | 19 | (2,'big',False):'>H', 20 | (2,'little',False):'I', 23 | (4,'little',False):'Q', 26 | (8,'little',False):'h', 32 | (2,'little',True):'i', 35 | (4,'little',True):'q', 38 | (8,'little',True):'> 1 for i in range(MAX_WORD+1) ] 50 | sign_bits[0] = 0 # powers of 0 are 1, but we need 0 51 | 52 | def unsigned(value, size): 53 | ''' Stolen from vivisect/envi/bits.py to keep vstruct dependency free. ''' 54 | return value & u_maxes[size] 55 | 56 | def signed(value, size): 57 | ''' Stolen from vivisect/envi/bits.py to keep vstruct dependency free. ''' 58 | x = unsigned(value, size) 59 | if x & sign_bits[size]: 60 | x = (x - u_maxes[size]) - 1 61 | return x 62 | 63 | def slowparsebytes(byts, offset, size, sign=False, byteorder='little'): 64 | ''' Stolen from vivisect/envi/bits.py to keep vstruct dependency free. ''' 65 | if byteorder == 'big': 66 | begin = offset 67 | inc = 1 68 | else: 69 | begin = offset + (size-1) 70 | inc = -1 71 | 72 | ret = 0 73 | ioff = 0 74 | for x in range(size): 75 | ret = ret << 8 76 | ret |= ord(byts[begin+ioff]) 77 | ioff += inc 78 | if sign: 79 | ret = signed(ret, size) 80 | return ret 81 | 82 | def bytes2int(byts, size, off=0, byteorder='little', signed=False): 83 | """ 84 | Mostly for pulling immediates out of strings... 85 | """ 86 | if size > 8: 87 | return slowparsebytes(byts, off, size, sign=signed, byteorder=byteorder) 88 | 89 | fmt = fmtchars.get( (size,byteorder,signed) ) 90 | if fmt != None: 91 | return struct.unpack(fmt, byts[off:off+size])[0] 92 | 93 | valu = 0 94 | vals = [ ord(c) for c in byts[off:off+size] ] 95 | 96 | if byteorder == 'little': 97 | vals.reverse() 98 | 99 | for i in range(size): 100 | valu <<= 8 101 | valu += vals[i] 102 | 103 | return valu 104 | 105 | def int2bytes(valu, size, byteorder='little', signed=False): 106 | fmt = fmtchars.get( (size,byteorder,signed) ) 107 | if fmt != None: 108 | return struct.pack(fmt, valu) 109 | 110 | ords = [] 111 | for i in range(size): 112 | ords.append( (valu >> (8*i) ) & 0xff ) 113 | 114 | if byteorder == 'big': 115 | ords.reverse() 116 | 117 | return ''.join([ chr(o) for o in ords ]) 118 | 119 | else: 120 | 121 | def int2bytes(valu, size, byteorder='little', signed=False): 122 | return valu.to_bytes(size, byteorder=byteorder, signed=signed) 123 | 124 | def bytes2int(byts, size, byteorder='little', signed=False, off=0): 125 | return int.from_bytes(byts[off:off+size], byteorder=byteorder, signed=signed) 126 | -------------------------------------------------------------------------------- /vstruct2/tests/test_types.py: -------------------------------------------------------------------------------- 1 | import io 2 | import unittest 3 | 4 | from ..types import * 5 | 6 | class S1(VStruct): 7 | 8 | def __init__(self): 9 | VStruct.__init__(self) 10 | 11 | self.i8 = int8() 12 | self.i16 = int16() 13 | self.i32 = int32() 14 | self.i64 = int64() 15 | 16 | self.u8 = uint8() 17 | self.u16 = uint16() 18 | self.u32 = uint32() 19 | self.u64 = uint64() 20 | 21 | self.by6 = vbytes(6) 22 | self.cs6 = cstr(6) 23 | self.zs = zstr() 24 | 25 | s1bytes = b'\xff' * 36 + b'qwerty' + b'asdf\x00\x00\x00\x00' 26 | 27 | class S2(VStruct): 28 | 29 | def __init__(self): 30 | VStruct.__init__(self) 31 | self.size = uint16().vsOnset( self.setBytesSize ) 32 | self.string = cstr() 33 | 34 | def setBytesSize(self): 35 | self['string'].vsResize( self.size ) 36 | 37 | class S3(VStruct): 38 | def __init__(self): 39 | VStruct.__init__(self) 40 | self.x = uint8() 41 | self.y = vbytes(6) 42 | 43 | class S4(VStruct): 44 | def __init__(self): 45 | VStruct.__init__(self) 46 | self.w = uint8() 47 | self.x = vbytes(3) 48 | self.y = zstr() 49 | self.z = uint16() 50 | 51 | class TypesTest(unittest.TestCase): 52 | 53 | def test_vstruct_basic(self): 54 | s1 = S1() 55 | s1.vsParse(s1bytes) 56 | 57 | self.assertEqual( s1.i8, -1 ) 58 | self.assertEqual( s1.i16, -1 ) 59 | self.assertEqual( s1.i32, -1 ) 60 | self.assertEqual( s1.i64, -1 ) 61 | 62 | self.assertEqual( s1.u8, 0xff) 63 | self.assertEqual( s1.u16, 0xffff) 64 | self.assertEqual( s1.u32, 0xffffffff) 65 | self.assertEqual( s1.u64, 0xffffffffffffffff) 66 | 67 | self.assertEqual( s1.by6, b'\xff' * 6 ) 68 | self.assertEqual( s1.cs6, 'qwerty') 69 | self.assertEqual( s1.zs, 'asdf') 70 | 71 | self.assertEqual( len(s1['zs']), 5 ) 72 | 73 | def test_vstruct_resize_onparse(self): 74 | 75 | s2 = S2() 76 | s2.vsParse(b'\x04\x00ASDFGGGGGGGGGGGGG') 77 | 78 | self.assertEqual( s2.size, 4 ) 79 | self.assertEqual( s2.string, 'ASDF' ) 80 | 81 | def test_vstruct_resize_onset(self): 82 | 83 | s2 = S2() 84 | self.assertEqual( s2.vsEmit(), b'\x00\x00') 85 | 86 | s2.size = 3 87 | self.assertEqual( s2.vsEmit(), b'\x03\x00\x00\x00\x00') 88 | 89 | def test_vstruct_lazy(self): 90 | 91 | s1 = S1() 92 | s1.vsParse(s1bytes) 93 | 94 | self.assertIsNone( s1['i8']._vs_value ) 95 | self.assertIsNone( s1['u8']._vs_value ) 96 | self.assertIsNone( s1['by6']._vs_value ) 97 | self.assertIsNone( s1['cs6']._vs_value ) 98 | 99 | def test_vstruct_writeback(self): 100 | 101 | buf = bytearray(b'\x00' * 8) 102 | 103 | s3 = S3() 104 | s3.vsParse( buf, writeback=True ) 105 | 106 | s3.x = 20 107 | self.assertEqual( bytes(buf), b'\x14' + b'\x00' * 7) 108 | 109 | def test_vstruct_load(self): 110 | s4 = S4() 111 | 112 | fd = io.BytesIO(b'\xffQQQabcd\x00VV') 113 | off = s4.vsLoad(fd, writeback=True) 114 | 115 | self.assertEqual(s4.w, 0xff) 116 | self.assertEqual(s4.x, b'QQQ') 117 | self.assertEqual(s4.y, 'abcd') 118 | self.assertEqual(s4.z, 0x5656) 119 | 120 | self.assertEqual(off,11) 121 | 122 | s4.x = b'RRR' 123 | fd.seek(0) 124 | 125 | self.assertEqual( fd.read(), b'\xffRRRabcd\x00VV') 126 | 127 | def test_vstruct_enum(self): 128 | 129 | foo = venum() 130 | foo.one = 1 131 | foo.two = 2 132 | 133 | class Bar(VStruct): 134 | def __init__(self): 135 | VStruct.__init__(self) 136 | self.baz = uint16(enum=foo) 137 | 138 | bar = Bar() 139 | bar.baz = 2 140 | 141 | self.assertEqual( repr(bar['baz']), 'two') 142 | 143 | self.assertEqual( foo.one, 1 ) 144 | self.assertEqual( foo.two, 2 ) 145 | 146 | self.assertEqual( foo[1], 'one') 147 | self.assertEqual( foo[2], 'two') 148 | self.assertEqual( foo['one'], 1 ) 149 | self.assertEqual( foo['two'], 2 ) 150 | 151 | def test_vstruct_endian(self): 152 | 153 | class Foo(VStruct): 154 | def __init__(self): 155 | VStruct.__init__(self) 156 | self._vs_endian = 'big' 157 | self.bar = uint16() 158 | self.baz = uint32() 159 | 160 | foo = Foo() 161 | foo.vsParse(b'\x01\x02\x03\x04\x05\x06') 162 | self.assertEqual( foo.bar, 0x0102 ) 163 | self.assertEqual( foo.baz, 0x03040506 ) 164 | -------------------------------------------------------------------------------- /vstruct2/bases.py: -------------------------------------------------------------------------------- 1 | import traceback 2 | from vstruct2.compat import int2bytes, bytes2int 3 | 4 | # This routine was coppied from vivisect to allow vstruct 5 | # to be free from dependencies 6 | MAX_WORD = 16 7 | def initmask(bits): 8 | return (1< 'TYPE_2' 457 | # foo['TYPE_2'] -> 2 458 | 459 | Notes: 460 | 461 | * provide as enum param to int ctors for auto-repr 462 | 463 | ''' 464 | def __init__(self): 465 | object.__setattr__(self, '_vs_enum_map', {}) 466 | 467 | def __setattr__(self, name, valu): 468 | self._vs_enum_map[valu] = name 469 | self._vs_enum_map[name] = valu 470 | return object.__setattr__(self, name, valu) 471 | 472 | def __getitem__(self, item): 473 | return self._vs_enum_map.get(item) 474 | --------------------------------------------------------------------------------