Parent

Class/Module Index [+]

Quicksearch

Rex::Zip::Jar

A Jar is a zip archive containing Java class files and a MANIFEST.MF listing those classes. Several variations exist based on the same idea of class files inside a zip, most notably:

Attributes

manifest[RW]

Public Instance Methods

add_files(files, path, base_dir="") click to toggle source

Add multiple files from an array

files should be structured like so:

[
  [ "path", "to", "file1" ],
  [ "path", "to", "file2" ]
]

and path should be the location on the file system to find the files to add. base_dir will be prepended to the path inside the jar.

Example:

war = Rex::Zip::Jar.new
war.add_file("WEB-INF/", '')
war.add_file("WEB-INF/web.xml", web_xml)
war.add_file("WEB-INF/classes/", '')
files = [
  [ "servlet", "examples", "HelloWorld.class" ],
  [ "Foo.class" ],
  [ "servlet", "Bar.class" ],
]
war.add_files(files, "./class_files/", "WEB-INF/classes/")

The above code would create a jar with the following structure from files found in ./class_files/ :

+- WEB-INF/
  +- web.xml
  +- classes/
    +- Foo.class
    +- servlet/
      +- Bar.class
      +- examples/
        +- HelloWorld.class
# File lib/rex/zip/jar.rb, line 108
def add_files(files, path, base_dir="")
        files.each do |file|
                # Add all of the subdirectories if they don't already exist
                1.upto(file.length - 1) do |idx|
                        full = base_dir + file[0,idx].join("/") + "/"
                        if !(entries.map{|e|e.name}.include?(full))
                                add_file(full, '')
                        end
                end
                # Now add the actual file, grabbing data from the filesystem
                fd = File.open(File.join( path, file ), "rb")
                data = fd.read(fd.stat.size)
                fd.close
                add_file(base_dir + file.join("/"), data)
        end
end
build_manifest(opts={}) click to toggle source

Create a MANIFEST.MF file based on the current Archive#entries.

See download.oracle.com/javase/1.4.2/docs/guide/jar/jar.html for some explanation of the format.

Example MANIFEST.MF

Manifest-Version: 1.0
Main-Class: metasploit.Payload

Name: metasploit.dat
SHA1-Digest: WJ7cUVYUryLKfQFmH80/ADfKmwM=

Name: metasploit/Payload.class
SHA1-Digest: KbAIMttBcLp1zCewA2ERYkcnRU8=

The SHA1-Digest lines are optional unless the jar is signed (see sign).

# File lib/rex/zip/jar.rb, line 36
def build_manifest(opts={})
        main_class = opts[:main_class] || nil
        existing_manifest = nil

        @manifest =  "Manifest-Version: 1.0\r\n"
        @manifest << "Main-Class: #{main_class}\r\n" if main_class
        @manifest << "\r\n"
        @entries.each { |e|
                next if e.name =~ %/$|
                if e.name == "META-INF/MANIFEST.MF"
                        existing_manifest = e
                        next
                end
                #next unless e.name =~ /\.class$/
                @manifest << "Name: #{e.name}\r\n"
                #@manifest << "SHA1-Digest: #{Digest::SHA1.base64digest(e.data)}\r\n"
                @manifest << "\r\n"
        }
        if existing_manifest
                existing_manifest.data = @manifest
        else
                add_file("META-INF/", '')
                add_file("META-INF/MANIFEST.MF", @manifest)
        end
end
length() click to toggle source

Length of the compressed blob

# File lib/rex/zip/jar.rb, line 69
def length
        pack.length
end
sign(key, cert, ca_certs=nil) click to toggle source

Add a signature to this jar given a key and a cert. cert should be an instance of OpenSSL::X509::Certificate and key is expected to be an instance of one of OpenSSL::PKey::DSA or OpenSSL::PKey::RSA.

This method aims to create signature files compatible with the jarsigner tool destributed with the JDK and any JVM should accept the resulting jar.

Signature contents

