Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Minor cleanup on the psexec_classic module #2

Open
wants to merge 19 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions lib/msf/core/exploit/dcerpc.rb
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
require 'msf/core/exploit/dcerpc_epm'
require 'msf/core/exploit/dcerpc_mgmt'
require 'msf/core/exploit/dcerpc_lsa'
require 'msf/core/exploit/dcerpc_services'

module Msf

Expand Down Expand Up @@ -32,6 +33,7 @@ module Exploit::Remote::DCERPC
include Exploit::Remote::DCERPC_EPM
include Exploit::Remote::DCERPC_MGMT
include Exploit::Remote::DCERPC_LSA
include Exploit::Remote::DCERPC_SERVICES

def initialize(info = {})
super
Expand Down
245 changes: 245 additions & 0 deletions lib/msf/core/exploit/dcerpc_services.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,245 @@
# -*- coding: binary -*-
module Msf

###
# This module implements MSRPC functions that control creating, deleting,
# starting, stopping, and querying system services.
###
module Exploit::Remote::DCERPC_SERVICES

NDR = Rex::Encoder::NDR


# Calls OpenSCManagerW() to obtain a handle to the service control manager.
#
# @param dcerpc [Rex::Proto::DCERPC::Client] the DCERPC client to use.
# @param rhost [String] the target host.
# @param access [Fixnum] the access flags requested.
#
# @return [String] the handle to the service control manager.
def dce_openscmanagerw(dcerpc, rhost, access = 0xF003F)
scm_handle = nil
scm_status = nil
stubdata =
NDR.uwstring("\\\\#{rhost}") +
NDR.long(0) +
NDR.long(access)
response = dcerpc.call(0x0f, stubdata)
if dcerpc.last_response and dcerpc.last_response.stub_data
scm_handle = dcerpc.last_response.stub_data[0,20]
scm_status = dcerpc.last_response.stub_data[20,4]
end

if scm_status.to_i != 0
scm_handle = nil
end
return scm_handle
end


# Calls CreateServiceW() to create a system service. Returns a handle to
# the service on success, or nil.
#
# @param dcerpc [Rex::Proto::DCERPC::Client] the DCERPC client to use.
# @param scm_handle [String] the SCM handle (from dce_openscmanagerw()).
# @param service_name [String] the service name.
# @param display_name [String] the display name.
# @param binary_path [String] the path of the binary to run.
# @param opts [Hash] a hash containing the following keys and values:
# access [Fixnum] the access level (default is maximum).
# type [Fixnum] the type of service (default is interactive,
# own process).
# start [Fixnum] the start options (default is on demand).
# errors [Fixnum] the error options (default is ignore).
# load_order_group [Fixnum] the load order group.
# dependencies [Fixnum] the dependencies of the service.
# service_start [Fixnum]
# password1 [Fixnum]
# password2 [Fixnum]
# password3 [Fixnum]
# password4 [Fixnum]
#
# @return [String] a handle to the created service.
def dce_createservicew(dcerpc, scm_handle, service_name, display_name, binary_path, opts)
default_opts = {
:access => 0x0F01FF, # Maximum access.
:type => 0x00000110, # Interactive, own process.
:start => 0x00000003, # Start on demand.
:errors => 0x00000000,# Ignore errors.
:load_order_group => 0,
:dependencies => 0,
:service_start => 0,
:password1 => 0,
:password2 => 0,
:password3 => 0,
:password4 => 0
}.merge(opts)

svc_handle = nil
svc_status = nil
stubdata = scm_handle +
NDR.wstring(service_name) +
NDR.uwstring(display_name) +
NDR.long(default_opts[:access]) +
NDR.long(default_opts[:type]) +
NDR.long(default_opts[:start]) +
NDR.long(default_opts[:errors]) +
NDR.wstring(binary_path) +
NDR.long(default_opts[:load_order_group]) +
NDR.long(default_opts[:dependencies]) +
NDR.long(default_opts[:service_start]) +
NDR.long(default_opts[:password1]) +
NDR.long(default_opts[:password2]) +
NDR.long(default_opts[:password3]) +
NDR.long(default_opts[:password4])
response = dcerpc.call(0x0c, stubdata)
if dcerpc.last_response and dcerpc.last_response.stub_data
svc_handle = dcerpc.last_response.stub_data[4,20]
svc_status = dcerpc.last_response.stub_data[20,4]
end

if svc_status.to_i != 0
svc_handle = nil
end
return svc_handle
end

# Calls CloseHandle() to close a handle. Returns true on success, or false.
#
# @param dcerpc [Rex::Proto::DCERPC::Client] the DCERPC client to use.
# @param handle [String] the handle to close.
#
# @return [Boolean] true if the handle was successfully closed, or false if
# not.
def dce_closehandle(dcerpc, handle)
ret = false
response = dcerpc.call(0x0, handle)
if dcerpc.last_response and dcerpc.last_response.stub_data
if dcerpc.last_response.stub_data[20,4].to_i == 0
ret = true
end
end
return ret
end

