diff --git a/CHANGELOG b/CHANGELOG index 6dd7be187..f728f36ab 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,5 +1,8 @@ # 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 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 diff --git a/nselib/smb.lua b/nselib/smb.lua index a7bac1155..46636b406 100644 --- a/nselib/smb.lua +++ b/nselib/smb.lua @@ -2010,6 +2010,111 @@ function delete_file(smb, path, overrides) return true 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("srch_attrs table containing one or more of the following boolean attributes: +-- ro - find read only files +-- hidden - find hidden files +-- system - find system files +-- volid - include volume ids in result +-- dir - find directories +-- archive - 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(" 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, 5)) ~= 0 ) + local first = true + + local last_name + repeat + local pos = 1 + + if ( not(first) ) then + local function_parameters = bin.pack("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("ls. +]] + +--- +-- @usage +-- nmap -p 445 --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 Documents and Settings +-- | 2009-09-08 13:26:10 e5a6b742d36facb19c5192852c43 +-- | 2008-12-01 02:06:29 Inetpub +-- | 2007-02-18 00:31:38 94720 msizap.exe +-- | 2007-12-02 00:55:01 Program Files +-- | 2008-12-01 02:05:52 temp +-- | 2011-12-16 14:40:18 usr +-- | 2007-12-02 00:42:40 WINDOWS +-- |_ 2007-12-02 00:22:38 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 '' or fe.eof), fe.fname, sha1) + else + tab.addrow(lstab, fe.created, (is_dir(fe) and '' 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