src/tools/py2sis/ensymble/decodesisx.py
changeset 0 ca70ae20a155
equal deleted inserted replaced
-1:000000000000 0:ca70ae20a155
       
     1 #!/usr/bin/env python
       
     2 # -*- coding: utf-8 -*-
       
     3 
       
     4 ##############################################################################
       
     5 # decodesisx.py - Decodes a Symbian OS v9.x SISX file
       
     6 # Copyright 2006, 2007 Jussi Ylänen
       
     7 #
       
     8 # This program is based on a whitepaper by Symbian's Security team:
       
     9 # Symbian OS v9.X SIS File Format Specification, Version 1.1, June 2006
       
    10 # http://developer.symbian.com/main/downloads/papers/SymbianOSv91/softwareinstallsis.pdf
       
    11 #
       
    12 # This program is part of Ensymble developer utilities for Symbian OS(TM).
       
    13 #
       
    14 # Ensymble is free software; you can redistribute it and/or modify
       
    15 # it under the terms of the GNU General Public License as published by
       
    16 # the Free Software Foundation; either version 2 of the License, or
       
    17 # (at your option) any later version.
       
    18 #
       
    19 # Ensymble is distributed in the hope that it will be useful,
       
    20 # but WITHOUT ANY WARRANTY; without even the implied warranty of
       
    21 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
       
    22 # GNU General Public License for more details.
       
    23 #
       
    24 # You should have received a copy of the GNU General Public License
       
    25 # along with Ensymble; if not, write to the Free Software
       
    26 # Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
       
    27 #
       
    28 #
       
    29 # Version history
       
    30 # ---------------
       
    31 #
       
    32 # v0.09 2006-09-22
       
    33 # Replaced every possible range(...) with xrange(...) for efficiency
       
    34 #
       
    35 # v0.08 2006-09-12
       
    36 # Implemented a SISController dump option (-c, for use with -d and/or -f)
       
    37 #
       
    38 # v0.07 2006-09-05
       
    39 # Implemented a header hex dump option (-e)
       
    40 #
       
    41 # v0.06 2006-08-29
       
    42 # Fixed errors in uncompressed SISCompressed field handling
       
    43 #
       
    44 # v0.05 2006-08-10
       
    45 # Option --dumptofile (-f) now uses a directory for dumped files
       
    46 # A temporary directory is generated if none given by the user
       
    47 # Other small corrections and polishing
       
    48 #
       
    49 # v0.04 2006-08-09
       
    50 # Added command line options using getopt
       
    51 # Added support for reading the SISX file from stdin
       
    52 # Made it possible to extract files from SISX
       
    53 # Improved hexdump ASCII support
       
    54 #
       
    55 # v0.03 2006-08-07
       
    56 # Added some crude debug features: dumping to files, decompressed data dumping
       
    57 #
       
    58 # v0.02 2006-08-06
       
    59 # Changed field type flags to callbacks for flexibility
       
    60 # Added an UID checking and printing
       
    61 #
       
    62 # v0.01 2006-08-04
       
    63 # Initial version
       
    64 #
       
    65 # v0.00 2006-08-03
       
    66 # Work started
       
    67 ##############################################################################
       
    68 
       
    69 VERSION = "v0.09 2006-09-22"
       
    70 
       
    71 import sys
       
    72 import os
       
    73 import zlib
       
    74 import struct
       
    75 import getopt
       
    76 import random
       
    77 import tempfile
       
    78 
       
    79 # Parameters
       
    80 MAXSISFILESIZE      = 8 * 1024 * 1024   # Arbitrary maximum size of SISX file
       
    81 
       
    82 sisfilename = None
       
    83 tempdir = None
       
    84 dumpcounter = 0
       
    85 norecursecompressed = False
       
    86 
       
    87 class options:
       
    88     '''Command line options'''
       
    89 
       
    90     hexdump         = False
       
    91     headerdump      = False
       
    92     dumpcontroller  = False
       
    93     dumptofile      = False
       
    94 
       
    95 def mkdtemp(template):
       
    96     '''
       
    97     Create a unique temporary directory.
       
    98 
       
    99     tempfile.mkdtemp() was introduced in Python v2.3. This is for
       
   100     backward compatibility.
       
   101     '''
       
   102 
       
   103     # Cross-platform way to determine a suitable location for temporary files.
       
   104     systemp = tempfile.gettempdir()
       
   105 
       
   106     if not template.endswith("XXXXXX"):
       
   107         raise ValueError("invalid template for mkdtemp(): %s" % template)
       
   108 
       
   109     for n in xrange(10000):
       
   110         randchars = []
       
   111         for m in xrange(6):
       
   112             randchars.append(random.choice("abcdefghijklmnopqrstuvwxyz"))
       
   113 
       
   114         tempdir = os.path.join(systemp, template[: -6]) + "".join(randchars)
       
   115 
       
   116         try:
       
   117             os.mkdir(tempdir, 0700)
       
   118             return tempdir
       
   119         except OSError:
       
   120             pass
       
   121 
       
   122 def hexdump(data, datalen = None):
       
   123     '''Print binary data as a human readable hex dump.'''
       
   124 
       
   125     if datalen == None or datalen > len(data):
       
   126         datalen = len(data)
       
   127 
       
   128     offset = 0
       
   129     while offset < datalen:
       
   130         line = []
       
   131         line.append("%06x:" % offset)
       
   132         for n in xrange(16):
       
   133             if n & 3 == 0:
       
   134                 line.append(" ")
       
   135             if (offset + n) < datalen:
       
   136                 c = data[offset + n]
       
   137                 line.append("%02x " % ord(c))
       
   138             else:
       
   139                 line.append("   ")
       
   140         line.append(' "')
       
   141         for n in xrange(16):
       
   142             if (offset + n) < datalen:
       
   143                 c = data[offset + n]
       
   144                 if ord(c) >= 32 and ord(c) < 127:
       
   145                     line.append(c)
       
   146                 else:
       
   147                     line.append(".")
       
   148             else:
       
   149                 break
       
   150         line.append('"')
       
   151 
       
   152         print "".join(line)
       
   153         offset += 16
       
   154 
       
   155 def handlearray(data, datalen, reclevel):
       
   156     '''Handle SISArray.'''
       
   157 
       
   158     arraytype = data[:4]
       
   159     data = data[4:]
       
   160 
       
   161     arraypos = 0
       
   162     arraylen = datalen - 4
       
   163     while arraypos < arraylen:
       
   164         # Construct virtual SISFields for each array element.
       
   165         arraydata = arraytype + data[arraypos:]
       
   166         arraypos += parsesisfield(arraydata,
       
   167                                   arraylen - arraypos + 4, reclevel + 1) - 4
       
   168 
       
   169     if arraypos != arraylen:
       
   170         raise ValueError("SISArray data length mismatch")
       
   171 
       
   172 def handlecompressed(data, datalen, reclevel):
       
   173     '''Handle SISCompressed.'''
       
   174 
       
   175     if datalen < 12:
       
   176         raise ValueError("SISCompressed contents too short")
       
   177 
       
   178     compalgo = struct.unpack("<L", data[:4])[0]
       
   179     uncomplen = struct.unpack("<Q", data[4:12])[0]
       
   180 
       
   181     print "%s%s  %d bytes uncompressed, algorithm %d" % ("  " * reclevel,
       
   182                                                          " " * 13, uncomplen,
       
   183                                                          compalgo)
       
   184 
       
   185     if compalgo == 0:
       
   186         # No compression, strip SISField and SISCompressed headers.
       
   187         data = data[12:datalen]
       
   188     elif compalgo == 1:
       
   189         # RFC1950 (zlib header and checksum) compression, decompress.
       
   190         data = zlib.decompress(data[12:datalen])
       
   191     else:
       
   192         raise ValueError("invalid SISCompressed algorithm %d" % compalgo)
       
   193 
       
   194     if uncomplen != len(data):
       
   195         raise ValueError("SISCompressed uncompressed data length mismatch")
       
   196 
       
   197     if norecursecompressed:
       
   198         # Recursive parsing disabled temporarily from handlefiledata().
       
   199         # Dump data instead.
       
   200         dumpdata(data, uncomplen, reclevel + 1)
       
   201     else:
       
   202         # Normal recursive parsing, delegate to handlerecursive().
       
   203         handlerecursive(data, uncomplen, reclevel)
       
   204 
       
   205 def handlerecursive(data, datalen, reclevel):
       
   206     '''Handle recursive SISFields, i.e. SISFields only containing
       
   207     other SISFields.'''
       
   208 
       
   209     parselen = parsebuffer(data, datalen, reclevel + 1)
       
   210     if datalen != parselen:
       
   211         raise ValueError("recursive SISField data length mismatch %d %d" %
       
   212                             (datalen, parselen))
       
   213 
       
   214 def handlefiledata(data, datalen, reclevel):
       
   215     '''Handle SISFileData.'''
       
   216 
       
   217     global norecursecompressed
       
   218 
       
   219     # Temporarily disable recursion for handlecompressed().
       
   220     oldnrc = norecursecompressed
       
   221     norecursecompressed = True
       
   222     handlerecursive(data, datalen, reclevel)
       
   223     norecursecompressed = oldnrc
       
   224 
       
   225 def handlecontroller(data, datalen, reclevel):
       
   226     '''Handle SISController SISField. Dump data if required.'''
       
   227 
       
   228     if options.dumpcontroller:
       
   229         dumpdata(data, datalen, reclevel)
       
   230 
       
   231     # Handle contained fields as usual.
       
   232     handlerecursive(data, datalen, reclevel)
       
   233 
       
   234 def dumpdata(data, datalen, reclevel):
       
   235     '''Dumps data to a file in a temporary directory.'''
       
   236 
       
   237     global tempdir, dumpcounter
       
   238 
       
   239     if options.hexdump:
       
   240         hexdump(data, datalen)
       
   241         print
       
   242     if options.dumptofile:
       
   243         if tempdir == None:
       
   244             # Create temporary directory for dumped files.
       
   245             tempdir = mkdtemp("decodesisx-XXXXXX")
       
   246             dumpcounter = 0
       
   247 
       
   248         filename = os.path.join(tempdir, "dump%04d" % dumpcounter)
       
   249         dumpcounter += 1
       
   250         f = file(filename, "wb")
       
   251         f.write(data[:datalen])
       
   252         f.close()
       
   253         print "%sContents written to %s" % ("  " * reclevel, filename)
       
   254 
       
   255 # SISField types and callbacks
       
   256 sisfieldtypes = [
       
   257     ("Invalid SISField",                None),
       
   258     ("SISString",                       dumpdata),
       
   259     ("SISArray",                        handlearray),
       
   260     ("SISCompressed",                   handlecompressed),
       
   261     ("SISVersion",                      dumpdata),
       
   262     ("SISVersionRange",                 handlerecursive),
       
   263     ("SISDate",                         dumpdata),
       
   264     ("SISTime",                         dumpdata),
       
   265     ("SISDateTime",                     handlerecursive),
       
   266     ("SISUid",                          dumpdata),
       
   267     ("Unused",                          None),
       
   268     ("SISLanguage",                     dumpdata),
       
   269     ("SISContents",                     handlerecursive),
       
   270     ("SISController",                   handlecontroller),
       
   271     ("SISInfo",                         dumpdata),  # TODO: SISInfo
       
   272     ("SISSupportedLanguages",           handlerecursive),
       
   273     ("SISSupportedOptions",             handlerecursive),
       
   274     ("SISPrerequisites",                handlerecursive),
       
   275     ("SISDependency",                   handlerecursive),
       
   276     ("SISProperties",                   handlerecursive),
       
   277     ("SISProperty",                     dumpdata),
       
   278     ("SISSignatures",                   handlerecursive),
       
   279     ("SISCertificateChain",             handlerecursive),
       
   280     ("SISLogo",                         handlerecursive),
       
   281     ("SISFileDescription",              dumpdata),  # TODO: SISFileDescription
       
   282     ("SISHash",                         dumpdata),  # TODO: SISHash
       
   283     ("SISIf",                           handlerecursive),
       
   284     ("SISElseIf",                       handlerecursive),
       
   285     ("SISInstallBlock",                 handlerecursive),
       
   286     ("SISExpression",                   dumpdata),  # TODO: SISExpression
       
   287     ("SISData",                         handlerecursive),
       
   288     ("SISDataUnit",                     handlerecursive),
       
   289     ("SISFileData",                     handlefiledata),
       
   290     ("SISSupportedOption",              handlerecursive),
       
   291     ("SISControllerChecksum",           dumpdata),
       
   292     ("SISDataChecksum",                 dumpdata),
       
   293     ("SISSignature",                    handlerecursive),
       
   294     ("SISBlob",                         dumpdata),
       
   295     ("SISSignatureAlgorithm",           handlerecursive),
       
   296     ("SISSignatureCertificateChain",    handlerecursive),
       
   297     ("SISDataIndex",                    dumpdata),
       
   298     ("SISCapabilities",                 dumpdata)   # TODO: SISCapabilities
       
   299 ]
       
   300 
       
   301 def parsesisfieldheader(data):
       
   302     datalen = len(data)
       
   303 
       
   304     headerlen = 8
       
   305     if datalen < headerlen:
       
   306         raise ValueError("not enough data for a complete SISField header")
       
   307 
       
   308     # Get SISField type.
       
   309     fieldtype = struct.unpack("<L", data[:4])[0]
       
   310 
       
   311     # Get SISField length, 31-bit or 63-bit.
       
   312     fieldlen = struct.unpack("<L", data[4:8])[0]
       
   313     fieldlen2 = None
       
   314     if fieldlen & 0x8000000L:
       
   315         # 63-bit length, read rest of length.
       
   316         headerlen = 12
       
   317         if datalen < headerlen:
       
   318             raise ValueError("not enough data for a complete SISField header")
       
   319         fieldlen2 = struct.unpack("<L", data[8:12])[0]
       
   320         fieldlen = (fieldlen & 0x7ffffffL) | (fieldlen2 << 31)
       
   321 
       
   322     return fieldtype, headerlen, fieldlen
       
   323 
       
   324 def parsesisfield(data, datalen, reclevel):
       
   325     '''Parse one SISField. Call an appropriate callback
       
   326     from sisfieldtypes[].'''
       
   327 
       
   328     fieldtype, headerlen, fieldlen = parsesisfieldheader(data)
       
   329 
       
   330     # Check SISField type.
       
   331     fieldcallback = None
       
   332     if fieldtype < len(sisfieldtypes):
       
   333         fieldname, fieldcallback = sisfieldtypes[fieldtype]
       
   334 
       
   335     if fieldcallback == None:
       
   336         # Invalid field type, terminate.
       
   337         raise ValueError("invalid SISField type %d" % fieldtype)
       
   338 
       
   339     # Calculate padding to 32-bit boundary.
       
   340     padlen = ((fieldlen + 3) & ~0x3L) - fieldlen
       
   341 
       
   342     # Verify length.
       
   343     if (headerlen + fieldlen + padlen) > datalen:
       
   344         raise ValueError("SISField contents too short")
       
   345 
       
   346     print "%s%s: %d bytes" % ("  " * reclevel, fieldname, fieldlen)
       
   347 
       
   348     if options.headerdump:
       
   349         hexdump(data[:headerlen])
       
   350         print
       
   351 
       
   352     # Call field callback.
       
   353     sisfieldtypes[fieldtype][1](data[headerlen:], fieldlen, reclevel)
       
   354 
       
   355     return headerlen + fieldlen + padlen
       
   356 
       
   357 def parsebuffer(data, datalen, reclevel):
       
   358     '''Parse all successive SISFields.'''
       
   359 
       
   360     datapos = 0
       
   361     while datapos < datalen:
       
   362         fieldlen = parsesisfield(data[datapos:], datalen - datapos, reclevel)
       
   363         datapos += fieldlen
       
   364 
       
   365     return datapos
       
   366 
       
   367 def main():
       
   368     global sisfilename, tempdir, dumpcounter, options
       
   369 
       
   370     pgmname     = os.path.basename(sys.argv[0])
       
   371     pgmversion  = VERSION
       
   372 
       
   373     try:
       
   374         try:
       
   375             gopt = getopt.gnu_getopt
       
   376         except:
       
   377             # Python <v2.3, GNU-style parameter ordering not supported.
       
   378             gopt = getopt.getopt
       
   379 
       
   380         # Parse command line using getopt.
       
   381         short_opts = "decft:h"
       
   382         long_opts = [
       
   383             "hexdump", "headerdump", "dumpcontroller",
       
   384             "dumptofile", "dumpdir", "help"
       
   385         ]
       
   386         args = gopt(sys.argv[1:], short_opts, long_opts)
       
   387 
       
   388         opts = dict(args[0])
       
   389         pargs = args[1]
       
   390 
       
   391         if len(pargs) > 1 or "--help" in opts.keys() or "-h" in opts.keys():
       
   392             # Help requested.
       
   393             print (
       
   394 '''
       
   395 DecodeSISX - Symbian OS v9.x SISX file decoder %(pgmversion)s
       
   396 
       
   397 usage: %(pgmname)s [--dumptofile] [--hexdump] [--dumpdir=DIR] [sisfile]
       
   398 
       
   399         -d, --hexdump        - Show interesting SISFields as hex dumps
       
   400         -e, --headerdump     - Show SISField headers as hex dumps
       
   401         -c, --dumpcontroller - Dump each SISController SISField separately
       
   402         -f, --dumptofile     - Save interesting SISFields to files
       
   403         -t, --dumpdir        - Directory to use for dumped files (automatic)
       
   404         sisfile              - SIS file to decode (stdin if not given or -)
       
   405 
       
   406 ''' % locals())
       
   407             return 0
       
   408 
       
   409         if "--hexdump" in opts.keys() or "-d" in opts.keys():
       
   410             options.hexdump = True
       
   411 
       
   412         if "--headerdump" in opts.keys() or "-e" in opts.keys():
       
   413             options.headerdump = True
       
   414 
       
   415         if "--dumpcontroller" in opts.keys() or "-c" in opts.keys():
       
   416             options.dumpcontroller = True
       
   417 
       
   418         if "--dumptofile" in opts.keys() or "-f" in opts.keys():
       
   419             options.dumptofile = True
       
   420 
       
   421         # A temporary directory is generated by default.
       
   422         tempdir = opts.get("--dumpdir", opts.get("-t", None))
       
   423 
       
   424         if len(pargs) == 0 or pargs[0] == '-':
       
   425             sisfilename = "stdin"
       
   426             sisfile = sys.stdin
       
   427         else:
       
   428             sisfilename = pargs[0]
       
   429             sisfile = file(sisfilename, "rb")
       
   430 
       
   431         try:
       
   432             # Load the whole SIS file as a string.
       
   433             sisdata = sisfile.read(MAXSISFILESIZE)
       
   434             if len(sisdata) == MAXSISFILESIZE:
       
   435                 raise IOError("%s: file too large" % sisfilename)
       
   436         finally:
       
   437             if sisfile != sys.stdin:
       
   438                 sisfile.close()
       
   439 
       
   440         if len(sisdata) < 16:
       
   441             raise ValueError("%s: file too short" % sisfilename)
       
   442 
       
   443         # Check UIDs.
       
   444         uid1, uid2, uid3, uidcrc = struct.unpack("<LLLL", sisdata[:16])
       
   445         if uid1 != 0x10201a7a:
       
   446             if (uid2 in (0x1000006D, 0x10003A12)) and uid3 == 0x10000419:
       
   447                 raise ValueError("%s: pre-9.1 SIS file" % sisfilename)
       
   448             else:
       
   449                 raise ValueError("%s: not a SIS file" % sisfilename)
       
   450 
       
   451         print "UID1: 0x%08x, UID2: 0x%08x, UID3: 0x%08x, UIDCRC: 0x%08x\n" % (
       
   452             uid1, uid2, uid3, uidcrc)
       
   453 
       
   454         # Recursively parse the SIS file.
       
   455         parsebuffer(sisdata[16:], len(sisdata) - 16, 0)
       
   456     except (TypeError, ValueError, IOError, OSError), e:
       
   457         return "%s: %s" % (pgmname, str(e))
       
   458     except KeyboardInterrupt:
       
   459         return ""
       
   460 
       
   461 # Call main if run as stand-alone executable.
       
   462 if __name__ == '__main__':
       
   463     sys.exit(main())