1
0
mirror of https://github.com/nmap/nmap.git synced 2025-12-19 05:59:01 +00:00

Merging changes from my experimental branch; the new versions of this scripts, which have significant changes to their core functionality, managed to hold their own against Brandon's network. More testing would be very helpful, though, especially with credentials (most of Brandon's scans were anonymous).

This commit is contained in:
ron
2008-12-24 00:53:01 +00:00
parent a246aaf469
commit 773000b65a
17 changed files with 1930 additions and 515 deletions

View File

@@ -1,5 +1,9 @@
# Nmap Changelog ($Id$); -*-text-*-
o Added smb-enum-processes.nse, a script that allows a user with administrator
credentials to view a tree of the processes running on the remote system
(uses HKEY_PERFORMANCE_DATA hive). [Ron Bowes]
o A problem that caused OS detection to fail for most hosts in a
certain was fixed. It happened when sending raw Ethernet frames
(by default on Windows or on other platforms with --send-eth) to

View File

@@ -436,8 +436,11 @@ int process_mainloop(lua_State *L) {
while (!running_scripts.empty()) {
current = *(running_scripts.begin());
if (current.rr.host->timedOut(&now))
state = LUA_ERRRUN;
if (current.rr.host->timedOut(&now)) {
printf("thread (%p) timed out\n", (void *) current.thread);
SCRIPT_ENGINE_TRY(process_finalize(L, current.registry_idx));
continue;
}
else
state = lua_resume(current.thread, current.resume_arguments);

View File

@@ -198,3 +198,4 @@ aaliyah1
zxcvbnm1
young1
test

View File

@@ -81,6 +81,26 @@ TRANSFER_SYNTAX = string.char(0x04, 0x5d, 0x88, 0x8a, 0xeb, 0x1c, 0xc9, 0x11, 0x
-- The 'referent_id' value is ignored, as far as I can tell, so this value is passed for it. No, it isn't random. :)
REFERENT_ID = 0x50414d4e
-- The maximum length of a packet fragment
MAX_FRAGMENT = 0x800
---The number of SAMR records to pull at once. This was originally 1, but since I've written
-- proper fragmentation code, I've successfully done it with 110 users, although I'd be surprised
-- if you couldn't go a lot higher. I had some issues that I suspect was UNIX truncating packets,
-- so I scaled it back.
local SAMR_GROUPSIZE = 20
---The number of LSA RIDs to check at once. I've successfully tested with up to about 110. Note that
-- due to very long message sizes, Wireshark might truncate packets if you have more than 30 together,
-- so for debugging, setting this to 30 might be a plan. Like SAMR, I scaled this back due to UNIX
-- truncation.
local LSA_GROUPSIZE = 20
---The number of consecutive empty groups to stop after. Basically, this means that after
-- <code>LSA_MINEMPTY</code> groups of <code>LSA_GROUPSIZE</code> users come back empty, we give
-- up. Raising this could find more users, but at the expense of more packets.
local LSA_MINEMPTY = 10
--- This is a wrapper around the SMB class, designed to get SMB going quickly for MSRPC calls. This will
-- connect to the SMB server, negotiate the protocol, open a session, connect to the IPC$ share, and
-- open the named pipe given by 'path'. When this successfully returns, the 'smbstate' table can be immediately
@@ -180,8 +200,8 @@ function bind(smbstate, interface_uuid, interface_version, transfer_syntax)
0x0048, -- Frag length
0x0000, -- Auth length
0x41414141, -- Call ID (I use 'AAAA' because it's easy to recognize)
0x10b8, -- Max transmit frag
0x10b8, -- Max receive frag
MAX_FRAGMENT, -- Max transmit frag
MAX_FRAGMENT, -- Max receive frag
0x00000000, -- Assoc group
0x01, -- Number of items
0x00, -- Padding/alignment
@@ -200,7 +220,12 @@ function bind(smbstate, interface_uuid, interface_version, transfer_syntax)
2 -- Syntax version
)
status, result = smb.send_transaction(smbstate, 0x0026, "", data)
status, result = smb.write_file(smbstate, data, 0)
if(status ~= true) then
return false, result
end
status, result = smb.read_file(smbstate, 0, MAX_FRAGMENT)
if(status ~= true) then
return false, result
end
@@ -213,6 +238,9 @@ function bind(smbstate, interface_uuid, interface_version, transfer_syntax)
-- Extract the first part from the resposne
pos, result['version_major'], result['version_minor'], result['packet_type'], result['packet_flags'], result['data_representation'], result['frag_length'], result['auth_length'], result['call_id'] = bin.unpack("<CCCC>I<SSI", data)
if(result['call_id'] == nil) then
return false, "MSRPC: ERROR: Ran off the end of SMB packet; likely due to server truncation"
end
-- Check if the packet tyep was a fault
if(result['packet_type'] == 0x03) then -- MSRPC_FAULT
@@ -241,13 +269,22 @@ function bind(smbstate, interface_uuid, interface_version, transfer_syntax)
-- If we made it this far, then we have a valid Bind() result. Pull out some more parameters.
pos, result['max_transmit_frag'], result['max_receive_frag'], result['assoc_group'], result['secondary_address_length'] = bin.unpack("SSIS", data, pos)
if(result['secondary_address_length'] == nil) then
return false, "MSRPC: ERROR: Ran off the end of SMB packet; likely due to server truncation"
end
-- Read the secondary address
pos, result['secondary_address'] = bin.unpack(string.format("<A%d", result['secondary_address_length']), data, pos)
if(result['secondary_address'] == nil) then
return false, "MSRPC: ERROR: Ran off the end of SMB packet; likely due to server truncation"
end
pos = pos + ((4 - ((pos - 1) % 4)) % 4); -- Alignment -- don't ask how I came up with this, it was a lot of drawing, and there's probably a far better way
-- Read the number of results
pos, result['num_results'] = bin.unpack("<C", data, pos)
if(result['num_results'] == nil) then
return false, "MSRPC: ERROR: Ran off the end of SMB packet; likely due to server truncation"
end
pos = pos + ((4 - ((pos - 1) % 4)) % 4); -- Alignment
-- Verify we got back what we expected
@@ -257,6 +294,9 @@ function bind(smbstate, interface_uuid, interface_version, transfer_syntax)
-- Read in the last bits
pos, result['ack_result'], result['align'], result['transfer_syntax'], result['syntax_version'] = bin.unpack("<SSA16I", data, pos)
if(result['syntax_version'] == nil) then
return false, "MSRPC: ERROR: Ran off the end of SMB packet; likely due to server truncation"
end
return true, result
end
@@ -275,13 +315,17 @@ end
--@param opnum The operating number (ie, the function). Find this in the MSRPC documentation or with a packet logger.
--@param arguments The marshalled arguments to pass to the function. Currently, marshalling is all done manually.
--@return (status, result) If status is false, result is an error message. Otherwise, result is a table of values, the most
-- useful one being 'arguments', which are the values returned by the server.
-- useful one being 'arguments', which are the values returned by the server. If the packet is fragmented, the fragments
-- will be reassembled and 'arguments' will represent all the arguments; however, the rest of the result table will represent
-- the most recent fragment.
local function call_function(smbstate, opnum, arguments)
local i
local status, result
local parameters, data
local pos, align
local result
local first = true
local is_first, is_last
data = bin.pack("<CCCC>I<SSIISSA",
0x05, -- Version (major)
@@ -301,7 +345,16 @@ local function call_function(smbstate, opnum, arguments)
stdnse.print_debug(3, "MSRPC: Calling function 0x%02x with %d bytes of arguments", string.len(arguments), opnum)
-- Pass the information up to the smb layer
status, result = smb.send_transaction(smbstate, 0x0026, "", data)
status, result = smb.write_file(smbstate, data, 0)
if(status ~= true) then
return false, result
end
-- Loop over the fragments
local arguments = ""
repeat
-- Read the information from the smb layer
status, result = smb.read_file(smbstate, 0, 0x1001)
if(status ~= true) then
return false, result
end
@@ -312,6 +365,23 @@ local function call_function(smbstate, opnum, arguments)
-- Extract the first part from the resposne
pos, result['version_major'], result['version_minor'], result['packet_type'], result['packet_flags'], result['data_representation'], result['frag_length'], result['auth_length'], result['call_id'] = bin.unpack("<CCCC>I<SSI", data)
if(result['call_id'] == nil) then
return false, "MSRPC: ERROR: Ran off the end of SMB packet; likely due to server truncation"
end
-- Check if we're fragmented
is_first = (bit.band(result['packet_flags'], 0x01) == 0x01)
is_last = (bit.band(result['packet_flags'], 0x02) == 0x02)
-- We have a fragmented packet, make sure it's the first (if we're on the first)
if(first == true and is_first == false) then
return false, "MSRPC: First fragment doesn't have proper 'first' (0x01) flag set"
end
-- We have a fragmented packet, make sure it isn't the first (if we aren't on the first)
if(first == false and is_first) then
return false, "MSRPC: Middle (or last) fragment doesn't have proper 'first' (0x01) flag set"
end
-- Check if there was an error
if(result['packet_type'] == 0x03) then -- MSRPC_FAULT
@@ -323,9 +393,6 @@ local function call_function(smbstate, opnum, arguments)
if(result['auth_length'] ~= 0) then
return false, "MSRPC call returned an 'auth length', which we don't know how to deal with"
end
if(bit.band(result['packet_flags'], 0x03) ~= 0x03) then
return false, "MSRPC call returned a fragmented packet, which we don't know how to handle"
end
if(result['packet_type'] ~= 0x02) then
return false, "MSRPC call returned an unexpected packet type (not RESPONSE)"
end
@@ -335,13 +402,22 @@ local function call_function(smbstate, opnum, arguments)
-- Extract some more
pos, result['alloc_hint'], result['context_id'], result['cancel_count'], align = bin.unpack("<ISCC", data, pos)
if(align == nil) then
return false, "MSRPC: ERROR: Ran off the end of SMB packet; likely due to server truncation"
end
-- Rest is the arguments
result['arguments'] = string.sub(data, pos)
arguments = arguments .. string.sub(data, pos)
-- No longer the 'first'
first = false
until is_last == true
result['arguments'] = arguments
stdnse.print_debug(3, "MSRPC: Function call successful, %d bytes of returned argumenst", string.len(result['arguments']))
return true, result
end
---A proxy to a <code>msrpctypes</code> function that converts a ShareType to an english string.
@@ -1054,16 +1130,21 @@ end
--@param smbstate The SMB state table
--@param domain_handle The domain handle, returned by <code>samr_opendomain</code>
--@param index The index of the user to check; the first user is 0, next is 1, etc.
--@param count [optional] The number of users to return; you may want to be careful about going too high. Default: 1.
--@return (status, result) If status is false, result is an error message. Otherwise, result is a table of values, the most
-- useful ones being 'names', a list of all the usernames, and 'details', a further list of tables with the elements
-- 'name', 'fullname', and 'description' (note that any of them can be nil if the server didn't return a value). Finally,
-- 'flags' is the numeric flags for the user, while 'flags_list' is an array of strings, representing the flags.
function samr_querydisplayinfo(smbstate, domain_handle, index)
function samr_querydisplayinfo(smbstate, domain_handle, index, count)
local i, j
local status, result
local arguments
local pos, align
if(count == nil) then
count = 1
end
-- This loop is because, in my testing, if I asked for all the results at once, it would blow up (ERR_BUFFER_OVERFLOW). So, instead,
-- I put a little loop here and grab the names individually.
stdnse.print_debug(2, "MSRPC: Calling QueryDisplayInfo(%d) [%s]", index, smbstate['ip'])
@@ -1078,10 +1159,10 @@ function samr_querydisplayinfo(smbstate, domain_handle, index)
arguments = arguments .. msrpctypes.marshall_int32(index)
-- [in] uint32 max_entries,
arguments = arguments .. msrpctypes.marshall_int32(1)
arguments = arguments .. msrpctypes.marshall_int32(count)
-- [in] uint32 buf_size,
arguments = arguments .. msrpctypes.marshall_int32(0)
arguments = arguments .. msrpctypes.marshall_int32(0x7FFFFFFF)
-- [out] uint32 total_size,
-- [out] uint32 returned_size,
@@ -1110,7 +1191,6 @@ function samr_querydisplayinfo(smbstate, domain_handle, index)
-- [out] uint32 returned_size,
pos, result['returned_size'] = msrpctypes.unmarshall_int32(arguments, pos)
-- [out,switch_is(level)] samr_DispInfo info
pos, result['info'] = msrpctypes.unmarshall_samr_DispInfo(arguments, pos)
if(pos == nil) then
@@ -1966,10 +2046,10 @@ function winreg_queryvalue(smbstate, handle, value)
arguments = arguments .. msrpctypes.marshall_winreg_Type_ptr("REG_NONE")
-- [in,out,size_is(*size),length_is(*length)] uint8 *data,
arguments = arguments .. msrpctypes.marshall_int8_array_ptr("", 520)
arguments = arguments .. msrpctypes.marshall_int8_array_ptr("", 1000000)
-- [in,out] uint32 *size,
arguments = arguments .. msrpctypes.marshall_int32_ptr(520)
arguments = arguments .. msrpctypes.marshall_int32_ptr(1000000)
-- [in,out] uint32 *length
arguments = arguments .. msrpctypes.marshall_int32_ptr(0)
@@ -1991,9 +2071,7 @@ function winreg_queryvalue(smbstate, handle, value)
-- [in,ref] policy_handle *handle,
-- [in] winreg_String value_name,
-- [in,out] winreg_Type *type,
pos,
pos = pos + 4
pos, result['type'] = msrpctypes.unmarshall_winreg_Type(arguments, pos)
pos, result['type'] = msrpctypes.unmarshall_winreg_Type_ptr(arguments, pos)
-- [in,out,size_is(*size),length_is(*length)] uint8 *data,
pos, result['data'] = msrpctypes.unmarshall_int8_array_ptr(arguments, pos)
@@ -2004,6 +2082,8 @@ function winreg_queryvalue(smbstate, handle, value)
_, result['value'] = bin.unpack("<I", result['data'])
elseif(result['type'] == "REG_SZ" or result['type'] == "REG_MULTI_SZ" or result['type'] == "REG_EXPAND_SZ") then
_, result['value'] = msrpctypes.unicode_to_string(result['data'], 1, #result['data'] / 2)
elseif(result['type'] == "REG_BINARY") then
result['value'] = result['data']
else
stdnse.print_debug("MSRPC ERROR: Unknown type: %s\n\n", result['type'])
result['value'] = result['type']
@@ -2019,6 +2099,7 @@ function winreg_queryvalue(smbstate, handle, value)
pos, result['length'] = msrpctypes.unmarshall_int32_ptr(arguments, pos)
pos, result['return'] = msrpctypes.unmarshall_int32(arguments, pos)
if(result['return'] == nil) then
return false, "Read off the end of the packet (winreg.queryvalue)"
end
@@ -2075,3 +2156,348 @@ function winreg_closekey(smbstate, handle)
return true, result
end
---Attempt to enumerate users using SAMR functions.
--
--@param host The host object.
--@return (status, result) If status is false, result is an error message. Otherwise, result
-- is an array of tables, each of which contain the following fields:
-- * name
-- * fullname
-- * description
-- * rid
-- * domain
-- * typestr
-- * source
-- * flags[]
function samr_enum_users(host)
local i, j
stdnse.print_debug(3, "Entering enum_samr()")
local smbstate
local bind_result, connect4_result, enumdomains_result
local connect_handle
local status, smbstate
local response = {}
-- Create the SMB session
status, smbstate = msrpc.start_smb(host, msrpc.SAMR_PATH)
if(status == false) then
return false, smbstate
end
-- Bind to SAMR service
status, bind_result = msrpc.bind(smbstate, msrpc.SAMR_UUID, msrpc.SAMR_VERSION, nil)
if(status == false) then
msrpc.stop_smb(smbstate)
return false, bind_result
end
-- Call connect4()
status, connect4_result = msrpc.samr_connect4(smbstate, host.ip)
if(status == false) then
msrpc.stop_smb(smbstate)
return false, connect4_result
end
-- Save the connect_handle
connect_handle = connect4_result['connect_handle']
-- Call EnumDomains()
status, enumdomains_result = msrpc.samr_enumdomains(smbstate, connect_handle)
if(status == false) then
msrpc.stop_smb(smbstate)
return false, enumdomains_result
end
-- If no domains were returned, go back with an error
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['sam']['entries'], 1 do
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
local domain_handle
local opendomain_result, querydisplayinfo_result
-- Call LookupDomain()
status, lookupdomain_result = msrpc.samr_lookupdomain(smbstate, connect_handle, domain)
if(status == false) then
msrpc.stop_smb(smbstate)
return false, lookupdomain_result
end
-- Save the sid
sid = lookupdomain_result['sid']
-- Call OpenDomain()
status, opendomain_result = msrpc.samr_opendomain(smbstate, connect_handle, sid)
if(status == false) then
msrpc.stop_smb(smbstate)
return false, opendomain_result
end
-- Save the domain handle
domain_handle = opendomain_result['domain_handle']
-- Loop as long as we're getting valid results
j = 0
repeat
-- Call QueryDisplayInfo()
status, querydisplayinfo_result = msrpc.samr_querydisplayinfo(smbstate, domain_handle, j, SAMR_GROUPSIZE)
if(status == false) then
msrpc.stop_smb(smbstate)
return false, querydisplayinfo_result
end
-- Save the response
if(querydisplayinfo_result['info'] ~= nil and querydisplayinfo_result['info']['entries'] ~= nil) then
local k
for k = 1, #querydisplayinfo_result['info']['entries'], 1 do
local array = {}
local l
-- The reason these are all indexed from '1' is because we request names one at a time.
array['name'] = querydisplayinfo_result['info']['entries'][k]['account_name']
array['fullname'] = querydisplayinfo_result['info']['entries'][k]['full_name']
array['description'] = querydisplayinfo_result['info']['entries'][k]['description']
array['rid'] = querydisplayinfo_result['info']['entries'][k]['rid']
array['domain'] = domain
array['type'] = 'SID_NAME_USER'
array['typestr'] = 'User'
array['source'] = 'SAMR Enumeration'
array['flags'] = querydisplayinfo_result['info']['entries'][k]['acct_flags']
-- Convert each element in the 'flags' array into the equivalent string
for l = 1, #array['flags'], 1 do
array['flags'][l] = msrpc.samr_AcctFlags_tostr(array['flags'][l])
end
-- Add it to the array
response[#response + 1] = array
end
end
j = j + SAMR_GROUPSIZE
until querydisplayinfo_result['return'] == 0
-- Close the domain handle
msrpc.samr_close(smbstate, domain_handle)
end -- Checking for 'builtin'
end -- Domain loop
-- Close the connect handle
msrpc.samr_close(smbstate, connect_handle)
-- Stop the SAMR SMB
msrpc.stop_smb(smbstate)
stdnse.print_debug(3, "Leaving enum_samr()")
return true, response
end
---Attempt to enumerate users using LSA functions.
--
--@param host The host object.
--@return status, result -- if status is false, result is an error message; otherwise, result is
-- an array of tables, each containing the following elements:
-- * name
-- * rid
-- * domain
-- * typestr
-- * source
function lsa_enum_users(host)
local smbstate
local response = {}
local status, smbstate, bind_result, openpolicy2_result, lookupnames2_result, lookupsids2_result
stdnse.print_debug(3, "Entering enum_lsa()")
-- Create the SMB session
status, smbstate = msrpc.start_smb(host, msrpc.LSA_PATH)
if(status == false) then
return false, smbstate
end
-- Bind to LSA service
status, bind_result = msrpc.bind(smbstate, msrpc.LSA_UUID, msrpc.LSA_VERSION, nil)
if(status == false) then
msrpc.stop_smb(smbstate)
return false, bind_result
end
-- Open the LSA policy
status, openpolicy2_result = msrpc.lsa_openpolicy2(smbstate, host.ip)
if(status == false) then
msrpc.stop_smb(smbstate)
return false, openpolicy2_result
end
-- Start with some common names, as well as the name returned by the negotiate call
-- Vista doesn't like a 'null' after the server name, so fix that (TODO: the way I strip the null here feels hackish, is there a better way?)
names = {"administrator", "guest", "test", smbstate['domain'], string.sub(smbstate['server'], 1, #smbstate['server'] - 1) }
-- Get the server's name from nbstat
local result, server_name = netbios.get_server_name(host.ip)
if(result == true) then
names[#names + 1] = server_name
end
-- Get the logged in user from nbstat
local result, user_name = netbios.get_user_name(host.ip)
if(result == true) then
names[#names + 1] = user_name
end
-- Look up the names, if any are valid than the server's SID will be returned
status, lookupnames2_result = msrpc.lsa_lookupnames2(smbstate, openpolicy2_result['policy_handle'], names)
if(status == false) then
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']['domains'], 1 do
local domain = lookupnames2_result['domains']['domains'][i]['name']
local sid = lookupnames2_result['domains']['domains'][i]['sid']
local sids = { }
-- Start by looking up 500 and up
for j = 500, 500 + LSA_GROUPSIZE, 1 do
sids[#sids + 1] = sid .. "-" .. j
end
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
-- 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['names']['names'][j]['name']
result['rid'] = 500 + j - 1
result['domain'] = domain
result['type'] = lookupsids2_result['names']['names'][j]['sid_type']
result['typestr'] = msrpc.lsa_SidType_tostr(result['type'])
result['source'] = "LSA Bruteforce"
table.insert(response, result)
end
end
end
-- Start at RID 1000
local start = 1000
-- Keep track of the number of consecutive empty groups
local empty = 0
repeat
-- Keep track of the number of names we found in this group
local used_names = 0
local sids = {}
for j = start, start + LSA_GROUPSIZE, 1 do
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'], 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['names']['names'], 1 do
if(lookupsids2_result['names']['names'][j]['sid_type'] ~= "SID_NAME_UNKNOWN") then
local result = {}
result['name'] = lookupsids2_result['names']['names'][j]['name']
result['rid'] = start + j - 1
result['domain'] = domain
result['type'] = lookupsids2_result['names']['names'][j]['sid_type']
result['typestr'] = msrpc.lsa_SidType_tostr(result['type'])
result['source'] = "LSA Bruteforce"
table.insert(response, result)
-- Increment the number of names we've found
used_names = used_names + 1
end
end
end
-- Either increment or reset the number of empty groups
if(used_names == 0) then
empty = empty + 1
else
empty = 0
end
-- Go to the next set of RIDs
start = start + LSA_GROUPSIZE
until (status == false or (empty == LSA_MINEMPTY))
end
-- Close the handle
msrpc.lsa_close(smbstate, openpolicy2_result['policy_handle'])
msrpc.stop_smb(smbstate)
stdnse.print_debug(3, "Leaving enum_lsa()")
return true, response
end
---Gets the best possible list of user accounts on the remote system using every available method.
--
-- TODO: Caching, store this in the registry
--
--@param host The host object.
--@return (status, result, names) If status is false, result is an error message; otherwise, result
-- is an array of users indexed by username and names is a sorted array of names.
function get_user_list(host)
local status_samr, result_samr
local status_lsa, result_lsa
local response = {}
local names = {}
local i, v
status_lsa, result_lsa = lsa_enum_users(host)
if(status_lsa == false) then
stdnse.print_debug("MSRPC: Failed to enumerate users through LSA: %s", result_lsa)
else
for i = 1, #result_lsa, 1 do
if(result_lsa[i]['name'] ~= nil and result_lsa[i]['type'] == "SID_NAME_USER") then
response[result_lsa[i]['domain'] .. '\\' .. result_lsa[i]['name']] = result_lsa[i]
end
end
end
status_samr, result_samr = samr_enum_users(host)
if(status_samr == false) then
stdnse.print_debug("MSRPC: Failed to enumerate users through SAMR: %s", result_samr)
else
for i = 1, #result_samr, 1 do
if(result_samr[i]['name'] ~= nil and result_samr[i]['type'] == "SID_NAME_USER") then
response[result_samr[i]['domain'] .. '\\' .. result_samr[i]['name']] = result_samr[i]
end
end
end
if(status_samr == false and status_lsa == false) then
return false, "MSRPC: Couldn't enumerate users; see debug output for more information"
end
for i, v in pairs(response) do
table.insert(names, i)
end
table.sort(names, function(a,b) return a:lower() < b:lower() end )
return true, response, names
end

601
nselib/msrpcperformance.lua Normal file
View File

@@ -0,0 +1,601 @@
---This module is designed to parse the <code>PERF_DATA_BLOCK</code> structure, which is
-- stored in the registry under HKEY_PERFORMANCE_DATA. By querying this structure, you can
-- get a whole lot of information about what's going on.
--
-- To use this from a script, see <code>get_performance_data</code>, it is the only
-- 'public' function in this module.
--
-- My primary sources of information were:
-- * This 1996 journal by Matt Pietrek: <http://www.microsoft.com/msj/archive/S271.aspx>
-- * The followup article: <http://www.microsoft.com/msj/archive/S2A9.aspx>
-- * The WinPerf.h header file
--
-- And my primary inspiration was PsTools, specifically, pstasklist.exe.
--
--@author Ron Bowes <ron@skullsecurity.net>
--@copyright See nmap's COPYING for licence
-----------------------------------------------------------------------
module(... or "msrpcperformance", package.seeall)
require 'msrpctypes'
---Parses the title database, which is a series of null-terminated string pairs.
--
--@param data The data being processed.
--@param pos The position within <code>data</code>.
--@return (status, pos, result) The status (true if successful), the new position in <code>data</code> (or an error
-- message), and a table representing the datatype, if any.
local function parse_perf_title_database(data, pos)
local result = {}
repeat
local number, name
pos, number, name = bin.unpack("<zz", data, pos)
result[tonumber(number)] = name
until pos >= #data
return true, pos, result
end
---Parses a PERF_DATA_BLOCK, which has the following definition (from "WinPerf.h" on Visual Studio 8):
--
--<code>
-- typedef struct _PERF_DATA_BLOCK {
-- WCHAR Signature[4]; // Signature: Unicode "PERF"
-- DWORD LittleEndian; // 0 = Big Endian, 1 = Little Endian
-- DWORD Version; // Version of these data structures
-- // starting at 1
-- DWORD Revision; // Revision of these data structures
-- // starting at 0 for each Version
-- DWORD TotalByteLength; // Total length of data block
-- DWORD HeaderLength; // Length of this structure
-- DWORD NumObjectTypes; // Number of types of objects
-- // being reported
-- LONG DefaultObject; // Object Title Index of default
-- // object to display when data from
-- // this system is retrieved (-1 =
-- // none, but this is not expected to
-- // be used)
-- SYSTEMTIME SystemTime; // Time at the system under
-- // measurement
-- LARGE_INTEGER PerfTime; // Performance counter value
-- // at the system under measurement
-- LARGE_INTEGER PerfFreq; // Performance counter frequency
-- // at the system under measurement
-- LARGE_INTEGER PerfTime100nSec; // Performance counter time in 100 nsec
-- // units at the system under measurement
-- DWORD SystemNameLength; // Length of the system name
-- DWORD SystemNameOffset; // Offset, from beginning of this
-- // structure, to name of system
-- // being measured
-- } PERF_DATA_BLOCK, *PPERF_DATA_BLOCK;
--</code>
--
--@param data The data being processed.
--@param pos The position within <code>data</code>.
--@return (status, pos, result) The status (true if successful), the new position in <code>data</code> (or an error
-- message), and a table representing the datatype, if any.
local function parse_perf_data_block(data, pos)
local result = {}
pos, result['Signature'] = msrpctypes.unicode_to_string(data, pos, 4, false)
if(result['Signature'] ~= "PERF") then
return false, "MSRPC: PERF_DATA_BLOCK signature is missing or incorrect"
end
pos, result['LittleEndian'] = msrpctypes.unmarshall_int32(data, pos)
if(result['LittleEndian'] ~= 1) then
return false, "MSRPC: PERF_DATA_BLOCK returned a non-understood endianness"
end
-- Parse the header
pos, result['Version'] = msrpctypes.unmarshall_int32(data, pos)
pos, result['Revision'] = msrpctypes.unmarshall_int32(data, pos)
pos, result['TotalByteLength'] = msrpctypes.unmarshall_int32(data, pos)
pos, result['HeaderLength'] = msrpctypes.unmarshall_int32(data, pos)
pos, result['NumObjectTypes'] = msrpctypes.unmarshall_int32(data, pos)
pos, result['DefaultObject'] = msrpctypes.unmarshall_int32(data, pos)
pos, result['SystemTime'] = msrpctypes.unmarshall_SYSTEMTIME(data, pos)
pos, result['PerfTime'] = msrpctypes.unmarshall_int64(data, pos)
pos, result['PerfFreq'] = msrpctypes.unmarshall_int64(data, pos)
pos, result['PerfTime100nSec'] = msrpctypes.unmarshall_int64(data, pos)
pos = pos + 4 -- This value doesn't seem to line up, so add 4
pos, result['SystemNameLength'] = msrpctypes.unmarshall_int32(data, pos)
pos, result['SystemNameOffset'] = msrpctypes.unmarshall_int32(data, pos)
-- Ensure that the system name is directly after the header. This technically shouldn't matter, but Microsoft's documentation
-- (in WinPref.h) says that the actual object comes "after the PERF_DATA_BLOCK", so it doesn't make sense that the SystemName
-- could be anywhere else.
if(pos ~= result['SystemNameOffset'] + 1) then
return false, "MSRPC: PERF_DATA_BLOCK has SystemName in the wrong location"
end
-- Read the system name from the next location (which happens to be identical to SystemNameOffset, on a proper system)
pos, result['SystemName'] = msrpctypes.unicode_to_string(data, pos, result['SystemNameLength'] / 2, true)
pos = pos + 4 -- Again, we end up not lined up so here we fix it
return true, pos, result
end
---Parse a PERF_OBJECT_TYPE structure. From Microsoft's documentation:
--
--<code>
-- //
-- // The _PERF_DATA_BLOCK structure is followed by NumObjectTypes of
-- // data sections, one for each type of object measured. Each object
-- // type section begins with a _PERF_OBJECT_TYPE structure.
-- //
-- typedef struct _PERF_OBJECT_TYPE {
-- DWORD TotalByteLength; // Length of this object definition
-- // including this structure, the
-- // counter definitions, and the
-- // instance definitions and the
-- // counter blocks for each instance:
-- // This is the offset from this
-- // structure to the next object, if
-- // any
-- DWORD DefinitionLength; // Length of object definition,
-- // which includes this structure
-- // and the counter definition
-- // structures for this object: this
-- // is the offset of the first
-- // instance or of the counters
-- // for this object if there is
-- // no instance
-- DWORD HeaderLength; // Length of this structure: this
-- // is the offset to the first
-- // counter definition for this
-- // object
-- DWORD ObjectNameTitleIndex;
-- // Index to name in Title Database
-- #ifdef _WIN64
-- DWORD ObjectNameTitle; // Should use this as an offset
-- #else
-- LPWSTR ObjectNameTitle; // Initially NULL, for use by
-- // analysis program to point to
-- // retrieved title string
-- #endif
-- DWORD ObjectHelpTitleIndex;
-- // Index to Help in Title Database
-- #ifdef _WIN64
-- DWORD ObjectHelpTitle; // Should use this as an offset
-- #else
-- LPWSTR ObjectHelpTitle; // Initially NULL, for use by
-- // analysis program to point to
-- // retrieved title string
-- #endif
-- DWORD DetailLevel; // Object level of detail (for
-- // controlling display complexity);
-- // will be min of detail levels
-- // for all this object's counters
-- DWORD NumCounters; // Number of counters in each
-- // counter block (one counter
-- // block per instance)
-- LONG DefaultCounter; // Default counter to display when
-- // this object is selected, index
-- // starting at 0 (-1 = none, but
-- // this is not expected to be used)
-- LONG NumInstances; // Number of object instances
-- // for which counters are being
-- // returned from the system under
-- // measurement. If the object defined
-- // will never have any instance data
-- // structures (PERF_INSTANCE_DEFINITION)
-- // then this value should be -1, if the
-- // object can have 0 or more instances,
-- // but has none present, then this
-- // should be 0, otherwise this field
-- // contains the number of instances of
-- // this counter.
-- DWORD CodePage; // 0 if instance strings are in
-- // UNICODE, else the Code Page of
-- // the instance names
-- LARGE_INTEGER PerfTime; // Sample Time in "Object" units
-- //
-- LARGE_INTEGER PerfFreq; // Frequency of "Object" units in
-- // counts per second.
-- } PERF_OBJECT_TYPE, *PPERF_OBJECT_TYPE;
--</code>
--
--@param data The data being processed.
--@param pos The position within <code>data</code>.
--@return (status, pos, result) The status (true if successful), the new position in <code>data</code> (or an error
-- message), and a table representing the datatype, if any.
local function parse_perf_object_type(data, pos)
local result = {}
pos, result['TotalByteLength'] = msrpctypes.unmarshall_int32(data, pos) -- Offset to the next object
pos, result['DefinitionLength'] = msrpctypes.unmarshall_int32(data, pos) -- Offset to the first instance (or counter, if no instances)
pos, result['HeaderLength'] = msrpctypes.unmarshall_int32(data, pos) -- Offset to the first counter definition
pos, result['ObjectNameTitleIndex'] = msrpctypes.unmarshall_int32(data, pos) -- Index in the Title Database
pos, result['ObjectNameTitle'] = msrpctypes.unmarshall_int32(data, pos) -- TODO: will this work with 64-bit?
pos, result['ObjectHelpTitleIndex'] = msrpctypes.unmarshall_int32(data, pos) -- Index in the Help Database
pos, result['ObjectHelpTitle'] = msrpctypes.unmarshall_int32(data, pos) -- TODO: will this workw ith 64-bit?
pos, result['DetailLevel'] = msrpctypes.unmarshall_int32(data, pos)
pos, result['NumCounters'] = msrpctypes.unmarshall_int32(data, pos) -- The number of counters in each counter block
pos, result['DefaultCounter'] = msrpctypes.unmarshall_int32(data, pos)
pos, result['NumInstances'] = msrpctypes.unmarshall_int32(data, pos) -- Numer of object instances for which counters are being returned
pos, result['CodePage'] = msrpctypes.unmarshall_int32(data, pos) -- 0 if strings are in UNICODE, otherwise the Code Page
-- if(result['CodePage'] ~= 0) then
-- return false, string.format("Unknown Code Page for data: %d\n", result['CodePage'])
-- end
pos, result['PerfTime'] = msrpctypes.unmarshall_int64(data, pos) -- Sample time in "Object" units
pos, result['PerfFreq'] = msrpctypes.unmarshall_int64(data, pos) -- Frequency of "Object" units in counts/second
return true, pos, result
end
---Parse a PERF_COUNTER_DEFINITION structure. From Microsoft's documentation:
--
--<code>
-- // There is one of the following for each of the
-- // PERF_OBJECT_TYPE.NumCounters. The Unicode names in this structure MUST
-- // come from a message file.
-- typedef struct _PERF_COUNTER_DEFINITION {
-- DWORD ByteLength; // Length in bytes of this structure
-- DWORD CounterNameTitleIndex;
-- // Index of Counter name into
-- // Title Database
-- #ifdef _WIN64
-- DWORD CounterNameTitle;
-- #else
-- LPWSTR CounterNameTitle; // Initially NULL, for use by
-- // analysis program to point to
-- // retrieved title string
-- #endif
-- DWORD CounterHelpTitleIndex;
-- // Index of Counter Help into
-- // Title Database
-- #ifdef _WIN64
-- DWORD CounterHelpTitle;
-- #else
-- LPWSTR CounterHelpTitle; // Initially NULL, for use by
-- // analysis program to point to
-- // retrieved title string
-- #endif
-- LONG DefaultScale; // Power of 10 by which to scale
-- // chart line if vertical axis is 100
-- // 0 ==> 1, 1 ==> 10, -1 ==>1/10, etc.
-- DWORD DetailLevel; // Counter level of detail (for
-- // controlling display complexity)
-- DWORD CounterType; // Type of counter
-- DWORD CounterSize; // Size of counter in bytes
-- DWORD CounterOffset; // Offset from the start of the
-- // PERF_COUNTER_BLOCK to the first
-- // byte of this counter
-- } PERF_COUNTER_DEFINITION, *PPERF_COUNTER_DEFINITION;
--</code>
--
--@param data The data being processed.
--@param pos The position within <code>data</code>.
--@return (status, pos, result) The status (true if successful), the new position in <code>data</code> (or an error
-- message), and a table representing the datatype, if any.
local function parse_perf_counter_definition(data, pos)
local result = {}
local initial_pos = pos
pos, result['ByteLength'] = msrpctypes.unmarshall_int32(data, pos)
pos, result['CounterNameTitleIndex'] = msrpctypes.unmarshall_int32(data, pos)
pos, result['CounterNameTitle'] = msrpctypes.unmarshall_int32(data, pos)
pos, result['CounterHelpTitleIndex'] = msrpctypes.unmarshall_int32(data, pos)
pos, result['CounterHelpTitle'] = msrpctypes.unmarshall_int32(data, pos)
pos, result['DefaultScale'] = msrpctypes.unmarshall_int32(data, pos)
pos, result['DetailLevel'] = msrpctypes.unmarshall_int32(data, pos)
pos, result['CounterType'] = msrpctypes.unmarshall_int32(data, pos)
pos, result['CounterSize'] = msrpctypes.unmarshall_int32(data, pos)
pos, result['CounterOffset'] = msrpctypes.unmarshall_int32(data, pos)
pos = initial_pos + result['ByteLength']
return true, pos, result
end
---Parse the actual counter value. This is a fairly simple function, it takes a counter
-- definition and pulls out data based on it.
--
-- Note: I don't think this is doing the 8-byte values right, I suspect that they're supposed
-- to be doubles.
--
--@param data The data being processed.
--@param pos The position within <code>data</code>.
--@param counter_definition The matching counter_definition.
--@return (status, pos, result) The status (true if successful), the new position in <code>data</code> (or an error
-- message), and a table representing the datatype, if any.
local function parse_perf_counter(data, pos, counter_definition)
local result
if(counter_definition['CounterSize'] == 4) then
pos, result = msrpctypes.unmarshall_int32(data, pos)
elseif(counter_definition['CounterSize'] == 8) then
pos, result = msrpctypes.unmarshall_int64(data, pos)
-- pos, result = bin.unpack("<d", data, pos)
else
pos, result = msrpctypes.unmarshall_raw(data, pos, counter_definition['CounterSize'])
end
return true, pos, result
end
---Parse a PERF_INSTANCE_DEFINITION structure. From Microsoft's documentation:
--
--<code>
-- // If (PERF_DATA_BLOCK.NumInstances >= 0) then there will be
-- // PERF_DATA_BLOCK.NumInstances of a (PERF_INSTANCE_DEFINITION
-- // followed by a PERF_COUNTER_BLOCK followed by the counter data fields)
-- // for each instance.
-- //
-- // If (PERF_DATA_BLOCK.NumInstances < 0) then the counter definition
-- // strucutre above will be followed by only a PERF_COUNTER_BLOCK and the
-- // counter data for that COUNTER.
-- typedef struct _PERF_INSTANCE_DEFINITION {
-- DWORD ByteLength; // Length in bytes of this structure,
-- // including the subsequent name
-- DWORD ParentObjectTitleIndex;
-- // Title Index to name of "parent"
-- // object (e.g., if thread, then
-- // process is parent object type);
-- // if logical drive, the physical
-- // drive is parent object type
-- DWORD ParentObjectInstance;
-- // Index to instance of parent object
-- // type which is the parent of this
-- // instance.
-- LONG UniqueID; // A unique ID used instead of
-- // matching the name to identify
-- // this instance, -1 = none
-- DWORD NameOffset; // Offset from beginning of
-- // this struct to the Unicode name
-- // of this instance
-- DWORD NameLength; // Length in bytes of name; 0 = none
-- // this length includes the characters
-- // in the string plus the size of the
-- // terminating NULL char. It does not
-- // include any additional pad bytes to
-- // correct structure alignment
-- } PERF_INSTANCE_DEFINITION, *PPERF_INSTANCE_DEFINITION;
--</code>
--
--@param data The data being processed.
--@param pos The position within <code>data</code>.
--@return (status, pos, result) The status (true if successful), the new position in <code>data</code> (or an error
-- message), and a table representing the datatype, if any.
local function parse_perf_instance_definition(data, pos)
local result = {}
-- Remember where we started. I noticed that where the counter part starts can move around, so we have to
-- determine it by adding ByteLength to the initial position
local initial_pos = pos
pos, result['ByteLength'] = msrpctypes.unmarshall_int32(data, pos)
pos, result['ParentObjectTitleIndex'] = msrpctypes.unmarshall_int32(data, pos)
pos, result['ParentObjectInstance'] = msrpctypes.unmarshall_int32(data, pos)
pos, result['UniqueID'] = msrpctypes.unmarshall_int32(data, pos)
pos, result['NameOffset'] = msrpctypes.unmarshall_int32(data, pos)
pos, result['NameLength'] = msrpctypes.unmarshall_int32(data, pos)
pos, result['InstanceName'] = msrpctypes.unicode_to_string(data, pos, result['NameLength'] / 2, true)
pos = initial_pos + result['ByteLength']
return true, pos, result
end
---Parse a PERF_COUNTER_BLOCK structure. From Microsoft's documentation:
--
--<code>
-- typedef struct _PERF_COUNTER_BLOCK {
-- DWORD ByteLength; // Length in bytes of this structure,
-- // including the following counters
-- } PERF_COUNTER_BLOCK, *PPERF_COUNTER_BLOCK;
--
--</code>
--
--@param data The data being processed.
--@param pos The position within <code>data</code>.
--@return (status, pos, result) The status (true if successful), the new position in <code>data</code> (or an error
-- message), and a table representing the datatype, if any.
local function parse_perf_counter_block(data, pos)
local result = {}
pos, result['ByteLength'] = msrpctypes.unmarshall_int32(data, pos)
return true, pos, result
end
---Retrieve the parsed performance data from the given host for the requested object values. To get a list of possible
-- object values, leave 'objects' blank and look at <code>result['title_database']</code> -- it'll contain a list of
-- indexes that can be looked up. These indexes are passed as a string or as a series of space-separated strings (eg,
-- "230" for "Process" and "238" for "Process" and "Processor").
--
--@param host The host object
--@param objects [optional] The space-separated list of object numbers to retrieve. Default: only retrieve the database.
function get_performance_data(host, objects)
local status, smbstate
local bind_result, openhkpd_result, queryvalue_result, data_block
local pos
local result = {}
local i, j, k
local pos
-- Create the SMB session
status, smbstate = msrpc.start_smb(host, msrpc.WINREG_PATH)
if(status == false) then
return false, smbstate
end
-- Bind to WINREG service
status, bind_result = msrpc.bind(smbstate, msrpc.WINREG_UUID, msrpc.WINREG_VERSION, nil)
if(status == false) then
msrpc.stop_smb(smbstate)
return false, bind_result
end
-- Open HKEY_PERFORMANCE_DATA
status, openhkpd_result = msrpc.winreg_openhkpd(smbstate)
if(status == false) then
msrpc.stop_smb(smbstate)
return false, openhkpd_result
end
status, queryvalue_result = msrpc.winreg_queryvalue(smbstate, openhkpd_result['handle'], "Counter 009")
if(status == false) then
msrpc.stop_smb(smbstate)
return false, queryvalue_result
end
-- Parse the title database
pos = 1
status, pos, result['title_database'] = parse_perf_title_database(queryvalue_result['value'], pos)
result['title_database'][0] = "<null>"
if(status == false) then
msrpc.stop_smb(smbstate)
return false, pos
end
if(objects ~= nil and #objects > 0) then
-- Query for the objects
status, queryvalue_result = msrpc.winreg_queryvalue(smbstate, openhkpd_result['handle'], objects)
if(status == false) then
msrpc.stop_smb(smbstate)
return false, queryvalue_result
end
-- Parse the header
pos = 1
status, pos, data_block = parse_perf_data_block(queryvalue_result['value'], pos)
if(status == false) then
msrpc.stop_smb(smbstate)
return false, pos
end
-- Move past the header
pos = 1 + data_block['HeaderLength']
-- Parse the data sections
for i = 1, data_block['NumObjectTypes'], 1 do
local object_start = pos
local object_name
local counter_definitions = {}
local object_instances = {}
local counter_definitions = {}
-- Get the type of the object (this is basically the class definition -- info about the object instances)
status, pos, object_type = parse_perf_object_type(queryvalue_result['value'], pos)
if(status == false) then
msrpc.stop_smb(smbstate)
return false, pos
end
-- Start setting up the result object
--io.write(string.format("Index = %d\n", object_type['ObjectNameTitleIndex']))
object_name = result['title_database'][object_type['ObjectNameTitleIndex']]
result[object_name] = {}
--io.write(string.format("\n\nOBJECT: %s\n", object_name))
--io.write(string.format(" Counters: %d\n", object_type['NumCounters']))
--io.write(string.format(" Instances: %d\n", object_type['NumInstances']))
--io.write("-----------------\n")
-- Bring the position to the beginning of the counter definitions
pos = object_start + object_type['HeaderLength']
-- Parse the counter definitions
for j = 1, object_type['NumCounters'], 1 do
status, pos, counter_definitions[j] = parse_perf_counter_definition(queryvalue_result['value'], pos)
if(status == false) then
msrpc.stop_smb(smbstate)
return false, pos
end
--io.write(string.format(" Counter definition #%2d: [%d bytes] %s\n", j, counter_definitions[j]['CounterSize'], result['title_database'][counter_definitions[j]['CounterNameTitleIndex']]))
end
-- Bring the position to the beginning of the instances (or counters)
pos = object_start + object_type['DefinitionLength']
-- Check if we have any instances (sometimes we don't -- if we don't, the value returned is a negative)
if(bit.band(object_type['NumInstances'], 0x80000000) == 0) then
-- Parse the object instances and counters
for j = 1, object_type['NumInstances'], 1 do
local instance_start = pos
local instance_name
local counter_block
-- Instance definition
status, pos, object_instances[j] = parse_perf_instance_definition(queryvalue_result['value'], pos)
if(status == false) then
msrpc.stop_smb(smbstate)
return false, pos
end
-- Set up the instance array
instance_name = object_instances[j]['InstanceName']
result[object_name][instance_name] = {}
-- Bring the pos to the start of the counter block
pos = instance_start + object_instances[j]['ByteLength']
--io.write(string.format("\n INSTANCE: %s\n", instance_name))
--io.write(string.format(" Length: %d\n", object_instances[j]['ByteLength']))
--io.write(string.format(" NameOffset: %d\n", object_instances[j]['NameOffset']))
--io.write(string.format(" NameLength: %d\n", object_instances[j]['NameLength']))
--io.write(" --------------\n")
-- The counter block
status, pos, counter_block = parse_perf_counter_block(queryvalue_result['value'], pos)
if(status == false) then
msrpc.stop_smb(smbstate)
return false, pos
end
for k = 1, object_type['NumCounters'], 1 do
local counter_name
-- Each individual counter
status, pos, counter_result = parse_perf_counter(queryvalue_result['value'], pos, counter_definitions[k])
if(status == false) then
msrpc.stop_smb(smbstate)
return false, pos
end
counter_name = result['title_database'][counter_definitions[k]['CounterNameTitleIndex']]
--io.write(string.format(" %s: %s\n", counter_name, counter_result))
-- Save it in the result
result[object_name][instance_name][counter_name] = counter_result
end
-- Bring the pos to the end of the next section
pos = instance_start + object_instances[j]['ByteLength'] + counter_block['ByteLength']
end
else
for k = 1, object_type['NumCounters'], 1 do
local counter_name
-- Each individual counter
status, pos, counter_result = parse_perf_counter(queryvalue_result['value'], pos, counter_definitions[k])
if(status == false) then
msrpc.stop_smb(smbstate)
return false, pos
end
counter_name = result['title_database'][counter_definitions[k]['CounterNameTitleIndex']]
--io.write(string.format(" %s: %s\n", counter_name, counter_result))
-- Save it in the result
result[object_name][counter_name] = counter_result
end
end
end
-- Blank out the database
result['title_database'] = nil
end
msrpc.stop_smb(smbstate)
return true, result
end

View File

@@ -289,7 +289,7 @@ end
-- (for the pointer data), or ALL (for both together). Generally, unless the
-- referent_id is split from the data (for example, in an array), you will want
-- ALL.
--@param data The data packet being processed.
--@param data The data being processed.
--@param pos The position within <code>data</code>
--@param func The function that's used to process the body data (only called if it isn't a null
-- pointer). This function has to conform to a specific prototype, see above.
@@ -311,6 +311,10 @@ local function unmarshall_ptr(location, data, pos, func, args, result)
if(location == HEAD or location == ALL) then
local referent_id
pos, referent_id = bin.unpack("<I", data, pos)
if(referent_id == nil) then
stdnse.print_debug(1, "MSRPC: ERROR: Ran off the end of a packet in unmarshall_ptr(). Please report!")
end
if(location == HEAD) then
if(referent_id == 0) then
result = false
@@ -435,7 +439,7 @@ end
-- whether or not to null-terminate a string, or whether or not to pad an int16. If different types are
-- required, you're probably out of luck.
--
--@param data The data packet being processed.
--@param data The data being processed.
--@param pos The position within <code>data</code>.
--@param count The number of elements in the array.
--@param func The function to call to unmarshall each parameter. Has to match a specific prototype;
@@ -454,6 +458,9 @@ local function unmarshall_array(data, pos, count, func, args)
end
pos, max_count = bin.unpack("<I", data, pos)
if(max_count == nil) then
stdnse.print_debug(1, "MSRPC: ERROR: Ran off the end of a packet in unmarshall_array(). Please report!")
end
-- Unmarshall the header, which will be referent_ids and base types.
for i = 1, count, 1 do
@@ -483,7 +490,7 @@ end
-- func(location, data, pos, result, <args>)
--</code>
--
--@param data The data packet being processed.
--@param data The data being processed.
--@param pos The position within <code>data</code>.
--@param func The function to call to unmarshall each parameter. Has to match a specific prototype;
-- see the function comment.
@@ -594,6 +601,9 @@ function unmarshall_unicode(data, pos, do_null)
end
pos, max, offset, actual = bin.unpack("<III", data, pos)
if(actual == nil) then
stdnse.print_debug(1, "MSRPC: ERROR: Ran off the end of a packet in unmarshall_unicode(). Please report!")
end
pos, str = unicode_to_string(data, pos, actual, do_null, true)
@@ -604,7 +614,7 @@ end
---Unmarshall a pointer to a unicode string.
--
--@param data The data packet being processed.
--@param data The data being processed.
--@param pos The position within <code>data</code>.
--@param do_null [optional] Assumes a null is at the end of the string. Default false.
--@return (pos, result) The new position and the string.
@@ -695,7 +705,7 @@ end
--- Unmarshall an int64. See <code>marshall_int64</code> for more information.
--
--@param data The data packet being processed.
--@param data The data being processed.
--@param pos The position within <code>data</code>.
--@return (pos, int64) The new position, and the value.
function unmarshall_int64(data, pos)
@@ -703,6 +713,9 @@ function unmarshall_int64(data, pos)
stdnse.print_debug(4, string.format("MSRPC: Entering unmarshall_int64()"))
pos, value = bin.unpack("<l", data, pos)
if(value == nil) then
stdnse.print_debug(1, "MSRPC: ERROR: Ran off the end of a packet in unmarshall_int64(). Please report!")
end
stdnse.print_debug(4, string.format("MSRPC: Leaving unmarshall_int64()"))
return pos, value
@@ -710,7 +723,7 @@ end
--- Unmarshall an int32. See <code>marshall_int32</code> for more information.
--
--@param data The data packet being processed.
--@param data The data being processed.
--@param pos The position within <code>data</code>.
--@return (pos, int32) The new position, and the value.
function unmarshall_int32(data, pos)
@@ -718,6 +731,9 @@ function unmarshall_int32(data, pos)
stdnse.print_debug(4, string.format("MSRPC: Entering unmarshall_int32()"))
pos, value = bin.unpack("<I", data, pos)
if(value == nil) then
stdnse.print_debug(1, "MSRPC: ERROR: Ran off the end of a packet in unmarshall_int32(). Please report!")
end
stdnse.print_debug(4, string.format("MSRPC: Leaving unmarshall_int32()"))
return pos, value
@@ -735,6 +751,9 @@ function unmarshall_int16(data, pos, pad)
stdnse.print_debug(4, string.format("MSRPC: Entering unmarshall_int16()"))
pos, value = bin.unpack("<S", data, pos)
if(value == nil) then
stdnse.print_debug(1, "MSRPC: ERROR: Ran off the end of a packet in unmarshall_int16(). Please report!")
end
if(pad == nil or pad == true) then
pos = pos + 2
@@ -757,6 +776,9 @@ function unmarshall_int8(data, pos, pad)
stdnse.print_debug(4, string.format("MSRPC: Entering unmarshall_int8()"))
pos, value = bin.unpack("<C", data, pos)
if(value == nil) then
stdnse.print_debug(1, "MSRPC: ERROR: Ran off the end of a packet in unmarshall_int8(). Please report!")
end
if(pad == nil or pad == true) then
pos = pos + 3
@@ -918,7 +940,14 @@ function unmarshall_int8_array(data, pos, pad)
stdnse.print_debug(4, string.format("MSRPC: Entering unmarshall_int8_array()"))
pos, max, offset, actual = bin.unpack("<III", data, pos)
if(actual == nil) then
stdnse.print_debug(1, "MSRPC: ERROR: Ran off the end of a packet in unmarshall_int8_array(). Please report!")
end
pos, str = bin.unpack("<A"..actual, data, pos)
if(str == nil) then
stdnse.print_debug(1, "MSRPC: ERROR: Ran off the end of a packet in unmarshall_int8_array() [2]. Please report!")
end
-- Do the alignment (note the "- 1", it's there because of 1-based arrays)
if(pad == nil or pad == true) then
@@ -997,6 +1026,9 @@ function unmarshall_NTTIME(data, pos)
stdnse.print_debug(4, string.format("MSRPC: Entering unmarshall_NTTIME()"))
pos, time = bin.unpack("<L", data, pos)
if(time == nil) then
stdnse.print_debug(1, "MSRPC: ERROR: Ran off the end of a packet in unmarshall_NTTIME(). Please report!")
end
if(time ~= 0) then
time = (time / 10000000) - 11644473600
@@ -1035,6 +1067,36 @@ function unmarshall_NTTIME_ptr(data, pos)
return pos, time
end
---Unmarshall a SYSTEMTIME structure, converting it to a standard representation. The structure is a
-- follows:
--
-- <code>
-- typedef struct _SYSTEMTIME {
-- WORD wYear;
-- WORD wMonth;
-- WORD wDayOfWeek;
-- WORD wDay;
-- WORD wHour;
-- WORD wMinute;
-- WORD wSecond;
-- WORD wMilliseconds;
-- } SYSTEMTIME
-- </code>
--
--@param data The data packet.
--@param pos The position within the data.
--@return (pos, time) The new position, and the time in seconds since 1970.
function unmarshall_SYSTEMTIME(data, pos)
local date = {}
pos, date['year'], date['month'], _, date['day'], date['hour'], date['min'], date['sec'], _ = bin.unpack("<SSSSSSSS", data, pos)
if(date['sec'] == nil) then
stdnse.print_debug(1, "MSRPC: ERROR: Ran off the end of a packet in unmarshall_SYSTEMTIME(). Please report!")
end
return pos, os.time(date)
end
---Unmarshalls a <code>hyper</code>. I have no idea what a <code>hyper</code> is, just that it seems
-- to be a 64-bit data type used for measuring time, and that the units happen to be negative
-- microseconds. This function converts the value to seconds and returns it.
@@ -1143,6 +1205,7 @@ end
--@param pos The position within the data.
--@param table The table to use for lookups. The keys should be the names, and the values should be
-- the numbers.
--@return (pos, array) The new position, and a table representing the enumeration values.
local function unmarshall_Enum32_array(data, pos, table)
local array = {}
local i, v
@@ -1161,6 +1224,23 @@ local function unmarshall_Enum32_array(data, pos, table)
return pos, array
end
---Unmarshall raw data.
--@param data The data packet.
--@param pos The position within the data.
--@param length The number of bytes to unmarshall.
--@return (pos, data) The new position in the packet, and a string representing the raw data.
function unmarshall_raw(data, pos, length)
local val
stdnse.print_debug(4, string.format("MSRPC: Entering unmarshall_raw()"))
pos, val = bin.unpack(string.format("A%d", length), data, pos)
if(val == nil) then
stdnse.print_debug(1, "MSRPC: ERROR: Ran off the end of a packet in unmarshall_raw(). Please report!")
end
stdnse.print_debug(4, string.format("MSRPC: Leaving unmarshall_raw()"))
return pos, val
end
-------------------------------------
@@ -1202,6 +1282,9 @@ local function unmarshall_guid(data, pos)
stdnse.print_debug(4, string.format("MSRPC: Entering unmarshall_guid()"))
pos, guid['time_low'], guid['time_high'], guid['time_hi_and_version'], guid['clock_seq'], guid['node'] = bin.unpack("<ISSA2A6", data, pos)
if(guid['node'] == nil) then
stdnse.print_debug(1, "MSRPC: ERROR: Ran off the end of a packet in unmarshall_guid(). Please report!")
end
stdnse.print_debug(4, string.format("MSRPC: Leaving unmarshall_guid()"))
return pos, guid
@@ -1237,7 +1320,7 @@ function unmarshall_policy_handle(data, pos)
local policy_handle = {}
stdnse.print_debug(4, string.format("MSRPC: Entering unmarshall_policy_handle()"))
pos, policy_handle['handle_type'] = bin.unpack("<I", data, pos)
pos, policy_handle['handle_type'] = unmarshall_int32(data, pos)
pos, policy_handle['uuid'] = unmarshall_guid(data, pos)
stdnse.print_debug(4, string.format("MSRPC: Leaving unmarshall_policy_handle()"))
@@ -1260,7 +1343,7 @@ end
-- } dom_sid;
--</code>
--
--@param data The data packet being processed.
--@param data The data being processed.
--@param pos The position within <code>data</code>.
--@return (pos, result) The new position in <code>data</code>, and a table representing the datatype.
function unmarshall_dom_sid2(data, pos)
@@ -1269,16 +1352,20 @@ function unmarshall_dom_sid2(data, pos)
-- Read the SID from the packet
local sid = {}
pos, sid['count'] = bin.unpack("<I", data, pos)
pos, sid['sid_rev_num'] = bin.unpack("<C", data, pos)
pos, sid['num_auths'] = bin.unpack("<C", data, pos)
pos, sid['count'] = unmarshall_int32(data, pos)
pos, sid['sid_rev_num'] = unmarshall_int8(data, pos, false)
pos, sid['num_auths'] = unmarshall_int8(data, pos, false)
-- Note that authority is big endian (I guess it's an array, not really an integer like we're handling it)
pos, sid['authority_high'], sid['authority_low'] = bin.unpack(">SI", data, pos)
if(sid['authority_low'] == nil) then
stdnse.print_debug(1, "MSRPC: ERROR: Ran off the end of a packet in unmarshall_dom_sid2(). Please report!")
end
sid['authority'] = bit.bor(bit.lshift(sid['authority_high'], 32), sid['authority_low'])
sid['sub_auths'] = {}
for i = 1, sid['num_auths'], 1 do
pos, sid['sub_auths'][i] = bin.unpack("<I", data, pos)
pos, sid['sub_auths'][i] = unmarshall_int32(data, pos)
end
-- Convert the SID to a string
@@ -1294,7 +1381,7 @@ end
---Unmarshall a pointer to a <code>dom_sid2</code> struct. See the <code>unmarshall_dom_sid2</code> function
-- for more information.
--
--@param data The data packet being processed.
--@param data The data being processed.
--@param pos The position within <code>data</code>.
--@return (pos, result) The new position in <code>data</code>, and a table representing the datatype.
function unmarshall_dom_sid2_ptr(data, pos)
@@ -1452,7 +1539,9 @@ local function unmarshall_lsa_String_internal(location, data, pos, result)
stdnse.print_debug(4, string.format("MSRPC: Entering unmarshall_lsa_String_internal()"))
if(location == HEAD or location == ALL) then
pos, length, size = bin.unpack("<SS", data, pos)
pos, length = unmarshall_int16(data, pos, false)
pos, size = unmarshall_int16(data, pos, false)
pos, str = unmarshall_ptr(HEAD, data, pos, unmarshall_unicode, {false})
end
@@ -1702,7 +1791,7 @@ end
-- (for nothing, since this isn't a pointer), or ALL (for the data). Generally, unless the
-- referent_id is split from the data (for example, in an array), you will want
-- ALL.
--@param data The data packet being processed.
--@param data The data being processed.
--@param pos The position within <code>data</code>.
--@param result This is required when unmarshalling the BODY section, which always comes after
-- unmarshalling the HEAD. It is the result returned for this parameter during the
@@ -1779,7 +1868,7 @@ end
-- (for nothing, since this isn't a pointer), or ALL (for the data). Generally, unless the
-- referent_id is split from the data (for example, in an array), you will want
-- ALL.
--@param data The data packet being processed.
--@param data The data being processed.
--@param pos The position within <code>data</code>.
--@param result This is required when unmarshalling the BODY section, which always comes after
-- unmarshalling the HEAD. It is the result returned for this parameter during the
@@ -1853,7 +1942,7 @@ end
-- (for nothing, since this isn't a pointer), or ALL (for the data). Generally, unless the
-- referent_id is split from the data (for example, in an array), you will want
-- ALL.
--@param data The data packet being processed.
--@param data The data being processed.
--@param pos The position within <code>data</code>.
--@param result This is required when unmarshalling the BODY section, which always comes after
-- unmarshalling the HEAD. It is the result returned for this parameter during the
@@ -1866,7 +1955,9 @@ local function unmarshall_lsa_StringLarge(location, data, pos, result)
stdnse.print_debug(4, string.format("MSRPC: Entering unmarshall_lsa_StringLarge()"))
if(location == HEAD or location == ALL) then
pos, length, size = bin.unpack("<SS", data, pos)
pos, length = unmarshall_int16(data, pos, false)
pos, size = unmarshall_int16(data, pos, false)
pos, str = unmarshall_ptr(HEAD, data, pos, unmarshall_unicode, {false})
end
@@ -1891,7 +1982,7 @@ end
-- (for nothing, since this isn't a pointer), or ALL (for the data). Generally, unless the
-- referent_id is split from the data (for example, in an array), you will want
-- ALL.
--@param data The data packet being processed.
--@param data The data being processed.
--@param pos The position within <code>data</code>.
--@param result This is required when unmarshalling the BODY section, which always comes after
-- unmarshalling the HEAD. It is the result returned for this parameter during the
@@ -1928,7 +2019,7 @@ end
-- } lsa_RefDomainList;
--</code>
--
--@param data The data packet being processed.
--@param data The data being processed.
--@param pos The position within <code>data</code>.
--@return (pos, result) The new position in <code>data</code>, and a table representing the datatype.
function unmarshall_lsa_RefDomainList(data, pos)
@@ -1950,7 +2041,7 @@ end
---Unmarshall a pointer to a <code>lsa_RefDomainList</code>. See the <code>unmarshall_lsa_RefDomainList</code> function
-- for more information.
--
--@param data The data packet being processed.
--@param data The data being processed.
--@param pos The position within <code>data</code>.
--@return (pos, result) The new position in <code>data</code>, and a table representing the datatype.
function unmarshall_lsa_RefDomainList_ptr(data, pos)
@@ -1972,7 +2063,7 @@ end
-- } lsa_TransSidArray2;
--</code>
--
--@param data The data packet being processed.
--@param data The data being processed.
--@param pos The position within <code>data</code>.
--@return (pos, result) The new position in <code>data</code>, and a table representing the datatype.
function unmarshall_lsa_TransSidArray2(data, pos)
@@ -2139,7 +2230,7 @@ end
---Unmarshall a <code>lsa_TransNameArray2</code> structure. See the <code>marshall_lsa_TransNameArray2</code> for more
-- information.
--
--@param data The data packet being processed.
--@param data The data being processed.
--@param pos The position within <code>data</code>.
--@return (pos, result) The new position in <code>data</code>, and a table representing the datatype.
function unmarshall_lsa_TransNameArray2(data, pos)
@@ -2403,7 +2494,9 @@ function unmarshall_winreg_StringBuf(data, pos)
local str
stdnse.print_debug(4, string.format("MSRPC: Entering unmarshall_winreg_StringBuf()"))
pos, length, size = bin.unpack("<SS", data, pos)
pos, length = unmarshall_int16(data, pos, false)
pos, size = unmarshall_int16(data, pos, false)
pos, str = unmarshall_ptr(ALL, data, pos, unmarshall_unicode, {true})
stdnse.print_debug(4, string.format("MSRPC: Leaving unmarshall_winreg_StringBuf()"))
@@ -2819,7 +2912,7 @@ function unmarshall_srvsvc_NetShareCtr0(data, pos)
local result = {}
stdnse.print_debug(4, string.format("MSRPC: Entering unmarshall_srvsvc_NetShareCtr0()"))
pos, count = bin.unpack("<I", data, pos)
pos, count = unmarshall_int32(data, pos)
pos, result['array'] = unmarshall_ptr(ALL, data, pos, unmarshall_array, {count, unmarshall_srvsvc_NetShareInfo0, {}})
@@ -2972,7 +3065,7 @@ function unmarshall_srvsvc_NetShareCtr(data, pos)
local result
stdnse.print_debug(4, string.format("MSRPC: Entering unmarshall_srv_NetShareCtr()"))
pos, level = bin.unpack("<I", data, pos)
pos, level = unmarshall_int32(data, pos)
if(level == 0) then
pos, result = unmarshall_ptr(ALL, data, pos, unmarshall_srvsvc_NetShareCtr0, {})
@@ -3008,7 +3101,7 @@ end
--
--@param level The level to request. Different levels will return different results, but also require
-- different access levels to call.
--@param data The data packet being processed.
--@param data The data being processed.
--@param pos The position within <code>data</code>.
--@return (pos, result) The new position in <code>data</code>, and a table representing the datatype. This may be
-- <code>nil</code> if there was an error.
@@ -3155,7 +3248,7 @@ function unmarshall_srvsvc_NetSessCtr10(data, pos)
local result = {}
stdnse.print_debug(4, string.format("MSRPC: Entering unmarshall_srvsvc_NetSessCtr10()"))
pos, count = bin.unpack("<I", data, pos)
pos, count = unmarshall_int32(data, pos)
pos, result['array'] = unmarshall_ptr(ALL, data, pos, unmarshall_array, {count, unmarshall_srvsvc_NetSessInfo10, {}})
@@ -3200,7 +3293,7 @@ end
---Unmarshall the top-level NetShareCtr. This is a union; see the marshall function for more information.
--
--@param data The data packet being processed.
--@param data The data being processed.
--@param pos The position within <code>data</code>
--@return (pos, result) The new position in <code>data</code>, and a table representing the datatype. Can be
-- <code>nil</code> if there's an error.
@@ -3249,7 +3342,7 @@ end
--
-- Note that Wireshark (at least, the version I'm using, 1.0.3) gets this wrong, so be careful.
--
--@param data The data packet being processed.
--@param data The data being processed.
--@param pos The position within <code>data</code>
--@return (pos, result) The new position in <code>data</code>, and a table representing the datatype.
function unmarshall_srvsvc_Statistics(data, pos)
@@ -3283,7 +3376,7 @@ end
--
-- See <code>unmarshall_srvsvc_Statistics</code> for more information.
--
--@param data The data packet being processed.
--@param data The data being processed.
--@param pos The position within <code>data</code>
--@return (pos, result) The new position in <code>data</code>, and a table representing the datatype.
function unmarshall_srvsvc_Statistics_ptr(data, pos)
@@ -3615,7 +3708,7 @@ end
-- (for nothing, since this isn't a pointer), or ALL (for the data). Generally, unless the
-- referent_id is split from the data (for example, in an array), you will want
-- ALL.
--@param data The data packet being processed.
--@param data The data being processed.
--@param pos The position within <code>data</code>.
--@param result This is required when unmarshalling the BODY section, which always comes after
-- unmarshalling the HEAD. It is the result returned for this parameter during the
@@ -3651,7 +3744,7 @@ end
-- } samr_SamArray;
--</code>
--
--@param data The data packet being processed.
--@param data The data being processed.
--@param pos The position within <code>data</code>.
--@return (pos, result) The new position in <code>data</code>, and a table representing the datatype.
function unmarshall_samr_SamArray(data, pos)
@@ -3668,7 +3761,7 @@ end
---Unmarshall a pointer to a <code>samr_SamArray</code> type. See <code>unmarshall_samr_SamArray</code> for
-- more information.
--
--@param data The data packet being processed.
--@param data The data being processed.
--@param pos The position within <code>data</code>.
--@return (pos, result) The new position in <code>data</code>, and a table representing the datatype.
function unmarshall_samr_SamArray_ptr(data, pos)
@@ -3698,7 +3791,7 @@ end
-- (for nothing, since this isn't a pointer), or ALL (for the data). Generally, unless the
-- referent_id is split from the data (for example, in an array), you will want
-- ALL.
--@param data The data packet being processed.
--@param data The data being processed.
--@param pos The position within <code>data</code>.
--@param result This is required when unmarshalling the BODY section, which always comes after
-- unmarshalling the HEAD. It is the result returned for this parameter during the
@@ -3740,7 +3833,7 @@ end
-- } samr_DispInfoGeneral;
--</code>
--
--@param data The data packet being processed.
--@param data The data being processed.
--@param pos The position within <code>data</code>.
--@return (pos, result) The new position in <code>data</code>, and a table representing the datatype.
function unmarshall_samr_DispInfoGeneral(data, pos)
@@ -3767,7 +3860,7 @@ end
-- } samr_DispInfo;
--</code>
--
--@param data The data packet being processed.
--@param data The data being processed.
--@param pos The position within <code>data</code>.
--@return (pos, result) The new position in <code>data</code>, and a table representing the datatype. It may also return
-- <code>nil</code>, if there was an error.
@@ -3802,7 +3895,7 @@ end
-- } samr_DomInfo1;
--</code>
--
--@param data The data packet being processed.
--@param data The data being processed.
--@param pos The position within <code>data</code>.
--@return (pos, result) The new position in <code>data</code>, and a table representing the datatype.
function unmarshall_samr_DomInfo1(data, pos)
@@ -3828,7 +3921,7 @@ end
-- } samr_DomInfo8;
--</code>
--
--@param data The data packet being processed.
--@param data The data being processed.
--@param pos The position within <code>data</code>.
--@return (pos, result) The new position in <code>data</code>, and a table representing the datatype.
function unmarshall_samr_DomInfo8(data, pos)
@@ -3852,7 +3945,7 @@ end
-- } samr_DomInfo12;
--</code>
--
--@param data The data packet being processed.
--@param data The data being processed.
--@param pos The position within <code>data</code>.
--@return (pos, result) The new position in <code>data</code>, and a table representing the datatype.
function unmarshall_samr_DomInfo12(data, pos)
@@ -3886,7 +3979,7 @@ end
-- } samr_DomainInfo;
--</code>
--
--@param data The data packet being processed.
--@param data The data being processed.
--@param pos The position within <code>data</code>.
--@return (pos, result) The new position in <code>data</code>, and a table representing the datatype. May return
-- <code>nil</code> if there was an error.
@@ -3915,7 +4008,7 @@ end
---Unmarshall a pointer to a <code>samr_DomainInfo</code>. See <code>unmarshall_samr_DomainInfo</code> for
-- more information.
--
--@param data The data packet being processed.
--@param data The data being processed.
--@param pos The position within <code>data</code>.
--@return (pos, result) The new position in <code>data</code>, and a table representing the datatype. May return
-- <code>nil</code> if there was an error.
@@ -3931,3 +4024,7 @@ end

View File

@@ -353,6 +353,9 @@ function do_nbstat(host)
rrlength = rrlength - 18
end
if(rrlength > 0) then
rrlength = rrlength - 1
end
pos, statistics = bin.unpack(string.format(">A%d", rrlength), result, pos)
-- Put it in the registry, in case anybody else needs it

View File

@@ -1,62 +0,0 @@
--- Debugging functions for Nmap scripts.
--
-- This module contains various handy functions for debugging. These should
-- never be used for actual results, only during testing.
--
-- @copyright Same as Nmap--See http://nmap.org/book/man-legal.html
local require = require
local type = type
local pairs = pairs
local nmap = require "nmap";
local stdnse = require "stdnse";
local EMPTY = {}; -- Empty constant table
module(... or "nmapdebug");
---Converts an arbitrary data type into a string. Will recursively convert
-- tables. This can be very useful for debugging.
--
--@param data The data to convert.
--@param indent (optional) The number of times to indent the line. Default
-- is 0.
--@return A string representation of a data, will be one or more full lines.
function tostr(data, indent)
local str = ""
if(indent == nil) then
indent = 0
end
-- Check the type
if(type(data) == "nil") then
str = str .. (" "):rep(indent) .. data .. "\n"
elseif(type(data) == "string") then
str = str .. (" "):rep(indent) .. data .. "\n"
elseif(type(data) == "number") then
str = str .. (" "):rep(indent) .. data .. "\n"
elseif(type(data) == "boolean") then
if(data == true) then
str = str .. "true"
else
str = str .. "false"
end
elseif(type(data) == "table") then
local i, v
for i, v in pairs(data) do
-- Check for a table in a table
if(type(v) == "table") then
str = str .. (" "):rep(indent) .. i .. ":\n"
str = str .. tostr(v, indent + 2)
else
str = str .. (" "):rep(indent) .. i .. ": " .. tostr(v, 0)
end
end
else
stdnse.print_debug(1, "Error: unknown data type: %s", type(data))
end
return str
end

119
nselib/nsedebug.lua Normal file
View File

@@ -0,0 +1,119 @@
-- Debugging functions for Nmap scripts.
--
-- This module contains various handy functions for debugging. These should
-- never be used for actual results, only during testing.
--
-- @copyright Same as Nmap--See http://nmap.org/book/man-legal.html
require "stdnse"
local EMPTY = {}; -- Empty constant table
module(... or "nsedebug", package.seeall);
---Converts an arbitrary data type into a string. Will recursively convert
-- tables. This can be very useful for debugging.
--
--@param data The data to convert.
--@param indent (optional) The number of times to indent the line. Default
-- is 0.
--@return A string representation of a data, will be one or more full lines.
function tostr(data, indent)
local str = ""
if(indent == nil) then
indent = 0
end
-- Check the type
if(type(data) == "nil") then
str = str .. (" "):rep(indent) .. data .. "\n"
elseif(type(data) == "string") then
str = str .. (" "):rep(indent) .. data .. "\n"
elseif(type(data) == "number") then
str = str .. (" "):rep(indent) .. data .. "\n"
elseif(type(data) == "boolean") then
if(data == true) then
str = str .. "true"
else
str = str .. "false"
end
elseif(type(data) == "table") then
local i, v
for i, v in pairs(data) do
-- Check for a table in a table
if(type(v) == "table") then
str = str .. (" "):rep(indent) .. i .. ":\n"
str = str .. tostr(v, indent + 2)
else
str = str .. (" "):rep(indent) .. i .. ": " .. tostr(v, 0)
end
end
else
stdnse.print_debug(1, "Error: unknown data type: %s", type(data))
end
return str
end
-- Print out a string in hex, for debugging.
function print_hex(str)
-- Prints out the full lines
for line=1, string.len(str)/16, 1 do
io.write(string.format("%08x ", (line - 1) * 16))
-- Loop through the string, printing the hex
for char=1, 16, 1 do
ch = string.byte(str, ((line - 1) * 16) + char)
io.write(string.format("%02x ", ch))
end
io.write(" ")
-- Loop through the string again, this time the ascii
for char=1, 16, 1 do
ch = string.byte(str, ((line - 1) * 16) + char)
if ch < 0x20 or ch > 0x7f then
ch = string.byte(".", 1)
end
io.write(string.format("%c", ch))
end
io.write("\n")
end
-- Prints out the final, partial line
line = math.floor((string.len(str)/16)) + 1
io.write(string.format("%08x ", (line - 1) * 16))
for char=1, string.len(str) % 16, 1 do
ch = string.byte(str, ((line - 1) * 16) + char)
io.write(string.format("%02x ", ch))
end
io.write(string.rep(" ", 16 - (string.len(str) % 16)));
io.write(" ")
for char=1, string.len(str) % 16, 1 do
ch = string.byte(str, ((line - 1) * 16) + char)
if ch < 0x20 or ch > 0x7f then
ch = string.byte(".", 1)
end
io.write(string.format("%c", ch))
end
-- Print out the length
io.write(string.format("\n Length: %d [0x%x]\n", string.len(str), string.len(str)))
end
---Print out a stacktrace. The stacktrace will naturally include this function call.
function print_stack()
local thread = coroutine.running()
local trace = debug.traceback(thread);
if trace ~= "stack traceback:" then
print(thread, "\n", trace, "\n");
end
end

View File

@@ -66,9 +66,9 @@
-- If that's successful, <code>SMB_COM_SESSION_SETUP_ANDX</code> is sent. It is essentially the logon
-- packet, where the username, domain, and password are sent to the server for verification.
-- The username and password are generally picked up from the program parameters, which are
-- set when running a script, or from the registry [TODO: Where?], which are set by other
-- scripts. However, they can also be passed as parameters to the function, which will
-- override any other username/password set.
-- set when running a script, or from the registry (<code>nmap.registry[<ip>]['smbaccounts'])
-- where it can be set by other scripts (for example, smb-brute.nse). However, they can also
-- be passed as parameters to the function, which will override any other username/password set.
--
-- If a username is set without a password, then a NULL session is started. If a login fails,
-- we attempt to log in as the 'GUEST' account with a blank password. If that fails, we try
@@ -151,6 +151,8 @@ status_names = {}
local mutexes = setmetatable({}, {__mode = "k"});
--local debug_mutex = nmap.mutex("SMB-DEBUG")
local TIMEOUT = 5000
---Returns the mutex that should be used by the current connection. This mutex attempts
-- to use the name, first, then falls back to the IP if no name was returned.
--
@@ -220,7 +222,7 @@ end
function get_status_name(status)
if(status_names[status] == nil) then
-- If the name wasn't found in the array, do a linear search on it (TODO: Why is this happening??)
-- If the name wasn't found in the array, do a linear search on it (TODO: Why is this happening??) (XXX: I think I fixed this)
for i, v in pairs(status_names) do
if(v == status) then
return i
@@ -380,6 +382,7 @@ function start_raw(host, port)
local status, err
local socket = nmap.new_socket()
socket:set_timeout(TIMEOUT)
status, err = socket:connect(host.ip, port, "tcp")
if(status == false) then
@@ -486,6 +489,7 @@ function start_netbios(host, port, name)
);
stdnse.print_debug(3, "SMB: Connecting to %s", host.ip)
socket:set_timeout(TIMEOUT)
status, err = socket:connect(host.ip, port, "tcp")
if(status == false) then
socket:close()
@@ -499,7 +503,7 @@ function start_netbios(host, port, name)
socket:close()
return false, "SMB: Failed to send: " .. err
end
socket:set_timeout(5000)
socket:set_timeout(TIMEOUT)
-- Receive the session response
stdnse.print_debug(3, "SMB: Receiving NetBIOS session response")
@@ -509,6 +513,9 @@ function start_netbios(host, port, name)
return false, "SMB: Failed to close socket: " .. result
end
pos, result, flags, length = bin.unpack(">CCS", result)
if(length == nil) then
return false, "SMB: ERROR: Ran off the end of SMB packet; likely due to server truncation [1]"
end
-- Check for a position session response (0x82)
if result == 0x82 then
@@ -747,6 +754,11 @@ local function smb_encode_header(smb, command)
-- the server that we deal in ASCII.
local flags2 = bit.bor(0x4000, 0x0040, 0x0001) -- SMB_FLAGS2_32BIT_STATUS | SMB_FLAGS2_IS_LONG_NAME | SMB_FLAGS2_KNOWS_LONG_NAMES
-- TreeID should never ever be 'nil', but it seems to happen once in awhile so print an error
if(smb['tid'] == nil) then
return false, string.format("SMB: ERROR: TreeID value was set to nil on host %s", smb['ip'])
end
local header = bin.pack("<CCCCCICSSLSSSSS",
sig:byte(1), -- Header
sig:byte(2), -- Header
@@ -824,10 +836,10 @@ function smb_read(smb)
local status, result
local pos, netbios_length, length, header, parameter_length, parameters, data_length, data
stdnse.print_debug(3, "SMB: Receiving SMB packet")
-- Receive the response -- we make sure to receive at least 4 bytes, the length of the NetBIOS length
-- [TODO] set the timeout length per jah's strategy:
-- http://seclists.org/nmap-dev/2008/q3/0702.html
smb['socket']:set_timeout(5000)
smb['socket']:set_timeout(TIMEOUT)
status, result = smb['socket']:receive_bytes(4);
-- Make sure the connection is still alive
@@ -839,7 +851,7 @@ function smb_read(smb)
-- The NetBIOS header is 24 bits, big endian
pos, netbios_length = bin.unpack(">I", result)
if(netbios_length == nil) then
return false, "SMB: SMB server didn't comply with standards (incorrect data was returned) [1]"
return false, "SMB: ERROR: Ran off the end of SMB packet; likely due to server truncation [2]"
end
-- Make the length 24 bits
netbios_length = bit.band(netbios_length, 0x00FFFFFF)
@@ -850,10 +862,9 @@ function smb_read(smb)
-- If we haven't received enough bytes, try and get the rest (fragmentation!)
if(#result < length) then
local new_result
status, new_result = smb['socket']:receive_bytes(netbios_length)
stdnse.print_debug(1, "SMB: Received a fragmented packet, attempting to receive the rest of it (got %d bytes, need %d)", #result, length)
status, new_result = smb['socket']:receive_bytes(netbios_length - #result)
-- Make sure the connection is still alive
if(status ~= true) then
return false, "SMB: Failed to receive bytes: " .. result
@@ -872,31 +883,31 @@ function smb_read(smb)
-- The header is 32 bytes.
pos, header = bin.unpack("<A32", result, pos)
if(header == nil) then
return false, "SMB: SMB server didn't comply with standards (incorrect data was returned) [2]"
return false, "SMB: ERROR: Ran off the end of SMB packet; likely due to server truncation [3]"
end
-- The parameters length is a 1-byte value.
pos, parameter_length = bin.unpack("<C", result, pos)
if(parameter_length == nil) then
return false, "SMB: SMB server didn't comply with standards (incorrect data was returned) [3]"
return false, "SMB: ERROR: Ran off the end of SMB packet; likely due to server truncation [4]"
end
-- Double the length parameter, since parameters are two-byte values.
pos, parameters = bin.unpack(string.format("<A%d", parameter_length*2), result, pos)
if(parameters == nil) then
return false, "SMB: SMB server didn't comply with standards (incorrect data was returned) [4]"
return false, "SMB: ERROR: Ran off the end of SMB packet; likely due to server truncation [5]"
end
-- The data length is a 2-byte value.
pos, data_length = bin.unpack("<S", result, pos)
if(data_length == nil) then
return false, "SMB: SMB server didn't comply with standards (incorrect data was returned) [5]"
return false, "SMB: ERROR: Ran off the end of SMB packet; likely due to server truncation [6]"
end
-- Read that many bytes of data.
pos, data = bin.unpack(string.format("<A%d", data_length), result, pos)
if(data == nil) then
return false, "SMB: SMB server didn't comply with standards (incorrect data was returned) [6]"
return false, "SMB: ERROR: Ran off the end of SMB packet; likely due to server truncation [7]"
end
stdnse.print_debug(3, "SMB: Received %d bytes", string.len(result))
@@ -967,15 +978,18 @@ function negotiate_protocol(smb)
-- Check if we fell off the packet (if that happened, the last parameter will be nil)
if(mid == nil) then
return false, "SMB: SMB server didn't comply with standards (incorrect data was returned) [7]"
return false, "SMB: ERROR: Ran off the end of SMB packet; likely due to server truncation [8]"
end
-- Parse the parameter section
pos, dialect = bin.unpack("<S", parameters)
if(dialect == nil) then
return false, "SMB: ERROR: Ran off the end of SMB packet; likely due to server truncation [9]"
end
-- Check if we ran off the packet
if(dialect == nil) then
return false, "SMB: SMB server didn't comply with standards (incorrect data was returned) [8]"
return false, "SMB: ERROR: Ran off the end of SMB packet; likely due to server truncation [10]"
end
-- Check if the server didn't like our requested protocol
if(dialect ~= 0) then
@@ -983,7 +997,9 @@ function negotiate_protocol(smb)
end
pos, security_mode, max_mpx, max_vc, max_buffer, max_raw_buffer, session_key, capabilities, time, timezone, key_length = bin.unpack("<CSSIIIILsC", parameters, pos)
if(capabilities == nil) then
return false, "SMB: ERROR: Ran off the end of SMB packet; likely due to server truncation [11]"
end
-- Some broken implementations of SMB don't send these variables
if(time == nil) then
time = 0
@@ -1010,6 +1026,9 @@ function negotiate_protocol(smb)
-- Data section
-- This one's a little messier, because I don't appear to have unicode support
pos, server_challenge = bin.unpack(string.format("<A%d", key_length), data)
if(server_challenge == nil) then
return false, "SMB: ERROR: Ran off the end of SMB packet; likely due to server truncation [12]"
end
-- Get the domain as a Unicode string
local ch, dummy
@@ -1017,18 +1036,26 @@ function negotiate_protocol(smb)
server = ""
pos, ch, dummy = bin.unpack("<CC", data, pos)
if(dummy == nil) then
return false, "SMB: ERROR: Ran off the end of SMB packet; likely due to server truncation [13]"
end
while ch ~= nil and ch ~= 0 do
domain = domain .. string.char(ch)
pos, ch, dummy = bin.unpack("<CC", data, pos)
if(dummy == nil) then
return false, "SMB: ERROR: Ran off the end of SMB packet; likely due to server truncation [14]"
end
end
-- Get the server name as a Unicode string
-- Note: This can be nil, Samba leaves this off
pos, ch, dummy = bin.unpack("<CC", data, pos)
while ch ~= nil and ch ~= 0 do
server = server .. string.char(ch)
pos, ch, dummy = bin.unpack("<CC", data, pos)
end
-- Fill out smb variables
smb['security_mode'] = security_mode
smb['max_mpx'] = max_mpx
@@ -1074,15 +1101,24 @@ end
---Determines which username is going to be used, based on the function parameters, the registry, and
-- the nmap arguments (in that order).
--
--@param ip The ip address, used when reading from the registry
--@param username [optional] The function parameter version, which will override all others if set.
--@return The highest priority username that's set.
-- TODO: Get username from the registry
local function get_username(username)
local function get_username(ip, username)
if(username ~= nil) then
stdnse.print_debug(2, "SMB: Using username passed as a parameter: %s", username)
else
if(nmap.registry.args.smbusername ~= nil) then
if(nmap.registry[ip] ~= nil and nmap.registry[ip]['smbaccounts'] ~= nil and next(nmap.registry[ip]['smbaccounts']) ~= nil) then
if(nmap.registry[ip]['smbaccounts']['administrator'] ~= nil) then
-- If we found an administrator account, use it first
username = "administrator"
else
-- Otherwise, use whichever is first
username, _ = next(nmap.registry[ip]['smbaccounts'])
end
stdnse.print_debug(2, "SMB: Using username found in the registry: %s", username)
elseif(nmap.registry.args.smbusername ~= nil) then
username = nmap.registry.args.smbusername
stdnse.print_debug(2, "SMB: Using username passed as an nmap parameter (smbusername): %s", username)
elseif(nmap.registry.args.smbuser ~= nil) then
@@ -1125,6 +1161,7 @@ end
--
-- The output passwords are hashed based on the hash type.
--
--@param ip The ip address of the host, used for registry lookups.
--@param username The username, which is used for v2 passwords.
--@param domain The username, which is used for v2 passwords.
--@param password [optional] The overriding password.
@@ -1132,7 +1169,7 @@ end
--@param challenge The server challenge.
--@param hash_type The way in which to hash the password.
--@return (lm_response, ntlm_response) The two strings that can be sent directly back to the server.
local function get_password_response(username, domain, password, password_hash, challenge, hash_type)
local function get_password_response(ip, username, domain, password, password_hash, challenge, hash_type)
local lm_hash = nil
local ntlm_hash = nil
@@ -1141,7 +1178,10 @@ local function get_password_response(username, domain, password, password_hash,
if(password ~= nil) then
stdnse.print_debug(2, "SMB: Using password passed as a parameter")
else
if(nmap.registry.args.smbpassword ~= nil) then
if(nmap.registry[ip] ~= nil and nmap.registry[ip]['smbaccounts'] ~= nil and nmap.registry[ip]['smbaccounts'][username] ~= nil) then
password = nmap.registry[ip]['smbaccounts'][username]
stdnse.print_debug(2, "SMB: Using password found in the registry")
elseif(nmap.registry.args.smbpassword ~= nil) then
password = nmap.registry.args.smbpassword
stdnse.print_debug(2, "SMB: Using password passed as an nmap parameter (smbpassword)")
elseif(nmap.registry.args.smbpass ~= nil) then
@@ -1253,24 +1293,25 @@ end
--@param password [optional] The password to override with.
--@param password_hash [optional] The password hash to override this (shouldn't be used along with password).
--@param hash_type [optional] The type of hash to override with.
--@param use_default [optional] If set, will attempt anonymous/guest. Default: true.
--@return An array of tables, each of which contain a 'username', 'domain', 'lanman', 'ntlm'.
local function get_logins(ip, challenge, username, domain, password, password_hash, hash_type)
local function get_logins(ip, challenge, username, domain, password, password_hash, hash_type, use_default)
local response = {}
-- If we don't have OpenSSL, don't bother with any of this
if(have_ssl == true) then
-- We choose *one* username to try, here. First, see if the user set a username
-- in the function parameters, then in the registry [TODO], then as an nmap
-- in the function parameters, then in the registry, then as an nmap
-- parameter, then disable it.
-- If a username was found, look for a domain and password
username = get_username(username)
username = get_username(ip, username)
domain = get_domain(domain)
hash_type = get_hash_type(hash_type)
if(username ~= nil) then
lm_response, ntlm_response = get_password_response(username, domain, password, password_hash, challenge, hash_type)
lm_response, ntlm_response = get_password_response(ip, username, domain, password, password_hash, challenge, hash_type)
if(lm_response ~= nil and ntlm_response ~= nil) then
local data = {}
@@ -1293,10 +1334,10 @@ local function get_logins(ip, challenge, username, domain, password, password_ha
stdnse.print_debug(1, "SMB: ERROR: Couldn't find OpenSSL library, only checking Guest and/or Anonymous accounts")
end
-- Check if we're using default accounts
if(use_default == nil or use_default == true) then
local data
-- Add guest account
stdnse.print_debug(2, "SMB: Going to try guest account before attempting anonymous")
data = {}
data['username'] = 'guest'
data['domain'] = ''
@@ -1311,6 +1352,7 @@ local function get_logins(ip, challenge, username, domain, password, password_ha
data['lanman'] = ''
data['ntlm'] = ''
response[#response + 1] = data
end
return response
end
@@ -1333,21 +1375,23 @@ end
--@param domain [optional] Overrides the domain to use.
--@param password [optional] Overrides the password to use. Will use Nmap parameters or registry by default.
--@param hash_type [optional] Overrides the hash type to use (can be v1, LM, NTLM, LMv2, v2). Default is 'NTLM'.
--@param use_default [optional] If set, will attempt anonymous/guest. Default: true.
--@param log_errors [optional] If set, will display login. Default: true.
--@return (status, result) If status is false, result is an error message. Otherwise, result is nil and the following
-- elements are added to the smb table:
-- * 'uid' The UserID for the session
-- * 'is_guest' If set, the username wasn't found so the user was automatically logged in as the guest account
-- * 'os' The operating system
-- * 'lanmanager' The servers's LAN Manager
function start_session(smb, username, domain, password, password_hash, hash_type)
function start_session(smb, username, domain, password, password_hash, hash_type, use_default, log_errors)
local i
local status, result
local header, parameters, data
local pos
local header1, header2, header3, header4, command, status, flags, flags2, pid_high, signature, unused, tid, pid, uid, mid
local andx_command, andx_reserved, andx_offset, action
local os, lanmanager, domain
local logins = get_logins(smb['ip'], smb['server_challenge'], username, domain, password, password_hash, hash_type)
local os, lanmanager
local logins = get_logins(smb['ip'], smb['server_challenge'], username, domain, password, password_hash, hash_type, use_default)
header = smb_encode_header(smb, command_codes['SMB_COM_SESSION_SETUP_ANDX'])
@@ -1358,7 +1402,7 @@ function start_session(smb, username, domain, password, password_hash, hash_type
0xFF, -- ANDX -- no further commands
0x00, -- ANDX -- Reserved (0)
0x0000, -- ANDX -- next offset
0x1000, -- Max buffer size
0xFFFF, -- Max buffer size
0x0001, -- Max multiplexes
0x0000, -- Virtual circuit num
smb['session_key'], -- The session key
@@ -1394,7 +1438,7 @@ function start_session(smb, username, domain, password, password_hash, hash_type
pos, header1, header2, header3, header4, command, status, flags, flags2, pid_high, signature, unused, tid, pid, uid, mid = bin.unpack("<CCCCCICSSlSSSSS", header)
if(mid == nil) then
return false, "SMB: SMB server didn't comply with standards (incorrect data was returned) [9]"
return false, "SMB: ERROR: Ran off the end of SMB packet; likely due to server truncation [17]"
end
-- Check if we're successful
@@ -1403,11 +1447,14 @@ function start_session(smb, username, domain, password, password_hash, hash_type
-- Parse the parameters
pos, andx_command, andx_reserved, andx_offset, action = bin.unpack("<CCSS", parameters)
if(action == nil) then
return false, "SMB: SMB server didn't comply with standards (incorrect data was returned) [10]"
return false, "SMB: ERROR: Ran off the end of SMB packet; likely due to server truncation [18]"
end
-- Parse the data
pos, os, lanmanager, domain = bin.unpack("<zzz", data)
if(domain == nil) then
return false, "SMB: ERROR: Ran off the end of SMB packet; likely due to server truncation [19]"
end
-- Fill in the smb object and smb string
smb['uid'] = uid
@@ -1423,21 +1470,27 @@ function start_session(smb, username, domain, password, password_hash, hash_type
end
-- Check if they were logged in as a guest
if(log_errors == nil or log_errors == true) then
if(smb['is_guest'] == 1) then
stdnse.print_debug(1, "SMB: Login as %s\\%s failed, but was given guest access (username may be wrong, or system may only allow guest)", logins[i]['domain'], string_or_blank(logins[i]['username']))
else
stdnse.print_debug(1, "SMB: Login as %s\\%s succeeded", logins[i]['domain'], string_or_blank(logins[i]['username']))
end
end
return true
else
-- This username failed, print a warning and keep going
stdnse.print_debug(1, "SMB: Login as %s\\%s failed (%s), trying next login", logins[i]['domain'], string_or_blank(logins[i]['username']), get_status_name(status))
if(log_errors == nil or log_errors == true) then
stdnse.print_debug(1, "SMB: Login as %s\\%s failed (%s)", logins[i]['domain'], string_or_blank(logins[i]['username']), get_status_name(status))
end
end
end
if(log_errors == nil or log_errors == true) then
stdnse.print_debug(1, "SMB: ERROR: All logins failed, sorry it didn't work out!")
end
return false, get_status_name(status)
end
@@ -1492,7 +1545,7 @@ function tree_connect(smb, path)
-- Check if we were allowed in
pos, header1, header2, header3, header4, command, status, flags, flags2, pid_high, signature, unused, tid, pid, uid, mid = bin.unpack("<CCCCCICSSlSSSSS", header)
if(mid == nil) then
return false, "SMB: SMB server didn't comply with standards (incorrect data was returned) [11]"
return false, "SMB: ERROR: Ran off the end of SMB packet; likely due to server truncation [20]"
end
if(status ~= 0) then
@@ -1531,7 +1584,7 @@ function tree_disconnect(smb)
-- Check if there was an error
pos, header1, header2, header3, header4, command, status, flags, flags2, pid_high, signature, unused, tid, pid, uid, mid = bin.unpack("<CCCCCICSSlSSSSS", header)
if(mid == nil) then
return false, "SMB: SMB server didn't comply with standards (incorrect data was returned) [12]"
return false, "SMB: ERROR: Ran off the end of SMB packet; likely due to server truncation [21]"
end
if(status ~= 0) then
return false, get_status_name(status)
@@ -1577,7 +1630,7 @@ function logoff(smb)
-- Check if there was an error
pos, header1, header2, header3, header4, command, status, flags, flags2, pid_high, signature, unused, tid, pid, uid, mid = bin.unpack("<CCCCCICSSlSSSSS", header)
if(mid == nil) then
return false, "SMB: SMB server didn't comply with standards (incorrect data was returned) [13]"
return false, "SMB: ERROR: Ran off the end of SMB packet; likely due to server truncation [22]"
end
if(status ~= 0) then
return false, get_status_name(status)
@@ -1613,14 +1666,14 @@ function create_file(smb, path)
string.len(path), -- Path length
0x00000016, -- Create flags
0x00000000, -- Root FID
0x0002019F, -- Access mask
0x02000000, -- Access mask
0x0000000000000000, -- Allocation size
0x00000000, -- File attributes
0x00000003, -- Share attributes
0x00000001, -- Disposition
0x00400040, -- Create options
0x00000000, -- Create options
0x00000002, -- Impersonation
0x01 -- Security flags
0x00 -- Security flags
)
data = bin.pack("z", path)
@@ -1641,7 +1694,7 @@ function create_file(smb, path)
-- Check if we were allowed in
pos, header1, header2, header3, header4, command, status, flags, flags2, pid_high, signature, unused, tid, pid, uid, mid = bin.unpack("<CCCCCICSSlSSSSS", header)
if(mid == nil) then
return false, "SMB: SMB server didn't comply with standards (incorrect data was returned) [14]"
return false, "SMB: ERROR: Ran off the end of SMB packet; likely due to server truncation [23]"
end
if(status ~= 0) then
return false, get_status_name(status)
@@ -1650,7 +1703,7 @@ function create_file(smb, path)
-- Parse the parameters
pos, andx_command, andx_reserved, andx_offset, oplock_level, fid, create_action, created, last_access, last_write, last_change, attributes, allocation_size, end_of_file, filetype, ipc_state, is_directory = bin.unpack("<CCSCSILLLLILLSSC", parameters)
if(is_directory == nil) then
return false, "SMB: SMB server didn't comply with standards (incorrect data was returned) [15]"
return false, "SMB: ERROR: Ran off the end of SMB packet; likely due to server truncation [24]"
end
-- Fill in the smb string
@@ -1669,9 +1722,165 @@ function create_file(smb, path)
smb['is_directory'] = is_directory
return true
end
--- This sends a SMB request to read from a file (or a pipe).
--
--@param smb The SMB object associated with the connection
--@param offset The offset to read from (ignored if it's a pipe)
--@param count The maximum number of bytes to read
--@return (status, result) If status is false, result is an error message. Otherwise, result is a table
-- containing a lot of different elements, the most important one being 'fid', the handle to the opened file.
function read_file(smb, offset, count)
local header, parameters, data
local pos
local header1, header2, header3, header4, command, status, flags, flags2, pid_high, signature, unused, pid, mid
local andx_command, andx_reserved, andx_offset
local remaining, data_compaction_mode, reserved_1, data_length_low, data_offset, data_length_high, reserved_2, reserved_3
local response = {}
header = smb_encode_header(smb, command_codes['SMB_COM_READ_ANDX'])
parameters = bin.pack("<CCSSISSISI",
0xFF, -- ANDX no further commands
0x00, -- ANDX reserved
0x0000, -- ANDX offset
smb['fid'], -- FID
offset, -- Offset
count, -- Max count low
count, -- Min count
0xFFFFFFFF, -- Reserved
0, -- Remaining
0x00000000 -- High offset
)
data = ""
-- Send the create file
stdnse.print_debug(2, "SMB: Sending SMB_COM_READ_ANDX")
result, err = smb_send(smb, header, parameters, data)
if(result == false) then
return false, err
end
-- Read the result
status, header, parameters, data = smb_read(smb)
if(status ~= true) then
return false, header
end
-- Check if we were allowed in
pos, header1, header2, header3, header4, command, status, flags, flags2, pid_high, signature, unused, tid, pid, uid, mid = bin.unpack("<CCCCCICSSlSSSSS", header)
if(mid == nil) then
return false, "SMB: ERROR: Ran off the end of SMB packet; likely due to server truncation [25]"
end
if(status ~= 0) then
return false, get_status_name(status)
end
-- Parse the parameters
pos, andx_command, andx_reserved, andx_offset, remaining, data_compaction_mode, reserved_1, data_length_low, data_offset, data_length_high, reserved_2, reserved_3 = bin.unpack("<CCSSSSSSISI", parameters)
if(reserved_3 == nil) then
return false, "SMB: ERROR: Ran off the end of SMB packet; likely due to server truncation [26]"
end
response['remaining'] = remaining
response['data_length'] = bit.bor(data_length_low, bit.lshift(data_length_high, 16))
-- data_start is the offset of the beginning of the data section -- we use this to calculate where the read data lives
local data_start = #header + 1 + #parameters + 2
if(data_offset < data_start) then
return false, "SMB: Start of data isn't in data section"
end
-- Figure out the offset into the data section
data_offset = data_offset - data_start
-- Make sure we don't run off the edge of the packet
if(data_offset + response['data_length'] > #data) then
return false, "SMB: Data returned runs off the end of the packet"
end
-- Pull the data string out of the data
response['data'] = string.sub(data, data_offset + 1, data_offset + response['data_length'])
return true, response
end
--- This sends a SMB request to write to a file (or a pipe).
--
--@param smb The SMB object associated with the connection
--@param write_data The data to write
--@param offset The offset to write it to (ignored for pipes)
--@param path The path of the file or pipe to open
--@return (status, result) If status is false, result is an error message. Otherwise, result is a table
-- containing a lot of different elements, the most important one being 'fid', the handle to the opened file.
function write_file(smb, write_data, offset)
local header, parameters, data
local pos
local header1, header2, header3, header4, command, status, flags, flags2, pid_high, signature, unused, pid, mid
local andx_command, andx_reserved, andx_offset
local response = {}
header = smb_encode_header(smb, command_codes['SMB_COM_WRITE_ANDX'])
parameters = bin.pack("<CCSSIISSSSSI",
0xFF, -- ANDX no further commands
0x00, -- ANDX reserved
0x0000, -- ANDX offset
smb['fid'], -- FID
offset, -- Offset
0xFFFFFFFF, -- Reserved
0x0008, -- Write mode (Message start, don't write raw, don't return remaining, don't write through
#write_data,-- Remaining
0x0000, -- Data length high
#write_data,-- Data length low -- TODO: set this properly (to the 2-byte value)
0x003F, -- Data offset
0x00000000 -- Data offset high
)
data = write_data
-- Send the create file
stdnse.print_debug(2, "SMB: Sending SMB_COM_WRITE_ANDX")
result, err = smb_send(smb, header, parameters, data)
if(result == false) then
return false, err
end
-- Read the result
status, header, parameters, data = smb_read(smb)
if(status ~= true) then
return false, header
end
-- Check if we were allowed in
pos, header1, header2, header3, header4, command, status, flags, flags2, pid_high, signature, unused, tid, pid, uid, mid = bin.unpack("<CCCCCICSSlSSSSS", header)
if(mid == nil) then
return false, "SMB: ERROR: Ran off the end of SMB packet; likely due to server truncation [27]"
end
if(status ~= 0) then
return false, get_status_name(status)
end
-- Parse the parameters
pos, andx_command, andx_reserved, andx_offset, count_low, remaining, count_high, reserved = bin.unpack("<CCSSSSS", parameters)
if(reserved == nil) then
return false, "SMB: ERROR: Ran off the end of SMB packet; likely due to server truncation [28]"
end
response['count_low'] = count_low
response['remaining'] = remaining
response['count_high'] = count_high
response['reserved'] = count_reserved
return true, response
end
---This is the core of making MSRPC calls. It sends out a MSRPC packet with the given parameters and data.
-- Don't confuse these parameters and data with SMB's concepts of parameters and data -- they are completely
-- different. In fact, these parameters and data are both sent in the SMB packet's 'data' section.
@@ -1742,7 +1951,7 @@ function send_transaction(smb, func, function_parameters, function_data)
-- Check if it worked
pos, header1, header2, header3, header4, command, status, flags, flags2, pid_high, signature, unused, tid, pid, uid, mid = bin.unpack("<CCCCCICSSlSSSSS", header)
if(mid == nil) then
return false, "SMB: SMB server didn't comply with standards (incorrect data was returned) [16]"
return false, "SMB: ERROR: Ran off the end of SMB packet; likely due to server truncation [29]"
end
if(status ~= 0) then
if(status_names[status] == nil) then
@@ -1755,7 +1964,7 @@ function send_transaction(smb, func, function_parameters, function_data)
-- Parse the parameters
pos, total_word_count, total_data_count, reserved1, parameter_count, parameter_offset, parameter_displacement, data_count, data_offset, data_displacement, setup_count, reserved2 = bin.unpack("<SSSSSSSSSCC", parameters)
if(reserved2 == nil) then
return false, "SMB: SMB server didn't comply with standards (incorrect data was returned) [17]"
return false, "SMB: ERROR: Ran off the end of SMB packet; likely due to server truncation [30]"
end
-- Convert the parameter/data offsets into something more useful (the offset into the data section)
@@ -1868,11 +2077,12 @@ status_codes =
NT_STATUS_WERR_ACCESS_DENIED = 0x00000005,
NT_STATUS_WERR_INVALID_NAME = 0x0000007b,
NT_STATUS_WERR_UNKNOWN_LEVEL = 0x0000007c,
NT_STATUS_WERR_MORE_DATA = 0x000000ea,
NT_STATUS_NO_MORE_ITEMS = 0x00000103,
NT_STATUS_MORE_ENTRIES = 0x00000105,
NT_STATUS_SOME_NOT_MAPPED = 0x00000107,
DOS_STATUS_UNKNOWN_ERROR = 0x00010001,
DOS_STATUS_UNKNOWN_ERROR_2 = 0x00010002,
DOS_STATUS_NONSPECIFIC_ERROR = 0x00010002,
DOS_STATUS_DIRECTORY_NOT_FOUND = 0x00030001,
DOS_STATUS_ACCESS_DENIED = 0x00050001,
DOS_STATUS_INVALID_FID = 0x00060001,

View File

@@ -63,6 +63,8 @@ Entry{ category = "version", filename = "skypev2-version.nse" }
Entry{ category = "intrusive", filename = "smb-check-vulns.nse" }
Entry{ category = "discovery", filename = "smb-enum-domains.nse" }
Entry{ category = "intrusive", filename = "smb-enum-domains.nse" }
Entry{ category = "discovery", filename = "smb-enum-processes.nse" }
Entry{ category = "intrusive", filename = "smb-enum-processes.nse" }
Entry{ category = "discovery", filename = "smb-enum-sessions.nse" }
Entry{ category = "intrusive", filename = "smb-enum-sessions.nse" }
Entry{ category = "discovery", filename = "smb-enum-shares.nse" }

View File

@@ -61,6 +61,9 @@ author = "Ron Bowes"
copyright = "Ron Bowes"
license = "Same as Nmap--See http://nmap.org/book/man-legal.html"
categories = {"intrusive"}
-- Set the runlevel to >2 so this runs last (so if it DOES crash something, it doesn't
-- till other scans have had a chance to run)
runlevel = 2
require 'msrpc'
require 'smb'
@@ -181,6 +184,7 @@ end
action = function(host)
local status, result
local response = " \n"
local found = false

View File

@@ -57,6 +57,7 @@ hostrule = function(host)
end
action = function(host)
local response = " \n"
local status, smbstate
local i, j

View File

@@ -0,0 +1,282 @@
description = [[
Pulls a list of processes from the remote server over SMB (using the remote registry service and
HKEY_PERFORMANCE_DATA).
Requires Administrator access.
WARNING: I have experienced crashes in regsvc.exe while making registry calls against a fully patched Windows
2000 system; I've fixed the issue that caused it, but there's no guarantee that it (or a similar vuln in the
same code) won't show up again.
]]
---
-- @usage
-- nmap --script smb-enum-processes.nse -p445 <host>
-- sudo nmap -sU -sS --script smb-enum-processes.nse -p U:137,T:139 <host>
--
---
-- @output
-- Host script results:
-- |_ smb-enum-processes: Idle, _Total, System, wmiprvse, VMwareUser, VMwareTray, smss, csrss, winlogon, services, lsass, logon.scr, spoolsv, msdtc, VMwareService, svchost, alg, explorer
-- --
-- Host script results:
-- | smb-enum-processes:
-- | Idle
-- | | PID: 0, Parent: 0 [Idle]
-- | | Priority: 0
-- | |_Thread Count: 1, Handle Count: 0
-- | System
-- | | PID: 4, Parent: 0 [Idle]
-- | | Priority: 8
-- | |_Thread Count: 48, Handle Count: 392
-- | VMwareUser
-- | | PID: 212, Parent: 1832 [explorer]
-- | | Priority: 8
-- | |_Thread Count: 1, Handle Count: 45
-- | VMwareTray
-- | | PID: 240, Parent: 1832 [explorer]
-- | | Priority: 8
-- | |_Thread Count: 1, Handle Count: 41
-- | smss
-- | | PID: 252, Parent: 4 [System]
-- | | Priority: 11
-- | |_Thread Count: 3, Handle Count: 19
-- | csrss
-- | | PID: 300, Parent: 252 [smss]
-- | | Priority: 13
-- | |_Thread Count: 10, Handle Count: 347
-- | winlogon
-- | | PID: 324, Parent: 252 [smss]
-- | | Priority: 13
-- | |_Thread Count: 18, Handle Count: 513
-- | services
-- | | PID: 372, Parent: 324 [winlogon]
-- | | Priority: 9
-- | |_Thread Count: 17, Handle Count: 275
-- | lsass
-- | | PID: 384, Parent: 324 [winlogon]
-- | | Priority: 9
-- | |_Thread Count: 29, Handle Count: 415
-- | logon.scr
-- | | PID: 868, Parent: 324 [winlogon]
-- | | Priority: 4
-- | |_Thread Count: 1, Handle Count: 22
-- ...
--
-- @args smb* This script supports the <code>smbusername</code>,
-- <code>smbpassword</code>, <code>smbhash</code>, and <code>smbtype</code>
-- script arguments of the <code>smb</code> module.
-----------------------------------------------------------------------
author = "Ron Bowes"
copyright = "Ron Bowes"
license = "Same as Nmap--See http://nmap.org/book/man-legal.html"
categories = {"discovery","intrusive"}
require "bin"
require 'msrpc'
require 'msrpcperformance'
require 'smb'
require 'stdnse'
-- Strings used to separate processes from one another.
local separators = {
first = "-+-";
last = " `-";
middle = " +-";
only = "---";
}
function psl_add (psl, ps)
-- Add process.
psl[ps.pid] = ps
-- Add dummy parent if no real one exists.
if psl[ps.ppid] == nil then
psl[ps.ppid] = {
name = 'Unknown';
pid = ps.ppid;
ppid = ps.ppid;
}
end
end
function psl_mode (list, i)
local mode
-- Decide connector for process.
if table.maxn(list) == 1 then
mode = "only"
elseif i == 1 then
mode = "first"
elseif i == table.maxn(list) then
mode = "last"
else
mode = "middle"
end
return mode
end
function psl_print (psl)
local result = ""
-- Find how many root processes there are.
local roots = {}
for i,ps in pairs(psl) do
if psl[ps.ppid] == nil or ps.ppid == ps.pid then
table.insert(roots, i)
end
end
table.sort(roots)
-- Create vertical sibling link.
local bars = {}
if table.maxn(roots) ~= 1 then
table.insert(bars, 2)
end
-- Print out each root of the tree.
for i,root in ipairs(roots) do
local mode = psl_mode(roots, i)
result = result .. psl_tree(psl, root, 0, bars, mode)
end
return result
end
function psl_tree (psl, pid, column, bars, mode)
local ps = psl[pid]
-- Delete vertical sibling link.
if mode == 'last' then
table.remove(bars)
end
-- Print lead-in.
local prefix = ''
if mode == 'middle' or mode == 'last' then
prefix = '\n'
local i = 1
for j = 1, column do
if table.maxn(bars) >= i and
bars[i] == j then
prefix = prefix .. '|'
i = i + 1
else
prefix = prefix .. ' '
end
end
end
-- Format process itself.
output = separators[mode] .. ps.name .. '(' .. ps.pid .. ')'
column = column + #output
local result = prefix .. output
-- Find process' children.
local children = {}
for child_pid,child in pairs(psl) do
if child_pid ~= pid and child.ppid == pid then
table.insert(children, child_pid)
end
end
table.sort(children)
-- Create vertical sibling link between children.
if table.maxn(children) > 1 then
table.insert(bars, column + 2)
end
-- Format process' children.
for i,pid in ipairs(children) do
local mode = psl_mode(children, i)
result = result .. psl_tree(psl, pid, column, bars, mode)
end
return result
end
hostrule = function(host)
return smb.get_port(host) ~= nil
end
action = function(host)
local status, result
local process
local response = " \n"
-- Get the process list
status, result = msrpcperformance.get_performance_data(host, "230")
if(status == false) then
if(nmap.debugging() > 0) then
return "ERROR: " .. result
else
return nil
end
end
-- Get the process table
process = result['Process']
-- for i, v in pairs(result['Processor']['_Total']) do
-- io.write(string.format("i = %s\n", i))
-- end
-- Put the processes into an array, and sort them by process id
local names = {}
for i, v in pairs(process) do
if(i ~= '_Total') then
names[#names + 1] = i
end
end
table.sort(names, function (a, b) return process[a]['ID Process'] < process[b]['ID Process'] end)
-- Put the processes into an array indexed by process id and with a value equal to the name (so we can look it up
-- easily when we need to)
local process_id = {}
for i, v in pairs(process) do
process_id[v['ID Process']] = i
end
if(nmap.verbosity() == 1) then
local psl = {}
for i,name in ipairs(names) do
if(name ~= '_Total') then
psl_add(psl, {
name = name;
pid = process[name]['ID Process'];
ppid = process[name]['Creating Process ID'];
})
end
end
response = ' \n' .. psl_print(psl)
elseif(nmap.verbosity() > 0) then
for i = 1, #names, 1 do
local name = names[i]
if(name ~= '_Total') then
local parent = process_id[process[name]['Creating Process ID']]
if(parent == nil) then
parent = "n/a"
end
-- response = response .. string.format("%6d %24s (Parent: %24s, Priority: %4d, Threads: %4d, Handles: %4d)\n", process[name]['ID Process'], name, parent, process[name]['Priority Base'], process[name]['Thread Count'], process[name]['Handle Count'])
response = response .. string.format("%s [%d]\n", name, process[name]['ID Process'])
response = response .. string.format("| Parent: %s [%s]\n", process[name]['Creating Process ID'], parent)
response = response .. string.format("| Priority: %s, Thread Count: %s, Handle Count: %s\n", process[name]['Priority Base'], process[name]['Thread Count'], process[name]['Handle Count'])
end
end
else
response = stdnse.strjoin(", ", names)
end
return response
end

View File

@@ -247,6 +247,7 @@ local function winreg_enum_rids(host)
end
action = function(host)
local response = " \n"
local status1, status2

View File

@@ -191,7 +191,9 @@ function check_shares(host, shares)
stdnse.print_debug(3, "EnumShares: Access was denied")
denied_shares[#denied_shares + 1] = shares[i]
else
stdnse.print_debug(3, "ERROR: EnumShares: Share didn't pan out: %s", err)
-- If we're here, an error that we weren't prepared for came up.
smb.stop(smbstate)
return false, string.format("Error while checking shares: %s", err)
end
else
-- Add it to allowed shares
@@ -244,6 +246,7 @@ local function get_share_info(host, name)
end
action = function(host)
local enum_result
local result, shared
local response = " \n"

View File

@@ -9,10 +9,10 @@ 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).
* Stealthier (requires one packet/user account, whereas LSA uses at least 10
packets while SAMR uses half that; 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.
@@ -58,16 +58,11 @@ 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.
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 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. An ideal solution might be to continue
doing groups of 5, but wait until we get 5-10 consecutive empty groups before giving up.
To do this, the script breaks users into groups of RIDs based on the <code>LSA_GROUPSIZE</code>
constant. All members of this group are checked simultaneously, and the responses recorded.
When a series of empty groups are found (<code>LSA_MINEMPTY</code> groups, specifically),
the scan ends. As long as you are getting a few groups with active accounts, the scan will
continue.
Before attempting this conversion, the SID of the server has to be determined.
The SID is determined by doing the reverse operation; that is, by converting a name into
@@ -156,284 +151,8 @@ hostrule = function(host)
return smb.get_port(host) ~= nil
end
---Attempt to enumerate users through SAMR methods. See the file description for more information.
--
--@param host The host object.
--@return Status (true or false).
--@return Array of user tables (if status is true) or an an error string (if
--status is false). Each user table contains the fields <code>name</code>,
--<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()")
local smbstate
local bind_result, connect4_result, enumdomains_result
local connect_handle
local status, smbstate
local response = {}
-- Create the SMB session
status, smbstate = msrpc.start_smb(host, msrpc.SAMR_PATH)
if(status == false) then
return false, smbstate
end
-- Bind to SAMR service
status, bind_result = msrpc.bind(smbstate, msrpc.SAMR_UUID, msrpc.SAMR_VERSION, nil)
if(status == false) then
msrpc.stop_smb(smbstate)
return false, bind_result
end
-- Call connect4()
status, connect4_result = msrpc.samr_connect4(smbstate, host.ip)
if(status == false) then
msrpc.stop_smb(smbstate)
return false, connect4_result
end
-- Save the connect_handle
connect_handle = connect4_result['connect_handle']
-- Call EnumDomains()
status, enumdomains_result = msrpc.samr_enumdomains(smbstate, connect_handle)
if(status == false) then
msrpc.stop_smb(smbstate)
return false, enumdomains_result
end
-- If no domains were returned, go back with an error
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['sam']['entries'], 1 do
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
local domain_handle
local opendomain_result, querydisplayinfo_result
-- Call LookupDomain()
status, lookupdomain_result = msrpc.samr_lookupdomain(smbstate, connect_handle, domain)
if(status == false) then
msrpc.stop_smb(smbstate)
return false, lookupdomain_result
end
-- Save the sid
sid = lookupdomain_result['sid']
-- Call OpenDomain()
status, opendomain_result = msrpc.samr_opendomain(smbstate, connect_handle, sid)
if(status == false) then
msrpc.stop_smb(smbstate)
return false, opendomain_result
end
-- Save the domain handle
domain_handle = opendomain_result['domain_handle']
-- 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
end -- Checking for 'builtin'
end -- Domain loop
-- Close the connect handle
msrpc.samr_close(smbstate, connect_handle)
-- Stop the SAMR SMB
msrpc.stop_smb(smbstate)
stdnse.print_debug(3, "Leaving enum_samr()")
return true, response
end
---Attempt to enumerate users through LSA methods. See the file description for more information.
--
--@param host The host object.
--@return Status (true or false).
--@return Array of user tables (if status is true) or an an error string (if
--status is false). Each user table contains the fields <code>name</code>,
--<code>domain</code>, and <code>rid</code>.
local function enum_lsa(host)
local smbstate
local status
local response = {}
stdnse.print_debug(3, "Entering enum_lsa()")
-- Create the SMB session
status, smbstate = msrpc.start_smb(host, msrpc.LSA_PATH)
if(status == false) then
return false, smbstate
end
-- Bind to LSA service
status, bind_result = msrpc.bind(smbstate, msrpc.LSA_UUID, msrpc.LSA_VERSION, nil)
if(status == false) then
msrpc.stop_smb(smbstate)
return false, bind_result
end
-- Open the LSA policy
status, openpolicy2_result = msrpc.lsa_openpolicy2(smbstate, host.ip)
if(status == false) then
msrpc.stop_smb(smbstate)
return false, openpolicy2_result
end
-- Start with some common names, as well as the name returned by the negotiate call
-- Vista doesn't like a 'null' after the server name, so fix that (TODO: the way I strip the null here feels hackish, is there a better way?)
names = {"administrator", "guest", "test", smbstate['domain'], string.sub(smbstate['server'], 1, #smbstate['server'] - 1) }
-- Get the server's name from nbstat
local result, server_name = netbios.get_server_name(host.ip)
if(result == true) then
names[#names + 1] = server_name
end
-- Get the logged in user from nbstat
local result, user_name = netbios.get_user_name(host.ip)
if(result == true) then
names[#names + 1] = user_name
end
-- Look up the names, if any are valid than the server's SID will be returned
status, lookupnames2_result = msrpc.lsa_lookupnames2(smbstate, openpolicy2_result['policy_handle'], names)
if(status == false) then
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']['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
sids[#sids + 1] = sid .. "-" .. j
end
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
-- 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['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
end
end
-- Now do groups of 5 users, until we get past 1100 and have an empty group
repeat
local used_names = 0
local sids = {}
for j = start, start + 4, 1 do
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'], 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['names']['names'], 1 do
if(lookupsids2_result['names']['names'][j]['sid_type'] ~= "SID_NAME_UNKNOWN") then
local result = {}
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
end
end
end
-- Go to the next set of RIDs
start = start + 5
until status == false or (used_names == 0 and start > 1100)
end
-- Close the handle
msrpc.lsa_close(smbstate, openpolicy2_result['policy_handle'])
msrpc.stop_smb(smbstate)
stdnse.print_debug(3, "Leaving enum_lsa()")
return true, response
end
action = function(host)
local i, j
local samr_status = false
local lsa_status = false
@@ -450,7 +169,7 @@ action = function(host)
-- Try enumerating through LSA first. Since LSA provides less information, we want the
-- SAMR result to overwrite it.
if(do_lsa) then
lsa_status, lsa_result = enum_lsa(host)
lsa_status, lsa_result = msrpc.lsa_enum_users(host)
if(lsa_status == false) then
if(nmap.debugging() > 0) then
response = response .. "ERROR: Couldn't enumerate through LSA: " .. lsa_result .. "\n"
@@ -468,7 +187,7 @@ action = function(host)
-- Try enumerating through SAMR
if(do_samr) then
samr_status, samr_result = enum_samr(host)
samr_status, samr_result = msrpc.samr_enum_users(host)
if(samr_status == false) then
if(nmap.debugging() > 0) then
response = response .. "ERROR: Couldn't enumerate through SAMR: " .. samr_result .. "\n"
@@ -523,6 +242,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'])) end
if(nmap.verbosity() > 1) then