Commit c5d4ec7b870d46c31949ef29dc91e634b8896ba7
1 parent
dbd2a780
WIP: better parsing for /o
Showing
1 changed file
with
120 additions
and
76 deletions
oletools/oleform.py
| @@ -7,36 +7,60 @@ class OleFormParsingError(Exception): | @@ -7,36 +7,60 @@ class OleFormParsingError(Exception): | ||
| 7 | 7 | ||
| 8 | class Mask(object): | 8 | class Mask(object): |
| 9 | def __init__(self, val): | 9 | def __init__(self, val): |
| 10 | - self._val = [(val & (1<<i))>>i for i in range(32)] | 10 | + self._val = [(val & (1<<i))>>i for i in range(self._size)] |
| 11 | 11 | ||
| 12 | def __str__(self): | 12 | def __str__(self): |
| 13 | - return ', '.join(self._names[i] for i in range(32) if self._val[i]) | 13 | + return ', '.join(self._names[i] for i in range(self._size) if self._val[i]) |
| 14 | 14 | ||
| 15 | def __getattr__(self, name): | 15 | def __getattr__(self, name): |
| 16 | return self._val[self._names.index(name)] | 16 | return self._val[self._names.index(name)] |
| 17 | 17 | ||
| 18 | -class PropMask(Mask): | 18 | + def __len__(self): |
| 19 | + return self.size | ||
| 20 | + | ||
| 21 | + def __getitem__(self, key): | ||
| 22 | + return self._val[self._names.index(key)] | ||
| 23 | + | ||
| 24 | +class FormPropMask(Mask): | ||
| 25 | + """FormPropMask: [MS-OFORMS] 2.2.10.2""" | ||
| 26 | + _size = 28 | ||
| 19 | _names = ['Unused1', 'fBackColor', 'fForeColor', 'fNextAvailableID', 'Unused2_0', 'Unused2_1', | 27 | _names = ['Unused1', 'fBackColor', 'fForeColor', 'fNextAvailableID', 'Unused2_0', 'Unused2_1', |
| 20 | 'fBooleanProperties', 'fBooleanProperties', 'fMousePointer', 'fScrollBars', | 28 | 'fBooleanProperties', 'fBooleanProperties', 'fMousePointer', 'fScrollBars', |
| 21 | 'fDisplayedSize', 'fLogicalSize', 'fScrollPosition', 'fGroupCnt', 'Reserved', | 29 | 'fDisplayedSize', 'fLogicalSize', 'fScrollPosition', 'fGroupCnt', 'Reserved', |
| 22 | 'fMouseIcon', 'fCycle', 'fSpecialEffect', 'fBorderColor', 'fCaption', 'fFont', | 30 | 'fMouseIcon', 'fCycle', 'fSpecialEffect', 'fBorderColor', 'fCaption', 'fFont', |
| 23 | 'fPicture', 'fZoom', 'fPictureAlignment', 'fPictureTiling', 'fPictureSizeMode', | 31 | 'fPicture', 'fZoom', 'fPictureAlignment', 'fPictureTiling', 'fPictureSizeMode', |
| 24 | - 'fShapeCookie', 'fDrawBuffer', 'Unused3_0', 'Unused3_1', 'Unused3_2', 'Unused3_3'] | 32 | + 'fShapeCookie', 'fDrawBuffer'] |
| 25 | 33 | ||
| 26 | class SitePropMask(Mask): | 34 | class SitePropMask(Mask): |
| 35 | + """SitePropMask: [MS-OFORMS] 2.2.10.12.2""" | ||
| 36 | + _size = 15 | ||
| 27 | _names = ['fName', 'fTag', 'fID', 'fHelpContextID', 'fBitFlags', 'fObjectStreamSize', | 37 | _names = ['fName', 'fTag', 'fID', 'fHelpContextID', 'fBitFlags', 'fObjectStreamSize', |
| 28 | 'fTabIndex', 'fClsidCacheIndex', 'fPosition', 'fGroupID', 'Unused1', | 38 | 'fTabIndex', 'fClsidCacheIndex', 'fPosition', 'fGroupID', 'Unused1', |
| 29 | - 'fControlTipText', 'fRuntimeLicKey', 'fControlSource', 'fRowSource', 'Unused2_0', | ||
| 30 | - 'Unused2_1', 'Unused2_2', 'Unused2_3', 'Unused2_4', 'Unused2_5', 'Unused2_6', | ||
| 31 | - 'Unused2_7', 'Unused2_8', 'Unused2_9', 'Unused2_10', 'Unused2_11', 'Unused2_12', | ||
| 32 | - 'Unused2_13', 'Unused2_14', 'Unused2_15', 'Unused2_16'] | 39 | + 'fControlTipText', 'fRuntimeLicKey', 'fControlSource', 'fRowSource'] |
| 40 | + | ||
| 41 | +class MorphDataPropMask(Mask): | ||
| 42 | + """MorphDataPropMask: [MS-OFORMS] 2.2.5.2""" | ||
| 43 | + _size = 33 | ||
| 44 | + _names = ['fVariousPropertyBits', 'fBackColor', 'fForeColor', 'fMaxLength', 'fBorderStyle', | ||
| 45 | + 'fScrollBars', 'fDisplayStyle', 'fMousePointer', 'fSize', 'fPasswordChar', | ||
| 46 | + 'fListWidth', 'fBoundColumn', 'fTextColumn', 'fColumnCount', 'fListRows', | ||
| 47 | + 'fcColumnInfo', 'fMatchEntry', 'fListStyle', 'fShowDropButtonWhen', 'UnusedBits1', | ||
| 48 | + 'fDropButtonStyle', 'fMultiSelect', 'fValue', 'fCaption', 'fPicturePosition', | ||
| 49 | + 'fBorderColor', 'fSpecialEffect', 'fMouseIcon', 'fPicture', 'fAccelerator', | ||
| 50 | + 'UnusedBits2', 'Reserved', 'fGroupName'] | ||
| 33 | 51 | ||
| 34 | class OleUserFormParser(object): | 52 | class OleUserFormParser(object): |
| 35 | - def __init__(self, stream): | ||
| 36 | - self.content = [] | ||
| 37 | - self._stream = stream | 53 | + def __init__(self, control_stream, data_stream): |
| 54 | + self.variables = [] | ||
| 55 | + self.set_stream(control_stream) | ||
| 56 | + self.consume_FormControl() | ||
| 57 | + self.set_stream(data_stream) | ||
| 58 | + self.consume_stored_data() | ||
| 59 | + | ||
| 60 | + def set_stream(self, stream): | ||
| 38 | self._pos = 0 | 61 | self._pos = 0 |
| 39 | self._frozen_pos = [] | 62 | self._frozen_pos = [] |
| 63 | + self._stream = stream | ||
| 40 | 64 | ||
| 41 | def read(self, size): | 65 | def read(self, size): |
| 42 | self._pos += size | 66 | self._pos += size |
| @@ -67,6 +91,12 @@ class OleUserFormParser(object): | @@ -67,6 +91,12 @@ class OleUserFormParser(object): | ||
| 67 | def check_value(self, name, format, size, expected): | 91 | def check_value(self, name, format, size, expected): |
| 68 | self.check_values(name, format, size, (expected,)) | 92 | self.check_values(name, format, size, (expected,)) |
| 69 | 93 | ||
| 94 | + def consume_TextProps(self): | ||
| 95 | + # TextProps: [MS-OFORMS] 2.3.1 | ||
| 96 | + self.check_values('TextProps (versions)', '<BB', 2, (0, 2)) | ||
| 97 | + cbTextProps = self.unpack('<H', 2) | ||
| 98 | + self.read(cbTextProps) | ||
| 99 | + | ||
| 70 | def consume_GuidAndFont(self): | 100 | def consume_GuidAndFont(self): |
| 71 | # GuidAndFont: [MS-OFORMS] 2.4.7 | 101 | # GuidAndFont: [MS-OFORMS] 2.4.7 |
| 72 | UUIDS = self.unpacks('<LHH', 8) + self.unpacks('>Q', 8) | 102 | UUIDS = self.unpacks('<LHH', 8) + self.unpacks('>Q', 8) |
| @@ -80,10 +110,7 @@ class OleUserFormParser(object): | @@ -80,10 +110,7 @@ class OleUserFormParser(object): | ||
| 80 | self.read(bFaceLen) | 110 | self.read(bFaceLen) |
| 81 | elif UUIDs == (2948729120, 55886, 4558, 13349514450607572916L): | 111 | elif UUIDs == (2948729120, 55886, 4558, 13349514450607572916L): |
| 82 | # UUID == {AFC20920-DA4E-11CE-B94300AA006887B4} | 112 | # UUID == {AFC20920-DA4E-11CE-B94300AA006887B4} |
| 83 | - # TextProps: [MS-OFORMS] 2.3.1 | ||
| 84 | - self.check_value('TextProps (versions)', '<BB', 2, (0, 2)) | ||
| 85 | - cbTextProps = self.unpack('<H', 2) | ||
| 86 | - self.read(cbTextProps) | 113 | + self.consume_TextProps() |
| 87 | else: | 114 | else: |
| 88 | raise OleFormParsingError('Invalid GuidAndFont at {0}: UUID'.format(self._pos - 16)) | 115 | raise OleFormParsingError('Invalid GuidAndFont at {0}: UUID'.format(self._pos - 16)) |
| 89 | 116 | ||
| @@ -101,7 +128,6 @@ class OleUserFormParser(object): | @@ -101,7 +128,6 @@ class OleUserFormParser(object): | ||
| 101 | # CountOfBytesWithCompressionFlag or CountOfCharsWithCompressionFlag: [MS-OFORMS] 2.4.14.2 or 2.4.14.3 | 128 | # CountOfBytesWithCompressionFlag or CountOfCharsWithCompressionFlag: [MS-OFORMS] 2.4.14.2 or 2.4.14.3 |
| 102 | count = self.unpack('<L', 4) | 129 | count = self.unpack('<L', 4) |
| 103 | if not count & 0x80000000 and count != 0: | 130 | if not count & 0x80000000 and count != 0: |
| 104 | - print(count) | ||
| 105 | raise OleFormParsingError('Uncompress string length at {0}', self._pos - 4) | 131 | raise OleFormParsingError('Uncompress string length at {0}', self._pos - 4) |
| 106 | return count & 0x7FFFFFFF | 132 | return count & 0x7FFFFFFF |
| 107 | 133 | ||
| @@ -126,45 +152,37 @@ class OleUserFormParser(object): | @@ -126,45 +152,37 @@ class OleUserFormParser(object): | ||
| 126 | self.check_value('OleSiteConcreteControl (version)', '<H', 2, 0) | 152 | self.check_value('OleSiteConcreteControl (version)', '<H', 2, 0) |
| 127 | cbSite = self.unpack('<H', 2) | 153 | cbSite = self.unpack('<H', 2) |
| 128 | self.freeze() | 154 | self.freeze() |
| 129 | - sitepropmask = SitePropMask(self.unpack('<L', 4)) | 155 | + propmask = SitePropMask(self.unpack('<L', 4)) |
| 130 | # SiteDataBlock: [MS-OFORMS] 2.2.10.12.3 | 156 | # SiteDataBlock: [MS-OFORMS] 2.2.10.12.3 |
| 131 | name_len = tag_len = id = 0 | 157 | name_len = tag_len = id = 0 |
| 132 | - if sitepropmask.fName: | 158 | + if propmask.fName: |
| 133 | name_len = self.consume_CountOfBytesWithCompressionFlag() | 159 | name_len = self.consume_CountOfBytesWithCompressionFlag() |
| 134 | - if sitepropmask.fTag: | 160 | + if propmask.fTag: |
| 135 | tag_len = self.consume_CountOfBytesWithCompressionFlag() | 161 | tag_len = self.consume_CountOfBytesWithCompressionFlag() |
| 136 | - if sitepropmask.fID: | 162 | + if propmask.fID: |
| 137 | id = self.unpack('<L', 4) | 163 | id = self.unpack('<L', 4) |
| 138 | - if sitepropmask.fHelpContextID: | ||
| 139 | - self.read(4) | ||
| 140 | - if sitepropmask.fBitFlags: | ||
| 141 | - self.read(4) | ||
| 142 | - if sitepropmask.fObjectStreamSize: | ||
| 143 | - self.read(4) | 164 | + for prop in ['fHelpContextID', 'fBitFlags', 'fObjectStreamSize']: |
| 165 | + if propmask[prop]: | ||
| 166 | + self.read(4) | ||
| 144 | tabindex = ClsidCacheIndex = 0 | 167 | tabindex = ClsidCacheIndex = 0 |
| 145 | self.freeze() | 168 | self.freeze() |
| 146 | - if sitepropmask.fTabIndex: | 169 | + if propmask.fTabIndex: |
| 147 | tabindex = self.unpack('<H', 2) | 170 | tabindex = self.unpack('<H', 2) |
| 148 | - if sitepropmask.fClsidCacheIndex: | 171 | + if propmask.fClsidCacheIndex: |
| 149 | ClsidCacheIndex = self.unpack('<H', 2) | 172 | ClsidCacheIndex = self.unpack('<H', 2) |
| 150 | - if sitepropmask.fGroupID: | 173 | + if propmask.fGroupID: |
| 151 | self.read(2) | 174 | self.read(2) |
| 152 | self.unfreeze_pad() | 175 | self.unfreeze_pad() |
| 153 | # For the next 4 entries, the documentation adds padding, but it should already be aligned?? | 176 | # For the next 4 entries, the documentation adds padding, but it should already be aligned?? |
| 154 | - if sitepropmask.fControlTipText: | ||
| 155 | - self.read(4) | ||
| 156 | - if sitepropmask.fRuntimeLicKey: | ||
| 157 | - self.read(4) | ||
| 158 | - if sitepropmask.fControlSource: | ||
| 159 | - self.read(4) | ||
| 160 | - if sitepropmask.fRowSource: | ||
| 161 | - self.read(4) | 177 | + for prop in ['fControlTipText', 'fRuntimeLicKey', 'fControlSource', 'fRowSource']: |
| 178 | + if propmask[prop]: | ||
| 179 | + self.read(4) | ||
| 162 | # SiteExtraDataBlock: [MS-OFORMS] 2.2.10.12.4 | 180 | # SiteExtraDataBlock: [MS-OFORMS] 2.2.10.12.4 |
| 163 | name = self.read(name_len) | 181 | name = self.read(name_len) |
| 164 | tag = self.read(tag_len) | 182 | tag = self.read(tag_len) |
| 165 | - self.content.append({'name': name, 'tag': tag, 'id': id, | ||
| 166 | - 'tabindex': tabindex, | ||
| 167 | - 'ClsidCacheIndex': ClsidCacheIndex}) | 183 | + self.variables.append({'name': name, 'tag': tag, 'id': id, |
| 184 | + 'tabindex': tabindex, | ||
| 185 | + 'ClsidCacheIndex': ClsidCacheIndex}) | ||
| 168 | self.unfreeze(cbSite) | 186 | self.unfreeze(cbSite) |
| 169 | 187 | ||
| 170 | def consume_FormControl(self): | 188 | def consume_FormControl(self): |
| @@ -172,14 +190,11 @@ class OleUserFormParser(object): | @@ -172,14 +190,11 @@ class OleUserFormParser(object): | ||
| 172 | self.check_values('FormControl (versions)', '<BB', 2, (0, 4)) | 190 | self.check_values('FormControl (versions)', '<BB', 2, (0, 4)) |
| 173 | cbform = self.unpack('<H', 2) | 191 | cbform = self.unpack('<H', 2) |
| 174 | self.freeze() | 192 | self.freeze() |
| 175 | - propmask = PropMask(self.unpack('<L', 4)) | 193 | + propmask = FormPropMask(self.unpack('<L', 4)) |
| 176 | # FormDataBlock: [MS-OFORMS] 2.2.10.3 | 194 | # FormDataBlock: [MS-OFORMS] 2.2.10.3 |
| 177 | - if propmask.fBackColor: | ||
| 178 | - self.read(4) | ||
| 179 | - if propmask.fForeColor: | ||
| 180 | - self.read(4) | ||
| 181 | - if propmask.fNextAvailableID: | ||
| 182 | - self.read(4) | 195 | + for prop in ['fBackColor', 'fForeColor', 'fNextAvailableID']: |
| 196 | + if propmask[prop]: | ||
| 197 | + self.read(4) | ||
| 183 | if propmask.fBooleanProperties: | 198 | if propmask.fBooleanProperties: |
| 184 | BooleanProperties = self.unpack('<L', 4) | 199 | BooleanProperties = self.unpack('<L', 4) |
| 185 | FORM_FLAG_DONTSAVECLASSTABLE = (BooleanProperties & (1<<15)) >> 15 | 200 | FORM_FLAG_DONTSAVECLASSTABLE = (BooleanProperties & (1<<15)) >> 15 |
| @@ -208,36 +223,65 @@ class OleUserFormParser(object): | @@ -208,36 +223,65 @@ class OleUserFormParser(object): | ||
| 208 | for i in range(CountOfSites): | 223 | for i in range(CountOfSites): |
| 209 | self.consume_OleSiteConcreteControl() | 224 | self.consume_OleSiteConcreteControl() |
| 210 | 225 | ||
| 211 | - def consume_stream_o(self): | ||
| 212 | - # Adapted from plugin_stream_o.py from Didier Stevens's oledump.py | ||
| 213 | - while(True): | ||
| 214 | - try: | ||
| 215 | - (code, length) = self.unpacks('<HH', 4) | ||
| 216 | - except struct.error: | ||
| 217 | - break | ||
| 218 | - self.freeze() | ||
| 219 | - if code == 0x200: | ||
| 220 | - fieldtype = self.unpack('<I', 4) | ||
| 221 | - if fieldtype == 0x80400101: | ||
| 222 | - self.read(8) | ||
| 223 | - lengthString = self.unpack('<I', 4) & 0x7FFFFFFF #self.consume_CountOfBytesWithCompressionFlag() | ||
| 224 | - self.read(8) | ||
| 225 | - self.content.append(self.read(lengthString)) | ||
| 226 | - elif fieldtype == 0x80000101: | ||
| 227 | - self.content.append('') | ||
| 228 | - self.unfreeze(length) | 226 | + def consume_MorphDataControl(self): |
| 227 | + # MorphDataControl: [MS-OFORMS] 2.2.5.1 | ||
| 228 | + self.check_values('MorphDataControl (versions)', '<BB', 2, (0, 2)) | ||
| 229 | + cbMorphData = self.unpack('<H', 2) | ||
| 230 | + self.freeze() | ||
| 231 | + propmask = MorphDataPropMask(self.unpack('<Q', 8)) | ||
| 232 | + # MorphDataDataBlock: [MS-OFORMS] 2.2.5.3 | ||
| 233 | + for prop in ['fVariousPropertyBits', 'fBackColor', 'fForeColor', 'fMaxLength']: | ||
| 234 | + if propmask[prop]: | ||
| 235 | + self.read(4) | ||
| 236 | + self.freeze() | ||
| 237 | + for prop in ['fBorderStyle', 'fScrollBars', 'fDisplayStyle', 'fMousePointer']: | ||
| 238 | + if propmask[prop]: | ||
| 239 | + self.read(1) | ||
| 240 | + self.unfreeze_pad() | ||
| 241 | + # PasswordChar, BoundColumn, TextColumn, ColumnCount, and ListRows are 2B + pad = 4B | ||
| 242 | + # ListWidth is 4B + pad = 4B | ||
| 243 | + for prop in ['fPasswordChar', 'fListWidth', 'fBoundColumn', 'fTextColumn', 'fColumnCount', | ||
| 244 | + 'fListRows']: | ||
| 245 | + if propmask[prop]: | ||
| 246 | + self.read(4) | ||
| 247 | + self.freeze() | ||
| 248 | + if propmask.fcColumnInfo: | ||
| 249 | + self.read(2) | ||
| 250 | + for prop in ['fMatchEntry', 'fListStyle', 'fShowDropButtonWhen', 'fDropButtonStyle', | ||
| 251 | + 'fMultiSelect']: | ||
| 252 | + if propmask[prop]: | ||
| 253 | + self.read(1) | ||
| 254 | + self.unfreeze_pad() | ||
| 255 | + if propmask.fValue: | ||
| 256 | + value_size = self.consume_CountOfBytesWithCompressionFlag() | ||
| 257 | + else: | ||
| 258 | + value_size = 0 | ||
| 259 | + # Caption, PicturePosition, BorderColor, SpecialEffect, GroupName are 4B + pad = 4B | ||
| 260 | + # MouseIcon, Picture, Accelerator are 2B + pad = 4B | ||
| 261 | + for prop in ['fCaption', 'fPicturePosition', 'fBorderColor', 'fSpecialEffect', | ||
| 262 | + 'fMouseIcon', 'fPicture', 'fAccelerator', 'fGroupName']: | ||
| 263 | + if propmask[prop]: | ||
| 264 | + self.read(4) | ||
| 265 | + # MorphDataExtraDataBlock: [MS-OFORMS] 2.2.5.4 | ||
| 266 | + self.read(8) | ||
| 267 | + value = self.read(value_size) | ||
| 268 | + self.unfreeze(cbMorphData) | ||
| 269 | + # MorphDataStreamData: [MS-OFORMS] 2.2.5.5 | ||
| 270 | + if propmask.fMouseIcon: | ||
| 271 | + self.consume_GuidAndPicture() | ||
| 272 | + if propmask.fPicture: | ||
| 273 | + self.consume_GuidAndPicture() | ||
| 274 | + self.consume_TextProps() | ||
| 275 | + return value | ||
| 276 | + | ||
| 277 | + def consume_stored_data(self): | ||
| 278 | + for var in self.variables: | ||
| 279 | + if var['ClsidCacheIndex'] != 23: | ||
| 280 | + raise OleFormParsingError('Unsupported stored type: {0}'.format(str(var['ClsidCacheIndex']))) | ||
| 281 | + var['value'] = self.consume_MorphDataControl() | ||
| 229 | 282 | ||
| 230 | def OleFormVariables(ole_file, stream_dir): | 283 | def OleFormVariables(ole_file, stream_dir): |
| 231 | control_stream = ole_file.openstream('/'.join(stream_dir + ['f'])) | 284 | control_stream = ole_file.openstream('/'.join(stream_dir + ['f'])) |
| 232 | - control_form = OleUserFormParser(control_stream) | ||
| 233 | - control_form.consume_FormControl() | ||
| 234 | - variables = control_form.content | ||
| 235 | data_stream = ole_file.openstream('/'.join(stream_dir + ['o'])) | 285 | data_stream = ole_file.openstream('/'.join(stream_dir + ['o'])) |
| 236 | - data = OleUserFormParser(data_stream) | ||
| 237 | - data.consume_stream_o() | ||
| 238 | - values = data.content | ||
| 239 | - if len(variables) != len(values): | ||
| 240 | - raise OleFormParsingError('Incompatible number of variables: {0} VS {1}'.format(len(variables), len(values))) | ||
| 241 | - for i in range(len(variables)): | ||
| 242 | - variables[i]['value'] = values[i] | ||
| 243 | - return variables | 286 | + form = OleUserFormParser(control_stream, data_stream) |
| 287 | + return form.variables |