Commit 8dba5609 authored by Sophie Brun's avatar Sophie Brun

Imported Upstream version 0.3

parent 846ea3d4
-----------------------------------------------
peepdf 0.3 r235, 2014-06-09
-----------------------------------------------
* New features:
- Added descriptive titles for the vulns found
- Added detection of CVE-2013-2729 (Adobe Reader BMP/RLE heap corruption)
- Added support for more than one script block in objects containing Javascript (e.g. XFA objects)
- Updated colorama to version 3.1 (2014-04-19)
- Added detection of CVE-2013-3346 (ToolButton Use-After-Free)
- Added command "js_vars" to show the variables defined in the Javascript context and their content
- Added command "js_jjdecode" to decode Javascript code using the jjencode algorithm (Thanks to Nahuel Riva @crackinglandia)
- Added static detection for CVE-2010-0188
- Added detection for CoolType.dll SING uniqueName vulnerability (CVE-2010-2883). Better late than never ;p
- Added new command "vtcheck" to check for detection on VirusTotal (API key included)
- Added option to avoid automatic Javascript analysis (useful with endless loops)
- Added PyV8 as Javascript engine and removed Spidermonkey (Windows issues).
* Fixes:
- Fixed bug when encrypting/decrypting hexadecimal objects (Thanks to Timo Hirvonen for the feedback)
- Fixed silly bug related to abbreviated PDF Filters
- Fixed bug related to the GNU readline function not handling correctly colorized prompts
- Fixed log_output function, it was storing the previous command output instead of the current one
- Fixed bug in PDFStream to show the stream content when the stream dictionary is empty (Thanks to Nahuel Riva)
- Fixed Issue 12, related to bad JS code parsing due to HTML entities in the XFA form (Thanks to robomotic)
- Fixed Issue 10 related to bad error handling in the PDFFile.decrypt() method
- Fixed Issue 9, related to an uncaught exception when PyV8 is not installed
- Fixed bug in do_metadata() when objects contain /Metadata but they are not really Metadata objects
* Others
- Removed the old redirection method using the "set" command, it is useless now with the shell-like redirection (>, >>, $>, $>>)
* Known issues
- It exists a problem related to the readline module in Mac OS X (it uses editline instead of GNU readline), not handling correctly colorized prompts.
-----------------------------------------------
peepdf Black Hat Vegas (0.2 r156), 2012-07-25
-----------------------------------------------
......
#
# peepdf is a tool to analyse and modify PDF files
# http://peepdf.eternal-todo.com
# By Jose Miguel Esparza <jesparza AT eternal-todo.com>
# peepdf is a tool to analyse and modify PDF files
# http://peepdf.eternal-todo.com
# By Jose Miguel Esparza <jesparza AT eternal-todo.com>
#
# Copyright (C) 2012 Jose Miguel Esparza
# Copyright (C) 2011-2014 Jose Miguel Esparza
#
# This file is part of peepdf.
# This file is part of peepdf.
#
# peepdf is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
# peepdf is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# peepdf is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
# peepdf is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with peepdf. If not, see <http://www.gnu.org/licenses/>.
# You should have received a copy of the GNU General Public License
# along with peepdf. If not, see <http://www.gnu.org/licenses/>.
#
'''
This module contains some functions to analyse Javascript code inside the PDF file
'''
import sys, re , os, jsbeautifier
from PDFUtils import unescapeHTMLEntities
import sys, re , os, jsbeautifier, traceback
from PDFUtils import unescapeHTMLEntities, escapeString
try:
from spidermonkey import Runtime
JS_MODULE = True
import PyV8
JS_MODULE = True
class Global(PyV8.JSClass):
evalCode = ''
def evalOverride(self, expression):
self.evalCode += '\n\n// New evaluated code\n' + expression
return
except:
JS_MODULE = False
JS_MODULE = False
errorsFile = 'errors.txt'
newLine = os.linesep
reJSscript = '<script[^>]*?contentType\s*?=\s*?[\'"]application/x-javascript[\'"][^>]*?>(.*?)</script>'
def analyseJS(code):
preDefinedCode = 'var app = this;'
def analyseJS(code, context = None, manualAnalysis = False):
'''
Search for obfuscated functions in the Javascript code
Hooks the eval function and search for obfuscated elements in the Javascript code
@param code: The Javascript code (string)
@return: List with analysis information of the Javascript code: [JSCode,unescapedBytes,urlsFound], where JSCode is a list with the several stages Javascript code, unescapedBytes is a list with the parameters of unescape functions, and urlsFound is a list with the URLs found in the unescaped bytes.
@return: List with analysis information of the Javascript code: [JSCode,unescapedBytes,urlsFound,errors,context], where
JSCode is a list with the several stages Javascript code,
unescapedBytes is a list with the parameters of unescape functions,
urlsFound is a list with the URLs found in the unescaped bytes,
errors is a list of errors,
context is the context of execution of the Javascript code.
'''
error = ''
errors = []
JSCode = []
unescapedBytes = []
urlsFound = []
oldStdErr = sys.stderr
errorFile = open('jserror.log','wb')
sys.stderr = errorFile
try:
scriptCode = re.findall(reJSscript, code, re.DOTALL | re.IGNORECASE)
if scriptCode != []:
code = scriptCode[0]
code = unescapeHTMLEntities(code)
scriptElements = re.findall(reJSscript, code, re.DOTALL | re.IGNORECASE)
if scriptElements != []:
code = ''
for scriptElement in scriptElements:
code += scriptElement + '\n\n'
code = jsbeautifier.beautify(code)
JSCode.append(code)
if code != None and JS_MODULE:
r = Runtime()
context = r.new_context()
if code != None and JS_MODULE and not manualAnalysis:
if context == None:
context = PyV8.JSContext(Global())
context.enter()
# Hooking the eval function
context.eval('eval=evalOverride')
#context.eval(preDefinedCode)
while True:
evalFunctionsData = searchObfuscatedFunctions(code, 'eval')
originalElement = code
for evalFunctionData in evalFunctionsData:
if not evalFunctionData[2]:
modifiedCode = evalFunctionData[1][0].replace(evalFunctionData[0],'return')
code = originalElement.replace(evalFunctionData[1][0],modifiedCode)
originalCode = code
try:
context.eval(code)
evalCode = context.eval('evalCode')
evalCode = jsbeautifier.beautify(evalCode)
if evalCode != '' and evalCode != code:
code = evalCode
JSCode.append(code)
else:
code = originalElement.replace(evalFunctionData[1][0],evalFunctionData[1][1]+';')
try:
executedJS = context.eval_script(code)
if executedJS == None:
raise exception
break
except:
if evalFunctionData[2]:
modifiedCode = evalFunctionData[1][0].replace(evalFunctionData[0],'return')
code = originalElement.replace(evalFunctionData[1][0],modifiedCode)
else:
code = originalElement.replace(evalFunctionData[1][0],evalFunctionData[1][1]+';')
try:
executedJS = context.eval_script(code)
if executedJS == None:
raise exception
except:
code = originalElement
continue
else:
break
if executedJS != originalElement and executedJS != None and executedJS != '':
code = executedJS
JSCode.append(code)
else:
except:
error = str(sys.exc_info()[1])
open('jserror.log','ab').write(error + newLine)
errors.append(error)
break
if code != None:
if code != '':
escapedVars = re.findall('(\w*?)\s*?=\s*?(unescape\((.*?)\))', code, re.DOTALL)
for var in escapedVars:
bytes = var[2]
if bytes.find('+') != -1:
if bytes.find('+') != -1 or bytes.find('%') == -1:
varContent = getVarContent(code, bytes)
if len(varContent) > 150:
ret = unescape(varContent)
......@@ -126,29 +128,22 @@ def analyseJS(code):
if url not in urlsFound:
urlsFound.append(url)
except:
errors.append('Unknown error!!')
traceback.print_exc(file=open(errorsFile,'a'))
errors.append('Unexpected error in the JSAnalysis module!!')
finally:
errorFile.close()
sys.stderr = oldStdErr
errorFileContent = open('jserror.log','rb').read()
if errorFileContent != '' and errorFileContent.find('JavaScript error') != -1:
lines = errorFileContent.split(newLine)
for line in lines:
if line.find('JavaScript error') != -1 and line not in errors:
errors.append(line)
for js in JSCode:
if js == None or js == '':
JSCode.remove(js)
return [JSCode,unescapedBytes,urlsFound,errors]
return [JSCode,unescapedBytes,urlsFound,errors,context]
def getVarContent(jsCode, varContent):
'''
Given the Javascript code and the content of a variable this method tries to obtain the real value of the variable, cleaning expressions like "a = eval; a(js_code);"
@param jsCode: The Javascript code (string)
@param varContent: The content of the variable (string)
@return: A string with real value of the variable
'''
Given the Javascript code and the content of a variable this method tries to obtain the real value of the variable, cleaning expressions like "a = eval; a(js_code);"
@param jsCode: The Javascript code (string)
@param varContent: The content of the variable (string)
@return: A string with real value of the variable
'''
clearBytes = ''
varContent = varContent.replace('\n','')
varContent = varContent.replace('\r','')
......@@ -159,6 +154,7 @@ def getVarContent(jsCode, varContent):
if re.match('["\'].*?["\']', part, re.DOTALL):
clearBytes += part[1:-1]
else:
part = escapeString(part)
varContent = re.findall(part + '\s*?=\s*?(.*?)[,;]', jsCode, re.DOTALL)
if varContent != []:
clearBytes += getVarContent(jsCode, varContent[0])
......@@ -200,25 +196,25 @@ def isJavascript(content):
def searchObfuscatedFunctions(jsCode, function):
'''
Search for obfuscated functions in the Javascript code
@param jsCode: The Javascript code (string)
@param function: The function name to look for (string)
@return: List with obfuscated functions information [functionName,functionCall,containsReturns]
'''
Search for obfuscated functions in the Javascript code
@param jsCode: The Javascript code (string)
@param function: The function name to look for (string)
@return: List with obfuscated functions information [functionName,functionCall,containsReturns]
'''
obfuscatedFunctionsInfo = []
if jsCode != None:
match = re.findall('\W('+function+'\s{0,5}?\((.*?)\)\s{0,5}?;)', jsCode, re.DOTALL)
if match != []:
for m in match:
if re.findall('return',m[1],re.IGNORECASE) != []:
obfuscatedFunctionsInfo.append([function,m,True])
else:
obfuscatedFunctionsInfo.append([function,m,False])
obfuscatedFunctions = re.findall('\s*?((\w*?)\s*?=\s*?'+function+')\s*?;', jsCode, re.DOTALL)
for obfuscatedFunction in obfuscatedFunctions:
obfuscatedElement = obfuscatedFunction[1]
obfuscatedFunctionsInfo += searchObfuscatedFunctions(jsCode, obfuscatedElement)
match = re.findall('\W('+function+'\s{0,5}?\((.*?)\)\s{0,5}?;)', jsCode, re.DOTALL)
if match != []:
for m in match:
if re.findall('return',m[1],re.IGNORECASE) != []:
obfuscatedFunctionsInfo.append([function,m,True])
else:
obfuscatedFunctionsInfo.append([function,m,False])
obfuscatedFunctions = re.findall('\s*?((\w*?)\s*?=\s*?'+function+')\s*?;', jsCode, re.DOTALL)
for obfuscatedFunction in obfuscatedFunctions:
obfuscatedElement = obfuscatedFunction[1]
obfuscatedFunctionsInfo += searchObfuscatedFunctions(jsCode, obfuscatedElement)
return obfuscatedFunctionsInfo
def unescape(escapedBytes, unicode = True):
......@@ -235,8 +231,11 @@ def unescape(escapedBytes, unicode = True):
else:
unicodePadding = ''
try:
if escapedBytes.find('%u') != -1 or escapedBytes.find('%U') != -1 or escapedBytes.find('%') != -1:
splitBytes = escapedBytes.split('%')
if escapedBytes.lower().find('%u') != -1 or escapedBytes.lower().find('\u') != -1 or escapedBytes.find('%') != -1:
if escapedBytes.lower().find('\u') != -1:
splitBytes = escapedBytes.split('\\')
else:
splitBytes = escapedBytes.split('%')
for i in range(len(splitBytes)):
splitByte = splitBytes[i]
if splitByte == '':
......
This source diff could not be displayed because it is too large. You can view the blob instead.
......@@ -3,7 +3,7 @@
# http://peepdf.eternal-todo.com
# By Jose Miguel Esparza <jesparza AT eternal-todo.com>
#
# Copyright (C) 2012 Jose Miguel Esparza
# Copyright (C) 2011-2014 Jose Miguel Esparza
#
# This file is part of peepdf.
#
......@@ -40,13 +40,49 @@ MAL_BAD_HEAD = 6
pdfFile = None
newLine = os.linesep
isForceMode = False
isManualAnalysis = False
spacesChars = ['\x00','\x09','\x0a','\x0c','\x0d','\x20']
delimiterChars = ['<<','(','<','[','{','/','%']
monitorizedEvents = ['/OpenAction ','/AA ','/Names ','/AcroForm ']
monitorizedEvents = ['/OpenAction ','/AA ','/Names ','/AcroForm ', '/XFA ']
monitorizedActions = ['/JS ','/JavaScript','/Launch','/SubmitForm','/ImportData']
monitorizedElements = ['/EmbeddedFiles ','/EmbeddedFile','/JBIG2Decode','getPageNthWord','arguments.callee','/U3D','/PRC']
jsVulns = ['mailto','Collab.collectEmailInfo','util.printf','getAnnots','getIcon','spell.customDictionaryOpen','media.newPlayer','doc.printSeps']
vulnsDict = {'/JBIG2Decode':['CVE-2009-0658'],'mailto':['CVE-2007-5020'],'Collab.collectEmailInfo':['CVE-2007-5659'],'util.printf':['CVE-2008-2992'],'getAnnots':['CVE-2009-1492'],'getIcon':['CVE-2009-0927'],'spell.customDictionaryOpen':['CVE-2009-1493'],'media.newPlayer':['CVE-2009-4324'],'doc.printSeps':['CVE-2010-4091'],'/U3D':['CVE-2009-3953','CVE-2009-3959','CVE-2011-2462'],'/PRC':['CVE-2011-4369']}
monitorizedElements = ['/EmbeddedFiles ',
'/EmbeddedFile',
'/JBIG2Decode',
'getPageNthWord',
'arguments.callee',
'/U3D',
'/PRC',
'/RichMedia',
'.rawValue',
'keep.previous']
jsVulns = ['mailto',
'Collab.collectEmailInfo',
'util.printf',
'getAnnots',
'getIcon',
'spell.customDictionaryOpen',
'media.newPlayer',
'doc.printSeps',
'app.removeToolButton']
singUniqueName = 'CoolType.SING.uniqueName'
bmpVuln = 'BMP/RLE heap corruption'
vulnsDict = {'mailto':('mailto',['CVE-2007-5020']),
'Collab.collectEmailInfo':('Collab.collectEmailInfo',['CVE-2007-5659']),
'util.printf':('util.printf',['CVE-2008-2992']),
'/JBIG2Decode':('Adobe JBIG2Decode Heap Corruption',['CVE-2009-0658']),
'getIcon':('getIcon',['CVE-2009-0927']),
'getAnnots':('getAnnots',['CVE-2009-1492']),
'spell.customDictionaryOpen':('spell.customDictionaryOpen',['CVE-2009-1493']),
'media.newPlayer':('media.newPlayer',['CVE-2009-4324']),
'.rawValue':('Adobe Acrobat Bundled LibTIFF Integer Overflow',['CVE-2010-0188']),
singUniqueName:(singUniqueName,['CVE-2010-2883']),
'doc.printSeps':('doc.printSeps',['CVE-2010-4091']),
'/U3D':('/U3D',['CVE-2009-3953','CVE-2009-3959','CVE-2011-2462']),
'/PRC':('/PRC',['CVE-2011-4369']),
'keep.previous':('Adobe Reader XFA oneOfChild Un-initialized memory vulnerability',['CVE-2013-0640']), # https://labs.portcullis.co.uk/blog/cve-2013-0640-adobe-reader-xfa-oneofchild-un-initialized-memory-vulnerability-part-1/
bmpVuln:(bmpVuln,['CVE-2013-2729']),
'app.removeToolButton':('app.removeToolButton',['CVE-2013-3346'])}
jsContexts = {'global':None}
class PDFObject :
'''
......@@ -550,7 +586,7 @@ class PDFString (PDFObject) :
return (-1,errorMessage)
if isJavascript(self.value):
self.containsJScode = True
self.JSCode, self.unescapedBytes, self.urlsFound, jsErrors = analyseJS(self.value)
self.JSCode, self.unescapedBytes, self.urlsFound, jsErrors, jsContexts['global'] = analyseJS(self.value, jsContexts['global'], isManualAnalysis)
if jsErrors != []:
for jsError in jsErrors:
errorMessage = 'Error analysing Javascript: '+jsError
......@@ -655,9 +691,9 @@ class PDFHexString (PDFObject) :
self.errors = []
self.compressedIn = None
self.encrypted = False
self.value = ''
self.rawValue = hex
self.encryptedValue = hex
self.value = '' # Value after hex decoding and decryption
self.rawValue = hex # Hex characters
self.encryptedValue = hex # Value after hex decoding
self.updateNeeded = False
self.containsJScode = False
self.JSCode = []
......@@ -672,7 +708,7 @@ class PDFHexString (PDFObject) :
else:
raise Exception(ret[1])
def update(self, decrypt = False):
def update(self, decrypt = False, newHexValue = True):
'''
Updates the object after some modification has occurred
......@@ -680,24 +716,30 @@ class PDFHexString (PDFObject) :
@return: A tuple (status,statusContent), where statusContent is empty in case status = 0 or an error message in case status = -1
'''
self.errors = []
self.value = ''
self.containsJScode = False
self.JSCode = []
self.unescapedBytes = []
self.urlsFound = []
tmpValue = self.rawValue
if len(tmpValue) % 2 != 0:
tmpValue += '0'
try:
for i in range(0,len(tmpValue),2):
self.value += chr(int(tmpValue[i:i+2],16))
except:
errorMessage = 'Error in hexadecimal conversion'
self.addError(errorMessage)
return (-1,errorMessage)
if not decrypt:
try:
if newHexValue:
# New hexadecimal value
self.value = ''
tmpValue = self.rawValue
if len(tmpValue) % 2 != 0:
tmpValue += '0'
self.value = tmpValue.decode('hex')
else:
# New decoded value
self.rawValue = self.value.encode('hex')
self.encryptedValue = self.value
except:
errorMessage = 'Error in hexadecimal conversion'
self.addError(errorMessage)
return (-1,errorMessage)
if isJavascript(self.value):
self.containsJScode = True
self.JSCode, self.unescapedBytes, self.urlsFound, jsErrors = analyseJS(self.value)
self.JSCode, self.unescapedBytes, self.urlsFound, jsErrors, jsContexts['global'] = analyseJS(self.value, jsContexts['global'], isManualAnalysis)
if jsErrors != []:
for jsError in jsErrors:
errorMessage = 'Error analysing Javascript: '+jsError
......@@ -716,7 +758,8 @@ class PDFHexString (PDFObject) :
if password != None:
self.encryptionKey = password
try:
self.encryptedValue = RC4(self.rawValue,self.encryptionKey)
self.encryptedValue = RC4(self.value,self.encryptionKey)
self.rawValue = self.encryptedValue.encode('hex')
except:
errorMessage = 'Error encrypting with RC4'
self.addError(errorMessage)
......@@ -736,11 +779,11 @@ class PDFHexString (PDFObject) :
try:
cleanString = unescapeString(self.encryptedValue)
if algorithm == 'RC4':
self.rawValue = RC4(cleanString,self.encryptionKey)
self.value = RC4(cleanString,self.encryptionKey)
elif algorithm == 'AES':
ret = AES.decryptData(cleanString,self.encryptionKey)
if ret[0] != -1:
self.rawValue = ret[1]
self.value = ret[1]
else:
errorMessage = 'AES decryption error: '+ret[1]
self.addError(errorMessage)
......@@ -753,7 +796,7 @@ class PDFHexString (PDFObject) :
return ret
def getEncryptedValue(self):
return '<'+self.encryptedValue+'>'
return '<'+self.rawValue+'>'
def getJSCode(self):
'''
......@@ -908,8 +951,8 @@ class PDFArray (PDFObject) :
self.unescapedBytes += element.getUnescapedBytes()
self.urlsFound += element.getURLs()
if element.isFaulty():
errorMessage = 'Children element is faulty'
self.addError(errorMessage)
for error in valueObject.getErrors():
self.addError('Children element contains errors: ' + error)
if type in ['string','hexstring','array','dictionary'] and self.encrypted and not decrypt:
ret = element.encrypt(self.encryptionKey)
if ret[0] == -1:
......@@ -1195,8 +1238,8 @@ class PDFDictionary (PDFObject):
self.unescapedBytes += valueObject.getUnescapedBytes()
self.urlsFound += valueObject.getURLs()
if valueObject.isFaulty():
errorMessage = 'Children element is faulty'
self.addError(errorMessage)
for error in valueObject.getErrors():
self.addError('Children element contains errors: ' + error)
if self.rawNames.has_key(keys[i]):
rawName = self.rawNames[keys[i]]
rawValue = rawName.getRawValue()
......@@ -1536,15 +1579,18 @@ class PDFStream (PDFDictionary) :
self.file = None
self.isEncodedStream = False
self.decodingError = False
if elements != {}:
ret = self.update()
if ret[0] == -1:
if isForceMode:
self.addError(ret[1])
else:
raise Exception(ret[1])
else:
self.addError('No dictionary in stream object')
if elements == {}:
errorMessage = 'No dictionary in stream object'
if isForceMode:
self.addError(errorMessage)
else:
raise Exception(errorMessage)
ret = self.update()
if ret[0] == -1:
if isForceMode:
self.addError(ret[1])
else:
raise Exception(ret[1])
def update(self, onlyElements = False, decrypt = False, algorithm = 'RC4'):
'''
......@@ -1659,8 +1705,8 @@ class PDFStream (PDFDictionary) :
self.unescapedBytes = list(set(self.unescapedBytes + valueElement.getUnescapedBytes()))
self.urlsFound = list(set(self.urlsFound + valueElement.getURLs()))
if valueElement.isFaulty():
errorMessage = 'Children element is faulty'
self.addError(errorMessage)
for error in valueObject.getErrors():
self.addError('Children element contains errors: ' + error)
if self.rawNames.has_key(keys[i]):
rawName = self.rawNames[keys[i]]
rawValue = rawName.getRawValue()
......@@ -1717,7 +1763,7 @@ class PDFStream (PDFDictionary) :
self.references = list(set(self.references))
if isJavascript(self.decodedStream):
self.containsJScode = True
self.JSCode, self.unescapedBytes, self.urlsFound, jsErrors = analyseJS(self.decodedStream)
self.JSCode, self.unescapedBytes, self.urlsFound, jsErrors, jsContexts['global'] = analyseJS(self.decodedStream, jsContexts['global'], isManualAnalysis)
if jsErrors != []:
for jsError in jsErrors:
errorMessage = 'Error analysing Javascript: '+jsError
......@@ -1816,7 +1862,7 @@ class PDFStream (PDFDictionary) :
self.references = list(set(self.references))
if isJavascript(self.decodedStream):
self.containsJScode = True
self.JSCode, self.unescapedBytes, self.urlsFound, jsErrors = analyseJS(self.decodedStream)
self.JSCode, self.unescapedBytes, self.urlsFound, jsErrors, jsContexts['global'] = analyseJS(self.decodedStream, jsContexts['global'], isManualAnalysis)
if jsErrors != []:
for jsError in jsErrors:
errorMessage = 'Error analysing Javascript: '+jsError
......@@ -1887,7 +1933,7 @@ class PDFStream (PDFDictionary) :
self.references = list(set(self.references))
if isJavascript(self.decodedStream):
self.containsJScode = True
self.JSCode, self.unescapedBytes, self.urlsFound, jsErrors = analyseJS(self.decodedStream)
self.JSCode, self.unescapedBytes, self.urlsFound, jsErrors, jsContexts['global'] = analyseJS(self.decodedStream, jsContexts['global'], isManualAnalysis)
if jsErrors != []:
for jsError in jsErrors:
errorMessage = 'Error analysing Javascript: '+jsError
......@@ -1950,12 +1996,14 @@ class PDFStream (PDFDictionary) :
break
'''
streamLength = len(stream)
'''
if streamLength > 1 and stream[:2] == '\r\n':
stream = stream[2:]
streamLength -= 2
elif streamLength > 0 and (stream[0] == '\r' or stream[0] == '\n'):
stream = stream[1:]
streamLength -= 1
'''
if streamLength > 1 and stream[-2:] == '\r\n':
stream = stream[:-2]
elif streamLength > 0 and (stream[-1] == '\r' or stream[-1] == '\n'):
......@@ -2476,7 +2524,7 @@ class PDFStream (PDFDictionary) :
self.references = list(set(self.references))
if isJavascript(self.decodedStream):
self.containsJScode = True
self.JSCode, self.unescapedBytes, self.urlsFound, jsErrors = analyseJS(self.decodedStream)
self.JSCode, self.unescapedBytes, self.urlsFound, jsErrors, jsContexts['global'] = analyseJS(self.decodedStream, jsContexts['global'], isManualAnalysis)
if jsErrors != []:
for jsError in jsErrors:
errorMessage = 'Error analysing Javascript: '+jsError
......@@ -2937,9 +2985,9 @@ class PDFObjectStream (PDFStream) :
else:
try:
if algorithm == 'RC4':
self.decodedStream = RC4(self.decodedStream,self.encryptionKey)
self.decodedStream = RC4(self.rawStream,self.encryptionKey)
elif algorithm == 'AES':
ret = AES.decryptData(self.decodedStream,self.encryptionKey)
ret = AES.decryptData(self.rawStream,self.encryptionKey)
if ret[0] != -1:
self.decodedStream = ret[1]
else:
......@@ -2984,7 +3032,7 @@ class PDFObjectStream (PDFStream) :
self.references = list(set(self.references))
if isJavascript(self.decodedStream):
self.containsJScode = True
self.JSCode, self.unescapedBytes, self.urlsFound, jsErrors = analyseJS(self.decodedStream)
self.JSCode, self.unescapedBytes, self.urlsFound, jsErrors, jsContexts['global'] = analyseJS(self.decodedStream, jsContexts['global'], isManualAnalysis)
if jsErrors != []:
for jsError in jsErrors:
errorMessage = 'Error analysing Javascript: '+jsError
......@@ -4162,7 +4210,6 @@ class PDFBody :
def updateOffsets (self) :
pass
def updateStats(self, id, pdfObject, delete = False):
if pdfObject == None:
......@@ -4231,6 +4278,30 @@ class PDFBody :
self.vulns[vuln].append(id)
else:
self.vulns[vuln] = [id]
## Extra checks
objectType = pdfObject.getType()
if objectType == 'stream':
vulnFound = None
streamContent = pdfObject.getStream()
if len(streamContent) > 327 and streamContent[236:240] == 'SING' and streamContent[327] != '\0':
# CVE-2010-2883
# http://opensource.adobe.com/svn/opensource/tin/src/SING.cpp
# http://community.websense.com/blogs/securitylabs/archive/2010/09/10/brief-analysis-on-adobe-reader-sing-table-parsing-vulnerability-cve-2010-2883.aspx
vulnFound = singUniqueName
elif streamContent.count('AAL/AAAC/wAAAv8A') > 1000:
# CVE-2013-2729
# Adobe Reader BMP/RLE heap corruption
# http://blog.binamuse.com/2013/05/readerbmprle.html
vulnFound = bmpVuln
if vulnFound != None:
if self.suspiciousElements.has_key(vulnFound):
if delete:
if id in self.suspiciousElements[vulnFound]:
self.suspiciousElements[vulnFound].remove(id)
elif id not in self.suspiciousElements[vulnFound]:
self.suspiciousElements[vulnFound].append(id)
elif not delete:
self.suspiciousElements[vulnFound] = [id]
return (0,'')
......@@ -4525,6 +4596,8 @@ class PDFFile :
self.md5 = ''
self.sha1 = ''
self.sha256 = ''
self.detectionRate = []
self.detectionReport = ''
self.body = [] # PDFBody[]
self.binary = False
self.binaryChars = ''
......@@ -4841,6 +4914,8 @@ class PDFFile :
return (0,lastId)
def decrypt(self, password = ''):
badPassword = False
fatalError = False
errorMessage = ''
passType = None
encryptionAlgorithms = []
......@@ -4848,6 +4923,11 @@ class PDFFile :
stmAlgorithm = None
strAlgorithm = None
embedAlgorithm = None
computedUserPass = ''
dictO = ''
dictU = ''
perm = 0
revision = 0
fileId = self.getFileId()
self.removeError(errorType = 'Decryption error')
if self.encryptDict == None or self.encryptDict[1] == []:
......@@ -4866,18 +4946,21 @@ class PDFFile :
if filter != '/Standard':
errorMessage = 'Decryption error: Filter not supported!!'
if isForceMode:
fatalError = True
self.addError(errorMessage)
else:
return (-1, errorMessage)
else:
errorMessage = 'Decryption error: Bad format for /Filter!!'
if isForceMode:
fatalError = True
self.addError(errorMessage)
else:
return (-1, errorMessage)
else:
errorMessage = 'Decryption error: Filter not found!!'
if isForceMode:
fatalError = True
self.addError(errorMessage)
else:
return (-1, errorMessage)
......@@ -4923,9 +5006,9 @@ class PDFFile :
else:
return (-1, errorMessage)
else:
cfmValue = ''
errorMessage = 'Decryption error: Bad format for /CFM!!'
if isForceMode:
cfmValue = ''
self.addError(errorMessage)
else: