diff --git a/ineptpdf8.pyw b/ineptpdf8.pyw index cc3e689..d620f75 100644 --- a/ineptpdf8.pyw +++ b/ineptpdf8.pyw @@ -1,12 +1,17 @@ #! /usr/bin/python -# ineptpdf74.pyw -# ineptpdf, version 7.4 +# ineptpdf8.pyw +# ineptpdf, version 8.2 # To run this program install Python 2.6 from http://www.python.org/download/ # and PyCrypto from http://www.voidspace.org.uk/python/modules.shtml#pycrypto -# (make sure to install the version for Python 2.6). Save this script file as -# ineptpdf.pyw and double-click on it to run it. +# (make sure to install the version for Python 2.6). +# +# Always use the 32-Bit Windows Python version - even with 64-bit +# windows systems. +# +# Save this script file as +# ineptpdf82.pyw and double-click on it to run it. # Revision history: # 1 - Initial release @@ -27,6 +32,10 @@ # 7.4 - Force all generation numbers in output file to be 0, like in v6. # Fallback code for wrong xref improved (search till last trailer # instead of first) +# 8 - fileopen user machine identifier support (Tetrachroma) +# 8.1 - fileopen user cookies support (Tetrachroma) +# 8.2 - fileopen user name/password support (Tetrachroma) + """ Decrypt Adobe ADEPT-encrypted PDF files. """ @@ -47,6 +56,16 @@ import Tkinter import Tkconstants import tkFileDialog import tkMessageBox +# added for fileopen support +import urllib +import urlparse +import time +import ctypes +import socket +import string +import uuid +import subprocess + try: from Crypto.Cipher import ARC4 @@ -63,6 +82,9 @@ except ImportError: class ADEPTError(Exception): pass +# global variable (needed for fileopen) +INPUTFILEPATH = '' + # Do we generate cross reference streams on output? # 0 = never # 1 = only if present in input @@ -911,6 +933,7 @@ class PDFStream(PDFObject): if 'Filter' not in self.dic: self.data = data self.rawdata = None + print self.dict return filters = self.dic['Filter'] if not isinstance(filters, list): @@ -1140,6 +1163,9 @@ class PDFDocument(object): self.parser = None self.encryption = None self.decipher = None + # dictionaries for fileopen + self.fileopen = {} + self.urlresult = {} self.ready = False return @@ -1186,7 +1212,6 @@ class PDFDocument(object): if STRICT: raise PDFSyntaxError('Catalog not found!') return - # initialize(password='') # Perform the initialization with a given password. # This step is mandatory even if there's no password associated @@ -1202,8 +1227,145 @@ class PDFDocument(object): return self.initialize_standard(password, docid, param) if type == 'EBX_HANDLER': return self.initialize_ebx(password, docid, param) + if type == 'FOPN_foweb': + return self.initialize_fopn(password, docid, param) raise PDFEncryptionError('Unknown filter: param=%r' % param) + # experimental fileopen support + def initialize_fopn(self, password, docid, param): + self.is_printable = self.is_modifiable = self.is_extractable = True + # get parameters and add it to the fo dictionary + self.fileopen['Length'] = int_value(param.get('Length', 0)) / 8 + self.fileopen['VEID'] = str_value(param.get('VEID')) + self.fileopen['BUILD'] = str_value(param.get('BUILD')) + self.fileopen['SVID'] = str_value(param.get('SVID')) + self.fileopen['DUID'] = str_value(param.get('DUID')) + self.fileopen['V'] = int_value(param.get('V',2)) + # crypt base + rights = str_value(param.get('INFO')).decode('base64') + rights = self.genkey_fileopeninfo(rights) + # print rights + for pair in rights.split(';'): + try: + key, value = pair.split('=') + self.fileopen[key] = value + # fix for some misconfigured INFO variables + except: + pass + kattr = { 'SVID': 'ServiceID', 'DUID': 'DocumentID', 'I3ID': 'Ident3ID', \ + 'I4ID': 'Ident4ID', 'VERS': 'EncrVer', 'PRID': 'USR'} + for keys in kattr: + try: + self.fileopen[kattr[keys]] = self.fileopen[keys] + del self.fileopen[keys] + except: + continue + # add static arguments for http/https request + self.fo_setattributes() + # add hardware specific arguments for http/https request + self.fo_sethwids() + # print self.fileopen + try: + buildurl = self.fileopen['UURL'] + except: + buildurl = self.fileopen['PURL'] + buildurl = buildurl + self.fileopen['DPRM'] + '?' + buildurl = buildurl + 'Request=DocPerm' + # check for reversed offline handler + try: + test = self.fileopen['IdentID4'] + raise ADEPTError('Reversed offline handler not supported yet!') + # great IdentID4 not present, let's keep on moving + except: + pass + # is it a user/pw pdf? + try: + test = self.fileopen['Ident3ID'] + except: + self.pwtk = Tkinter.Tk() + self.pwtk.title('Ineptpdf8') + self.pwtk.minsize(150, 0) + self.label1 = Tkinter.Label(self.pwtk, text="Username") + self.un_entry = Tkinter.Entry(self.pwtk) + # cursor here + self.un_entry.focus() + self.label2 = Tkinter.Label(self.pwtk, text="Password") + self.pw_entry = Tkinter.Entry(self.pwtk, show="*") + self.button = Tkinter.Button(self.pwtk, text='Go for it!', command=self.fo_save_values) + # widget layout, stack vertical + self.label1.pack() + self.un_entry.pack() + self.label2.pack() + self.pw_entry.pack() + self.button.pack() + self.pwtk.update() + # start the event loop + self.pwtk.mainloop() + # drive through tupple for building the url + burl = ( 'Stamp', 'Mode', 'USR', 'ServiceID', 'DocumentID',\ + 'Ident3ID', 'Ident4ID','DocStrFmt', 'OSType', 'Language',\ + 'LngLCID', 'LngRFC1766', 'LngISO4Char', 'Build', 'ProdVer', 'EncrVer',\ + 'Machine', 'Disk', 'Uuid', 'User', 'SaUser', 'SaSID',\ + 'FormHFT', 'UserName', 'UserPass',\ + 'SelServer', 'AcroVersion', 'AcroProduct', 'AcroReader',\ + 'AcroCanEdit', 'AcroPrefIDib', 'InBrowser', 'CliAppName',\ + 'DocIsLocal', 'DocPathUrl', 'VolName', 'VolType', 'VolSN',\ + 'FSName', 'FowpKbd', 'OSBuild', 'RequestSchema') + for keys in burl: + try: + buildurl = buildurl + '&' + keys + '=' + self.fileopen[keys] + except: + continue + # print 'url:' + # print buildurl + # custom user agent identification? + try: + useragent = self.fileopen['AGEN'] + urllib.URLopener.version = useragent + # attribute doesn't exist - take the default user agent + except: + urllib.URLopener.version = 'Windows NT 6.0' + # try to open the url + try: + u = urllib.urlopen(buildurl) + u.geturl() + result = u.read() + except: + raise ADEPTError('No internet connection or a blocking firewall!') + # print result + if result[0:8] == 'RetVal=1': + for pair in result.split('&') : + key, value = pair.split('=') + self.urlresult[key] = value + else: + raise ADEPTError(result) + # print result + try: + self.decrypt_key = self.urlresult['Code'] + except: + raise ADEPTError('Cannot find decryption key') + self.genkey = self.genkey_fo + self.decipher = self.decrypt_rc4 + self.ready = True + return + + # user/password dialog + def fo_save_values(self): + getout = 0 + username = self.un_entry.get() + password = self.pw_entry.get() + un_length = len(username) + pw_length = len(password) + if (un_length != 0) and (pw_length != 0): + getout = 1 + if getout == 1: + self.fileopen['UserName'] = urllib.quote(username) + self.fileopen['UserPass'] = urllib.quote(password) + # doesn't always close the password window, who + # knows why (Tkinter secrets ;=)) + self.pwtk.quit() + + def initialize_ebx(self, password, docid, param): self.is_printable = self.is_modifiable = self.is_extractable = True with open(password, 'rb') as f: @@ -1312,12 +1474,136 @@ class PDFDocument(object): hash = hashlib.md5(key) key = hash.digest()[:min(len(self.decrypt_key) + 5, 16)] return key + + def genkey_fo(self, objid, genno): + objid = struct.pack('> 5 + outputhw = outputhw + chr(darray[index]) + pdid = (ord(edisk[4]) << 2)|pdid + # get the last 2 bits from the hwid + low part of the cpuid + for i in range(0,2): + index = pdid & 0x1f + # shift the disk id 5 bits to the right + pdid = pdid >> 5 + outputhw = outputhw + chr(darray[index]) + return outputhw + + def fo_sethwids(self): + # write hardware keys + hwkey = 0 + # get the os type and save it in ostype + ostype = os.name + # if ostype is Windows + if ostype=='nt': + import win32api + v0 = win32api.GetVolumeInformation('C:\\') + v1 = win32api.GetSystemInfo()[6] + volserial = v0[1] + lowcpu = v1 & 255 + highcpu = (v1 >> 8) & 255 + volserial = struct.pack('