Skip to content

Commit

Permalink
Land #19461, Modernize NetWkstaUserEnum
Browse files Browse the repository at this point in the history
Modernize NetWkstaUserEnum in smb scanner
  • Loading branch information
smcintyre-r7 committed Sep 17, 2024
2 parents 1a14916 + 7abfb6c commit 409b1ae
Show file tree
Hide file tree
Showing 4 changed files with 161 additions and 158 deletions.
2 changes: 1 addition & 1 deletion Gemfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -495,7 +495,7 @@ GEM
ruby-progressbar (1.13.0)
ruby-rc4 (0.1.5)
ruby2_keywords (0.0.5)
ruby_smb (3.3.9)
ruby_smb (3.3.10)
bindata (= 2.4.15)
openssl-ccm
openssl-cmac
Expand Down
3 changes: 2 additions & 1 deletion db/modules_metadata_base.json
Original file line number Diff line number Diff line change
Expand Up @@ -54959,7 +54959,8 @@
"type": "auxiliary",
"author": [
"natron <[email protected]>",
"Joshua D. Abraham <[email protected]>"
"Joshua D. Abraham <[email protected]>",
"NtAlexio2 <[email protected]>"
],
"description": "Determine what domain users are logged into a remote system via a DCERPC to NetWkstaUserEnum.",
"references": [
Expand Down
76 changes: 76 additions & 0 deletions lib/msf/core/exploit/remote/ms_wkst.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
###
#
# The Workstation Service Remote Protocol is used to perform tasks on a computer remotely on a
# network, including:
#
# - Configuring properties and behavior of a Server Message Block network
# redirector (SMB network redirector).
# - Managing domain membership and computer names.
# - Gathering information, such as the number of enabled transport protocols and the number of
# currently logged-on users.
#
# -*- coding: binary -*-

module Msf

module Exploit::Remote::MsWkst

include Msf::Exploit::Remote::SMB::Client::Ipc

class MsWkstError < StandardError; end
class MsWkstConnectionError < MsWkstError; end
class MsWkstAuthenticationError < MsWkstError; end
class MsWkstUnexpectedReplyError < MsWkstError; end

WKS_UUID = '6bffd098-a112-3610-9833-46c3f87e345a'.freeze
WKS_VERS = '1.0'.freeze
WKSSVC_ENDPOINT = RubySMB::Dcerpc::Wkssvc.freeze

# The currently connected WKSSVC pipe
attr_reader :wkssvc_pipe

def user_enum(level)
self.wkssvc_pipe.netr_wksta_user_enum(
level: level
)
end

def get_info()
self.wkssvc_pipe.netr_wksta_get_info
end

def disconnect_wkssvc
begin
self.wkssvc_pipe.close if self.wkssvc_pipe&.is_connected?
rescue RubySMB::Error::UnexpectedStatusCode, RubySMB::Error::CommunicationError => e
wlog e
end
end

module_function

def connect_wkssvc(tree)
begin
vprint_status('Connecting to Workstation Service Remote Protocol')
self.wkssvc_pipe = tree.open_file(filename: 'wkssvc', write: true, read: true)

raise MsWkstConnectionError.new('Could not open wkssvc pipe on remote SMB server.') unless wkssvc_pipe

vprint_status('Binding to \\wkssvc...')
self.wkssvc_pipe.bind(endpoint: WKSSVC_ENDPOINT)
vprint_good('Bound to \\wkssvc')

self.wkssvc_pipe
rescue RubySMB::Dcerpc::Error::FaultError => e
elog(e.message, error: e)
raise MsWkstUnexpectedReplyError, "Connection failed (DCERPC fault: #{e.status_name})"
end
end

protected

attr_writer :wkssvc_pipe

end

end
238 changes: 82 additions & 156 deletions modules/auxiliary/scanner/smb/smb_enumusers_domain.rb
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
class MetasploitModule < Msf::Auxiliary

# Exploit mixins should be called first
include Msf::Exploit::Remote::MsWkst
include Msf::Exploit::Remote::SMB::Client
include Msf::Exploit::Remote::SMB::Client::Authenticated
include Msf::Exploit::Remote::DCERPC
Expand All @@ -24,191 +25,116 @@ def initialize
[
'natron', # original module
'Joshua D. Abraham <jabra[at]praetorian.com>', # database storage
'NtAlexio2 <[email protected]>', # refactor
],
'References' =>
[
[ 'URL', 'https://docs.microsoft.com/en-us/windows/win32/api/lmwksta/nf-lmwksta-netwkstauserenum' ]
],
'License' => MSF_LICENSE,
)

