]> xmof Git - DeDRM.git/commitdiff
ineptpdf 8.2 fileopen
authorTetraChroma <tetrachroma@wordpress.com>
Sat, 12 Jun 2010 07:18:24 +0000 (08:18 +0100)
committerApprentice Alf <apprenticealf@gmail.com>
Tue, 3 Mar 2015 07:18:58 +0000 (07:18 +0000)
ineptpdf8.pyw

index cc3e6890175a2ed65d61f5ea3aa3d284fcf9541c..d620f755f53862205f6489df1d3c3d9792bbe0ee 100644 (file)
@@ -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
 #   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('<L', objid)[:3]
+        genno = struct.pack('<L', genno)[:2]
+        key =  self.decrypt_key + objid + genno
+        hash = hashlib.md5(key)
+        key = hash.digest()[:min(len(self.decrypt_key) + 5, 16)]
+        return key
     
     def decrypt_rc4(self, objid, genno, data):
         key = self.genkey(objid, genno)
         return ARC4.new(key).decrypt(data)
     
+    def fo_setattributes(self):
+        self.fileopen['Request']='DocPerm'
+        self.fileopen['Mode']='CNR'
+        self.fileopen['DocStrFmt']='ASCII'
+        self.fileopen['OSType']='Windows'
+        self.fileopen['Language']='ENU'
+        self.fileopen['LngLCID']='ENU'
+        self.fileopen['LngRFC1766']='en'
+        self.fileopen['LngISO4Char']='en-us'
+        self.fileopen['Build']='879'
+        self.fileopen['ProdVer']='1.8.7.9'
+        # Machine, Disk, Uuid,
+        self.fileopen['FormHFT']='Yes'
+        self.fileopen['SelServer']='Yes'
+        self.fileopen['AcroVersion']='9.256'
+        self.fileopen['AcroProduct']='Exchange-Pro'
+        self.fileopen['AcroReader']='No'
+        self.fileopen['AcroCanEdit']='Yes'
+        self.fileopen['AcroPrefIDib']='Yes'
+        self.fileopen['InBrowser']='Unk'
+        self.fileopen['CliAppName']=''
+        self.fileopen['DocIsLocal']='Yes'
+        self.fileopen['FSName']='NTFS'
+        self.fileopen['FowpKbd']='No'
+        self.fileopen['OSBuild']='7600'
+        self.fileopen['RequestSchema']='Default'
+        self.fileopen['VolType']='Fixed'
+
+    # get nic mac address
+    def get_win_macaddress(self):
+        p = subprocess.Popen('ipconfig /all', shell = True, stdout=subprocess.PIPE)
+        p.wait()
+        rawtxt = p.stdout.read()
+        return re.findall(r'\s([0-9A-F-]{17})\s',rawtxt)[0].replace('-','')
+     
+    # custom conversion 5 bytes to 8 chars method
+    def fo_convert5to8(self, edisk):
+        # byte to number/char mapping table
+        darray=[0x32,0x33,0x34,0x35,0x36,0x37,0x38,0x39,0x41,0x42,0x43,0x44,0x45,\
+                0x46,0x47,0x48,0x4A,0x4B,0x4C,0x4D,0x4E,0x50,0x51,0x52,0x53,0x54,\
+                0x55,0x56,0x57,0x58,0x59,0x5A]
+        pdid = struct.pack('<I', int(edisk[0:4].encode("hex"),16))
+        pdid = int(pdid.encode("hex"),16)
+        outputhw = ''
+        # disk id processing
+        for i in range(0,6):
+            index = pdid & 0x1f
+            # shift the disk id 5 bits to the right
+            pdid = pdid >> 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('<L', int(volserial))
+            lowcpu   = struct.pack('B', lowcpu)
+            highcpu = struct.pack('B',highcpu)
+            # save it to the fo dictionary
+            encrypteddisk = volserial + lowcpu + highcpu
+            self.fileopen['Disk'] = self.fo_convert5to8(encrypteddisk)
+            # get primary used default mac address
+            pmac = self.get_win_macaddress().decode("hex");
+            self.fileopen['Machine'] = self.fo_convert5to8(pmac[1:])
+            # get uuid
+            self.fileopen['Uuid'] = str(uuid.uuid1())
+            # get time stamp
+            self.fileopen['Stamp'] = str(time.time())[:-3]
+            # get fileopen input pdf name + path
+            self.fileopen['DocPathUrl'] = 'file%3a%2f%2f%2f' + INPUTFILEPATH
+            # get volume name (urllib quote necessairy?)
+            self.fileopen['VolName'] = urllib.quote(win32api.GetVolumeInformation("C:\\")[0])
+            # get volume serial number
+            self.fileopen['VolSN'] = str(win32api.GetVolumeInformation("C:\\")[1])
+        elif ostype=='linux':
+            adeptout = 'Linux is not supported, yet.\n'
+            raise ADEPTError(adeptout)            
+        else:
+            adeptout = adeptout + 'Due to various privacy violations from Apple\n'
+            adeptout = adeptout + 'Mac OS X support is disabled by default.'
+            raise ADEPTError(adeptout)
+        return
+
+    # decryption routine for the INFO area
+    def genkey_fileopeninfo(self, data):
+        input1 = struct.pack('L', 0xa4da49de)
+        seed   = struct.pack('B', 0x82)
+        key = input1[3] + input1[2] +input1[1] +input1[0] + seed
+        hash = hashlib.md5()
+        key = hash.update(key)
+        spointer4 = struct.pack('<L', 0xec8d6c58)
+        seed = struct.pack('B', 0x07)
+        key = spointer4[3] + spointer4[2] + spointer4[1] + spointer4[0] + seed 
+        key = hash.update(key)
+        md5 = hash.digest()
+        key = md5[0:10]
+        return ARC4.new(key).decrypt(data)
+    
     KEYWORD_OBJ = PSKeywordTable.intern('obj')
