Commit 5a23b490f898cc6a7901f6d3ad3449c2505f6850
1 parent
ac2f7443
added olevba: a new tool to extract VBA macro code from MS Office documents
Showing
1 changed file
with
658 additions
and
0 deletions
oletools/olevba.py
0 → 100644
| 1 | +#!/usr/bin/env python | |
| 2 | +""" | |
| 3 | +olevba.py v0.02 2014-08-15 | |
| 4 | + | |
| 5 | +olevba is a script to parse OLE files such as MS Office documents (e.g. Word, | |
| 6 | +Excel), to extract VBA Macro code in clear text. | |
| 7 | + | |
| 8 | +olevba project website: http://www.decalage.info/python/olevba | |
| 9 | + | |
| 10 | +olevba is part of the python-oletools package: | |
| 11 | +http://www.decalage.info/python/oletools | |
| 12 | + | |
| 13 | +Usage: olevba.py <file> | |
| 14 | +""" | |
| 15 | + | |
| 16 | +__version__ = '0.02' | |
| 17 | + | |
| 18 | +#=== LICENSE ================================================================== | |
| 19 | + | |
| 20 | +# olevba is copyright (c) 2014 Philippe Lagadec (http://www.decalage.info) | |
| 21 | +# All rights reserved. | |
| 22 | +# | |
| 23 | +# Redistribution and use in source and binary forms, with or without modification, | |
| 24 | +# are permitted provided that the following conditions are met: | |
| 25 | +# | |
| 26 | +# * Redistributions of source code must retain the above copyright notice, this | |
| 27 | +# list of conditions and the following disclaimer. | |
| 28 | +# * Redistributions in binary form must reproduce the above copyright notice, | |
| 29 | +# this list of conditions and the following disclaimer in the documentation | |
| 30 | +# and/or other materials provided with the distribution. | |
| 31 | +# | |
| 32 | +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND | |
| 33 | +# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED | |
| 34 | +# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE | |
| 35 | +# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE | |
| 36 | +# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL | |
| 37 | +# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR | |
| 38 | +# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER | |
| 39 | +# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, | |
| 40 | +# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE | |
| 41 | +# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | |
| 42 | + | |
| 43 | + | |
| 44 | +# olevba contains modified source code from the officeparser project, published | |
| 45 | +# under the following MIT License (MIT): | |
| 46 | +# | |
| 47 | +# officeparser is copyright (c) 2014 John William Davison | |
| 48 | +# | |
| 49 | +# Permission is hereby granted, free of charge, to any person obtaining a copy | |
| 50 | +# of this software and associated documentation files (the "Software"), to deal | |
| 51 | +# in the Software without restriction, including without limitation the rights | |
| 52 | +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | |
| 53 | +# copies of the Software, and to permit persons to whom the Software is | |
| 54 | +# furnished to do so, subject to the following conditions: | |
| 55 | +# | |
| 56 | +# The above copyright notice and this permission notice shall be included in all | |
| 57 | +# copies or substantial portions of the Software. | |
| 58 | +# | |
| 59 | +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | |
| 60 | +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | |
| 61 | +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | |
| 62 | +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | |
| 63 | +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | |
| 64 | +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE | |
| 65 | +# SOFTWARE. | |
| 66 | + | |
| 67 | +#------------------------------------------------------------------------------ | |
| 68 | +# CHANGELOG: | |
| 69 | +# 2014-08-05 v0.01 PL: - first version based on officeparser code | |
| 70 | +# 2014-08-14 v0.02 PL: - fixed bugs in code, added license from officeparser | |
| 71 | +# 2014-08-15 PL: - fixed incorrect value check in PROJECTHELPFILEPATH Record | |
| 72 | + | |
| 73 | +#------------------------------------------------------------------------------ | |
| 74 | +# TODO: | |
| 75 | +# + optparse | |
| 76 | +# + nicer output | |
| 77 | +# + output to file | |
| 78 | +# + setup logging (common with other oletools) | |
| 79 | +# + support OpenXML files | |
| 80 | +# + process several files in dirs or zips with password | |
| 81 | +# + look for VBA in embedded documents (e.g. Excel in Word) | |
| 82 | +# - python 3.x support | |
| 83 | +# - add support for PowerPoint macros (see libclamav, libgsf) | |
| 84 | +# - check VBA macros in Visio, Access, Project, etc | |
| 85 | +# - extract_macros: convert to a class, split long function into smaller methods | |
| 86 | +# - extract_macros: read bytes from stream file objects instead of strings | |
| 87 | +# - extract_macros: use combined struct.unpack instead of many calls | |
| 88 | + | |
| 89 | +#------------------------------------------------------------------------------ | |
| 90 | +# REFERENCES: | |
| 91 | +# - [MS-OVBA]: Microsoft Office VBA File Format Structure | |
| 92 | +# http://msdn.microsoft.com/en-us/library/office/cc313094%28v=office.12%29.aspx | |
| 93 | +# - officeparser: https://github.com/unixfreak0037/officeparser | |
| 94 | + | |
| 95 | + | |
| 96 | +#--- IMPORTS ------------------------------------------------------------------ | |
| 97 | + | |
| 98 | +import sys, logging | |
| 99 | +import struct | |
| 100 | +import cStringIO | |
| 101 | +import math | |
| 102 | + | |
| 103 | +from thirdparty.OleFileIO_PL import OleFileIO_PL | |
| 104 | + | |
| 105 | +#--- CONSTANTS ---------------------------------------------------------------- | |
| 106 | + | |
| 107 | +MODULE_EXTENSION = "bas" | |
| 108 | +CLASS_EXTENSION = "cls" | |
| 109 | +FORM_EXTENSION = "frm" | |
| 110 | + | |
| 111 | +BINFILE_PATH = "xl/vbaProject.bin" | |
| 112 | + | |
| 113 | + | |
| 114 | +#--- FUNCTIONS ---------------------------------------------------------------- | |
| 115 | + | |
| 116 | +def copytoken_help(decompressed_current, decompressed_chunk_start): | |
| 117 | + """ | |
| 118 | + compute bit masks to decode a CopyToken according to MS-OVBA 2.4.1.3.19.1 CopyToken Help | |
| 119 | + | |
| 120 | + decompressed_current: number of decompressed bytes so far, i.e. len(decompressed_container) | |
| 121 | + decompressed_chunk_start: offset of the current chunk in the decompressed container | |
| 122 | + return length_mask, offset_mask, bit_count, maximum_length | |
| 123 | + """ | |
| 124 | + difference = decompressed_current - decompressed_chunk_start | |
| 125 | + bit_count = int(math.ceil(math.log(difference, 2))) | |
| 126 | + bit_count = max([bit_count, 4]) | |
| 127 | + length_mask = 0xFFFF >> bit_count | |
| 128 | + offset_mask = ~length_mask | |
| 129 | + maximum_length = (0xFFFF >> bit_count) + 3 | |
| 130 | + return length_mask, offset_mask, bit_count, maximum_length | |
| 131 | + | |
| 132 | + | |
| 133 | +def decompress_stream (compressed_container): | |
| 134 | + """ | |
| 135 | + Decompress a stream according to MS-OVBA section 2.4.1 | |
| 136 | + | |
| 137 | + compressed_container: string compressed according to the MS-OVBA 2.4.1.3.6 Compression algorithm | |
| 138 | + return the decompressed container as a string (bytes) | |
| 139 | + """ | |
| 140 | + # 2.4.1.2 State Variables | |
| 141 | + | |
| 142 | + # The following state is maintained for the CompressedContainer (section 2.4.1.1.1): | |
| 143 | + # CompressedRecordEnd: The location of the byte after the last byte in the CompressedContainer (section 2.4.1.1.1). | |
| 144 | + # CompressedCurrent: The location of the next byte in the CompressedContainer (section 2.4.1.1.1) to be read by | |
| 145 | + # decompression or to be written by compression. | |
| 146 | + | |
| 147 | + # The following state is maintained for the current CompressedChunk (section 2.4.1.1.4): | |
| 148 | + # CompressedChunkStart: The location of the first byte of the CompressedChunk (section 2.4.1.1.4) within the | |
| 149 | + # CompressedContainer (section 2.4.1.1.1). | |
| 150 | + | |
| 151 | + # The following state is maintained for a DecompressedBuffer (section 2.4.1.1.2): | |
| 152 | + # DecompressedCurrent: The location of the next byte in the DecompressedBuffer (section 2.4.1.1.2) to be written by | |
| 153 | + # decompression or to be read by compression. | |
| 154 | + # DecompressedBufferEnd: The location of the byte after the last byte in the DecompressedBuffer (section 2.4.1.1.2). | |
| 155 | + | |
| 156 | + # The following state is maintained for the current DecompressedChunk (section 2.4.1.1.3): | |
| 157 | + # DecompressedChunkStart: The location of the first byte of the DecompressedChunk (section 2.4.1.1.3) within the | |
| 158 | + # DecompressedBuffer (section 2.4.1.1.2). | |
| 159 | + | |
| 160 | + decompressed_container = '' # result | |
| 161 | + compressed_current = 0 | |
| 162 | + | |
| 163 | + sig_byte = ord(compressed_container[compressed_current]) | |
| 164 | + if sig_byte != 0x01: | |
| 165 | + raise ValueError('invalid signature byte {0:02X}'.format(sig_byte)) | |
| 166 | + | |
| 167 | + compressed_current += 1 | |
| 168 | + | |
| 169 | + #NOTE: the definition of CompressedRecordEnd is ambiguous. Here we assume that | |
| 170 | + # CompressedRecordEnd = len(compressed_container) | |
| 171 | + while compressed_current < len(compressed_container): | |
| 172 | + # 2.4.1.1.5 | |
| 173 | + compressed_chunk_start = compressed_current | |
| 174 | + # chunk header = first 16 bits | |
| 175 | + compressed_chunk_header = struct.unpack("<H", compressed_container[compressed_chunk_start:compressed_chunk_start + 2])[0] | |
| 176 | + # chunk size = 12 first bits of header + 3 | |
| 177 | + chunk_size = (compressed_chunk_header & 0x0FFF) + 3 | |
| 178 | + # chunk signature = 3 next bits - should always be 0b011 | |
| 179 | + chunk_signature = (compressed_chunk_header >> 12) & 0x07 | |
| 180 | + if chunk_signature != 0b011: | |
| 181 | + raise ValueError('Invalid CompressedChunkSignature in VBA compressed stream') | |
| 182 | + # chunk flag = next bit - 1 == compressed, 0 == uncompressed | |
| 183 | + chunk_flag = (compressed_chunk_header >> 15) & 0x01 | |
| 184 | + logging.debug("chunk size = {0}, compressed flag = {1}".format(chunk_size, chunk_flag)) | |
| 185 | + | |
| 186 | + #MS-OVBA 2.4.1.3.12: the maximum size of a chunk including its header is 4098 bytes (header 2 + data 4096) | |
| 187 | + # The minimum size is 3 bytes | |
| 188 | + # NOTE: there seems to be a typo in MS-OVBA, the check should be with 4098, not 4095 (which is the max value | |
| 189 | + # in chunk header before adding 3. | |
| 190 | + # Also the first test is not useful since a 12 bits value cannot be larger than 4095. | |
| 191 | + if chunk_flag == 1 and chunk_size > 4098: | |
| 192 | + raise ValueError('CompressedChunkSize > 4098 but CompressedChunkFlag == 1') | |
| 193 | + if chunk_flag == 0 and chunk_size != 4098: | |
| 194 | + raise ValueError('CompressedChunkSize != 4098 but CompressedChunkFlag == 0') | |
| 195 | + | |
| 196 | + # check if chunk_size goes beyond the compressed data, instead of silently cutting it: | |
| 197 | + #TODO: raise an exception? | |
| 198 | + if compressed_chunk_start + chunk_size > len(compressed_container): | |
| 199 | + logging.warning('Chunk size is larger than remaining compressed data') | |
| 200 | + compressed_end = min([len(compressed_container), compressed_chunk_start + chunk_size]) | |
| 201 | + # read after chunk header: | |
| 202 | + compressed_current = compressed_chunk_start + 2 | |
| 203 | + | |
| 204 | + if chunk_flag == 0: | |
| 205 | + # MS-OVBA 2.4.1.3.3 Decompressing a RawChunk | |
| 206 | + # uncompressed chunk: read the next 4096 bytes as-is | |
| 207 | + #TODO: check if there are at least 4096 bytes left | |
| 208 | + decompressed_container += compressed_container[compressed_current:compressed_current + 4096] | |
| 209 | + compressed_current += 4096 | |
| 210 | + else: | |
| 211 | + # MS-OVBA 2.4.1.3.2 Decompressing a CompressedChunk | |
| 212 | + # compressed chunk | |
| 213 | + decompressed_chunk_start = len(decompressed_container) | |
| 214 | + while compressed_current < compressed_end: | |
| 215 | + # MS-OVBA 2.4.1.3.4 Decompressing a TokenSequence | |
| 216 | + # logging.debug('compressed_current = %d / compressed_end = %d' % (compressed_current, compressed_end)) | |
| 217 | + # FlagByte: 8 bits indicating if the following 8 tokens are either literal (1 byte of plain text) or | |
| 218 | + # copy tokens (reference to a previous literal token) | |
| 219 | + flag_byte = ord(compressed_container[compressed_current]) | |
| 220 | + compressed_current += 1 | |
| 221 | + for bit_index in xrange(0, 8): | |
| 222 | + # logging.debug('bit_index=%d / compressed_current=%d / compressed_end=%d' % (bit_index, compressed_current, compressed_end)) | |
| 223 | + if compressed_current >= compressed_end: | |
| 224 | + break | |
| 225 | + # MS-OVBA 2.4.1.3.5 Decompressing a Token | |
| 226 | + # MS-OVBA 2.4.1.3.17 Extract FlagBit | |
| 227 | + flag_bit = (flag_byte >> bit_index) & 1 | |
| 228 | + #logging.debug('bit_index=%d: flag_bit=%d' % (bit_index, flag_bit)) | |
| 229 | + if flag_bit == 0: # LiteralToken | |
| 230 | + # copy one byte directly to output | |
| 231 | + decompressed_container += compressed_container[compressed_current] | |
| 232 | + compressed_current += 1 | |
| 233 | + else: # CopyToken | |
| 234 | + # MS-OVBA 2.4.1.3.19.2 Unpack CopyToken | |
| 235 | + copy_token = struct.unpack("<H", compressed_container[compressed_current:compressed_current + 2])[0] | |
| 236 | + #TODO: check this | |
| 237 | + length_mask, offset_mask, bit_count, maximum_length = copytoken_help( | |
| 238 | + len(decompressed_container), decompressed_chunk_start) | |
| 239 | + length = (copy_token & length_mask) + 3 | |
| 240 | + temp1 = copy_token & offset_mask | |
| 241 | + temp2 = 16 - bit_count | |
| 242 | + offset = (temp1 >> temp2) + 1 | |
| 243 | + #logging.debug('offset=%d length=%d' % (offset, length)) | |
| 244 | + copy_source = len(decompressed_container) - offset | |
| 245 | + for index in xrange(copy_source, copy_source + length): | |
| 246 | + decompressed_container += decompressed_container[index] | |
| 247 | + compressed_current += 2 | |
| 248 | + return decompressed_container | |
| 249 | + | |
| 250 | + | |
| 251 | +def extract_macros(ole): | |
| 252 | + """ | |
| 253 | + Extract VBA macros from an OLE file | |
| 254 | + """ | |
| 255 | + # Find the VBA project root (different in MS Word, Excel, etc): | |
| 256 | + vba_root = None | |
| 257 | + for stream in ('Macros', '_VBA_PROJECT_CUR'): | |
| 258 | + if ole.exists(stream): | |
| 259 | + logging.debug('found VBA root stream: %s' % stream) | |
| 260 | + vba_root = stream | |
| 261 | + break | |
| 262 | + if vba_root is None: | |
| 263 | + logging.debug('VBA root stream not found') | |
| 264 | + return None | |
| 265 | + # Find the PROJECT stream: | |
| 266 | + project = None | |
| 267 | + project_path = vba_root + '/PROJECT' | |
| 268 | + if ole.exists(project_path): | |
| 269 | + logging.debug('found PROJECT stream: %s' % project_path) | |
| 270 | + project = ole.openstream(project_path) | |
| 271 | + else: | |
| 272 | + logging.debug('missing PROJECT stream') | |
| 273 | + return None | |
| 274 | + | |
| 275 | + # sample content of the PROJECT stream: | |
| 276 | + | |
| 277 | + ## ID="{5312AC8A-349D-4950-BDD0-49BE3C4DD0F0}" | |
| 278 | + ## Document=ThisDocument/&H00000000 | |
| 279 | + ## Module=NewMacros | |
| 280 | + ## Name="Project" | |
| 281 | + ## HelpContextID="0" | |
| 282 | + ## VersionCompatible32="393222000" | |
| 283 | + ## CMG="F1F301E705E705E705E705" | |
| 284 | + ## DPB="8F8D7FE3831F2020202020" | |
| 285 | + ## GC="2D2FDD81E51EE61EE6E1" | |
| 286 | + ## | |
| 287 | + ## [Host Extender Info] | |
| 288 | + ## &H00000001={3832D640-CF90-11CF-8E43-00A0C911005A};VBE;&H00000000 | |
| 289 | + ## &H00000002={000209F2-0000-0000-C000-000000000046};Word8.0;&H00000000 | |
| 290 | + ## | |
| 291 | + ## [Workspace] | |
| 292 | + ## ThisDocument=22, 29, 339, 477, Z | |
| 293 | + ## NewMacros=-4, 42, 832, 510, C | |
| 294 | + | |
| 295 | + code_modules = {} | |
| 296 | + | |
| 297 | + for line in project: | |
| 298 | + line = line.strip() | |
| 299 | + if '=' in line: | |
| 300 | + # split line at the 1st equal sign: | |
| 301 | + name, value = line.split('=', 1) | |
| 302 | + # looking for code modules | |
| 303 | + # add the code module as a key in the dictionary | |
| 304 | + # the value will be the extension needed later | |
| 305 | + if name == 'Document': | |
| 306 | + # split value at the 1st slash, keep 1st part: | |
| 307 | + value = value.split('/', 1)[0] | |
| 308 | + code_modules[value] = CLASS_EXTENSION | |
| 309 | + elif name == 'Module': | |
| 310 | + code_modules[value] = MODULE_EXTENSION | |
| 311 | + elif name == 'Class': | |
| 312 | + code_modules[value] = CLASS_EXTENSION | |
| 313 | + elif name == 'BaseClass': | |
| 314 | + code_modules[value] = FORM_EXTENSION | |
| 315 | + | |
| 316 | + # Find the dir stream | |
| 317 | + dir_path = vba_root + '/VBA/dir' | |
| 318 | + if not ole.exists(dir_path): | |
| 319 | + logging.debug('missing dir stream') | |
| 320 | + return None | |
| 321 | + # read data from dir stream (compressed) | |
| 322 | + dir_compressed = ole.openstream(dir_path).read() | |
| 323 | + | |
| 324 | + def check_value(name, expected, value): | |
| 325 | + if expected != value: | |
| 326 | + logging.error("invalid value for {0} expected {1:04X} got {2:04X}".format(name, expected, value)) | |
| 327 | + | |
| 328 | + dir_stream = cStringIO.StringIO(decompress_stream(dir_compressed)) | |
| 329 | + | |
| 330 | + # PROJECTSYSKIND Record | |
| 331 | + PROJECTSYSKIND_Id = struct.unpack("<H", dir_stream.read(2))[0] | |
| 332 | + check_value('PROJECTSYSKIND_Id', 0x0001, PROJECTSYSKIND_Id) | |
| 333 | + PROJECTSYSKIND_Size = struct.unpack("<L", dir_stream.read(4))[0] | |
| 334 | + check_value('PROJECTSYSKIND_Size', 0x0004, PROJECTSYSKIND_Size) | |
| 335 | + PROJECTSYSKIND_SysKind = struct.unpack("<L", dir_stream.read(4))[0] | |
| 336 | + if PROJECTSYSKIND_SysKind == 0x00: | |
| 337 | + logging.debug("16-bit Windows") | |
| 338 | + elif PROJECTSYSKIND_SysKind == 0x01: | |
| 339 | + logging.debug("32-bit Windows") | |
| 340 | + elif PROJECTSYSKIND_SysKind == 0x02: | |
| 341 | + logging.debug("Macintosh") | |
| 342 | + elif PROJECTSYSKIND_SysKind == 0x03: | |
| 343 | + logging.debug("64-bit Windows") | |
| 344 | + else: | |
| 345 | + logging.error("invalid PROJECTSYSKIND_SysKind {0:04X}".format(PROJECTSYSKIND_SysKind)) | |
| 346 | + | |
| 347 | + # PROJECTLCID Record | |
| 348 | + PROJECTLCID_Id = struct.unpack("<H", dir_stream.read(2))[0] | |
| 349 | + check_value('PROJECTLCID_Id', 0x0002, PROJECTLCID_Id) | |
| 350 | + PROJECTLCID_Size = struct.unpack("<L", dir_stream.read(4))[0] | |
| 351 | + check_value('PROJECTLCID_Size', 0x0004, PROJECTLCID_Size) | |
| 352 | + PROJECTLCID_Lcid = struct.unpack("<L", dir_stream.read(4))[0] | |
| 353 | + check_value('PROJECTLCID_Lcid', 0x409, PROJECTLCID_Lcid) | |
| 354 | + | |
| 355 | + # PROJECTLCIDINVOKE Record | |
| 356 | + PROJECTLCIDINVOKE_Id = struct.unpack("<H", dir_stream.read(2))[0] | |
| 357 | + check_value('PROJECTLCIDINVOKE_Id', 0x0014, PROJECTLCIDINVOKE_Id) | |
| 358 | + PROJECTLCIDINVOKE_Size = struct.unpack("<L", dir_stream.read(4))[0] | |
| 359 | + check_value('PROJECTLCIDINVOKE_Size', 0x0004, PROJECTLCIDINVOKE_Size) | |
| 360 | + PROJECTLCIDINVOKE_LcidInvoke = struct.unpack("<L", dir_stream.read(4))[0] | |
| 361 | + check_value('PROJECTLCIDINVOKE_LcidInvoke', 0x409, PROJECTLCIDINVOKE_LcidInvoke) | |
| 362 | + | |
| 363 | + # PROJECTCODEPAGE Record | |
| 364 | + PROJECTCODEPAGE_Id = struct.unpack("<H", dir_stream.read(2))[0] | |
| 365 | + check_value('PROJECTCODEPAGE_Id', 0x0003, PROJECTCODEPAGE_Id) | |
| 366 | + PROJECTCODEPAGE_Size = struct.unpack("<L", dir_stream.read(4))[0] | |
| 367 | + check_value('PROJECTCODEPAGE_Size', 0x0002, PROJECTCODEPAGE_Size) | |
| 368 | + PROJECTCODEPAGE_CodePage = struct.unpack("<H", dir_stream.read(2))[0] | |
| 369 | + | |
| 370 | + # PROJECTNAME Record | |
| 371 | + PROJECTNAME_Id = struct.unpack("<H", dir_stream.read(2))[0] | |
| 372 | + check_value('PROJECTNAME_Id', 0x0004, PROJECTNAME_Id) | |
| 373 | + PROJECTNAME_SizeOfProjectName = struct.unpack("<L", dir_stream.read(4))[0] | |
| 374 | + if PROJECTNAME_SizeOfProjectName < 1 or PROJECTNAME_SizeOfProjectName > 128: | |
| 375 | + logging.error("PROJECTNAME_SizeOfProjectName value not in range: {0}".format(PROJECTNAME_SizeOfProjectName)) | |
| 376 | + PROJECTNAME_ProjectName = dir_stream.read(PROJECTNAME_SizeOfProjectName) | |
| 377 | + | |
| 378 | + # PROJECTDOCSTRING Record | |
| 379 | + PROJECTDOCSTRING_Id = struct.unpack("<H", dir_stream.read(2))[0] | |
| 380 | + check_value('PROJECTDOCSTRING_Id', 0x0005, PROJECTDOCSTRING_Id) | |
| 381 | + PROJECTDOCSTRING_SizeOfDocString = struct.unpack("<L", dir_stream.read(4))[0] | |
| 382 | + if PROJECTNAME_SizeOfProjectName > 2000: | |
| 383 | + logging.error("PROJECTDOCSTRING_SizeOfDocString value not in range: {0}".format(PROJECTDOCSTRING_SizeOfDocString)) | |
| 384 | + PROJECTDOCSTRING_DocString = dir_stream.read(PROJECTDOCSTRING_SizeOfDocString) | |
| 385 | + PROJECTDOCSTRING_Reserved = struct.unpack("<H", dir_stream.read(2))[0] | |
| 386 | + check_value('PROJECTDOCSTRING_Reserved', 0x0040, PROJECTDOCSTRING_Reserved) | |
| 387 | + PROJECTDOCSTRING_SizeOfDocStringUnicode = struct.unpack("<L", dir_stream.read(4))[0] | |
| 388 | + if PROJECTDOCSTRING_SizeOfDocStringUnicode % 2 != 0: | |
| 389 | + logging.error("PROJECTDOCSTRING_SizeOfDocStringUnicode is not even") | |
| 390 | + PROJECTDOCSTRING_DocStringUnicode = dir_stream.read(PROJECTDOCSTRING_SizeOfDocStringUnicode) | |
| 391 | + | |
| 392 | + # PROJECTHELPFILEPATH Record - MS-OVBA 2.3.4.2.1.7 | |
| 393 | + PROJECTHELPFILEPATH_Id = struct.unpack("<H", dir_stream.read(2))[0] | |
| 394 | + check_value('PROJECTHELPFILEPATH_Id', 0x0006, PROJECTHELPFILEPATH_Id) | |
| 395 | + PROJECTHELPFILEPATH_SizeOfHelpFile1 = struct.unpack("<L", dir_stream.read(4))[0] | |
| 396 | + if PROJECTHELPFILEPATH_SizeOfHelpFile1 > 260: | |
| 397 | + logging.error("PROJECTHELPFILEPATH_SizeOfHelpFile1 value not in range: {0}".format(PROJECTHELPFILEPATH_SizeOfHelpFile1)) | |
| 398 | + PROJECTHELPFILEPATH_HelpFile1 = dir_stream.read(PROJECTHELPFILEPATH_SizeOfHelpFile1) | |
| 399 | + PROJECTHELPFILEPATH_Reserved = struct.unpack("<H", dir_stream.read(2))[0] | |
| 400 | + check_value('PROJECTHELPFILEPATH_Reserved', 0x003D, PROJECTHELPFILEPATH_Reserved) | |
| 401 | + PROJECTHELPFILEPATH_SizeOfHelpFile2 = struct.unpack("<L", dir_stream.read(4))[0] | |
| 402 | + if PROJECTHELPFILEPATH_SizeOfHelpFile2 != PROJECTHELPFILEPATH_SizeOfHelpFile1: | |
| 403 | + logging.error("PROJECTHELPFILEPATH_SizeOfHelpFile1 does not equal PROJECTHELPFILEPATH_SizeOfHelpFile2") | |
| 404 | + PROJECTHELPFILEPATH_HelpFile2 = dir_stream.read(PROJECTHELPFILEPATH_SizeOfHelpFile2) | |
| 405 | + if PROJECTHELPFILEPATH_HelpFile2 != PROJECTHELPFILEPATH_HelpFile1: | |
| 406 | + logging.error("PROJECTHELPFILEPATH_HelpFile1 does not equal PROJECTHELPFILEPATH_HelpFile2") | |
| 407 | + | |
| 408 | + # PROJECTHELPCONTEXT Record | |
| 409 | + PROJECTHELPCONTEXT_Id = struct.unpack("<H", dir_stream.read(2))[0] | |
| 410 | + check_value('PROJECTHELPCONTEXT_Id', 0x0007, PROJECTHELPCONTEXT_Id) | |
| 411 | + PROJECTHELPCONTEXT_Size = struct.unpack("<L", dir_stream.read(4))[0] | |
| 412 | + check_value('PROJECTHELPCONTEXT_Size', 0x0004, PROJECTHELPCONTEXT_Size) | |
| 413 | + PROJECTHELPCONTEXT_HelpContext = struct.unpack("<L", dir_stream.read(4))[0] | |
| 414 | + | |
| 415 | + # PROJECTLIBFLAGS Record | |
| 416 | + PROJECTLIBFLAGS_Id = struct.unpack("<H", dir_stream.read(2))[0] | |
| 417 | + check_value('PROJECTLIBFLAGS_Id', 0x0008, PROJECTLIBFLAGS_Id) | |
| 418 | + PROJECTLIBFLAGS_Size = struct.unpack("<L", dir_stream.read(4))[0] | |
| 419 | + check_value('PROJECTLIBFLAGS_Size', 0x0004, PROJECTLIBFLAGS_Size) | |
| 420 | + PROJECTLIBFLAGS_ProjectLibFlags = struct.unpack("<L", dir_stream.read(4))[0] | |
| 421 | + check_value('PROJECTLIBFLAGS_ProjectLibFlags', 0x0000, PROJECTLIBFLAGS_ProjectLibFlags) | |
| 422 | + | |
| 423 | + # PROJECTVERSION Record | |
| 424 | + PROJECTVERSION_Id = struct.unpack("<H", dir_stream.read(2))[0] | |
| 425 | + check_value('PROJECTVERSION_Id', 0x0009, PROJECTVERSION_Id) | |
| 426 | + PROJECTVERSION_Reserved = struct.unpack("<L", dir_stream.read(4))[0] | |
| 427 | + check_value('PROJECTVERSION_Reserved', 0x0004, PROJECTVERSION_Reserved) | |
| 428 | + PROJECTVERSION_VersionMajor = struct.unpack("<L", dir_stream.read(4))[0] | |
| 429 | + PROJECTVERSION_VersionMinor = struct.unpack("<H", dir_stream.read(2))[0] | |
| 430 | + | |
| 431 | + # PROJECTCONSTANTS Record | |
| 432 | + PROJECTCONSTANTS_Id = struct.unpack("<H", dir_stream.read(2))[0] | |
| 433 | + check_value('PROJECTCONSTANTS_Id', 0x000C, PROJECTCONSTANTS_Id) | |
| 434 | + PROJECTCONSTANTS_SizeOfConstants = struct.unpack("<L", dir_stream.read(4))[0] | |
| 435 | + if PROJECTCONSTANTS_SizeOfConstants > 1015: | |
| 436 | + logging.error("PROJECTCONSTANTS_SizeOfConstants value not in range: {0}".format(PROJECTCONSTANTS_SizeOfConstants)) | |
| 437 | + PROJECTCONSTANTS_Constants = dir_stream.read(PROJECTCONSTANTS_SizeOfConstants) | |
| 438 | + PROJECTCONSTANTS_Reserved = struct.unpack("<H", dir_stream.read(2))[0] | |
| 439 | + check_value('PROJECTCONSTANTS_Reserved', 0x003C, PROJECTCONSTANTS_Reserved) | |
| 440 | + PROJECTCONSTANTS_SizeOfConstantsUnicode = struct.unpack("<L", dir_stream.read(4))[0] | |
| 441 | + if PROJECTCONSTANTS_SizeOfConstantsUnicode % 2 != 0: | |
| 442 | + logging.error("PROJECTCONSTANTS_SizeOfConstantsUnicode is not even") | |
| 443 | + PROJECTCONSTANTS_ConstantsUnicode = dir_stream.read(PROJECTCONSTANTS_SizeOfConstantsUnicode) | |
| 444 | + | |
| 445 | + # array of REFERENCE records | |
| 446 | + check = None | |
| 447 | + while True: | |
| 448 | + check = struct.unpack("<H", dir_stream.read(2))[0] | |
| 449 | + logging.debug("reference type = {0:04X}".format(check)) | |
| 450 | + if check == 0x000F: | |
| 451 | + break | |
| 452 | + | |
| 453 | + if check == 0x0016: | |
| 454 | + # REFERENCENAME | |
| 455 | + REFERENCE_Id = check | |
| 456 | + REFERENCE_SizeOfName = struct.unpack("<L", dir_stream.read(4))[0] | |
| 457 | + REFERENCE_Name = dir_stream.read(REFERENCE_SizeOfName) | |
| 458 | + REFERENCE_Reserved = struct.unpack("<H", dir_stream.read(2))[0] | |
| 459 | + check_value('REFERENCE_Reserved', 0x003E, REFERENCE_Reserved) | |
| 460 | + REFERENCE_SizeOfNameUnicode = struct.unpack("<L", dir_stream.read(4))[0] | |
| 461 | + REFERENCE_NameUnicode = dir_stream.read(REFERENCE_SizeOfNameUnicode) | |
| 462 | + continue | |
| 463 | + | |
| 464 | + if check == 0x0033: | |
| 465 | + # REFERENCEORIGINAL (followed by REFERENCECONTROL) | |
| 466 | + REFERENCEORIGINAL_Id = check | |
| 467 | + REFERENCEORIGINAL_SizeOfLibidOriginal = struct.unpack("<L", dir_stream.read(4))[0] | |
| 468 | + REFERENCEORIGINAL_LibidOriginal = dir_stream.read(REFERENCEORIGINAL_SizeOfLibidOriginal) | |
| 469 | + continue | |
| 470 | + | |
| 471 | + if check == 0x002F: | |
| 472 | + # REFERENCECONTROL | |
| 473 | + REFERENCECONTROL_Id = check | |
| 474 | + REFERENCECONTROL_SizeTwiddled = struct.unpack("<L", dir_stream.read(4))[0] # ignore | |
| 475 | + REFERENCECONTROL_SizeOfLibidTwiddled = struct.unpack("<L", dir_stream.read(4))[0] | |
| 476 | + REFERENCECONTROL_LibidTwiddled = dir_stream.read(REFERENCECONTROL_SizeOfLibidTwiddled) | |
| 477 | + REFERENCECONTROL_Reserved1 = struct.unpack("<L", dir_stream.read(4))[0] # ignore | |
| 478 | + check_value('REFERENCECONTROL_Reserved1', 0x0000, REFERENCECONTROL_Reserved1) | |
| 479 | + REFERENCECONTROL_Reserved2 = struct.unpack("<H", dir_stream.read(2))[0] # ignore | |
| 480 | + check_value('REFERENCECONTROL_Reserved2', 0x0000, REFERENCECONTROL_Reserved2) | |
| 481 | + # optional field | |
| 482 | + check2 = struct.unpack("<H", dir_stream.read(2))[0] | |
| 483 | + if check2 == 0x0016: | |
| 484 | + REFERENCECONTROL_NameRecordExtended_Id = check | |
| 485 | + REFERENCECONTROL_NameRecordExtended_SizeofName = struct.unpack("<L", dir_stream.read(4))[0] | |
| 486 | + REFERENCECONTROL_NameRecordExtended_Name = dir_stream.read(REFERENCECONTROL_NameRecordExtended_SizeofName) | |
| 487 | + REFERENCECONTROL_NameRecordExtended_Reserved = struct.unpack("<H", dir_stream.read(2))[0] | |
| 488 | + check_value('REFERENCECONTROL_NameRecordExtended_Reserved', 0x003E, REFERENCECONTROL_NameRecordExtended_Reserved) | |
| 489 | + REFERENCECONTROL_NameRecordExtended_SizeOfNameUnicode = struct.unpack("<L", dir_stream.read(4))[0] | |
| 490 | + REFERENCECONTROL_NameRecordExtended_NameUnicode = dir_stream.read(REFERENCECONTROL_NameRecordExtended_SizeOfNameUnicode) | |
| 491 | + REFERENCECONTROL_Reserved3 = struct.unpack("<H", dir_stream.read(2))[0] | |
| 492 | + else: | |
| 493 | + REFERENCECONTROL_Reserved3 = check2 | |
| 494 | + | |
| 495 | + check_value('REFERENCECONTROL_Reserved3', 0x0030, REFERENCECONTROL_Reserved3) | |
| 496 | + REFERENCECONTROL_SizeExtended = struct.unpack("<L", dir_stream.read(4))[0] | |
| 497 | + REFERENCECONTROL_SizeOfLibidExtended = struct.unpack("<L", dir_stream.read(4))[0] | |
| 498 | + REFERENCECONTROL_LibidExtended = dir_stream.read(REFERENCECONTROL_SizeOfLibidExtended) | |
| 499 | + REFERENCECONTROL_Reserved4 = struct.unpack("<L", dir_stream.read(4))[0] | |
| 500 | + REFERENCECONTROL_Reserved5 = struct.unpack("<H", dir_stream.read(2))[0] | |
| 501 | + REFERENCECONTROL_OriginalTypeLib = dir_stream.read(16) | |
| 502 | + REFERENCECONTROL_Cookie = struct.unpack("<L", dir_stream.read(4))[0] | |
| 503 | + continue | |
| 504 | + | |
| 505 | + if check == 0x000D: | |
| 506 | + # REFERENCEREGISTERED | |
| 507 | + REFERENCEREGISTERED_Id = check | |
| 508 | + REFERENCEREGISTERED_Size = struct.unpack("<L", dir_stream.read(4))[0] | |
| 509 | + REFERENCEREGISTERED_SizeOfLibid = struct.unpack("<L", dir_stream.read(4))[0] | |
| 510 | + REFERENCEREGISTERED_Libid = dir_stream.read(REFERENCEREGISTERED_SizeOfLibid) | |
| 511 | + REFERENCEREGISTERED_Reserved1 = struct.unpack("<L", dir_stream.read(4))[0] | |
| 512 | + check_value('REFERENCEREGISTERED_Reserved1', 0x0000, REFERENCEREGISTERED_Reserved1) | |
| 513 | + REFERENCEREGISTERED_Reserved2 = struct.unpack("<H", dir_stream.read(2))[0] | |
| 514 | + check_value('REFERENCEREGISTERED_Reserved2', 0x0000, REFERENCEREGISTERED_Reserved2) | |
| 515 | + continue | |
| 516 | + | |
| 517 | + if check == 0x000E: | |
| 518 | + # REFERENCEPROJECT | |
| 519 | + REFERENCEPROJECT_Id = check | |
| 520 | + REFERENCEPROJECT_Size = struct.unpack("<L", dir_stream.read(4))[0] | |
| 521 | + REFERENCEPROJECT_SizeOfLibidAbsolute = struct.unpack("<L", dir_stream.read(4))[0] | |
| 522 | + REFERENCEPROJECT_LibidAbsolute = dir_stream.read(REFERENCEPROJECT_SizeOfLibidAbsolute) | |
| 523 | + REFERENCEPROJECT_SizeOfLibidRelative = struct.unpack("<L", dir_stream.read(4))[0] | |
| 524 | + REFERENCEPROJECT_LibidRelative = dir_stream.read(REFERENCEPROJECT_SizeOfLibidRelative) | |
| 525 | + REFERENCEPROJECT_MajorVersion = struct.unpack("<L", dir_stream.read(4))[0] | |
| 526 | + REFERENCEPROJECT_MinorVersion = struct.unpack("<H", dir_stream.read(2))[0] | |
| 527 | + continue | |
| 528 | + | |
| 529 | + logging.error('invalid or unknown check Id {0:04X}'.format(check)) | |
| 530 | + sys.exit(0) | |
| 531 | + | |
| 532 | + PROJECTMODULES_Id = check #struct.unpack("<H", dir_stream.read(2))[0] | |
| 533 | + check_value('PROJECTMODULES_Id', 0x000F, PROJECTMODULES_Id) | |
| 534 | + PROJECTMODULES_Size = struct.unpack("<L", dir_stream.read(4))[0] | |
| 535 | + check_value('PROJECTMODULES_Size', 0x0002, PROJECTMODULES_Size) | |
| 536 | + PROJECTMODULES_Count = struct.unpack("<H", dir_stream.read(2))[0] | |
| 537 | + PROJECTMODULES_ProjectCookieRecord_Id = struct.unpack("<H", dir_stream.read(2))[0] | |
| 538 | + check_value('PROJECTMODULES_ProjectCookieRecord_Id', 0x0013, PROJECTMODULES_ProjectCookieRecord_Id) | |
| 539 | + PROJECTMODULES_ProjectCookieRecord_Size = struct.unpack("<L", dir_stream.read(4))[0] | |
| 540 | + check_value('PROJECTMODULES_ProjectCookieRecord_Size', 0x0002, PROJECTMODULES_ProjectCookieRecord_Size) | |
| 541 | + PROJECTMODULES_ProjectCookieRecord_Cookie = struct.unpack("<H", dir_stream.read(2))[0] | |
| 542 | + | |
| 543 | + logging.debug("parsing {0} modules".format(PROJECTMODULES_Count)) | |
| 544 | + for x in xrange(0, PROJECTMODULES_Count): | |
| 545 | + MODULENAME_Id = struct.unpack("<H", dir_stream.read(2))[0] | |
| 546 | + check_value('MODULENAME_Id', 0x0019, MODULENAME_Id) | |
| 547 | + MODULENAME_SizeOfModuleName = struct.unpack("<L", dir_stream.read(4))[0] | |
| 548 | + MODULENAME_ModuleName = dir_stream.read(MODULENAME_SizeOfModuleName) | |
| 549 | + # account for optional sections | |
| 550 | + section_id = struct.unpack("<H", dir_stream.read(2))[0] | |
| 551 | + if section_id == 0x0047: | |
| 552 | + MODULENAMEUNICODE_Id = section_id | |
| 553 | + MODULENAMEUNICODE_SizeOfModuleNameUnicode = struct.unpack("<L", dir_stream.read(4))[0] | |
| 554 | + MODULENAMEUNICODE_ModuleNameUnicode = dir_stream.read(MODULENAMEUNICODE_SizeOfModuleNameUnicode) | |
| 555 | + section_id = struct.unpack("<H", dir_stream.read(2))[0] | |
| 556 | + if section_id == 0x001A: | |
| 557 | + MODULESTREAMNAME_id = section_id | |
| 558 | + MODULESTREAMNAME_SizeOfStreamName = struct.unpack("<L", dir_stream.read(4))[0] | |
| 559 | + MODULESTREAMNAME_StreamName = dir_stream.read(MODULESTREAMNAME_SizeOfStreamName) | |
| 560 | + MODULESTREAMNAME_Reserved = struct.unpack("<H", dir_stream.read(2))[0] | |
| 561 | + check_value('MODULESTREAMNAME_Reserved', 0x0032, MODULESTREAMNAME_Reserved) | |
| 562 | + MODULESTREAMNAME_SizeOfStreamNameUnicode = struct.unpack("<L", dir_stream.read(4))[0] | |
| 563 | + MODULESTREAMNAME_StreamNameUnicode = dir_stream.read(MODULESTREAMNAME_SizeOfStreamNameUnicode) | |
| 564 | + section_id = struct.unpack("<H", dir_stream.read(2))[0] | |
| 565 | + if section_id == 0x001C: | |
| 566 | + MODULEDOCSTRING_Id = section_id | |
| 567 | + check_value('MODULEDOCSTRING_Id', 0x001C, MODULEDOCSTRING_Id) | |
| 568 | + MODULEDOCSTRING_SizeOfDocString = struct.unpack("<L", dir_stream.read(4))[0] | |
| 569 | + MODULEDOCSTRING_DocString = dir_stream.read(MODULEDOCSTRING_SizeOfDocString) | |
| 570 | + MODULEDOCSTRING_Reserved = struct.unpack("<H", dir_stream.read(2))[0] | |
| 571 | + check_value('MODULEDOCSTRING_Reserved', 0x0048, MODULEDOCSTRING_Reserved) | |
| 572 | + MODULEDOCSTRING_SizeOfDocStringUnicode = struct.unpack("<L", dir_stream.read(4))[0] | |
| 573 | + MODULEDOCSTRING_DocStringUnicode = dir_stream.read(MODULEDOCSTRING_SizeOfDocStringUnicode) | |
| 574 | + section_id = struct.unpack("<H", dir_stream.read(2))[0] | |
| 575 | + if section_id == 0x0031: | |
| 576 | + MODULEOFFSET_Id = section_id | |
| 577 | + check_value('MODULEOFFSET_Id', 0x0031, MODULEOFFSET_Id) | |
| 578 | + MODULEOFFSET_Size = struct.unpack("<L", dir_stream.read(4))[0] | |
| 579 | + check_value('MODULEOFFSET_Size', 0x0004, MODULEOFFSET_Size) | |
| 580 | + MODULEOFFSET_TextOffset = struct.unpack("<L", dir_stream.read(4))[0] | |
| 581 | + section_id = struct.unpack("<H", dir_stream.read(2))[0] | |
| 582 | + if section_id == 0x001E: | |
| 583 | + MODULEHELPCONTEXT_Id = section_id | |
| 584 | + check_value('MODULEHELPCONTEXT_Id', 0x001E, MODULEHELPCONTEXT_Id) | |
| 585 | + MODULEHELPCONTEXT_Size = struct.unpack("<L", dir_stream.read(4))[0] | |
| 586 | + check_value('MODULEHELPCONTEXT_Size', 0x0004, MODULEHELPCONTEXT_Size) | |
| 587 | + MODULEHELPCONTEXT_HelpContext = struct.unpack("<L", dir_stream.read(4))[0] | |
| 588 | + section_id = struct.unpack("<H", dir_stream.read(2))[0] | |
| 589 | + if section_id == 0x002C: | |
| 590 | + MODULECOOKIE_Id = section_id | |
| 591 | + check_value('MODULECOOKIE_Id', 0x002C, MODULECOOKIE_Id) | |
| 592 | + MODULECOOKIE_Size = struct.unpack("<L", dir_stream.read(4))[0] | |
| 593 | + check_value('MODULECOOKIE_Size', 0x0002, MODULECOOKIE_Size) | |
| 594 | + MODULECOOKIE_Cookie = struct.unpack("<H", dir_stream.read(2))[0] | |
| 595 | + section_id = struct.unpack("<H", dir_stream.read(2))[0] | |
| 596 | + if section_id == 0x0021 or section_id == 0x0022: | |
| 597 | + MODULETYPE_Id = section_id | |
| 598 | + MODULETYPE_Reserved = struct.unpack("<L", dir_stream.read(4))[0] | |
| 599 | + section_id = struct.unpack("<H", dir_stream.read(2))[0] | |
| 600 | + if section_id == 0x0025: | |
| 601 | + MODULEREADONLY_Id = section_id | |
| 602 | + check_value('MODULEREADONLY_Id', 0x0025, MODULEREADONLY_Id) | |
| 603 | + MODULEREADONLY_Reserved = struct.unpack("<L", dir_stream.read(4))[0] | |
| 604 | + check_value('MODULEREADONLY_Reserved', 0x0000, MODULEREADONLY_Reserved) | |
| 605 | + section_id = struct.unpack("<H", dir_stream.read(2))[0] | |
| 606 | + if section_id == 0x0028: | |
| 607 | + MODULEPRIVATE_Id = section_id | |
| 608 | + check_value('MODULEPRIVATE_Id', 0x0028, MODULEPRIVATE_Id) | |
| 609 | + MODULEPRIVATE_Reserved = struct.unpack("<L", dir_stream.read(4))[0] | |
| 610 | + check_value('MODULEPRIVATE_Reserved', 0x0000, MODULEPRIVATE_Reserved) | |
| 611 | + section_id = struct.unpack("<H", dir_stream.read(2))[0] | |
| 612 | + if section_id == 0x002B: # TERMINATOR | |
| 613 | + MODULE_Reserved = struct.unpack("<L", dir_stream.read(4))[0] | |
| 614 | + check_value('MODULE_Reserved', 0x0000, MODULE_Reserved) | |
| 615 | + section_id = None | |
| 616 | + if section_id != None: | |
| 617 | + logging.warning('unknown or invalid module section id {0:04X}'.format(section_id)) | |
| 618 | + | |
| 619 | + logging.debug("ModuleName = {0}".format(MODULENAME_ModuleName)) | |
| 620 | + logging.debug("StreamName = {0}".format(MODULESTREAMNAME_StreamName)) | |
| 621 | + logging.debug("TextOffset = {0}".format(MODULEOFFSET_TextOffset)) | |
| 622 | + | |
| 623 | + code_path = vba_root + '/VBA/' + MODULESTREAMNAME_StreamName | |
| 624 | + #TODO: test if stream exists | |
| 625 | + code_data = ole.openstream(code_path).read() | |
| 626 | + logging.debug("length of code_data = {0}".format(len(code_data))) | |
| 627 | + logging.debug("offset of code_data = {0}".format(MODULEOFFSET_TextOffset)) | |
| 628 | + code_data = code_data[MODULEOFFSET_TextOffset:] | |
| 629 | + if len(code_data) > 0: | |
| 630 | + code_data = decompress_stream(code_data) | |
| 631 | + filext = code_modules[MODULENAME_ModuleName] | |
| 632 | + filename = '{0}.{1}'.format(MODULENAME_ModuleName, filext) | |
| 633 | + #TODO: return list of strings or dict instead of printing | |
| 634 | + print '-'*79 | |
| 635 | + print filename | |
| 636 | + print '' | |
| 637 | + print code_data | |
| 638 | + print '' | |
| 639 | + logging.debug('extracted file {0}'.format(filename)) | |
| 640 | + else: | |
| 641 | + logging.warning("module stream {0} has code data length 0".format(MODULESTREAMNAME_StreamName)) | |
| 642 | + return | |
| 643 | + | |
| 644 | + | |
| 645 | +#=== MAIN ===================================================================== | |
| 646 | + | |
| 647 | +if __name__ == '__main__': | |
| 648 | + | |
| 649 | + if len(sys.argv)<2: | |
| 650 | + print __doc__ | |
| 651 | + sys.exit(1) | |
| 652 | + | |
| 653 | + logging.basicConfig(format='%(levelname)s: %(message)s', level=logging.WARNING) | |
| 654 | + | |
| 655 | + ole = OleFileIO_PL.OleFileIO(sys.argv[1]) | |
| 656 | + extract_macros(ole) | |
| 657 | + | |
| 658 | + ole.close() | ... | ... |