Skip to content

Commit

Permalink
20210818a
Browse files Browse the repository at this point in the history
  • Loading branch information
DidierStevens committed Aug 18, 2021
1 parent d2a4d85 commit 1f9a661
Show file tree
Hide file tree
Showing 4 changed files with 62 additions and 29 deletions.
11 changes: 7 additions & 4 deletions oledump.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@

__description__ = 'Analyze OLE files (Compound Binary Files)'
__author__ = 'Didier Stevens'
__version__ = '0.0.61'
__date__ = '2021/06/20'
__version__ = '0.0.62'
__date__ = '2021/08/11'

"""
Expand Down Expand Up @@ -110,6 +110,7 @@
2021/02/06: 0.0.59 small change to XML detection logic
2021/02/23: 0.0.60 small change PIP message
2021/06/20: 0.0.61 updated man
2021/08/11: 0.0.62 fix return code bug for multiple OLE files inside OOXML container
Todo:
Expand Down Expand Up @@ -2200,7 +2201,8 @@ def OLEDump(filename, options):
if not options.quiet and not options.jsonoutput:
print('%s: %s' % (letter, info.filename))
ole = olefile.OleFileIO(DataIO(content))
returnCode, selectionCounter = OLESub(ole, content, letter, rules, options)
returnCodeSub, selectionCounter = OLESub(ole, content, letter, rules, options)
returnCode = max(returnCode, returnCodeSub)
selectionCounterTotal += selectionCounter
oleFileFound = True
ole.close()
Expand Down Expand Up @@ -2242,7 +2244,8 @@ def OLEDump(filename, options):
break
print('%s: %s' % (letter, nameValue))
ole = olefile.OleFileIO(DataIO(content))
returnCode, selectionCounter = OLESub(ole, content, letter, rules, options)
returnCodeSub, selectionCounter = OLESub(ole, content, letter, rules, options)
returnCode = max(returnCode, returnCodeSub)
PrintWarningSelection(options.select, selectionCounter)
ole.close()
elif data.startswith(ACTIVEMIME_MAGIC):
Expand Down
13 changes: 7 additions & 6 deletions pdf-parser.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
#!/usr/bin/python
#!/usr/bin/env python

__description__ = 'pdf-parser, use it to parse a PDF document'
__author__ = 'Didier Stevens'
__version__ = '0.7.4'
__date__ = '2019/11/05'
__version__ = '0.7.5'
__date__ = '2021/07/03'
__minimum_python_version__ = (2, 5, 1)
__maximum_python_version__ = (3, 7, 5)
__maximum_python_version__ = (3, 9, 5)

"""
Source code put in public domain by Didier Stevens, no Copyright
Expand Down Expand Up @@ -69,6 +69,7 @@
2019/07/30: bug fixes (including fixes Josef Hinteregger)
2019/09/26: V0.7.3 added multiple id selection to option -o; added man page (-m); added environment variable PDFPARSER_OPTIONS; bug fixes
2019/11/05: V0.7.4 fixed plugin path when compiled with pyinstaller, replaced eval with int
2021/07/03: V0.7.5 bug fixes; fixed ASCII85Decode Python 3 bug thanks to R Primus
Todo:
- handle printf todo
Expand Down Expand Up @@ -978,7 +979,7 @@ def ConditionalCanonicalize(sIn, nocanonicalizedoutput):
def ASCII85Decode(data):
import struct
n = b = 0
out = ''
out = b''
for c in data:
if '!' <= c and c <= 'u':
n += 1
Expand All @@ -988,7 +989,7 @@ def ASCII85Decode(data):
n = b = 0
elif c == 'z':
assert n == 0
out += '\0\0\0\0'
out += b'\0\0\0\0'
elif c == '~':
if n:
for _ in range(5-n):
Expand Down
19 changes: 13 additions & 6 deletions pdfid.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@

__description__ = 'Tool to test a PDF file'
__author__ = 'Didier Stevens'
__version__ = '0.2.7'
__date__ = '2019/11/05'
__version__ = '0.2.8'
__date__ = '2020/11/21'

