1
+ ##
2
+ # ## This file is part of the Metasploit Framework and may be subject to
3
+ # redistribution and commercial restrictions. Please see the Metasploit
4
+ # Framework web site for more information on licensing and terms of use.
5
+ # http://metasploit.com/framework/
6
+ ##
7
+
8
+ require 'msf/core'
9
+ require 'rex'
10
+ require 'msf/core/post/common'
11
+
12
+ class Metasploit3 < Msf ::Post
13
+
14
+ include Msf ::Post ::Windows ::Priv
15
+ include Msf ::Auxiliary ::Report
16
+ include Msf ::Auxiliary ::Scanner
17
+ include Msf ::Post ::Common
18
+
19
+ def initialize ( info = { } )
20
+ super (
21
+ 'Name' => 'Windows Local Admin Search' ,
22
+ 'Description' => %q{
23
+ This module will identify systems in a given range that the
24
+ supplied domain user (should migrate into a user pid) has administrative
25
+ access to by using the windows api OpenSCManagerA to establishing a handle
26
+ to the remote host. Additionally it can enumerate logged in users and group
27
+ membership via windows api NetWkstaUserEnum and NetUserGetGroups.
28
+ } ,
29
+ 'License' => MSF_LICENSE ,
30
+ 'Version' => '$Revision: 14767 $' ,
31
+ 'Author' => [ 'Brandon McCann "zeknox" <bmccann [at] accuvant.com>' , 'Thomas McCarthy "smilingraccoon" <smilingraccoon [at] gmail.com>' , 'Royce Davis "r3dy" <rdavis [at] accuvant.com>' ] ,
32
+ 'Platform' => [ 'windows' ] ,
33
+ 'SessionTypes' => [ 'meterpreter' ]
34
+ )
35
+
36
+ register_options (
37
+ [
38
+ OptBool . new ( 'ENUM_USERS' , [ true , 'Enumerates logged on users.' , true ] ) ,
39
+ OptBool . new ( 'ENUM_GROUPS' , [ false , 'Enumerates groups for identified users.' , true ] ) ,
40
+ OptString . new ( 'DOMAIN' , [ false , 'Domain to enumerate user\'s groups for' , nil ] ) ,
41
+ OptString . new ( 'DOMAIN_CONTROLLER' , [ false , 'Domain Controller to query groups' , nil ] )
42
+
43
+ ] , self . class )
44
+ end
45
+
46
+ def setup
47
+ super
48
+
49
+ if is_system?
50
+ # running as SYSTEM and will not pass any network credentials
51
+ print_error "Running as SYSTEM, module should be run with USER level rights"
52
+ return
53
+ else
54
+ @adv = client . railgun . advapi32
55
+
56
+ # Get domain and domain controller if options left blank
57
+ if datastore [ 'DOMAIN' ] . nil?
58
+ user = client . sys . config . getuid
59
+ datastore [ 'DOMAIN' ] = user . split ( '\\' ) [ 0 ]
60
+ end
61
+
62
+ if datastore [ 'DOMAIN_CONTROLLER' ] . nil? and datastore [ 'ENUM_GROUPS' ]
63
+ @dc_error = false
64
+
65
+ # Uses DC which applied policy since it would be a DC this device normally talks to
66
+ cmd = "gpresult /SCOPE COMPUTER"
67
+ # If Vista/2008 or later add /R
68
+ if ( sysinfo [ 'OS' ] =~ /Build [6-9]\d \d \d / )
69
+ cmd << " /R"
70
+ end
71
+ res = cmd_exec ( "cmd.exe" , "/c #{ cmd } " )
72
+
73
+ # Check if RSOP data exists, if not disable group check
74
+ unless res =~ /does not have RSOP data./
75
+ datastore [ 'DOMAIN_CONTROLLER' ] = /Group Policy was applied from:\s *(.*)\s */ . match ( res ) [ 1 ] . chomp
76
+ else
77
+ @dc_error = true
78
+ print_error ( "User never logged into device, will not enumerate groups or manually specify DC." )
79
+ end
80
+ end
81
+ end
82
+ end
83
+
84
+ # main control method
85
+ def run_host ( ip )
86
+ connect ( ip )
87
+ end
88
+
89
+ # http://msdn.microsoft.com/en-us/library/windows/desktop/aa370669(v=vs.85).aspx
90
+ # enumerate logged in users
91
+ def enum_users ( host )
92
+ begin
93
+ # Connect to host and enumerate logged in users
94
+ winsessions = client . railgun . netapi32 . NetWkstaUserEnum ( "\\ \\ #{ host } " , 1 , 4 , -1 , 4 , 4 , nil )
95
+ rescue ::Exception => e
96
+ print_error ( "Issue enumerating users on #{ host } " )
97
+ print_error ( e . backtrace ) if datastore [ 'VERBOSE' ]
98
+ end
99
+ count = winsessions [ 'totalentries' ] * 2
100
+ startmem = winsessions [ 'bufptr' ]
101
+
102
+ base = 0
103
+ userlist = Array . new
104
+ begin
105
+ mem = client . railgun . memread ( startmem , 8 *count )
106
+ rescue ::Exception => e
107
+ print_error ( "Issue reading memory for #{ host } " )
108
+ print_error ( e . backtrace ) if datastore [ 'VERBOSE' ]
109
+ end
110
+ # For each entry returned, get domain and name of logged in user
111
+ begin
112
+ count . times { |i |
113
+ temp = { }
114
+ userptr = mem [ ( base + 0 ) , 4 ] . unpack ( "V*" ) [ 0 ]
115
+ temp [ :user ] = client . railgun . memread ( userptr , 255 ) . split ( "\0 \0 " ) [ 0 ] . split ( "\0 " ) . join
116
+ nameptr = mem [ ( base + 4 ) , 4 ] . unpack ( "V*" ) [ 0 ]
117
+ temp [ :domain ] = client . railgun . memread ( nameptr , 255 ) . split ( "\0 \0 " ) [ 0 ] . split ( "\0 " ) . join
118
+
119
+ # Ignore if empty or machine account
120
+ unless temp [ :user ] . empty? or temp [ :user ] [ -1 , 1 ] == "$"
121
+
122
+ # Check if enumerated user's domain matches supplied domain, if there was
123
+ # an error, or if option disabled
124
+ data = ""
125
+ if datastore [ 'DOMAIN' ] . upcase == temp [ :domain ] . upcase and not @dc_error and datastore [ 'ENUM_GROUPS' ]
126
+ data << " - Groups: #{ enum_groups ( temp [ :user ] ) . chomp ( ", " ) } "
127
+ end
128
+ line = "\t Logged in user:\t #{ temp [ :domain ] } \\ #{ temp [ :user ] } #{ data } \n "
129
+
130
+ # Write user and groups to notes database
131
+ db_note ( host , "#{ temp [ :domain ] } \\ #{ temp [ :user ] } #{ data } " , "localadmin.user.loggedin" )
132
+ userlist << line unless userlist . include? line
133
+
134
+ end
135
+
136
+ base = base + 8
137
+ }
138
+ rescue ::Exception => e
139
+ print_error ( "Issue enumerating users on #{ host } " )
140
+ print_error ( e . backtrace ) if datastore [ 'VERBOSE' ]
141
+ end
142
+ return userlist
143
+ end
144
+
145
+ # http://msdn.microsoft.com/en-us/library/windows/desktop/aa370653(v=vs.85).aspx
146
+ # Enumerate groups for identified users
147
+ def enum_groups ( user )
148
+ grouplist = ""
149
+
150
+ dc = "\\ \\ #{ datastore [ 'DOMAIN_CONTROLLER' ] } "
151
+ begin
152
+ # Connect to DC and enumerate groups of user
153
+ usergroups = client . railgun . netapi32 . NetUserGetGroups ( dc , user , 0 , 4 , -1 , 4 , 4 )
154
+
155
+ rescue ::Exception => e
156
+ print_error ( "Issue connecting to DC, try manually setting domain and DC" )
157
+ print_error ( e . backtrace ) if datastore [ 'VERBOSE' ]
158
+ end
159
+
160
+ count = usergroups [ 'totalentries' ]
161
+ startmem = usergroups [ 'bufptr' ]
162
+ base = 0
163
+
164
+ begin
165
+ mem = client . railgun . memread ( startmem , 8 *count )
166
+ rescue ::Exception => e
167
+ print_error ( "Issue reading memory for groups for user #{ user } " )
168
+ print_error ( e . backtrace ) if datastore [ 'VERBOSE' ]
169
+ end
170
+
171
+ begin
172
+ # For each entry returned, get group
173
+ count . to_i . times { |i |
174
+ temp = { }
175
+ groupptr = mem [ ( base + 0 ) , 4 ] . unpack ( "V*" ) [ 0 ]
176
+ temp [ :group ] = client . railgun . memread ( groupptr , 255 ) . split ( "\0 \0 " ) [ 0 ] . split ( "\0 " ) . join
177
+
178
+ # Add group to string to be returned
179
+ grouplist << "#{ temp [ :group ] } , "
180
+ if ( i % 5 ) == 2
181
+ grouplist <<"\n\t- "
182
+ end
183
+ base = base + 4
184
+ }
185
+
186
+ rescue ::Exception => e
187
+ print_error("Issue enumerating groups for user #{ user } , check domain")
188
+ print_error(e.backtrace) if datastore['VERBOSE']
189
+ end
190
+
191
+ return grouplist.chomp("\n\t- ")
192
+
193
+ end
194
+
195
+ # http://msdn.microsoft.com/en-us/library/windows/desktop/ms684323(v=vs.85).aspx
196
+ # method to connect to remote host using windows api
197
+ def connect(host)
198
+ if @adv.nil?
199
+ return
200
+ end
201
+
202
+ user = client.sys.config.getuid
203
+ # use railgun and OpenSCManagerA api to connect to remote host
204
+ manag = @adv.OpenSCManagerA("\\ \\ #{ host } ", nil, 0xF003F) # SC_MANAGER_ALL_ACCESS
205
+
206
+ if(manag["return"] != 0) # we have admin rights
207
+ result = "#{ host . ljust ( 16 ) } #{ user } - Local admin found\n"
208
+ # Run enumerate users on all hosts if option was set
209
+
210
+ if datastore['ENUM_USERS']
211
+ enum_users(host).each {|i|
212
+ result << i
213
+ }
214
+ end
215
+
216
+ # close the handle if connection was made
217
+ @adv.CloseServiceHandle(manag["return"])
218
+ # Append data to loot table within database
219
+ print_good(result.chomp("\n")) unless result.nil?
220
+ db_loot(host, user, "localadmin.user")
221
+ else
222
+ # we dont have admin rights
223
+ print_error("#{ host . ljust ( 16 ) } #{ user } - No Local Admin rights")
224
+ end
225
+ end
226
+
227
+ # Write to notes database
228
+ def db_note(host, data, type)
229
+ if db
230
+ report_note(
231
+ :type => type,
232
+ :data => data,
233
+ :host => host,
234
+ :update => :unique_data
235
+ )
236
+ end
237
+ end
238
+
239
+ # Write to loot database
240
+ def db_loot(host, user, type)
241
+ if db
242
+ p = store_loot(type, 'text/plain', host, "#{ host } :#{ user } ", 'hosts_localadmin.txt', user)
243
+ print_status("User data stored in: #{ p } ") if datastore['VERBOSE']
244
+ end
245
+ end
246
+ end
0 commit comments