Initial attempt
parent
54c83d98fd
commit
e057467329
|
@ -0,0 +1,34 @@
|
||||||
|
# -*- coding: binary -*-
|
||||||
|
module Rex
|
||||||
|
module Post
|
||||||
|
module Meterpreter
|
||||||
|
module Extensions
|
||||||
|
module Stdapi
|
||||||
|
module Railgun
|
||||||
|
module Def
|
||||||
|
|
||||||
|
class Def_wldap32
|
||||||
|
|
||||||
|
def self.create_dll(dll_path = 'wldap32')
|
||||||
|
dll = DLL.new(dll_path, ApiConstants.manager)
|
||||||
|
|
||||||
|
dll.add_function( 'ldap_sslinitW', 'PDWORD',[
|
||||||
|
['PCHAR', 'HostName', 'in'],
|
||||||
|
['DWORD', 'PortNumber', 'in'],
|
||||||
|
['DWORD', 'secure', 'in']
|
||||||
|
])
|
||||||
|
|
||||||
|
dll.add_function( 'ldap_simple_bind_sW', 'DWORD',[
|
||||||
|
['DWORD', 'ld', 'in'],
|
||||||
|
['PCHAR', 'dn', 'in'],
|
||||||
|
['PCHAR', 'passwd', 'in']
|
||||||
|
])
|
||||||
|
|
||||||
|
return dll
|
||||||
|
end
|
||||||
|
|
||||||
|
end
|
||||||
|
|
||||||
|
end; end; end; end; end; end; end
|
||||||
|
|
||||||
|
|
|
@ -1,298 +1,299 @@
|
||||||
# -*- coding: binary -*-
|
# -*- coding: binary -*-
|
||||||
# Copyright (c) 2010, patrickHVE@googlemail.com
|
# Copyright (c) 2010, patrickHVE@googlemail.com
|
||||||
# All rights reserved.
|
# All rights reserved.
|
||||||
#
|
#
|
||||||
# Redistribution and use in source and binary forms, with or without
|
# Redistribution and use in source and binary forms, with or without
|
||||||
# modification, are permitted provided that the following conditions are met:
|
# modification, are permitted provided that the following conditions are met:
|
||||||
# * Redistributions of source code must retain the above copyright
|
# * Redistributions of source code must retain the above copyright
|
||||||
# notice, this list of conditions and the following disclaimer.
|
# notice, this list of conditions and the following disclaimer.
|
||||||
# * Redistributions in binary form must reproduce the above copyright
|
# * Redistributions in binary form must reproduce the above copyright
|
||||||
# notice, this list of conditions and the following disclaimer in the
|
# notice, this list of conditions and the following disclaimer in the
|
||||||
# documentation and/or other materials provided with the distribution.
|
# documentation and/or other materials provided with the distribution.
|
||||||
# * The names of the author may not be used to endorse or promote products
|
# * The names of the author may not be used to endorse or promote products
|
||||||
# derived from this software without specific prior written permission.
|
# derived from this software without specific prior written permission.
|
||||||
#
|
#
|
||||||
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
# 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
|
# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||||
# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||||
# DISCLAIMED. IN NO EVENT SHALL patrickHVE@googlemail.com BE LIABLE FOR ANY
|
# DISCLAIMED. IN NO EVENT SHALL patrickHVE@googlemail.com BE LIABLE FOR ANY
|
||||||
# DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
# DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||||
# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||||
# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
||||||
# ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
# 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
|
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||||
# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
|
||||||
#
|
#
|
||||||
# sf - Sept 2010 - Modified for x64 support and merged into the stdapi extension.
|
# sf - Sept 2010 - Modified for x64 support and merged into the stdapi extension.
|
||||||
#
|
#
|
||||||
|
|
||||||
#
|
#
|
||||||
# chao - June 2011 - major overhaul of dll lazy loading, caching, and bit of everything
|
# chao - June 2011 - major overhaul of dll lazy loading, caching, and bit of everything
|
||||||
#
|
#
|
||||||
|
|
||||||
require 'pp'
|
require 'pp'
|
||||||
require 'enumerator'
|
require 'enumerator'
|
||||||
|
|
||||||
require 'rex/post/meterpreter/extensions/stdapi/railgun/api_constants'
|
require 'rex/post/meterpreter/extensions/stdapi/railgun/api_constants'
|
||||||
require 'rex/post/meterpreter/extensions/stdapi/railgun/tlv'
|
require 'rex/post/meterpreter/extensions/stdapi/railgun/tlv'
|
||||||
require 'rex/post/meterpreter/extensions/stdapi/railgun/util'
|
require 'rex/post/meterpreter/extensions/stdapi/railgun/util'
|
||||||
require 'rex/post/meterpreter/extensions/stdapi/railgun/win_const_manager'
|
require 'rex/post/meterpreter/extensions/stdapi/railgun/win_const_manager'
|
||||||
require 'rex/post/meterpreter/extensions/stdapi/railgun/multicall'
|
require 'rex/post/meterpreter/extensions/stdapi/railgun/multicall'
|
||||||
require 'rex/post/meterpreter/extensions/stdapi/railgun/dll'
|
require 'rex/post/meterpreter/extensions/stdapi/railgun/dll'
|
||||||
require 'rex/post/meterpreter/extensions/stdapi/railgun/dll_wrapper'
|
require 'rex/post/meterpreter/extensions/stdapi/railgun/dll_wrapper'
|
||||||
|
|
||||||
module Rex
|
module Rex
|
||||||
module Post
|
module Post
|
||||||
module Meterpreter
|
module Meterpreter
|
||||||
module Extensions
|
module Extensions
|
||||||
module Stdapi
|
module Stdapi
|
||||||
module Railgun
|
module Railgun
|
||||||
|
|
||||||
|
|
||||||
#
|
#
|
||||||
# The Railgun class to dynamically expose the Windows API.
|
# The Railgun class to dynamically expose the Windows API.
|
||||||
#
|
#
|
||||||
class Railgun
|
class Railgun
|
||||||
|
|
||||||
#
|
#
|
||||||
# Railgun::DLL's that have builtin definitions.
|
# Railgun::DLL's that have builtin definitions.
|
||||||
#
|
#
|
||||||
# If you want to add additional DLL definitions to be preloaded create a
|
# If you want to add additional DLL definitions to be preloaded create a
|
||||||
# definition class 'rex/post/meterpreter/extensions/stdapi/railgun/def/'.
|
# definition class 'rex/post/meterpreter/extensions/stdapi/railgun/def/'.
|
||||||
# Naming is important and should follow convention. For example, if your
|
# Naming is important and should follow convention. For example, if your
|
||||||
# dll's name was "my_dll"
|
# dll's name was "my_dll"
|
||||||
# file name: def_my_dll.rb
|
# file name: def_my_dll.rb
|
||||||
# class name: Def_my_dll
|
# class name: Def_my_dll
|
||||||
# entry below: 'my_dll'
|
# entry below: 'my_dll'
|
||||||
#
|
#
|
||||||
BUILTIN_DLLS = [
|
BUILTIN_DLLS = [
|
||||||
'kernel32',
|
'kernel32',
|
||||||
'ntdll',
|
'ntdll',
|
||||||
'user32',
|
'user32',
|
||||||
'ws2_32',
|
'ws2_32',
|
||||||
'iphlpapi',
|
'iphlpapi',
|
||||||
'advapi32',
|
'advapi32',
|
||||||
'shell32',
|
'shell32',
|
||||||
'netapi32',
|
'netapi32',
|
||||||
'crypt32',
|
'crypt32',
|
||||||
'wlanapi',
|
'wlanapi',
|
||||||
].freeze
|
'wldap32'
|
||||||
|
].freeze
|
||||||
##
|
|
||||||
# Returns a Hash containing DLLs added to this instance with #add_dll
|
##
|
||||||
# as well as references to any frozen cached dlls added directly in #get_dll
|
# Returns a Hash containing DLLs added to this instance with #add_dll
|
||||||
# and copies of any frozen dlls (added directly with #add_function)
|
# as well as references to any frozen cached dlls added directly in #get_dll
|
||||||
# that the user attempted to modify with #add_function.
|
# and copies of any frozen dlls (added directly with #add_function)
|
||||||
#
|
# that the user attempted to modify with #add_function.
|
||||||
# Keys are friendly DLL names and values are the corresponding DLL instance
|
#
|
||||||
attr_accessor :dlls
|
# Keys are friendly DLL names and values are the corresponding DLL instance
|
||||||
|
attr_accessor :dlls
|
||||||
##
|
|
||||||
# Contains a reference to the client that corresponds to this instance of railgun
|
##
|
||||||
attr_accessor :client
|
# Contains a reference to the client that corresponds to this instance of railgun
|
||||||
|
attr_accessor :client
|
||||||
##
|
|
||||||
# These DLLs are loaded lazily and then shared amongst all railgun instances.
|
##
|
||||||
# For safety reasons this variable should only be read/written within #get_dll.
|
# These DLLs are loaded lazily and then shared amongst all railgun instances.
|
||||||
@@cached_dlls = {}
|
# For safety reasons this variable should only be read/written within #get_dll.
|
||||||
|
@@cached_dlls = {}
|
||||||
# if you are going to touch @@cached_dlls, wear protection
|
|
||||||
@@cache_semaphore = Mutex.new
|
# if you are going to touch @@cached_dlls, wear protection
|
||||||
|
@@cache_semaphore = Mutex.new
|
||||||
def initialize(client)
|
|
||||||
self.client = client
|
def initialize(client)
|
||||||
self.dlls = {}
|
self.client = client
|
||||||
end
|
self.dlls = {}
|
||||||
|
end
|
||||||
def self.builtin_dlls
|
|
||||||
BUILTIN_DLLS
|
def self.builtin_dlls
|
||||||
end
|
BUILTIN_DLLS
|
||||||
|
end
|
||||||
#
|
|
||||||
# Return this Railgun's Util instance.
|
#
|
||||||
#
|
# Return this Railgun's Util instance.
|
||||||
def util
|
#
|
||||||
if @util.nil?
|
def util
|
||||||
@util = Util.new(self, client.platform)
|
if @util.nil?
|
||||||
end
|
@util = Util.new(self, client.platform)
|
||||||
|
end
|
||||||
return @util
|
|
||||||
end
|
return @util
|
||||||
|
end
|
||||||
#
|
|
||||||
# Return this Railgun's WinConstManager instance, initially populated with
|
#
|
||||||
# constants defined in ApiConstants.
|
# Return this Railgun's WinConstManager instance, initially populated with
|
||||||
#
|
# constants defined in ApiConstants.
|
||||||
def constant_manager
|
#
|
||||||
# Loads lazily
|
def constant_manager
|
||||||
return ApiConstants.manager
|
# Loads lazily
|
||||||
end
|
return ApiConstants.manager
|
||||||
|
end
|
||||||
#
|
|
||||||
# Read data from a memory address on the host (useful for working with
|
#
|
||||||
# LPVOID parameters)
|
# Read data from a memory address on the host (useful for working with
|
||||||
#
|
# LPVOID parameters)
|
||||||
def memread(address, length)
|
#
|
||||||
|
def memread(address, length)
|
||||||
raise "Invalid parameters." if(not address or not length)
|
|
||||||
|
raise "Invalid parameters." if(not address or not length)
|
||||||
request = Packet.create_request('stdapi_railgun_memread')
|
|
||||||
|
request = Packet.create_request('stdapi_railgun_memread')
|
||||||
request.add_tlv(TLV_TYPE_RAILGUN_MEM_ADDRESS, address)
|
|
||||||
request.add_tlv(TLV_TYPE_RAILGUN_MEM_LENGTH, length)
|
request.add_tlv(TLV_TYPE_RAILGUN_MEM_ADDRESS, address)
|
||||||
|
request.add_tlv(TLV_TYPE_RAILGUN_MEM_LENGTH, length)
|
||||||
response = client.send_request(request)
|
|
||||||
if(response.result == 0)
|
response = client.send_request(request)
|
||||||
return response.get_tlv_value(TLV_TYPE_RAILGUN_MEM_DATA)
|
if(response.result == 0)
|
||||||
end
|
return response.get_tlv_value(TLV_TYPE_RAILGUN_MEM_DATA)
|
||||||
|
end
|
||||||
return nil
|
|
||||||
end
|
return nil
|
||||||
|
end
|
||||||
#
|
|
||||||
# Write data to a memory address on the host (useful for working with
|
#
|
||||||
# LPVOID parameters)
|
# Write data to a memory address on the host (useful for working with
|
||||||
#
|
# LPVOID parameters)
|
||||||
def memwrite(address, data, length)
|
#
|
||||||
|
def memwrite(address, data, length)
|
||||||
raise "Invalid parameters." if(not address or not data or not length)
|
|
||||||
|
raise "Invalid parameters." if(not address or not data or not length)
|
||||||
request = Packet.create_request('stdapi_railgun_memwrite')
|
|
||||||
|
request = Packet.create_request('stdapi_railgun_memwrite')
|
||||||
request.add_tlv(TLV_TYPE_RAILGUN_MEM_ADDRESS, address)
|
|
||||||
request.add_tlv(TLV_TYPE_RAILGUN_MEM_DATA, data)
|
request.add_tlv(TLV_TYPE_RAILGUN_MEM_ADDRESS, address)
|
||||||
request.add_tlv(TLV_TYPE_RAILGUN_MEM_LENGTH, length)
|
request.add_tlv(TLV_TYPE_RAILGUN_MEM_DATA, data)
|
||||||
|
request.add_tlv(TLV_TYPE_RAILGUN_MEM_LENGTH, length)
|
||||||
response = client.send_request(request)
|
|
||||||
if(response.result == 0)
|
response = client.send_request(request)
|
||||||
return true
|
if(response.result == 0)
|
||||||
end
|
return true
|
||||||
|
end
|
||||||
return false
|
|
||||||
end
|
return false
|
||||||
|
end
|
||||||
#
|
|
||||||
# Adds a function to an existing DLL definition.
|
#
|
||||||
#
|
# Adds a function to an existing DLL definition.
|
||||||
# If the DLL definition is frozen (ideally this should be the case for all
|
#
|
||||||
# cached dlls) an unfrozen copy is created and used henceforth for this
|
# If the DLL definition is frozen (ideally this should be the case for all
|
||||||
# instance.
|
# cached dlls) an unfrozen copy is created and used henceforth for this
|
||||||
#
|
# instance.
|
||||||
def add_function(dll_name, function_name, return_type, params, windows_name=nil)
|
#
|
||||||
|
def add_function(dll_name, function_name, return_type, params, windows_name=nil)
|
||||||
unless known_dll_names.include?(dll_name)
|
|
||||||
raise "DLL #{dll_name} not found. Known DLLs: #{PP.pp(known_dll_names, "")}"
|
unless known_dll_names.include?(dll_name)
|
||||||
end
|
raise "DLL #{dll_name} not found. Known DLLs: #{PP.pp(known_dll_names, "")}"
|
||||||
|
end
|
||||||
dll = get_dll(dll_name)
|
|
||||||
|
dll = get_dll(dll_name)
|
||||||
# For backwards compatibility, we ensure the dll is thawed
|
|
||||||
if dll.frozen?
|
# For backwards compatibility, we ensure the dll is thawed
|
||||||
# Duplicate not only the dll, but its functions as well. Frozen status will be lost
|
if dll.frozen?
|
||||||
dll = Marshal.load(Marshal.dump(dll))
|
# Duplicate not only the dll, but its functions as well. Frozen status will be lost
|
||||||
|
dll = Marshal.load(Marshal.dump(dll))
|
||||||
# Update local dlls with the modifiable duplicate
|
|
||||||
dlls[dll_name] = dll
|
# Update local dlls with the modifiable duplicate
|
||||||
end
|
dlls[dll_name] = dll
|
||||||
|
end
|
||||||
dll.add_function(function_name, return_type, params, windows_name)
|
|
||||||
end
|
dll.add_function(function_name, return_type, params, windows_name)
|
||||||
|
end
|
||||||
#
|
|
||||||
# Adds a DLL to this Railgun.
|
#
|
||||||
#
|
# Adds a DLL to this Railgun.
|
||||||
# The +windows_name+ is the name used on the remote system and should be
|
#
|
||||||
# set appropriately if you want to include a path or the DLL name contains
|
# The +windows_name+ is the name used on the remote system and should be
|
||||||
# non-ruby-approved characters.
|
# set appropriately if you want to include a path or the DLL name contains
|
||||||
#
|
# non-ruby-approved characters.
|
||||||
# Raises an exception if a dll with the given name has already been
|
#
|
||||||
# defined.
|
# Raises an exception if a dll with the given name has already been
|
||||||
#
|
# defined.
|
||||||
def add_dll(dll_name, windows_name=dll_name)
|
#
|
||||||
|
def add_dll(dll_name, windows_name=dll_name)
|
||||||
if dlls.has_key? dll_name
|
|
||||||
raise "A DLL of name #{dll_name} has already been loaded."
|
if dlls.has_key? dll_name
|
||||||
end
|
raise "A DLL of name #{dll_name} has already been loaded."
|
||||||
|
end
|
||||||
dlls[dll_name] = DLL.new(windows_name, constant_manager)
|
|
||||||
end
|
dlls[dll_name] = DLL.new(windows_name, constant_manager)
|
||||||
|
end
|
||||||
|
|
||||||
def known_dll_names
|
|
||||||
return BUILTIN_DLLS | dlls.keys
|
def known_dll_names
|
||||||
end
|
return BUILTIN_DLLS | dlls.keys
|
||||||
|
end
|
||||||
#
|
|
||||||
# Attempts to provide a DLL instance of the given name. Handles lazy
|
#
|
||||||
# loading and caching. Note that if a DLL of the given name does not
|
# Attempts to provide a DLL instance of the given name. Handles lazy
|
||||||
# exist, returns nil
|
# loading and caching. Note that if a DLL of the given name does not
|
||||||
#
|
# exist, returns nil
|
||||||
def get_dll(dll_name)
|
#
|
||||||
|
def get_dll(dll_name)
|
||||||
# If the DLL is not local, we now either load it from cache or load it lazily.
|
|
||||||
# In either case, a reference to the dll is stored in the collection "dlls"
|
# If the DLL is not local, we now either load it from cache or load it lazily.
|
||||||
# If the DLL can not be found/created, no actions are taken
|
# In either case, a reference to the dll is stored in the collection "dlls"
|
||||||
unless dlls.has_key? dll_name
|
# If the DLL can not be found/created, no actions are taken
|
||||||
# We read and write to @@cached_dlls and rely on state consistency
|
unless dlls.has_key? dll_name
|
||||||
@@cache_semaphore.synchronize do
|
# We read and write to @@cached_dlls and rely on state consistency
|
||||||
if @@cached_dlls.has_key? dll_name
|
@@cache_semaphore.synchronize do
|
||||||
dlls[dll_name] = @@cached_dlls[dll_name]
|
if @@cached_dlls.has_key? dll_name
|
||||||
elsif BUILTIN_DLLS.include? dll_name
|
dlls[dll_name] = @@cached_dlls[dll_name]
|
||||||
# I highly doubt this case will ever occur, but I am paranoid
|
elsif BUILTIN_DLLS.include? dll_name
|
||||||
if dll_name !~ /^\w+$/
|
# I highly doubt this case will ever occur, but I am paranoid
|
||||||
raise "DLL name #{dll_name} is bad. Correct Railgun::BUILTIN_DLLS"
|
if dll_name !~ /^\w+$/
|
||||||
end
|
raise "DLL name #{dll_name} is bad. Correct Railgun::BUILTIN_DLLS"
|
||||||
|
end
|
||||||
require 'rex/post/meterpreter/extensions/stdapi/railgun/def/def_' << dll_name
|
|
||||||
dll = Def.const_get('Def_' << dll_name).create_dll.freeze
|
require 'rex/post/meterpreter/extensions/stdapi/railgun/def/def_' << dll_name
|
||||||
|
dll = Def.const_get('Def_' << dll_name).create_dll.freeze
|
||||||
@@cached_dlls[dll_name] = dll
|
|
||||||
dlls[dll_name] = dll
|
@@cached_dlls[dll_name] = dll
|
||||||
end
|
dlls[dll_name] = dll
|
||||||
end
|
end
|
||||||
|
end
|
||||||
end
|
|
||||||
|
end
|
||||||
return dlls[dll_name]
|
|
||||||
end
|
return dlls[dll_name]
|
||||||
|
end
|
||||||
#
|
|
||||||
# Fake having members like user32 and kernel32.
|
#
|
||||||
# reason is that
|
# Fake having members like user32 and kernel32.
|
||||||
# ...user32.MessageBoxW()
|
# reason is that
|
||||||
# is prettier than
|
# ...user32.MessageBoxW()
|
||||||
# ...dlls["user32"].functions["MessageBoxW"]()
|
# is prettier than
|
||||||
#
|
# ...dlls["user32"].functions["MessageBoxW"]()
|
||||||
def method_missing(dll_symbol, *args)
|
#
|
||||||
dll_name = dll_symbol.to_s
|
def method_missing(dll_symbol, *args)
|
||||||
|
dll_name = dll_symbol.to_s
|
||||||
unless known_dll_names.include? dll_name
|
|
||||||
raise "DLL #{dll_name} not found. Known DLLs: #{PP.pp(known_dll_names, '')}"
|
unless known_dll_names.include? dll_name
|
||||||
end
|
raise "DLL #{dll_name} not found. Known DLLs: #{PP.pp(known_dll_names, '')}"
|
||||||
|
end
|
||||||
dll = get_dll(dll_name)
|
|
||||||
|
dll = get_dll(dll_name)
|
||||||
return DLLWrapper.new(dll, client)
|
|
||||||
end
|
return DLLWrapper.new(dll, client)
|
||||||
|
end
|
||||||
#
|
|
||||||
# Return a Windows constant matching +str+.
|
#
|
||||||
#
|
# Return a Windows constant matching +str+.
|
||||||
def const(str)
|
#
|
||||||
return constant_manager.parse(str)
|
def const(str)
|
||||||
end
|
return constant_manager.parse(str)
|
||||||
|
end
|
||||||
#
|
|
||||||
# The multi-call shorthand (["kernel32", "ExitProcess", [0]])
|
#
|
||||||
#
|
# The multi-call shorthand (["kernel32", "ExitProcess", [0]])
|
||||||
def multi(functions)
|
#
|
||||||
if @multicaller.nil?
|
def multi(functions)
|
||||||
@multicaller = MultiCaller.new(client, self)
|
if @multicaller.nil?
|
||||||
end
|
@multicaller = MultiCaller.new(client, self)
|
||||||
|
end
|
||||||
return @multicaller.call(functions)
|
|
||||||
end
|
return @multicaller.call(functions)
|
||||||
end
|
end
|
||||||
|
end
|
||||||
end; end; end; end; end; end
|
|
||||||
|
end; end; end; end; end; end
|
||||||
|
|
Loading…
Reference in New Issue