src/tools/py2sis/ensymble/decodesisx.py
changeset 0 ca70ae20a155
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/tools/py2sis/ensymble/decodesisx.py	Tue Feb 16 10:07:05 2010 +0530
@@ -0,0 +1,463 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+
+##############################################################################
+# decodesisx.py - Decodes a Symbian OS v9.x SISX file
+# Copyright 2006, 2007 Jussi Ylänen
+#
+# This program is based on a whitepaper by Symbian's Security team:
+# Symbian OS v9.X SIS File Format Specification, Version 1.1, June 2006
+# http://developer.symbian.com/main/downloads/papers/SymbianOSv91/softwareinstallsis.pdf
+#
+# This program is part of Ensymble developer utilities for Symbian OS(TM).
+#
+# Ensymble 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 2 of the License, or
+# (at your option) any later version.
+#
+# Ensymble 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 Ensymble; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
+#
+#
+# Version history
+# ---------------
+#
+# v0.09 2006-09-22
+# Replaced every possible range(...) with xrange(...) for efficiency
+#
+# v0.08 2006-09-12
+# Implemented a SISController dump option (-c, for use with -d and/or -f)
+#
+# v0.07 2006-09-05
+# Implemented a header hex dump option (-e)
+#
+# v0.06 2006-08-29
+# Fixed errors in uncompressed SISCompressed field handling
+#
+# v0.05 2006-08-10
+# Option --dumptofile (-f) now uses a directory for dumped files
+# A temporary directory is generated if none given by the user
+# Other small corrections and polishing
+#
+# v0.04 2006-08-09
+# Added command line options using getopt
+# Added support for reading the SISX file from stdin
+# Made it possible to extract files from SISX
+# Improved hexdump ASCII support
+#
+# v0.03 2006-08-07
+# Added some crude debug features: dumping to files, decompressed data dumping
+#
+# v0.02 2006-08-06
+# Changed field type flags to callbacks for flexibility
+# Added an UID checking and printing
+#
+# v0.01 2006-08-04
+# Initial version
+#
+# v0.00 2006-08-03
+# Work started
+##############################################################################
+
+VERSION = "v0.09 2006-09-22"
+
+import sys
+import os
+import zlib
+import struct
+import getopt
+import random
+import tempfile
+
+# Parameters
+MAXSISFILESIZE      = 8 * 1024 * 1024   # Arbitrary maximum size of SISX file
+
+sisfilename = None
+tempdir = None
+dumpcounter = 0
+norecursecompressed = False
+
+class options:
+    '''Command line options'''
+
+    hexdump         = False
+    headerdump      = False
+    dumpcontroller  = False
+    dumptofile      = False
+
+def mkdtemp(template):
+    '''
+    Create a unique temporary directory.
+
+    tempfile.mkdtemp() was introduced in Python v2.3. This is for
+    backward compatibility.
+    '''
+
+    # Cross-platform way to determine a suitable location for temporary files.
+    systemp = tempfile.gettempdir()
+
+    if not template.endswith("XXXXXX"):
+        raise ValueError("invalid template for mkdtemp(): %s" % template)
+
+    for n in xrange(10000):
+        randchars = []
+        for m in xrange(6):
+            randchars.append(random.choice("abcdefghijklmnopqrstuvwxyz"))
+
+        tempdir = os.path.join(systemp, template[: -6]) + "".join(randchars)
+
+        try:
+            os.mkdir(tempdir, 0700)
+            return tempdir
+        except OSError:
+            pass
+
+def hexdump(data, datalen = None):
+    '''Print binary data as a human readable hex dump.'''
+
+    if datalen == None or datalen > len(data):
+        datalen = len(data)
+
+    offset = 0
+    while offset < datalen:
+        line = []
+        line.append("%06x:" % offset)
+        for n in xrange(16):
+            if n & 3 == 0:
+                line.append(" ")
+            if (offset + n) < datalen:
+                c = data[offset + n]
+                line.append("%02x " % ord(c))
+            else:
+                line.append("   ")
+        line.append(' "')
+        for n in xrange(16):
+            if (offset + n) < datalen:
+                c = data[offset + n]
+                if ord(c) >= 32 and ord(c) < 127:
+                    line.append(c)
+                else:
+                    line.append(".")
+            else:
+                break
+        line.append('"')
+
+        print "".join(line)
+        offset += 16
+
+def handlearray(data, datalen, reclevel):
+    '''Handle SISArray.'''
+
+    arraytype = data[:4]
+    data = data[4:]
+
+    arraypos = 0
+    arraylen = datalen - 4
+    while arraypos < arraylen:
+        # Construct virtual SISFields for each array element.
+        arraydata = arraytype + data[arraypos:]
+        arraypos += parsesisfield(arraydata,
+                                  arraylen - arraypos + 4, reclevel + 1) - 4
+
+    if arraypos != arraylen:
+        raise ValueError("SISArray data length mismatch")
+
+def handlecompressed(data, datalen, reclevel):
+    '''Handle SISCompressed.'''
+
+    if datalen < 12:
+        raise ValueError("SISCompressed contents too short")
+
+    compalgo = struct.unpack("<L", data[:4])[0]
+    uncomplen = struct.unpack("<Q", data[4:12])[0]
+
+    print "%s%s  %d bytes uncompressed, algorithm %d" % ("  " * reclevel,
+                                                         " " * 13, uncomplen,
+                                                         compalgo)
+
+    if compalgo == 0:
+        # No compression, strip SISField and SISCompressed headers.
+        data = data[12:datalen]
+    elif compalgo == 1:
+        # RFC1950 (zlib header and checksum) compression, decompress.
+        data = zlib.decompress(data[12:datalen])
+    else:
+        raise ValueError("invalid SISCompressed algorithm %d" % compalgo)
+
+    if uncomplen != len(data):
+        raise ValueError("SISCompressed uncompressed data length mismatch")
+
+    if norecursecompressed:
+        # Recursive parsing disabled temporarily from handlefiledata().
+        # Dump data instead.
+        dumpdata(data, uncomplen, reclevel + 1)
+    else:
+        # Normal recursive parsing, delegate to handlerecursive().
+        handlerecursive(data, uncomplen, reclevel)
+
+def handlerecursive(data, datalen, reclevel):
+    '''Handle recursive SISFields, i.e. SISFields only containing
+    other SISFields.'''
+
+    parselen = parsebuffer(data, datalen, reclevel + 1)
+    if datalen != parselen:
+        raise ValueError("recursive SISField data length mismatch %d %d" %
+                            (datalen, parselen))
+
+def handlefiledata(data, datalen, reclevel):
+    '''Handle SISFileData.'''
+
+    global norecursecompressed
+
+    # Temporarily disable recursion for handlecompressed().
+    oldnrc = norecursecompressed
+    norecursecompressed = True
+    handlerecursive(data, datalen, reclevel)
+    norecursecompressed = oldnrc
+
+def handlecontroller(data, datalen, reclevel):
+    '''Handle SISController SISField. Dump data if required.'''
+
+    if options.dumpcontroller:
+        dumpdata(data, datalen, reclevel)
+
+    # Handle contained fields as usual.
+    handlerecursive(data, datalen, reclevel)
+
+def dumpdata(data, datalen, reclevel):
+    '''Dumps data to a file in a temporary directory.'''
+
+    global tempdir, dumpcounter
+
+    if options.hexdump:
+        hexdump(data, datalen)
+        print
+    if options.dumptofile:
+        if tempdir == None:
+            # Create temporary directory for dumped files.
+            tempdir = mkdtemp("decodesisx-XXXXXX")
+            dumpcounter = 0
+
+        filename = os.path.join(tempdir, "dump%04d" % dumpcounter)
+        dumpcounter += 1
+        f = file(filename, "wb")
+        f.write(data[:datalen])
+        f.close()
+        print "%sContents written to %s" % ("  " * reclevel, filename)
+
+# SISField types and callbacks
+sisfieldtypes = [
+    ("Invalid SISField",                None),
+    ("SISString",                       dumpdata),
+    ("SISArray",                        handlearray),
+    ("SISCompressed",                   handlecompressed),
+    ("SISVersion",                      dumpdata),
+    ("SISVersionRange",                 handlerecursive),
+    ("SISDate",                         dumpdata),
+    ("SISTime",                         dumpdata),
+    ("SISDateTime",                     handlerecursive),
+    ("SISUid",                          dumpdata),
+    ("Unused",                          None),
+    ("SISLanguage",                     dumpdata),
+    ("SISContents",                     handlerecursive),
+    ("SISController",                   handlecontroller),
+    ("SISInfo",                         dumpdata),  # TODO: SISInfo
+    ("SISSupportedLanguages",           handlerecursive),
+    ("SISSupportedOptions",             handlerecursive),
+    ("SISPrerequisites",                handlerecursive),
+    ("SISDependency",                   handlerecursive),
+    ("SISProperties",                   handlerecursive),
+    ("SISProperty",                     dumpdata),
+    ("SISSignatures",                   handlerecursive),
+    ("SISCertificateChain",             handlerecursive),
+    ("SISLogo",                         handlerecursive),
+    ("SISFileDescription",              dumpdata),  # TODO: SISFileDescription
+    ("SISHash",                         dumpdata),  # TODO: SISHash
+    ("SISIf",                           handlerecursive),
+    ("SISElseIf",                       handlerecursive),
+    ("SISInstallBlock",                 handlerecursive),
+    ("SISExpression",                   dumpdata),  # TODO: SISExpression
+    ("SISData",                         handlerecursive),
+    ("SISDataUnit",                     handlerecursive),
+    ("SISFileData",                     handlefiledata),
+    ("SISSupportedOption",              handlerecursive),
+    ("SISControllerChecksum",           dumpdata),
+    ("SISDataChecksum",                 dumpdata),
+    ("SISSignature",                    handlerecursive),
+    ("SISBlob",                         dumpdata),
+    ("SISSignatureAlgorithm",           handlerecursive),
+    ("SISSignatureCertificateChain",    handlerecursive),
+    ("SISDataIndex",                    dumpdata),
+    ("SISCapabilities",                 dumpdata)   # TODO: SISCapabilities
+]
+
+def parsesisfieldheader(data):
+    datalen = len(data)
+
+    headerlen = 8
+    if datalen < headerlen:
+        raise ValueError("not enough data for a complete SISField header")
+
+    # Get SISField type.
+    fieldtype = struct.unpack("<L", data[:4])[0]
+
+    # Get SISField length, 31-bit or 63-bit.
+    fieldlen = struct.unpack("<L", data[4:8])[0]
+    fieldlen2 = None
+    if fieldlen & 0x8000000L:
+        # 63-bit length, read rest of length.
+        headerlen = 12
+        if datalen < headerlen:
+            raise ValueError("not enough data for a complete SISField header")
+        fieldlen2 = struct.unpack("<L", data[8:12])[0]
+        fieldlen = (fieldlen & 0x7ffffffL) | (fieldlen2 << 31)
+
+    return fieldtype, headerlen, fieldlen
+
+def parsesisfield(data, datalen, reclevel):
+    '''Parse one SISField. Call an appropriate callback
+    from sisfieldtypes[].'''
+
+    fieldtype, headerlen, fieldlen = parsesisfieldheader(data)
+
+    # Check SISField type.
+    fieldcallback = None
+    if fieldtype < len(sisfieldtypes):
+        fieldname, fieldcallback = sisfieldtypes[fieldtype]
+
+    if fieldcallback == None:
+        # Invalid field type, terminate.
+        raise ValueError("invalid SISField type %d" % fieldtype)
+
+    # Calculate padding to 32-bit boundary.
+    padlen = ((fieldlen + 3) & ~0x3L) - fieldlen
+
+    # Verify length.
+    if (headerlen + fieldlen + padlen) > datalen:
+        raise ValueError("SISField contents too short")
+
+    print "%s%s: %d bytes" % ("  " * reclevel, fieldname, fieldlen)
+
+    if options.headerdump:
+        hexdump(data[:headerlen])
+        print
+
+    # Call field callback.
+    sisfieldtypes[fieldtype][1](data[headerlen:], fieldlen, reclevel)
+
+    return headerlen + fieldlen + padlen
+
+def parsebuffer(data, datalen, reclevel):
+    '''Parse all successive SISFields.'''
+
+    datapos = 0
+    while datapos < datalen:
+        fieldlen = parsesisfield(data[datapos:], datalen - datapos, reclevel)
+        datapos += fieldlen
+
+    return datapos
+
+def main():
+    global sisfilename, tempdir, dumpcounter, options
+
+    pgmname     = os.path.basename(sys.argv[0])
+    pgmversion  = VERSION
+
+    try:
+        try:
+            gopt = getopt.gnu_getopt
+        except:
+            # Python <v2.3, GNU-style parameter ordering not supported.
+            gopt = getopt.getopt
+
+        # Parse command line using getopt.
+        short_opts = "decft:h"
+        long_opts = [
+            "hexdump", "headerdump", "dumpcontroller",
+            "dumptofile", "dumpdir", "help"
+        ]
+        args = gopt(sys.argv[1:], short_opts, long_opts)
+
+        opts = dict(args[0])
+        pargs = args[1]
+
+        if len(pargs) > 1 or "--help" in opts.keys() or "-h" in opts.keys():
+            # Help requested.
+            print (
+'''
+DecodeSISX - Symbian OS v9.x SISX file decoder %(pgmversion)s
+
+usage: %(pgmname)s [--dumptofile] [--hexdump] [--dumpdir=DIR] [sisfile]
+
+        -d, --hexdump        - Show interesting SISFields as hex dumps
+        -e, --headerdump     - Show SISField headers as hex dumps
+        -c, --dumpcontroller - Dump each SISController SISField separately
+        -f, --dumptofile     - Save interesting SISFields to files
+        -t, --dumpdir        - Directory to use for dumped files (automatic)
+        sisfile              - SIS file to decode (stdin if not given or -)
+
+''' % locals())
+            return 0
+
+        if "--hexdump" in opts.keys() or "-d" in opts.keys():
+            options.hexdump = True
+
+        if "--headerdump" in opts.keys() or "-e" in opts.keys():
+            options.headerdump = True
+
+        if "--dumpcontroller" in opts.keys() or "-c" in opts.keys():
+            options.dumpcontroller = True
+
+        if "--dumptofile" in opts.keys() or "-f" in opts.keys():
+            options.dumptofile = True
+
+        # A temporary directory is generated by default.
+        tempdir = opts.get("--dumpdir", opts.get("-t", None))
+
+        if len(pargs) == 0 or pargs[0] == '-':
+            sisfilename = "stdin"
+            sisfile = sys.stdin
+        else:
+            sisfilename = pargs[0]
+            sisfile = file(sisfilename, "rb")
+
+        try:
+            # Load the whole SIS file as a string.
+            sisdata = sisfile.read(MAXSISFILESIZE)
+            if len(sisdata) == MAXSISFILESIZE:
+                raise IOError("%s: file too large" % sisfilename)
+        finally:
+            if sisfile != sys.stdin:
+                sisfile.close()
+
+        if len(sisdata) < 16:
+            raise ValueError("%s: file too short" % sisfilename)
+
+        # Check UIDs.
+        uid1, uid2, uid3, uidcrc = struct.unpack("<LLLL", sisdata[:16])
+        if uid1 != 0x10201a7a:
+            if (uid2 in (0x1000006D, 0x10003A12)) and uid3 == 0x10000419:
+                raise ValueError("%s: pre-9.1 SIS file" % sisfilename)
+            else:
+                raise ValueError("%s: not a SIS file" % sisfilename)
+
+        print "UID1: 0x%08x, UID2: 0x%08x, UID3: 0x%08x, UIDCRC: 0x%08x\n" % (
+            uid1, uid2, uid3, uidcrc)
+
+        # Recursively parse the SIS file.
+        parsebuffer(sisdata[16:], len(sisdata) - 16, 0)
+    except (TypeError, ValueError, IOError, OSError), e:
+        return "%s: %s" % (pgmname, str(e))
+    except KeyboardInterrupt:
+        return ""
+
+# Call main if run as stand-alone executable.
+if __name__ == '__main__':
+    sys.exit(main())