Commit c5d4ec7b870d46c31949ef29dc91e634b8896ba7

Authored by Vincent Brillault
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