deregister_options('RPORT')

end

def parse_value(resp, idx)
#val_length = resp[idx,4].unpack("V")[0]
idx += 4
#val_offset = resp[idx,4].unpack("V")[0]
idx += 4
val_actual = resp[idx,4].unpack("V")[0]
idx += 4
value = resp[idx,val_actual*2]
idx += val_actual * 2

idx += val_actual % 2 * 2 # alignment

return value,idx
def rport
@rport
end

def parse_net_wksta_enum_users_info(resp)
accounts = [ Hash.new() ]

idx = 20
count = resp[idx,4].unpack("V")[0] # wkssvc_NetWkstaEnumUsersInfo -> Info -> PtrCt0 -> User() -> Ptr -> Max Count
idx += 4

1.upto(count) do
# wkssvc_NetWkstaEnumUsersInfo -> Info -> PtrCt0 -> User() -> Ptr -> Ref ID
idx += 4 # ref id name
idx += 4 # ref id logon domain
idx += 4 # ref id other domains
idx += 4 # ref id logon server
end

1.upto(count) do
# wkssvc_NetWkstaEnumUsersInfo -> Info -> PtrCt0 -> User() -> Ptr -> ID1 max count

account_name,idx = parse_value(resp, idx)
logon_domain,idx = parse_value(resp, idx)
other_domains,idx = parse_value(resp, idx)
logon_server,idx = parse_value(resp, idx)

accounts << {
:account_name => account_name,
:logon_domain => logon_domain,
:other_domains => other_domains,
:logon_server => logon_server
}
end

accounts
def smb_direct
@smb_direct
end

def rport
@rport || datastore['RPORT']
def connect(*args, **kwargs)
super(*args, **kwargs, direct: @smb_direct)
end

def smb_direct
@smbdirect || datastore['SMBDirect']
def run_session
smb_services = [{ port: self.simple.peerport, direct: self.simple.direct }]
smb_services.map { |smb_service| run_service(smb_service[:port], smb_service[:direct]) }
end

def store_username(username, res, ip, rport)
service_data = {
address: ip,
port: rport,
service_name: 'smb',
protocol: 'tcp',
workspace_id: myworkspace_id,
proof: res
}

credential_data = {
origin_type: :service,
module_fullname: fullname,
username: username
}

credential_data.merge!(service_data)

credential_core = create_credential(credential_data)

login_data = {
core: credential_core,
status: Metasploit::Model::Login::Status::UNTRIED
}

login_data.merge!(service_data)
create_credential_login(login_data)
end
def run_rhost
if datastore['RPORT'].blank? || datastore['RPORT'] == 0
smb_services = [
{ port: 445, direct: true },
{ port: 139, direct: false }
]
else
smb_services = [
{ port: datastore['RPORT'], direct: datastore['SMBDirect'] }
]
end

def run_host(ip)
smb_services.map { |smb_service| run_service(smb_service[:port], smb_service[:direct]) }
end

ports = [139, 445]
def run_service(port, direct)
@rport = port
@smb_direct = direct

ipc_tree = connect_ipc
wkssvc_pipe = connect_wkssvc(ipc_tree)
endpoint = RubySMB::Dcerpc::Wkssvc.freeze

user_info = user_enum(endpoint::WKSTA_USER_INFO_1)
user_info.wkui1_buffer

rescue Msf::Exploit::Remote::SMB::Client::Ipc::SmbIpcAuthenticationError => e
print_warning(e.message)
nil
rescue RubySMB::Error::RubySMBError => e
print_error("Error: #{e.message}")
nil
rescue ::Timeout::Error
rescue ::Exception => e
print_error("Error: #{e.class} #{e}")
nil
ensure
disconnect_wkssvc
end