Modifies the META-INF/MANIFEST.MF entry adding SHA1-Digest attributes in each Name section. The signature consists of two files, a .SF and a .DSA (or .RSA if signing with an RSA key). The .SF file is similar to the manifest with Name sections but the SHA1-Digest is not optional. The difference is in what gets hashed for the SHA1-Digest line -- in the manifest, it is the file's contents, in the .SF, it is the file's section in the manifest (including trailing newline!). The .DSA/.RSA file is a PKCS7 signature of the .SF file contents.

Links

A short description of the format: download.oracle.com/javase/1.4.2/docs/guide/jar/jar.html#Signed%20JAR%20File

Some info on importing a private key into a keystore which is not directly supported by keytool for some unfathomable reason www.agentbob.info/agentbob/79-AB.html

# File lib/rex/zip/jar.rb, line 152
def sign(key, cert, ca_certs=nil)
        m = self.entries.find { |e| e.name == "META-INF/MANIFEST.MF" }
        raise RuntimeError.new("Jar has no manifest") unless m

        ca_certs ||= [ cert ]

        new_manifest = ''
        sigdata =  "Signature-Version: 1.0\r\n"
        sigdata << "Created-By: 1.6.0_18 (Sun Microsystems Inc.)\r\n"
        sigdata << "\r\n"

        # Grab the sections of the manifest
        files = m.data.split(/\r?\n\r?\n/)
        if files[0] =~ /Manifest-Version/
                # keep the header as is
                new_manifest << files[0]
                new_manifest << "\r\n\r\n"
                files = files[1,files.length]
        end

        # The file sections should now look like this:
        #  "Name: metasploit/Payload.class\r\nSHA1-Digest: KbAIMttBcLp1zCewA2ERYkcnRU8=\r\n\r\n"
        files.each do |f|
                next unless f =~ /Name: (.*)/
                name = $1
                e = self.entries.find { |e| e.name == name }
                if e
                        digest = OpenSSL::Digest::SHA1.digest(e.data)
                        manifest_section =  "Name: #{name}\r\n"
                        manifest_section << "SHA1-Digest: #{[digest].pack('m').strip}\r\n"
                        manifest_section << "\r\n"

                        manifest_digest = OpenSSL::Digest::SHA1.digest(manifest_section)

                        sigdata << "Name: #{name}\r\n"
                        sigdata << "SHA1-Digest: #{[manifest_digest].pack('m')}\r\n"
                        new_manifest << manifest_section
                end
        end

        # Now overwrite with the new manifest
        m.data = new_manifest

        flags = 0
        flags |= OpenSSL::PKCS7::BINARY
        flags |= OpenSSL::PKCS7::DETACHED
        # SMIME and ATTRs are technically valid in the signature but they
        # both screw up the java verifier, so don't include them.
        flags |= OpenSSL::PKCS7::NOSMIMECAP
        flags |= OpenSSL::PKCS7::NOATTR

        signature = OpenSSL::PKCS7.sign(cert, key, sigdata, ca_certs, flags)
        sigalg = case key
                when OpenSSL::PKey::RSA; "RSA"
                when OpenSSL::PKey::DSA; "DSA"
                # Don't really know what to do if it's not DSA or RSA.  Can
                # OpenSSL::PKCS7 actually sign stuff with it in that case?
                # Regardless, the java spec says signatures can only be RSA,
                # DSA, or PGP, so just assume it's PGP and hope for the best
                else; "PGP"
                end

        # SIGNFILE is the default name in documentation.  MYKEY is probably
        # more common, though because that's what keytool defaults to.  We
        # can probably randomize this with no ill effects.
        add_file("META-INF/SIGNFILE.SF", sigdata)
        add_file("META-INF/SIGNFILE.#{sigalg}", signature.to_der)

        return true
end
to_s() click to toggle source
# File lib/rex/zip/jar.rb, line 62
def to_s
        pack
end

[Validate]

Generated with the Darkfish Rdoc Generator 2.