--- /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())