+    
     def getobj(self, objid):
         if not self.ready:
             raise PDFException('PDFDocument not initialized')
@@ -1803,7 +2089,8 @@ def cli_main(argv=sys.argv):
 class DecryptionDialog(Tkinter.Frame):
     def __init__(self, root):
         Tkinter.Frame.__init__(self, root, border=5)
-        self.status = Tkinter.Label(self, text='Select files for decryption')
+        ltext='Select file for decryption\n(Ignore Key file option for Fileopen PDFs)'        
+        self.status = Tkinter.Label(self, text=ltext)
         self.status.pack(fill=Tkconstants.X, expand=1)
         body = Tkinter.Frame(self)
         body.pack(fill=Tkconstants.X, expand=1)
@@ -1870,6 +2157,7 @@ class DecryptionDialog(Tkinter.Frame):
         return
 
     def decrypt(self):
+        global INPUTFILEPATH 
         keypath = self.keypath.get()
         inpath = self.inpath.get()
         outpath = self.outpath.get()
@@ -1885,8 +2173,9 @@ class DecryptionDialog(Tkinter.Frame):
         if inpath == outpath:
             self.status['text'] = 'Must have different input and output files'
             return
+        INPUTFILEPATH = urllib.quote(inpath)        
         argv = [sys.argv[0], keypath, inpath, outpath]
-        self.status['text'] = 'Decrypting...'
+        self.status['text'] = 'Processing ...'
         try:
             cli_main(argv)
         except Exception, e:
@@ -1903,9 +2192,9 @@ def gui_main():
             "This script requires PyCrypto, which must be installed "
             "separately.  Read the top-of-script comment for details.")
         return 1
-    root.title('INEPT PDF Decrypter')
+    root.title('INEPT PDF Decrypter 8.2 (FileOpen Support)')
     root.resizable(True, False)
-    root.minsize(300, 0)
+    root.minsize(310, 0)
     DecryptionDialog(root).pack(fill=Tkconstants.X, expand=1)
     root.mainloop()
     return 0