diff --git a/README b/README index fb835e3..7997115 100644 --- a/README +++ b/README @@ -23,7 +23,11 @@ http://twitter.com/peepdf ** Installation ** -Run, in peepdf directory +You can either + pip install peepdf +This module comes from jbremer fork. + +Or download the source and then run inside it : easy_install . @@ -42,38 +46,36 @@ There are two important options when peepdf is executed: Shows the statistics of the file after being decoded/decrypted and analysed: - peepdf.py [options] pdf_file + peepdf [options] pdf_file * Interactive console Executes the interactive console to let play with the PDF file: - peepdf.py -i [options] pdf_file + peepdf -i [options] pdf_file If no PDF file is specified it's possible to use the decode/encode/js*/sctest commands and create a new PDF file: - peepdf.py -i + peepdf -i * Batch execution It's possible to use a commands file to specify the commands to be executed in the batch mode. This type of execution is good to automatise analysis of several files: - peepdf.py [options] -s commands_file pdf_file + peepdf [options] -s commands_file pdf_file ** Updating ** -The option has been desactivated as it is not working for now. -To update, cd to peepdf directory and type: +No more updating other than pip utility. +Or run inside peepdf dir : - git pull origin master + git pull easy_install . - - ** Some hints ** If the information shown when a PDF file is parsed is not enough to know if it's harmful or not, the following commands can help to do it: diff --git a/peepdf/PDFConsole.py b/peepdf/PDFConsole.py index c20ae0a..007b1b9 100644 --- a/peepdf/PDFConsole.py +++ b/peepdf/PDFConsole.py @@ -44,6 +44,8 @@ PDFReference, PDFString, PDFArray, PDFBool, PDFNull, vulnsDict, PDFParser ) +from peepdf.PDFOutput import PDFOutput + from base64 import b64encode, b64decode from PDFFilters import decodeStream, encodeStream from peepdf.jjdecode import JJDecoder @@ -79,7 +81,6 @@ VAR_WRITE = 3 VAR_ADD = 4 newLine = os.linesep -errorsFile = os.path.expanduser("~/.peepdf-error.txt") filter2RealFilterDict = {'b64': 'base64', 'base64': 'base64', 'asciihex': '/ASCIIHexDecode', 'ahx': '/ASCIIHexDecode', 'ascii85': '/ASCII85Decode', 'a85': '/ASCII85Decode', 'lzw': '/LZWDecode', @@ -88,43 +89,44 @@ 'jbig2': '/JBIG2Decode', 'dct': '/DCTDecode', 'jpx': '/JPXDecode'} -class PDFConsole(cmd.Cmd): +class PDFConsole(PDFOutput, cmd.Cmd): ''' Class of the peepdf interactive console. To see details about commands: http://code.google.com/p/peepdf/wiki/Commands ''' - def __init__(self, pdfFile, vtKey, avoidOutputColors=False, stdin=None, batchMode=False, jsonOutput=False): - global COLORIZED_OUTPUT - cmd.Cmd.__init__(self, stdin=stdin) - self.warningColor = '' - self.errorColor = '' - self.alertColor = '' - self.staticColor = '' - self.resetColor = '' - if not COLORIZED_OUTPUT or avoidOutputColors: - self.avoidOutputColors = True - else: + def __init__(self, pdfFile, vtKey, avoidColors=False, stdin=None, batchMode=False, jsonOutput=False, scriptFile=None): + """ + @batchMode: deprecated mode which purpose was to handle command on cli execution + @scriptFile: Script file path to execute. Overlap @stdin. + """ + self.avoidColors = avoidColors + PDFOutput.__init__(self, avoidColors=self.avoidColors) + if not self.avoidColors: try: - init() - self.warningColor = Fore.YELLOW - self.errorColor = Fore.RED - self.alertColor = Fore.RED - self.staticColor = Fore.BLUE self.promptColor = RL_PROMPT_START_IGNORE + Fore.GREEN + RL_PROMPT_END_IGNORE - self.resetColor = Style.RESET_ALL - self.avoidOutputColors = False except: - self.avoidOutputColors = True - COLORIZED_OUTPUT = False + self.avoidColors = True - if not self.avoidOutputColors: - self.prompt = self.promptColor + 'PPDF> ' + RL_PROMPT_START_IGNORE + self.resetColor + RL_PROMPT_END_IGNORE - else: - self.prompt = 'PPDF> ' - self.use_rawinput = True - if stdin is not None: + if pdfFile is not None and stdin is None: + self.intro = self.getPeepReport(pdfFile.getStats()) + if scriptFile is not None: + self.stdin = open(scriptFile, 'rb') self.use_rawinput = False self.prompt = '' + elif stdin is not None: + self.stdin = stdin + self.use_rawinput = False + self.prompt = '' + else: + self.stdin = stdin + self.use_rawinput = True + if not self.avoidColors: + self.prompt = self.promptColor + 'PPDF> ' + RL_PROMPT_START_IGNORE + self.resetColor + RL_PROMPT_END_IGNORE + else: + self.prompt = 'PPDF> ' + # if self.stdin is None, Cmd.__init__ set it to sys.stdin + cmd.Cmd.__init__(self, stdin=self.stdin) + self.pdfFile = pdfFile self.variables = {'output_limit': [500, 500], 'malformed_options': [[], []], @@ -141,6 +143,27 @@ def __init__(self, pdfFile, vtKey, avoidOutputColors=False, stdin=None, batchMod self.outputVarName = None self.outputFileName = None + def runit(self): + while not self.leaving: + try: + self.cmdloop() + except KeyboardInterrupt as e: + sys.exit() + + except: + if self.stdin is not None: + errorMessage = '*** Error: Exception not handled using the batch mode!!' + traceback.print_exc(file=open(self.errorsFile, 'a')) + raise Exception('PeepException', 'Send me an email ;)') + else: + errorMessage = '*** Error: Exception not handled using the interactive console!! Please, report it to the author!!' + print pdfOutput.errorColor + errorMessage + pdfOutput.resetColor + pdfOutput.newLine + traceback.print_exc(file=open(self.errorsFile, 'a')) + finally: + if self.stdin is not None: + self.stdin.close() + + def emptyline(self): return @@ -1413,173 +1436,19 @@ def do_info(self, argv): message = '*** Error: You must open a file!!' self.log_output('info ' + argv, message) return False - stats = '' args = self.parseArgs(argv) if args is None: message = '*** Error: The command line arguments have not been parsed successfully!!' self.log_output('info ' + argv, message) return False - if not self.avoidOutputColors: + if not self.avoidColors: beforeStaticLabel = self.staticColor else: beforeStaticLabel = '' + stats = "" if len(args) == 0: statsDict = self.pdfFile.getStats() - stats += beforeStaticLabel + 'File: ' + self.resetColor + statsDict['File'] + newLine - stats += beforeStaticLabel + 'MD5: ' + self.resetColor + statsDict['MD5'] + newLine - stats += beforeStaticLabel + 'SHA1: ' + self.resetColor + statsDict['SHA1'] + newLine - # stats += beforeStaticLabel + 'SHA256: ' + self.resetColor + statsDict['SHA256'] + newLine - stats += beforeStaticLabel + 'Size: ' + self.resetColor + statsDict['Size'] + ' bytes' + newLine - if statsDict['Detection'] != []: - detectionReportInfo = '' - if statsDict['Detection'] is not None: - detectionLevel = statsDict['Detection'][0] / (statsDict['Detection'][1] / 3) - if detectionLevel == 0: - detectionColor = self.alertColor - elif detectionLevel == 1: - detectionColor = self.warningColor - else: - detectionColor = '' - detectionRate = '%s%d%s/%d' % ( - detectionColor, statsDict['Detection'][0], self.resetColor, statsDict['Detection'][1]) - if statsDict['Detection report'] != '': - detectionReportInfo = beforeStaticLabel + 'Detection report: ' + self.resetColor + statsDict[ - 'Detection report'] + newLine - else: - detectionRate = 'File not found on VirusTotal' - stats += beforeStaticLabel + 'Detection: ' + self.resetColor + detectionRate + newLine - stats += detectionReportInfo - stats += beforeStaticLabel + 'Version: ' + self.resetColor + statsDict['Version'] + newLine - stats += beforeStaticLabel + 'Binary: ' + self.resetColor + statsDict['Binary'] + newLine - stats += beforeStaticLabel + 'Linearized: ' + self.resetColor + statsDict['Linearized'] + newLine - stats += beforeStaticLabel + 'Encrypted: ' + self.resetColor + statsDict['Encrypted'] - if statsDict['Encryption Algorithms'] != []: - stats += ' (' - for algorithmInfo in statsDict['Encryption Algorithms']: - stats += algorithmInfo[0] + ' ' + str(algorithmInfo[1]) + ' bits, ' - stats = stats[:-2] + ')' - stats += newLine - stats += beforeStaticLabel + 'Updates: ' + self.resetColor + statsDict['Updates'] + newLine - stats += beforeStaticLabel + 'Objects: ' + self.resetColor + statsDict['Objects'] + newLine - stats += beforeStaticLabel + 'Streams: ' + self.resetColor + statsDict['Streams'] + newLine - stats += beforeStaticLabel + 'URIs: ' + self.resetColor + statsDict['URIs'] + newLine - stats += beforeStaticLabel + 'Comments: ' + self.resetColor + statsDict['Comments'] + newLine - stats += beforeStaticLabel + 'Errors: ' + self.resetColor + str(len(statsDict['Errors'])) + newLine * 2 - for version in range(len(statsDict['Versions'])): - statsVersion = statsDict['Versions'][version] - stats += beforeStaticLabel + 'Version ' + self.resetColor + str(version) + ':' + newLine - if statsVersion['Catalog'] is not None: - stats += beforeStaticLabel + '\tCatalog: ' + self.resetColor + statsVersion['Catalog'] + newLine - else: - stats += beforeStaticLabel + '\tCatalog: ' + self.resetColor + 'No' + newLine - if statsVersion['Info'] is not None: - stats += beforeStaticLabel + '\tInfo: ' + self.resetColor + statsVersion['Info'] + newLine - else: - stats += beforeStaticLabel + '\tInfo: ' + self.resetColor + 'No' + newLine - stats += ( - beforeStaticLabel + '\tObjects (' + statsVersion['Objects'][0] + '): ' + - self.resetColor + str(statsVersion['Objects'][1]) + newLine - ) - if statsVersion['Compressed Objects'] is not None: - stats += ( - beforeStaticLabel + '\tCompressed objects (' + - statsVersion['Compressed Objects'][0] + '): ' + self.resetColor + - str(statsVersion['Compressed Objects'][1]) + newLine - ) - if statsVersion['Errors'] is not None: - stats += ( - beforeStaticLabel + '\t\tErrors (' + statsVersion['Errors'][0] + '): ' + - self.resetColor + str(statsVersion['Errors'][1]) + newLine - ) - stats += ( - beforeStaticLabel + '\tStreams (' + statsVersion['Streams'][0] + '): ' + - self.resetColor + str(statsVersion['Streams'][1]) - ) - if statsVersion['Xref Streams'] is not None: - stats += ( - newLine + beforeStaticLabel + '\t\tXref streams (' + - statsVersion['Xref Streams'][0] + '): ' + self.resetColor + - str(statsVersion['Xref Streams'][1]) - ) - if statsVersion['Object Streams'] is not None: - stats += ( - newLine + beforeStaticLabel + '\t\tObject streams (' + - statsVersion['Object Streams'][0] + '): ' + self.resetColor + - str(statsVersion['Object Streams'][1]) - ) - if int(statsVersion['Streams'][0]) > 0: - stats += ( - newLine + beforeStaticLabel + '\t\tEncoded (' + statsVersion['Encoded'][0] + '): ' + - self.resetColor + str(statsVersion['Encoded'][1]) - ) - if statsVersion['Decoding Errors'] is not None: - stats += ( - newLine + beforeStaticLabel + '\t\tDecoding errors (' + - statsVersion['Decoding Errors'][0] + '): ' + self.resetColor + - str(statsVersion['Decoding Errors'][1]) - ) - if statsVersion['URIs'] is not None: - stats += ( - newLine + beforeStaticLabel + '\tObjects with URIs (' + - statsVersion['URIs'][0] + '): ' + self.resetColor + str(statsVersion['URIs'][1]) - ) - if not self.avoidOutputColors: - beforeStaticLabel = self.warningColor - if statsVersion['Objects with JS code'] is not None: - stats += ( - newLine + beforeStaticLabel + '\tObjects with JS code (' + - statsVersion['Objects with JS code'][0] + '): ' + - self.resetColor + str(statsVersion['Objects with JS code'][1]) - ) - actions = statsVersion['Actions'] - events = statsVersion['Events'] - vulns = statsVersion['Vulns'] - elements = statsVersion['Elements'] - if events is not None or actions is not None or vulns is not None or elements is not None: - stats += newLine + beforeStaticLabel + '\tSuspicious elements:' + self.resetColor + newLine - if events is not None: - for event in events: - stats += '\t\t' + beforeStaticLabel + event + ' (%d): ' % len(events[event]) + \ - self.resetColor + str(events[event]) + newLine - if actions is not None: - for action in actions: - stats += '\t\t' + beforeStaticLabel + action + ' (%d): ' % len(actions[action]) + \ - self.resetColor + str(actions[action]) + newLine - if vulns is not None: - for vuln in vulns: - if vuln in vulnsDict: - vulnName = vulnsDict[vuln][0] - vulnCVEList = vulnsDict[vuln][1] - stats += '\t\t' + beforeStaticLabel + vulnName + ' (' - for vulnCVE in vulnCVEList: - stats += vulnCVE + ',' - stats = ( - stats[:-1] + ') (%d): ' % len(vulns[vuln]) + self.resetColor + - str(vulns[vuln]) + newLine - ) - else: - stats += '\t\t' + beforeStaticLabel + vuln + ' (%d): ' % len(vulns[vuln]) + \ - self.resetColor + str(vulns[vuln]) + newLine - if elements is not None: - for element in elements: - if element in vulnsDict: - vulnName = vulnsDict[element][0] - vulnCVEList = vulnsDict[element][1] - stats += '\t\t' + beforeStaticLabel + vulnName + ' (' - for vulnCVE in vulnCVEList: - stats += vulnCVE + ',' - stats = stats[:-1] + '): ' + self.resetColor + str(elements[element]) + newLine - else: - stats += '\t\t' + beforeStaticLabel + element + ': ' + self.resetColor + str( - elements[element]) + newLine - if not self.avoidOutputColors: - beforeStaticLabel = self.staticColor - urls = statsVersion['URLs'] - if urls is not None: - stats += newLine + beforeStaticLabel + '\tFound URLs:' + self.resetColor + newLine - for url in urls: - stats += '\t\t' + url + newLine - stats += newLine * 2 + stats = self.getPeepReport(statsDict) self.log_output('info ' + argv, stats) return False elif len(args) == 1: @@ -2188,6 +2057,7 @@ def do_js_eval(self, argv): self.log_output('js_eval ' + argv, evalCode) except: error = str(sys.exc_info()[1]) + #TODO use the global variable for erro file path f = open(os.path.expanduser("~/.peepdf-jserror.log"), "ab") f.write(error + newLine) @@ -2886,8 +2756,7 @@ def do_open(self, argv): message = '*** Error: Opening document failed!!' self.pdfFile = None self.log_output('open ' + argv, message) - if not JS_MODULE: - print 'Warning: PyV8 is not installed!!' + newLine + print self.dependenciesWarning() if self.pdfFile is not None: self.do_info('') @@ -2910,8 +2779,10 @@ def do_rawobject(self, argv): message = '*** Error: You must open a file!!' self.log_output('rawobject ' + argv, message) return False + compressed = False rawValue = '' offset = 0 + size = 0 args = self.parseArgs(argv) if args is None: message = '*** Error: The command line arguments have not been parsed successfully!!' @@ -2944,6 +2815,7 @@ def do_rawobject(self, argv): xrefArray = ret[1] if xrefArray[0] is not None: offset = xrefArray[0].getOffset() + size = xrefArray[0].getSize() rawValue = xrefArray[0].toFile() elif id == 'trailer': ret = self.pdfFile.getTrailer(version) @@ -2955,6 +2827,7 @@ def do_rawobject(self, argv): trailerArray = ret[1] if trailerArray[0] is not None: offset = trailerArray[0].getOffset() + size = trailerArray[0].getSize() rawValue = trailerArray[0].toFile() else: id = int(id) @@ -2964,7 +2837,9 @@ def do_rawobject(self, argv): self.log_output('rawobject ' + argv, message) return False object = indirectObject.getObject() + compressed = object.isCompressed() offset = indirectObject.getOffset() + size = indirectObject.getSize() rawValue = str(object.getRawValue()) if offset == -1: message = '*** Error: offset cannot be calculated!!' @@ -3743,7 +3618,7 @@ def do_vtcheck(self, argv): if args == []: self.pdfFile.setDetectionRate([jsonDict['positives'], jsonDict['total']]) self.pdfFile.setDetectionReport(jsonDict['permalink']) - if not self.avoidOutputColors: + if not self.avoidColors: detectionLevel = jsonDict['positives'] / (jsonDict['total'] / 3) if detectionLevel == 0: detectionColor = self.alertColor diff --git a/peepdf/PDFCore.py b/peepdf/PDFCore.py index c7eea9f..51a5f51 100644 --- a/peepdf/PDFCore.py +++ b/peepdf/PDFCore.py @@ -34,7 +34,7 @@ import peepdf.aes as AES from peepdf.PDFUtils import ( encodeName, unescapeString, encodeString, escapeString, numToHex, - numToString + numToString, vtcheck ) from peepdf.PDFCrypto import ( RC4, computeObjectKey, computeUserPass, isUserPass, isOwnerPass, @@ -4775,6 +4775,27 @@ def __init__(self): self.numDecodingErrors = 0 self.maxObjectId = 0 + def getVtInfo(self, vt_key): + ret = vtcheck(self.getMD5(), vt_key) + if ret[0] == -1: + self.addError(ret[1]) + else: + self.parseVtReport(ret[1]) + + def parseVtReport(self, vtJsonDict): + if vtJsonDict.has_key('response_code'): + if vtJsonDict['response_code'] == 1: + if vtJsonDict.has_key('positives') and vtJsonDict.has_key('total'): + self.setDetectionRate([vtJsonDict['positives'], vtJsonDict['total']]) + else: + self.addError('Missing elements in the response from VirusTotal!!') + if vtJsonDict.has_key('permalink'): + self.setDetectionReport(vtJsonDict['permalink']) + else: + self.setDetectionRate(None) + else: + self.addError('Bad response from VirusTotal!!') + def addBody(self, newBody): if newBody is not None and isinstance(newBody, PDFBody): self.body.append(newBody) diff --git a/peepdf/PDFOutput.py b/peepdf/PDFOutput.py new file mode 100644 index 0000000..9c15d70 --- /dev/null +++ b/peepdf/PDFOutput.py @@ -0,0 +1,561 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +# +# peepdf is a tool to analyse and modify PDF files +# http://peepdf.eternal-todo.com +# By Jose Miguel Esparza +# +# Copyright (C) 2011-2017 Jose Miguel Esparza +# +# 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 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 . +# + +import os +import sys +import re +import urllib2 +import hashlib +import traceback +import json +from datetime import datetime +from peepdf.PDFCore import PDFParser, vulnsDict +from peepdf.PDFUtils import vtcheck + +from peepdf.constants import AUTHOR, AUTHOR_EMAIL, PEEPDF_VERSION, PEEPDF_REVISION, \ + PEEPDF_URL, PEEPDF_ROOT, ERROR_FILE + +try: + import PyV8 + JS_MODULE = True +except: + JS_MODULE = False +try: + import pylibemu + EMU_MODULE = True +except: + EMU_MODULE = False +try: + from colorama import init, Fore, Back, Style + COLORIZED_OUTPUT = True +except: + COLORIZED_OUTPUT = False + +try: + from PIL import Image + PIL_MODULE = True +except: + PIL_MODULE = False + +try: + from lxml import etree + LXML_MODULE = True +except: + LXML_MODULE = False + + +class PDFOutput(object): + """ + Class to get an output from PDFFile instance object + """ + + def __init__(self, errorsFile=ERROR_FILE, avoidColors=False): + """ + @errorsFile: path to the log error file + @avoidColors: Bool to prevent color on output + """ + global COLORIZED_OUTPUT + self.warningColor = '' + self.errorColor = '' + self.alertColor = '' + self.staticColor = '' + self.resetColor = '' + if avoidColors or not COLORIZED_OUTPUT: + self.avoidColors = True + else: + try: + init() + self.warningColor = Fore.YELLOW + self.errorColor = Fore.RED + self.alertColor = Fore.RED + self.staticColor = Fore.BLUE + self.resetColor = Style.RESET_ALL + self.avoidColors = False + except: + self.avoidColors = True + COLORIZED_OUTPUT = False + + self.newLine = os.linesep + self.errorsFile = errorsFile + + def getDependenciesWarning(self): + warnings = '' + if not JS_MODULE: + warningMessage = 'Warning: PyV8 is not installed!!' + warnings += self.warningColor + warningMessage + self.resetColor + self.newLine + if not EMU_MODULE: + warningMessage = 'Warning: pylibemu is not installed!!' + warnings += self.warningColor + warningMessage + self.resetColor + self.newLine + if not PIL_MODULE: + warningMessage = 'Warning: Python Imaging Library (PIL) is not installed!!' + warnings += self.warningColor + warningMessage + self.resetColor + self.newLine + return warnings + + def getDecryptError(self, statsDict): + errors = statsDict['Errors'] + warnings = '' + for error in errors: + if error.find('Decryption error') != -1: + warnings += self.errorColor + error + self.resetColor + self.newLine + return warnings + + def getJSON(self, statsDict): + try: + return self.getPeepJSON(statsDict) + except: + errorMessage = '*** Error: Exception while generating the JSON report!!' + print self.errorColor + errorMessage + self.resetColor + self.newLine + traceback.print_exc(file=open(self.errorsFile, 'a')) + raise Exception('PeepException', 'Send me an email ;)') + + def getPeepJSON(self, statsDict): + # peepdf info + peepdfDict = {'version': PEEPDF_VERSION, + 'revision': PEEPDF_REVISION, + 'author': AUTHOR, + 'url': PEEPDF_URL} + # Basic info + basicDict = {} + basicDict['filename'] = statsDict['File'] + basicDict['md5'] = statsDict['MD5'] + basicDict['sha1'] = statsDict['SHA1'] + basicDict['sha256'] = statsDict['SHA256'] + basicDict['size'] = int(statsDict['Size']) + basicDict['detection'] = {} + if statsDict['Detection'] != [] and statsDict['Detection'] is not None: + basicDict['detection']['rate'] = '%d/%d' % (statsDict['Detection'][0], statsDict['Detection'][1]) + basicDict['detection']['report_link'] = statsDict['Detection report'] + basicDict['pdf_version'] = statsDict['Version'] + basicDict['binary'] = bool(statsDict['Binary']) + basicDict['linearized'] = bool(statsDict['Linearized']) + basicDict['encrypted'] = bool(statsDict['Encrypted']) + basicDict['encryption_algorithms'] = [] + if statsDict['Encryption Algorithms']: + for algorithmInfo in statsDict['Encryption Algorithms']: + basicDict['encryption_algorithms'].append({'bits': algorithmInfo[1], 'algorithm': algorithmInfo[0]}) + basicDict['updates'] = int(statsDict['Updates']) + basicDict['num_objects'] = int(statsDict['Objects']) + basicDict['num_streams'] = int(statsDict['Streams']) + basicDict['comments'] = int(statsDict['Comments']) + basicDict['errors'] = [] + for error in statsDict['Errors']: + basicDict['errors'].append(error) + # Advanced info + advancedInfo = [] + for version in range(len(statsDict['Versions'])): + statsVersion = statsDict['Versions'][version] + if version == 0: + versionType = 'original' + else: + versionType = 'update' + versionInfo = {} + versionInfo['version_number'] = version + versionInfo['version_type'] = versionType + versionInfo['catalog'] = statsVersion['Catalog'] + versionInfo['info'] = statsVersion['Info'] + if statsVersion['Objects'] is not None: + versionInfo['objects'] = statsVersion['Objects'][1] + else: + versionInfo['objects'] = [] + if statsVersion['Compressed Objects'] is not None: + versionInfo['compressed_objects'] = statsVersion['Compressed Objects'][1] + else: + versionInfo['compressed_objects'] = [] + if statsVersion['Errors'] is not None: + versionInfo['error_objects'] = statsVersion['Errors'][1] + else: + versionInfo['error_objects'] = [] + if statsVersion['Streams'] is not None: + versionInfo['streams'] = statsVersion['Streams'][1] + else: + versionInfo['streams'] = [] + if statsVersion['Xref Streams'] is not None: + versionInfo['xref_streams'] = statsVersion['Xref Streams'][1] + else: + versionInfo['xref_streams'] = [] + if statsVersion['Encoded'] is not None: + versionInfo['encoded_streams'] = statsVersion['Encoded'][1] + else: + versionInfo['encoded_streams'] = [] + if versionInfo['encoded_streams'] and statsVersion['Decoding Errors'] is not None: + versionInfo['decoding_error_streams'] = statsVersion['Decoding Errors'][1] + else: + versionInfo['decoding_error_streams'] = [] + if statsVersion['Objects with JS code'] is not None: + versionInfo['js_objects'] = statsVersion['Objects with JS code'][1] + else: + versionInfo['js_objects'] = [] + elements = statsVersion['Elements'] + elementArray = [] + if elements: + for element in elements: + elementInfo = {'name': element} + if element in vulnsDict: + elementInfo['vuln_name'] = vulnsDict[element][0] + elementInfo['vuln_cve_list'] = vulnsDict[element][1] + elementInfo['objects'] = elements[element] + elementArray.append(elementInfo) + vulns = statsVersion['Vulns'] + vulnArray = [] + if vulns: + for vuln in vulns: + vulnInfo = {'name': vuln} + if vuln in vulnsDict: + vulnInfo['vuln_name'] = vulnsDict[vuln][0] + vulnInfo['vuln_cve_list'] = vulnsDict[vuln][1] + vulnInfo['objects'] = vulns[vuln] + vulnArray.append(vulnInfo) + versionInfo['suspicious_elements'] = {'triggers': statsVersion['Events'], + 'actions': statsVersion['Actions'], + 'elements': elementArray, + 'js_vulns': vulnArray, + 'urls': statsVersion['URLs']} + versionReport = {'version_info': versionInfo} + advancedInfo.append(versionReport) + jsonDict = {'peepdf_analysis': + {'peepdf_info': peepdfDict, + 'date': datetime.today().strftime('%Y-%m-%d %H:%M'), + 'basic': basicDict, + 'advanced': advancedInfo} + } + return json.dumps(jsonDict, indent=4, sort_keys=True) + + def getXML(self, statsDict): + try: + return self.getPeepXML(statsDict) + except ImportError: + raise + except: + errorMessage = '*** Error: Exception while generating the XML file!!' + print self.errorColor + errorMessage + self.resetColor + self.newLine + traceback.print_exc(file=open(self.errorsFile, 'a')) + raise Exception('PeepException', 'Send me an email ;)') + + def getPeepXML(self, statsDict): + root = etree.Element('peepdf_analysis', + version=PEEPDF_VERSION + ' r' + PEEPDF_REVISION, + url=PEEPDF_URL, + author=AUTHOR) + analysisDate = etree.SubElement(root, 'date') + analysisDate.text = datetime.today().strftime('%Y-%m-%d %H:%M') + basicInfo = etree.SubElement(root, 'basic') + fileName = etree.SubElement(basicInfo, 'filename') + fileName.text = statsDict['File'] + md5 = etree.SubElement(basicInfo, 'md5') + md5.text = statsDict['MD5'] + sha1 = etree.SubElement(basicInfo, 'sha1') + sha1.text = statsDict['SHA1'] + sha256 = etree.SubElement(basicInfo, 'sha256') + sha256.text = statsDict['SHA256'] + size = etree.SubElement(basicInfo, 'size') + size.text = statsDict['Size'] + detection = etree.SubElement(basicInfo, 'detection') + if statsDict['Detection']: + detectionRate = etree.SubElement(detection, 'rate') + detectionRate.text = '%d/%d' % (statsDict['Detection'][0], statsDict['Detection'][1]) + detectionReport = etree.SubElement(detection, 'report_link') + detectionReport.text = statsDict['Detection report'] + version = etree.SubElement(basicInfo, 'pdf_version') + version.text = statsDict['Version'] + binary = etree.SubElement(basicInfo, 'binary', status=statsDict['Binary'].lower()) + linearized = etree.SubElement(basicInfo, 'linearized', status=statsDict['Linearized'].lower()) + encrypted = etree.SubElement(basicInfo, 'encrypted', status=statsDict['Encrypted'].lower()) + if statsDict['Encryption Algorithms']: + algorithms = etree.SubElement(encrypted, 'algorithms') + for algorithmInfo in statsDict['Encryption Algorithms']: + algorithm = etree.SubElement(algorithms, 'algorithm', bits=str(algorithmInfo[1])) + algorithm.text = algorithmInfo[0] + updates = etree.SubElement(basicInfo, 'updates') + updates.text = statsDict['Updates'] + objects = etree.SubElement(basicInfo, 'num_objects') + objects.text = statsDict['Objects'] + streams = etree.SubElement(basicInfo, 'num_streams') + streams.text = statsDict['Streams'] + comments = etree.SubElement(basicInfo, 'comments') + comments.text = statsDict['Comments'] + errors = etree.SubElement(basicInfo, 'errors', num=str(len(statsDict['Errors']))) + for error in statsDict['Errors']: + errorMessageXML = etree.SubElement(errors, 'error_message') + errorMessageXML.text = error + advancedInfo = etree.SubElement(root, 'advanced') + for version in range(len(statsDict['Versions'])): + statsVersion = statsDict['Versions'][version] + if version == 0: + versionType = 'original' + else: + versionType = 'update' + versionInfo = etree.SubElement(advancedInfo, 'version', num=str(version), type=versionType) + catalog = etree.SubElement(versionInfo, 'catalog') + if statsVersion['Catalog'] is not None: + catalog.set('object_id', statsVersion['Catalog']) + info = etree.SubElement(versionInfo, 'info') + if statsVersion['Info'] is not None: + info.set('object_id', statsVersion['Info']) + objects = etree.SubElement(versionInfo, 'objects', num=statsVersion['Objects'][0]) + for id in statsVersion['Objects'][1]: + object = etree.SubElement(objects, 'object', id=str(id)) + if statsVersion['Compressed Objects'] is not None: + if id in statsVersion['Compressed Objects'][1]: + object.set('compressed', 'true') + else: + object.set('compressed', 'false') + if statsVersion['Errors'] is not None: + if id in statsVersion['Errors'][1]: + object.set('errors', 'true') + else: + object.set('errors', 'false') + streams = etree.SubElement(versionInfo, 'streams', num=statsVersion['Streams'][0]) + for id in statsVersion['Streams'][1]: + stream = etree.SubElement(streams, 'stream', id=str(id)) + if statsVersion['Xref Streams'] is not None: + if id in statsVersion['Xref Streams'][1]: + stream.set('xref_stream', 'true') + else: + stream.set('xref_stream', 'false') + if statsVersion['Object Streams'] is not None: + if id in statsVersion['Object Streams'][1]: + stream.set('object_stream', 'true') + else: + stream.set('object_stream', 'false') + if statsVersion['Encoded'] is not None: + if id in statsVersion['Encoded'][1]: + stream.set('encoded', 'true') + if statsVersion['Decoding Errors'] is not None: + if id in statsVersion['Decoding Errors'][1]: + stream.set('decoding_errors', 'true') + else: + stream.set('decoding_errors', 'false') + else: + stream.set('encoded', 'false') + jsObjects = etree.SubElement(versionInfo, 'js_objects') + if statsVersion['Objects with JS code'] is not None: + for id in statsVersion['Objects with JS code'][1]: + etree.SubElement(jsObjects, 'container_object', id=str(id)) + actions = statsVersion['Actions'] + events = statsVersion['Events'] + vulns = statsVersion['Vulns'] + elements = statsVersion['Elements'] + suspicious = etree.SubElement(versionInfo, 'suspicious_elements') + if events != None or actions != None or vulns != None or elements != None: + if events: + triggers = etree.SubElement(suspicious, 'triggers') + for event in events: + trigger = etree.SubElement(triggers, 'trigger', name=event) + for id in events[event]: + etree.SubElement(trigger, 'container_object', id=str(id)) + if actions: + actionsList = etree.SubElement(suspicious, 'actions') + for action in actions: + actionInfo = etree.SubElement(actionsList, 'action', name=action) + for id in actions[action]: + etree.SubElement(actionInfo, 'container_object', id=str(id)) + if elements: + elementsList = etree.SubElement(suspicious, 'elements') + for element in elements: + elementInfo = etree.SubElement(elementsList, 'element', name=element) + if vulnsDict.has_key(element): + vulnName = vulnsDict[element][0] + vulnCVEList = vulnsDict[element][1] + for vulnCVE in vulnCVEList: + cve = etree.SubElement(elementInfo, 'cve') + cve.text = vulnCVE + for id in elements[element]: + etree.SubElement(elementInfo, 'container_object', id=str(id)) + if vulns: + vulnsList = etree.SubElement(suspicious, 'js_vulns') + for vuln in vulns: + vulnInfo = etree.SubElement(vulnsList, 'vulnerable_function', name=vuln) + if vulnsDict.has_key(vuln): + vulnName = vulnsDict[vuln][0] + vulnCVEList = vulnsDict[vuln][1] + for vulnCVE in vulnCVEList: + cve = etree.SubElement(vulnInfo, 'cve') + cve.text = vulnCVE + for id in vulns[vuln]: + etree.SubElement(vulnInfo, 'container_object', id=str(id)) + urls = statsVersion['URLs'] + suspiciousURLs = etree.SubElement(versionInfo, 'suspicious_urls') + if urls != None: + for url in urls: + urlInfo = etree.SubElement(suspiciousURLs, 'url') + urlInfo.text = url + return etree.tostring(root, pretty_print=True) + + + def getPeepReport(self, statsDict): + + if not self.avoidColors: + beforeStaticLabel = self.staticColor + else: + beforeStaticLabel = '' + + stats = beforeStaticLabel + 'File: ' + self.resetColor + statsDict['File'] + self.newLine + stats += beforeStaticLabel + 'MD5: ' + self.resetColor + statsDict['MD5'] + self.newLine + stats += beforeStaticLabel + 'SHA1: ' + self.resetColor + statsDict['SHA1'] + self.newLine + stats += beforeStaticLabel + 'SHA256: ' + self.resetColor + statsDict['SHA256'] + self.newLine + stats += beforeStaticLabel + 'Size: ' + self.resetColor + statsDict['Size'] + ' bytes' + self.newLine + + if statsDict['Detection'] != []: + detectionReportInfo = '' + if statsDict['Detection'] is not None: + detectionColor = '' + if not self.avoidColors: + detectionLevel = statsDict['Detection'][0] / (statsDict['Detection'][1] / 3) + if detectionLevel == 0: + detectionColor = self.alertColor + elif detectionLevel == 1: + detectionColor = self.warningColor + detectionRate = '%s%d%s/%d' % ( + detectionColor, statsDict['Detection'][0], self.resetColor, statsDict['Detection'][1]) + if statsDict['Detection report'] != '': + detectionReportInfo = beforeStaticLabel + 'Detection report: ' + self.resetColor + \ + statsDict['Detection report'] + self.newLine + else: + detectionRate = 'File not found on VirusTotal' + stats += beforeStaticLabel + 'Detection: ' + self.resetColor + detectionRate + self.newLine + stats += detectionReportInfo + stats += beforeStaticLabel + 'Version: ' + self.resetColor + statsDict['Version'] + self.newLine + stats += beforeStaticLabel + 'Binary: ' + self.resetColor + statsDict['Binary'] + self.newLine + stats += beforeStaticLabel + 'Linearized: ' + self.resetColor + statsDict['Linearized'] + self.newLine + stats += beforeStaticLabel + 'Encrypted: ' + self.resetColor + statsDict['Encrypted'] + if statsDict['Encryption Algorithms'] != []: + stats += ' (' + for algorithmInfo in statsDict['Encryption Algorithms']: + stats += algorithmInfo[0] + ' ' + str(algorithmInfo[1]) + ' bits, ' + stats = stats[:-2] + ')' + stats += self.newLine + stats += beforeStaticLabel + 'Updates: ' + self.resetColor + statsDict['Updates'] + self.newLine + stats += beforeStaticLabel + 'Objects: ' + self.resetColor + statsDict['Objects'] + self.newLine + stats += beforeStaticLabel + 'Streams: ' + self.resetColor + statsDict['Streams'] + self.newLine + stats += beforeStaticLabel + 'URIs: ' + self.resetColor + statsDict['URIs'] + self.newLine + stats += beforeStaticLabel + 'Comments: ' + self.resetColor + statsDict['Comments'] + self.newLine + stats += beforeStaticLabel + 'Errors: ' + self.resetColor + str(len(statsDict['Errors'])) + self.newLine * 2 + for version in range(len(statsDict['Versions'])): + statsVersion = statsDict['Versions'][version] + stats += beforeStaticLabel + 'Version ' + self.resetColor + str(version) + ':' + self.newLine + if statsVersion['Catalog'] != None: + stats += beforeStaticLabel + '\tCatalog: ' + self.resetColor + statsVersion['Catalog'] + self.newLine + else: + stats += beforeStaticLabel + '\tCatalog: ' + self.resetColor + 'No' + self.newLine + if statsVersion['Info'] != None: + stats += beforeStaticLabel + '\tInfo: ' + self.resetColor + statsVersion['Info'] + self.newLine + else: + stats += beforeStaticLabel + '\tInfo: ' + self.resetColor + 'No' + self.newLine + stats += beforeStaticLabel + '\tObjects (' + statsVersion['Objects'][ + 0] + '): ' + self.resetColor + str(statsVersion['Objects'][1]) + self.newLine + if statsVersion['Compressed Objects'] != None: + stats += beforeStaticLabel + '\tCompressed objects (' + statsVersion['Compressed Objects'][ + 0] + '): ' + self.resetColor + str(statsVersion['Compressed Objects'][1]) + self.newLine + if statsVersion['Errors'] != None: + stats += beforeStaticLabel + '\t\tErrors (' + statsVersion['Errors'][ + 0] + '): ' + self.resetColor + str(statsVersion['Errors'][1]) + self.newLine + stats += beforeStaticLabel + '\tStreams (' + statsVersion['Streams'][ + 0] + '): ' + self.resetColor + str(statsVersion['Streams'][1]) + if statsVersion['Xref Streams'] != None: + stats += self.newLine + beforeStaticLabel + '\t\tXref streams (' + statsVersion['Xref Streams'][ + 0] + '): ' + self.resetColor + str(statsVersion['Xref Streams'][1]) + if statsVersion['Object Streams'] != None: + stats += self.newLine + beforeStaticLabel + '\t\tObject streams (' + \ + statsVersion['Object Streams'][0] + '): ' + self.resetColor + str( + statsVersion['Object Streams'][1]) + if int(statsVersion['Streams'][0]) > 0: + stats += self.newLine + beforeStaticLabel + '\t\tEncoded (' + statsVersion['Encoded'][ + 0] + '): ' + self.resetColor + str(statsVersion['Encoded'][1]) + if statsVersion['Decoding Errors'] != None: + stats += self.newLine + beforeStaticLabel + '\t\tDecoding errors (' + \ + statsVersion['Decoding Errors'][0] + '): ' + self.resetColor + str( + statsVersion['Decoding Errors'][1]) + if statsVersion['URIs'] is not None: + stats += self.newLine + beforeStaticLabel + '\tObjects with URIs (' + \ + statsVersion['URIs'][0] + '): ' + self.resetColor + str(statsVersion['URIs'][1]) + if not self.avoidColors: + beforeStaticLabel = self.warningColor + if statsVersion['Objects with JS code'] != None: + stats += self.newLine + beforeStaticLabel + '\tObjects with JS code (' + \ + statsVersion['Objects with JS code'][0] + '): ' + self.resetColor + str( + statsVersion['Objects with JS code'][1]) + actions = statsVersion['Actions'] + events = statsVersion['Events'] + vulns = statsVersion['Vulns'] + elements = statsVersion['Elements'] + if events != None or actions != None or vulns != None or elements != None: + stats += self.newLine + beforeStaticLabel + '\tSuspicious elements:' + self.resetColor + self.newLine + if events != None: + for event in events: + stats += '\t\t' + beforeStaticLabel + event + ' (%d): ' % len(events[event]) + \ + self.resetColor + str(events[event]) + self.newLine + if actions != None: + for action in actions: + stats += '\t\t' + beforeStaticLabel + action + ' (%d): ' % len(actions[action]) + \ + self.resetColor + str(actions[action]) + self.newLine + if vulns != None: + for vuln in vulns: + if vulnsDict.has_key(vuln): + vulnName = vulnsDict[vuln][0] + vulnCVEList = vulnsDict[vuln][1] + stats += '\t\t' + beforeStaticLabel + vulnName + ' (' + for vulnCVE in vulnCVEList: + stats += vulnCVE + ',' + stats = stats[:-1] + ') (%d): ' % len(vulns[vuln]) + self.resetColor + str(vulns[vuln]) + self.newLine + else: + stats += '\t\t' + beforeStaticLabel + vuln + ' (%d): ' % len(vulns[vuln]) + \ + self.resetColor + str(vulns[vuln]) + self.newLine + if elements != None: + for element in elements: + if vulnsDict.has_key(element): + vulnName = vulnsDict[element][0] + vulnCVEList = vulnsDict[element][1] + stats += '\t\t' + beforeStaticLabel + vulnName + ' (' + for vulnCVE in vulnCVEList: + stats += vulnCVE + ',' + stats = stats[:-1] + '): ' + self.resetColor + str(elements[element]) + self.newLine + else: + stats += '\t\t' + beforeStaticLabel + element + ': ' + self.resetColor + str( + elements[element]) + self.newLine + if not self.avoidColors: + beforeStaticLabel = self.staticColor + urls = statsVersion['URLs'] + if urls != None: + stats += self.newLine + beforeStaticLabel + '\tFound URLs:' + self.resetColor + self.newLine + for url in urls: + stats += '\t\t' + url + self.newLine + stats += self.newLine * 2 + return stats + + def getReport(self, statsDict, format="text"): + """ + Wrapper to get a formatted report info. + @statsDict: dict with pdf info. + @format: expected output format. + Possible value are 'text', 'xml' and 'json' + """ + if format == "text": + return self.getPeepReport(statsDict) + elif format == "json": + return self.getPeepJSON(statsDict) + elif format == "xml": + return self.getPeepXML(statsDict) + else: + raise Exception("Format {} not handled".format(format)) diff --git a/peepdf/PDFUtils.py b/peepdf/PDFUtils.py index d92a7a7..a13cb1e 100644 --- a/peepdf/PDFUtils.py +++ b/peepdf/PDFUtils.py @@ -162,7 +162,6 @@ def escapeRegExpString(string): def escapeString(string): ''' Escape the given string - @param string: A string to be escaped @return: Escaped string ''' diff --git a/peepdf/constants.py b/peepdf/constants.py new file mode 100644 index 0000000..9a4130d --- /dev/null +++ b/peepdf/constants.py @@ -0,0 +1,26 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +""" +Here is defined info about the project, its author +and some constants to customize + +""" + +import os +import sys + +PEEPDF_VERSION = '0.3' +PEEPDF_REVISION = '3' +AUTHOR = 'Jose Miguel Esparza' +AUTHOR_EMAIL = 'peepdf AT eternal-todo(dot)com' +AUTHOR_TWITTER = 'http://twitter.com/EternalTodo' +LICENCE = "GNU GPLv3" +PEEPDF_URL = 'http://peepdf.eternal-todo.com' +GITHUB_URL = 'https://github.com/jesparza/peepdf' +TWITTER_URL = 'http://twitter.com/peepdf' + +PEEPDF_ROOT = os.path.dirname( + os.path.realpath(os.path.join(sys.argv[0], "..")) +) +ERROR_FILE = os.path.expanduser("~/.peepdf-errors.log") diff --git a/peepdf/main.py b/peepdf/main.py index 291f6fe..c5dc8c3 100644 --- a/peepdf/main.py +++ b/peepdf/main.py @@ -31,324 +31,27 @@ import os import optparse import traceback -import json -from datetime import datetime -from peepdf.PDFCore import PDFParser, vulnsDict -from peepdf.PDFUtils import vtcheck +import StringIO +import peepdf.PDFOutput as PDFOutput +import peepdf.PDFUtils as PDFUtils +from peepdf.PDFCore import PDFParser +from peepdf.PDFConsole import PDFConsole -VT_KEY = 'fc90df3f5ac749a94a94cb8bf87e05a681a2eb001aef34b6a0084b8c22c97a64' - -try: - import PyV8 - JS_MODULE = True - - PyV8 -except: - JS_MODULE = False -try: - import pylibemu - EMU_MODULE = True - - pylibemu -except: - EMU_MODULE = False -try: - from colorama import init, Fore, Style - COLORIZED_OUTPUT = True -except: - COLORIZED_OUTPUT = False - -try: - from PIL import Image - PIL_MODULE = True - - Image -except: - PIL_MODULE = False - - -try: - from lxml import etree -except: - pass - - -def getPeepXML(statsDict, version, revision): - root = etree.Element('peepdf_analysis', version=version + ' r' + revision, url='http://peepdf.eternal-todo.com', - author='Jose Miguel Esparza') - analysisDate = etree.SubElement(root, 'date') - analysisDate.text = datetime.today().strftime('%Y-%m-%d %H:%M') - basicInfo = etree.SubElement(root, 'basic') - fileName = etree.SubElement(basicInfo, 'filename') - fileName.text = statsDict['File'] - md5 = etree.SubElement(basicInfo, 'md5') - md5.text = statsDict['MD5'] - sha1 = etree.SubElement(basicInfo, 'sha1') - sha1.text = statsDict['SHA1'] - sha256 = etree.SubElement(basicInfo, 'sha256') - sha256.text = statsDict['SHA256'] - size = etree.SubElement(basicInfo, 'size') - size.text = statsDict['Size'] - detection = etree.SubElement(basicInfo, 'detection') - if statsDict['Detection']: - detectionRate = etree.SubElement(detection, 'rate') - detectionRate.text = '%d/%d' % (statsDict['Detection'][0], statsDict['Detection'][1]) - detectionReport = etree.SubElement(detection, 'report_link') - detectionReport.text = statsDict['Detection report'] - version = etree.SubElement(basicInfo, 'pdf_version') - version.text = statsDict['Version'] - encrypted = etree.SubElement(basicInfo, 'encrypted', status=statsDict['Encrypted'].lower()) - if statsDict['Encryption Algorithms']: - algorithms = etree.SubElement(encrypted, 'algorithms') - for algorithmInfo in statsDict['Encryption Algorithms']: - algorithm = etree.SubElement(algorithms, 'algorithm', bits=str(algorithmInfo[1])) - algorithm.text = algorithmInfo[0] - updates = etree.SubElement(basicInfo, 'updates') - updates.text = statsDict['Updates'] - objects = etree.SubElement(basicInfo, 'num_objects') - objects.text = statsDict['Objects'] - streams = etree.SubElement(basicInfo, 'num_streams') - streams.text = statsDict['Streams'] - comments = etree.SubElement(basicInfo, 'comments') - comments.text = statsDict['Comments'] - errors = etree.SubElement(basicInfo, 'errors', num=str(len(statsDict['Errors']))) - for error in statsDict['Errors']: - errorMessageXML = etree.SubElement(errors, 'error_message') - errorMessageXML.text = error - advancedInfo = etree.SubElement(root, 'advanced') - for version in range(len(statsDict['Versions'])): - statsVersion = statsDict['Versions'][version] - if version == 0: - versionType = 'original' - else: - versionType = 'update' - versionInfo = etree.SubElement(advancedInfo, 'version', num=str(version), type=versionType) - catalog = etree.SubElement(versionInfo, 'catalog') - if statsVersion['Catalog'] is not None: - catalog.set('object_id', statsVersion['Catalog']) - info = etree.SubElement(versionInfo, 'info') - if statsVersion['Info'] is not None: - info.set('object_id', statsVersion['Info']) - objects = etree.SubElement(versionInfo, 'objects', num=statsVersion['Objects'][0]) - for id in statsVersion['Objects'][1]: - object = etree.SubElement(objects, 'object', id=str(id)) - if statsVersion['Compressed Objects'] is not None: - if id in statsVersion['Compressed Objects'][1]: - object.set('compressed', 'true') - else: - object.set('compressed', 'false') - if statsVersion['Errors'] is not None: - if id in statsVersion['Errors'][1]: - object.set('errors', 'true') - else: - object.set('errors', 'false') - streams = etree.SubElement(versionInfo, 'streams', num=statsVersion['Streams'][0]) - for id in statsVersion['Streams'][1]: - stream = etree.SubElement(streams, 'stream', id=str(id)) - if statsVersion['Xref Streams'] is not None: - if id in statsVersion['Xref Streams'][1]: - stream.set('xref_stream', 'true') - else: - stream.set('xref_stream', 'false') - if statsVersion['Object Streams'] is not None: - if id in statsVersion['Object Streams'][1]: - stream.set('object_stream', 'true') - else: - stream.set('object_stream', 'false') - if statsVersion['Encoded'] is not None: - if id in statsVersion['Encoded'][1]: - stream.set('encoded', 'true') - if statsVersion['Decoding Errors'] is not None: - if id in statsVersion['Decoding Errors'][1]: - stream.set('decoding_errors', 'true') - else: - stream.set('decoding_errors', 'false') - else: - stream.set('encoded', 'false') - jsObjects = etree.SubElement(versionInfo, 'js_objects') - if statsVersion['Objects with JS code'] is not None: - for id in statsVersion['Objects with JS code'][1]: - etree.SubElement(jsObjects, 'container_object', id=str(id)) - actions = statsVersion['Actions'] - events = statsVersion['Events'] - vulns = statsVersion['Vulns'] - elements = statsVersion['Elements'] - suspicious = etree.SubElement(versionInfo, 'suspicious_elements') - if events is not None or actions is not None or vulns is not None or elements is not None: - if events: - triggers = etree.SubElement(suspicious, 'triggers') - for event in events: - trigger = etree.SubElement(triggers, 'trigger', name=event) - for id in events[event]: - etree.SubElement(trigger, 'container_object', id=str(id)) - if actions: - actionsList = etree.SubElement(suspicious, 'actions') - for action in actions: - actionInfo = etree.SubElement(actionsList, 'action', name=action) - for id in actions[action]: - etree.SubElement(actionInfo, 'container_object', id=str(id)) - if elements: - elementsList = etree.SubElement(suspicious, 'elements') - for element in elements: - elementInfo = etree.SubElement(elementsList, 'element', name=element) - if element in vulnsDict: - vulnCVEList = vulnsDict[element][1] - for vulnCVE in vulnCVEList: - cve = etree.SubElement(elementInfo, 'cve') - cve.text = vulnCVE - for id in elements[element]: - etree.SubElement(elementInfo, 'container_object', id=str(id)) - if vulns: - vulnsList = etree.SubElement(suspicious, 'js_vulns') - for vuln in vulns: - vulnInfo = etree.SubElement(vulnsList, 'vulnerable_function', name=vuln) - if vuln in vulnsDict: - vulnCVEList = vulnsDict[vuln][1] - for vulnCVE in vulnCVEList: - cve = etree.SubElement(vulnInfo, 'cve') - cve.text = vulnCVE - for id in vulns[vuln]: - etree.SubElement(vulnInfo, 'container_object', id=str(id)) - urls = statsVersion['URLs'] - suspiciousURLs = etree.SubElement(versionInfo, 'suspicious_urls') - if urls is not None: - for url in urls: - urlInfo = etree.SubElement(suspiciousURLs, 'url') - urlInfo.text = url - return etree.tostring(root, pretty_print=True) - - -def getPeepJSON(statsDict, version, revision): - # peepdf info - peepdfDict = {'version': version, - 'revision': revision, - 'author': 'Jose Miguel Esparza', - 'url': 'http://peepdf.eternal-todo.com'} - # Basic info - basicDict = {} - basicDict['filename'] = statsDict['File'] - basicDict['md5'] = statsDict['MD5'] - basicDict['sha1'] = statsDict['SHA1'] - basicDict['sha256'] = statsDict['SHA256'] - basicDict['size'] = int(statsDict['Size']) - basicDict['detection'] = {} - if statsDict['Detection'] != [] and statsDict['Detection'] is not None: - basicDict['detection']['rate'] = '%d/%d' % (statsDict['Detection'][0], statsDict['Detection'][1]) - basicDict['detection']['report_link'] = statsDict['Detection report'] - basicDict['pdf_version'] = statsDict['Version'] - basicDict['binary'] = bool(statsDict['Binary']) - basicDict['linearized'] = bool(statsDict['Linearized']) - basicDict['encrypted'] = bool(statsDict['Encrypted']) - basicDict['encryption_algorithms'] = [] - if statsDict['Encryption Algorithms']: - for algorithmInfo in statsDict['Encryption Algorithms']: - basicDict['encryption_algorithms'].append({'bits': algorithmInfo[1], 'algorithm': algorithmInfo[0]}) - basicDict['updates'] = int(statsDict['Updates']) - basicDict['num_objects'] = int(statsDict['Objects']) - basicDict['num_streams'] = int(statsDict['Streams']) - basicDict['comments'] = int(statsDict['Comments']) - basicDict['errors'] = [] - for error in statsDict['Errors']: - basicDict['errors'].append(error) - # Advanced info - advancedInfo = [] - for version in range(len(statsDict['Versions'])): - statsVersion = statsDict['Versions'][version] - if version == 0: - versionType = 'original' - else: - versionType = 'update' - versionInfo = {} - versionInfo['version_number'] = version - versionInfo['version_type'] = versionType - versionInfo['catalog'] = statsVersion['Catalog'] - versionInfo['info'] = statsVersion['Info'] - if statsVersion['Objects'] is not None: - versionInfo['objects'] = statsVersion['Objects'][1] - else: - versionInfo['objects'] = [] - if statsVersion['Compressed Objects'] is not None: - versionInfo['compressed_objects'] = statsVersion['Compressed Objects'][1] - else: - versionInfo['compressed_objects'] = [] - if statsVersion['Errors'] is not None: - versionInfo['error_objects'] = statsVersion['Errors'][1] - else: - versionInfo['error_objects'] = [] - if statsVersion['Streams'] is not None: - versionInfo['streams'] = statsVersion['Streams'][1] - else: - versionInfo['streams'] = [] - if statsVersion['Xref Streams'] is not None: - versionInfo['xref_streams'] = statsVersion['Xref Streams'][1] - else: - versionInfo['xref_streams'] = [] - if statsVersion['Encoded'] is not None: - versionInfo['encoded_streams'] = statsVersion['Encoded'][1] - else: - versionInfo['encoded_streams'] = [] - if versionInfo['encoded_streams'] and statsVersion['Decoding Errors'] is not None: - versionInfo['decoding_error_streams'] = statsVersion['Decoding Errors'][1] - else: - versionInfo['decoding_error_streams'] = [] - if statsVersion['Objects with JS code'] is not None: - versionInfo['js_objects'] = statsVersion['Objects with JS code'][1] - else: - versionInfo['js_objects'] = [] - elements = statsVersion['Elements'] - elementArray = [] - if elements: - for element in elements: - elementInfo = {'name': element} - if element in vulnsDict: - elementInfo['vuln_name'] = vulnsDict[element][0] - elementInfo['vuln_cve_list'] = vulnsDict[element][1] - elementInfo['objects'] = elements[element] - elementArray.append(elementInfo) - vulns = statsVersion['Vulns'] - vulnArray = [] - if vulns: - for vuln in vulns: - vulnInfo = {'name': vuln} - if vuln in vulnsDict: - vulnInfo['vuln_name'] = vulnsDict[vuln][0] - vulnInfo['vuln_cve_list'] = vulnsDict[vuln][1] - vulnInfo['objects'] = vulns[vuln] - vulnArray.append(vulnInfo) - versionInfo['suspicious_elements'] = {'triggers': statsVersion['Events'], - 'actions': statsVersion['Actions'], - 'elements': elementArray, - 'js_vulns': vulnArray, - 'urls': statsVersion['URLs']} - versionReport = {'version_info': versionInfo} - advancedInfo.append(versionReport) - jsonDict = { - 'peepdf_analysis': { - 'peepdf_info': peepdfDict, - 'date': datetime.today().strftime('%Y-%m-%d %H:%M'), - 'basic': basicDict, - 'advanced': advancedInfo, - } - } - return json.dumps(jsonDict, indent=4, sort_keys=True) +from peepdf.constants import PEEPDF_VERSION, PEEPDF_REVISION, AUTHOR, AUTHOR_EMAIL, \ + AUTHOR_TWITTER, PEEPDF_URL, GITHUB_URL, TWITTER_URL, PEEPDF_ROOT, ERROR_FILE +VT_KEY = 'fc90df3f5ac749a94a94cb8bf87e05a681a2eb001aef34b6a0084b8c22c97a64' -author = 'Jose Miguel Esparza' -email = 'peepdf AT eternal-todo.com' -url = 'http://peepdf.eternal-todo.com' -twitter = 'http://twitter.com/EternalTodo' -peepTwitter = 'http://twitter.com/peepdf' -_version = '0.3' -revision = '275' newLine = os.linesep -errorsFile = os.path.expanduser("~/.peepdf-error.txt") -versionHeader = 'Version: peepdf ' + _version + ' r' + revision -peepdfHeader = ( - versionHeader + newLine * 2 + url + newLine + peepTwitter + newLine + - email + newLine * 2 + author + newLine + twitter + newLine -) +versionHeader = 'Version: peepdf ' + PEEPDF_VERSION + ' r' + PEEPDF_REVISION +PeepdfHeader = versionHeader + newLine * 2 + \ + PEEPDF_URL + newLine + \ + TWITTER_URL + newLine + \ + AUTHOR_EMAIL + newLine * 2 + \ + AUTHOR + newLine + \ + AUTHOR_TWITTER + newLine def main(): global COLORIZED_OUTPUT @@ -366,301 +69,59 @@ def main(): argsParser.add_option('-j', '--json', action='store_true', dest='jsonOutput', default=False, help='Shows the document information in JSON format.') argsParser.add_option('-C', '--command', action='append', type='string', dest='commands', help='Specifies a command from the interactive console to be executed.') (options, args) = argsParser.parse_args() - - stats = "" - pdf = None - fileName = None - statsDict = None - vtJsonDict = None - + try: - # Avoid colors in the output - if not COLORIZED_OUTPUT or options.avoidColors: - warningColor = '' - errorColor = '' - alertColor = '' - staticColor = '' - resetColor = '' - else: - warningColor = Fore.YELLOW - errorColor = Fore.RED - alertColor = Fore.RED - staticColor = Fore.BLUE - resetColor = Style.RESET_ALL - + if options.scriptFile is not None and options.commands: + sys.stdout.write("scriptFile and commands options can't be used at the same time.\n") + sys.exit(argsParser.print_help()) if options.version: print peepdfHeader - else: - if len(args) == 1: - fileName = args[0] - if not os.path.exists(fileName): - sys.exit('Error: The file "' + fileName + '" does not exist!!') - elif len(args) > 1 or (len(args) == 0 and not options.isInteractive): - sys.exit(argsParser.print_help()) - - if options.scriptFile is not None: - if not os.path.exists(options.scriptFile): - sys.exit('Error: The script file "' + options.scriptFile + '" does not exist!!') - if fileName is not None: - pdfParser = PDFParser() - ret, pdf = pdfParser.parse(fileName, options.isForceMode, options.isLooseMode, options.isManualAnalysis) - if options.checkOnVT: - # Checks the MD5 on VirusTotal - md5Hash = pdf.getMD5() - ret = vtcheck(md5Hash, VT_KEY) - if ret[0] == -1: - pdf.addError(ret[1]) - else: - vtJsonDict = ret[1] - if "response_code" in vtJsonDict: - if vtJsonDict['response_code'] == 1: - if "positives" in vtJsonDict and "total" in vtJsonDict: - pdf.setDetectionRate([vtJsonDict['positives'], vtJsonDict['total']]) - else: - pdf.addError('Missing elements in the response from VirusTotal!!') - if "permalink" in vtJsonDict: - pdf.setDetectionReport(vtJsonDict['permalink']) - else: - pdf.setDetectionRate(None) - else: - pdf.addError('Bad response from VirusTotal!!') - statsDict = pdf.getStats() - - if options.xmlOutput: - try: - xml = getPeepXML(statsDict, _version, revision) - sys.stdout.write(xml) - except: - errorMessage = '*** Error: Exception while generating the XML file!!' - traceback.print_exc(file=open(errorsFile, 'a')) - raise Exception('PeepException', 'Send me an email ;)') - elif options.jsonOutput and not options.commands: - try: - jsonReport = getPeepJSON(statsDict, _version, revision) - sys.stdout.write(jsonReport) - except: - errorMessage = '*** Error: Exception while generating the JSON report!!' - traceback.print_exc(file=open(errorsFile, 'a')) - raise Exception('PeepException', 'Send me an email ;)') + pdfName = None + if len(args) > 1 or (len(args) == 0 and not options.isInteractive): + sys.exit(argsParser.print_help()) + elif len(args) == 1: + pdfName = args[0] + if not os.path.exists(pdfName): + sys.exit('Error: The file "' + pdfName + '" does not exist!!') + if options.scriptFile and not os.path.exists(options.scriptFile): + sys.exit('Error: The script file "' + options.scriptFile + '" does not exist!!') + pdf = None + statsDict = None + + if pdfName is not None: + pdfParser = PDFParser() + ret, pdf = pdfParser.parse(pdfName, options.isForceMode, options.isLooseMode, options.isManualAnalysis) + if options.checkOnVT: + # Checks the MD5 on VirusTotal + pdf.getVtInfo(VT_KEY) + statsDict = pdf.getStats() + + if options.isInteractive: + console = PDFConsole(pdf, VT_KEY, options.avoidColors) + console.runit() + elif options.scriptFile is not None: + console = PDFConsole(pdf, VT_KEY, options.avoidColors, scriptFile=options.scriptFile) + console.runit() + elif options.commands is not None: + commands = "\n".join(options.commands) + inputCommands = StringIO.StringIO(commands) + console = PDFConsole(pdf, VT_KEY, options.avoidColors, stdin=inputCommands) + console.runit() + elif statsDict is not None: + pdfOutput = PDFOutput.PDFOutput(avoidColors=options.avoidColors) + warnings = pdfOutput.getDecryptError(statsDict) + warnings += pdfOutput.getDependenciesWarning() + if warnings != "": + sys.stderr.write(warnings) + if options.jsonOutput: + format = "json" + elif options.xmlOutput: + format = "xml" else: - if COLORIZED_OUTPUT and not options.avoidColors: - try: - init() - except: - COLORIZED_OUTPUT = False - if options.scriptFile is not None: - from peepdf.PDFConsole import PDFConsole - - scriptFileObject = open(options.scriptFile, 'rb') - console = PDFConsole(pdf, VT_KEY, options.avoidColors, stdin=scriptFileObject) - try: - console.cmdloop() - except: - errorMessage = '*** Error: Exception not handled using the batch mode!!' - scriptFileObject.close() - traceback.print_exc(file=open(errorsFile, 'a')) - raise Exception('PeepException', 'Send me an email ;)') - elif options.commands is not None: - from PDFConsole import PDFConsole - - console = PDFConsole(pdf, VT_KEY, options.avoidColors) - try: - for command in options.commands: - console.onecmd(command) - except: - errorMessage = '*** Error: Exception not handled using the batch commands!!' - traceback.print_exc(file=open(errorsFile, 'a')) - raise Exception('PeepException', 'Send me an email ;)') - else: - if statsDict is not None: - if COLORIZED_OUTPUT and not options.avoidColors: - beforeStaticLabel = staticColor - else: - beforeStaticLabel = '' - - if not JS_MODULE: - warningMessage = 'Warning: PyV8 is not installed!!' - stats += warningColor + warningMessage + resetColor + newLine - if not EMU_MODULE: - warningMessage = 'Warning: pylibemu is not installed!!' - stats += warningColor + warningMessage + resetColor + newLine - if not PIL_MODULE: - warningMessage = 'Warning: Python Imaging Library (PIL) is not installed!!' - stats += warningColor + warningMessage + resetColor + newLine - errors = statsDict['Errors'] - for error in errors: - if error.find('Decryption error') != -1: - stats += errorColor + error + resetColor + newLine - if stats != '': - stats += newLine - statsDict = pdf.getStats() - - stats += beforeStaticLabel + 'File: ' + resetColor + statsDict['File'] + newLine - stats += beforeStaticLabel + 'MD5: ' + resetColor + statsDict['MD5'] + newLine - stats += beforeStaticLabel + 'SHA1: ' + resetColor + statsDict['SHA1'] + newLine - stats += beforeStaticLabel + 'SHA256: ' + resetColor + statsDict['SHA256'] + newLine - stats += beforeStaticLabel + 'Size: ' + resetColor + statsDict['Size'] + ' bytes' + newLine - if options.checkOnVT: - if statsDict['Detection'] != []: - detectionReportInfo = '' - if statsDict['Detection'] is not None: - detectionColor = '' - if COLORIZED_OUTPUT and not options.avoidColors: - detectionLevel = statsDict['Detection'][0] / (statsDict['Detection'][1] / 3) - if detectionLevel == 0: - detectionColor = alertColor - elif detectionLevel == 1: - detectionColor = warningColor - detectionRate = '%s%d%s/%d' % ( - detectionColor, statsDict['Detection'][0], resetColor, statsDict['Detection'][1]) - if statsDict['Detection report'] != '': - detectionReportInfo = ( - beforeStaticLabel + 'Detection report: ' + resetColor + - statsDict['Detection report'] + newLine - ) - else: - detectionRate = 'File not found on VirusTotal' - stats += beforeStaticLabel + 'Detection: ' + resetColor + detectionRate + newLine - stats += detectionReportInfo - stats += beforeStaticLabel + 'Version: ' + resetColor + statsDict['Version'] + newLine - stats += beforeStaticLabel + 'Binary: ' + resetColor + statsDict['Binary'] + newLine - stats += beforeStaticLabel + 'Linearized: ' + resetColor + statsDict['Linearized'] + newLine - stats += beforeStaticLabel + 'Encrypted: ' + resetColor + statsDict['Encrypted'] - if statsDict['Encryption Algorithms'] != []: - stats += ' (' - for algorithmInfo in statsDict['Encryption Algorithms']: - stats += algorithmInfo[0] + ' ' + str(algorithmInfo[1]) + ' bits, ' - stats = stats[:-2] + ')' - stats += newLine - stats += beforeStaticLabel + 'Updates: ' + resetColor + statsDict['Updates'] + newLine - stats += beforeStaticLabel + 'Objects: ' + resetColor + statsDict['Objects'] + newLine - stats += beforeStaticLabel + 'Streams: ' + resetColor + statsDict['Streams'] + newLine - stats += beforeStaticLabel + 'URIs: ' + resetColor + statsDict['URIs'] + newLine - stats += beforeStaticLabel + 'Comments: ' + resetColor + statsDict['Comments'] + newLine - stats += beforeStaticLabel + 'Errors: ' + resetColor + str(len(statsDict['Errors'])) + newLine * 2 - for version in range(len(statsDict['Versions'])): - statsVersion = statsDict['Versions'][version] - stats += beforeStaticLabel + 'Version ' + resetColor + str(version) + ':' + newLine - if statsVersion['Catalog'] is not None: - stats += beforeStaticLabel + '\tCatalog: ' + resetColor + statsVersion['Catalog'] + newLine - else: - stats += beforeStaticLabel + '\tCatalog: ' + resetColor + 'No' + newLine - if statsVersion['Info'] is not None: - stats += beforeStaticLabel + '\tInfo: ' + resetColor + statsVersion['Info'] + newLine - else: - stats += beforeStaticLabel + '\tInfo: ' + resetColor + 'No' + newLine - stats += beforeStaticLabel + '\tObjects (' + statsVersion['Objects'][ - 0] + '): ' + resetColor + str(statsVersion['Objects'][1]) + newLine - if statsVersion['Compressed Objects'] is not None: - stats += beforeStaticLabel + '\tCompressed objects (' + statsVersion['Compressed Objects'][ - 0] + '): ' + resetColor + str(statsVersion['Compressed Objects'][1]) + newLine - if statsVersion['Errors'] is not None: - stats += beforeStaticLabel + '\t\tErrors (' + statsVersion['Errors'][ - 0] + '): ' + resetColor + str(statsVersion['Errors'][1]) + newLine - stats += beforeStaticLabel + '\tStreams (' + statsVersion['Streams'][ - 0] + '): ' + resetColor + str(statsVersion['Streams'][1]) - if statsVersion['Xref Streams'] is not None: - stats += newLine + beforeStaticLabel + '\t\tXref streams (' + statsVersion['Xref Streams'][ - 0] + '): ' + resetColor + str(statsVersion['Xref Streams'][1]) - if statsVersion['Object Streams'] is not None: - stats += ( - newLine + beforeStaticLabel + '\t\tObject streams (' + - statsVersion['Object Streams'][0] + '): ' + resetColor + - str(statsVersion['Object Streams'][1]) - ) - if int(statsVersion['Streams'][0]) > 0: - stats += ( - newLine + beforeStaticLabel + '\t\tEncoded (' + statsVersion['Encoded'][0] + - '): ' + resetColor + str(statsVersion['Encoded'][1]) - ) - if statsVersion['Decoding Errors'] is not None: - stats += ( - newLine + beforeStaticLabel + '\t\tDecoding errors (' + - statsVersion['Decoding Errors'][0] + '): ' + resetColor + - str(statsVersion['Decoding Errors'][1]) - ) - if statsVersion['URIs'] is not None: - stats += ( - newLine + beforeStaticLabel + '\tObjects with URIs (' + - statsVersion['URIs'][0] + '): ' + resetColor + - str(statsVersion['URIs'][1]) - ) - if COLORIZED_OUTPUT and not options.avoidColors: - beforeStaticLabel = warningColor - if statsVersion['Objects with JS code'] is not None: - stats += ( - newLine + beforeStaticLabel + '\tObjects with JS code (' + - statsVersion['Objects with JS code'][0] + '): ' + resetColor + - str(statsVersion['Objects with JS code'][1]) - ) - actions = statsVersion['Actions'] - events = statsVersion['Events'] - vulns = statsVersion['Vulns'] - elements = statsVersion['Elements'] - if events is not None or actions is not None or vulns is not None or elements is not None: - stats += newLine + beforeStaticLabel + '\tSuspicious elements:' + resetColor + newLine - if events is not None: - for event in events: - stats += ( - '\t\t' + beforeStaticLabel + event + ' (%d): ' % len(events[event]) + - resetColor + str(events[event]) + newLine - ) - if actions is not None: - for action in actions: - stats += ( - '\t\t' + beforeStaticLabel + action + ' (%d): ' % len(actions[action]) + - resetColor + str(actions[action]) + newLine - ) - if vulns is not None: - for vuln in vulns: - if vuln in vulnsDict: - vulnName = vulnsDict[vuln][0] - vulnCVEList = vulnsDict[vuln][1] - stats += '\t\t' + beforeStaticLabel + vulnName + ' (' - for vulnCVE in vulnCVEList: - stats += vulnCVE + ',' - stats = stats[:-1] + ') (%d): ' % len(vulns[vuln]) + resetColor + str(vulns[vuln]) + newLine - else: - stats += ( - '\t\t' + beforeStaticLabel + vuln + ' (%d): ' % len(vulns[vuln]) + - resetColor + str(vulns[vuln]) + newLine - ) - if elements is not None: - for element in elements: - if element in vulnsDict: - vulnName = vulnsDict[element][0] - vulnCVEList = vulnsDict[element][1] - stats += '\t\t' + beforeStaticLabel + vulnName + ' (' - for vulnCVE in vulnCVEList: - stats += vulnCVE + ',' - stats = stats[:-1] + '): ' + resetColor + str(elements[element]) + newLine - else: - stats += '\t\t' + beforeStaticLabel + element + ': ' + resetColor + str( - elements[element]) + newLine - if COLORIZED_OUTPUT and not options.avoidColors: - beforeStaticLabel = staticColor - urls = statsVersion['URLs'] - if urls is not None: - stats += newLine + beforeStaticLabel + '\tFound URLs:' + resetColor + newLine - for url in urls: - stats += '\t\t' + url + newLine - stats += newLine * 2 - if fileName is not None: - print stats - if options.isInteractive: - from peepdf.PDFConsole import PDFConsole - - console = PDFConsole(pdf, VT_KEY, options.avoidColors) - while not console.leaving: - try: - console.cmdloop() - except KeyboardInterrupt as e: - sys.exit() - except: - errorMessage = '*** Error: Exception not handled using the interactive console!! Please, report it to the author!!' - print errorColor + errorMessage + resetColor + newLine - traceback.print_exc(file=open(errorsFile, 'a')) + format = "text" + stats = pdfOutput.getReport(statsDict, format) + sys.stdout.write(stats) except Exception as e: if len(e.args) == 2: excName, excReason = e.args @@ -668,13 +129,12 @@ def main(): excName = None if excName is None or excName != 'PeepException': errorMessage = '*** Error: Exception not handled!!' - traceback.print_exc(file=open(errorsFile, 'a')) - print errorColor + errorMessage + resetColor + newLine + traceback.print_exc(file=open(ERROR_FILE, 'a')) + sys.stderr.write(errorMessage) finally: - if os.path.exists(errorsFile): + if os.path.exists(ERROR_FILE): message = newLine + 'Please, don\'t forget to report errors if found:' + newLine * 2 - message += '\t- Sending the file "%s" to the author (mailto:peepdfREMOVETHIS@eternal-todo.com)%s' % ( - errorsFile, newLine) + message += '\t- Sending the file "%s" to the author (mailto:%s)%s' % ( + ERROR_FILE, AUTHOR_EMAIL, newLine) message += '\t- And/Or creating an issue on the project webpage (https://github.com/jesparza/peepdf/issues)' + newLine - message = errorColor + message + resetColor - sys.exit(message) + sys.stderr.write(message) diff --git a/setup.py b/setup.py index 04208e6..789a852 100644 --- a/setup.py +++ b/setup.py @@ -1,11 +1,13 @@ from setuptools import setup +execfile("peepdf/constants.py") + setup( name="peepdf", - version="0.3.3", - author="Jose Miguel Esparza", - license="GNU GPLv3", - url="http://eternal-todo.com", + version=PEEPDF_VERSION, + author=AUTHOR, + license=LICENCE, + url=PEEPDF_URL, install_requires=[ "jsbeautifier==1.6.2", "colorama==0.3.7",