66class MetasploitModule < Msf ::Auxiliary
77
88 # Exploit mixins should be called first
9+ include Msf ::Exploit ::Remote ::MsWkst
910 include Msf ::Exploit ::Remote ::SMB ::Client
1011 include Msf ::Exploit ::Remote ::SMB ::Client ::Authenticated
1112 include Msf ::Exploit ::Remote ::DCERPC
@@ -24,191 +25,116 @@ def initialize
2425 [
2526 'natron' , # original module
2627 'Joshua D. Abraham <jabra[at]praetorian.com>' , # database storage
28+ 'NtAlexio2 <[email protected] >' , # refactor 2729 ] ,
2830 'References' =>
2931 [
3032 [ 'URL' , 'https://docs.microsoft.com/en-us/windows/win32/api/lmwksta/nf-lmwksta-netwkstauserenum' ]
3133 ] ,
3234 'License' => MSF_LICENSE ,
3335 )
34-
35- deregister_options ( 'RPORT' )
36-
3736 end
3837
39- def parse_value ( resp , idx )
40- #val_length = resp[idx,4].unpack("V")[0]
41- idx += 4
42- #val_offset = resp[idx,4].unpack("V")[0]
43- idx += 4
44- val_actual = resp [ idx , 4 ] . unpack ( "V" ) [ 0 ]
45- idx += 4
46- value = resp [ idx , val_actual *2 ]
47- idx += val_actual * 2
48-
49- idx += val_actual % 2 * 2 # alignment
50-
51- return value , idx
38+ def rport
39+ @rport
5240 end
5341
54- def parse_net_wksta_enum_users_info ( resp )
55- accounts = [ Hash . new ( ) ]
56-
57- idx = 20
58- count = resp [ idx , 4 ] . unpack ( "V" ) [ 0 ] # wkssvc_NetWkstaEnumUsersInfo -> Info -> PtrCt0 -> User() -> Ptr -> Max Count
59- idx += 4
60-
61- 1 . upto ( count ) do
62- # wkssvc_NetWkstaEnumUsersInfo -> Info -> PtrCt0 -> User() -> Ptr -> Ref ID
63- idx += 4 # ref id name
64- idx += 4 # ref id logon domain
65- idx += 4 # ref id other domains
66- idx += 4 # ref id logon server
67- end
68-
69- 1 . upto ( count ) do
70- # wkssvc_NetWkstaEnumUsersInfo -> Info -> PtrCt0 -> User() -> Ptr -> ID1 max count
71-
72- account_name , idx = parse_value ( resp , idx )
73- logon_domain , idx = parse_value ( resp , idx )
74- other_domains , idx = parse_value ( resp , idx )
75- logon_server , idx = parse_value ( resp , idx )
76-
77- accounts << {
78- :account_name => account_name ,
79- :logon_domain => logon_domain ,
80- :other_domains => other_domains ,
81- :logon_server => logon_server
82- }
83- end
84-
85- accounts
42+ def smb_direct
43+ @smb_direct
8644 end
8745
88- def rport
89- @rport || datastore [ 'RPORT' ]
46+ def connect ( * args , ** kwargs )
47+ super ( * args , ** kwargs , direct : @smb_direct )
9048 end
9149
92- def smb_direct
93- @smbdirect || datastore [ 'SMBDirect' ]
50+ def run_session
51+ smb_services = [ { port : self . simple . peerport , direct : self . simple . direct } ]
52+ smb_services . map { |smb_service | run_service ( smb_service [ :port ] , smb_service [ :direct ] ) }
9453 end
9554
96- def store_username ( username , res , ip , rport )
97- service_data = {
98- address : ip ,
99- port : rport ,
100- service_name : 'smb' ,
101- protocol : 'tcp' ,
102- workspace_id : myworkspace_id ,
103- proof : res
104- }
105-
106- credential_data = {
107- origin_type : :service ,
108- module_fullname : fullname ,
109- username : username
110- }
111-
112- credential_data . merge! ( service_data )
113-
114- credential_core = create_credential ( credential_data )
115-
116- login_data = {
117- core : credential_core ,
118- status : Metasploit ::Model ::Login ::Status ::UNTRIED
119- }
120-
121- login_data . merge! ( service_data )
122- create_credential_login ( login_data )
123- end
55+ def run_rhost
56+ if datastore [ 'RPORT' ] . blank? || datastore [ 'RPORT' ] == 0
57+ smb_services = [
58+ { port : 445 , direct : true } ,
59+ { port : 139 , direct : false }
60+ ]
61+ else
62+ smb_services = [
63+ { port : datastore [ 'RPORT' ] , direct : datastore [ 'SMBDirect' ] }
64+ ]
65+ end
12466
125- def run_host ( ip )
67+ smb_services . map { |smb_service | run_service ( smb_service [ :port ] , smb_service [ :direct ] ) }
68+ end
12669
127- ports = [ 139 , 445 ]
70+ def run_service ( port , direct )
71+ @rport = port
72+ @smb_direct = direct
73+
74+ ipc_tree = connect_ipc
75+ wkssvc_pipe = connect_wkssvc ( ipc_tree )
76+ endpoint = RubySMB ::Dcerpc ::Wkssvc . freeze
77+
78+ user_info = user_enum ( endpoint ::WKSTA_USER_INFO_1 )
79+ user_info . wkui1_buffer
80+
81+ rescue Msf ::Exploit ::Remote ::SMB ::Client ::Ipc ::SmbIpcAuthenticationError => e
82+ print_warning ( e . message )
83+ nil
84+ rescue RubySMB ::Error ::RubySMBError => e
85+ print_error ( "Error: #{ e . message } " )
86+ nil
87+ rescue ::Timeout ::Error
88+ rescue ::Exception => e
89+ print_error ( "Error: #{ e . class } #{ e } " )
90+ nil
91+ ensure
92+ disconnect_wkssvc
93+ end
12894
95+ def run_host ( _ip )
12996 if session
130- print_status ( "Using existing session #{ session . sid } " )
131- client = session . client
132- self . simple = ::Rex ::Proto ::SMB ::SimpleClient . new ( client . dispatcher . tcp_socket , client : client )
133- ports = [ simple . port ]
134- self . simple . connect ( "\\ \\ #{ simple . address } \\ IPC$" ) # smb_login connects to this share for some reason and it doesn't work unless we do too
97+ self . simple = session . simple_client
98+ results = run_session
99+ else
100+ results = run_rhost
135101 end
136102
137- ports . each do |port |
103+ unless results . to_s . empty?
104+ accounts = [ Hash . new ( ) ]
105+ results . compact . each do |result_set |
106+ result_set . each { |result | accounts << {
107+ :account_name => result . wkui1_username . encode ( 'UTF-8' ) ,
108+ :logon_domain => result . wkui1_logon_domain . encode ( 'UTF-8' ) ,
109+ :other_domains => result . wkui1_oth_domains . encode ( 'UTF-8' ) ,
110+ :logon_server => result . wkui1_logon_server . encode ( 'UTF-8' ) } }
111+ end
112+ accounts . shift
138113
139- @rport = port
140- begin
141- unless session
142- connect ( )
143- smb_login ( )
114+ if datastore [ 'VERBOSE' ]
115+ accounts . each do |x |
116+ print_status x [ :logon_domain ] + "\\ " + x [ :account_name ] +
117+ "\t (logon_server: #{ x [ :logon_server ] } , other_domains: #{ x [ :other_domains ] } )"
144118 end
119+ else
120+ print_status "#{ accounts . collect { |x | x [ :logon_domain ] + "\\ " + x [ :account_name ] } . join ( ", " ) } "
121+ end
122+
123+ found_accounts = [ ]
124+ accounts . each do |x |
125+ comp_user = x [ :logon_domain ] + "\\ " + x [ :account_name ]
126+ found_accounts . push ( comp_user . scan ( /[[:print:]]/ ) . join ) unless found_accounts . include? ( comp_user . scan ( /[[:print:]]/ ) . join )
127+ end
145128
146- uuid = [ '6bffd098-a112-3610-9833-46c3f87e345a' , '1.0' ]
147-
148- handle = dcerpc_handle_target (
149- uuid [ 0 ] , uuid [ 1 ] , 'ncacn_np' , [ "\\ wkssvc" ] , simple . address
150- )
151- begin
152- dcerpc_bind ( handle )
153- stub =
154- NDR . uwstring ( "\\ \\ " + simple . address ) + # Server Name
155- NDR . long ( 1 ) + # Level
156- NDR . long ( 1 ) + # Ctr
157- NDR . long ( rand ( 0xffffffff ) ) + # ref id
158- NDR . long ( 0 ) + # entries read
159- NDR . long ( 0 ) + # null ptr to user0
160-
161- NDR . long ( 0xffffffff ) + # Prefmaxlen
162- NDR . long ( rand ( 0xffffffff ) ) + # ref id
163- NDR . long ( 0 ) # null ptr to resume handle
164-
165- dcerpc . call ( 2 , stub )
166-
167- resp = dcerpc . last_response ? dcerpc . last_response . stub_data : nil
168-
169- accounts = parse_net_wksta_enum_users_info ( resp )
170- accounts . shift
171-
172- if datastore [ 'VERBOSE' ]
173- accounts . each do |x |
174- print_status x [ :logon_domain ] + "\\ " + x [ :account_name ] +
175- "\t (logon_server: #{ x [ :logon_server ] } , other_domains: #{ x [ :other_domains ] } )"
176- end
177- else
178- print_status "#{ accounts . collect { |x | x [ :logon_domain ] + "\\ " + x [ :account_name ] } . join ( ", " ) } "
179- end
180-
181- found_accounts = [ ]
182- accounts . each do |x |
183- comp_user = x [ :logon_domain ] + "\\ " + x [ :account_name ]
184- found_accounts . push ( comp_user . scan ( /[[:print:]]/ ) . join ) unless found_accounts . include? ( comp_user . scan ( /[[:print:]]/ ) . join )
185- end
186-
187- found_accounts . each do |comp_user |
188- if comp_user . to_s =~ /\$ $/
189- next
190- end
191-
192- print_good ( "Found user: #{ comp_user } " )
193- store_username ( comp_user , resp , simple . address , rport )
194- end
195-
196- rescue ::Rex ::Proto ::SMB ::Exceptions ::ErrorCode => e
197- print_error ( "UUID #{ uuid [ 0 ] } #{ uuid [ 1 ] } ERROR 0x%.8x" % e . error_code )
198- #puts e
199- #return
200- rescue ::Exception => e
201- print_error ( "UUID #{ uuid [ 0 ] } #{ uuid [ 1 ] } ERROR #{ $!} " )
202- #puts e
203- #return
129+ found_accounts . each do |comp_user |
130+ if comp_user . to_s =~ /\$ $/
131+ next
204132 end
205133
206- disconnect ( )
207- return
208- rescue ::Exception
209- print_line ( $!. to_s )
134+ print_good ( "Found user: #{ comp_user } " )
210135 end
136+
211137 end
212- end
213138
139+ end
214140end
0 commit comments