6
6
class MetasploitModule < Msf ::Auxiliary
7
7
8
8
# Exploit mixins should be called first
9
+ include Msf ::Exploit ::Remote ::MsWkst
9
10
include Msf ::Exploit ::Remote ::SMB ::Client
10
11
include Msf ::Exploit ::Remote ::SMB ::Client ::Authenticated
11
12
include Msf ::Exploit ::Remote ::DCERPC
@@ -24,191 +25,116 @@ def initialize
24
25
[
25
26
'natron' , # original module
26
27
'Joshua D. Abraham <jabra[at]praetorian.com>' , # database storage
28
+ 'NtAlexio2 <[email protected] >' , # refactor
27
29
] ,
28
30
'References' =>
29
31
[
30
32
[ 'URL' , 'https://docs.microsoft.com/en-us/windows/win32/api/lmwksta/nf-lmwksta-netwkstauserenum' ]
31
33
] ,
32
34
'License' => MSF_LICENSE ,
33
35
)
34
-
35
- deregister_options ( 'RPORT' )
36
-
37
36
end
38
37
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
52
40
end
53
41
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
86
44
end
87
45
88
- def rport
89
- @rport || datastore [ 'RPORT' ]
46
+ def connect ( * args , ** kwargs )
47
+ super ( * args , ** kwargs , direct : @smb_direct )
90
48
end
91
49
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 ] ) }
94
53
end
95
54
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
124
66
125
- def run_host ( ip )
67
+ smb_services . map { |smb_service | run_service ( smb_service [ :port ] , smb_service [ :direct ] ) }
68
+ end
126
69
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
128
94
95
+ def run_host ( _ip )
129
96
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
135
101
end
136
102
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
138
113
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 ] } )"
144
118
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
145
128
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
204
132
end
205
133
206
- disconnect ( )
207
- return
208
- rescue ::Exception
209
- print_line ( $!. to_s )
134
+ print_good ( "Found user: #{ comp_user } " )
210
135
end
136
+
211
137
end
212
- end
213
138
139
+ end
214
140
end
0 commit comments