# -*- coding: binary -*- ### # # This mixn provides methods for interacting with a JDK installation to perform # functions such as dynamic compilation and jar signing. # # Dependencies: # - JDK6 # - rjb (rjb.rubyforge.org) # - the $JAVA_HOME variable must point to the JDK # # Nathan Keltner # ### require 'msf/core' module Msf module Exploit::Java def initialize(info = {}) super register_advanced_options( [ OptString.new( 'JavaCache', [true, 'Java cache location', File.join(Msf::Config.config_directory, "javacache")]), OptString.new( 'AddClassPath', [false, 'Additional java classpath', nil]), ], self.class) begin require 'rjb' @rjb_loaded = true init_jvm rescue ::Exception => e @rjb_loaded = false @jvm_init = false @java_error = e end end def init_jvm(jvmoptions = nil) if (not ENV['JAVA_HOME']) raise RuntimeError, 'JAVA_HOME is not set' end toolsjar = File.join(ENV['JAVA_HOME'], "lib", "tools.jar") if (not File.exists? toolsjar) raise RuntimeError, 'JAVA_HOME does not point to a valid JDK installation.' end # Instantiate the JVM with a classpath pointing to the JDK tools.jar # and our javatoolkit jar. classpath = File.join(Msf::Config.install_root, "data", "exploits", "msfJavaToolkit.jar") classpath += ":" + toolsjar classpath += ":" + datastore['ADDCLASSPATH'] if datastore['ADDCLASSPATH'] Rjb::load(classpath, jvmargs=[]) @jvm_init = true end def query_jvm return @jvmInit end def save_to_file(classnames, codez, location) path = File.join( Msf::Config.install_root, "external", "source", location ) if not File.exists? path Dir.mkdir(path) end i = 0 classnames.each { |fil| file = File.join( path, fil + ".java") fp = File.open( file, "wb" ) print_status "Writing #{fil} to " + file fp.puts codez[i] i += 1 fp.close } end def compile(classnames, codez, compile_options=nil) if !@rjb_loaded or !@jvm_init raise RuntimeError, "Could not load rjb and/or the JVM: " + @java_error.to_s end if compile_options.class.to_s != "Array" && compile_options raise RuntimeError, "Compiler options must be of type Array." end compile_options = [] if compile_options.nil? # Create the directory if it doesn't exist Dir.mkdir(datastore['JavaCache']) if !File.exists? datastore['JavaCache'] # For compatibility, some exploits need to have the target and source version # set to a previous JRE version. std_compiler_opts = [ "-target", "1.3", "-source", "1.3", "-d", datastore['JavaCache'] ] compile_options += std_compiler_opts java_compiler_klass = Rjb::import('javaCompile.CompileSourceInMemory') # If we were passed arrays if classnames.class == [].class && codez.class == [].class # default compile class begin # Sames as java_compiler_klass.CompileFromMemory( String[] classnames, # String[] codez, String[] compilerOptions) success = java_compiler_klass._invoke('CompileFromMemory', # Signature explained: [ means array, Lpath.to.object; means object # Thus, this reads as call the method with 3 String[] args. '[Ljava.lang.String;[Ljava.lang.String;[Ljava.lang.String;', classnames, codez, compile_options) rescue Exception => e print_error "Received unknown error: " + e end else raise RuntimeError, "The Java mixin received unknown argument-type combinations and cannot continue." end if !success raise RuntimeError, "Compile failed." end end def build_jar(output_jar, in_files) if output_jar.class != "".class || in_files.class != [].class raise RuntimeError, "Building a jar requires an output_jar and an Array of in_files." end # Add paths in_files = in_files.map { |file| File.join(datastore['JavaCache'], file) } create_jar_klass = Rjb::import('javaCompile.CreateJarFile') file_class = Rjb::import('java.io.File') file_out_jar = file_class.new_with_sig('Ljava.lang.String;', File.join(datastore['JavaCache'], output_jar) ) files_in = Array.new in_files.each { |file| files_in << file_class.new_with_sig('Ljava.lang.String;', file) } create_jar_klass._invoke('createJarArchive', 'Ljava.io.File;[Ljava.io.File;', file_out_jar, files_in) end # # http://www.defcon.org/images/defcon-17/dc-17-presentations/defcon-17-valsmith-metaphish.pdf # def sign_jar(cert_cn, unsiged_jar, signed_jar, cert_alias="signFiles", msf_keystore="msfkeystore", msf_store_pass="msfstorepass", msf_key_pass="msfkeypass") # Dependent on $JAVA_HOME/lib/tools.jar that comes with the JDK. signer_klass = Rjb::import('javaCompile.SignJar') # Check if the keystore exists from previous run. If it does, delete it. msf_keystore = File.join(datastore['JavaCache'], msf_keystore) File.delete msf_keystore if File.exists? msf_keystore # Rjb pukes on a CN with a comma in it so bad that it crashes to shell # and turns input echoing off. Simple fix for this ugly bug is # just to get rid of commas which kinda sucks but whatever. See #1543. keytool_opts = [ "-genkey", "-alias", cert_alias, "-keystore", msf_keystore, "-storepass", msf_store_pass, "-dname", "CN=#{cert_cn.gsub(",",'')}", "-keypass", "msfkeypass" ] # Build the cert keystore signer_klass._invoke('KeyToolMSF','[Ljava.lang.String;',keytool_opts) jarsigner_opts = [ "-keystore", msf_keystore, "-storepass", msf_store_pass, "-keypass", msf_key_pass, "-signedJar", File.join(datastore['JavaCache'], signed_jar), # Signed Jar File.join(datastore['JavaCache'], unsiged_jar), # Input Jar we're signing cert_alias # The cert we're using ] signer_klass._invoke('JarSignerMSF','[Ljava.lang.String;',jarsigner_opts) # There are warnings in the source for KeyTool/JarSigner warning that security providers # are not released, and if you are calling .main(foo) from another app, you need to release # them manually. This is not done here, and should Rjb be used for anything in the future, # this may need to be cleaned up. end end end