diff --git a/oletools/oleform.py b/oletools/oleform.py index 245a102..99f9059 100644 --- a/oletools/oleform.py +++ b/oletools/oleform.py @@ -24,6 +24,11 @@ class Mask(object): def __getitem__(self, key): return self._val[self._names.index(key)] + def consume(self, stream, props): + for (name, size) in props: + if self[name]: + stream.read(size) + class FormPropMask(Mask): """FormPropMask: [MS-OFORMS] 2.2.10.2""" _size = 28 @@ -52,12 +57,60 @@ class MorphDataPropMask(Mask): 'fBorderColor', 'fSpecialEffect', 'fMouseIcon', 'fPicture', 'fAccelerator', 'UnusedBits2', 'Reserved', 'fGroupName'] +class ImagePropMask(Mask): + """ImagePropMask: [MS-OFORMS] 2.2.3.2""" + _size = 15 + _names = ['UnusedBits1_1', 'UnusedBits1_2', 'fAutoSize', 'fBorderColor', 'fBackColor', + 'fBorderStyle', 'fMousePointer', 'fPictureSizeMode', 'fSpecialEffect', 'fSize', + 'fPicture', 'fPictureAlignment', 'fPictureTiling', 'fVariousPropertyBits', + 'fMouseIcon'] + +class CommandButtonPropMask(Mask): + """CommandButtonPropMask: [MS-OFORMS] 2.2.1.2""" + _size = 11 + _names = ['fForeColor', 'fBackColor', 'fVariousPropertyBits', 'fCaption', 'fPicturePosition', + 'fSize', 'fMousePointer', 'fPicture', 'fAccelerator', 'fTakeFocusOnClick', + 'fMouseIcon'] + +class SpinButtonPropMask(Mask): + """SpinButtonPropMask: [MS-OFORMS] 2.2.8.2""" + _size = 15 + _names = ['fForeColor', 'fBackColor', 'fVariousPropertyBits', 'fSize', 'UnusedBits1', + 'fMin', 'fMax', 'fPosition', 'fPrevEnabled', 'fNextEnabled', 'fSmallChange', + 'fOrientation', 'fDelay', 'fMouseIcon', 'fMousePointer'] + +class TabStripPropMask(Mask): + """TabStripPropMask: [MS-OFORMS] 2.2.9.2""" + _size = 25 + _names = ['fListIndex', 'fBackColor', 'fForeColor', 'Unused1', 'fSize', 'fItems', + 'fMousePointer', 'Unused2', 'fTabOrientation', 'fTabStyle', 'fMultiRow', + 'fTabFixedWidth', 'fTabFixedHeight', 'fTooltips', 'Unused3', 'fTipStrings', + 'Unused4', 'fNames', 'fVariousPropertyBits', 'fNewVersion', 'fTabsAllocated', + 'fTags', 'fTabData', 'fAccelerator', 'fMouseIcon'] + +class LabelPropMask(Mask): + """LabelPropMask: [MS-OFORMS] 2.2.4.2""" + _size = 13 + _names = ['fForeColor', 'fBackColor', 'fVariousPropertyBits', 'fCaption', + 'fPicturePosition', 'fSize', 'fMousePointer', 'fBorderColor', 'fBorderStyle', + 'fSpecialEffect', 'fPicture', 'fAccelerator', 'fMouseIcon'] + +class ScrollBarPropMask(Mask): + """ScrollBarPropMask: [MS-OFORMS] 2.2.7.2""" + _size = 17 + _names = ['fForeColor', 'fBackColor', 'fVariousPropertyBits', 'fSize', 'fMousePointer', + 'fMin', 'fMax', 'fPosition', 'UnusedBits1', 'fPrevEnabled', 'fNextEnabled', + 'fSmallChange', 'fLargeChange', 'fOrientation', 'fProportionalThumb', + 'fDelay', 'fMouseIcon'] + class ExtendedStream(object): def __init__(self, stream, path): self._pos = 0 self._jumps = [] self._stream = stream self._path = path + self._padding = False + self._pad_start = 0 @classmethod def open(cls, ole_file, path): @@ -68,31 +121,55 @@ class ExtendedStream(object): # print('declared size: %d' % ole_file.get_size(path)) return cls(stream, path) - def read(self, size): + def _read(self, size): self._pos += size return self._stream.read(size) + def _pad(self, start, size=4): + offset = (self._pos - start) % size + if offset: + self._read(size - offset) + + def read(self, size): + if self._padding: + self._pad(self._pad_start, size) + return self._read(size) + def will_jump_to(self, size): - self._next_jump = (True, size) + self._next_jump = ('jump', (self._pos, size)) + return self + + def will_pad(self): + self._next_jump = ('pad', self._pos) return self - def will_pad(self, pad=4): - self._next_jump = (False, pad) + def padded_struct(self): + self._next_jump = ('padded', (self._padding, self._pad_start)) + self._padding = True + self._pad_start = self._pos return self def __enter__(self): - (jump_type, size) = self._next_jump - self._jumps.append((self._pos, jump_type, size)) + assert(self._next_jump) + self._jumps.append(self._next_jump) + self._next_jump = None def __exit__(self, exc_type, exc_value, traceback): if exc_type is None: - (start, jump_type, size) = self._jumps.pop() - if jump_type: - self.read(size - (self._pos - start)) - else: - align = (self._pos - start) % size - if align: - self.read(size - align) + (jump_type, data) = self._jumps.pop() + if jump_type == 'jump': + (start, size) = data + consummed = self._pos - start + if consummed > size: + self.raise_error('Bad jump: too much read ({0} > {1})'.format(consummed, size)) + self.read(size - consummed) + elif jump_type == 'pad': + self._pad(data) + elif jump_type == 'padded': + (prev_padding, prev_pad_start) = data + self._pad(self._pad_start) + self._padding = prev_padding + self._pad_start = prev_pad_start def unpacks(self, format, size): return struct.unpack(format, self.read(size)) @@ -148,8 +225,6 @@ def consume_GuidAndPicture(stream): def consume_CountOfBytesWithCompressionFlag(stream): # CountOfBytesWithCompressionFlag or CountOfCharsWithCompressionFlag: [MS-OFORMS] 2.4.14.2 or 2.4.14.3 count = stream.unpack('> 15 @@ -233,11 +300,12 @@ def consume_FormControl(stream): consume_SiteClassInfo(stream) (CountOfSites, CountOfBytes) = stream.unpacks(' 0: - remaining_SiteDepthsAndTypes -= consume_FormObjectDepthTypeCount(stream) - for i in range(CountOfSites): - yield consume_OleSiteConcreteControl(stream) + with stream.will_jump_to(CountOfBytes): + with stream.will_pad(): + while remaining_SiteDepthsAndTypes > 0: + remaining_SiteDepthsAndTypes -= consume_FormObjectDepthTypeCount(stream) + for i in range(CountOfSites): + yield consume_OleSiteConcreteControl(stream) def consume_MorphDataControl(stream): # MorphDataControl: [MS-OFORMS] 2.2.5.1 @@ -246,36 +314,25 @@ def consume_MorphDataControl(stream): with stream.will_jump_to(cbMorphData): propmask = MorphDataPropMask(stream.unpack(' 2: + # Unfortunately, olevba3 doesn't have extract_form_strings_extended + return + for sample, expected_result in SAMPLES: + full_name = join(DATA_BASE_DIR, 'oleform', sample) + parser = VBA_Parser(full_name) + variables = list(parser.extract_form_strings_extended()) + self.assertEqual(variables, expected_result) + + +# just in case somebody calls this file as a script +if __name__ == '__main__': + unittest.main() + diff --git a/tests/test-data/oleform/oleform-PR314.docm b/tests/test-data/oleform/oleform-PR314.docm new file mode 100755 index 0000000..bb2c0aa --- /dev/null +++ b/tests/test-data/oleform/oleform-PR314.docm