# Calls OpenServiceW to obtain a handle to an existing service.
#
# @param dcerpc [Rex::Proto::DCERPC::Client] the DCERPC client to use.
# @param scm_handle [String] the SCM handle (from dce_openscmanagerw()).
# @param service_name [String] the name of the service to open.
# @param access [Fixnum] the level of access requested (default is maximum).
#
# @return [String, nil] the handle of the service opened, or nil on failure.
def dce_openservicew(dcerpc, scm_handle, service_name, access = 0xF01FF)
svc_handle = nil
svc_status = nil
stubdata = scm_handle + NDR.wstring(service_name) + NDR.long(access)
response = dcerpc.call(0x10, stubdata)
if dcerpc.last_response and dcerpc.last_response.stub_data
svc_handle = dcerpc.last_response.stub_data[0,20]
svc_status = dcerpc.last_response.stub_data[20,4]
end

if svc_status.to_i != 0
svc_handle = nil
end
return svc_handle
end

# Calls StartService() on a handle to an existing service in order to start
# it. Returns true on success, or false.
#
# @param dcerpc [Rex::Proto::DCERPC::Client] the DCERPC client to use.
# @param svc_handle [String] the handle of the service to start (from
# dce_openservicew()).
# @param magic1 [Fixnum] an unknown value.
# @param magic2 [Fixnum] another unknown value.
#
# @return [Boolean] true if the service was successfully started, false if
# it was not.
def dce_startservice(dcerpc, svc_handle, magic1 = 0, magic2 = 0)
ret = false
stubdata = svc_handle + NDR.long(magic1) + NDR.long(magic2)
response = dcerpc.call(0x13, stubdata)
if dcerpc.last_response and dcerpc.last_response.stub_data
if dcerpc.last_response.stub_data[0,4].to_i == 0
ret = true
end
end
return ret
end

# Stops a running service.
#
# @param dcerpc [Rex::Proto::DCERPC::Client] the DCERPC client to use.
# @param svc_handle [String] the handle of the service to stop (from
# dce_openservicew()).
#
# @return [Boolean] true if the service was successfully stopped, false if
# it was not.
def dce_stopservice(dcerpc, svc_handle)
return dce_controlservice(dcerpc, svc_handle, 1)
end

# Controls an existing service.
#
# @param dcerpc [Rex::Proto::DCERPC::Client] the DCERPC client to use.
# @param svc_handle [String] the handle of the service to control
# (from dce_openservicew()).
# @param operation [Fixnum] the operation number to perform (1 = stop
# service; others are unknown).
#
# @return [Boolean] true if the operation was successful, false if it was
# not.
def dce_controlservice(dcerpc, svc_handle, operation)
ret = false
response = dcerpc.call(0x01, svc_handle + NDR.long(operation))
if dcerpc.last_response and dcerpc.last_response.stub_data
if dcerpc.last_response.stub_data[28,4].to_i == 0
ret = true
end
end
return ret
end

# Calls DeleteService() to delete a service.
#
# @param dcerpc [Rex::Proto::DCERPC::Client] the DCERPC client to use.
# @param svc_handle [String] the handle of the service to delete (from
# dce_openservicew()).
#
# @return [Boolean] true if the service was successfully deleted, false if
# it was not.
def dce_deleteservice(dcerpc, svc_handle)
ret = false
response = dcerpc.call(0x02, svc_handle)
if dcerpc.last_response and dcerpc.last_response.stub_data
if dcerpc.last_response.stub_data[0,4].to_i == 0
ret = true
end
end
return ret
end

# Calls QueryServiceStatus() to query the status of a service.
#
# @param dcerpc [Rex::Proto::DCERPC::Client] the DCERPC client to use.
# @param svc_handle [String] the handle of the service to query (from
# dce_openservicew()).
#
# @return [Fixnum] Returns 0 if the query failed (i.e.: a state was returned
# that isn't implemented), 1 if the service is running, and
# 2 if the service is stopped.
def dce_queryservice(dcerpc, svc_handle)
ret = 0
response = dcerpc.call(0x06, svc_handle)
if response[0,9] == "\x10\x00\x00\x00\x04\x00\x00\x00\x01"
ret = 1
elsif response[0,9] == "\x10\x00\x00\x00\x01\x00\x00\x00\x00"
ret = 2
end
return ret
end

end
end
72 changes: 72 additions & 0 deletions lib/msf/core/exploit/smb/psexec_svc.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
# -*- coding: binary -*-
require 'digest'

module Msf

####
# This allows one to extract PSEXESVC.EXE from Microsoft Sysinternal's
# PsExec.exe.
####
module Exploit::Remote::SMB::PsexecSvc

