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-*- # 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 o A problem that caused OS detection to fail for most hosts in a
certain was fixed. It happened when sending raw Ethernet frames certain was fixed. It happened when sending raw Ethernet frames
(by default on Windows or on other platforms with --send-eth) to (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()) { while (!running_scripts.empty()) {
current = *(running_scripts.begin()); current = *(running_scripts.begin());
if (current.rr.host->timedOut(&now)) if (current.rr.host->timedOut(&now)) {
state = LUA_ERRRUN; printf("thread (%p) timed out\n", (void *) current.thread);
SCRIPT_ENGINE_TRY(process_finalize(L, current.registry_idx));
continue;
}
else else
state = lua_resume(current.thread, current.resume_arguments); state = lua_resume(current.thread, current.resume_arguments);

View File

@@ -198,3 +198,4 @@ aaliyah1
zxcvbnm1 zxcvbnm1
young1 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. :) -- 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 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 --- 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 -- 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 -- 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 0x0048, -- Frag length
0x0000, -- Auth length 0x0000, -- Auth length
0x41414141, -- Call ID (I use 'AAAA' because it's easy to recognize) 0x41414141, -- Call ID (I use 'AAAA' because it's easy to recognize)
0x10b8, -- Max transmit frag MAX_FRAGMENT, -- Max transmit frag
0x10b8, -- Max receive frag MAX_FRAGMENT, -- Max receive frag
0x00000000, -- Assoc group 0x00000000, -- Assoc group
0x01, -- Number of items 0x01, -- Number of items
0x00, -- Padding/alignment 0x00, -- Padding/alignment
@@ -200,7 +220,12 @@ function bind(smbstate, interface_uuid, interface_version, transfer_syntax)
2 -- Syntax version 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 if(status ~= true) then
return false, result return false, result
end end
@@ -213,6 +238,9 @@ function bind(smbstate, interface_uuid, interface_version, transfer_syntax)
-- Extract the first part from the resposne -- 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) 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 -- Check if the packet tyep was a fault
if(result['packet_type'] == 0x03) then -- MSRPC_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. -- 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) 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 -- Read the secondary address
pos, result['secondary_address'] = bin.unpack(string.format("<A%d", result['secondary_address_length']), data, pos) 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 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 -- Read the number of results
pos, result['num_results'] = bin.unpack("<C", data, pos) 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 pos = pos + ((4 - ((pos - 1) % 4)) % 4); -- Alignment
-- Verify we got back what we expected -- 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 -- Read in the last bits
pos, result['ack_result'], result['align'], result['transfer_syntax'], result['syntax_version'] = bin.unpack("<SSA16I", data, pos) 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 return true, result
end 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 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. --@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 --@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 function call_function(smbstate, opnum, arguments)
local i local i
local status, result local status, result
local parameters, data local parameters, data
local pos, align local pos, align
local result local result
local first = true
local is_first, is_last
data = bin.pack("<CCCC>I<SSIISSA", data = bin.pack("<CCCC>I<SSIISSA",
0x05, -- Version (major) 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) 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 -- 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 if(status ~= true) then
return false, result return false, result
end end
@@ -312,6 +365,23 @@ local function call_function(smbstate, opnum, arguments)
-- Extract the first part from the resposne -- 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) 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 -- Check if there was an error
if(result['packet_type'] == 0x03) then -- MSRPC_FAULT 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 if(result['auth_length'] ~= 0) then
return false, "MSRPC call returned an 'auth length', which we don't know how to deal with" return false, "MSRPC call returned an 'auth length', which we don't know how to deal with"
end 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 if(result['packet_type'] ~= 0x02) then
return false, "MSRPC call returned an unexpected packet type (not RESPONSE)" return false, "MSRPC call returned an unexpected packet type (not RESPONSE)"
end end
@@ -335,13 +402,22 @@ local function call_function(smbstate, opnum, arguments)
-- Extract some more -- Extract some more
pos, result['alloc_hint'], result['context_id'], result['cancel_count'], align = bin.unpack("<ISCC", data, pos) 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 -- 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'])) stdnse.print_debug(3, "MSRPC: Function call successful, %d bytes of returned argumenst", string.len(result['arguments']))
return true, result return true, result
end end
---A proxy to a <code>msrpctypes</code> function that converts a ShareType to an english string. ---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 smbstate The SMB state table
--@param domain_handle The domain handle, returned by <code>samr_opendomain</code> --@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 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 --@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 -- 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, -- '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. -- '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 i, j
local status, result local status, result
local arguments local arguments
local pos, align 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, -- 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. -- I put a little loop here and grab the names individually.
stdnse.print_debug(2, "MSRPC: Calling QueryDisplayInfo(%d) [%s]", index, smbstate['ip']) 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) arguments = arguments .. msrpctypes.marshall_int32(index)
-- [in] uint32 max_entries, -- [in] uint32 max_entries,
arguments = arguments .. msrpctypes.marshall_int32(1) arguments = arguments .. msrpctypes.marshall_int32(count)
-- [in] uint32 buf_size, -- [in] uint32 buf_size,
arguments = arguments .. msrpctypes.marshall_int32(0) arguments = arguments .. msrpctypes.marshall_int32(0x7FFFFFFF)
-- [out] uint32 total_size, -- [out] uint32 total_size,
-- [out] uint32 returned_size, -- [out] uint32 returned_size,
@@ -1110,7 +1191,6 @@ function samr_querydisplayinfo(smbstate, domain_handle, index)
-- [out] uint32 returned_size, -- [out] uint32 returned_size,
pos, result['returned_size'] = msrpctypes.unmarshall_int32(arguments, pos) pos, result['returned_size'] = msrpctypes.unmarshall_int32(arguments, pos)
-- [out,switch_is(level)] samr_DispInfo info -- [out,switch_is(level)] samr_DispInfo info
pos, result['info'] = msrpctypes.unmarshall_samr_DispInfo(arguments, pos) pos, result['info'] = msrpctypes.unmarshall_samr_DispInfo(arguments, pos)
if(pos == nil) then if(pos == nil) then
@@ -1966,10 +2046,10 @@ function winreg_queryvalue(smbstate, handle, value)
arguments = arguments .. msrpctypes.marshall_winreg_Type_ptr("REG_NONE") arguments = arguments .. msrpctypes.marshall_winreg_Type_ptr("REG_NONE")
-- [in,out,size_is(*size),length_is(*length)] uint8 *data, -- [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, -- [in,out] uint32 *size,
arguments = arguments .. msrpctypes.marshall_int32_ptr(520) arguments = arguments .. msrpctypes.marshall_int32_ptr(1000000)
-- [in,out] uint32 *length -- [in,out] uint32 *length
arguments = arguments .. msrpctypes.marshall_int32_ptr(0) arguments = arguments .. msrpctypes.marshall_int32_ptr(0)
@@ -1991,9 +2071,7 @@ function winreg_queryvalue(smbstate, handle, value)
-- [in,ref] policy_handle *handle, -- [in,ref] policy_handle *handle,
-- [in] winreg_String value_name, -- [in] winreg_String value_name,
-- [in,out] winreg_Type *type, -- [in,out] winreg_Type *type,
pos, pos, result['type'] = msrpctypes.unmarshall_winreg_Type_ptr(arguments, pos)
pos = pos + 4
pos, result['type'] = msrpctypes.unmarshall_winreg_Type(arguments, pos)
-- [in,out,size_is(*size),length_is(*length)] uint8 *data, -- [in,out,size_is(*size),length_is(*length)] uint8 *data,
pos, result['data'] = msrpctypes.unmarshall_int8_array_ptr(arguments, pos) 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']) _, 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 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) _, result['value'] = msrpctypes.unicode_to_string(result['data'], 1, #result['data'] / 2)
elseif(result['type'] == "REG_BINARY") then
result['value'] = result['data']
else else
stdnse.print_debug("MSRPC ERROR: Unknown type: %s\n\n", result['type']) stdnse.print_debug("MSRPC ERROR: Unknown type: %s\n\n", result['type'])
result['value'] = 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['length'] = msrpctypes.unmarshall_int32_ptr(arguments, pos)
pos, result['return'] = msrpctypes.unmarshall_int32(arguments, pos) pos, result['return'] = msrpctypes.unmarshall_int32(arguments, pos)
if(result['return'] == nil) then if(result['return'] == nil) then
return false, "Read off the end of the packet (winreg.queryvalue)" return false, "Read off the end of the packet (winreg.queryvalue)"
end end
@@ -2075,3 +2156,348 @@ function winreg_closekey(smbstate, handle)
return true, result return true, result
end 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 -- (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 -- referent_id is split from the data (for example, in an array), you will want
-- ALL. -- ALL.
--@param data The data packet being processed. --@param data The data being processed.
--@param pos The position within <code>data</code> --@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 --@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. -- 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 if(location == HEAD or location == ALL) then
local referent_id local referent_id
pos, referent_id = bin.unpack("<I", data, pos) 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(location == HEAD) then
if(referent_id == 0) then if(referent_id == 0) then
result = false 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 -- 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. -- 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 pos The position within <code>data</code>.
--@param count The number of elements in the array. --@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; --@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 end
pos, max_count = bin.unpack("<I", data, pos) 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. -- Unmarshall the header, which will be referent_ids and base types.
for i = 1, count, 1 do for i = 1, count, 1 do
@@ -483,7 +490,7 @@ end
-- func(location, data, pos, result, <args>) -- func(location, data, pos, result, <args>)
--</code> --</code>
-- --
--@param data The data packet being processed. --@param data The data being processed.
--@param pos The position within <code>data</code>. --@param pos The position within <code>data</code>.
--@param func The function to call to unmarshall each parameter. Has to match a specific prototype; --@param func The function to call to unmarshall each parameter. Has to match a specific prototype;
-- see the function comment. -- see the function comment.
@@ -594,6 +601,9 @@ function unmarshall_unicode(data, pos, do_null)
end end
pos, max, offset, actual = bin.unpack("<III", data, pos) 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) pos, str = unicode_to_string(data, pos, actual, do_null, true)
@@ -604,7 +614,7 @@ end
---Unmarshall a pointer to a unicode string. ---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 pos The position within <code>data</code>.
--@param do_null [optional] Assumes a null is at the end of the string. Default false. --@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. --@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. --- 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>. --@param pos The position within <code>data</code>.
--@return (pos, int64) The new position, and the value. --@return (pos, int64) The new position, and the value.
function unmarshall_int64(data, pos) function unmarshall_int64(data, pos)
@@ -703,6 +713,9 @@ function unmarshall_int64(data, pos)
stdnse.print_debug(4, string.format("MSRPC: Entering unmarshall_int64()")) stdnse.print_debug(4, string.format("MSRPC: Entering unmarshall_int64()"))
pos, value = bin.unpack("<l", data, pos) 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()")) stdnse.print_debug(4, string.format("MSRPC: Leaving unmarshall_int64()"))
return pos, value return pos, value
@@ -710,7 +723,7 @@ end
--- Unmarshall an int32. See <code>marshall_int32</code> for more information. --- 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>. --@param pos The position within <code>data</code>.
--@return (pos, int32) The new position, and the value. --@return (pos, int32) The new position, and the value.
function unmarshall_int32(data, pos) function unmarshall_int32(data, pos)
@@ -718,6 +731,9 @@ function unmarshall_int32(data, pos)
stdnse.print_debug(4, string.format("MSRPC: Entering unmarshall_int32()")) stdnse.print_debug(4, string.format("MSRPC: Entering unmarshall_int32()"))
pos, value = bin.unpack("<I", data, pos) 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()")) stdnse.print_debug(4, string.format("MSRPC: Leaving unmarshall_int32()"))
return pos, value return pos, value
@@ -735,6 +751,9 @@ function unmarshall_int16(data, pos, pad)
stdnse.print_debug(4, string.format("MSRPC: Entering unmarshall_int16()")) stdnse.print_debug(4, string.format("MSRPC: Entering unmarshall_int16()"))
pos, value = bin.unpack("<S", data, pos) 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 if(pad == nil or pad == true) then
pos = pos + 2 pos = pos + 2
@@ -757,6 +776,9 @@ function unmarshall_int8(data, pos, pad)
stdnse.print_debug(4, string.format("MSRPC: Entering unmarshall_int8()")) stdnse.print_debug(4, string.format("MSRPC: Entering unmarshall_int8()"))
pos, value = bin.unpack("<C", data, pos) 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 if(pad == nil or pad == true) then
pos = pos + 3 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()")) stdnse.print_debug(4, string.format("MSRPC: Entering unmarshall_int8_array()"))
pos, max, offset, actual = bin.unpack("<III", data, pos) 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) 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) -- Do the alignment (note the "- 1", it's there because of 1-based arrays)
if(pad == nil or pad == true) then 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()")) stdnse.print_debug(4, string.format("MSRPC: Entering unmarshall_NTTIME()"))
pos, time = bin.unpack("<L", data, pos) 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 if(time ~= 0) then
time = (time / 10000000) - 11644473600 time = (time / 10000000) - 11644473600
@@ -1035,6 +1067,36 @@ function unmarshall_NTTIME_ptr(data, pos)
return pos, time return pos, time
end 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 ---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 -- 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. -- microseconds. This function converts the value to seconds and returns it.
@@ -1143,6 +1205,7 @@ end
--@param pos The position within the data. --@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 --@param table The table to use for lookups. The keys should be the names, and the values should be
-- the numbers. -- the numbers.
--@return (pos, array) The new position, and a table representing the enumeration values.
local function unmarshall_Enum32_array(data, pos, table) local function unmarshall_Enum32_array(data, pos, table)
local array = {} local array = {}
local i, v local i, v
@@ -1161,6 +1224,23 @@ local function unmarshall_Enum32_array(data, pos, table)
return pos, array return pos, array
end 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()")) 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) 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()")) stdnse.print_debug(4, string.format("MSRPC: Leaving unmarshall_guid()"))
return pos, guid return pos, guid
@@ -1237,7 +1320,7 @@ function unmarshall_policy_handle(data, pos)
local policy_handle = {} local policy_handle = {}
stdnse.print_debug(4, string.format("MSRPC: Entering unmarshall_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) pos, policy_handle['uuid'] = unmarshall_guid(data, pos)
stdnse.print_debug(4, string.format("MSRPC: Leaving unmarshall_policy_handle()")) stdnse.print_debug(4, string.format("MSRPC: Leaving unmarshall_policy_handle()"))
@@ -1260,7 +1343,7 @@ end
-- } dom_sid; -- } dom_sid;
--</code> --</code>
-- --
--@param data The data packet being processed. --@param data The data being processed.
--@param pos The position within <code>data</code>. --@param pos The position within <code>data</code>.
--@return (pos, result) The new position in <code>data</code>, and a table representing the datatype. --@return (pos, result) The new position in <code>data</code>, and a table representing the datatype.
function unmarshall_dom_sid2(data, pos) function unmarshall_dom_sid2(data, pos)
@@ -1269,16 +1352,20 @@ function unmarshall_dom_sid2(data, pos)
-- Read the SID from the packet -- Read the SID from the packet
local sid = {} local sid = {}
pos, sid['count'] = bin.unpack("<I", data, pos) pos, sid['count'] = unmarshall_int32(data, pos)
pos, sid['sid_rev_num'] = bin.unpack("<C", data, pos) pos, sid['sid_rev_num'] = unmarshall_int8(data, pos, false)
pos, sid['num_auths'] = bin.unpack("<C", data, pos) 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) -- 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) 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['authority'] = bit.bor(bit.lshift(sid['authority_high'], 32), sid['authority_low'])
sid['sub_auths'] = {} sid['sub_auths'] = {}
for i = 1, sid['num_auths'], 1 do 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 end
-- Convert the SID to a string -- 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 ---Unmarshall a pointer to a <code>dom_sid2</code> struct. See the <code>unmarshall_dom_sid2</code> function
-- for more information. -- for more information.
-- --
--@param data The data packet being processed. --@param data The data being processed.
--@param pos The position within <code>data</code>. --@param pos The position within <code>data</code>.
--@return (pos, result) The new position in <code>data</code>, and a table representing the datatype. --@return (pos, result) The new position in <code>data</code>, and a table representing the datatype.
function unmarshall_dom_sid2_ptr(data, pos) 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()")) stdnse.print_debug(4, string.format("MSRPC: Entering unmarshall_lsa_String_internal()"))
if(location == HEAD or location == ALL) then 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}) pos, str = unmarshall_ptr(HEAD, data, pos, unmarshall_unicode, {false})
end end
@@ -1702,7 +1791,7 @@ end
-- (for nothing, since this isn't a pointer), or ALL (for the data). Generally, unless the -- (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 -- referent_id is split from the data (for example, in an array), you will want
-- ALL. -- ALL.
--@param data The data packet being processed. --@param data The data being processed.
--@param pos The position within <code>data</code>. --@param pos The position within <code>data</code>.
--@param result This is required when unmarshalling the BODY section, which always comes after --@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 -- 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 -- (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 -- referent_id is split from the data (for example, in an array), you will want
-- ALL. -- ALL.
--@param data The data packet being processed. --@param data The data being processed.
--@param pos The position within <code>data</code>. --@param pos The position within <code>data</code>.
--@param result This is required when unmarshalling the BODY section, which always comes after --@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 -- 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 -- (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 -- referent_id is split from the data (for example, in an array), you will want
-- ALL. -- ALL.
--@param data The data packet being processed. --@param data The data being processed.
--@param pos The position within <code>data</code>. --@param pos The position within <code>data</code>.
--@param result This is required when unmarshalling the BODY section, which always comes after --@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 -- 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()")) stdnse.print_debug(4, string.format("MSRPC: Entering unmarshall_lsa_StringLarge()"))
if(location == HEAD or location == ALL) then 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}) pos, str = unmarshall_ptr(HEAD, data, pos, unmarshall_unicode, {false})
end end
@@ -1891,7 +1982,7 @@ end
-- (for nothing, since this isn't a pointer), or ALL (for the data). Generally, unless the -- (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 -- referent_id is split from the data (for example, in an array), you will want
-- ALL. -- ALL.
--@param data The data packet being processed. --@param data The data being processed.
--@param pos The position within <code>data</code>. --@param pos The position within <code>data</code>.
--@param result This is required when unmarshalling the BODY section, which always comes after --@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 -- unmarshalling the HEAD. It is the result returned for this parameter during the
@@ -1928,7 +2019,7 @@ end
-- } lsa_RefDomainList; -- } lsa_RefDomainList;
--</code> --</code>
-- --
--@param data The data packet being processed. --@param data The data being processed.
--@param pos The position within <code>data</code>. --@param pos The position within <code>data</code>.
--@return (pos, result) The new position in <code>data</code>, and a table representing the datatype. --@return (pos, result) The new position in <code>data</code>, and a table representing the datatype.
function unmarshall_lsa_RefDomainList(data, pos) 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 ---Unmarshall a pointer to a <code>lsa_RefDomainList</code>. See the <code>unmarshall_lsa_RefDomainList</code> function
-- for more information. -- for more information.
-- --
--@param data The data packet being processed. --@param data The data being processed.
--@param pos The position within <code>data</code>. --@param pos The position within <code>data</code>.
--@return (pos, result) The new position in <code>data</code>, and a table representing the datatype. --@return (pos, result) The new position in <code>data</code>, and a table representing the datatype.
function unmarshall_lsa_RefDomainList_ptr(data, pos) function unmarshall_lsa_RefDomainList_ptr(data, pos)
@@ -1972,7 +2063,7 @@ end
-- } lsa_TransSidArray2; -- } lsa_TransSidArray2;
--</code> --</code>
-- --
--@param data The data packet being processed. --@param data The data being processed.
--@param pos The position within <code>data</code>. --@param pos The position within <code>data</code>.
--@return (pos, result) The new position in <code>data</code>, and a table representing the datatype. --@return (pos, result) The new position in <code>data</code>, and a table representing the datatype.
function unmarshall_lsa_TransSidArray2(data, pos) 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 ---Unmarshall a <code>lsa_TransNameArray2</code> structure. See the <code>marshall_lsa_TransNameArray2</code> for more
-- information. -- information.
-- --
--@param data The data packet being processed. --@param data The data being processed.
--@param pos The position within <code>data</code>. --@param pos The position within <code>data</code>.
--@return (pos, result) The new position in <code>data</code>, and a table representing the datatype. --@return (pos, result) The new position in <code>data</code>, and a table representing the datatype.
function unmarshall_lsa_TransNameArray2(data, pos) function unmarshall_lsa_TransNameArray2(data, pos)
@@ -2403,7 +2494,9 @@ function unmarshall_winreg_StringBuf(data, pos)
local str local str
stdnse.print_debug(4, string.format("MSRPC: Entering unmarshall_winreg_StringBuf()")) 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}) pos, str = unmarshall_ptr(ALL, data, pos, unmarshall_unicode, {true})
stdnse.print_debug(4, string.format("MSRPC: Leaving unmarshall_winreg_StringBuf()")) stdnse.print_debug(4, string.format("MSRPC: Leaving unmarshall_winreg_StringBuf()"))
@@ -2819,7 +2912,7 @@ function unmarshall_srvsvc_NetShareCtr0(data, pos)
local result = {} local result = {}
stdnse.print_debug(4, string.format("MSRPC: Entering unmarshall_srvsvc_NetShareCtr0()")) 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, {}}) 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 local result
stdnse.print_debug(4, string.format("MSRPC: Entering unmarshall_srv_NetShareCtr()")) 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 if(level == 0) then
pos, result = unmarshall_ptr(ALL, data, pos, unmarshall_srvsvc_NetShareCtr0, {}) 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 --@param level The level to request. Different levels will return different results, but also require
-- different access levels to call. -- 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>. --@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 --@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. -- <code>nil</code> if there was an error.
@@ -3155,7 +3248,7 @@ function unmarshall_srvsvc_NetSessCtr10(data, pos)
local result = {} local result = {}
stdnse.print_debug(4, string.format("MSRPC: Entering unmarshall_srvsvc_NetSessCtr10()")) 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, {}}) 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. ---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> --@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 --@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. -- <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. -- 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> --@param pos The position within <code>data</code>
--@return (pos, result) The new position in <code>data</code>, and a table representing the datatype. --@return (pos, result) The new position in <code>data</code>, and a table representing the datatype.
function unmarshall_srvsvc_Statistics(data, pos) function unmarshall_srvsvc_Statistics(data, pos)
@@ -3283,7 +3376,7 @@ end
-- --
-- See <code>unmarshall_srvsvc_Statistics</code> for more information. -- 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> --@param pos The position within <code>data</code>
--@return (pos, result) The new position in <code>data</code>, and a table representing the datatype. --@return (pos, result) The new position in <code>data</code>, and a table representing the datatype.
function unmarshall_srvsvc_Statistics_ptr(data, pos) 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 -- (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 -- referent_id is split from the data (for example, in an array), you will want
-- ALL. -- ALL.
--@param data The data packet being processed. --@param data The data being processed.
--@param pos The position within <code>data</code>. --@param pos The position within <code>data</code>.
--@param result This is required when unmarshalling the BODY section, which always comes after --@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 -- unmarshalling the HEAD. It is the result returned for this parameter during the
@@ -3651,7 +3744,7 @@ end
-- } samr_SamArray; -- } samr_SamArray;
--</code> --</code>
-- --
--@param data The data packet being processed. --@param data The data being processed.
--@param pos The position within <code>data</code>. --@param pos The position within <code>data</code>.
--@return (pos, result) The new position in <code>data</code>, and a table representing the datatype. --@return (pos, result) The new position in <code>data</code>, and a table representing the datatype.
function unmarshall_samr_SamArray(data, pos) 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 ---Unmarshall a pointer to a <code>samr_SamArray</code> type. See <code>unmarshall_samr_SamArray</code> for
-- more information. -- more information.
-- --
--@param data The data packet being processed. --@param data The data being processed.
--@param pos The position within <code>data</code>. --@param pos The position within <code>data</code>.
--@return (pos, result) The new position in <code>data</code>, and a table representing the datatype. --@return (pos, result) The new position in <code>data</code>, and a table representing the datatype.
function unmarshall_samr_SamArray_ptr(data, pos) 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 -- (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 -- referent_id is split from the data (for example, in an array), you will want
-- ALL. -- ALL.
--@param data The data packet being processed. --@param data The data being processed.
--@param pos The position within <code>data</code>. --@param pos The position within <code>data</code>.
--@param result This is required when unmarshalling the BODY section, which always comes after --@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 -- unmarshalling the HEAD. It is the result returned for this parameter during the
@@ -3740,7 +3833,7 @@ end
-- } samr_DispInfoGeneral; -- } samr_DispInfoGeneral;
--</code> --</code>
-- --
--@param data The data packet being processed. --@param data The data being processed.
--@param pos The position within <code>data</code>. --@param pos The position within <code>data</code>.
--@return (pos, result) The new position in <code>data</code>, and a table representing the datatype. --@return (pos, result) The new position in <code>data</code>, and a table representing the datatype.
function unmarshall_samr_DispInfoGeneral(data, pos) function unmarshall_samr_DispInfoGeneral(data, pos)
@@ -3767,7 +3860,7 @@ end
-- } samr_DispInfo; -- } samr_DispInfo;
--</code> --</code>
-- --
--@param data The data packet being processed. --@param data The data being processed.
--@param pos The position within <code>data</code>. --@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 --@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. -- <code>nil</code>, if there was an error.
@@ -3802,7 +3895,7 @@ end
-- } samr_DomInfo1; -- } samr_DomInfo1;
--</code> --</code>
-- --
--@param data The data packet being processed. --@param data The data being processed.
--@param pos The position within <code>data</code>. --@param pos The position within <code>data</code>.
--@return (pos, result) The new position in <code>data</code>, and a table representing the datatype. --@return (pos, result) The new position in <code>data</code>, and a table representing the datatype.
function unmarshall_samr_DomInfo1(data, pos) function unmarshall_samr_DomInfo1(data, pos)
@@ -3828,7 +3921,7 @@ end
-- } samr_DomInfo8; -- } samr_DomInfo8;
--</code> --</code>
-- --
--@param data The data packet being processed. --@param data The data being processed.
--@param pos The position within <code>data</code>. --@param pos The position within <code>data</code>.
--@return (pos, result) The new position in <code>data</code>, and a table representing the datatype. --@return (pos, result) The new position in <code>data</code>, and a table representing the datatype.
function unmarshall_samr_DomInfo8(data, pos) function unmarshall_samr_DomInfo8(data, pos)
@@ -3852,7 +3945,7 @@ end
-- } samr_DomInfo12; -- } samr_DomInfo12;
--</code> --</code>
-- --
--@param data The data packet being processed. --@param data The data being processed.
--@param pos The position within <code>data</code>. --@param pos The position within <code>data</code>.
--@return (pos, result) The new position in <code>data</code>, and a table representing the datatype. --@return (pos, result) The new position in <code>data</code>, and a table representing the datatype.
function unmarshall_samr_DomInfo12(data, pos) function unmarshall_samr_DomInfo12(data, pos)
@@ -3886,7 +3979,7 @@ end
-- } samr_DomainInfo; -- } samr_DomainInfo;
--</code> --</code>
-- --
--@param data The data packet being processed. --@param data The data being processed.
--@param pos The position within <code>data</code>. --@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 --@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. -- <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 ---Unmarshall a pointer to a <code>samr_DomainInfo</code>. See <code>unmarshall_samr_DomainInfo</code> for
-- more information. -- more information.
-- --
--@param data The data packet being processed. --@param data The data being processed.
--@param pos The position within <code>data</code>. --@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 --@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. -- <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 rrlength = rrlength - 18
end end
if(rrlength > 0) then
rrlength = rrlength - 1
end
pos, statistics = bin.unpack(string.format(">A%d", rrlength), result, pos) pos, statistics = bin.unpack(string.format(">A%d", rrlength), result, pos)
-- Put it in the registry, in case anybody else needs it -- 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 -- 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. -- 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 -- 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 -- set when running a script, or from the registry (<code>nmap.registry[<ip>]['smbaccounts'])
-- scripts. However, they can also be passed as parameters to the function, which will -- where it can be set by other scripts (for example, smb-brute.nse). However, they can also
-- override any other username/password set. -- 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, -- 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 -- 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 mutexes = setmetatable({}, {__mode = "k"});
--local debug_mutex = nmap.mutex("SMB-DEBUG") --local debug_mutex = nmap.mutex("SMB-DEBUG")
local TIMEOUT = 5000
---Returns the mutex that should be used by the current connection. This mutex attempts ---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. -- 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) function get_status_name(status)
if(status_names[status] == nil) then 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 for i, v in pairs(status_names) do
if(v == status) then if(v == status) then
return i return i
@@ -380,6 +382,7 @@ function start_raw(host, port)
local status, err local status, err
local socket = nmap.new_socket() local socket = nmap.new_socket()
socket:set_timeout(TIMEOUT)
status, err = socket:connect(host.ip, port, "tcp") status, err = socket:connect(host.ip, port, "tcp")
if(status == false) then if(status == false) then
@@ -486,6 +489,7 @@ function start_netbios(host, port, name)
); );
stdnse.print_debug(3, "SMB: Connecting to %s", host.ip) stdnse.print_debug(3, "SMB: Connecting to %s", host.ip)
socket:set_timeout(TIMEOUT)
status, err = socket:connect(host.ip, port, "tcp") status, err = socket:connect(host.ip, port, "tcp")
if(status == false) then if(status == false) then
socket:close() socket:close()
@@ -499,7 +503,7 @@ function start_netbios(host, port, name)
socket:close() socket:close()
return false, "SMB: Failed to send: " .. err return false, "SMB: Failed to send: " .. err
end end
socket:set_timeout(5000) socket:set_timeout(TIMEOUT)
-- Receive the session response -- Receive the session response
stdnse.print_debug(3, "SMB: Receiving NetBIOS 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 return false, "SMB: Failed to close socket: " .. result
end end
pos, result, flags, length = bin.unpack(">CCS", result) 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) -- Check for a position session response (0x82)
if result == 0x82 then if result == 0x82 then
@@ -747,6 +754,11 @@ local function smb_encode_header(smb, command)
-- the server that we deal in ASCII. -- 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 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", local header = bin.pack("<CCCCCICSSLSSSSS",
sig:byte(1), -- Header sig:byte(1), -- Header
sig:byte(2), -- Header sig:byte(2), -- Header
@@ -824,10 +836,10 @@ function smb_read(smb)
local status, result local status, result
local pos, netbios_length, length, header, parameter_length, parameters, data_length, data 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 -- 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: smb['socket']:set_timeout(TIMEOUT)
-- http://seclists.org/nmap-dev/2008/q3/0702.html
smb['socket']:set_timeout(5000)
status, result = smb['socket']:receive_bytes(4); status, result = smb['socket']:receive_bytes(4);
-- Make sure the connection is still alive -- Make sure the connection is still alive
@@ -839,7 +851,7 @@ function smb_read(smb)
-- The NetBIOS header is 24 bits, big endian -- The NetBIOS header is 24 bits, big endian
pos, netbios_length = bin.unpack(">I", result) pos, netbios_length = bin.unpack(">I", result)
if(netbios_length == nil) then 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 end
-- Make the length 24 bits -- Make the length 24 bits
netbios_length = bit.band(netbios_length, 0x00FFFFFF) 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 we haven't received enough bytes, try and get the rest (fragmentation!)
if(#result < length) then if(#result < length) then
local new_result 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) 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 -- Make sure the connection is still alive
if(status ~= true) then if(status ~= true) then
return false, "SMB: Failed to receive bytes: " .. result return false, "SMB: Failed to receive bytes: " .. result
@@ -872,31 +883,31 @@ function smb_read(smb)
-- The header is 32 bytes. -- The header is 32 bytes.
pos, header = bin.unpack("<A32", result, pos) pos, header = bin.unpack("<A32", result, pos)
if(header == nil) then 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 end
-- The parameters length is a 1-byte value. -- The parameters length is a 1-byte value.
pos, parameter_length = bin.unpack("<C", result, pos) pos, parameter_length = bin.unpack("<C", result, pos)
if(parameter_length == nil) then 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 end
-- Double the length parameter, since parameters are two-byte values. -- Double the length parameter, since parameters are two-byte values.
pos, parameters = bin.unpack(string.format("<A%d", parameter_length*2), result, pos) pos, parameters = bin.unpack(string.format("<A%d", parameter_length*2), result, pos)
if(parameters == nil) then 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 end
-- The data length is a 2-byte value. -- The data length is a 2-byte value.
pos, data_length = bin.unpack("<S", result, pos) pos, data_length = bin.unpack("<S", result, pos)
if(data_length == nil) then 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 end
-- Read that many bytes of data. -- Read that many bytes of data.
pos, data = bin.unpack(string.format("<A%d", data_length), result, pos) pos, data = bin.unpack(string.format("<A%d", data_length), result, pos)
if(data == nil) then 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 end
stdnse.print_debug(3, "SMB: Received %d bytes", string.len(result)) 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) -- Check if we fell off the packet (if that happened, the last parameter will be nil)
if(mid == nil) then 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 end
-- Parse the parameter section -- Parse the parameter section
pos, dialect = bin.unpack("<S", parameters) 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 -- Check if we ran off the packet
if(dialect == nil) then 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 end
-- Check if the server didn't like our requested protocol -- Check if the server didn't like our requested protocol
if(dialect ~= 0) then if(dialect ~= 0) then
@@ -983,7 +997,9 @@ function negotiate_protocol(smb)
end 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) 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 -- Some broken implementations of SMB don't send these variables
if(time == nil) then if(time == nil) then
time = 0 time = 0
@@ -1010,6 +1026,9 @@ function negotiate_protocol(smb)
-- Data section -- Data section
-- This one's a little messier, because I don't appear to have unicode support -- 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) 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 -- Get the domain as a Unicode string
local ch, dummy local ch, dummy
@@ -1017,18 +1036,26 @@ function negotiate_protocol(smb)
server = "" server = ""
pos, ch, dummy = bin.unpack("<CC", data, pos) 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 while ch ~= nil and ch ~= 0 do
domain = domain .. string.char(ch) domain = domain .. string.char(ch)
pos, ch, dummy = bin.unpack("<CC", data, pos) 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 end
-- Get the server name as a Unicode string -- 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) pos, ch, dummy = bin.unpack("<CC", data, pos)
while ch ~= nil and ch ~= 0 do while ch ~= nil and ch ~= 0 do
server = server .. string.char(ch) server = server .. string.char(ch)
pos, ch, dummy = bin.unpack("<CC", data, pos) pos, ch, dummy = bin.unpack("<CC", data, pos)
end end
-- Fill out smb variables -- Fill out smb variables
smb['security_mode'] = security_mode smb['security_mode'] = security_mode
smb['max_mpx'] = max_mpx 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 ---Determines which username is going to be used, based on the function parameters, the registry, and
-- the nmap arguments (in that order). -- 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. --@param username [optional] The function parameter version, which will override all others if set.
--@return The highest priority username that's set. --@return The highest priority username that's set.
-- TODO: Get username from the registry local function get_username(ip, username)
local function get_username(username)
if(username ~= nil) then if(username ~= nil) then
stdnse.print_debug(2, "SMB: Using username passed as a parameter: %s", username) stdnse.print_debug(2, "SMB: Using username passed as a parameter: %s", username)
else 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 username = nmap.registry.args.smbusername
stdnse.print_debug(2, "SMB: Using username passed as an nmap parameter (smbusername): %s", username) stdnse.print_debug(2, "SMB: Using username passed as an nmap parameter (smbusername): %s", username)
elseif(nmap.registry.args.smbuser ~= nil) then elseif(nmap.registry.args.smbuser ~= nil) then
@@ -1125,6 +1161,7 @@ end
-- --
-- The output passwords are hashed based on the hash type. -- 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 username The username, which is used for v2 passwords.
--@param domain The username, which is used for v2 passwords. --@param domain The username, which is used for v2 passwords.
--@param password [optional] The overriding password. --@param password [optional] The overriding password.
@@ -1132,7 +1169,7 @@ end
--@param challenge The server challenge. --@param challenge The server challenge.
--@param hash_type The way in which to hash the password. --@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. --@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 lm_hash = nil
local ntlm_hash = nil local ntlm_hash = nil
@@ -1141,7 +1178,10 @@ local function get_password_response(username, domain, password, password_hash,
if(password ~= nil) then if(password ~= nil) then
stdnse.print_debug(2, "SMB: Using password passed as a parameter") stdnse.print_debug(2, "SMB: Using password passed as a parameter")
else 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 password = nmap.registry.args.smbpassword
stdnse.print_debug(2, "SMB: Using password passed as an nmap parameter (smbpassword)") stdnse.print_debug(2, "SMB: Using password passed as an nmap parameter (smbpassword)")
elseif(nmap.registry.args.smbpass ~= nil) then elseif(nmap.registry.args.smbpass ~= nil) then
@@ -1253,24 +1293,25 @@ end
--@param password [optional] The password to override with. --@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 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 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'. --@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 = {} local response = {}
-- If we don't have OpenSSL, don't bother with any of this -- If we don't have OpenSSL, don't bother with any of this
if(have_ssl == true) then if(have_ssl == true) then
-- We choose *one* username to try, here. First, see if the user set a username -- 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. -- parameter, then disable it.
-- If a username was found, look for a domain and password -- If a username was found, look for a domain and password
username = get_username(username) username = get_username(ip, username)
domain = get_domain(domain) domain = get_domain(domain)
hash_type = get_hash_type(hash_type) hash_type = get_hash_type(hash_type)
if(username ~= nil) then 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 if(lm_response ~= nil and ntlm_response ~= nil) then
local data = {} 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") stdnse.print_debug(1, "SMB: ERROR: Couldn't find OpenSSL library, only checking Guest and/or Anonymous accounts")
end end
-- Check if we're using default accounts
if(use_default == nil or use_default == true) then
local data local data
-- Add guest account -- Add guest account
stdnse.print_debug(2, "SMB: Going to try guest account before attempting anonymous")
data = {} data = {}
data['username'] = 'guest' data['username'] = 'guest'
data['domain'] = '' data['domain'] = ''
@@ -1311,6 +1352,7 @@ local function get_logins(ip, challenge, username, domain, password, password_ha
data['lanman'] = '' data['lanman'] = ''
data['ntlm'] = '' data['ntlm'] = ''
response[#response + 1] = data response[#response + 1] = data
end
return response return response
end end
@@ -1333,21 +1375,23 @@ end
--@param domain [optional] Overrides the domain to use. --@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 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 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 --@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: -- elements are added to the smb table:
-- * 'uid' The UserID for the session -- * '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 -- * 'is_guest' If set, the username wasn't found so the user was automatically logged in as the guest account
-- * 'os' The operating system -- * 'os' The operating system
-- * 'lanmanager' The servers's LAN Manager -- * '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 i
local status, result local status, result
local header, parameters, data local header, parameters, data
local pos local pos
local header1, header2, header3, header4, command, status, flags, flags2, pid_high, signature, unused, tid, pid, uid, mid 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 andx_command, andx_reserved, andx_offset, action
local os, lanmanager, domain local os, lanmanager
local logins = get_logins(smb['ip'], smb['server_challenge'], username, domain, password, password_hash, hash_type) 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']) 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 0xFF, -- ANDX -- no further commands
0x00, -- ANDX -- Reserved (0) 0x00, -- ANDX -- Reserved (0)
0x0000, -- ANDX -- next offset 0x0000, -- ANDX -- next offset
0x1000, -- Max buffer size 0xFFFF, -- Max buffer size
0x0001, -- Max multiplexes 0x0001, -- Max multiplexes
0x0000, -- Virtual circuit num 0x0000, -- Virtual circuit num
smb['session_key'], -- The session key 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) 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 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 end
-- Check if we're successful -- Check if we're successful
@@ -1403,11 +1447,14 @@ function start_session(smb, username, domain, password, password_hash, hash_type
-- Parse the parameters -- Parse the parameters
pos, andx_command, andx_reserved, andx_offset, action = bin.unpack("<CCSS", parameters) pos, andx_command, andx_reserved, andx_offset, action = bin.unpack("<CCSS", parameters)
if(action == nil) then 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 end
-- Parse the data -- Parse the data
pos, os, lanmanager, domain = bin.unpack("<zzz", 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 -- Fill in the smb object and smb string
smb['uid'] = uid smb['uid'] = uid
@@ -1423,21 +1470,27 @@ function start_session(smb, username, domain, password, password_hash, hash_type
end end
-- Check if they were logged in as a guest -- Check if they were logged in as a guest
if(log_errors == nil or log_errors == true) then
if(smb['is_guest'] == 1) 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'])) 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 else
stdnse.print_debug(1, "SMB: Login as %s\\%s succeeded", logins[i]['domain'], string_or_blank(logins[i]['username'])) stdnse.print_debug(1, "SMB: Login as %s\\%s succeeded", logins[i]['domain'], string_or_blank(logins[i]['username']))
end end
end
return true return true
else else
-- This username failed, print a warning and keep going -- 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
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!") stdnse.print_debug(1, "SMB: ERROR: All logins failed, sorry it didn't work out!")
end
return false, get_status_name(status) return false, get_status_name(status)
end end
@@ -1492,7 +1545,7 @@ function tree_connect(smb, path)
-- Check if we were allowed in -- 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) 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 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 end
if(status ~= 0) then if(status ~= 0) then
@@ -1531,7 +1584,7 @@ function tree_disconnect(smb)
-- Check if there was an error -- 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) 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 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 end
if(status ~= 0) then if(status ~= 0) then
return false, get_status_name(status) return false, get_status_name(status)
@@ -1577,7 +1630,7 @@ function logoff(smb)
-- Check if there was an error -- 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) 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 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 end
if(status ~= 0) then if(status ~= 0) then
return false, get_status_name(status) return false, get_status_name(status)
@@ -1613,14 +1666,14 @@ function create_file(smb, path)
string.len(path), -- Path length string.len(path), -- Path length
0x00000016, -- Create flags 0x00000016, -- Create flags
0x00000000, -- Root FID 0x00000000, -- Root FID
0x0002019F, -- Access mask 0x02000000, -- Access mask
0x0000000000000000, -- Allocation size 0x0000000000000000, -- Allocation size
0x00000000, -- File attributes 0x00000000, -- File attributes
0x00000003, -- Share attributes 0x00000003, -- Share attributes
0x00000001, -- Disposition 0x00000001, -- Disposition
0x00400040, -- Create options 0x00000000, -- Create options
0x00000002, -- Impersonation 0x00000002, -- Impersonation
0x01 -- Security flags 0x00 -- Security flags
) )
data = bin.pack("z", path) data = bin.pack("z", path)
@@ -1641,7 +1694,7 @@ function create_file(smb, path)
-- Check if we were allowed in -- 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) 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 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 end
if(status ~= 0) then if(status ~= 0) then
return false, get_status_name(status) return false, get_status_name(status)
@@ -1650,7 +1703,7 @@ function create_file(smb, path)
-- Parse the parameters -- 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) 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 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 end
-- Fill in the smb string -- Fill in the smb string
@@ -1669,9 +1722,165 @@ function create_file(smb, path)
smb['is_directory'] = is_directory smb['is_directory'] = is_directory
return true return true
end 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. ---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 -- 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. -- 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 -- 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) 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 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 end
if(status ~= 0) then if(status ~= 0) then
if(status_names[status] == nil) then if(status_names[status] == nil) then
@@ -1755,7 +1964,7 @@ function send_transaction(smb, func, function_parameters, function_data)
-- Parse the parameters -- 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) 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 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 end
-- Convert the parameter/data offsets into something more useful (the offset into the data section) -- 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_ACCESS_DENIED = 0x00000005,
NT_STATUS_WERR_INVALID_NAME = 0x0000007b, NT_STATUS_WERR_INVALID_NAME = 0x0000007b,
NT_STATUS_WERR_UNKNOWN_LEVEL = 0x0000007c, NT_STATUS_WERR_UNKNOWN_LEVEL = 0x0000007c,
NT_STATUS_WERR_MORE_DATA = 0x000000ea,
NT_STATUS_NO_MORE_ITEMS = 0x00000103, NT_STATUS_NO_MORE_ITEMS = 0x00000103,
NT_STATUS_MORE_ENTRIES = 0x00000105, NT_STATUS_MORE_ENTRIES = 0x00000105,
NT_STATUS_SOME_NOT_MAPPED = 0x00000107, NT_STATUS_SOME_NOT_MAPPED = 0x00000107,
DOS_STATUS_UNKNOWN_ERROR = 0x00010001, DOS_STATUS_UNKNOWN_ERROR = 0x00010001,
DOS_STATUS_UNKNOWN_ERROR_2 = 0x00010002, DOS_STATUS_NONSPECIFIC_ERROR = 0x00010002,
DOS_STATUS_DIRECTORY_NOT_FOUND = 0x00030001, DOS_STATUS_DIRECTORY_NOT_FOUND = 0x00030001,
DOS_STATUS_ACCESS_DENIED = 0x00050001, DOS_STATUS_ACCESS_DENIED = 0x00050001,
DOS_STATUS_INVALID_FID = 0x00060001, 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 = "intrusive", filename = "smb-check-vulns.nse" }
Entry{ category = "discovery", filename = "smb-enum-domains.nse" } Entry{ category = "discovery", filename = "smb-enum-domains.nse" }
Entry{ category = "intrusive", 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 = "discovery", filename = "smb-enum-sessions.nse" }
Entry{ category = "intrusive", filename = "smb-enum-sessions.nse" } Entry{ category = "intrusive", filename = "smb-enum-sessions.nse" }
Entry{ category = "discovery", filename = "smb-enum-shares.nse" } Entry{ category = "discovery", filename = "smb-enum-shares.nse" }

