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 7  
8 8 class Mask(object):
9 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 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 15 def __getattr__(self, name):
16 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 27 _names = ['Unused1', 'fBackColor', 'fForeColor', 'fNextAvailableID', 'Unused2_0', 'Unused2_1',
20 28 'fBooleanProperties', 'fBooleanProperties', 'fMousePointer', 'fScrollBars',
21 29 'fDisplayedSize', 'fLogicalSize', 'fScrollPosition', 'fGroupCnt', 'Reserved',
22 30 'fMouseIcon', 'fCycle', 'fSpecialEffect', 'fBorderColor', 'fCaption', 'fFont',
23 31 'fPicture', 'fZoom', 'fPictureAlignment', 'fPictureTiling', 'fPictureSizeMode',
24   - 'fShapeCookie', 'fDrawBuffer', 'Unused3_0', 'Unused3_1', 'Unused3_2', 'Unused3_3']
  32 + 'fShapeCookie', 'fDrawBuffer']
25 33  
26 34 class SitePropMask(Mask):
  35 + """SitePropMask: [MS-OFORMS] 2.2.10.12.2"""
  36 + _size = 15
27 37 _names = ['fName', 'fTag', 'fID', 'fHelpContextID', 'fBitFlags', 'fObjectStreamSize',
28 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 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 61 self._pos = 0
39 62 self._frozen_pos = []
  63 + self._stream = stream
40 64  
41 65 def read(self, size):
42 66 self._pos += size
... ... @@ -67,6 +91,12 @@ class OleUserFormParser(object):
67 91 def check_value(self, name, format, size, expected):
68 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 100 def consume_GuidAndFont(self):
71 101 # GuidAndFont: [MS-OFORMS] 2.4.7
72 102 UUIDS = self.unpacks('<LHH', 8) + self.unpacks('>Q', 8)
... ... @@ -80,10 +110,7 @@ class OleUserFormParser(object):
80 110 self.read(bFaceLen)
81 111 elif UUIDs == (2948729120, 55886, 4558, 13349514450607572916L):
82 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 114 else:
88 115 raise OleFormParsingError('Invalid GuidAndFont at {0}: UUID'.format(self._pos - 16))
89 116  
... ... @@ -101,7 +128,6 @@ class OleUserFormParser(object):
101 128 # CountOfBytesWithCompressionFlag or CountOfCharsWithCompressionFlag: [MS-OFORMS] 2.4.14.2 or 2.4.14.3
102 129 count = self.unpack('<L', 4)
103 130 if not count & 0x80000000 and count != 0:
104   - print(count)
105 131 raise OleFormParsingError('Uncompress string length at {0}', self._pos - 4)
106 132 return count & 0x7FFFFFFF
107 133  
... ... @@ -126,45 +152,37 @@ class OleUserFormParser(object):
126 152 self.check_value('OleSiteConcreteControl (version)', '<H', 2, 0)
127 153 cbSite = self.unpack('<H', 2)
128 154 self.freeze()
129   - sitepropmask = SitePropMask(self.unpack('<L', 4))
  155 + propmask = SitePropMask(self.unpack('<L', 4))
130 156 # SiteDataBlock: [MS-OFORMS] 2.2.10.12.3
131 157 name_len = tag_len = id = 0
132   - if sitepropmask.fName:
  158 + if propmask.fName:
133 159 name_len = self.consume_CountOfBytesWithCompressionFlag()
134   - if sitepropmask.fTag:
  160 + if propmask.fTag:
135 161 tag_len = self.consume_CountOfBytesWithCompressionFlag()
136   - if sitepropmask.fID:
  162 + if propmask.fID:
137 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 167 tabindex = ClsidCacheIndex = 0
145 168 self.freeze()
146   - if sitepropmask.fTabIndex:
  169 + if propmask.fTabIndex:
147 170 tabindex = self.unpack('<H', 2)
148   - if sitepropmask.fClsidCacheIndex:
  171 + if propmask.fClsidCacheIndex:
149 172 ClsidCacheIndex = self.unpack('<H', 2)
150   - if sitepropmask.fGroupID:
  173 + if propmask.fGroupID:
151 174 self.read(2)
152 175 self.unfreeze_pad()
153 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 180 # SiteExtraDataBlock: [MS-OFORMS] 2.2.10.12.4
163 181 name = self.read(name_len)
164 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 186 self.unfreeze(cbSite)
169 187  
170 188 def consume_FormControl(self):
... ... @@ -172,14 +190,11 @@ class OleUserFormParser(object):
172 190 self.check_values('FormControl (versions)', '<BB', 2, (0, 4))
173 191 cbform = self.unpack('<H', 2)
174 192 self.freeze()
175   - propmask = PropMask(self.unpack('<L', 4))
  193 + propmask = FormPropMask(self.unpack('<L', 4))
176 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 198 if propmask.fBooleanProperties:
184 199 BooleanProperties = self.unpack('<L', 4)
185 200 FORM_FLAG_DONTSAVECLASSTABLE = (BooleanProperties & (1<<15)) >> 15
... ... @@ -208,36 +223,65 @@ class OleUserFormParser(object):
208 223 for i in range(CountOfSites):
209 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 283 def OleFormVariables(ole_file, stream_dir):
231 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 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
... ...