"""
Expand Down Expand Up @@ -56,6 +56,7 @@
2018/07/05: V0.2.5 introduced cExpandFilenameArguments; renamed option literal to literalfilenames
2019/09/30: V0.2.6 color bugfix, thanks to Leo
2019/11/05: V0.2.7 fixed plugin path when compiled with pyinstaller
2020/11/21: V0.2.8 added data argument to PDFiD function
Todo:
- update XML example (entropy, EOF)
Expand Down Expand Up @@ -84,6 +85,10 @@
import configparser as ConfigParser
else:
import ConfigParser
if sys.version_info[0] >= 3:
from io import BytesIO as DataIO
else:
from cStringIO import StringIO as DataIO

#Convert 2 Bytes If Python 3
def C2BIP3(string):
Expand All @@ -93,9 +98,11 @@ def C2BIP3(string):
return string

class cBinaryFile:
def __init__(self, file):
def __init__(self, file, data=None):
self.file = file
if file == '':
if data != None:
self.infile = DataIO(data)
elif file == '':
self.infile = sys.stdin
elif file.lower().startswith('http://') or file.lower().startswith('https://'):
try:
Expand Down Expand Up @@ -382,7 +389,7 @@ def ParseINIFile():
keywords.append(key)
return keywords

def PDFiD(file, allNames=False, extraData=False, disarm=False, force=False):
def PDFiD(file, allNames=False, extraData=False, disarm=False, force=False, data=None):
"""Example of XML output:
<PDFiD ErrorOccured="False" ErrorMessage="" Filename="test.pdf" Header="%PDF-1.1" IsPDF="True" Version="0.0.4" Entropy="4.28">
<Keywords>
Expand Down Expand Up @@ -454,7 +461,7 @@ def PDFiD(file, allNames=False, extraData=False, disarm=False, force=False):
try:
attIsPDF = xmlDoc.createAttribute('IsPDF')
xmlDoc.documentElement.setAttributeNode(attIsPDF)
oBinaryFile = cBinaryFile(file)
oBinaryFile = cBinaryFile(file, data)
if extraData:
oPDFDate = cPDFDate()
oEntropy = cEntropy()
Expand Down
48 changes: 35 additions & 13 deletions plugin_biff.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@

__description__ = 'BIFF plugin for oledump.py'
__author__ = 'Didier Stevens'
__version__ = '0.0.23'
__date__ = '2021/06/18'
__version__ = '0.0.24'
__date__ = '2021/08/12'

