From 04b118c26c22dc845be1c8f34566673873dd48b6 Mon Sep 17 00:00:00 2001 From: decalage2 Date: Tue, 4 May 2021 21:13:05 +0200 Subject: [PATCH] updated plugin_biff to v0.0.22, fixes #647, fixes #674 --- oletools/olevba.py | 2 +- oletools/thirdparty/oledump/plugin_biff.py | 3912 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++---------------------------------------------------------------------------------------------------- setup.py | 2 +- 3 files changed, 3814 insertions(+), 102 deletions(-) diff --git a/oletools/olevba.py b/oletools/olevba.py index e9c9fd9..a32aa5f 100644 --- a/oletools/olevba.py +++ b/oletools/olevba.py @@ -235,7 +235,7 @@ from __future__ import print_function # for issue #619) # 2021-04-14 PL: - added detection of Workbook_BeforeClose (issue #518) -__version__ = '0.56.2.dev1' +__version__ = '0.56.2.dev2' #------------------------------------------------------------------------------ # TODO: diff --git a/oletools/thirdparty/oledump/plugin_biff.py b/oletools/thirdparty/oledump/plugin_biff.py index b3ba6a8..31544ca 100644 --- a/oletools/thirdparty/oledump/plugin_biff.py +++ b/oletools/thirdparty/oledump/plugin_biff.py @@ -2,8 +2,8 @@ __description__ = 'BIFF plugin for oledump.py' __author__ = 'Didier Stevens' -__version__ = '0.0.17' -__date__ = '2020/07/17' +__version__ = '0.0.22' +__date__ = '2021/02/23' # Slightly modified version by Philippe Lagadec to be imported into olevba @@ -44,9 +44,17 @@ History: 2020/05/22: 0.0.15 Python 3 fix STRING record 0x207 2020/05/26: 0.0.16 added logic for reserved bits in BOUNDSHEET 2020/07/17: 0.0.17 added option --statistics - + 2020/10/03: 0.0.18 improved FILEPASS record handling + 2020/12/03: 0.0.19 added detection of BIFF5/BIFF7 and FILEPASS record parsing + 2020/12/19: 0.0.20 added FILEPASS XOR Obfuscation password cracking (option -w) + 2021/02/06: 0.0.21 added option --hexrecord, added option -D + 2021/02/07: added key extraction for XOR obfuscation + 2021/02/09: added recordsNotXORObfuscated + 2021/02/21: 0.0.22 bug fix + 2021/02/23: added PASSWORD record cracking Todo: + updated parsing of records for BIFF5 record format """ import struct @@ -1194,6 +1202,8 @@ def ParseExpression(expression, definesNames, sheetNames, cellrefformat): stringValue = P23Decode(expression[:length*2]) result += '"%s" ' % stringValue expression = expression[length*2:] + else: + stringValue = '' stack.append('"' + stringValue + '"') elif ptgid == 0x19: grbit = P23Ord(expression[0]) @@ -1314,13 +1324,3619 @@ def DecodeRKValue(data): else: return struct.unpack('> (8 - count)) & 0xFF + +def ror(byte, count): + return (byte >> count | byte << (8 - count)) & 0xFF + +def RorBytes(data, index): + return data[index:] + data[:index] + +def Xor(data, key): + if sys.version_info[0] > 2: + return bytes([byte ^ key[index % len(key)] for index, byte in enumerate(data)]) else: - return repr(data[2:2 + cch * 2]) + return ''.join([chr(ord(char) ^ ord(key[index % len(key)])) for index, char in enumerate(data)]) + +def XorDeobfuscate(data, key, position): + return bytes([ror(byte, 5) for byte in Xor(data, RorBytes(key, position % 16))]) + +def FindOpcodeInLine(opcodes, line): + for opcode in opcodes.split(','): + if opcode.lower() in line.lower(): + return True + return False class cBIFF(cPluginParent): macroOnly = False @@ -1598,6 +5214,16 @@ class cBIFF(cPluginParent): 0x8ca: 'MKREXT : Extension information for markers in Mac Office 11' } + # https://docs.microsoft.com/en-us/openspecs/office_file_formats/ms-xls/0f2ea0a1-9fc8-468d-97aa-9d333b72d106?redirectedfrom=MSDN + recordsNotXORObfuscated = [ 0x2F, # FILEPASS + 0xE1, # INTERFACEHDR + 0x138, # RRDHEAD + 0x194, # USREXCL + 0x195, # FILELOCK + 0x196, # RRDINFO + 0x809, # BOF + ] + if self.streamname in [['Workbook'], ['Book']]: self.ran = True stream = self.stream @@ -1606,36 +5232,56 @@ class cBIFF(cPluginParent): oParser.add_option('-s', '--strings', action='store_true', default=False, help='Dump strings') oParser.add_option('-a', '--hexascii', action='store_true', default=False, help='Dump hex ascii') oParser.add_option('-X', '--hex', action='store_true', default=False, help='Dump hex without whitespace') + oParser.add_option('-R', '--hexrecord', action='store_true', default=False, help='Dump hex of complete record without whitespace') oParser.add_option('-b', '--formulabytes', action='store_true', default=False, help='Dump formula bytes') oParser.add_option('-d', '--dump', action='store_true', default=False, help='Dump') oParser.add_option('-x', '--xlm', action='store_true', default=False, help='Select all records relevant for Excel 4.0 macros') - oParser.add_option('-o', '--opcode', type=str, default='', help='Opcode to filter for') + oParser.add_option('-o', '--opcode', type=str, default='', help='Opcode to filter for (use , to separate multiple opcodes') oParser.add_option('-f', '--find', type=str, default='', help='Content to search for') oParser.add_option('-c', '--csv', action='store_true', default=False, help='Produce CSV') oParser.add_option('-j', '--json', action='store_true', default=False, help='Produce JSON') oParser.add_option('-r', '--cellrefformat', type=str, default='rc', help='Cell reference format (RC, LN)') oParser.add_option('-S', '--statistics', action='store_true', default=False, help='Produce BIFF record statistics') + oParser.add_option('-w', '--wordlist', type=str, default='', help='Try to crack password with provided passwordlist') + oParser.add_option('-D', '--xordeobfuscate', action='store_true', default=False, help='XOR Deobfuscate') (options, args) = oParser.parse_args(self.options.split(' ')) if options.find.startswith('0x'): options.find = binascii.a2b_hex(options.find[2:]) + if options.wordlist == '': + passwordlistFilename = '.' + else: + passwordlistFilename = options.wordlist + position = 0 macros4Found = False filepassFound = False + isBIFF8 = True dSheetNames = {} sheetNames = [] definesNames = [] currentSheetname = '' dOpcodeStatistics = {} + xorObfuscationKey = None while position < len(stream): + decrypted = False formatcodes = 'HH' formatsize = struct.calcsize(formatcodes) if len(stream[position:position + formatsize]) < formatsize: break - opcode, length = struct.unpack(formatcodes, stream[position:position + formatsize]) + header = stream[position:position + formatsize] + opcode, length = struct.unpack(formatcodes, header) dOpcodeStatistics[opcode] = [dOpcodeStatistics.get(opcode, [0, 0])[0] + 1, dOpcodeStatistics.get(opcode, [0, 0])[1] + length] data = stream[position + formatsize:position + formatsize + length] + if xorObfuscationKey != None and xorObfuscationKey != '?' and options.xordeobfuscate: + if not opcode in recordsNotXORObfuscated: + dataDeobfuscated = XorDeobfuscate(data, xorObfuscationKey, position + 4 + len(data)) + decrypted = True + if opcode == 0x85: #BOUNDSHEET + data = data[:4] + dataDeobfuscated[4:] + else: + data = dataDeobfuscated positionBIFFRecord = position position = position + formatsize + length @@ -1647,116 +5293,178 @@ class cBIFF(cPluginParent): csvrow = None + # PASSWORD record and PROT4REVPASS reconrd + if (opcode == 0x13 or opcode == 0x01bc) and len(data) == 2: + if not filepassFound or decrypted: + verifier = struct.unpack('= 21: - cellref, dummy = ParseLoc(data, options.cellrefformat, True) - formatcodes = 'H' - formatsize = struct.calcsize(formatcodes) - length = struct.unpack(formatcodes, data[20:20 + formatsize])[0] - expression = data[22:] - parsedExpression, stack = ParseExpression(expression, definesNames, sheetNames, options.cellrefformat) - line += ' - %s len=%d %s' % (cellref, length, parsedExpression) - if len(stack) == 1: - csvrow = [currentSheetname, cellref, stack[0], ''] - else: - csvrow = [currentSheetname, cellref, repr(stack), ''] - if options.formulabytes: - data_hex = P23Decode(binascii.b2a_hex(data)) - spaced_data_hex = ' '.join(a+b for a,b in zip(data_hex[::2], data_hex[1::2])) - line += '\nFORMULA BYTES: %s' % spaced_data_hex + if not filepassFound: + cellref, dummy = ParseLoc(data, options.cellrefformat, True) + formatcodes = 'H' + formatsize = struct.calcsize(formatcodes) + length = struct.unpack(formatcodes, data[20:20 + formatsize])[0] + expression = data[22:] + parsedExpression, stack = ParseExpression(expression, definesNames, sheetNames, options.cellrefformat) + line += ' - %s len=%d %s' % (cellref, length, parsedExpression) + if len(stack) == 1: + csvrow = [currentSheetname, cellref, stack[0], ''] + else: + csvrow = [currentSheetname, cellref, repr(stack), ''] + if options.formulabytes: + data_hex = P23Decode(binascii.b2a_hex(data)) + spaced_data_hex = ' '.join(a+b for a,b in zip(data_hex[::2], data_hex[1::2])) + line += '\nFORMULA BYTES: %s' % spaced_data_hex # LABEL record #a# difference BIFF4 and BIFF5+ if opcode == 0x18 and len(data) >= 16: - flags = P23Ord(data[0]) - lnName = P23Ord(data[3]) - szFormula = P23Ord(data[4]) + P23Ord(data[5]) * 0x100 - offset = 14 - if P23Ord(data[offset]) == 0: #a# hack with BIFF8 Unicode - offset = 15 - if flags & 0x20: - dBuildInNames = {1: 'Auto_Open', 2: 'Auto_Close'} - code = P23Ord(data[offset]) - name = dBuildInNames.get(code, '?') - line += ' - built-in-name %d %s' % (code, name) - else: - name = P23Decode(data[offset:offset+lnName]) - line += ' - %s' % (name) - definesNames.append(name) - if flags & 0x01: - line += ' hidden' - try: - parsedExpression, stack = ParseExpression(data[offset+lnName:offset+lnName+szFormula], definesNames, sheetNames, options.cellrefformat) - except IndexError: - parsedExpression = '*PARSING ERROR*' - line += ' len=%d %s' % (szFormula, parsedExpression) + if not filepassFound: + flags = P23Ord(data[0]) + lnName = P23Ord(data[3]) + szFormula = P23Ord(data[4]) + P23Ord(data[5]) * 0x100 + offset = 14 + if P23Ord(data[offset]) == 0: #a# hack with BIFF8 Unicode + offset = 15 + if flags & 0x20: + dBuildInNames = {1: 'Auto_Open', 2: 'Auto_Close'} + code = P23Ord(data[offset]) + name = dBuildInNames.get(code, '?') + line += ' - built-in-name %d %s' % (code, name) + else: + name = P23Decode(data[offset:offset+lnName]) + line += ' - %s' % (name) + definesNames.append(name) + if flags & 0x01: + line += ' hidden' + try: + parsedExpression, stack = ParseExpression(data[offset+lnName:offset+lnName+szFormula], definesNames, sheetNames, options.cellrefformat) + except IndexError: + parsedExpression = '*PARSING ERROR*' + line += ' len=%d %s' % (szFormula, parsedExpression) # FILEPASS record if opcode == 0x2f: filepassFound = True + if len(data) == 4: + line += ' - XOR obfuscation < BIFF8' + key, verifier, password = AnalyzeXORObfuscationStructure(data, passwordlistFilename) + xorObfuscationKey = '?' + if password != None: + line += ' - password: ' + password + if password == 'VelvetSweatshop': + keyVelvetSweatshop = binascii.a2b_hex('87 6B 9A E2 1E E3 05 62 1E 69 96 60 98 6E 94 04'.replace(' ', '')) + xorObfuscationKey = keyVelvetSweatshop + elif len(data) >= 6: + formatcodes = '= 6: - formatcodes = ' 3: - visibility = 'reserved bits not zero: 0x%02x ' % (sheetState & 0xFC) - visibility += dSheetState.get(sheetState & 3, '0x%02x' % (sheetState & 3)) - - line += ' - %s, %s - %s' % (dSheetType.get(sheetType, '%02x' % sheetType), visibility, sheetName) + if not filepassFound or xorObfuscationKey != None and xorObfuscationKey != '?' and options.xordeobfuscate: + formatcodes = ' 3: + visibility = 'reserved bits not zero: 0x%02x ' % (sheetState & 0xFC) + visibility += dSheetState.get(sheetState & 3, '0x%02x' % (sheetState & 3)) + + line += ' - %s, %s - %s' % (dSheetType.get(sheetType, '%02x' % sheetType), visibility, sheetName) # BOF record - if opcode == 0x0809 and len(data) >= 4: - formatcodes = 'H' - formatsize = struct.calcsize(formatcodes) - dt = struct.unpack(formatcodes, data[2:2 + formatsize])[0] - dStreamType = {5: 'workbook', 0x10: 'dialog sheet/worksheet', 0x20: 'chart sheet', 0x40: 'macro sheet'} - line += ' - %s' % (dStreamType.get(dt, '0x%04x' % dt)) - if positionBIFFRecord in dSheetNames: - line += ' - %s' % (dSheetNames[positionBIFFRecord]) - currentSheetname = dSheetNames[positionBIFFRecord] + if opcode == 0x0809 and len(data) >= 8: + if not filepassFound: + formatcodes = '= 4: - values = list(Strings(data[3:]).values()) - strings = '' - if values[0] != []: - strings = values[0][0].encode() - if values[1] != []: - if strings != '': - strings += ' ' - strings += ' '.join(values[1]) - line += ' - %s' % strings + if not filepassFound: + values = list(Strings(data[3:]).values()) + strings = '' + if values[0] != []: + strings = values[0][0].encode() + if values[1] != []: + if strings != '': + strings += ' ' + strings += ' '.join(values[1]) + line += ' - %s' % strings # number record if opcode == 0x0203: - cellref, data2 = ParseLoc(data, options.cellrefformat, True) - formatcodes = '