metasploit-framework/lib/msf/core/exploit/java.rb

189 lines
5.9 KiB
Ruby
Raw Normal View History

# -*- 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 <natron@metasploit.com>
#
###
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