"""
Expand Down Expand Up @@ -52,6 +52,8 @@
2021/02/23: added PASSWORD record cracking
2021/05/23: 0.0.23 bruteforce
2021/06/18: added userdefinedfunction names (sample RANDBETWEEN)
2021/07/23: 0.0.24 added CreateXorKey_Method1
2021/08/12: continue xor deobfuscation
Todo:
updated parsing of records for BIFF5 record format
Expand Down Expand Up @@ -196,6 +198,8 @@ def ParseLocRelU(expression):
def ParseLoc(expression, cellrefformat, ignoreRelFlags=False):
formatcodes = 'HH'
formatsize = struct.calcsize(formatcodes)
if len(expression) < formatsize:
return '', expression
row, column = struct.unpack(formatcodes, expression[0:formatsize])
if ignoreRelFlags:
rowRelative = False
Expand Down Expand Up @@ -1352,13 +1356,14 @@ def Next(self):

def ParsePasswordFileName(filename):
dCharacterSets = {
'n': '0123456789',
'a': 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789',
'p': ''' !"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~''',
}

if not filename.startswith('#'):
return None
if filename[1] in ['a', 'p']:
if filename[1] in dCharacterSets:
return [dCharacterSets[filename[1]], int(filename[2:])]
else:
return [dCharacterSets['a'], int(filename[1:])]
Expand Down Expand Up @@ -4927,8 +4932,9 @@ def GetDictionary(passwordfile):
for password in passwordsJTR:
yield password
else:
for password in File2Strings(passwordfile):
yield password
with open(passwordfile, encoding='latin') as fPasswords:
for password in fPasswords:
yield password.rstrip('\n')

def CreatePasswordVerifier_Method1(password):
verifier = 0
Expand All @@ -4945,11 +4951,27 @@ def CreatePasswordVerifier_Method1(password):
verifier = intermediate3 ^ P23Ord(passwordbyte)
return verifier ^ 0xCE4B

def CreateXorKey_Method1(password):
password = password[:15]
initialCode = ( 0xE1F0, 0x1D0F, 0xCC9C, 0x84C0, 0x110C, 0x0E10, 0xF1CE, 0x313E, 0x1872, 0xE139, 0xD40F, 0x84F9, 0x280C, 0xA96A, 0x4EC3 )
xorMatrix = ( 0xAEFC, 0x4DD9, 0x9BB2, 0x2745, 0x4E8A, 0x9D14, 0x2A09, 0x7B61, 0xF6C2, 0xFDA5, 0xEB6B, 0xC6F7, 0x9DCF, 0x2BBF, 0x4563, 0x8AC6, 0x05AD, 0x0B5A, 0x16B4, 0x2D68, 0x5AD0, 0x0375, 0x06EA, 0x0DD4, 0x1BA8, 0x3750, 0x6EA0, 0xDD40, 0xD849, 0xA0B3, 0x5147, 0xA28E, 0x553D, 0xAA7A, 0x44D5, 0x6F45, 0xDE8A, 0xAD35, 0x4A4B, 0x9496, 0x390D, 0x721A, 0xEB23, 0xC667, 0x9CEF, 0x29FF, 0x53FE, 0xA7FC, 0x5FD9, 0x47D3, 0x8FA6, 0x0F6D, 0x1EDA, 0x3DB4, 0x7B68, 0xF6D0, 0xB861, 0x60E3, 0xC1C6, 0x93AD, 0x377B, 0x6EF6, 0xDDEC, 0x45A0, 0x8B40, 0x06A1, 0x0D42, 0x1A84, 0x3508, 0x6A10, 0xAA51, 0x4483, 0x8906, 0x022D, 0x045A, 0x08B4, 0x1168, 0x76B4, 0xED68, 0xCAF1, 0x85C3, 0x1BA7, 0x374E, 0x6E9C, 0x3730, 0x6E60, 0xDCC0, 0xA9A1, 0x4363, 0x86C6, 0x1DAD, 0x3331, 0x6662, 0xCCC4, 0x89A9, 0x0373, 0x06E6, 0x0DCC, 0x1021, 0x2042, 0x4084, 0x8108, 0x1231, 0x2462, 0x48C4 )

xorkey = initialCode[len(password) - 1]
currentElement = 0x68
for char in password[::-1]:
charValue = ord(char)
for i in range(7):
if charValue & 0x40 != 0:
xorkey = xorkey ^ xorMatrix[currentElement]
charValue = charValue * 2
currentElement -= 1
return xorkey

def AnalyzeXORObfuscationStructure(data, passwordlistFilename):
key, verifier = struct.unpack('<HH', data)
password = None
for candidate in GetDictionary(passwordlistFilename):
if CreatePasswordVerifier_Method1(candidate) == verifier:
if CreatePasswordVerifier_Method1(candidate) == verifier and CreateXorKey_Method1(candidate) == key:
password = candidate
break
return key, verifier, password
Expand Down Expand Up @@ -5358,7 +5380,7 @@ def Analyze(self):

# FORMULA record
if opcode == 0x06 and len(data) >= 21:
if not filepassFound:
if not filepassFound or decrypted:
cellref, dummy = ParseLoc(data, options.cellrefformat, True)
formatcodes = 'H'
formatsize = struct.calcsize(formatcodes)
Expand All @@ -5377,7 +5399,7 @@ def Analyze(self):

# LABEL record #a# difference BIFF4 and BIFF5+
if opcode == 0x18 and len(data) >= 16:
if not filepassFound:
if not filepassFound or decrypted:
flags = P23Ord(data[0])
lnName = P23Ord(data[3])
szFormula = P23Ord(data[4]) + P23Ord(data[5]) * 0x100
Expand Down Expand Up @@ -5465,7 +5487,7 @@ def Analyze(self):

# BOF record
if opcode == 0x0809 and len(data) >= 8:
if not filepassFound:
if not filepassFound or decrypted:
formatcodes = '<HHHH'
formatsize = struct.calcsize(formatcodes)
vers, dt, rupBuild, rupYear = struct.unpack(formatcodes, data[0:formatsize])
Expand All @@ -5479,7 +5501,7 @@ def Analyze(self):

# STRING record
if opcode == 0x207 and len(data) >= 4:
if not filepassFound:
if not filepassFound or decrypted:
values = list(Strings(data[3:]).values())
strings = ''
if values[0] != []:
Expand All @@ -5492,7 +5514,7 @@ def Analyze(self):

# number record
if opcode == 0x0203:
if not filepassFound:
if not filepassFound or decrypted:
cellref, data2 = ParseLoc(data, options.cellrefformat, True)
formatcodes = '<Hd'
formatsize = struct.calcsize(formatcodes)
Expand All @@ -5502,7 +5524,7 @@ def Analyze(self):

# RK record
if opcode == 0x027E and len(data) == 10:
if not filepassFound:
if not filepassFound or decrypted:
cellref, data2 = ParseLoc(data, options.cellrefformat, True)
formatcodes = '<H'
formatsize = struct.calcsize(formatcodes)
Expand All @@ -5514,7 +5536,7 @@ def Analyze(self):
# unknown record
#a# handle more than one UDF
if opcode == 0x23 and len(data) > 8:
if not filepassFound:
if not filepassFound or decrypted:
dummy1, dummy2, stringLength, remainder = Unpack('<IHH', data)
userdefinedfunctionNames.append(remainder[:stringLength].decode())

Expand Down

0 comments on commit 1f9a661

Please sign in to comment.