Files
metasploit-gs/lib/rex/post/meterpreter/extensions/stdapi/railgun/multicall.rb
T
2017-06-27 18:00:01 -04:00

325 lines
12 KiB
Ruby

# -*- coding: binary -*-
# Copyright (c) 2010, patrickHVE@googlemail.com
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are met:
# * Redistributions of source code must retain the above copyright
# notice, this list of conditions and the following disclaimer.
# * Redistributions in binary form must reproduce the above copyright
# notice, this list of conditions and the following disclaimer in the
# documentation and/or other materials provided with the distribution.
# * The names of the author may not be used to endorse or promote products
# derived from this software without specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
# DISCLAIMED. IN NO EVENT SHALL patrickHVE@googlemail.com BE LIABLE FOR ANY
# DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
# ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
require 'pp'
require 'enumerator'
require 'rex/post/meterpreter/extensions/stdapi/railgun/tlv'
require 'rex/post/meterpreter/extensions/stdapi/railgun/library_helper'
require 'rex/post/meterpreter/extensions/stdapi/railgun/buffer_item'
module Rex
module Post
module Meterpreter
module Extensions
module Stdapi
module Railgun
# A easier way to call multiple functions in a single request
class MultiCaller
include LibraryHelper
def initialize(client, parent, consts_mgr)
@parent = parent
@client = client
# needed by LibraryHelper
@consts_mgr = consts_mgr
if @client.native_arch == ARCH_X64
@native = 'Q<'
else
@native = 'V'
end
end
def call(functions)
request = Packet.create_request('stdapi_railgun_api_multi')
function_results = []
layouts = []
functions.each do |f|
lib_name, function, args = f
lib_host = @parent.get_library(lib_name)
if not lib_host
raise "Library #{lib_name} has not been loaded"
end
unless function.instance_of? LibraryFunction
function = lib_host.functions[function]
if not function
raise "Library #{lib_name} function #{function} has not been defined"
end
end
raise "#{function.params.length} arguments expected. #{args.length} arguments provided." unless args.length == function.params.length
# We transmit the immediate stack and three heap-buffers:
# in, inout and out. The reason behind the separation is bandwidth.
# We don't want to transmit uninitialized data in or no-longer-needed data out.
# out-only-buffers that are ONLY transmitted on the way BACK
out_only_layout = {} # paramName => BufferItem
out_only_size_bytes = 0
#puts " assembling out-only buffer"
function.params.each_with_index do |param_desc, param_idx|
#puts " processing #{param_desc[1]}"
# Special case:
# The user can choose to supply a Null pointer instead of a buffer
# in this case we don't need space in any heap buffer
if param_desc[0][0,1] == 'P' # type is a pointer
if args[param_idx] == nil
next
end
end
# we care only about out-only buffers
if param_desc[2] == 'out'
if !args[param_idx].class.kind_of? Integer
raise "error in param #{param_desc[1]}: Out-only buffers must be described by a number indicating their size in bytes "
end
buffer_size = args[param_idx]
# bump up the size for an x64 pointer
if @native == 'Q<' && buffer_size == 4
args[param_idx] = 8
buffer_size = args[param_idx]
end
if @native == 'Q<'
if buffer_size != 8
raise "Please pass 8 for 'out' PDWORDS, since they require a buffer of size 8"
end
elsif( @native == 'V' )
if buffer_size != 4
raise "Please pass 4 for 'out' PDWORDS, since they require a buffer of size 4"
end
end
out_only_layout[param_desc[1]] = BufferItem.new(param_idx, out_only_size_bytes, buffer_size, param_desc[0])
out_only_size_bytes += buffer_size
end
end
tmp = assemble_buffer('in', function, args)
in_only_layout = tmp[0]
in_only_buffer = tmp[1]
tmp = assemble_buffer('inout', function, args)
inout_layout = tmp[0]
inout_buffer = tmp[1]
# now we build the stack
# every stack dword will be described by two dwords:
# first dword describes second dword:
# 0 - literal,
# 1 = relative to in-only buffer
# 2 = relative to out-only buffer
# 3 = relative to inout buffer
# (literal numbers and pointers to buffers we have created)
literal_pairs_blob = ""
#puts " assembling literal stack"
function.params.each_with_index do |param_desc, param_idx|
#puts " processing (#{param_desc[0]}, #{param_desc[1]}, #{param_desc[2]})"
buffer = nil
# is it a pointer to a buffer on our stack
if ['PDWORD', 'PWCHAR', 'PCHAR', 'PBLOB'].include? param_desc[0]
#puts ' pointer'
if args[param_idx] == nil # null pointer?
buffer = [0].pack(@native) # type: DWORD (so the library does not rebase it)
buffer += [0].pack(@native) # value: 0
elsif param_desc[2] == 'in'
buffer = [1].pack(@native)
buffer += [in_only_layout[param_desc[1]].addr].pack(@native)
elsif param_desc[2] == 'out'
buffer = [2].pack(@native)
buffer += [out_only_layout[param_desc[1]].addr].pack(@native)
elsif param_desc[2] == 'inout'
buffer = [3].pack(@native)
buffer += [inout_layout[param_desc[1]].addr].pack(@native)
else
raise 'unexpected direction'
end
else
#puts ' not a pointer'
# it's not a pointer
buffer = [0].pack(@native)
case param_desc[0]
when 'LPVOID', 'HANDLE', 'SIZE_T'
num = param_to_number(args[param_idx])
buffer += [num].pack(@native)
when 'DWORD'
num = param_to_number(args[param_idx])
buffer += [num % 4294967296].pack(@native)
when 'WORD'
num = param_to_number(args[param_idx])
buffer += [num % 65536].pack(@native)
when 'BYTE'
num = param_to_number(args[param_idx])
buffer += [num % 256].pack(@native)
when 'BOOL'
case args[param_idx]
when true
buffer += [1].pack('V')
when false
buffer += [0].pack('V')
else
raise "param #{param_desc[1]}: true or false expected"
end
else
raise "unexpected type for param #{param_desc[1]}"
end
end
#puts " adding pair to blob"
literal_pairs_blob += buffer
#puts " buffer size %X" % buffer.length
#puts " blob size so far: %X" % literal_pairs_blob.length
end
#puts "\n\nsending Stuff to meterpreter"
group = Rex::Post::Meterpreter::GroupTlv.new(TLV_TYPE_RAILGUN_MULTI_GROUP)
group.add_tlv(TLV_TYPE_RAILGUN_SIZE_OUT, out_only_size_bytes)
group.add_tlv(TLV_TYPE_RAILGUN_STACKBLOB, literal_pairs_blob)
group.add_tlv(TLV_TYPE_RAILGUN_BUFFERBLOB_IN, in_only_buffer)
group.add_tlv(TLV_TYPE_RAILGUN_BUFFERBLOB_INOUT, inout_buffer)
group.add_tlv(TLV_TYPE_RAILGUN_LIBNAME, lib_host.library_path)
group.add_tlv(TLV_TYPE_RAILGUN_FUNCNAME, function.remote_name)
request.tlvs << group
layouts << [inout_layout, out_only_layout]
end
call_results = []
res = @client.send_request(request)
res.each(TLV_TYPE_RAILGUN_MULTI_GROUP) do |val|
call_results << val
end
functions.each do |f|
lib_name, function, args = f
lib_host = @parent.get_library(lib_name)
function = lib_host.functions[function] unless function.instance_of? LibraryFunction
response = call_results.shift
inout_layout, out_only_layout = layouts.shift
rec_inout_buffers = response.get_tlv_value(TLV_TYPE_RAILGUN_BACK_BUFFERBLOB_INOUT)
rec_out_only_buffers = response.get_tlv_value(TLV_TYPE_RAILGUN_BACK_BUFFERBLOB_OUT)
rec_return_value = response.get_tlv_value(TLV_TYPE_RAILGUN_BACK_RET)
rec_last_error = response.get_tlv_value(TLV_TYPE_RAILGUN_BACK_ERR)
rec_err_msg = response.get_tlv_value(TLV_TYPE_RAILGUN_BACK_MSG)
# Error messages come back with trailing CRLF, so strip it out
# if we do get a message.
rec_err_msg.strip! if not rec_err_msg.nil?
# The hash the function returns
return_hash = {
'GetLastError' => rec_last_error,
'ErrorMessage' => rec_err_msg
}
#process return value
case function.return_type
when 'LPVOID', 'HANDLE'
if( @native == 'Q<' )
return_hash['return'] = rec_return_value
else
return_hash['return'] = rec_return_value % 4294967296
end
when 'DWORD'
return_hash['return'] = rec_return_value % 4294967296
when 'WORD'
return_hash['return'] = rec_return_value % 65536
when 'BYTE'
return_hash['return'] = rec_return_value % 256
when 'BOOL'
return_hash['return'] = (rec_return_value != 0)
when 'VOID'
return_hash['return'] = nil
else
raise "unexpected return type: #{function.return_type}"
end
#puts return_hash
#puts "out_only_layout:"
#puts out_only_layout
# process out-only buffers
#puts "processing out-only buffers:"
out_only_layout.each_pair do |param_name, buffer_item|
#puts " #{param_name}"
buffer = rec_out_only_buffers[buffer_item.addr, buffer_item.length_in_bytes]
case buffer_item.datatype
when 'PDWORD'
return_hash[param_name] = buffer.unpack('V')[0]
when 'PCHAR'
return_hash[param_name] = asciiz_to_str(buffer)
when 'PWCHAR'
return_hash[param_name] = uniz_to_str(buffer)
when 'PBLOB'
return_hash[param_name] = buffer
else
raise "unexpected type in out-only buffer of #{param_name}: #{buffer_item.datatype}"
end
end
#puts return_hash
# process in-out buffers
#puts "processing in-out buffers:"
inout_layout.each_pair do |param_name, buffer_item|
#puts ' #{param_name}'
buffer = rec_inout_buffers[buffer_item.addr, buffer_item.length_in_bytes]
case buffer_item.datatype
when 'PDWORD'
return_hash[param_name] = buffer.unpack('V')[0]
when 'PCHAR'
return_hash[param_name] = asciiz_to_str(buffer)
when 'PWCHAR'
return_hash[param_name] = uniz_to_str(buffer)
when 'PBLOB'
return_hash[param_name] = buffer
else
raise "unexpected type in in-out-buffer of #{param_name}: #{buffer_item.datatype}"
end
end
#puts return_hash
#puts "finished"
function_results << return_hash
end
function_results
end
# process_multi_function_call
protected
end # MultiCall
end; end; end; end; end; end