# Returns the bytes for PSEXESVC.EXE and the version of PsExec in use on
# success, or nil on error.
#
# @param psexec_path [String] the local filesystem path to PsExec.exe
# @param verbose [Boolean] true if verbosity is desired, false if otherwise.
#
# @return [String],[Float] the bytes corresponding to PSEXESVC.EXE, and
# the version of PsExec in use, respectively.
def extract_psexesvc(psexec_path, verbose = false)
read_offset = 0
bytes_to_read = 0
psexec_version = nil
if verbose
print_status("Calculating SHA-256 hash of #{psexec_path}...")
end
hash = Digest::SHA256.file(psexec_path).hexdigest
# The read offset and size of the PSEXESVC.EXE binary for v1.98 is
# 193,288 and 181,064, respectively.
if hash == 'f8dbabdfa03068130c277ce49c60e35c029ff29d9e3c74c362521f3fb02670d5'
psexec_version = 1.98
read_offset = 193288
bytes_to_read = 181064
# For v2.0...
elsif hash == '3c26ef3208a8bf6c2a23d46ef15c238197f528c04877db0bac2a090d15ec53b2'
psexec_version = 2.0
read_offset = 194312
bytes_to_read = 185160
elsif hash == '2a9c136176bbd1204b534933ee0880eaf747ed659b36d7eb13bd6aa77d35dd02'
psexec_version = 2.1
read_offset = 198408
bytes_to_read = 189792
elsif hash == '3b08535b4add194f5661e1131c8e81af373ca322cf669674cf1272095e5cab95'
psexec_version = 2.11
read_offset = 198408
bytes_to_read = 189792
else
if verbose
print_error("Hash is not correct! One of the following is expected:\n" +
" * f8dbabdfa03068130c277ce49c60e35c029ff29d9e3c74c362521f3fb02670d5\n" +
" * 3c26ef3208a8bf6c2a23d46ef15c238197f528c04877db0bac2a090d15ec53b2\n" +
" * 2a9c136176bbd1204b534933ee0880eaf747ed659b36d7eb13bd6aa77d35dd02\n" +
" * 3b08535b4add194f5661e1131c8e81af373ca322cf669674cf1272095e5cab95\n" +
"Actual: #{hash}\nEnsure that you have PsExec v1.98, v2.0, v2.1, or v2.11.")
end
return nil
end

if verbose
print_status("File hash verified. PsExec v#{psexec_version} detected. Extracting PSEXESVC.EXE code from #{psexec_path}...")
end
# Extract the PSEXESVC.EXE code from PsExec.exe.
hPsExec = File.open(psexec_path, 'rb')
hPsExec.seek(read_offset)
psexesvc = hPsExec.read(bytes_to_read)
hPsExec.close

return psexesvc, psexec_version
end

end
end
37 changes: 37 additions & 0 deletions lib/rex/proto/smb/client.rb
Original file line number Diff line number Diff line change
Expand Up @@ -148,6 +148,7 @@ def smb_defaults(packet)
packet.v['TreeID'] = self.last_tree_id.to_i
packet.v['UserID'] = self.auth_user_id.to_i
packet.v['ProcessID'] = self.process_id.to_i
self.multiplex_id = (self.multiplex_id + 16) % 65536
end

# Receive a full SMB reply and cache the parsed packet
Expand Down Expand Up @@ -1336,6 +1337,42 @@ def write(file_id = self.last_file_id, offset = 0, data = '', do_recv = true)
end


# Used by auxiliary/admin/smb/psexec_classic.rb to send ANDX writes with
# greater precision.
def write_raw(args)

pkt = CONST::SMB_WRITE_PKT.make_struct
self.smb_defaults(pkt['Payload']['SMB'])

pkt['Payload']['SMB'].v['Command'] = CONST::SMB_COM_WRITE_ANDX
pkt['Payload']['SMB'].v['Flags1'] = args[:flags1]
pkt['Payload']['SMB'].v['Flags2'] = args[:flags2]

pkt['Payload']['SMB'].v['WordCount'] = args[:wordcount]

pkt['Payload'].v['AndX'] = args[:andx_command]
pkt['Payload'].v['AndXOffset'] = args[:andx_offset]
pkt['Payload'].v['FileID'] = args[:file_id]
pkt['Payload'].v['Offset'] = args[:offset]
pkt['Payload'].v['Reserved2'] = -1
pkt['Payload'].v['WriteMode'] = args[:write_mode]
pkt['Payload'].v['Remaining'] = args[:remaining]
pkt['Payload'].v['DataLenHigh'] = args[:data_len_high]
pkt['Payload'].v['DataLenLow'] = args[:data_len_low]
pkt['Payload'].v['DataOffset'] = args[:data_offset]
pkt['Payload'].v['HighOffset'] = args[:high_offset]
pkt['Payload'].v['ByteCount'] = args[:byte_count]

pkt['Payload'].v['Payload'] = args[:data]

ret = self.smb_send(pkt.to_s)
return ret if not args[:do_recv]

ack = self.smb_recv_parse(CONST::SMB_COM_WRITE_ANDX)
return ack
end


# Reads data from an open file handle
def read(file_id = self.last_file_id, offset = 0, data_length = 64000, do_recv = true)

Expand Down
Loading