View File

@@ -61,6 +61,9 @@ author = "Ron Bowes"
copyright = "Ron Bowes" copyright = "Ron Bowes"
license = "Same as Nmap--See http://nmap.org/book/man-legal.html" license = "Same as Nmap--See http://nmap.org/book/man-legal.html"
categories = {"intrusive"} 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 'msrpc'
require 'smb' require 'smb'
@@ -181,6 +184,7 @@ end
action = function(host) action = function(host)
local status, result local status, result
local response = " \n" local response = " \n"
local found = false local found = false

View File

@@ -57,6 +57,7 @@ hostrule = function(host)
end end
action = function(host) action = function(host)
local response = " \n" local response = " \n"
local status, smbstate local status, smbstate
local i, j 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 end
action = function(host) action = function(host)
local response = " \n" local response = " \n"
local status1, status2 local status1, status2

View File

@@ -191,7 +191,9 @@ function check_shares(host, shares)
stdnse.print_debug(3, "EnumShares: Access was denied") stdnse.print_debug(3, "EnumShares: Access was denied")
denied_shares[#denied_shares + 1] = shares[i] denied_shares[#denied_shares + 1] = shares[i]
else 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 end
else else
-- Add it to allowed shares -- Add it to allowed shares
@@ -244,6 +246,7 @@ local function get_share_info(host, name)
end end
action = function(host) action = function(host)
local enum_result local enum_result
local result, shared local result, shared
local response = " \n" 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. leave the defaults; however, there are advantages to using them individually.
Advantages of using SAMR enumeration: Advantages of using SAMR enumeration:
* Stealthier (requires one packet/user account, whereas LSA uses at least 20 * Stealthier (requires one packet/user account, whereas LSA uses at least 10
packets; additionally, LSA makes a lot of noise in the Windows event log (LSA packets while SAMR uses half that; additionally, LSA makes a lot of noise in
enumeration is the only script I (Ron Bowes) have been called on by the the Windows event log (LSA enumeration is the only script I (Ron Bowes) have
administrator of a box I was testing against). been called on by the administrator of a box I was testing against).
* More information is returned (more than just the username). * More information is returned (more than just the username).
* Every account will be found, since they're being enumerated with a function * Every account will be found, since they're being enumerated with a function
that's designed to enumerate users. 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 (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. 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 To do this, the script breaks users into groups of RIDs based on the <code>LSA_GROUPSIZE</code>
(checking too many at once causes problems). We continue checking until we reach constant. All members of this group are checked simultaneously, and the responses recorded.
1100, and get an empty group of five. This probably isn't the most effective way, but it When a series of empty groups are found (<code>LSA_MINEMPTY</code> groups, specifically),
seems to work. It might be a good idea to modify this, in the future, with some more the scan ends. As long as you are getting a few groups with active accounts, the scan will
intelligence. I (Ron Bowes) performed a test on an old server with a lot of accounts, continue.
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.
Before attempting this conversion, the SID of the server has to be determined. 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 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 return smb.get_port(host) ~= nil
end 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) action = function(host)
local i, j local i, j
local samr_status = false local samr_status = false
local lsa_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 -- Try enumerating through LSA first. Since LSA provides less information, we want the
-- SAMR result to overwrite it. -- SAMR result to overwrite it.
if(do_lsa) then 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(lsa_status == false) then
if(nmap.debugging() > 0) then if(nmap.debugging() > 0) then
response = response .. "ERROR: Couldn't enumerate through LSA: " .. lsa_result .. "\n" response = response .. "ERROR: Couldn't enumerate through LSA: " .. lsa_result .. "\n"
@@ -468,7 +187,7 @@ action = function(host)
-- Try enumerating through SAMR -- Try enumerating through SAMR
if(do_samr) then 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(samr_status == false) then
if(nmap.debugging() > 0) then if(nmap.debugging() > 0) then
response = response .. "ERROR: Couldn't enumerate through SAMR: " .. samr_result .. "\n" response = response .. "ERROR: Couldn't enumerate through SAMR: " .. samr_result .. "\n"
@@ -523,6 +242,7 @@ action = function(host)
end end
if(names[name]['fullname'] ~= nil) then response = response .. string.format(" |_ Full name: %s\n", names[name]['fullname']) 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]['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(names[name]['flags'] ~= nil) then response = response .. string.format(" |_ Flags: %s\n", stdnse.strjoin(", ", names[name]['flags'])) end
if(nmap.verbosity() > 1) then if(nmap.verbosity() > 1) then