Module: Msf::Post::Windows::Accounts

Included in:
LDAP, Priv, UserProfiles, Scripts::Meterpreter::Common
Defined in:
lib/msf/core/post/windows/accounts.rb

Constant Summary collapse

GUID =
[
  ['Data1',:DWORD],
  ['Data2',:WORD],
  ['Data3',:WORD],
  ['Data4','BYTE[8]']
]
DOMAIN_CONTROLLER_INFO =
[
  ['DomainControllerName',:LPSTR],
  ['DomainControllerAddress',:LPSTR],
  ['DomainControllerAddressType',:ULONG],
  ['DomainGuid',GUID],
  ['DomainName',:LPSTR],
  ['DnsForestName',:LPSTR],
  ['Flags',:ULONG],
  ['DcSiteName',:LPSTR],
  ['ClientSiteName',:LPSTR]
]

Instance Method Summary collapse

Instance Method Details

#delete_user(username, server_name = nil) ⇒ Object

delete_user(username, server_name = nil)

Summary:

Deletes a user account from the given server (or local if none given)

Parameters

username    - The username of the user to delete (not-qualified, e.g. BOB)
server_name - DNS or NetBIOS name of remote server on which to delete user

Returns:

One of the following:
   :success          - Everything went as planned
   :invalid_server   - The server name provided was invalid
   :not_on_primary   - Operation allowed only on domain controller
   :user_not_found   - User specified does not exist on the given server
   :access_denied    - You do not have permission to delete the given user

OR nil if there was an exceptional windows error (example: ran out of memory)

Caveats:

nil is returned if there is an *exceptional* windows error. That error is printed.
Everything other than ':success' signifies failure

90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
# File 'lib/msf/core/post/windows/accounts.rb', line 90

def delete_user(username, server_name = nil)
  deletion = client.railgun.netapi32.NetUserDel(server_name, username)

  #http://msdn.microsoft.com/en-us/library/aa370674.aspx
  case deletion['return']
  when 2221 # NERR_UserNotFound
    return :user_not_found
  when 2351 # NERR_InvalidComputer
    return :invalid_server
  when 2226 # NERR_NotPrimary
    return :not_on_primary
  when client.railgun.const('ERROR_ACCESS_DENIED')
    return :access_denied
  when 0
    return :success
  else
    error = deletion['GetLastError']
    if error != 0
      print_error "Unexpected Windows System Error #{error}"
    else
      # Uh... we shouldn't be here
      print_error "DeleteUser unexpectedly returned #{deletion['return']}"
    end
  end

  # If we got here, then something above failed
  return nil
end

#get_domain(server_name = nil) ⇒ Object

get_domain(server_name=nil)

Summary:

Retrieves the current DomainName the given server is
a member of.

Parameters

server_name - DNS or NetBIOS name of the remote server

Returns:

The DomainName of the remote server or nil if windows
could not retrieve the DomainControllerInfo or encountered
an exception.

42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
# File 'lib/msf/core/post/windows/accounts.rb', line 42

def get_domain(server_name=nil)
  domain = nil
  result = session.railgun.netapi32.DsGetDcNameA(
    server_name,
    nil,
    nil,
    nil,
    0,
    4)

  begin
    dc_info_addr = result['DomainControllerInfo']
    unless dc_info_addr == 0
      dc_info = session.railgun.util.read_data(DOMAIN_CONTROLLER_INFO, dc_info_addr)
      pointer = session.railgun.util.unpack_pointer(dc_info['DomainName'])
      domain = session.railgun.util.read_string(pointer)
    end
  ensure
    session.railgun.netapi32.NetApiBufferFree(dc_info_addr)
  end

  domain
end

#resolve_sid(sid, system_name = nil) ⇒ Object

resolve_sid(sid, system_name = nil)

Summary:

Retrieves the name, domain, and type of account for the given sid

Parameters:

sid         - A SID string (e.g. S-1-5-32-544)
system_name - Where to search. If nil, first local system then trusted DCs

Returns:

{
  :name   => account name (e.g. "SYSTEM")
  :domain => domain where the account name was found. May have values such as
             the work station's name, BUILTIN, NT AUTHORITY, or an empty string
  :type   => one of :user, :group, :domain, :alias, :well_known_group,
             :deleted_account, :invalid, :unknown, :computer
  :mapped => There was a mapping found for the SID
}

OR nil if there was an exceptional windows error (example: ran out of memory)

Caveats:

If a valid mapping is not found, only { :mapped => false } will be returned
nil is returned if there is an *exceptional* windows error. That error is printed.
If an invalid system_name is provided, there will be a windows error and nil returned

147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
# File 'lib/msf/core/post/windows/accounts.rb', line 147

def resolve_sid(sid, system_name = nil)
  adv = client.railgun.advapi32;

  # Second param is the size of the buffer where the pointer will be written
  # In railgun, if you specify 4 bytes for a PDWORD it will grow to 8, as needed.
  conversion = adv.ConvertStringSidToSidA(sid, 4)

  # If the call failed, handle errors accordingly.
  unless conversion['return']
    error = conversion['GetLastError']

    case error
    when client.railgun.const('ERROR_INVALID_SID')
      # An invalid SID was supplied
      return { :type => :invalid, :mapped => false }
    else
      print_error "Unexpected windows error #{error}"
      return nil
    end
  end

  # A reference to the SID data structure. Generally needed when working with sids
  psid = conversion['pSid']

  # http://msdn.microsoft.com/en-us/library/aa379166(v=vs.85).aspx
  # TODO: The buffer sizes here need to be reviewed/adjusted/optimized
  lookup = adv.LookupAccountSidA(system_name, psid, 100, 100, 100, 100, 1)

  # We no longer need the sid so free it.
  # NOTE: We do not check to see if this call succeeded. Do we care?
  adv.FreeSid(psid)

  # If the call failed, handle errors accordingly.
  unless lookup['return']
    error = lookup['GetLastError']

    case error
    when client.railgun.const('ERROR_INVALID_PARAMETER')
      # Unless the railgun call is broken, this means revesion is wrong
      return { :type => :invalid }
    when client.railgun.const('ERROR_NONE_MAPPED')
      # There were no accounts associated with this SID
      return { :mapped => false }
    else
      print_error "Unexpected windows error #{error}"
      return nil
    end
  end

  # peUse is the enum "SID_NAME_USE"
  sid_type = lookup_SID_NAME_USE(lookup['peUse'].unpack('C')[0])

  return {
    :name   => lookup['Name'],
    :domain => lookup['ReferencedDomainName'],
    :type   => sid_type,
    :mapped => true
  }
end