1
0
mirror of https://github.com/nmap/nmap.git synced 2025-12-08 05:31:31 +00:00

o [NSE] Added the script smb-ls that lists files on SMB shares and produces

output similar to the dir command on Windows. [Patrik Karlsson]
This commit is contained in:
patrik
2012-06-03 18:10:49 +00:00
parent f3bfdaec3d
commit 0372cf9e7a
4 changed files with 328 additions and 0 deletions

View File

@@ -1,5 +1,8 @@
# Nmap Changelog ($Id$); -*-text-*- # Nmap Changelog ($Id$); -*-text-*-
o [NSE] Added the script smb-ls that lists files on SMB shares and produces
output similar to the dir command on Windows. [Patrik Karlsson]
o Fixed a bug that caused Nmap to fail to find any network interface when o Fixed a bug that caused Nmap to fail to find any network interface when
at least one of them is in the monitor mode. The fix was to define the at least one of them is in the monitor mode. The fix was to define the
ARP_HRD_IEEE80211_RADIOTAP 802.11 radiotap header identifier in the ARP_HRD_IEEE80211_RADIOTAP 802.11 radiotap header identifier in the

View File

@@ -2010,6 +2010,111 @@ function delete_file(smb, path, overrides)
return true return true
end end
---
-- Implements SMB_COM_TRANSACTION2 to support the find_files function
-- This function has not been extensively tested
--
--@param smb The SMB object associated with the connection
--@param sub_command The SMB_COM_TRANSACTION2 sub command
--@param function_parameters The parameter data to pass to the function. This is untested, since none of the
-- transactions I've done have required parameters.
--@param function_data The data to send with the packet. This is basically the next protocol layer
--@param overrides The overrides table
--@return (status, result) If status is false, result is an error message. Otherwise, result is a table
-- containing 'parameters' and 'data', representing the parameters and data returned by the server.
local function send_transaction2(smb, sub_command, function_parameters, function_data, overrides)
overrides = overrides or {}
local header1, header2, header3, header4, command, status, flags, flags2, pid_high, signature, unused, pid, mid
local header, parameters, data
local parameter_offset = 0
local parameter_size = 0
local data_offset = 0
local data_size = 0
local total_word_count, total_data_count, reserved1, parameter_count, parameter_displacement, data_count, data_displacement, setup_count, reserved2
local response = {}
-- Header is 0x20 bytes long (not counting NetBIOS header).
header = smb_encode_header(smb, command_codes['SMB_COM_TRANSACTION2'], overrides) -- 0x32 = SMB_COM_TRANSACTION2
if(function_parameters) then
parameter_offset = 0x44
parameter_size = #function_parameters
data_offset = #function_parameters + 33 + 32
end
-- Parameters are 0x20 bytes long.
parameters = bin.pack("<SSSSCCSISSSSSCCS",
parameter_size, -- Total parameter count.
data_size, -- Total data count.
0x000a, -- Max parameter count.
0x3984, -- Max data count.
0x00, -- Max setup count.
0x00, -- Reserved.
0x0000, -- Flags (0x0000 = 2-way transaction, don't disconnect TIDs).
0x00001388, -- Timeout (0x00000000 = return immediately).
0x0000, -- Reserved.
parameter_size, -- Parameter bytes.
parameter_offset, -- Parameter offset.
data_size, -- Data bytes.
data_offset, -- Data offset.
0x01, -- Setup Count
0x00, -- Reserved
sub_command -- Sub command
)
local data = "\0\0\0" .. (function_parameters or '')
data = data .. (function_data or '')
-- Send the transaction request
stdnse.print_debug(2, "SMB: Sending SMB_COM_TRANSACTION2")
local result, err = smb_send(smb, header, parameters, data, overrides)
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 it worked
local uid, tid, pos
pos, header1, header2, header3, header4, command, status, flags, flags2, pid_high, signature, unused, tid, pid, uid, mid = bin.unpack("<CCCCCICSSlSSSSS", header)
if(header1 == nil or mid == nil) then
return false, "SMB: ERROR: Server returned less data than it was supposed to (one or more fields are missing); aborting [29]"
end
if(status ~= 0) then
if(status_names[status] == nil) then
return false, string.format("Unknown SMB error: 0x%08x\n", status)
else
return false, status_names[status]
end
end
-- Parse the parameters
pos, total_word_count, total_data_count, reserved1, parameter_count, parameter_offset, parameter_displacement, data_count, data_offset, data_displacement, setup_count, reserved2 = bin.unpack("<SSSSSSSSSCC", parameters)
if(total_word_count == nil or reserved2 == nil) then
return false, "SMB: ERROR: Server returned less data than it was supposed to (one or more fields are missing); aborting [30]"
end
-- Convert the parameter/data offsets into something more useful (the offset into the data section)
-- - 0x20 for the header, - 0x01 for the length.
parameter_offset = parameter_offset - 0x20 - 0x01 - #parameters - 0x02;
-- - 0x20 for the header, - 0x01 for parameter length, the parameter length, and - 0x02 for the data length.
data_offset = data_offset - 0x20 - 0x01 - #parameters - 0x02;
-- I'm not sure I entirely understand why the '+1' is here, but I think it has to do with the string starting at '1' and not '0'.
function_parameters = string.sub(data, parameter_offset + 1, parameter_offset + parameter_count)
function_data = string.sub(data, data_offset + 1, data_offset + data_count)
response['parameters'] = function_parameters
response['data'] = function_data
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.
@@ -2484,6 +2589,112 @@ function file_delete(host, share, remotefile)
return true return true
end end
---
-- List files based on a pattern withing a given share and directory
--
-- @param smbstate the SMB object associated with the connection
-- @param fname filename to search for, relative to share path
-- @param options table containing none or more of the following
-- <code>srch_attrs</code> table containing one or more of the following boolean attributes:
-- <code>ro</code> - find read only files
-- <code>hidden</code> - find hidden files
-- <code>system</code> - find system files
-- <code>volid</code> - include volume ids in result
-- <code>dir</code> - find directories
-- <code>archive</code> - find archived files
-- @return iterator function retreiving the next result
function find_files(smbstate, fname, options)
local TRANS2_FIND_FIRST2, TRANS2_FIND_NEXT2 = 1, 2
options = options or {}
if (not(options.srch_attrs)) then
options.srch_attrs = { ro = true, hidden = true, system = true, dir = true}
end
local nattrs = ( options.srch_attrs.ro and 1 or 0 ) + ( options.srch_attrs.hidden and 2 or 0 ) +
( options.srch_attrs.hidden and 2 or 0 ) + ( options.srch_attrs.system and 4 or 0 ) +
( options.srch_attrs.volid and 8 or 0 ) + ( options.srch_attrs.dir and 16 or 0 ) +
( options.srch_attrs.archive and 32 or 0 )
if ( not(fname) ) then
fname = '\\*'
elseif( fname:sub(1,1) ~= '\\' ) then
fname = '\\' .. fname
end
local srch_count = 173 -- picked up by wireshark
local flags = 6 -- Return RESUME keys, close search if END OF SEARCH is reached
local loi = 260 -- Level of interest, return SMB_FIND_FILE_BOTH_DIRECTORY_INFO
local storage_type = 0 -- despite the documentation of having to be either 0x01 or 0x40, wireshark reports 0
local function_parameters = bin.pack("<SSSSIA", nattrs, srch_count, flags, loi, storage_type, fname)
-- SMB header: 32
-- trans2 header: 36
-- FIND_FIRST2 parameters: #function_parameters
local pad = ( 32 + 36 + #function_parameters ) % 4
if ( pad > 0 ) then
for i=1, ( 4-pad ) do
function_parameters = function_parameters .. "\0"
end
end
local function next_item()
local status, response = send_transaction2(smbstate, TRANS2_FIND_FIRST2, function_parameters, "")
if ( not(status) ) then
coroutine.yield()
end
local srch_id = select(2, bin.unpack("<S", response.parameters))
local stop_loop = ( select(2, bin.unpack(">S", response.parameters, 5)) ~= 0 )
local first = true
local last_name
repeat
local pos = 1
if ( not(first) ) then
local function_parameters = bin.pack("<SSSISA", srch_id, srch_count, loi, 0, flags, last_name)
status, response = send_transaction2(smbstate, TRANS2_FIND_NEXT2, function_parameters, "")
if ( not(status) ) then
coroutine.yield()
end
-- check whether END-OF-SEARCH was set
stop_loop = ( select(2, bin.unpack(">S", response.parameters, 3)) ~= 0 )
end
-- parse response, based on LOI == 260
repeat
local fe, last_pos, ne, f_len, ea_len, sf_len, _ = {}, pos
pos, ne, fe.fi, fe.created, fe.accessed, fe.write, fe.change,
fe.eof, fe.alloc_size, fe.attrs, f_len, ea_len, sf_len, _ = bin.unpack("<IILLLLLLIIICC", response.data, pos)
pos, fe.s_fname = bin.unpack("A24", response.data, pos)
local time = fe.created
time = (time / 10000000) - 11644473600
fe.created = os.date("%Y-%m-%d %H:%M:%S", time)
-- TODO: cleanup fe.s_fname
pos, fe.fname = bin.unpack("A" .. f_len, response.data, pos)
pos = last_pos + ne
-- removing trailing zero bytes from file name
fe.fname = fe.fname:sub(1, -2)
last_name = fe.fname
coroutine.yield(fe)
until ( ne == 0 )
first = false
until(stop_loop)
return
end
return coroutine.wrap(next_item)
end
---Determine whether or not the anonymous user has write access on the share. This is done by creating then ---Determine whether or not the anonymous user has write access on the share. This is done by creating then
-- deleting a file. -- deleting a file.
-- --

View File

@@ -313,6 +313,7 @@ Entry { filename = "smb-enum-sessions.nse", categories = { "discovery", "intrusi
Entry { filename = "smb-enum-shares.nse", categories = { "discovery", "intrusive", } } Entry { filename = "smb-enum-shares.nse", categories = { "discovery", "intrusive", } }
Entry { filename = "smb-enum-users.nse", categories = { "auth", "intrusive", } } Entry { filename = "smb-enum-users.nse", categories = { "auth", "intrusive", } }
Entry { filename = "smb-flood.nse", categories = { "dos", "intrusive", } } Entry { filename = "smb-flood.nse", categories = { "dos", "intrusive", } }
Entry { filename = "smb-ls.nse", categories = { "discovery", "safe", } }
Entry { filename = "smb-mbenum.nse", categories = { "discovery", "safe", } } Entry { filename = "smb-mbenum.nse", categories = { "discovery", "safe", } }
Entry { filename = "smb-os-discovery.nse", categories = { "default", "discovery", "safe", } } Entry { filename = "smb-os-discovery.nse", categories = { "default", "discovery", "safe", } }
Entry { filename = "smb-psexec.nse", categories = { "intrusive", } } Entry { filename = "smb-psexec.nse", categories = { "intrusive", } }

113
scripts/smb-ls.nse Normal file
View File

@@ -0,0 +1,113 @@
local bit = require 'bit'
local smb = require 'smb'
local stdnse = require 'stdnse'
local tab = require 'tab'
local openssl= require 'openssl'
description = [[
Attempts to get useful information about files from SMB volumes.
The output is intended to resemble the output of <code>ls</code>.
]]
---
-- @usage
-- nmap -p 445 <ip> --script smb-ls --script-args 'share=c$,path=\temp'
--
-- @output
-- Host script results:
-- | smb-ls:
-- | Directory of \\192.168.56.101\c$\
-- | 2007-12-02 00:20:09 0 AUTOEXEC.BAT
-- | 2007-12-02 00:20:09 0 CONFIG.SYS
-- | 2007-12-02 00:53:39 <DIR> Documents and Settings
-- | 2009-09-08 13:26:10 <DIR> e5a6b742d36facb19c5192852c43
-- | 2008-12-01 02:06:29 <DIR> Inetpub
-- | 2007-02-18 00:31:38 94720 msizap.exe
-- | 2007-12-02 00:55:01 <DIR> Program Files
-- | 2008-12-01 02:05:52 <DIR> temp
-- | 2011-12-16 14:40:18 <DIR> usr
-- | 2007-12-02 00:42:40 <DIR> WINDOWS
-- |_ 2007-12-02 00:22:38 <DIR> wmpub
--
-- @args smb-ls.share the share to connect to
-- @args smb-ls.path the path, relative to the share to list the contents from
-- @args smb-ls.pattern [optional] the search pattern to execute (default: *)
-- @args smb-ls.maxdepth [optional] the maximum depth to recurse into a directory
-- @args smb-ls.maxfiles [optional] return only a certain amount of files
-- @args smb-ls.checksum [optional] download each file and calculate a SHA1 checksum
--
local arg_share = stdnse.get_script_args(SCRIPT_NAME .. '.share')
local arg_path = stdnse.get_script_args(SCRIPT_NAME .. '.path')
local arg_pattern = stdnse.get_script_args(SCRIPT_NAME .. '.pattern') or '*'
local arg_maxfiles = tonumber(stdnse.get_script_args(SCRIPT_NAME .. '.maxfiles'))
local arg_maxdepth = tonumber(stdnse.get_script_args(SCRIPT_NAME .. '.maxdepth'))
local arg_checksum = stdnse.get_script_args(SCRIPT_NAME .. '.checksum')
author = "Patrik Karlsson"
license = "Same as Nmap--See http://nmap.org/book/man-legal.html"
categories = {"discovery", "safe"}
hostrule = function(host)
return ( smb.get_port(host) ~= nil and arg_share and arg_path )
end
-- checks whether the file entry is a directory
local function is_dir(fe)
return ( bit.band(fe.attrs, 16) == 16 )
end
local function fail(err) return ("\n ERROR: %s"):format(err or "") end
action = function(host)
local status, smbstate = smb.start_ex(host, true, true, arg_share, nil, nil, nil)
if ( not(status) ) then
return fail("Failed to authenticate to server")
end
-- remove leading slash
arg_path = ( arg_path:sub(1,2) == '\\' and arg_path:sub(2) or arg_path )
-- fixup checksum argument
arg_checksum = ( arg_checksum == 'true' or arg_checksum == '1' ) and true or false
local options = { max_depth = arg_maxdepth, max_files = arg_maxfiles }
local depth, path, output, dirs = 0, arg_path, {}, {}
repeat
local lstab = tab.new((arg_checksum and 4 or 3))
for fe in smb.find_files(smbstate, path .. '\\' .. arg_pattern, options ) do
if ( arg_checksum and not(is_dir(fe)) ) then
local status, content = smb.file_read(host, arg_share, path .. '\\' .. fe.fname, nil, {file_create_disposition=1})
local sha1 = ( status and stdnse.tohex(openssl.sha1(content)) or "" )
tab.addrow(lstab, fe.created, (is_dir(fe) and '<DIR>' or fe.eof), fe.fname, sha1)
else
tab.addrow(lstab, fe.created, (is_dir(fe) and '<DIR>' or fe.eof), fe.fname)
end
arg_maxfiles = ( arg_maxfiles and arg_maxfiles - 1 )
if ( arg_maxfiles == 0 ) then
break
end
if ( is_dir(fe) and fe.fname ~= '.' and fe.fname ~= '..' ) then
table.insert(dirs, { depth = depth + 1, path = path .. '\\' .. fe.fname } )
end
end
table.insert(output, { name = ("Directory of %s"):format( '\\\\' .. stdnse.get_hostname(host) .. '\\' .. arg_share .. path), tab.dump(lstab) })
path = nil
if ( #dirs ~= 0 ) then
local dir = table.remove(dirs, 1)
depth = dir.depth
if ( not(arg_maxdepth) or ( dir.depth < arg_maxdepth ) ) then
path = dir.path
table.insert(output, "")
end
end
until(not(path) or arg_maxfiles == 0)
smb.stop(smbstate)
return stdnse.format_output(true, output)
end