1
0
mirror of https://github.com/nmap/nmap.git synced 2025-12-06 04:31:29 +00:00

Merged in significant changes to Microsoft RPC calls

This commit is contained in:
ron
2008-12-07 16:16:11 +00:00
parent e6505d9954
commit ea42f39faa
15 changed files with 5107 additions and 1715 deletions

View File

@@ -1,13 +1,34 @@
description = [[
Attempts to enumerate the users on a remote Windows system, with as much
information as possible, through a variety of techniques (over SMB and MSRPC,
which uses port 445 or 139). Some functions in SAMR are used to enumerate
users, and some brute-force guessing using LSA functions is attempted.
information as possible, through two different techniques (both over MSRPC,
which uses port 445 or 139). Some SAMR functions are used to enumerate users,
and bruteforce LSA guessing is attempted.
One technique used is calling the <code>QueryDisplayInfo</code> function in the SAMR library.
If this succeeds, it will return a detailed list of users. This can be done
anonymously against Windows 2000, and with a user-level account on other Windows
versions (but not with a guest-level account).
By default, both SAMR enumeration and LSA bruteforcing are used; however, these
can be fine tuned using Nmap parameters. For the most possible information,
leave the defaults; however, there are advantages to using them individually.
Advantages of using SAMR enumeration:
* Stealthier (requires one packet/user account, whereas LSA uses at least 20
packets; additionally, LSA makes a lot of noise in the Windows event log (LSA
enumeration is the only script I (Ron Bowes) have been called on by the
administrator of a box I was testing against).
* More information is returned (more than just the username).
* Every account will be found, since they're being enumerated with a function
that's designed to enumerate users.
Advantages of using LSA bruteforcing:
* More accounts are returned (system accounts, groups, and aliases are returned,
not just users).
* Requires a lower-level account to run on Windows XP and higher (a 'guest' account
can be used, whereas SAMR enumeration requires a 'user' account; especially useful
when only guest access is allowed, or when an account has a blank password (which
effectively gives it guest access)).
SAMR enumeration is done with the <code>QueryDisplayInfo</code> function.
If this succeeds, it will return a detailed list of users, along with descriptions,
types, and full names. This can be done anonymously against Windows 2000, and
with a user-level account on other Windows versions (but not with a guest-level account).
To perform this test, the following functions are used:
* <code>Bind</code>: bind to the SAMR service.
@@ -29,7 +50,7 @@ against Windows 2000, and requires a guest account or better on other systems.
It has the advantage of running with less permission, and will also find more
account types (i.e., groups, aliases, etc.). The disadvantages is that it returns
less information, and that, because it's a brute-force guess, it's possible to miss
accounts.
accounts. It's also extremely noisy.
This isn't a brute-force technique in the common sense, however: it's a brute-forcing of users'
RIDs. A user's RID is a value (generally 500, 501, or 1000+) that uniquely identifies
@@ -37,26 +58,28 @@ a user on a domain or system. An LSA function is exposed which lets us convert t
(say, 1000) to the username (say, "Ron"). So, the technique will essentially try
converting 1000 to a name, then 1001, 1002, etc., until we think we're done.
Users are broken into groups of five RIDs, then checked individually (checking too many
at once causes problems). We continue checking until we reach 1100, and get an empty
group. This probably isn't the most effective way, but it seems to work.
It might be a good idea to modify this, in the future, with some more
To do this, this script breaks users into groups of five RIDs, then checked individually
(checking too many at once causes problems). We continue checking until we reach
1100, and get an empty group of five. This probably isn't the most effective way, but it
seems to work. It might be a good idea to modify this, in the future, with some more
intelligence. I (Ron Bowes) performed a test on an old server with a lot of accounts,
and I got these results: 500, 501, 1000, 1030, 1031, 1053, 1054, 1055,
and these were the active RIDs: 500, 501, 1000, 1030, 1031, 1053, 1054, 1055,
1056, 1057, 1058, 1059, 1060, 1061, 1062, 1063, 1064, 1065, 1066, 1067, 1070,
1075, 1081, 1088, 1090. The jump from 1000 to 1030 is quite large and can easily
result in missing accounts, in an automated check.
result in missing accounts, in an automated check. An ideal solution might be to continue
doing groups of 5, but wait until we get 5-10 consecutive empty groups before giving up.
Before attempting this conversion, the SID of the server has to be determined.
The SID is determined by doing the reverse operation, that is, converting a name into
a RID. The name is determined by looking up any name present on the system.
The SID is determined by doing the reverse operation; that is, by converting a name into
its RID. The name is determined by looking up any name present on the system.
We try:
* The computer name and domain name, returned in <code>SMB_COM_NEGOTIATE</code>;
* An nbstat query to get the server name and the user currently logged in; and
* Some common names: "administrator", "guest", and "test".
In theory, the computer name should be sufficient for this to always work, and
so far has in my tests, but I included the rest of the names for good measure.
it has so far has in my tests, but I included the rest of the names for good measure. It
doesn't hurt to add more.
The names and details from both of these techniques are merged and displayed.
If the output is verbose, then extra details are shown. The output is ordered alphabetically.
@@ -110,8 +133,14 @@ the code I wrote for this is largely based on the techniques used by them.
-- |_ |_ Domain: LOCALSYSTEM
--
-- @args smb* This script supports the <code>smbusername</code>,
-- <code>smbpassword</code>, <code>smbhash</code>, <code>smbguest</code>, and
-- <code>smbtype</code> script arguments of the <code>smb</code> module.
-- <code>smbpassword</code>, <code>smbhash</code>, and <code>smbtype</code>
-- script arguments of the <code>smb</code> module.
-- @args lsaonly If set, script will only enumerate using an LSA bruteforce (requires less
-- access than samr). Only set if you know what you're doing, you'll get better results
-- by using the default options.
-- @args samronly If set, script will only query a list of users using a SAMR lookup. This is
-- much quieter than LSA lookups, so enable this if you want stealth. Generally, however,
-- you'll get better results by using the default options.
-----------------------------------------------------------------------
author = "Ron Bowes"
@@ -136,6 +165,7 @@ end
--<code>domain</code>, <code>fullname</code>, <code>rid</code>, and
--<code>description</code>.
local function enum_samr(host)
local i, j
stdnse.print_debug(3, "Entering enum_samr()")
@@ -177,15 +207,15 @@ local function enum_samr(host)
end
-- If no domains were returned, go back with an error
if(#enumdomains_result['domains'] == 0) then
if(#enumdomains_result['sam']['entries'] == 0) then
msrpc.stop_smb(smbstate)
return false, "Couldn't find any domains"
end
-- Now, loop through the domains and find the users
for i = 1, #enumdomains_result['domains'], 1 do
for i = 1, #enumdomains_result['sam']['entries'], 1 do
local domain = enumdomains_result['domains'][i]
local domain = enumdomains_result['sam']['entries'][i]['name']
-- We don't care about the 'builtin' domain, in all my tests it's empty
if(domain ~= 'Builtin') then
local sid
@@ -211,25 +241,53 @@ local function enum_samr(host)
-- Save the domain handle
domain_handle = opendomain_result['domain_handle']
-- Call QueryDisplayInfo()
status, querydisplayinfo_result = msrpc.samr_querydisplayinfo(smbstate, domain_handle)
if(status == false) then
msrpc.stop_smb(smbstate)
return false, querydisplayinfo_result
end
-- Loop as long as we're getting valid results
j = 0
repeat
-- Call QueryDisplayInfo()
status, querydisplayinfo_result = msrpc.samr_querydisplayinfo(smbstate, domain_handle, j)
if(status == false) then
msrpc.stop_smb(smbstate)
return false, querydisplayinfo_result
end
-- Save the response
if(querydisplayinfo_result['return'] ~= 0 and querydisplayinfo_result['info'] ~= nil and querydisplayinfo_result['info']['entries'] ~= nil and querydisplayinfo_result['info']['entries'][1] ~= nil) then
local array = {}
local k
-- The reason these are all indexed from '1' is because we request names one at a time.
array['domain'] = domain
array['name'] = querydisplayinfo_result['info']['entries'][1]['account_name']
array['fullname'] = querydisplayinfo_result['info']['entries'][1]['full_name']
array['description'] = querydisplayinfo_result['info']['entries'][1]['description']
array['rid'] = querydisplayinfo_result['info']['entries'][1]['rid']
array['flags'] = querydisplayinfo_result['info']['entries'][1]['acct_flags']
array['source'] = "SAMR Enumeration"
-- Clean up the 'flags' array
for k = 1, #array['flags'], 1 do
array['flags'][k] = msrpc.samr_AcctFlags_tostr(array['flags'][k])
end
-- Add it to the array
response[#response + 1] = array
end
j = j + 1
until querydisplayinfo_result['return'] == 0
-- Close the domain handle
msrpc.samr_close(smbstate, domain_handle)
-- Finally, fill in the response!
for i = 1, #querydisplayinfo_result['details'], 1 do
querydisplayinfo_result['details'][i]['domain'] = domain
-- All we get from this is users
querydisplayinfo_result['details'][i]['typestr'] = "User"
querydisplayinfo_result['details'][i]['source'] = "SAMR Enumeration"
response[#response + 1] = querydisplayinfo_result['details'][i]
end
-- for i = 1, #querydisplayinfo_result['details'], 1 do
-- querydisplayinfo_result['details'][i]['domain'] = domain
-- -- All we get from this is users
-- querydisplayinfo_result['details'][i]['typestr'] = "User"
-- querydisplayinfo_result['details'][i]['source'] = "SAMR Enumeration"
-- response[#response + 1] = querydisplayinfo_result['details'][i]
-- end
end -- Checking for 'builtin'
end -- Domain loop
@@ -301,31 +359,30 @@ local function enum_lsa(host)
msrpc.stop_smb(smbstate)
return false, lookupnames2_result
end
-- Loop through the domains returned and find the users in each
for i = 1, #lookupnames2_result['domains'], 1 do
local domain = lookupnames2_result['domains'][i]['name']
local sid = lookupnames2_result['domains'][i]['sid']
local rids = { }
for i = 1, #lookupnames2_result['domains']['domains'], 1 do
local domain = lookupnames2_result['domains']['domains'][i]['name']
local sid = lookupnames2_result['domains']['domains'][i]['sid']
local sids = { }
local start = 1000
-- Start by looking up 500 - 505 (will likely be Administrator + guest)
for j = 500, 505, 1 do
rids[#rids + 1] = j
sids[#sids + 1] = sid .. "-" .. j
end
status, lookupsids2_result = msrpc.lsa_lookupsids2(smbstate, openpolicy2_result['policy_handle'], sid, rids)
status, lookupsids2_result = msrpc.lsa_lookupsids2(smbstate, openpolicy2_result['policy_handle'], sids)
if(status == false) then
stdnse.print_debug(1, string.format("Error looking up RIDs: %s", lookupsids2_result))
else
-- Put the details for each name into an array
for j = 1, #lookupsids2_result['details'], 1 do
if(lookupsids2_result['details'][j]['type'] ~= 8) then -- 8 = user not found
-- NOTE: Be sure to mirror any changes here in the next bit!
for j = 1, #lookupsids2_result['names']['names'], 1 do
if(lookupsids2_result['names']['names'][j]['sid_type'] ~= "SID_NAME_UNKNOWN") then
local result = {}
result['name'] = lookupsids2_result['details'][j]['name']
result['rid'] = 500 + j - 1
result['domain'] = domain
result['typestr'] = lookupsids2_result['details'][j]['typestr']
result['name'] = lookupsids2_result['names']['names'][j]['name']
result['rid'] = 500 + j - 1
result['domain'] = domain
result['typestr'] = msrpc.lsa_SidType_tostr(lookupsids2_result['names']['names'][j]['sid_type'])
result['source'] = "LSA Bruteforce"
response[#response + 1] = result
end
@@ -335,29 +392,26 @@ local function enum_lsa(host)
-- Now do groups of 5 users, until we get past 1100 and have an empty group
repeat
local used_names = 0
local rids = {}
local sids = {}
for j = start, start + 4, 1 do
rids[#rids + 1] = j
sids[#sids + 1] = sid .. "-" .. j
end
-- Try converting this group of RIDs into names
status, lookupsids2_result = msrpc.lsa_lookupsids2(smbstate, openpolicy2_result['policy_handle'], sid, rids)
status, lookupsids2_result = msrpc.lsa_lookupsids2(smbstate, openpolicy2_result['policy_handle'], sids)
if(status == false) then
stdnse.print_debug(1, string.format("Error looking up RIDs: %s", lookupsids2_result))
else
-- Put the details for each name into an array
for j = 1, #lookupsids2_result['details'], 1 do
if(lookupsids2_result['details'][j]['type'] ~= 8) then -- 8 = user not found
for j = 1, #lookupsids2_result['names']['names'], 1 do
if(lookupsids2_result['names']['names'][j]['sid_type'] ~= "SID_NAME_UNKNOWN") then
local result = {}
result['name'] = lookupsids2_result['details'][j]['name']
result['rid'] = start + j - 1
result['domain'] = domain
result['typestr'] = lookupsids2_result['details'][j]['typestr']
result['name'] = lookupsids2_result['names']['names'][j]['name']
result['rid'] = start + j - 1
result['domain'] = domain
result['typestr'] = msrpc.lsa_SidType_tostr(lookupsids2_result['names']['names'][j]['sid_type'])
result['source'] = "LSA Bruteforce"
response[#response + 1] = result
-- Increment the number of used names we have
used_names = used_names + 1
end
end
end
@@ -381,38 +435,50 @@ end
action = function(host)
local i, j
local samr_status, lsa_status
local samr_result, lsa_result
local samr_status = false
local lsa_status = false
local samr_result = "Didn't run"
local lsa_result = "Didn't run"
local names = {}
local name_strings = {}
local response = " \n"
local samronly = nmap.registry.args.samronly
local lsaonly = nmap.registry.args.lsaonly
local do_samr = samronly ~= nil or (samronly == nil and lsaonly == nil)
local do_lsa = lsaonly ~= nil or (samronly == nil and lsaonly == nil)
-- Try enumerating through LSA first. Since LSA provides less information, we want the
-- SAMR result to overwrite it.
lsa_status, lsa_result = enum_lsa(host)
if(lsa_status == false) then
if(nmap.debugging() > 0) then
response = response .. "ERROR: couldn't enum through LSA: " .. lsa_result .. "\n"
end
else
-- Copy the returned array into the names[] table, using the name as the key
stdnse.print_debug(2, "EnumUsers: Received %d names from LSA", #lsa_result)
for i = 1, #lsa_result, 1 do
names[string.upper(lsa_result[i]['name'])] = lsa_result[i]
if(do_lsa) then
lsa_status, lsa_result = enum_lsa(host)
if(lsa_status == false) then
if(nmap.debugging() > 0) then
response = response .. "ERROR: Couldn't enumerate through LSA: " .. lsa_result .. "\n"
end
else
-- Copy the returned array into the names[] table, using the name as the key
stdnse.print_debug(2, "EnumUsers: Received %d names from LSA", #lsa_result)
for i = 1, #lsa_result, 1 do
if(lsa_result[i]['name'] ~= nil) then
names[string.upper(lsa_result[i]['name'])] = lsa_result[i]
end
end
end
end
-- Try enumerating through SAMR
samr_status, samr_result = enum_samr(host)
if(samr_status == false) then
if(nmap.debugging() > 0) then
response = response .. "ERROR: couldn't enumerate through SAMR: " .. samr_result .. "\n"
end
else
-- Copy the returned array into the names[] table, using the name as the key
stdnse.print_debug(2, "EnumUsers: Received %d names from SAMR", #samr_result)
for i = 1, #samr_result, 1 do
names[string.upper(samr_result[i]['name'])] = samr_result[i]
if(do_samr) then
samr_status, samr_result = enum_samr(host)
if(samr_status == false) then
if(nmap.debugging() > 0) then
response = response .. "ERROR: Couldn't enumerate through SAMR: " .. samr_result .. "\n"
end
else
-- Copy the returned array into the names[] table, using the name as the key
stdnse.print_debug(2, "EnumUsers: Received %d names from SAMR", #samr_result)
for i = 1, #samr_result, 1 do
names[string.upper(samr_result[i]['name'])] = samr_result[i]
end
end
end
@@ -434,7 +500,7 @@ action = function(host)
-- Check if we actually got any names back
if(#name_strings == 0) then
response = response .. "Sorry, couldn't find any account names anonymously!"
response = response .. "Couldn't find any account names anonymously, sorry!"
else
-- If we're not verbose, just print out the names. Otherwise, print out everything we can
if(nmap.verbosity() < 1) then
@@ -457,7 +523,7 @@ action = function(host)
end
if(names[name]['fullname'] ~= nil) then response = response .. string.format(" |_ Full name: %s\n", names[name]['fullname']) end
if(names[name]['description'] ~= nil) then response = response .. string.format(" |_ Description: %s\n", names[name]['description']) end
if(names[name]['flags'] ~= nil) then response = response .. string.format(" |_ Flags: %s\n", stdnse.strjoin(", ", names[name]['flags_list'])) end
if(names[name]['flags'] ~= nil) then response = response .. string.format(" |_ Flags: %s\n", stdnse.strjoin(", ", names[name]['flags'])) end
if(nmap.verbosity() > 1) then
if(names[name]['source'] ~= nil) then response = response .. string.format(" |_ Source: %s\n", names[name]['source']) end