class 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:
-
WAR files store XML files, Java classes, JSPs and other stuff for servlet-based webservers (e.g.: Tomcat and Glassfish)
-
APK files are Android Package files
Attributes
@!attribute [rw] substitutions
The substitutions to apply when randomizing. Randomization is designed to be used in packages and/or classes names. @return [Hash]
Public Class Methods
# File lib/rex/zip/jar.rb, line 24 def initialize @substitutions = {} super end
Public Instance Methods
Adds a file to the JAR, randomizing the file name and the contents.
@see Rex::Zip::Archive#add_file
# File lib/rex/zip/jar.rb, line 242 def add_file(fname, fdata=nil, xtra=nil, comment=nil) super(randomize(fname), randomize(fdata), xtra, comment) end
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 123 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
Adds a substitution to have into account when randomizing. Substitutions must be added immediately after {#initialize}.
@param str [String] String to substitute. It's designed to randomize
class and/or package names.
@param bad [String] String containing bad characters to avoid when
applying substitutions.
@return [String] The substitution which will be used when randomizing.
# File lib/rex/zip/jar.rb, line 254 def add_sub(str, bad = '') if @substitutions.key?(str) return @substitutions[str] end @substitutions[str] = Rex::Text.rand_text_alpha(str.length, bad) end
Create a MANIFEST.MF file based on the current Rex::Zip::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 47 def build_manifest(opts={}) main_class = (opts[:main_class] ? randomize(opts[:main_class]) : nil) app_name = (opts[:app_name] ? randomize(opts[:app_name]) : nil) existing_manifest = nil meta_inf_exists = @entries.find_all{|item| item.name == 'META-INF/' }.length > 0 @manifest = "Manifest-Version: 1.0\r\n" @manifest << "Main-Class: #{main_class}\r\n" if main_class @manifest << "Application-Name: #{app_name}\r\n" if app_name @manifest << "Permissions: all-permissions\r\n" @manifest << "\r\n" @entries.each { |e| next if e.name =~ %r|/$| 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/", '') unless meta_inf_exists add_file("META-INF/MANIFEST.MF", @manifest) end end
Length of the compressed blob
# File lib/rex/zip/jar.rb, line 84 def length pack.length end
Randomizes an input by applying the `substitutions` available.
@param str [String] String to randomize. @return [String] The input `str` with all the possible `substitutions`
applied.
# File lib/rex/zip/jar.rb, line 267 def randomize(str) return str if str.nil? random = str @substitutions.each do |orig, subs| random = str.gsub(orig, subs) end random end
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 167 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
# File lib/rex/zip/jar.rb, line 77 def to_s pack end