def run_host(_ip)
if session
print_status("Using existing session #{session.sid}")
client = session.client
self.simple = ::Rex::Proto::SMB::SimpleClient.new(client.dispatcher.tcp_socket, client: client)
ports = [simple.port]
self.simple.connect("\\\\#{simple.address}\\IPC$") # smb_login connects to this share for some reason and it doesn't work unless we do too
self.simple = session.simple_client
results = run_session
else
results = run_rhost
end

ports.each do |port|
unless results.to_s.empty?
accounts = [ Hash.new() ]
results.compact.each do |result_set|
result_set.each { |result| accounts << {
:account_name => result.wkui1_username.encode('UTF-8'),
:logon_domain => result.wkui1_logon_domain.encode('UTF-8'),
:other_domains => result.wkui1_oth_domains.encode('UTF-8'),
:logon_server => result.wkui1_logon_server.encode('UTF-8')} }
end
accounts.shift

@rport = port
begin
unless session
connect()
smb_login()
if datastore['VERBOSE']
accounts.each do |x|
print_status x[:logon_domain] + "\\" + x[:account_name] +
"\t(logon_server: #{x[:logon_server]}, other_domains: #{x[:other_domains]})"
end
else
print_status "#{accounts.collect{|x| x[:logon_domain] + "\\" + x[:account_name]}.join(", ")}"
end

found_accounts = []
accounts.each do |x|
comp_user = x[:logon_domain] + "\\" + x[:account_name]
found_accounts.push(comp_user.scan(/[[:print:]]/).join) unless found_accounts.include?(comp_user.scan(/[[:print:]]/).join)
end

uuid = [ '6bffd098-a112-3610-9833-46c3f87e345a', '1.0' ]

handle = dcerpc_handle_target(
uuid[0], uuid[1], 'ncacn_np', ["\\wkssvc"], simple.address
)
begin
dcerpc_bind(handle)
stub =
NDR.uwstring("\\\\" + simple.address) + # Server Name
NDR.long(1) + # Level
NDR.long(1) + # Ctr
NDR.long(rand(0xffffffff)) + # ref id
NDR.long(0) + # entries read
NDR.long(0) + # null ptr to user0

NDR.long(0xffffffff) + # Prefmaxlen
NDR.long(rand(0xffffffff)) + # ref id
NDR.long(0) # null ptr to resume handle

dcerpc.call(2,stub)

resp = dcerpc.last_response ? dcerpc.last_response.stub_data : nil

accounts = parse_net_wksta_enum_users_info(resp)
accounts.shift

if datastore['VERBOSE']
accounts.each do |x|
print_status x[:logon_domain] + "\\" + x[:account_name] +
"\t(logon_server: #{x[:logon_server]}, other_domains: #{x[:other_domains]})"
end
else
print_status "#{accounts.collect{|x| x[:logon_domain] + "\\" + x[:account_name]}.join(", ")}"
end

found_accounts = []
accounts.each do |x|
comp_user = x[:logon_domain] + "\\" + x[:account_name]
found_accounts.push(comp_user.scan(/[[:print:]]/).join) unless found_accounts.include?(comp_user.scan(/[[:print:]]/).join)
end

found_accounts.each do |comp_user|
if comp_user.to_s =~ /\$$/
next
end

print_good("Found user: #{comp_user}")
store_username(comp_user, resp, simple.address, rport)
end

rescue ::Rex::Proto::SMB::Exceptions::ErrorCode => e
print_error("UUID #{uuid[0]} #{uuid[1]} ERROR 0x%.8x" % e.error_code)
#puts e
#return
rescue ::Exception => e
print_error("UUID #{uuid[0]} #{uuid[1]} ERROR #{$!}")
#puts e
#return
found_accounts.each do |comp_user|
if comp_user.to_s =~ /\$$/
next
end

disconnect()
return
rescue ::Exception
print_line($!.to_s)
print_good("Found user: #{comp_user}")
end

end
end

end
end

0 comments on commit 409b1ae

Please sign in to comment.