mirror of
https://github.com/nmap/nmap.git
synced 2025-12-06 04:31:29 +00:00
Mostly splitting function summaries (the first paragraph of NSEdoc) from the body of the description to make the summary indexes shorter and easier to scan. Also fixed some unbalanced code tags like <code>foo</table>
4321 lines
173 KiB
Lua
4321 lines
173 KiB
Lua
---
|
|
-- Implements functionality related to Server Message Block (SMB, an extension
|
|
-- of CIFS) traffic, which is a Windows protocol.
|
|
--
|
|
-- SMB traffic is normally sent to/from ports 139 or 445 of Windows systems. Other systems
|
|
-- implement SMB as well, including Samba and a lot of embedded devices. Some of them implement
|
|
-- it properly and many of them not. Although the protocol has been documented decently
|
|
-- well by Samba and others, many 3rd party implementations are broken or make assumptions.
|
|
-- Even Samba's and Windows' implementations aren't completely compatible. As a result,
|
|
-- creating an implementation that accepts everything is a bit of a minefield. Microsoft's
|
|
-- extensive documentation is available at the following URLs:
|
|
-- * SMB: http://msdn.microsoft.com/en-us/library/cc246231(v=prot.13).aspx
|
|
-- * CIFS: http://msdn.microsoft.com/en-us/library/ee442092(v=prot.13).aspx
|
|
--
|
|
-- Where possible, this implementation, since it's intended for scanning, will attempt to
|
|
-- accept any invalid implementations it can, and fail gracefully if it can't. This has
|
|
-- been tested against a great number of weird implementations, and it now works against
|
|
-- all of them.
|
|
--
|
|
-- The intention of this library is to eventually handle all aspects of the SMB protocol.
|
|
-- That being said, I'm only implementing the pieces that I (Ron Bowes) need. If you
|
|
-- require something more, let me know and I'll put it on my todo list.
|
|
--
|
|
-- A programmer using this library should already have some knowledge of the SMB protocol,
|
|
-- although a lot isn't necessary. You can pick up a lot by looking at the code. The basic
|
|
-- login/logoff is this:
|
|
--
|
|
-- <code>
|
|
-- [connect]
|
|
-- C->S SMB_COM_NEGOTIATE
|
|
-- S->C SMB_COM_NEGOTIATE
|
|
-- C->S SMB_COM_SESSION_SETUP_ANDX
|
|
-- S->C SMB_COM_SESSION_SETUP_ANDX
|
|
-- C->S SMB_COM_TREE_CONNECT_ANDX
|
|
-- S->C SMB_COM_TREE_CONNECT_ANDX
|
|
-- ...
|
|
-- C->S SMB_COM_TREE_DISCONNECT
|
|
-- S->C SMB_COM_TREE_DISCONNECT
|
|
-- C->S SMB_COM_LOGOFF_ANDX
|
|
-- S->C SMB_COM_LOGOFF_ANDX
|
|
-- [disconnect]
|
|
-- </code>
|
|
--
|
|
-- In terms of functions here, the protocol is:
|
|
--
|
|
-- <code>
|
|
-- status, smbstate = smb.start(host)
|
|
-- status, err = smb.negotiate_protocol(smbstate, {})
|
|
-- status, err = smb.start_session(smbstate, {})
|
|
-- status, err = smb.tree_connect(smbstate, path, {})
|
|
-- ...
|
|
-- status, err = smb.tree_disconnect(smbstate)
|
|
-- status, err = smb.logoff(smbstate)
|
|
-- status, err = smb.stop(smbstate)
|
|
-- </code>
|
|
--
|
|
-- The <code>stop</code> function will automatically call tree_disconnect and logoff,
|
|
-- cleaning up the session, if it hasn't been done already.
|
|
--
|
|
-- To initially begin the connection, there are two options:
|
|
--
|
|
-- 1) Attempt to start a raw session over 445, if it's open.
|
|
--
|
|
-- 2) Attempt to start a NetBIOS session over 139. Although the
|
|
-- protocol's the same, it requires a <code>session request</code> packet.
|
|
-- That packet requires the computer's name, which is requested
|
|
-- using a NBSTAT probe over UDP port 137.
|
|
--
|
|
-- Once it's connected, a <code>SMB_COM_NEGOTIATE</code> packet is sent, requesting the protocol
|
|
-- "NT LM 0.12", which is the most commonly supported one. Among other things, the server's
|
|
-- response contains the host's security level, the system time, and the computer/domain name.
|
|
-- Some systems will refuse to use that protocol and return "-1" or "1" instead of 0. If that's
|
|
-- detected, we kill the connection (because the protocol following won't work).
|
|
--
|
|
-- If that's successful, <code>SMB_COM_SESSION_SETUP_ANDX</code> is sent. It is essentially the logon
|
|
-- packet, where the username, domain, and password are sent to the server for verification.
|
|
-- The username and password are generally picked up from the program parameters, which are
|
|
-- set when running a script, or from the registry where it can be set by other scripts (for
|
|
-- example, <code>smb-brute.nse</code>). However, they can also be passed as parameters to the
|
|
-- function, which will override any other username/password set.
|
|
--
|
|
-- If a username and password are set, they are used for the first login attempt. If a login fails,
|
|
-- or they weren't set, a connection as the 'GUEST' account with a blank password is attempted. If
|
|
-- that fails, then a NULL session is established, which should always work. The username/password
|
|
-- will give the highest access level, GUEST will give lower access, and NULL will give the lowest
|
|
-- (often, NULL will give no access).
|
|
--
|
|
-- The actual login protocol used by <code>SMB_COM_SESSION_SETUP_ANDX</code> is explained in detail
|
|
-- in <code>smbauth.lua</code>.
|
|
--
|
|
-- Thanks go to Christopher R. Hertel and his book Implementing CIFS, which
|
|
-- taught me everything I know about Microsoft's protocols. Additionally, I used Samba's
|
|
-- list of error codes for my constants. Although I don't believe they would be covered
|
|
-- by GPL, since they're public now anyways, but I'm not a lawyer and, if somebody feels
|
|
-- differently, let me know and we can sort this out.
|
|
--
|
|
-- Scripts that use this module can use the script arguments listed below
|
|
-- example of using these script arguments:
|
|
-- <code>
|
|
-- nmap --script=smb-<script>.nse --script-args=smbuser=ron,smbpass=iagotest2k3,smbbasic=1,smbsign=force <host>
|
|
-- </code>
|
|
--
|
|
-- @args smbbasic Forces the authentication to use basic security, as opposed to "extended security".
|
|
-- Against most modern systems, extended security should work, but there may be cases
|
|
-- where you want to force basic. There's a chance that you'll get better results for
|
|
-- enumerating users if you turn on basic authentication.
|
|
-- @args smbsign Controls whether or not server signatures are checked in SMB packets. By default, on Windows,
|
|
-- server signatures aren't enabled or required. By default, this library will always sign
|
|
-- packets if it knows how, and will check signatures if the server says to. Possible values are:
|
|
-- * <code>force</code>: Always check server signatures, even if server says it doesn't support them (will
|
|
-- probably fail, but is technically more secure).
|
|
-- * <code>negotiate</code>: [default] Use signatures if server supports them.
|
|
-- * <code>ignore</code>: Never check server signatures. Not recommended.
|
|
-- * <code>disable</code>: Don't send signatures, at all, and don't check the server's. not recommended.
|
|
-- More information on signatures can be found in <code>smbauth.lua</code>.
|
|
-- @args smbport Override the default port choice. If <code>smbport</code> is open, it's used. It's assumed
|
|
-- to be the same protocol as port 445, not port 139. Since it probably isn't possible to change
|
|
-- Windows' ports normally, this is mostly useful if you're bouncing through a relay or something.
|
|
-- @args randomseed Set to a value to change the filenames/service names that are randomly generated.
|
|
--
|
|
-- @author Ron Bowes <ron@skullsecurity.net>
|
|
-- @copyright Same as Nmap--See http://nmap.org/book/man-legal.html
|
|
-----------------------------------------------------------------------
|
|
local asn1 = require "asn1"
|
|
local bin = require "bin"
|
|
local bit = require "bit"
|
|
local coroutine = require "coroutine"
|
|
local io = require "io"
|
|
local math = require "math"
|
|
local match = require "match"
|
|
local netbios = require "netbios"
|
|
local nmap = require "nmap"
|
|
local os = require "os"
|
|
local smbauth = require "smbauth"
|
|
local stdnse = require "stdnse"
|
|
local string = require "string"
|
|
local table = require "table"
|
|
local unicode = require "unicode"
|
|
_ENV = stdnse.module("smb", stdnse.seeall)
|
|
|
|
-- These arrays are filled in with constants at the bottom of this file
|
|
command_codes = {}
|
|
command_names = {}
|
|
status_codes = {}
|
|
status_names = {}
|
|
filetype_codes = {}
|
|
filetype_names = {}
|
|
|
|
local TIMEOUT = 10000
|
|
|
|
---Wrapper around <code>smbauth.add_account</code>.
|
|
function add_account(host, username, domain, password, password_hash, hash_type, is_admin)
|
|
smbauth.add_account(host, username, domain, password, password_hash, hash_type, is_admin)
|
|
end
|
|
|
|
---Wrapper around <code>smbauth.get_account</code>.
|
|
function get_account(host)
|
|
return smbauth.get_account(host)
|
|
end
|
|
---Create an 'overrides' table
|
|
function get_overrides(username, domain, password, password_hash, hash_type, overrides)
|
|
if(not(overrides)) then
|
|
return {username=username, domain=domain, password=password, password_hash=password_hash, hash_type=hash_type}
|
|
else
|
|
overrides['username'] = username
|
|
overrides['domain'] = domain
|
|
overrides['password'] = password
|
|
overrides['password_hash'] = password_hash
|
|
overrides['hash_type'] = hash_type
|
|
end
|
|
end
|
|
|
|
---Get an 'overrides' table for the anonymous user
|
|
--
|
|
--@param overrides [optional] A base table of overrides. The appropriate fields will be added.
|
|
function get_overrides_anonymous(overrides)
|
|
if(not(overrides)) then
|
|
return {username='', domain='', password='', password_hash=nil, hash_type='none'}
|
|
else
|
|
overrides['username'] = ''
|
|
overrides['domain'] = ''
|
|
overrides['password'] = ''
|
|
overrides['password_hash'] = ''
|
|
overrides['hash_type'] = 'none'
|
|
end
|
|
end
|
|
|
|
---Convert a status number from the SMB header into a status name, returning an error message (not nil) if
|
|
-- it wasn't found.
|
|
--
|
|
--@param status The numerical status.
|
|
--@return A string representing the error. Never nil.
|
|
function get_status_name(status)
|
|
|
|
if(status_names[status] == nil) then
|
|
-- If the name wasn't found in the array, do a linear search on it
|
|
for i, v in pairs(status_names) do
|
|
if(v == status) then
|
|
return i
|
|
end
|
|
end
|
|
|
|
return string.format("NT_STATUS_UNKNOWN (0x%08x)", status)
|
|
else
|
|
return status_names[status]
|
|
end
|
|
end
|
|
|
|
|
|
--- Determines whether or not SMB checks are possible on this host, and, if they are,
|
|
-- which port is best to use. This is how it decides:
|
|
--
|
|
-- * If port tcp/445 is open, use it for a raw connection
|
|
-- * Otherwise, if ports tcp/139 and udp/137 are open, do a NetBIOS connection. Since UDP scanning isn't default, we're also ok with udp/137 in an unknown state.
|
|
--
|
|
--@param host The host object.
|
|
--@return The port number to use, or nil if we don't have an SMB port
|
|
function get_port(host)
|
|
local port_u137 = nmap.get_port_state(host, {number=137, protocol="udp"})
|
|
local port_t139 = nmap.get_port_state(host, {number=139, protocol="tcp"})
|
|
local port_t445 = nmap.get_port_state(host, {number=445, protocol="tcp"})
|
|
local custom_port = nil
|
|
|
|
if(nmap.registry.args.smbport ~= nil) then
|
|
custom_port = nmap.get_port_state(host, {number=tonumber(nmap.registry.args.smbport), protocol="tcp"})
|
|
end
|
|
|
|
-- Try a user-defined port first
|
|
if(custom_port ~= nil and custom_port.state == "open") then
|
|
return custom_port.number
|
|
end
|
|
|
|
if(port_t445 ~= nil and port_t445.state == "open") then
|
|
-- tcp/445 is open, we're good
|
|
return 445
|
|
end
|
|
|
|
if(port_t139 ~= nil and port_t139.state == "open") then
|
|
-- tcp/139 is open, check uf udp/137 is open or unknown
|
|
if(port_u137 == nil or port_u137.state == "open" or port_u137.state == "open|filtered") then
|
|
return 139
|
|
end
|
|
end
|
|
|
|
return nil
|
|
end
|
|
|
|
---Turn off extended security negotiations for this connection.
|
|
--
|
|
-- There are a few reasons you might want to do that, the main ones being that
|
|
-- extended security is going to be marginally slower and it's not going to
|
|
-- give the same level of information in some cases (namely, it doesn't present
|
|
-- the server's name).
|
|
--@param smb The SMB state table.
|
|
function disable_extended(smb)
|
|
smb['extended_security'] = false
|
|
end
|
|
|
|
--- Begins a SMB session, automatically determining the best way to connect.
|
|
--
|
|
-- @param host The host object
|
|
-- @return (status, smb) if the status is true, result is the newly crated smb object;
|
|
-- otherwise, socket is the error message.
|
|
function start(host)
|
|
local port = get_port(host)
|
|
local status, result
|
|
local state = {}
|
|
|
|
state['uid'] = 0
|
|
state['tid'] = 0
|
|
state['mid'] = 1
|
|
state['pid'] = math.random(32766) + 1
|
|
state['host'] = host
|
|
state['ip'] = host.ip
|
|
state['sequence'] = -1
|
|
|
|
-- Check whether or not the user requested basic authentication
|
|
if(stdnse.get_script_args( "smbbasic" )) then
|
|
state['extended_security'] = false
|
|
else
|
|
state['extended_security'] = true
|
|
end
|
|
|
|
-- Store the name of the server
|
|
local nbcache_mutex = nmap.mutex("Netbios lookup mutex")
|
|
nbcache_mutex "lock"
|
|
if ( not(host.registry['netbios_name']) ) then
|
|
status, result = netbios.get_server_name(host.ip)
|
|
if(status == true) then
|
|
host.registry['netbios_name'] = result
|
|
state['name'] = result
|
|
end
|
|
else
|
|
stdnse.debug2("SMB: Resolved netbios name from cache")
|
|
state['name'] = host.registry['netbios_name']
|
|
end
|
|
nbcache_mutex "done"
|
|
|
|
stdnse.debug2("SMB: Starting SMB session for %s (%s)", host.name, host.ip)
|
|
|
|
if(port == nil) then
|
|
return false, "SMB: Couldn't find a valid port to check"
|
|
end
|
|
|
|
-- Initialize the accounts for logging on
|
|
smbauth.init_account(host)
|
|
|
|
if(port ~= 139) then
|
|
status, state['socket'] = start_raw(host, port)
|
|
state['port'] = port
|
|
|
|
if(status == false) then
|
|
return false, state['socket']
|
|
end
|
|
return true, state
|
|
|
|
else
|
|
status, state['socket'] = start_netbios(host, port)
|
|
state['port'] = port
|
|
if(status == false) then
|
|
return false, state['socket']
|
|
end
|
|
return true, state
|
|
|
|
end
|
|
|
|
return false, "SMB: Couldn't find a valid port to check"
|
|
end
|
|
|
|
---Initiates a SMB connection over whichever port it can, then optionally sends
|
|
-- the common initialization packets.
|
|
--
|
|
-- Note that each packet depends on the previous one, so if you want to go all
|
|
-- the way up to create_file, you have to set all parameters.
|
|
--
|
|
-- If anything fails, we back out of the connection and return an error, so the
|
|
-- calling function doesn't have to call smb.stop().
|
|
--
|
|
--@param host The host object.
|
|
--@param bool_negotiate_protocol [optional] If 'true', send the protocol
|
|
-- negotiation. Default: false.
|
|
--@param bool_start_session [optional] If 'true', start the session. Default:
|
|
-- false.
|
|
--@param str_tree_connect [optional] The tree to connect to, if given (eg.
|
|
-- "IPC$" or "C$"). If not given, packet isn't sent.
|
|
--@param str_create_file [optional] The path and name of the file (or pipe)
|
|
-- that's created, if given. If not given, packet isn't
|
|
-- sent.
|
|
--@param overrides [optional] A table of overrides (for, for example, username,
|
|
-- password, etc.) to pass to all functions.
|
|
--@param bool_disable_extended [optional] If set to true, disables extended
|
|
-- security negotiations.
|
|
function start_ex(host, bool_negotiate_protocol, bool_start_session, str_tree_connect, str_create_file, bool_disable_extended, overrides)
|
|
local smbstate
|
|
local status, err
|
|
|
|
-- Make sure we have overrides
|
|
overrides = overrides or {}
|
|
|
|
-- Begin the SMB session
|
|
status, smbstate = start(host)
|
|
if(status == false) then
|
|
return false, smbstate
|
|
end
|
|
|
|
-- Disable extended security if it was requested
|
|
if(bool_disable_extended == true) then
|
|
disable_extended(smbstate)
|
|
end
|
|
|
|
if(bool_negotiate_protocol == true) then
|
|
-- Negotiate the protocol
|
|
status, err = negotiate_protocol(smbstate, overrides)
|
|
if(status == false) then
|
|
stop(smbstate)
|
|
return false, err
|
|
end
|
|
|
|
if(bool_start_session == true) then
|
|
-- Start up a session
|
|
status, err = start_session(smbstate, overrides)
|
|
if(status == false) then
|
|
stop(smbstate)
|
|
return false, err
|
|
end
|
|
|
|
if(str_tree_connect ~= nil) then
|
|
-- Connect to share
|
|
status, err = tree_connect(smbstate, str_tree_connect, overrides)
|
|
if(status == false) then
|
|
stop(smbstate)
|
|
return false, err
|
|
end
|
|
|
|
if(str_create_file ~= nil) then
|
|
-- Try to connect to requested pipe
|
|
status, err = create_file(smbstate, str_create_file, overrides)
|
|
if(status == false) then
|
|
stop(smbstate)
|
|
return false, err
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
-- Return everything
|
|
return true, smbstate
|
|
end
|
|
|
|
--- Kills the SMB connection and closes the socket.
|
|
--
|
|
-- In addition to killing the connection, this function will log off the user and disconnect
|
|
-- the connected tree, if possible.
|
|
--
|
|
--@param smb The SMB object associated with the connection
|
|
--@return (status, result) If status is false, result is an error message. Otherwise, result
|
|
-- is undefined.
|
|
function stop(smb)
|
|
|
|
if(smb['tid'] ~= 0) then
|
|
tree_disconnect(smb)
|
|
end
|
|
|
|
if(smb['uid'] ~= 0) then
|
|
logoff(smb)
|
|
end
|
|
|
|
stdnse.debug2("SMB: Closing socket")
|
|
if(smb['socket'] ~= nil) then
|
|
local status, err = smb['socket']:close()
|
|
|
|
if(status == false) then
|
|
return false, "SMB: Failed to close socket: " .. err
|
|
end
|
|
end
|
|
|
|
return true
|
|
end
|
|
|
|
--- Begins a raw SMB session, likely over port 445. Since nothing extra is required, this
|
|
-- function simply makes a connection and returns the socket.
|
|
--
|
|
--@param host The host object to check.
|
|
--@param port The port to use (most likely 445).
|
|
--@return (status, socket) if status is true, result is the newly created socket.
|
|
-- Otherwise, socket is the error message.
|
|
function start_raw(host, port)
|
|
local status, err
|
|
local socket = nmap.new_socket()
|
|
|
|
socket:set_timeout(TIMEOUT)
|
|
status, err = socket:connect(host, port, "tcp")
|
|
|
|
if(status == false) then
|
|
return false, "SMB: Failed to connect to host: " .. err
|
|
end
|
|
|
|
return true, socket
|
|
end
|
|
|
|
--- This function will take a string like "a.b.c.d" and return "a", "a.b", "a.b.c", and "a.b.c.d".
|
|
--
|
|
-- This is used for discovering NetBIOS names. If a NetBIOS name is unknown, the substrings of the
|
|
-- DNS name can be used in this way.
|
|
--
|
|
--@param name The name to take apart
|
|
--@return An array of the sub names
|
|
local function get_subnames(name)
|
|
local i = -1
|
|
local list = {}
|
|
|
|
repeat
|
|
local subname = name
|
|
|
|
i = string.find(name, "[.]", i + 1)
|
|
if(i ~= nil) then
|
|
subname = string.sub(name, 1, i - 1)
|
|
end
|
|
|
|
list[#list + 1] = string.upper(subname)
|
|
|
|
until i == nil
|
|
|
|
return list
|
|
end
|
|
|
|
--- Begins a SMB session over NetBIOS.
|
|
--
|
|
-- This requires a NetBIOS Session Start message to be sent first, which in
|
|
-- turn requires the NetBIOS name. The name can be provided as a parameter, or
|
|
-- it can be automatically determined.
|
|
--
|
|
-- Automatically determining the name is interesting, to say the least. Here
|
|
-- are the names it tries, and the order it tries them in:
|
|
-- * The name the user provided, if present
|
|
-- * The name pulled from NetBIOS (udp/137), if possible
|
|
-- * The generic name "*SMBSERVER"
|
|
-- * Each subset of the domain name (for example, scanme.insecure.org would
|
|
-- attempt "scanme", "scanme.insecure", and "scanme.insecure.org")
|
|
--
|
|
-- This whole sequence is a little hackish, but it's the standard way of doing
|
|
-- it.
|
|
--
|
|
--@param host The host object to check.
|
|
--@param port The port to use (most likely 139).
|
|
--@param name [optional] The NetBIOS name of the host. Will attempt to
|
|
-- automatically determine if it isn't given.
|
|
--@return (status, socket) if status is true, result is the port
|
|
-- Otherwise, socket is the error message.
|
|
function start_netbios(host, port, name)
|
|
local i
|
|
local status, err
|
|
local pos, result, flags, length
|
|
local socket = nmap.new_socket()
|
|
|
|
-- First, populate the name array with all possible names, in order of significance
|
|
local names = {}
|
|
|
|
-- Use the name parameter
|
|
if(name ~= nil) then
|
|
names[#names + 1] = name
|
|
end
|
|
|
|
-- Get the name of the server from NetBIOS
|
|
status, name = netbios.get_server_name(host.ip)
|
|
if(status == true) then
|
|
names[#names + 1] = name
|
|
end
|
|
|
|
-- "*SMBSERVER" is a special name that any server should respond to
|
|
names[#names + 1] = "*SMBSERVER"
|
|
|
|
-- If all else fails, use each substring of the DNS name (this is a HUGE hack, but is actually
|
|
-- a recommended way of doing this!)
|
|
if(host.name ~= nil and host.name ~= "") then
|
|
local new_names = get_subnames(host.name)
|
|
for i = 1, #new_names, 1 do
|
|
names[#names + 1] = new_names[i]
|
|
end
|
|
end
|
|
|
|
-- This loop will try all the NetBIOS names we've collected, hoping one of them will work. Yes,
|
|
-- this is a hackish way, but it's actually the recommended way.
|
|
i = 1
|
|
repeat
|
|
|
|
-- Use the current name
|
|
name = names[i]
|
|
|
|
-- Some debug information
|
|
stdnse.debug1("SMB: Trying to start NetBIOS session with name = '%s'", name)
|
|
-- Request a NetBIOS session
|
|
local session_request = bin.pack(">CCSzz",
|
|
0x81, -- session request
|
|
0x00, -- flags
|
|
0x44, -- length
|
|
netbios.name_encode(name), -- server name
|
|
netbios.name_encode("NMAP") -- client name
|
|
);
|
|
|
|
stdnse.debug3("SMB: Connecting to %s", host.ip)
|
|
socket:set_timeout(TIMEOUT)
|
|
status, err = socket:connect(host, port, "tcp")
|
|
if(status == false) then
|
|
socket:close()
|
|
return false, "SMB: Failed to connect: " .. err
|
|
end
|
|
|
|
-- Send the session request
|
|
stdnse.debug3("SMB: Sending NetBIOS session request with name %s", name)
|
|
status, err = socket:send(session_request)
|
|
if(status == false) then
|
|
socket:close()
|
|
return false, "SMB: Failed to send: " .. err
|
|
end
|
|
socket:set_timeout(TIMEOUT)
|
|
|
|
-- Receive the session response
|
|
stdnse.debug3("SMB: Receiving NetBIOS session response")
|
|
status, result = socket:receive_buf(match.numbytes(4), true);
|
|
if(status == false) then
|
|
socket:close()
|
|
return false, "SMB: Failed to close socket: " .. result
|
|
end
|
|
pos, result, flags, length = bin.unpack(">CCS", result)
|
|
if(result == nil or length == nil) then
|
|
return false, "SMB: ERROR: Server returned less data than it was supposed to (one or more fields are missing); aborting [1]"
|
|
end
|
|
|
|
-- Check for a positive session response (0x82)
|
|
if result == 0x82 then
|
|
stdnse.debug3("SMB: Successfully established NetBIOS session with server name %s", name)
|
|
return true, socket
|
|
end
|
|
|
|
-- If the session failed, close the socket and try the next name
|
|
stdnse.debug1("SMB: Session request failed, trying next name")
|
|
socket:close()
|
|
|
|
-- Try the next name
|
|
i = i + 1
|
|
|
|
until i > #names
|
|
|
|
-- We reached the end of our names list
|
|
stdnse.debug1("SMB: None of the NetBIOS names worked!")
|
|
return false, "SMB: Couldn't find a NetBIOS name that works for the server. Sorry!"
|
|
end
|
|
|
|
--- Creates a string containing a SMB packet header. The header looks like this:
|
|
--
|
|
--<code>
|
|
-- --------------------------------------------------------------------------------------------------
|
|
-- | 31 30 29 28 27 26 25 24 23 22 21 20 19 18 17 16 15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0 |
|
|
-- --------------------------------------------------------------------------------------------------
|
|
-- | 0xFF | 'S' | 'M' | 'B' |
|
|
-- --------------------------------------------------------------------------------------------------
|
|
-- | Command | Status... |
|
|
-- --------------------------------------------------------------------------------------------------
|
|
-- | ...Status | Flags | Flags2 |
|
|
-- --------------------------------------------------------------------------------------------------
|
|
-- | PID_high | Signature..... |
|
|
-- --------------------------------------------------------------------------------------------------
|
|
-- | ....Signature.... |
|
|
-- --------------------------------------------------------------------------------------------------
|
|
-- | ....Signature | Unused |
|
|
-- --------------------------------------------------------------------------------------------------
|
|
-- | TID | PID |
|
|
-- --------------------------------------------------------------------------------------------------
|
|
-- | UID | MID |
|
|
-- -------------------------------------------------------------------------------------------------
|
|
--</code>
|
|
--
|
|
-- All fields are, incidentally, encoded in little endian byte order.
|
|
--
|
|
-- For the purposes here, the program doesn't care about most of the fields so they're given default
|
|
-- values. The "command" field is the only one we ever have to set manually, in my experience. The TID
|
|
-- and UID need to be set, but those are stored in the smb state and don't require user intervention.
|
|
--
|
|
--@param smb The smb state table.
|
|
--@param command The command to use.
|
|
--@param overrides The overrides table. Keep in mind that overriding things like flags is generally a very bad idea, unless you know what you're doing.
|
|
--@return A binary string containing the packed packet header.
|
|
function smb_encode_header(smb, command, overrides)
|
|
-- Make sure we have an overrides array
|
|
overrides = overrides or {}
|
|
|
|
-- Used for the header
|
|
local sig = string.char(0xFF) .. "SMB"
|
|
|
|
-- Pretty much every flags is deprecated. We set these two because they're required to be on.
|
|
local flags = bit.bor(0x10, 0x08) -- SMB_FLAGS_CANONICAL_PATHNAMES | SMB_FLAGS_CASELESS_PATHNAMES
|
|
-- These flags are less deprecated. We negotiate 32-bit status codes and long names. We also don't include Unicode, which tells
|
|
-- the server that we deal in ASCII.
|
|
local flags2 = bit.bor(0x4000, 0x2000, 0x0040, 0x0001) -- SMB_FLAGS2_32BIT_STATUS | SMB_FLAGS2_EXECUTE_ONLY_READS | SMB_FLAGS2_IS_LONG_NAME | SMB_FLAGS2_KNOWS_LONG_NAMES
|
|
|
|
-- Unless the user's disabled the security signature, add it
|
|
if(nmap.registry.args.smbsign ~= "disable") then
|
|
flags2 = bit.bor(flags2, 0x0004) -- SMB_FLAGS2_SECURITY_SIGNATURE
|
|
end
|
|
|
|
|
|
if(smb['extended_security'] == true) then
|
|
flags2 = bit.bor(flags2, 0x0800) -- SMB_EXTENDED_SECURITY
|
|
end
|
|
|
|
-- TreeID should never ever be 'nil', but it seems to happen once in awhile so print an error
|
|
if(smb['tid'] == nil) then
|
|
return false, string.format("SMB: ERROR: TreeID value was set to nil on host %s", smb['ip'])
|
|
end
|
|
|
|
local header = bin.pack("<CCCCCICSSLSSSSS",
|
|
sig:byte(1), -- Header
|
|
sig:byte(2), -- Header
|
|
sig:byte(3), -- Header
|
|
sig:byte(4), -- Header
|
|
command, -- Command
|
|
(overrides['status'] or 0), -- status
|
|
(overrides['flags'] or flags), -- flags
|
|
(overrides['flags2'] or flags2), -- flags2
|
|
(overrides['pid_high'] or 0), -- extra (pid_high)
|
|
(overrides['signature'] or 0), -- extra (signature)
|
|
(overrides['extra'] or 0), -- extra (unused)
|
|
(overrides['tid'] or smb['tid']), -- tid
|
|
(overrides['pid'] or smb['pid']), -- pid
|
|
(overrides['uid'] or smb['uid']), -- uid
|
|
(overrides['mid'] or smb['mid']) -- mid
|
|
)
|
|
|
|
return header
|
|
end
|
|
|
|
--- Converts a string containing the parameters section into the encoded
|
|
-- parameters string.
|
|
--
|
|
-- The encoding is simple:
|
|
-- * (1 byte) The number of 2-byte values in the parameters section
|
|
-- * (variable) The parameter section
|
|
-- This is automatically done by <code>smb_send</code>.
|
|
--
|
|
-- @param parameters The parameters section.
|
|
-- @param overrides The overrides table. The only thing possible to override here is the length.
|
|
-- @return The encoded parameters.
|
|
local function smb_encode_parameters(parameters, overrides)
|
|
-- Make sure we have an overrides array
|
|
overrides = overrides or {}
|
|
|
|
return bin.pack("<CA", (overrides['parameters_length'] or (#parameters / 2)), parameters)
|
|
end
|
|
|
|
--- Converts a string containing the data section into the encoded data string.
|
|
--
|
|
-- The encoding is simple:
|
|
-- * (2 bytes) The number of bytes in the data section
|
|
-- * (variable) The data section
|
|
-- This is automatically done by <code>smb_send</code>.
|
|
--
|
|
-- @param data The data section.
|
|
-- @param overrides The overrides table. The only thing possible to override here is the length.
|
|
-- @return The encoded data.
|
|
local function smb_encode_data(data, overrides)
|
|
-- Make sure we have an overrides array
|
|
overrides = overrides or {}
|
|
|
|
return bin.pack("<SA", (overrides['data_length'] or #data), data)
|
|
end
|
|
|
|
---Sign the message, if possible. This is done by replacing the signature with the sequence
|
|
-- number, creating a hash, then putting that hash in the signature location.
|
|
--@param smb The smb state object.
|
|
--@param body The body of the packet that's being signed.
|
|
--@return The body of the packet, with the signature in place.
|
|
local function message_sign(smb, body)
|
|
smb['sequence'] = smb['sequence'] + 1
|
|
|
|
if(smb['mac_key'] == nil) then
|
|
stdnse.debug3("SMB: Not signing message (missing mac_key)")
|
|
return body
|
|
elseif(nmap.registry.args.smbsign == "disable") then
|
|
stdnse.debug3("SMB: Not signing message (disabled by user)")
|
|
|
|
return body
|
|
end
|
|
|
|
-- Convert the sequence number to a string
|
|
local sequence = bin.pack("<L", smb['sequence'])
|
|
-- Create a new string, with the sequence number in place
|
|
local new_packet = string.sub(body, 1, 14) .. sequence .. string.sub(body, 23)
|
|
-- Calculate the signature
|
|
local signature = smbauth.calculate_signature(smb['mac_key'], new_packet)
|
|
|
|
return string.sub(body, 1, 14) .. signature .. string.sub(body, 23)
|
|
end
|
|
|
|
---Check the signature of the message.
|
|
--
|
|
-- This is the opposite of <code>message_sign</code>, and works the same way
|
|
-- (replaces the signature with the sequence number, calculates hash, checks)
|
|
--@param smb The smb state object.
|
|
--@param body The body of the packet that's being checked.
|
|
--@return A true/false value -- true if the packet was signed properly, false if it wasn't.
|
|
local function message_check_signature(smb, body)
|
|
smb['sequence'] = smb['sequence'] + 1
|
|
|
|
if(smb['mac_key'] == nil) then
|
|
stdnse.debug3("SMB: Not signing message (missing mac_key)")
|
|
return true
|
|
elseif(nmap.registry.args.smbsign ~= "force" and bit.band(smb['security_mode'], 0x0A) ~= 0) then
|
|
stdnse.debug3("SMB: Not signing message (server doesn't support it -- default)")
|
|
return true
|
|
elseif(nmap.registry.args.smbsign == "disable" or nmap.registry.args.smbsign == "ignore") then
|
|
stdnse.debug3("SMB: Not signing message (disabled by user)")
|
|
return true
|
|
end
|
|
|
|
-- Pull out the signature that they used
|
|
local signature = string.sub(body, 15, 22)
|
|
|
|
-- Turn the sequence into a string
|
|
local sequence = bin.pack("<L", smb['sequence'])
|
|
-- Create a new string, with the sequence number in place
|
|
local new_packet = string.sub(body, 1, 14) .. sequence .. string.sub(body, 23)
|
|
|
|
-- Calculate the proper signature
|
|
local real_signature = smbauth.calculate_signature(smb['mac_key'], new_packet)
|
|
|
|
-- Validate the signature
|
|
return signature == real_signature
|
|
end
|
|
|
|
--- Prepends the NetBIOS header to the packet, which is essentially the length, encoded
|
|
-- in 4 bytes of big endian, and sends it out.
|
|
--
|
|
-- The length field is actually 17 or 24 bits wide, depending on whether or
|
|
-- not we're using raw, but that shouldn't matter.
|
|
--
|
|
--@param smb The SMB object associated with the connection
|
|
--@param header The header, encoded with <code>smb_get_header</code>.
|
|
--@param parameters The parameters.
|
|
--@param data The data.
|
|
--@param overrides Overrides table.
|
|
--@return (result, err) If result is false, err is the error message. Otherwise, err is
|
|
-- undefined
|
|
function smb_send(smb, header, parameters, data, overrides)
|
|
overrides = overrides or {}
|
|
|
|
local encoded_parameters = smb_encode_parameters(parameters, overrides)
|
|
local encoded_data = smb_encode_data(data, overrides)
|
|
local body = header .. encoded_parameters .. encoded_data
|
|
local attempts = 5
|
|
local status, err
|
|
|
|
-- Calculate the message signature
|
|
body = message_sign(smb, body)
|
|
|
|
local out = bin.pack(">I<A", #body, body)
|
|
|
|
|
|
repeat
|
|
attempts = attempts - 1
|
|
stdnse.debug3("SMB: Sending SMB packet (len: %d, attempts remaining: %d)", #out, attempts)
|
|
status, err = smb['socket']:send(out)
|
|
until(status or (attempts == 0))
|
|
|
|
if(attempts == 0) then
|
|
stdnse.debug1("SMB: Sending packet failed after 5 tries! Giving up.")
|
|
end
|
|
|
|
return status, err
|
|
end
|
|
|
|
--- Reads the next packet from the socket, and parses it into the header, parameters,
|
|
-- and data.
|
|
--
|
|
--@param smb The SMB object associated with the connection
|
|
--@param read_data [optional] This function will read the data section if and only if
|
|
-- this value is true. This is a workaround for a bug in the tree connect packet,
|
|
-- where the length is set incorrectly. Default: true.
|
|
--@return (status, header, parameters, data) If status is true, the header,
|
|
-- parameters, and data are all the raw arrays (with the lengths already
|
|
-- removed). If status is false, header contains an error message and parameters/
|
|
-- data are undefined.
|
|
function smb_read(smb, read_data)
|
|
local status
|
|
local pos, netbios_data, netbios_length, length, header, parameter_length, parameters, data_length, data
|
|
local attempts = 5
|
|
|
|
stdnse.debug3("SMB: Receiving SMB packet")
|
|
|
|
-- Receive the response -- we make sure to receive at least 4 bytes, the length of the NetBIOS length
|
|
smb['socket']:set_timeout(TIMEOUT)
|
|
|
|
-- perform 5 attempt to read the Netbios header
|
|
local netbios
|
|
repeat
|
|
attempts = attempts - 1
|
|
status, netbios_data = smb['socket']:receive_buf(match.numbytes(4), true);
|
|
|
|
if ( not(status) and netbios_data == "EOF" ) then
|
|
stdnse.debug1("SMB: ERROR: Server disconnected the connection")
|
|
return false, "SMB: ERROR: Server disconnected the connection"
|
|
end
|
|
until(status or (attempts == 0))
|
|
|
|
-- Make sure the connection is still alive
|
|
if(status ~= true) then
|
|
return false, "SMB: Failed to receive bytes after 5 attempts: " .. netbios_data
|
|
end
|
|
|
|
-- The length of the packet is 4 bytes of big endian (for our purposes).
|
|
-- The NetBIOS header is 24 bits, big endian
|
|
pos, netbios_length = bin.unpack(">I", netbios_data)
|
|
if(netbios_length == nil) then
|
|
return false, "SMB: ERROR: Server returned less data than it was supposed to (one or more fields are missing); aborting [2]"
|
|
end
|
|
-- Make the length 24 bits
|
|
netbios_length = bit.band(netbios_length, 0x00FFFFFF)
|
|
|
|
-- The total length is the netbios_length, plus 4 (for the length itself)
|
|
length = netbios_length + 4
|
|
|
|
local attempts = 5
|
|
local smb_data
|
|
repeat
|
|
attempts = attempts - 1
|
|
status, smb_data = smb['socket']:receive_buf(match.numbytes(netbios_length), true)
|
|
until(status or (attempts == 0))
|
|
|
|
-- Make sure the connection is still alive
|
|
if(status ~= true) then
|
|
return false, "SMB: Failed to receive bytes after 5 attempts: " .. smb_data
|
|
end
|
|
|
|
local result = netbios_data .. smb_data
|
|
if(#result ~= length) then
|
|
stdnse.debug1("SMB: ERROR: Received wrong number of bytes, there will likely be issues (received %d, expected %d)", #result, length)
|
|
return false, string.format("SMB: ERROR: Didn't receive the expected number of bytes; received %d, expected %d. This will almost certainly cause some errors.", #result, length)
|
|
end
|
|
|
|
-- Check the message signature (ignoring the first four bytes, which are the netbios header)
|
|
local good_signature = message_check_signature(smb, string.sub(result, 5))
|
|
if(good_signature == false) then
|
|
return false, "SMB: ERROR: Server returned invalid signature"
|
|
end
|
|
|
|
-- The header is 32 bytes.
|
|
pos, header = bin.unpack("<A32", result, pos)
|
|
if(header == nil) then
|
|
return false, "SMB: ERROR: Server returned less data than it was supposed to (one or more fields are missing); aborting [3]"
|
|
end
|
|
|
|
-- The parameters length is a 1-byte value.
|
|
pos, parameter_length = bin.unpack("<C", result, pos)
|
|
if(parameter_length == nil) then
|
|
return false, "SMB: ERROR: Server returned less data than it was supposed to (one or more fields are missing); aborting [4]"
|
|
end
|
|
|
|
-- Double the length parameter, since parameters are two-byte values.
|
|
pos, parameters = bin.unpack(string.format("<A%d", parameter_length*2), result, pos)
|
|
if(parameters == nil) then
|
|
return false, "SMB: ERROR: Server returned less data than it was supposed to (one or more fields are missing); aborting [5]"
|
|
end
|
|
|
|
-- The data length is a 2-byte value.
|
|
pos, data_length = bin.unpack("<S", result, pos)
|
|
if(data_length == nil) then
|
|
return false, "SMB: ERROR: Server returned less data than it was supposed to (one or more fields are missing); aborting [6]"
|
|
end
|
|
|
|
-- Read that many bytes of data.
|
|
if(read_data == nil or read_data == true) then
|
|
pos, data = bin.unpack(string.format("<A%d", data_length), result, pos)
|
|
if(data == nil) then
|
|
return false, "SMB: ERROR: Server returned less data than it was supposed to (one or more fields are missing); aborting [7]"
|
|
end
|
|
else
|
|
data = nil
|
|
end
|
|
|
|
stdnse.debug3("SMB: Received %d bytes", #result)
|
|
return true, header, parameters, data
|
|
end
|
|
|
|
--- Sends out <code>SMB_COM_NEGOTIATE</code>, which is typically the first SMB packet sent out.
|
|
--
|
|
-- Sends the following:
|
|
-- * List of known protocols
|
|
--
|
|
-- Receives:
|
|
-- * The preferred dialect
|
|
-- * The security mode
|
|
-- * Max number of multiplexed connections, virtual circuits, and buffer sizes
|
|
-- * The server's system time and timezone
|
|
-- * The "encryption key" (aka, the server challenge)
|
|
-- * The capabilities
|
|
-- * The server and domain names
|
|
--
|
|
--@param smb The SMB object associated with the connection
|
|
--@param overrides [optional] Overrides for various fields
|
|
--@return (status, result) If status is false, result is an error message. Otherwise, result is
|
|
-- nil and the following elements are added to <code>smb</code>:
|
|
-- * 'security_mode' Whether or not to use cleartext passwords, message signatures, etc.
|
|
-- * 'max_mpx' Maximum number of multiplexed connections
|
|
-- * 'max_vc' Maximum number of virtual circuits
|
|
-- * 'max_buffer' Maximum buffer size
|
|
-- * 'max_raw_buffer' Maximum buffer size for raw connections (considered obsolete)
|
|
-- * 'session_key' A value that's basically just echoed back
|
|
-- * 'capabilities' The server's capabilities
|
|
-- * 'time' The server's time (in UNIX-style seconds since 1970)
|
|
-- * 'date' The server's date in a user-readable format
|
|
-- * 'timezone' The server's timezone, in hours from UTC
|
|
-- * 'timezone_str' The server's timezone, as a string
|
|
-- * 'server_challenge' A random string used for challenge/response
|
|
-- * 'domain' The server's primary domain or workgroup
|
|
-- * 'server' The server's name
|
|
function negotiate_protocol(smb, overrides)
|
|
local header, parameters, data
|
|
local pos
|
|
local header1, header2, header3, header4, command, status, flags, flags2, pid_high, signature, unused, pid, mid
|
|
|
|
header = smb_encode_header(smb, command_codes['SMB_COM_NEGOTIATE'], overrides)
|
|
|
|
-- Make sure we have overrides
|
|
overrides = overrides or {}
|
|
|
|
-- Parameters are blank
|
|
parameters = ""
|
|
|
|
-- Data is a list of strings, terminated by a blank one.
|
|
if(overrides['dialects'] == nil) then
|
|
data = bin.pack("<CzCz", 2, (overrides['dialect'] or "NT LM 0.12"), 2, "")
|
|
else
|
|
data = ""
|
|
for _, v in ipairs(overrides['dialects']) do
|
|
data = data .. bin.pack("<Cz", 2, v)
|
|
end
|
|
data = data .. bin.pack("Cz", 2, "")
|
|
end
|
|
|
|
-- Send the negotiate request
|
|
stdnse.debug2("SMB: Sending SMB_COM_NEGOTIATE")
|
|
local result, err = smb_send(smb, header, parameters, data, overrides)
|
|
if(status == false) then
|
|
return false, err
|
|
end
|
|
|
|
-- Read the result
|
|
status, header, parameters, data = smb_read(smb)
|
|
if(status ~= true) then
|
|
return false, header
|
|
end
|
|
|
|
-- Parse out the header
|
|
local uid, tid
|
|
pos, header1, header2, header3, header4, command, status, flags, flags2, pid_high, signature, unused, tid, pid, uid, mid = bin.unpack("<CCCCCICSSlSSSSS", header)
|
|
|
|
-- Get the protocol version
|
|
local protocol_version = string.char(header1, header2, header3, header4)
|
|
if(protocol_version == (string.char(0xFE) .. "SMB")) then
|
|
return false, "SMB: Server returned a SMBv2 packet, don't know how to handle"
|
|
end
|
|
|
|
-- Check if we fell off the packet (if that happened, the last parameter will be nil)
|
|
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 [8]"
|
|
end
|
|
|
|
-- Since this is the first response seen, check any necessary flags here
|
|
if(bit.band(flags2, 0x0800) ~= 0x0800) then
|
|
smb['extended_security'] = false
|
|
end
|
|
|
|
-- Parse the parameter section
|
|
pos, smb['dialect'] = bin.unpack("<S", parameters)
|
|
if(smb['dialect'] == nil) then
|
|
return false, "SMB: ERROR: Server returned less data than it was supposed to (one or more fields are missing); aborting [9]"
|
|
end
|
|
|
|
-- Check if we ran off the packet
|
|
if(smb['dialect'] == nil) then
|
|
return false, "SMB: ERROR: Server returned less data than it was supposed to (one or more fields are missing); aborting [10]"
|
|
end
|
|
-- Check if the server didn't like our requested protocol
|
|
if(smb['dialect'] ~= 0) then
|
|
return false, string.format("Server negotiated an unknown protocol (#%d) -- aborting", smb['dialect'])
|
|
end
|
|
|
|
pos, smb['security_mode'], smb['max_mpx'], smb['max_vc'], smb['max_buffer'], smb['max_raw_buffer'], smb['session_key'], smb['capabilities'], smb['time'], smb['timezone'], smb['key_length'] = bin.unpack("<CSSIIIILsC", parameters, pos)
|
|
if(smb['security_mode'] == nil or smb['capabilities'] == nil) then
|
|
return false, "SMB: ERROR: Server returned less data than it was supposed to (one or more fields are missing); aborting [11]"
|
|
end
|
|
-- Some broken implementations of SMB don't send these variables
|
|
if(smb['time'] == nil) then
|
|
smb['time'] = 0
|
|
end
|
|
if(smb['timezone'] == nil) then
|
|
smb['timezone'] = 0
|
|
end
|
|
if(smb['key_length'] == nil) then
|
|
smb['key_length'] = 0
|
|
end
|
|
if(smb['byte_count'] == nil) then
|
|
smb['byte_count'] = 0
|
|
end
|
|
|
|
-- Convert the time and timezone to more useful values
|
|
smb['time'] = (smb['time'] / 10000000) - 11644473600
|
|
smb['date'] = os.date("%Y-%m-%d %H:%M:%S", smb['time'])
|
|
smb['timezone'] = -(smb['timezone'] / 60)
|
|
if(smb['timezone'] == 0) then
|
|
smb['timezone_str'] = "UTC+0"
|
|
elseif(smb['timezone'] < 0) then
|
|
smb['timezone_str'] = "UTC-" .. math.abs(smb['timezone'])
|
|
else
|
|
smb['timezone_str'] = "UTC+" .. smb['timezone']
|
|
end
|
|
|
|
-- Data section
|
|
if(smb['extended_security'] == true) then
|
|
pos, smb['server_challenge'] = bin.unpack(string.format("<A%d", smb['key_length']), data)
|
|
if(smb['server_challenge'] == nil) then
|
|
return false, "SMB: ERROR: Server returned less data than it was supposed to (one or more fields are missing); aborting [12]"
|
|
end
|
|
|
|
pos, smb['server_guid'] = bin.unpack("<A16", data, pos)
|
|
if(smb['server_guid'] == nil) then
|
|
return false, "SMB: ERROR: Server returned less data than it was supposed to (one or more fields are missing); aborting [12]"
|
|
end
|
|
|
|
-- do we have a security blob?
|
|
if ( #data - pos > 0 ) then
|
|
pos, smb['security_blob'] = bin.unpack("<A" .. #data - pos, data, pos )
|
|
end
|
|
else
|
|
pos, smb['server_challenge'] = bin.unpack(string.format("<A%d", smb['key_length']), data)
|
|
if(smb['server_challenge'] == nil) then
|
|
return false, "SMB: ERROR: Server returned less data than it was supposed to (one or more fields are missing); aborting [12]"
|
|
end
|
|
|
|
-- Get the (null-terminated) domain as a Unicode string
|
|
smb['domain'] = ""
|
|
smb['server'] = ""
|
|
|
|
local remainder = unicode.utf16to8(string.sub(data, pos))
|
|
pos, pos = string.find(remainder, "\0", 1, true)
|
|
if pos == nil then
|
|
return false, "SMB: ERROR: Server returned less data than it was supposed to (one or more fields are missing); aborting [14]"
|
|
end
|
|
smb['domain'] = string.sub(remainder, 1, pos)
|
|
|
|
-- Get the server name as a Unicode string
|
|
-- Note: This can be nil, Samba leaves this off
|
|
local pos2 = pos + 1
|
|
pos, pos = string.find(remainder, "\0", pos2, true)
|
|
if pos ~= nil then
|
|
smb['server'] = string.sub(remainder, pos2, pos)
|
|
end
|
|
end
|
|
|
|
return true
|
|
end
|
|
|
|
|
|
--- This is an internal function and should not be called externally. Use
|
|
-- the start_session() function instead.
|
|
local function start_session_basic(smb, log_errors, overrides)
|
|
local i, err
|
|
local status, result
|
|
local header, parameters, data, domain
|
|
local pos
|
|
local header1, header2, header3, header4, command, status, flags, flags2, pid_high, signature, unused, tid, pid, uid, mid
|
|
local andx_command, andx_reserved, andx_offset, action
|
|
local os, lanmanager
|
|
local username, domain, password, password_hash, hash_type
|
|
local busy_count = 0
|
|
|
|
header = smb_encode_header(smb, command_codes['SMB_COM_SESSION_SETUP_ANDX'], overrides)
|
|
|
|
-- Get the first account, unless they overrode it
|
|
if(overrides ~= nil and overrides['username'] ~= nil) then
|
|
result = true
|
|
username = overrides['username']
|
|
domain = overrides['domain']
|
|
password = overrides['password']
|
|
password_hash = overrides['password_hash']
|
|
hash_type = overrides['hash_type']
|
|
else
|
|
result, username, domain, password, password_hash, hash_type = smbauth.get_account(smb['host'])
|
|
end
|
|
|
|
while result ~= false do
|
|
local lanman, ntlm
|
|
|
|
lanman, ntlm, smb['mac_key'] = smbauth.get_password_response(smb['ip'], username, domain, password, password_hash, hash_type, smb['server_challenge'], false)
|
|
|
|
-- Parameters
|
|
parameters = bin.pack("<CCSSSSISSII",
|
|
0xFF, -- ANDX -- no further commands
|
|
0x00, -- ANDX -- Reserved (0)
|
|
0x0000, -- ANDX -- next offset
|
|
0xFFFF, -- Max buffer size
|
|
0x0001, -- Max multiplexes
|
|
0x0001, -- Virtual circuit num
|
|
smb['session_key'], -- The session key
|
|
#lanman, -- ANSI/Lanman password length
|
|
#ntlm, -- Unicode/NTLM password length
|
|
0x00000000, -- Reserved
|
|
0x00000050 -- Capabilities
|
|
)
|
|
|
|
-- Data is a list of strings, terminated by a blank one.
|
|
data = bin.pack("<AAzzzz",
|
|
lanman, -- ANSI/Lanman password
|
|
ntlm, -- Unicode/NTLM password
|
|
username, -- Account
|
|
domain, -- Domain
|
|
"Nmap", -- OS
|
|
"Native Lanman" -- Native LAN Manager
|
|
)
|
|
|
|
-- Send the session setup request
|
|
stdnse.debug2("SMB: Sending SMB_COM_SESSION_SETUP_ANDX")
|
|
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 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(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 [17]"
|
|
end
|
|
|
|
-- Check if we're successful
|
|
if(status == 0) then
|
|
|
|
-- Parse the parameters
|
|
pos, andx_command, andx_reserved, andx_offset, action = bin.unpack("<CCSS", parameters)
|
|
if(andx_command == nil or action == nil) then
|
|
return false, "SMB: ERROR: Server returned less data than it was supposed to (one or more fields are missing); aborting [18]"
|
|
end
|
|
|
|
-- Parse the data
|
|
pos, os, lanmanager, domain = bin.unpack("<zzz", data)
|
|
if(os == nil or domain == nil) then
|
|
return false, "SMB: ERROR: Server returned less data than it was supposed to (one or more fields are missing); aborting [19]"
|
|
end
|
|
|
|
-- Fill in the smb object and smb string
|
|
smb['uid'] = uid
|
|
smb['is_guest'] = bit.band(action, 1)
|
|
smb['os'] = os
|
|
smb['lanmanager'] = lanmanager
|
|
|
|
-- Check if they're using an un-supported system
|
|
if(os == nil or lanmanager == nil or domain == nil) then
|
|
stdnse.debug1("SMB: WARNING: the server is using a non-standard SMB implementation; your mileage may vary (%s)", smb['ip'])
|
|
elseif(os == "Unix" or string.sub(lanmanager, 1, 5) == "Samba") then
|
|
stdnse.debug1("SMB: WARNING: the server appears to be Unix; your mileage may vary.")
|
|
end
|
|
|
|
-- Check if they were logged in as a guest
|
|
if(log_errors == nil or log_errors == true) then
|
|
if(smb['is_guest'] == 1) then
|
|
stdnse.debug1("SMB: Login as %s\\%s failed, but was given guest access (username may be wrong, or system may only allow guest)", domain, stdnse.string_or_blank(username))
|
|
else
|
|
stdnse.debug2("SMB: Login as %s\\%s succeeded", domain, stdnse.string_or_blank(username))
|
|
end
|
|
end
|
|
|
|
-- Set the initial sequence number
|
|
smb['sequence'] = 1
|
|
|
|
return true
|
|
|
|
else
|
|
-- Check if we got the error NT_STATUS_REQUEST_NOT_ACCEPTED
|
|
if(status == 0xc00000d0) then
|
|
busy_count = busy_count + 1
|
|
|
|
if(busy_count > 9) then
|
|
return false, "SMB: ERROR: Server has too many active connections; giving up."
|
|
end
|
|
|
|
local backoff = math.random() * 10
|
|
stdnse.debug1("SMB: Server has too many active connections; pausing for %s seconds.", math.floor(backoff * 100) / 100)
|
|
stdnse.sleep(backoff)
|
|
else
|
|
-- This username failed, print a warning and keep going
|
|
if(log_errors == nil or log_errors == true) then
|
|
stdnse.debug1("SMB: Login as %s\\%s failed (%s)", domain, stdnse.string_or_blank(username), get_status_name(status))
|
|
end
|
|
|
|
-- Go to the next account
|
|
if(overrides == nil or overrides['username'] == nil) then
|
|
smbauth.next_account(smb['host'])
|
|
result, username, domain, password, password_hash, hash_type = smbauth.get_account(smb['host'])
|
|
else
|
|
result = false
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
if(log_errors ~= false) then
|
|
stdnse.debug1("SMB: ERROR: %s", username)
|
|
end
|
|
|
|
if (status ~= nil) then
|
|
return false, get_status_name(status)
|
|
else
|
|
return false, username
|
|
end
|
|
end
|
|
|
|
--- This is an internal function and should not be called externally. Use
|
|
-- the start_session() function instead.
|
|
local function start_session_extended(smb, log_errors, overrides)
|
|
local i
|
|
local status, status_name, result, err
|
|
local header, parameters, data
|
|
local pos
|
|
local header1, header2, header3, header4, command, status, flags, flags2, pid_high, signature, unused, tid, pid, uid, mid
|
|
local andx_command, andx_reserved, andx_offset, action, security_blob_length
|
|
local os, lanmanager
|
|
local username, domain, password, password_hash, hash_type
|
|
local busy_count = 0
|
|
|
|
-- Set a default status_name, in case everything fails
|
|
status_name = "An unknown error has occurred"
|
|
|
|
-- Get the first account, unless they overrode it
|
|
if(overrides ~= nil and overrides['username'] ~= nil) then
|
|
result = true
|
|
username = overrides['username']
|
|
domain = overrides['domain']
|
|
password = overrides['password']
|
|
password_hash = overrides['password_hash']
|
|
hash_type = overrides['hash_type']
|
|
else
|
|
result, username, domain, password, password_hash, hash_type = smbauth.get_account(smb['host'])
|
|
if(not(result)) then
|
|
return result, username
|
|
end
|
|
end
|
|
|
|
-- check what kind of security blob we were given in the negotiate protocol request
|
|
local sp_nego = false
|
|
if ( smb['security_blob'] and #smb['security_blob'] > 11 ) then
|
|
local pos, oid = bin.unpack(">A6", smb['security_blob'], 5)
|
|
sp_nego = ( oid == "\x2b\x06\x01\x05\x05\x02" ) -- check for SPNEGO OID 1.3.6.1.5.5.2
|
|
end
|
|
|
|
while result ~= false do
|
|
-- These are loop variables
|
|
local security_blob = nil
|
|
local security_blob_length = 0
|
|
|
|
-- This loop takes care of the multiple packets that "extended security" requires
|
|
repeat
|
|
-- Get the new security blob, passing the old security blob as a parameter. If there was no previous security blob, then nil is passed, which creates a new one
|
|
if ( not(security_blob) ) then
|
|
status, security_blob, smb['mac_key'] = smbauth.get_security_blob(security_blob, smb['ip'], username, domain, password, password_hash, hash_type, (sp_nego and 0x00088215))
|
|
|
|
if ( sp_nego ) then
|
|
local enc = asn1.ASN1Encoder:new()
|
|
local mechtype = enc:encode( { type = 'A0', value = enc:encode( { type = '30', value = enc:encode( { type = '06', value = bin.pack("H", "2b06010401823702020a") } ) } ) } )
|
|
local oid = enc:encode( { type = '06', value = bin.pack("H", "2b0601050502") } )
|
|
|
|
security_blob = enc:encode(security_blob)
|
|
security_blob = enc:encode( { type = 'A2', value = security_blob } )
|
|
security_blob = mechtype .. security_blob
|
|
security_blob = enc:encode( { type = '30', value = security_blob } )
|
|
security_blob = enc:encode( { type = 'A0', value = security_blob } )
|
|
security_blob = oid .. security_blob
|
|
security_blob = enc:encode( { type = '60', value = security_blob } )
|
|
end
|
|
else
|
|
if ( sp_nego ) then
|
|
if ( smb['domain'] or smb['server'] and ( not(domain) or #domain == 0 ) ) then
|
|
domain = smb['domain'] or smb['server']
|
|
end
|
|
hash_type = "ntlm"
|
|
end
|
|
|
|
status, security_blob, smb['mac_key'] = smbauth.get_security_blob(security_blob, smb['ip'], username, domain, password, password_hash, hash_type, (sp_nego and 0x00088215))
|
|
|
|
if ( sp_nego ) then
|
|
local enc = asn1.ASN1Encoder:new()
|
|
security_blob = enc:encode(security_blob)
|
|
security_blob = enc:encode( { type = 'A2', value = security_blob } )
|
|
security_blob = enc:encode( { type = '30', value = security_blob } )
|
|
security_blob = enc:encode( { type = 'A1', value = security_blob } )
|
|
end
|
|
|
|
end
|
|
|
|
-- There was an error processing the security blob
|
|
if(status == false) then
|
|
return false, string.format("SMB: ERROR: Security blob: %s", security_blob)
|
|
end
|
|
|
|
header = smb_encode_header(smb, command_codes['SMB_COM_SESSION_SETUP_ANDX'], overrides)
|
|
|
|
-- Data is a list of strings, terminated by a blank one.
|
|
data = bin.pack("<Azzz",
|
|
security_blob, -- Security blob
|
|
"Nmap", -- OS
|
|
"Native Lanman", -- Native LAN Manager
|
|
"" -- Primary domain
|
|
)
|
|
|
|
-- Parameters
|
|
parameters = bin.pack("<CCSSSSISII",
|
|
0xFF, -- ANDX -- no further commands
|
|
0x00, -- ANDX -- Reserved (0)
|
|
#data + 24 + #header + 3, -- ANDX -- next offset
|
|
0xFFFF, -- Max buffer size
|
|
0x0001, -- Max multiplexes
|
|
0x0001, -- Virtual circuit num
|
|
smb['session_key'], -- The session key
|
|
#security_blob, -- Security blob length
|
|
0x00000000, -- Reserved
|
|
0x80000050 -- Capabilities
|
|
)
|
|
|
|
-- Send the session setup request
|
|
stdnse.debug2("SMB: Sending SMB_COM_SESSION_SETUP_ANDX")
|
|
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 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(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 [17]"
|
|
end
|
|
smb['uid'] = uid
|
|
|
|
-- Get a human readable name
|
|
status_name = get_status_name(status)
|
|
|
|
-- Only parse the parameters if it's ok or if we're going to keep going
|
|
if(status_name == "NT_STATUS_SUCCESS" or status_name == "NT_STATUS_MORE_PROCESSING_REQUIRED") then
|
|
-- Parse the parameters
|
|
pos, andx_command, andx_reserved, andx_offset, action, security_blob_length = bin.unpack("<CCSSS", parameters)
|
|
if(andx_command == nil or security_blob_length == nil) then
|
|
return false, "SMB: ERROR: Server returned less data than it was supposed to (one or more fields are missing); aborting [18]"
|
|
end
|
|
smb['is_guest'] = bit.band(action, 1)
|
|
|
|
-- Parse the data
|
|
pos, security_blob, os, lanmanager = bin.unpack(string.format("<A%dzz", security_blob_length), data)
|
|
|
|
if ( status_name == "NT_STATUS_MORE_PROCESSING_REQUIRED" and sp_nego ) then
|
|
local start = security_blob:find("NTLMSSP")
|
|
security_blob = security_blob:sub(start)
|
|
end
|
|
|
|
if(security_blob == nil or lanmanager == nil) then
|
|
return false, "SMB: ERROR: Server returned less data than it was supposed to (one or more fields are missing); aborting [19]"
|
|
end
|
|
smb['os'] = os
|
|
smb['lanmanager'] = lanmanager
|
|
|
|
local host_info = smbauth.get_host_info_from_security_blob(security_blob)
|
|
if ( host_info ) then
|
|
smb['fqdn'] = host_info['fqdn']
|
|
smb['domain_dns'] = host_info['dns_domain_name']
|
|
smb['forest_dns'] = host_info['dns_forest_name']
|
|
smb['server'] = host_info['netbios_computer_name']
|
|
smb['domain'] = host_info['netbios_domain_name']
|
|
end
|
|
|
|
|
|
-- If it's ok, do a cleanup and return true
|
|
if(status_name == "NT_STATUS_SUCCESS") then
|
|
-- Check if they're using an un-supported system
|
|
if(os == nil or lanmanager == nil) then
|
|
stdnse.debug1("SMB: WARNING: the server is using a non-standard SMB implementation; your mileage may vary (%s)", smb['ip'])
|
|
elseif(os == "Unix" or string.sub(lanmanager, 1, 5) == "Samba") then
|
|
stdnse.debug1("SMB: WARNING: the server appears to be Unix; your mileage may vary.")
|
|
end
|
|
|
|
-- Check if they were logged in as a guest
|
|
if(log_errors == nil or log_errors == true) then
|
|
if(smb['is_guest'] == 1) then
|
|
stdnse.debug1("SMB: Extended login to %s as %s\\%s failed, but was given guest access (username may be wrong, or system may only allow guest)", smb['ip'], domain, stdnse.string_or_blank(username))
|
|
else
|
|
stdnse.debug2("SMB: Extended login to %s as %s\\%s succeeded", smb['ip'], domain, stdnse.string_or_blank(username))
|
|
end
|
|
end
|
|
|
|
-- Set the initial sequence number
|
|
smb['sequence'] = 1
|
|
|
|
return true
|
|
end -- Status is ok
|
|
end -- Should we parse the parameters/data?
|
|
until status_name ~= "NT_STATUS_MORE_PROCESSING_REQUIRED"
|
|
|
|
-- Check if we got the error NT_STATUS_REQUEST_NOT_ACCEPTED
|
|
if(status == 0xc00000d0) then
|
|
busy_count = busy_count + 1
|
|
|
|
if(busy_count > 9) then
|
|
return false, "SMB: ERROR: Server has too many active connections; giving up."
|
|
end
|
|
|
|
local backoff = math.random() * 10
|
|
stdnse.debug1("SMB: Server has too many active connections; pausing for %s seconds.", math.floor(backoff * 100) / 100)
|
|
stdnse.sleep(backoff)
|
|
else
|
|
-- Display a message to the user, and try the next account
|
|
if(log_errors == nil or log_errors == true) then
|
|
stdnse.debug1("SMB: Extended login to %s as %s\\%s failed (%s)", smb['ip'], domain, stdnse.string_or_blank(username), status_name)
|
|
end
|
|
|
|
-- Go to the next account
|
|
if(overrides == nil or overrides['username'] == nil) then
|
|
smbauth.next_account(smb['host'])
|
|
result, username, domain, password, password_hash, hash_type = smbauth.get_account(smb['host'])
|
|
if(not(result)) then
|
|
return false, username
|
|
end
|
|
else
|
|
result = false
|
|
end
|
|
end
|
|
|
|
-- Reset the user id
|
|
smb['uid'] = 0
|
|
|
|
end -- Loop over the accounts
|
|
|
|
if(log_errors == nil or log_errors == true) then
|
|
stdnse.debug1("SMB: ERROR: All logins failed, sorry it didn't work out!")
|
|
end
|
|
|
|
return false, status_name
|
|
end
|
|
|
|
--- Sends out SMB_COM_SESSION_SETUP_ANDX, which attempts to log a user in.
|
|
--
|
|
-- Sends the following:
|
|
-- * Negotiated parameters (multiplexed connections, virtual circuit, capabilities)
|
|
-- * Passwords (plaintext, unicode, lanman, ntlm, lmv2, ntlmv2, etc)
|
|
-- * Account name
|
|
-- * OS (I just send "Nmap")
|
|
-- * Native LAN Manager (no clue what that is, but it seems to be ignored)
|
|
--
|
|
-- Receives the following:
|
|
-- * User ID
|
|
-- * Server OS
|
|
--
|
|
--@param smb The SMB object associated with the connection
|
|
--@param overrides [optional] A table of overrides for username, domain, password, password_hash, and hash_type.
|
|
-- If any of these are given, it's used first. If they aren't, then Nmap parameters, Nmap registry entries,
|
|
-- guest, and NULL sessions are used.
|
|
--@param log_errors [optional] If set, will display login. Default: true.
|
|
--@return (status, result) If status is false, result is an error message. Otherwise, result is nil and the following
|
|
-- elements are added to the smb table:
|
|
-- * 'uid' The UserID for the session
|
|
-- * 'is_guest' If set, the username wasn't found so the user was automatically logged in as the guest account
|
|
-- * 'os' The operating system
|
|
-- * 'lanmanager' The server's LAN Manager
|
|
function start_session(smb, overrides, log_errors)
|
|
-- Use a mutex to avoid some issues (see http://seclists.org/nmap-dev/2011/q1/464)
|
|
local smb_auth_mutex = nmap.mutex( "SMB Authentication Mutex" )
|
|
smb_auth_mutex( "lock" )
|
|
|
|
local status, result
|
|
if(smb['extended_security'] == true) then
|
|
status, result = start_session_extended(smb, log_errors, overrides)
|
|
else
|
|
status, result = start_session_basic(smb, log_errors, overrides)
|
|
end
|
|
|
|
smb_auth_mutex( "done" )
|
|
return status, result
|
|
end
|
|
|
|
--- Sends out <code>SMB_COM_SESSION_TREE_CONNECT_ANDX</code>, which attempts to
|
|
-- connect to a share.
|
|
--
|
|
-- Sends the following:
|
|
-- * Password (for share-level security, which we don't support)
|
|
-- * Share name
|
|
-- * Share type (or "?????" if it's unknown, that's what we do)
|
|
--
|
|
-- Receives the following:
|
|
-- * Tree ID
|
|
--
|
|
--@param smb The SMB object associated with the connection
|
|
--@param path The path to connect (eg, <code>"\\servername\C$"</code>)
|
|
--@param overrides [optional] Overrides for various fields
|
|
--@return (status, result) If status is false, result is an error message. Otherwise, result is a
|
|
-- table with the following elements:
|
|
-- * 'tid' The TreeID for the session
|
|
function tree_connect(smb, path, overrides)
|
|
local header, parameters, data, err, result
|
|
local pos
|
|
local header1, header2, header3, header4, command, status, flags, flags2, pid_high, signature, unused, pid, mid
|
|
local andx_command, andx_reserved, andx_offset, action
|
|
|
|
-- Make sure we have overrides
|
|
overrides = overrides or {}
|
|
|
|
header = smb_encode_header(smb, command_codes['SMB_COM_TREE_CONNECT_ANDX'], overrides)
|
|
parameters = bin.pack("<CCSSS",
|
|
0xFF, -- ANDX no further commands
|
|
0x00, -- ANDX reserved
|
|
0x0000, -- ANDX offset
|
|
(overrides['tree_connect_flags'] or 0x0000), -- flags
|
|
0x0000 -- password length (for share-level security)
|
|
)
|
|
data = bin.pack("zz",
|
|
-- Share-level password
|
|
path, -- Path
|
|
(overrides['tree_type'] or "?????") -- Type of tree ("?????" = any)
|
|
)
|
|
|
|
-- Send the tree connect request
|
|
stdnse.debug2("SMB: Sending SMB_COM_TREE_CONNECT_ANDX")
|
|
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 we were allowed in
|
|
local uid, tid
|
|
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 [20]"
|
|
end
|
|
|
|
if(status ~= 0) then
|
|
return false, get_status_name(status)
|
|
end
|
|
|
|
if(tid == 0 or tonumber(tid) == 0) then
|
|
return false, "SMB: ERROR: Server didn't establish a proper tree connection (likely an embedded system)"
|
|
end
|
|
|
|
smb['tid'] = tid
|
|
|
|
return true
|
|
|
|
end
|
|
|
|
--- Disconnects a tree session. Should be called before logging off and disconnecting.
|
|
--@param smb The SMB object associated with the connection
|
|
--@param overrides THe overrides table
|
|
--@return (status, result) If status is false, result is an error message. If status is true,
|
|
-- the disconnect was successful.
|
|
function tree_disconnect(smb, overrides)
|
|
overrides = overrides or {}
|
|
local header
|
|
local header1, header2, header3, header4, command, status, flags, flags2, pid_high, signature, unused, pid, mid
|
|
|
|
header = smb_encode_header(smb, command_codes['SMB_COM_TREE_DISCONNECT'], overrides)
|
|
|
|
-- Send the tree disconnect request
|
|
stdnse.debug2("SMB: Sending SMB_COM_TREE_DISCONNECT")
|
|
local result, err = smb_send(smb, header, "", "", overrides)
|
|
if(result == false) then
|
|
return false, err
|
|
end
|
|
|
|
-- Read the result
|
|
local status, header, parameters, data = smb_read(smb)
|
|
if(status ~= true) then
|
|
return false, header
|
|
end
|
|
|
|
-- Check if there was an error
|
|
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 [21]"
|
|
end
|
|
if(status ~= 0) then
|
|
return false, get_status_name(status)
|
|
end
|
|
|
|
smb['tid'] = 0
|
|
|
|
return true
|
|
|
|
end
|
|
|
|
---Logs off the current user. Strictly speaking this isn't necessary, but it's the polite thing to do.
|
|
--
|
|
--@param smb The SMB object associated with the connection
|
|
--@param overrides THe overrides table
|
|
--@return (status, result) If status is false, result is an error message. If status is true,
|
|
-- the logoff was successful.
|
|
function logoff(smb, overrides)
|
|
overrides = overrides or {}
|
|
local header, parameters, data
|
|
local header1, header2, header3, header4, command, status, flags, flags2, pid_high, signature, unused, pid, mid
|
|
|
|
header = smb_encode_header(smb, command_codes['SMB_COM_LOGOFF_ANDX'], overrides)
|
|
|
|
-- Parameters are a blank ANDX block
|
|
parameters = bin.pack("<CCS",
|
|
0xFF, -- ANDX no further commands
|
|
0x00, -- ANDX reserved
|
|
0x0000 -- ANDX offset
|
|
)
|
|
|
|
-- Send the tree disconnect request
|
|
stdnse.debug2("SMB: Sending SMB_COM_LOGOFF_ANDX")
|
|
local result, err = smb_send(smb, header, parameters, "", 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
|
|
|
|
-- Reset session variables (note: this has to come after the smb_read(), otherwise the message signatures cause a problem
|
|
smb['uid'] = 0
|
|
smb['sequence'] = -1
|
|
smb['mac_key'] = nil
|
|
|
|
-- Check if there was an error
|
|
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 [22]"
|
|
end
|
|
|
|
if(status == 0xc0000022) then
|
|
stdnse.debug1("SMB: ERROR: Access was denied in 'logoff', indicating a problem with your message signatures")
|
|
return false, "SMB: ERROR: Access was denied in 'logoff', indicating a problem with your message signatures"
|
|
end
|
|
if(status ~= 0) then
|
|
return false, get_status_name(status)
|
|
end
|
|
|
|
return true
|
|
|
|
end
|
|
|
|
--- This sends a SMB request to open or create a file.
|
|
--
|
|
-- Most of the parameters I pass here are used directly from a packetlog,
|
|
-- especially the various permissions fields and flags. I might make this
|
|
-- more adjustable in the future, but this has been working for me.
|
|
--
|
|
--@param smb The SMB object associated with the connection
|
|
--@param path The path of the file or pipe to open
|
|
--@param overrides [optional] Overrides for various fields
|
|
--@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 create_file(smb, path, overrides)
|
|
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 oplock_level, fid, create_action, created, last_access, last_write, last_change, attributes, allocation_size, end_of_file, filetype, ipc_state, is_directory
|
|
local error_count = 0
|
|
|
|
repeat
|
|
local mutex = nmap.mutex(smb['host'])
|
|
mutex "lock"
|
|
|
|
-- Make sure we have overrides
|
|
overrides = overrides or {}
|
|
|
|
header = smb_encode_header(smb, command_codes['SMB_COM_NT_CREATE_ANDX'], overrides)
|
|
parameters = bin.pack("<CCSCSIIILIIIIIC",
|
|
0xFF, -- ANDX no further commands
|
|
0x00, -- ANDX reserved
|
|
0x0000, -- ANDX offset
|
|
0x00, -- Reserved
|
|
#path, -- Path length
|
|
(overrides['file_create_flags'] or 0x00000016), -- Create flags
|
|
(overrides['file_create_root_fid'] or 0x00000000), -- Root FID
|
|
(overrides['file_create_access_mask'] or 0x02000000), -- Access mask
|
|
(overrides['file_create_allocation_size'] or 0x0000000000000000), -- Allocation size
|
|
(overrides['file_create_attributes'] or 0x00000000), -- File attributes
|
|
(overrides['file_create_share_attributes'] or 0x00000007), -- Share attributes
|
|
(overrides['file_create_disposition'] or 0x00000000), -- Disposition
|
|
(overrides['file_create_options'] or 0x00000000), -- Create options
|
|
(overrides['file_create_impersonation'] or 0x00000002), -- Impersonation
|
|
(overrides['file_create_security_flags'] or 0x01) -- Security flags
|
|
)
|
|
|
|
data = bin.pack("z", path)
|
|
|
|
-- Send the create file
|
|
stdnse.debug2("SMB: Sending SMB_COM_NT_CREATE_ANDX")
|
|
local result, err = smb_send(smb, header, parameters, data, overrides)
|
|
if(result == false) then
|
|
mutex "done"
|
|
return false, err
|
|
end
|
|
|
|
-- Read the result
|
|
status, header, parameters, data = smb_read(smb, false)
|
|
mutex "done"
|
|
if(status ~= true) then
|
|
return false, header
|
|
end
|
|
|
|
-- Check if we were allowed in
|
|
local uid, tid
|
|
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 [23]"
|
|
end
|
|
if(status == 0xc00000ac) then
|
|
error_count = error_count + 1
|
|
if(error_count > 10) then
|
|
return false, "SMB: ERROR: Server returned NT_STATUS_PIPE_NOT_AVAILABLE too many times; giving up."
|
|
end
|
|
stdnse.debug1("WARNING: Server refused connection with NT_STATUS_PIPE_NOT_AVAILABLE; trying again")
|
|
stdnse.sleep(.2)
|
|
end
|
|
until (status ~= 0xc00000ac)
|
|
|
|
if(status ~= 0) then
|
|
return false, get_status_name(status)
|
|
end
|
|
|
|
-- Parse the parameters
|
|
pos, andx_command, andx_reserved, andx_offset, oplock_level, fid, create_action, created, last_access, last_write, last_change, attributes, allocation_size, end_of_file, filetype, ipc_state, is_directory = bin.unpack("<CCSCSILLLLILLSSC", parameters)
|
|
if(andx_command == nil or is_directory == nil) then
|
|
return false, "SMB: ERROR: Server returned less data than it was supposed to (one or more fields are missing); aborting [24]"
|
|
end
|
|
|
|
-- Fill in the smb table
|
|
smb['oplock_level'] = oplock_level
|
|
smb['fid'] = fid
|
|
smb['create_action'] = create_action
|
|
smb['created'] = created
|
|
smb['last_access'] = last_access
|
|
smb['last_write'] = last_write
|
|
smb['last_change'] = last_change
|
|
smb['attributes'] = attributes
|
|
smb['allocation_size'] = allocation_size
|
|
smb['end_of_file'] = end_of_file
|
|
smb['filetype'] = filetype
|
|
smb['ipc_state'] = ipc_state
|
|
smb['is_directory'] = is_directory
|
|
|
|
return true
|
|
end
|
|
|
|
--- This sends a SMB request to read from a file (or a pipe).
|
|
--
|
|
--@param smb The SMB object associated with the connection
|
|
--@param offset The offset to read from (ignored if it's a pipe)
|
|
--@param count The maximum number of bytes to read
|
|
--@param overrides The overrides table
|
|
--@return (status, result) If status is false, result is an error message. Otherwise, result is a table
|
|
-- containing a lot of different elements.
|
|
function read_file(smb, offset, count, overrides)
|
|
overrides = overrides or {}
|
|
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'], overrides)
|
|
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.debug2("SMB: Sending SMB_COM_READ_ANDX")
|
|
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 we were allowed in
|
|
local uid, tid
|
|
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 [25]"
|
|
end
|
|
if(status ~= 0 and
|
|
(status ~= status_codes.NT_STATUS_BUFFER_OVERFLOW and (smb['filetype'] == filetype_codes.FILE_TYPE_BYTE_MODE_PIPE or
|
|
smb['filetype'] == filetype_codes.FILE_TYPE_MESSAGE_MODE_PIPE) ) ) 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(andx_command == nil or reserved_3 == nil) then
|
|
return false, "SMB: ERROR: Server returned less data than it was supposed to (one or more fields are missing); aborting [26]"
|
|
end
|
|
|
|
response['remaining'] = remaining
|
|
response['data_length'] = bit.bor(data_length_low, bit.lshift(data_length_high, 16))
|
|
response['status'] = status
|
|
|
|
|
|
-- data_start is the offset of the beginning of the data section -- we use this to calculate where the read data lives
|
|
if(response['data_length'] == 0) then
|
|
response['data'] = 0
|
|
else
|
|
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'])
|
|
end
|
|
|
|
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 overrides The overrides table
|
|
--@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, overrides)
|
|
overrides = overrides or {}
|
|
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'], overrides)
|
|
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.debug2("SMB: Sending SMB_COM_WRITE_ANDX")
|
|
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
|
|
|
|
local uid, tid
|
|
-- 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(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 [27]"
|
|
end
|
|
if(status ~= 0) then
|
|
return false, get_status_name(status)
|
|
end
|
|
|
|
-- Parse the parameters
|
|
local count_reserved, count_high, remaining, count_low
|
|
pos, andx_command, andx_reserved, andx_offset, count_low, remaining, count_high, count_reserved = bin.unpack("<CCSSSSS", parameters)
|
|
if(count_reserved == nil) then
|
|
return false, "SMB: ERROR: Server returned less data than it was supposed to (one or more fields are missing); aborting [28]"
|
|
end
|
|
|
|
response['count_low'] = count_low
|
|
response['remaining'] = remaining
|
|
response['count_high'] = count_high
|
|
response['reserved'] = count_reserved
|
|
|
|
return true, response
|
|
end
|
|
|
|
--- This sends a SMB request to close a file (or a pipe).
|
|
--
|
|
--@param smb The SMB object associated with the connection
|
|
--@param overrides The overrides table
|
|
--@return (status, result) If status is false, result is an error message. Otherwise, result is undefined.
|
|
function close_file(smb, overrides)
|
|
overrides = overrides or {}
|
|
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_CLOSE'], overrides)
|
|
parameters = bin.pack("<SI",
|
|
smb['fid'], -- FID
|
|
0xFFFFFFFF -- Last write (unspecified)
|
|
)
|
|
|
|
data = ""
|
|
|
|
-- Send the close file
|
|
stdnse.debug2("SMB: Sending SMB_CLOSE")
|
|
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 the close was successful
|
|
local uid, tid
|
|
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 [27]"
|
|
end
|
|
if(status ~= 0) then
|
|
return false, get_status_name(status)
|
|
end
|
|
|
|
-- Close response has no parameters or data
|
|
return true, response
|
|
end
|
|
|
|
--- This sends a SMB request to delete a file (or a pipe).
|
|
--
|
|
--@param smb The SMB object associated with the connection
|
|
--@param path The path of the file to delete
|
|
--@param overrides The overrides table
|
|
--@return (status, result) If status is false, result is an error message. Otherwise, result is undefined.
|
|
function delete_file(smb, path, overrides)
|
|
overrides = overrides or {}
|
|
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
|
|
|
|
header = smb_encode_header(smb, command_codes['SMB_COM_DELETE'], overrides)
|
|
parameters = bin.pack("<S",
|
|
0x0027 -- Search attributes (0x27 = include read only, hidden, system, and archive)
|
|
)
|
|
|
|
data = bin.pack("<Cz",
|
|
0x04, -- Ascii formatted filename
|
|
path)
|
|
|
|
-- Send the close file
|
|
stdnse.debug2("SMB: Sending SMB_CLOSE")
|
|
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 the close was successful
|
|
local uid, tid
|
|
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 [27]"
|
|
end
|
|
if(status ~= 0) then
|
|
return false, get_status_name(status)
|
|
end
|
|
|
|
-- Close response has no parameters or data
|
|
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("<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.debug2("SMB: Sending SMB_COM_TRANSACTION2")
|
|
local result, err = smb_send(smb, header, parameters, data, overrides)
|
|
if(result == false) then
|
|
return false, err
|
|
end
|
|
|
|
return true
|
|
end
|
|
|
|
local function receive_transaction2(smb)
|
|
|
|
-- Read the result
|
|
local status, header, parameters, data = smb_read(smb)
|
|
if(status ~= true) then
|
|
return false, header
|
|
end
|
|
|
|
-- Check if it worked
|
|
local 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
|
|
local 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'.
|
|
local function_parameters = string.sub(data, parameter_offset + 1, parameter_offset + parameter_count)
|
|
local function_data = string.sub(data, data_offset + 1, data_offset + data_count)
|
|
|
|
local response = {}
|
|
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.
|
|
--
|
|
-- 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.
|
|
--
|
|
-- It is probably best to think of this as another protocol layer. This
|
|
-- function will wrap SMB stuff around a MSRPC call, make the call, then unwrap
|
|
-- the SMB stuff from it before returning.
|
|
--
|
|
--@param smb The SMB object associated with the connection
|
|
--@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 pipe [optional] The pipe to transact on. Default: "\PIPE\".
|
|
--@param no_setup [optional] If set, the 'setup' is set to 0 and some
|
|
-- parameters are left off. This occurs while using the LANMAN
|
|
-- Remote API. Default: false.
|
|
--@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.
|
|
function send_transaction_named_pipe(smb, function_parameters, function_data, pipe, no_setup, 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 = {}
|
|
|
|
if(pipe == nil) then
|
|
pipe = "\\PIPE\\"
|
|
end
|
|
|
|
-- Header is 0x20 bytes long (not counting NetBIOS header).
|
|
header = smb_encode_header(smb, command_codes['SMB_COM_TRANSACTION'], overrides) -- 0x25 = SMB_COM_TRANSACTION
|
|
|
|
-- 0x20 for SMB header, 0x01 for parameters header, 0x20 for parameters length, 0x02 for data header, 0x07 for "\PIPE\"
|
|
if(function_parameters) then
|
|
parameter_offset = 0x20 + 0x01 + 0x20 + 0x02 + (#pipe + 1)
|
|
parameter_size = #function_parameters
|
|
end
|
|
|
|
if(function_data) then
|
|
data_offset = 0x20 + 0x01 + 0x20 + 0x02 + (#pipe + 1) + parameter_size
|
|
data_size = #function_data
|
|
end
|
|
|
|
-- Parameters are 0x20 bytes long.
|
|
parameters = bin.pack("<SSSSCCSISSSSS",
|
|
parameter_size, -- Total parameter count.
|
|
data_size, -- Total data count.
|
|
0x0008, -- 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.
|
|
)
|
|
|
|
if(no_setup) then
|
|
parameters = parameters .. bin.pack("<CC",
|
|
0x00, -- Number of 'setup' words (none)
|
|
0x00 -- Reserved.
|
|
)
|
|
else
|
|
parameters = parameters .. bin.pack("<CCSS",
|
|
0x02, -- Number of 'setup' words
|
|
0x00, -- Reserved.
|
|
0x0026, -- Function to call.
|
|
smb['fid'] -- Handle to open file
|
|
)
|
|
end
|
|
|
|
data = bin.pack("<z", pipe)
|
|
data = data .. bin.pack("<I", 0) -- Padding
|
|
|
|
data = data .. (function_parameters or '')
|
|
data = data .. (function_data or '')
|
|
|
|
-- Send the transaction request
|
|
stdnse.debug2("SMB: Sending SMB_COM_TRANSACTION")
|
|
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
|
|
|
|
function send_transaction_waitnamedpipe(smb, priority, pipe, 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, data_offset
|
|
local total_word_count, total_data_count, reserved1, parameter_count, parameter_offset, parameter_displacement, data_count, data_offset, data_displacement, setup_count, reserved2
|
|
local response = {}
|
|
local padding = ""
|
|
|
|
-- Header is 0x20 bytes long (not counting NetBIOS header).
|
|
header = smb_encode_header(smb, command_codes['SMB_COM_TRANSACTION'], overrides) -- 0x25 = SMB_COM_TRANSACTION
|
|
|
|
-- Parameters are 0x20 bytes long.
|
|
parameters = bin.pack("<SSSSCCSISSSSSCCSS",
|
|
0, -- Total parameter count.
|
|
0, -- Total data count.
|
|
0x000, -- Max parameter count.
|
|
0x400, -- Max data count.
|
|
0x00, -- Max setup count.
|
|
0x00, -- Reserved.
|
|
0x0000, -- Flags (0x0000 = 2-way transaction, don't disconnect TIDs).
|
|
30, -- Timeout (0x00000000 = return immediately).
|
|
0x0000, -- Reserved.
|
|
0, -- Parameter bytes.
|
|
0, -- Parameter offset.
|
|
0, -- Data bytes.
|
|
0, -- Data offset.
|
|
0x02, -- Number of 'setup' words (only ever seen '2').
|
|
0x00, -- Reserved.
|
|
0x0053, -- Function to call.
|
|
priority -- Handle to open file
|
|
)
|
|
|
|
while(((#pipe + 1 + #padding) % 4) ~= 0) do
|
|
padding = padding .. string.char(0)
|
|
end
|
|
data = bin.pack("<zA", pipe, padding);
|
|
|
|
-- Send the transaction request
|
|
stdnse.debug2("SMB: Sending SMB_COM_TRANSACTION (WaitNamedPipe)")
|
|
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 [31]"
|
|
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 [32]"
|
|
end
|
|
|
|
return true, response
|
|
end
|
|
|
|
---Upload a file from the local machine to the remote machine, on the given share.
|
|
--
|
|
--@param host The host object
|
|
--@param localfile The file on the local machine, relative to the nmap path
|
|
--@param share The share to upload it to (eg, C$).
|
|
--@param remotefile The remote file on the machine. It is relative to the share's root.
|
|
--@param overrides A table of override values that's passed to the smb functions.
|
|
--@param encoded Set to 'true' if the file is encoded (xor'ed with 0xFF), It will be decoded before upload. Default: false
|
|
--@return (status, err) If status is false, err is an error message. Otherwise, err is undefined.
|
|
function file_upload(host, localfile, share, remotefile, overrides, encoded)
|
|
local status, err, smbstate
|
|
local chunk = 1024
|
|
|
|
-- Attempt to open a handle to the file without adding a path to it
|
|
local handle = io.open(localfile, "r")
|
|
|
|
-- If the open failed, try to search for the file
|
|
if(not(handle)) then
|
|
stdnse.debug1("Couldn't open %s directly, searching Nmap's paths...", localfile)
|
|
local filename = nmap.fetchfile(localfile)
|
|
|
|
-- Check if it was found
|
|
if(filename == nil) then
|
|
return false, string.format("Couldn't find the file to upload (%s)", localfile)
|
|
end
|
|
handle = io.open(filename, "r")
|
|
end
|
|
|
|
-- Create the SMB session
|
|
status, smbstate = start_ex(host, true, true, share, remotefile, nil, overrides)
|
|
if(status == false) then
|
|
return false, smbstate
|
|
end
|
|
|
|
|
|
local i = 0
|
|
local data = handle:read(chunk)
|
|
while(data ~= nil and #data > 0) do
|
|
|
|
if(encoded) then
|
|
local new_data = ""
|
|
for j = 1, #data, 1 do
|
|
new_data = new_data .. string.char(bit.bxor(0xFF, string.byte(data, j)))
|
|
end
|
|
data = new_data
|
|
end
|
|
|
|
status, err = write_file(smbstate, data, i)
|
|
if(status == false) then
|
|
stop(smbstate)
|
|
return false, err
|
|
end
|
|
|
|
data = handle:read(chunk)
|
|
i = i + chunk
|
|
end
|
|
|
|
handle:close()
|
|
status, err = close_file(smbstate)
|
|
if(status == false) then
|
|
stop(smbstate)
|
|
return false, err
|
|
end
|
|
|
|
-- Stop the session
|
|
stop(smbstate)
|
|
|
|
return true
|
|
end
|
|
|
|
---Write given data to the remote machine on the given share. This is similar to <code>file_upload</code>, except the
|
|
-- data is given as a string, not a file.
|
|
--
|
|
--@param host The host object
|
|
--@param share The share to upload it to (eg, C$).
|
|
--@param remotefile The remote file on the machine. It is relative to the share's root.
|
|
--@param use_anonymous [optional] If set to 'true', test is done by the anonymous user rather than the current user.
|
|
--@return (status, err) If status is false, err is an error message. Otherwise, err is undefined.
|
|
function file_write(host, data, share, remotefile, use_anonymous)
|
|
local status, err, smbstate
|
|
local chunk = 1024
|
|
local overrides = nil
|
|
|
|
-- If anonymous is being used, create some overrides
|
|
if(use_anonymous) then
|
|
overrides = get_overrides_anonymous()
|
|
end
|
|
|
|
-- Create the SMB session
|
|
status, smbstate = start_ex(host, true, true, share, remotefile, nil, overrides)
|
|
|
|
if(status == false) then
|
|
return false, smbstate
|
|
end
|
|
|
|
local i = 1
|
|
while(i <= #data) do
|
|
local chunkdata = string.sub(data, i, i + chunk - 1)
|
|
status, err = write_file(smbstate, chunkdata, i - 1)
|
|
if(status == false) then
|
|
stop(smbstate)
|
|
return false, err
|
|
end
|
|
|
|
i = i + chunk
|
|
end
|
|
|
|
status, err = close_file(smbstate)
|
|
if(status == false) then
|
|
stop(smbstate)
|
|
return false, err
|
|
end
|
|
|
|
-- Stop the session
|
|
stop(smbstate)
|
|
|
|
return true
|
|
end
|
|
|
|
---Write given data to the remote machine on the given share. This is similar to <code>file_upload</code>, except the
|
|
-- data is given as a string, not a file.
|
|
--
|
|
--@param host The host object
|
|
--@param share The share to read it from (eg, C$).
|
|
--@param remotefile The remote file on the machine. It is relative to the share's root.
|
|
--@param use_anonymous [optional] If set to 'true', test is done by the anonymous user rather than the current user.
|
|
--@param overrides [optional] Override various fields in the SMB packets.
|
|
--@return (status, err) If status is false, err is an error message. Otherwise, err is undefined.
|
|
function file_read(host, share, remotefile, use_anonymous, overrides)
|
|
local status, err, smbstate
|
|
local result
|
|
local chunk = 1024
|
|
local read = ""
|
|
|
|
-- Make sure we got overrides
|
|
overrides = overrides or {}
|
|
|
|
-- If anonymous is being used, create some overrides
|
|
if(use_anonymous) then
|
|
overrides = get_overrides_anonymous(overrides)
|
|
end
|
|
|
|
-- Create the SMB session
|
|
status, smbstate = start_ex(host, true, true, share, remotefile, nil, overrides)
|
|
|
|
if(status == false) then
|
|
return false, smbstate
|
|
end
|
|
|
|
local i = 1
|
|
while true do
|
|
status, result = read_file(smbstate, i - 1, chunk)
|
|
if(status == false) then
|
|
stop(smbstate)
|
|
return false, result
|
|
end
|
|
|
|
if(result['data_length'] == 0) then
|
|
break
|
|
end
|
|
|
|
read = read .. result['data']
|
|
i = i + chunk
|
|
end
|
|
|
|
status, err = close_file(smbstate)
|
|
if(status == false) then
|
|
stop(smbstate)
|
|
return false, err
|
|
end
|
|
|
|
-- Stop the session
|
|
stop(smbstate)
|
|
return true, read
|
|
end
|
|
|
|
---Check how many files, in a given list, exist on the given share.
|
|
--
|
|
--@param host The host object
|
|
--@param share The share to read it from (eg, C$).
|
|
--@param files A list of files to look for; it is relative to the share's root.
|
|
--@param overrides [optional] Override various fields in the SMB packets.
|
|
--@return status: A true/false value indicating success
|
|
--@return count: The number of files that existed, or an error message if status is 'false'
|
|
--@return files: A list of the files that existed.
|
|
function files_exist(host, share, files, overrides)
|
|
local status, smbstate, result, err
|
|
|
|
-- Make sure we got overrides
|
|
overrides = overrides or {}
|
|
|
|
-- We don't wan to be creating the files
|
|
overrides['file_create_disposition'] = 1
|
|
|
|
-- Create the SMB session
|
|
status, smbstate = start_ex(host, true, true, share, nil, nil, overrides)
|
|
|
|
if(status == false) then
|
|
return false, smbstate
|
|
end
|
|
|
|
local exist = 0
|
|
local list = {}
|
|
|
|
for _, file in ipairs(files) do
|
|
-- Try and open the file
|
|
status, result = create_file(smbstate, file, overrides)
|
|
|
|
-- If there was an error other than 'file already exists', return an error
|
|
if(not(status) and result ~= 'NT_STATUS_OBJECT_NAME_NOT_FOUND') then
|
|
return false, result
|
|
end
|
|
|
|
-- If the file existed, count it and close it
|
|
if(status) then
|
|
exist = exist + 1
|
|
table.insert(list, file)
|
|
status, err = close_file(smbstate)
|
|
if(status == false) then
|
|
stop(smbstate)
|
|
return false, err
|
|
end
|
|
end
|
|
end
|
|
|
|
-- Stop the session
|
|
stop(smbstate)
|
|
return true, exist, list
|
|
end
|
|
|
|
---Delete a file from the remote machine
|
|
--
|
|
--@param host The host object
|
|
--@param share The share to upload it to (eg, C$).
|
|
--@param remotefile The remote file on the machine. It is relative to the share's root. It can be a string, or an array.
|
|
--@return (status, err) If status is false, err is an error message. Otherwise, err is undefined.
|
|
function file_delete(host, share, remotefile)
|
|
local status, smbstate, err
|
|
|
|
-- Create the SMB session
|
|
status, smbstate = start_ex(host, true, true, share)
|
|
if(status == false) then
|
|
return false, smbstate
|
|
end
|
|
|
|
-- Make sure the remotefile is always a table, to save on duplicate code
|
|
if(type(remotefile) ~= "table") then
|
|
remotefile = {remotefile}
|
|
end
|
|
|
|
|
|
for _, file in ipairs(remotefile) do
|
|
status, err = delete_file(smbstate, file)
|
|
if(status == false) then
|
|
stdnse.debug1("SMB: Couldn't delete %s\\%s: %s", share, file, err)
|
|
if(err ~= 'NT_STATUS_OBJECT_NAME_NOT_FOUND') then
|
|
stop(smbstate)
|
|
return false, err
|
|
end
|
|
end
|
|
end
|
|
|
|
-- Stop the session
|
|
stop(smbstate)
|
|
|
|
return true
|
|
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 retrieving 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
|
|
|
|
fname = fname .. '\0'
|
|
|
|
-- Sends the request and takes care of short/fragmented responses
|
|
local function send_and_receive_find_request(smbstate, trans_type, function_parameters)
|
|
|
|
local status, err = send_transaction2(smbstate, trans_type, function_parameters, "")
|
|
if ( not(status) ) then
|
|
return false, "Failed to send data to server: send_transaction2"
|
|
end
|
|
|
|
local status, response = receive_transaction2(smbstate)
|
|
if ( not(status) ) then
|
|
return false, "Failed to receive data from server: receive_transaction2"
|
|
end
|
|
|
|
local pos = ( TRANS2_FIND_FIRST2 == trans_type and 9 or 7 )
|
|
local last_name_offset = select(2, bin.unpack("<S", response.parameters, pos))
|
|
|
|
if ( not(last_name_offset) ) then
|
|
return false, "Could not determine last_name_offset"
|
|
end
|
|
|
|
-- check if we need more packets to reassemble this transaction
|
|
local NE_UP_TO_FNAME_SIZE = 94
|
|
while ( last_name_offset > ( #response.data - NE_UP_TO_FNAME_SIZE ) ) do
|
|
local status, tmp = receive_transaction2(smbstate)
|
|
if ( not(status) ) then
|
|
return false, "Failed to receive data from receive_transaction2"
|
|
end
|
|
response.data = response.data .. tmp.data
|
|
end
|
|
|
|
return true, response
|
|
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_and_receive_find_request(smbstate, TRANS2_FIND_FIRST2, function_parameters)
|
|
if ( not(status) ) then
|
|
return
|
|
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 .. "\0")
|
|
status, response = send_and_receive_find_request(smbstate, TRANS2_FIND_NEXT2, function_parameters)
|
|
if ( not(status) ) then
|
|
return
|
|
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
|
|
-- deleting a file.
|
|
--
|
|
--@param host The host object
|
|
--@param share The share to test
|
|
--@return (status, result) If status is false, result is an error message. The error message 'NT_STATUS_OBJECT_NAME_NOT_FOUND'
|
|
-- should be handled gracefully; it indicates that the share isn't a fileshare. Otherwise, result is a boolean value:
|
|
-- true if the file was successfully written, false if it was not.
|
|
function share_anonymous_can_write(host, share)
|
|
local filename, status, err
|
|
|
|
-- First, choose a filename. This should be random.
|
|
filename = "nmap-test-file"
|
|
|
|
-- Next, attempt to write to that file
|
|
status, err = file_write(host, string.rep("ABCDEFGHIJKLMNOPQRSTUVWXYZ", 10), share, filename, true)
|
|
if(status == false) then
|
|
if(err == "NT_STATUS_OBJECT_NAME_NOT_FOUND") then
|
|
return false, err
|
|
end
|
|
|
|
if(err == "NT_STATUS_ACCESS_DENIED" or err == "NT_STATUS_INVALID_PARAMETER") then
|
|
return true, false
|
|
end
|
|
|
|
return false, "Error writing test file to disk as anonymous: " .. err
|
|
end
|
|
|
|
-- Now the important part: delete it
|
|
status, err = file_delete(host, share, filename)
|
|
if(status == false) then
|
|
return false, "Error deleting test file as anonymous: " .. err
|
|
end
|
|
|
|
return true, true
|
|
end
|
|
|
|
|
|
---Determine whether or not the current user has read or read/write access on the share. This is done by creating then
|
|
-- deleting a file.
|
|
--
|
|
--@param host The host object
|
|
--@param share The share to test
|
|
--@return (status, result) If status is false, result is an error message. The error message 'NT_STATUS_OBJECT_NAME_NOT_FOUND'
|
|
-- should be handled gracefully; it indicates that the share isn't a fileshare. Otherwise, result is a boolean value:
|
|
-- true if the file was successfully written, false if it was not.
|
|
function share_user_can_write(host, share)
|
|
|
|
local filename, status, err
|
|
|
|
-- First, choose a filename. This should be random.
|
|
filename = "nmap-test-file"
|
|
|
|
-- Next, attempt to write to that file
|
|
status, err = file_write(host, string.rep("ABCDEFGHIJKLMNOPQRSTUVWXYZ", 10), share, filename)
|
|
if(status == false) then
|
|
if(err == "NT_STATUS_OBJECT_NAME_NOT_FOUND") then
|
|
return false, err
|
|
end
|
|
|
|
if(err == "NT_STATUS_ACCESS_DENIED" or err == "NT_STATUS_INVALID_PARAMETER") then
|
|
return true, false
|
|
end
|
|
|
|
return false, "Error writing test file to disk as user: " .. err
|
|
end
|
|
|
|
-- Now the important part: delete it
|
|
status, err = file_delete(host, share, filename)
|
|
if(status == false) then
|
|
return false, "Error deleting test file as user: " .. err
|
|
end
|
|
|
|
return true, true
|
|
end
|
|
|
|
---Check whether or not a share is accessible by the anonymous user. Assumes that <code>share_host_returns_proper_error</code>
|
|
-- has been called and returns <code>true</code>.
|
|
--
|
|
--@param host The host object
|
|
--@param share The share to test
|
|
--@return (status, result) If status is false, result is an error message. Otherwise, result is a boolean value:
|
|
-- true if anonymous access is permitted, false otherwise.
|
|
function share_anonymous_can_read(host, share)
|
|
local status, smbstate, err
|
|
local overrides = get_overrides_anonymous()
|
|
|
|
-- Begin the SMB session
|
|
status, smbstate = start(host)
|
|
if(status == false) then
|
|
return false, smbstate
|
|
end
|
|
|
|
-- Negotiate the protocol
|
|
status, err = negotiate_protocol(smbstate, overrides)
|
|
if(status == false) then
|
|
stop(smbstate)
|
|
return false, err
|
|
end
|
|
|
|
-- Start up a null session
|
|
status, err = start_session(smbstate, overrides)
|
|
|
|
if(status == false) then
|
|
stop(smbstate)
|
|
return false, err
|
|
end
|
|
|
|
-- Attempt a connection to the share
|
|
status, err = tree_connect(smbstate, share, overrides)
|
|
if(status == false) then
|
|
|
|
-- Stop the session
|
|
stop(smbstate)
|
|
|
|
-- ACCESS_DENIED is the expected error: it tells us that the connection failed
|
|
if(err == 0xc0000022 or err == 'NT_STATUS_ACCESS_DENIED') then
|
|
return true, false
|
|
else
|
|
return false, err
|
|
end
|
|
end
|
|
|
|
|
|
|
|
stop(smbstate)
|
|
return true, true
|
|
end
|
|
|
|
---Check whether or not a share is accessible by the current user. Assumes that <code>share_host_returns_proper_error</code>
|
|
-- has been called and returns <code>true</code>.
|
|
--
|
|
--@param host The host object
|
|
--@param share The share to test
|
|
--@return (status, result) If status is false, result is an error message. Otherwise, result is a boolean value:
|
|
-- true if anonymous access is permitted, false otherwise.
|
|
function share_user_can_read(host, share)
|
|
local status, smbstate, err
|
|
local overrides = {}
|
|
|
|
-- Begin the SMB session
|
|
status, smbstate = start(host)
|
|
if(status == false) then
|
|
return false, smbstate
|
|
end
|
|
|
|
-- Negotiate the protocol
|
|
status, err = negotiate_protocol(smbstate, overrides)
|
|
if(status == false) then
|
|
stop(smbstate)
|
|
return false, err
|
|
end
|
|
|
|
-- Start up a null session
|
|
status, err = start_session(smbstate, overrides)
|
|
if(status == false) then
|
|
stop(smbstate)
|
|
return false, err
|
|
end
|
|
|
|
-- Attempt a connection to the share
|
|
status, err = tree_connect(smbstate, share, overrides)
|
|
if(status == false) then
|
|
|
|
-- Stop the session
|
|
stop(smbstate)
|
|
|
|
-- ACCESS_DENIED is the expected error: it tells us that the connection failed
|
|
if(err == 0xc0000022 or err == 'NT_STATUS_ACCESS_DENIED') then
|
|
return true, false
|
|
else
|
|
return false, err
|
|
end
|
|
end
|
|
|
|
stop(smbstate)
|
|
return true, true
|
|
end
|
|
|
|
---Determine whether or not a host will accept any share name (I've seen this on certain systems; it's
|
|
-- bad, because it means we cannot tell whether or not a share exists).
|
|
--
|
|
--@param host The host object
|
|
--@param use_anonymous [optional] If set to 'true', test is done by the anonymous user rather than the current user.
|
|
--@return (status, result) If status is false, result is an error message. Otherwise, result is a boolean value:
|
|
-- true if the file was successfully written, false if it was not.
|
|
function share_host_returns_proper_error(host, use_anonymous)
|
|
local status, smbstate, err
|
|
local share = "nmap-share-test"
|
|
local overrides
|
|
|
|
if ( use_anonymous ) then
|
|
overrides = get_overrides_anonymous()
|
|
end
|
|
|
|
-- Begin the SMB session
|
|
status, smbstate = start(host)
|
|
if(status == false) then
|
|
return false, smbstate
|
|
end
|
|
|
|
-- Negotiate the protocol
|
|
status, err = negotiate_protocol(smbstate, overrides)
|
|
if(status == false) then
|
|
stop(smbstate)
|
|
return false, err
|
|
end
|
|
|
|
-- Start up a null session
|
|
status, err = start_session(smbstate, overrides)
|
|
if(status == false) then
|
|
stop(smbstate)
|
|
return false, err
|
|
end
|
|
|
|
-- Connect to the share
|
|
stdnse.debug1("SMB: Trying a random share to see if server responds properly: %s", share)
|
|
status, err = tree_connect(smbstate, share, overrides)
|
|
|
|
if(status == false) then
|
|
-- If the error is NT_STATUS_ACCESS_DENIED (0xc0000022), that's bad -- we don't want non-existent shares
|
|
-- showing up as 'access denied'. Any other error is ok.
|
|
if(err == 0xc0000022 or err == 'NT_STATUS_ACCESS_DENIED') then
|
|
stdnse.debug1("SMB: Server doesn't return proper value for non-existent shares (returns ACCESS_DENIED)")
|
|
stop(smbstate)
|
|
return true, false
|
|
end
|
|
else
|
|
-- If we were actually able to connect to this share, then there's probably a serious issue
|
|
stdnse.debug1("SMB: Server doesn't return proper value for non-existent shares (accepts the connection)")
|
|
stop(smbstate)
|
|
return true, false
|
|
end
|
|
|
|
stop(smbstate)
|
|
return true, true
|
|
end
|
|
|
|
---Get all the details we can about the share. These details are stored in a table and returned.
|
|
--
|
|
--@param host The host object.
|
|
--@param share An array of shares to check.
|
|
--@return (status, result) If status is false, result is an error message. Otherwise, result is a boolean value:
|
|
-- true if the file was successfully written, false if it was not.
|
|
function share_get_details(host, share)
|
|
local msrpc = require "msrpc" -- avoid require cycle
|
|
local smbstate, status, result
|
|
local i
|
|
local details = {}
|
|
|
|
-- Save the name
|
|
details['name'] = share
|
|
|
|
-- Check if the current user can read the share
|
|
stdnse.debug1("SMB: Checking if share %s can be read by the current user", share)
|
|
status, result = share_user_can_read(host, share)
|
|
if(status == false) then
|
|
return false, result
|
|
end
|
|
details['user_can_read'] = result
|
|
|
|
-- Check if the anonymous reader can read the share
|
|
stdnse.debug1("SMB: Checking if share %s can be read by the anonymous user", share)
|
|
status, result = share_anonymous_can_read(host, share)
|
|
if(status == true) then
|
|
details['anonymous_can_read'] = result
|
|
end
|
|
|
|
-- Check if the current user can write to the share
|
|
stdnse.debug1("SMB: Checking if share %s can be written by the current user", share)
|
|
status, result = share_user_can_write(host, share)
|
|
if(status == false) then
|
|
if(result == "NT_STATUS_OBJECT_NAME_NOT_FOUND") then
|
|
details['user_can_write'] = "NT_STATUS_OBJECT_NAME_NOT_FOUND"
|
|
else
|
|
return false, result
|
|
end
|
|
end
|
|
details['user_can_write'] = result
|
|
|
|
-- Check if the anonymous user can write to the share
|
|
stdnse.debug1("SMB: Checking if share %s can be written by the anonymous user", share)
|
|
status, result = share_anonymous_can_write(host, share)
|
|
if(status == false and result == "NT_STATUS_OBJECT_NAME_NOT_FOUND") then
|
|
details['anonymous_can_write'] = "NT_STATUS_OBJECT_NAME_NOT_FOUND"
|
|
elseif( status == true ) then
|
|
details['anonymous_can_write'] = result
|
|
end
|
|
|
|
-- Try and get full details about the share
|
|
status, result = msrpc.get_share_info(host, share)
|
|
if(status == false) then
|
|
-- We don't stop for this error (it's pretty common since administrative privileges are required here)
|
|
stdnse.debug1("SMB: Failed to get share info for %s: %s", share, result)
|
|
details['details'] = result
|
|
else
|
|
-- Process the result a bit
|
|
result = result['info']
|
|
if(result['max_users'] == 0xFFFFFFFF) then
|
|
result['max_users'] = "<unlimited>"
|
|
end
|
|
details['details'] = result
|
|
end
|
|
|
|
return true, details
|
|
end
|
|
|
|
---Retrieve a list of fileshares, along with any details that could be pulled. This is the core of smb-enum-shares.nse, but
|
|
-- can also be used by any script that needs to find an open share.
|
|
--
|
|
-- In the best care, the shares are determined by calling <code>msrpc.enum_shares</code>, and information is gathered by calling
|
|
-- <code>msrpc.get_share_info</code>. These require a certain level of access, though, so as a fallback, a pre-programmed list of
|
|
-- shares is used, and these are verified by attempting a connection.
|
|
--
|
|
--@param host The host object.
|
|
--@return (status, result, extra) If status is false, result is an error message. Otherwise, result is an array of shares with as much
|
|
-- detail as we could get. If extra isn't nil, it is set to extra information that should be displayed (such as a warning).
|
|
function share_get_list(host)
|
|
local msrpc = require "msrpc" -- avoid require cycle
|
|
local status, result
|
|
local enum_status
|
|
local extra = ""
|
|
local shares = {}
|
|
local share_details = {}
|
|
|
|
-- Try and do this the good way, make a MSRPC call to get the shares
|
|
stdnse.debug1("SMB: Attempting to log into the system to enumerate shares")
|
|
enum_status, shares = msrpc.enum_shares(host)
|
|
|
|
-- If that failed, try doing it with brute force. This almost certainly won't find everything, but it's the
|
|
-- best we can do.
|
|
if(enum_status == false) then
|
|
stdnse.debug1("SMB: Enumerating shares failed, guessing at common ones (%s)", shares)
|
|
extra = string.format("ERROR: Enumerating shares failed, guessing at common ones (%s)", shares)
|
|
|
|
-- Take some common share names I've seen (thanks to Brandon Enright for most of these, except the last few)
|
|
shares = {"ADMIN", "BACKUP", "DATA", "DESKTOP", "DOCS", "FILES", "GROUPS", "HD", "HOME", "INFO", "IPC", "MEDIA", "MY DOCUMENTS", "NETLOGON", "PICTURES", "PORN", "PR0N", "PRINT", "PROGRAMS", "PRON", "PUBLIC", "SHARE", "SHARED", "SOFTWARE", "STMP", "TEMP", "TEST", "TMP", "USERS", "WEB DOCUMENTS","WEBSERVER", "WWW", "XSERVE" }
|
|
|
|
-- Try every alphabetic share
|
|
for i = string.byte("A", 1), string.byte("Z", 1), 1 do
|
|
shares[#shares + 1] = string.char(i)
|
|
end
|
|
|
|
-- For each share, add one with the same name and a trailing '$'
|
|
local sharesLength = #shares
|
|
for shareItr = 1, sharesLength, 1 do
|
|
shares[ sharesLength + shareItr ] = shares[ shareItr ] .. '$'
|
|
end
|
|
else
|
|
stdnse.debug1("SMB: Found %d shares, will attempt to find more information", #shares)
|
|
end
|
|
|
|
-- Sort the shares
|
|
table.sort(shares)
|
|
|
|
-- Ensure that the server returns the proper error message
|
|
-- first try anonymously, then using a user account (in case anonymous connections are not supported)
|
|
for _, anon in ipairs({true, false}) do
|
|
status, result = share_host_returns_proper_error(host)
|
|
|
|
if(status == true and result == false) then
|
|
return false, "Server doesn't return proper value for non-existent shares; can't enumerate shares"
|
|
end
|
|
end
|
|
|
|
if(status == false) then
|
|
return false, result
|
|
end
|
|
|
|
-- Get more information on each share
|
|
for i = 1, #shares, 1 do
|
|
local status, result
|
|
stdnse.debug1("SMB: Getting information for share: %s", shares[i])
|
|
status, result = share_get_details(host, shares[i])
|
|
if(status == false and result == 'NT_STATUS_BAD_NETWORK_NAME') then
|
|
stdnse.debug1("SMB: Share doesn't exist: %s", shares[i])
|
|
elseif(status == false) then
|
|
stdnse.debug1("SMB: Error while getting share details: %s", result)
|
|
return false, result
|
|
else
|
|
-- Save the share details
|
|
table.insert(share_details, result)
|
|
end
|
|
end
|
|
|
|
return true, share_details, extra
|
|
end
|
|
|
|
---Find a share that the current user can write to. Return it, along with its path. If no share could be found,
|
|
-- an error is returned. If the path cannot be determined, the returned path is nil.
|
|
--
|
|
--@param host The host object.
|
|
--@return (status, name, path, names) If status is false, result is an error message. Otherwise, name is the name of the share,
|
|
-- path is its path, if it could be determined, and names is a list of all writable shares.
|
|
function share_find_writable(host)
|
|
local i
|
|
local status, shares
|
|
local main_name, main_path
|
|
local names = {}
|
|
local writable = {}
|
|
|
|
status, shares = share_get_list(host)
|
|
if(status == false) then
|
|
return false, shares
|
|
end
|
|
|
|
for i = 1, #shares, 1 do
|
|
if(shares[i]['user_can_write'] == true) then
|
|
if(main_name == nil) then
|
|
main_name = shares[i]['name']
|
|
|
|
if(shares[i]['details'] ~= nil) then
|
|
main_path = shares[i]['details']['path']
|
|
end
|
|
end
|
|
|
|
table.insert(names, shares[i]['name'])
|
|
end
|
|
end
|
|
|
|
if(main_name == nil) then
|
|
return false, "Couldn't find a writable share!"
|
|
else
|
|
return true, main_name, main_path, names
|
|
end
|
|
end
|
|
|
|
--- Converts numbered Windows version strings (<code>"Windows 5.0"</code>, <code>"Windows 5.1"</code>) to names (<code>"Windows 2000"</code>, <code>"Windows XP"</code>).
|
|
--@param os The numbered OS version.
|
|
--@return The actual name of the OS (or the same as the <code>os</code> parameter if no match was found).
|
|
function get_windows_version(os)
|
|
|
|
if(os == "Windows 5.0") then
|
|
return "Windows 2000"
|
|
elseif(os == "Windows 5.1")then
|
|
return "Windows XP"
|
|
end
|
|
|
|
return os
|
|
|
|
end
|
|
|
|
---Retrieve information about the host's operating system. This should always be possible to call, as long as there isn't already
|
|
-- a SMB session established.
|
|
--
|
|
-- The returned table has the following keys (shown here with sample values).
|
|
-- * <code>os</code>: <code>"Windows 7 Professional 7601 Service Pack 1"</code>
|
|
-- * <code>lanmanager</code>: <code>"Windows 7 Professional 6.1"</code>
|
|
-- * <code>domain</code>: <code>"WORKGROUP"</code>
|
|
-- * <code>server</code>: <code>"COMPUTERNAME"</code>
|
|
-- * <code>time</code>: <code>1347121470.0462</code>
|
|
-- * <code>date</code>: <code>"2012-09-08 09:24:30"</code>
|
|
-- * <code>timezone</code>: <code>-7</code>
|
|
-- * <code>timezone_str</code>: <code>UTC-7</code>
|
|
-- The table may also contain these additional keys:
|
|
-- * <code>fqdn</code>: <code>"Sql2008.lab.test.local"</code>
|
|
-- * <code>domain_dns</code>: <code>"lab.test.local"</code>
|
|
-- * <code>forest_dns</code>: <code>"test.local"</code>
|
|
-- * <code>workgroup</code>
|
|
--
|
|
--@param host The host object
|
|
--@return (status, data) If status is true, data is a table of values; otherwise, data is an error message.
|
|
function get_os(host)
|
|
local state
|
|
local status, smbstate
|
|
|
|
local response = {}
|
|
|
|
-- Start up SMB
|
|
status, smbstate = start_ex(host, true, true, nil, nil, true)
|
|
if(status == false) then
|
|
return false, smbstate
|
|
end
|
|
|
|
-- See if we actually got something
|
|
if(smbstate['os'] == nil and smbstate['lanmanager'] == nil) then
|
|
return false, "Server didn't return OS details"
|
|
end
|
|
|
|
response['os'] = smbstate['os']
|
|
response['lanmanager'] = smbstate['lanmanager']
|
|
response['domain'] = smbstate['domain']
|
|
response['server'] = smbstate['server']
|
|
response['date'] = smbstate['date']
|
|
response['time'] = smbstate['time']
|
|
response['timezone_str'] = smbstate['timezone_str']
|
|
response['timezone'] = smbstate['timezone']
|
|
|
|
-- Kill SMB
|
|
stop(smbstate)
|
|
|
|
|
|
-- Start another session with extended security. This will allow us to get
|
|
-- additional information about the target.
|
|
status, smbstate = start_ex(host, true, true, nil, nil, false)
|
|
if(status == true) then
|
|
-- See if we actually got something
|
|
if (smbstate['fqdn'] or smbstate['domain_dns'] or smbstate['forest_dns']) then
|
|
response['fqdn'] = smbstate['fqdn']
|
|
response['domain_dns'] = smbstate['domain_dns']
|
|
response['forest_dns'] = smbstate['forest_dns']
|
|
-- After a non-extended security negotiation, smbstate['domain'] will
|
|
-- contain the NetBIOS domain name, or the workgroup name. However,
|
|
-- after an extended-security session setup, smbstate['domain'] will
|
|
-- contain the NetBIOS domain name. For hosts in a workgroup, Windows
|
|
-- uses the NetBIOS hostname as the NetBIOS domain name. Comparing the
|
|
-- two will reveal whether the target is in a domain or a workgroup.
|
|
if ( smbstate['domain'] ~= nil and response['domain'] ~= smbstate['domain'] ) then
|
|
response['workgroup'] = response['domain']
|
|
response['domain'] = nil
|
|
end
|
|
end
|
|
|
|
-- Kill SMB again
|
|
stop(smbstate)
|
|
end
|
|
|
|
return true, response
|
|
end
|
|
|
|
---Basically a wrapper around <code>socket:get_info</code>, except that it also makes a SMB connection before calling the
|
|
-- <code>get_info</code> function. Returns the mac address as well, for convenience.
|
|
--
|
|
--@param host The host object
|
|
--@return status: true for successful, false otherwise.
|
|
--@return If status is true, the local ip address; otherwise, an error message.
|
|
--@return The local port (not really meaningful, since it'll change next time).
|
|
--@return The remote ip address.
|
|
--@return The report port.
|
|
--@return The mac address, if possible; nil otherwise.
|
|
function get_socket_info(host)
|
|
local status, lhost, lport, rhost, rport
|
|
local smbstate, socket
|
|
|
|
-- Start SMB (we need a socket to get the proper local ip
|
|
status, smbstate = start_ex(host)
|
|
if(status == false) then
|
|
return false, smbstate
|
|
end
|
|
|
|
socket = smbstate['socket']
|
|
status, lhost, lport, rhost, rport = socket:get_info()
|
|
if(status == false) then
|
|
return false, lhost
|
|
end
|
|
|
|
-- Stop SMB
|
|
stop(smbstate)
|
|
|
|
-- Get the mac in hex format, if possible
|
|
local lmac = nil
|
|
if(host.mac_addr_src) then
|
|
lmac = stdnse.tohex(host.mac_addr_src, {separator = ":"})
|
|
end
|
|
|
|
return true, lhost, lport, rhost, rport, lmac
|
|
end
|
|
|
|
---Generate a string that's somewhat unique, but is based on factors that won't
|
|
-- change on a host.
|
|
--
|
|
-- At the moment, this is a very simple hash based on the IP address. This hash
|
|
-- is *very* likely to have collisions, and that's by design -- while it should
|
|
-- be somewhat unique, I don't want it to be trivial to uniquely determine who
|
|
-- it originated from.
|
|
--
|
|
-- TODO: At some point, I should re-do this function properly, with a method of
|
|
-- hashing that's somewhat proven.
|
|
--
|
|
--@param host The host object
|
|
--@param extension [optional] The extension to add on the end of the file.
|
|
-- Default: none.
|
|
--@param seed [optional] Some randomness on which to base the name. If you want
|
|
-- to do multiple files, each with its own uniqueish name, this can
|
|
-- be used.
|
|
--@return (status, data) If status is true, data is a table of values;
|
|
-- otherwise, data is an error message. Can be any kind of string.
|
|
function get_uniqueish_name(host, extension, seed)
|
|
|
|
local status
|
|
local lhost, lport, rhost, rport
|
|
if(type(host) == "table") then
|
|
status, lhost = get_socket_info(host)
|
|
else
|
|
lhost = host
|
|
end
|
|
|
|
-- Create our ultra-weak hash by using a simple xor/shift algorithm
|
|
-- I tested this, and in 255 tests, there were roughly 10 collisions. That's about what I'm looking for.
|
|
local hash = 0
|
|
local i
|
|
local str = lhost .. (seed or "") .. (extension or "") .. (nmap.registry.args.randomseed or "")
|
|
|
|
for i = 1, #str, 1 do
|
|
local chr = string.byte(string.sub(str, i, i), 1)
|
|
hash = bit.bxor(hash, chr)
|
|
hash = bit.bor(bit.lshift(hash, 3), bit.rshift(hash, 29))
|
|
hash = bit.bxor(hash, 3)
|
|
hash = bit.band(hash, 0xFFFFFFFF)
|
|
end
|
|
|
|
local response
|
|
if(extension) then
|
|
response = string.format("%x.%s", hash, extension)
|
|
else
|
|
response = string.format("%x", hash)
|
|
end
|
|
|
|
return true, response
|
|
end
|
|
|
|
---Determines, as accurately as possible, whether or not an account is an administrator. If there is an error,
|
|
-- 'false' is simply returned.
|
|
function is_admin(host, username, domain, password, password_hash, hash_type)
|
|
local msrpc = require "msrpc" -- avoid require cycle
|
|
local status, smbstate, err, result
|
|
local overrides = get_overrides(username, domain, password, password_hash, hash_type)
|
|
|
|
stdnse.debug1("SMB: Checking if %s is an administrator", username)
|
|
|
|
status, smbstate = start(host)
|
|
if(status == false) then
|
|
stdnse.debug1("SMB; is_admin: Failed to start SMB: %s [%s]", smbstate, username)
|
|
stop(smbstate)
|
|
return false
|
|
end
|
|
|
|
status, err = negotiate_protocol(smbstate, overrides)
|
|
if(status == false) then
|
|
stdnse.debug1("SMB; is_admin: Failed to negotiate protocol: %s [%s]", err, username)
|
|
stop(smbstate)
|
|
return false
|
|
end
|
|
|
|
status, err = start_session(smbstate, overrides)
|
|
if(status == false) then
|
|
stdnse.debug1("SMB; is_admin: Failed to start session %s [%s]", err, username)
|
|
stop(smbstate)
|
|
return false
|
|
end
|
|
|
|
status, err = tree_connect(smbstate, "IPC$", overrides)
|
|
if(status == false) then
|
|
stdnse.debug1("SMB; is_admin: Failed to connect tree: %s [%s]", err, username)
|
|
stop(smbstate)
|
|
return false
|
|
end
|
|
|
|
status, err = create_file(smbstate, msrpc.SRVSVC_PATH, overrides)
|
|
if(status == false) then
|
|
stdnse.debug1("SMB; is_admin: Failed to create file: %s [%s]", err, username)
|
|
stop(smbstate)
|
|
return false
|
|
end
|
|
|
|
status, err = msrpc.bind(smbstate, msrpc.SRVSVC_UUID, msrpc.SRVSVC_VERSION, nil)
|
|
if(status == false) then
|
|
stdnse.debug1("SMB; is_admin: Failed to bind: %s [%s]", err, username)
|
|
stop(smbstate)
|
|
return false
|
|
end
|
|
|
|
-- Call netservergetstatistics for 'server'
|
|
status, err = msrpc.srvsvc_netservergetstatistics(smbstate, host.ip)
|
|
if(status == false) then
|
|
stdnse.debug1("SMB; is_admin: Couldn't get server stats (may be normal): %s [%s]", err, username)
|
|
stop(smbstate)
|
|
return false
|
|
end
|
|
|
|
stop(smbstate)
|
|
|
|
return true
|
|
end
|
|
|
|
command_codes =
|
|
{
|
|
SMB_COM_CREATE_DIRECTORY = 0x00,
|
|
SMB_COM_DELETE_DIRECTORY = 0x01,
|
|
SMB_COM_OPEN = 0x02,
|
|
SMB_COM_CREATE = 0x03,
|
|
SMB_COM_CLOSE = 0x04,
|
|
SMB_COM_FLUSH = 0x05,
|
|
SMB_COM_DELETE = 0x06,
|
|
SMB_COM_RENAME = 0x07,
|
|
SMB_COM_QUERY_INFORMATION = 0x08,
|
|
SMB_COM_SET_INFORMATION = 0x09,
|
|
SMB_COM_READ = 0x0A,
|
|
SMB_COM_WRITE = 0x0B,
|
|
SMB_COM_LOCK_BYTE_RANGE = 0x0C,
|
|
SMB_COM_UNLOCK_BYTE_RANGE = 0x0D,
|
|
SMB_COM_CREATE_TEMPORARY = 0x0E,
|
|
SMB_COM_CREATE_NEW = 0x0F,
|
|
SMB_COM_CHECK_DIRECTORY = 0x10,
|
|
SMB_COM_PROCESS_EXIT = 0x11,
|
|
SMB_COM_SEEK = 0x12,
|
|
SMB_COM_LOCK_AND_READ = 0x13,
|
|
SMB_COM_WRITE_AND_UNLOCK = 0x14,
|
|
SMB_COM_READ_RAW = 0x1A,
|
|
SMB_COM_READ_MPX = 0x1B,
|
|
SMB_COM_READ_MPX_SECONDARY = 0x1C,
|
|
SMB_COM_WRITE_RAW = 0x1D,
|
|
SMB_COM_WRITE_MPX = 0x1E,
|
|
SMB_COM_WRITE_MPX_SECONDARY = 0x1F,
|
|
SMB_COM_WRITE_COMPLETE = 0x20,
|
|
SMB_COM_QUERY_SERVER = 0x21,
|
|
SMB_COM_SET_INFORMATION2 = 0x22,
|
|
SMB_COM_QUERY_INFORMATION2 = 0x23,
|
|
SMB_COM_LOCKING_ANDX = 0x24,
|
|
SMB_COM_TRANSACTION = 0x25,
|
|
SMB_COM_TRANSACTION_SECONDARY = 0x26,
|
|
SMB_COM_IOCTL = 0x27,
|
|
SMB_COM_IOCTL_SECONDARY = 0x28,
|
|
SMB_COM_COPY = 0x29,
|
|
SMB_COM_MOVE = 0x2A,
|
|
SMB_COM_ECHO = 0x2B,
|
|
SMB_COM_WRITE_AND_CLOSE = 0x2C,
|
|
SMB_COM_OPEN_ANDX = 0x2D,
|
|
SMB_COM_READ_ANDX = 0x2E,
|
|
SMB_COM_WRITE_ANDX = 0x2F,
|
|
SMB_COM_NEW_FILE_SIZE = 0x30,
|
|
SMB_COM_CLOSE_AND_TREE_DISC = 0x31,
|
|
SMB_COM_TRANSACTION2 = 0x32,
|
|
SMB_COM_TRANSACTION2_SECONDARY = 0x33,
|
|
SMB_COM_FIND_CLOSE2 = 0x34,
|
|
SMB_COM_FIND_NOTIFY_CLOSE = 0x35,
|
|
SMB_COM_TREE_CONNECT = 0x70,
|
|
SMB_COM_TREE_DISCONNECT = 0x71,
|
|
SMB_COM_NEGOTIATE = 0x72,
|
|
SMB_COM_SESSION_SETUP_ANDX = 0x73,
|
|
SMB_COM_LOGOFF_ANDX = 0x74,
|
|
SMB_COM_TREE_CONNECT_ANDX = 0x75,
|
|
SMB_COM_QUERY_INFORMATION_DISK = 0x80,
|
|
SMB_COM_SEARCH = 0x81,
|
|
SMB_COM_FIND = 0x82,
|
|
SMB_COM_FIND_UNIQUE = 0x83,
|
|
SMB_COM_FIND_CLOSE = 0x84,
|
|
SMB_COM_NT_TRANSACT = 0xA0,
|
|
SMB_COM_NT_TRANSACT_SECONDARY = 0xA1,
|
|
SMB_COM_NT_CREATE_ANDX = 0xA2,
|
|
SMB_COM_NT_CANCEL = 0xA4,
|
|
SMB_COM_NT_RENAME = 0xA5,
|
|
SMB_COM_OPEN_PRINT_FILE = 0xC0,
|
|
SMB_COM_WRITE_PRINT_FILE = 0xC1,
|
|
SMB_COM_CLOSE_PRINT_FILE = 0xC2,
|
|
SMB_COM_GET_PRINT_QUEUE = 0xC3,
|
|
SMB_COM_READ_BULK = 0xD8,
|
|
SMB_COM_WRITE_BULK = 0xD9,
|
|
SMB_COM_WRITE_BULK_DATA = 0xDA,
|
|
SMB_NO_FURTHER_COMMANDS = 0xFF
|
|
}
|
|
|
|
for i, v in pairs(command_codes) do
|
|
command_names[v] = i
|
|
end
|
|
|
|
|
|
-- see http://msdn.microsoft.com/en-us/library/cc231196(v=prot.10).aspx
|
|
status_codes =
|
|
{
|
|
NT_STATUS_SUCCESS = 0x00000000,
|
|
NT_STATUS_WERR_BADFILE = 0x00000002,
|
|
NT_STATUS_WERR_ACCESS_DENIED = 0x00000005,
|
|
NT_STATUS_WERR_INVALID_PARAMETER = 0x00000057,
|
|
NT_STATUS_WERR_INVALID_NAME = 0x0000007b,
|
|
NT_STATUS_WERR_UNKNOWN_LEVEL = 0x0000007c,
|
|
NT_STATUS_WERR_MORE_DATA = 0x000000ea,
|
|
NT_STATUS_NO_MORE_ITEMS = 0x00000103,
|
|
NT_STATUS_MORE_ENTRIES = 0x00000105,
|
|
NT_STATUS_SOME_NOT_MAPPED = 0x00000107,
|
|
NT_STATUS_SERVICE_REQUEST_TIMEOUT = 0x0000041D,
|
|
NT_STATUS_SERVICE_NO_THREAD = 0x0000041E,
|
|
NT_STATUS_SERVICE_DATABASE_LOCKED = 0x0000041F,
|
|
NT_STATUS_SERVICE_ALREADY_RUNNING = 0x00000420,
|
|
NT_STATUS_INVALID_SERVICE_ACCOUNT = 0x00000421,
|
|
NT_STATUS_SERVICE_DISABLED = 0x00000422,
|
|
NT_STATUS_CIRCULAR_DEPENDENCY = 0x00000423,
|
|
NT_STATUS_SERVICE_DOES_NOT_EXIST = 0x00000424,
|
|
NT_STATUS_SERVICE_CANNOT_ACCEPT_CTRL = 0x00000425,
|
|
NT_STATUS_SERVICE_NOT_ACTIVE = 0x00000426,
|
|
NT_STATUS_FAILED_SERVICE_CONTROLLER_CONNECT = 0x00000427,
|
|
NT_STATUS_EXCEPTION_IN_SERVICE = 0x00000428,
|
|
NT_STATUS_DATABASE_DOES_NOT_EXIST = 0x00000429,
|
|
NT_STATUS_SERVICE_SPECIFIC_ERROR = 0x0000042a,
|
|
NT_STATUS_PROCESS_ABORTED = 0x0000042b,
|
|
NT_STATUS_SERVICE_DEPENDENCY_FAIL = 0x0000042c,
|
|
NT_STATUS_SERVICE_LOGON_FAILED = 0x0000042d,
|
|
NT_STATUS_SERVICE_START_HANG = 0x0000042e,
|
|
NT_STATUS_INVALID_SERVICE_LOCK = 0x0000042f,
|
|
NT_STATUS_SERVICE_MARKED_FOR_DELETE = 0x00000430,
|
|
NT_STATUS_SERVICE_EXISTS = 0x00000431,
|
|
NT_STATUS_ALREADY_RUNNING_LKG = 0x00000432,
|
|
NT_STATUS_SERVICE_DEPENDENCY_DELETED = 0x00000433,
|
|
NT_STATUS_BOOT_ALREADY_ACCEPTED = 0x00000434,
|
|
NT_STATUS_SERVICE_NEVER_STARTED = 0x00000435,
|
|
NT_STATUS_DUPLICATE_SERVICE_NAME = 0x00000436,
|
|
NT_STATUS_DIFFERENT_SERVICE_ACCOUNT = 0x00000437,
|
|
NT_STATUS_CANNOT_DETECT_DRIVER_FAILURE = 0x00000438,
|
|
DOS_STATUS_UNKNOWN_ERROR = 0x00010001,
|
|
DOS_STATUS_NONSPECIFIC_ERROR = 0x00010002,
|
|
DOS_STATUS_DIRECTORY_NOT_FOUND = 0x00030001,
|
|
DOS_STATUS_ACCESS_DENIED = 0x00050001,
|
|
DOS_STATUS_INVALID_FID = 0x00060001,
|
|
DOS_STATUS_INVALID_NETWORK_NAME = 0x00060002,
|
|
NT_STATUS_BUFFER_OVERFLOW = 0x80000005,
|
|
NT_STATUS_UNSUCCESSFUL = 0xc0000001,
|
|
NT_STATUS_NOT_IMPLEMENTED = 0xc0000002,
|
|
NT_STATUS_INVALID_INFO_CLASS = 0xc0000003,
|
|
NT_STATUS_INFO_LENGTH_MISMATCH = 0xc0000004,
|
|
NT_STATUS_ACCESS_VIOLATION = 0xc0000005,
|
|
NT_STATUS_IN_PAGE_ERROR = 0xc0000006,
|
|
NT_STATUS_PAGEFILE_QUOTA = 0xc0000007,
|
|
NT_STATUS_INVALID_HANDLE = 0xc0000008,
|
|
NT_STATUS_BAD_INITIAL_STACK = 0xc0000009,
|
|
NT_STATUS_BAD_INITIAL_PC = 0xc000000a,
|
|
NT_STATUS_INVALID_CID = 0xc000000b,
|
|
NT_STATUS_TIMER_NOT_CANCELED = 0xc000000c,
|
|
NT_STATUS_INVALID_PARAMETER = 0xc000000d,
|
|
NT_STATUS_NO_SUCH_DEVICE = 0xc000000e,
|
|
NT_STATUS_NO_SUCH_FILE = 0xc000000f,
|
|
NT_STATUS_INVALID_DEVICE_REQUEST = 0xc0000010,
|
|
NT_STATUS_END_OF_FILE = 0xc0000011,
|
|
NT_STATUS_WRONG_VOLUME = 0xc0000012,
|
|
NT_STATUS_NO_MEDIA_IN_DEVICE = 0xc0000013,
|
|
NT_STATUS_UNRECOGNIZED_MEDIA = 0xc0000014,
|
|
NT_STATUS_NONEXISTENT_SECTOR = 0xc0000015,
|
|
NT_STATUS_MORE_PROCESSING_REQUIRED = 0xc0000016,
|
|
NT_STATUS_NO_MEMORY = 0xc0000017,
|
|
NT_STATUS_CONFLICTING_ADDRESSES = 0xc0000018,
|
|
NT_STATUS_NOT_MAPPED_VIEW = 0xc0000019,
|
|
NT_STATUS_UNABLE_TO_FREE_VM = 0xc000001a,
|
|
NT_STATUS_UNABLE_TO_DELETE_SECTION = 0xc000001b,
|
|
NT_STATUS_INVALID_SYSTEM_SERVICE = 0xc000001c,
|
|
NT_STATUS_ILLEGAL_INSTRUCTION = 0xc000001d,
|
|
NT_STATUS_INVALID_LOCK_SEQUENCE = 0xc000001e,
|
|
NT_STATUS_INVALID_VIEW_SIZE = 0xc000001f,
|
|
NT_STATUS_INVALID_FILE_FOR_SECTION = 0xc0000020,
|
|
NT_STATUS_ALREADY_COMMITTED = 0xc0000021,
|
|
NT_STATUS_ACCESS_DENIED = 0xc0000022,
|
|
NT_STATUS_BUFFER_TOO_SMALL = 0xc0000023,
|
|
NT_STATUS_OBJECT_TYPE_MISMATCH = 0xc0000024,
|
|
NT_STATUS_NONCONTINUABLE_EXCEPTION = 0xc0000025,
|
|
NT_STATUS_INVALID_DISPOSITION = 0xc0000026,
|
|
NT_STATUS_UNWIND = 0xc0000027,
|
|
NT_STATUS_BAD_STACK = 0xc0000028,
|
|
NT_STATUS_INVALID_UNWIND_TARGET = 0xc0000029,
|
|
NT_STATUS_NOT_LOCKED = 0xc000002a,
|
|
NT_STATUS_PARITY_ERROR = 0xc000002b,
|
|
NT_STATUS_UNABLE_TO_DECOMMIT_VM = 0xc000002c,
|
|
NT_STATUS_NOT_COMMITTED = 0xc000002d,
|
|
NT_STATUS_INVALID_PORT_ATTRIBUTES = 0xc000002e,
|
|
NT_STATUS_PORT_MESSAGE_TOO_LONG = 0xc000002f,
|
|
NT_STATUS_INVALID_PARAMETER_MIX = 0xc0000030,
|
|
NT_STATUS_INVALID_QUOTA_LOWER = 0xc0000031,
|
|
NT_STATUS_DISK_CORRUPT_ERROR = 0xc0000032,
|
|
NT_STATUS_OBJECT_NAME_INVALID = 0xc0000033,
|
|
NT_STATUS_OBJECT_NAME_NOT_FOUND = 0xc0000034,
|
|
NT_STATUS_OBJECT_NAME_COLLISION = 0xc0000035,
|
|
NT_STATUS_HANDLE_NOT_WAITABLE = 0xc0000036,
|
|
NT_STATUS_PORT_DISCONNECTED = 0xc0000037,
|
|
NT_STATUS_DEVICE_ALREADY_ATTACHED = 0xc0000038,
|
|
NT_STATUS_OBJECT_PATH_INVALID = 0xc0000039,
|
|
NT_STATUS_OBJECT_PATH_NOT_FOUND = 0xc000003a,
|
|
NT_STATUS_OBJECT_PATH_SYNTAX_BAD = 0xc000003b,
|
|
NT_STATUS_DATA_OVERRUN = 0xc000003c,
|
|
NT_STATUS_DATA_LATE_ERROR = 0xc000003d,
|
|
NT_STATUS_DATA_ERROR = 0xc000003e,
|
|
NT_STATUS_CRC_ERROR = 0xc000003f,
|
|
NT_STATUS_SECTION_TOO_BIG = 0xc0000040,
|
|
NT_STATUS_PORT_CONNECTION_REFUSED = 0xc0000041,
|
|
NT_STATUS_INVALID_PORT_HANDLE = 0xc0000042,
|
|
NT_STATUS_SHARING_VIOLATION = 0xc0000043,
|
|
NT_STATUS_QUOTA_EXCEEDED = 0xc0000044,
|
|
NT_STATUS_INVALID_PAGE_PROTECTION = 0xc0000045,
|
|
NT_STATUS_MUTANT_NOT_OWNED = 0xc0000046,
|
|
NT_STATUS_SEMAPHORE_LIMIT_EXCEEDED = 0xc0000047,
|
|
NT_STATUS_PORT_ALREADY_SET = 0xc0000048,
|
|
NT_STATUS_SECTION_NOT_IMAGE = 0xc0000049,
|
|
NT_STATUS_SUSPEND_COUNT_EXCEEDED = 0xc000004a,
|
|
NT_STATUS_THREAD_IS_TERMINATING = 0xc000004b,
|
|
NT_STATUS_BAD_WORKING_SET_LIMIT = 0xc000004c,
|
|
NT_STATUS_INCOMPATIBLE_FILE_MAP = 0xc000004d,
|
|
NT_STATUS_SECTION_PROTECTION = 0xc000004e,
|
|
NT_STATUS_EAS_NOT_SUPPORTED = 0xc000004f,
|
|
NT_STATUS_EA_TOO_LARGE = 0xc0000050,
|
|
NT_STATUS_NONEXISTENT_EA_ENTRY = 0xc0000051,
|
|
NT_STATUS_NO_EAS_ON_FILE = 0xc0000052,
|
|
NT_STATUS_EA_CORRUPT_ERROR = 0xc0000053,
|
|
NT_STATUS_FILE_LOCK_CONFLICT = 0xc0000054,
|
|
NT_STATUS_LOCK_NOT_GRANTED = 0xc0000055,
|
|
NT_STATUS_DELETE_PENDING = 0xc0000056,
|
|
NT_STATUS_CTL_FILE_NOT_SUPPORTED = 0xc0000057,
|
|
NT_STATUS_UNKNOWN_REVISION = 0xc0000058,
|
|
NT_STATUS_REVISION_MISMATCH = 0xc0000059,
|
|
NT_STATUS_INVALID_OWNER = 0xc000005a,
|
|
NT_STATUS_INVALID_PRIMARY_GROUP = 0xc000005b,
|
|
NT_STATUS_NO_IMPERSONATION_TOKEN = 0xc000005c,
|
|
NT_STATUS_CANT_DISABLE_MANDATORY = 0xc000005d,
|
|
NT_STATUS_NO_LOGON_SERVERS = 0xc000005e,
|
|
NT_STATUS_NO_SUCH_LOGON_SESSION = 0xc000005f,
|
|
NT_STATUS_NO_SUCH_PRIVILEGE = 0xc0000060,
|
|
NT_STATUS_PRIVILEGE_NOT_HELD = 0xc0000061,
|
|
NT_STATUS_INVALID_ACCOUNT_NAME = 0xc0000062,
|
|
NT_STATUS_USER_EXISTS = 0xc0000063,
|
|
NT_STATUS_NO_SUCH_USER = 0xc0000064,
|
|
NT_STATUS_GROUP_EXISTS = 0xc0000065,
|
|
NT_STATUS_NO_SUCH_GROUP = 0xc0000066,
|
|
NT_STATUS_MEMBER_IN_GROUP = 0xc0000067,
|
|
NT_STATUS_MEMBER_NOT_IN_GROUP = 0xc0000068,
|
|
NT_STATUS_LAST_ADMIN = 0xc0000069,
|
|
NT_STATUS_WRONG_PASSWORD = 0xc000006a,
|
|
NT_STATUS_ILL_FORMED_PASSWORD = 0xc000006b,
|
|
NT_STATUS_PASSWORD_RESTRICTION = 0xc000006c,
|
|
NT_STATUS_LOGON_FAILURE = 0xc000006d,
|
|
NT_STATUS_ACCOUNT_RESTRICTION = 0xc000006e,
|
|
NT_STATUS_INVALID_LOGON_HOURS = 0xc000006f,
|
|
NT_STATUS_INVALID_WORKSTATION = 0xc0000070,
|
|
NT_STATUS_PASSWORD_EXPIRED = 0xc0000071,
|
|
NT_STATUS_ACCOUNT_DISABLED = 0xc0000072,
|
|
NT_STATUS_NONE_MAPPED = 0xc0000073,
|
|
NT_STATUS_TOO_MANY_LUIDS_REQUESTED = 0xc0000074,
|
|
NT_STATUS_LUIDS_EXHAUSTED = 0xc0000075,
|
|
NT_STATUS_INVALID_SUB_AUTHORITY = 0xc0000076,
|
|
NT_STATUS_INVALID_ACL = 0xc0000077,
|
|
NT_STATUS_INVALID_SID = 0xc0000078,
|
|
NT_STATUS_INVALID_SECURITY_DESCR = 0xc0000079,
|
|
NT_STATUS_PROCEDURE_NOT_FOUND = 0xc000007a,
|
|
NT_STATUS_INVALID_IMAGE_FORMAT = 0xc000007b,
|
|
NT_STATUS_NO_TOKEN = 0xc000007c,
|
|
NT_STATUS_BAD_INHERITANCE_ACL = 0xc000007d,
|
|
NT_STATUS_RANGE_NOT_LOCKED = 0xc000007e,
|
|
NT_STATUS_DISK_FULL = 0xc000007f,
|
|
NT_STATUS_SERVER_DISABLED = 0xc0000080,
|
|
NT_STATUS_SERVER_NOT_DISABLED = 0xc0000081,
|
|
NT_STATUS_TOO_MANY_GUIDS_REQUESTED = 0xc0000082,
|
|
NT_STATUS_GUIDS_EXHAUSTED = 0xc0000083,
|
|
NT_STATUS_INVALID_ID_AUTHORITY = 0xc0000084,
|
|
NT_STATUS_AGENTS_EXHAUSTED = 0xc0000085,
|
|
NT_STATUS_INVALID_VOLUME_LABEL = 0xc0000086,
|
|
NT_STATUS_SECTION_NOT_EXTENDED = 0xc0000087,
|
|
NT_STATUS_NOT_MAPPED_DATA = 0xc0000088,
|
|
NT_STATUS_RESOURCE_DATA_NOT_FOUND = 0xc0000089,
|
|
NT_STATUS_RESOURCE_TYPE_NOT_FOUND = 0xc000008a,
|
|
NT_STATUS_RESOURCE_NAME_NOT_FOUND = 0xc000008b,
|
|
NT_STATUS_ARRAY_BOUNDS_EXCEEDED = 0xc000008c,
|
|
NT_STATUS_FLOAT_DENORMAL_OPERAND = 0xc000008d,
|
|
NT_STATUS_FLOAT_DIVIDE_BY_ZERO = 0xc000008e,
|
|
NT_STATUS_FLOAT_INEXACT_RESULT = 0xc000008f,
|
|
NT_STATUS_FLOAT_INVALID_OPERATION = 0xc0000090,
|
|
NT_STATUS_FLOAT_OVERFLOW = 0xc0000091,
|
|
NT_STATUS_FLOAT_STACK_CHECK = 0xc0000092,
|
|
NT_STATUS_FLOAT_UNDERFLOW = 0xc0000093,
|
|
NT_STATUS_INTEGER_DIVIDE_BY_ZERO = 0xc0000094,
|
|
NT_STATUS_INTEGER_OVERFLOW = 0xc0000095,
|
|
NT_STATUS_PRIVILEGED_INSTRUCTION = 0xc0000096,
|
|
NT_STATUS_TOO_MANY_PAGING_FILES = 0xc0000097,
|
|
NT_STATUS_FILE_INVALID = 0xc0000098,
|
|
NT_STATUS_ALLOTTED_SPACE_EXCEEDED = 0xc0000099,
|
|
NT_STATUS_INSUFFICIENT_RESOURCES = 0xc000009a,
|
|
NT_STATUS_DFS_EXIT_PATH_FOUND = 0xc000009b,
|
|
NT_STATUS_DEVICE_DATA_ERROR = 0xc000009c,
|
|
NT_STATUS_DEVICE_NOT_CONNECTED = 0xc000009d,
|
|
NT_STATUS_DEVICE_POWER_FAILURE = 0xc000009e,
|
|
NT_STATUS_FREE_VM_NOT_AT_BASE = 0xc000009f,
|
|
NT_STATUS_MEMORY_NOT_ALLOCATED = 0xc00000a0,
|
|
NT_STATUS_WORKING_SET_QUOTA = 0xc00000a1,
|
|
NT_STATUS_MEDIA_WRITE_PROTECTED = 0xc00000a2,
|
|
NT_STATUS_DEVICE_NOT_READY = 0xc00000a3,
|
|
NT_STATUS_INVALID_GROUP_ATTRIBUTES = 0xc00000a4,
|
|
NT_STATUS_BAD_IMPERSONATION_LEVEL = 0xc00000a5,
|
|
NT_STATUS_CANT_OPEN_ANONYMOUS = 0xc00000a6,
|
|
NT_STATUS_BAD_VALIDATION_CLASS = 0xc00000a7,
|
|
NT_STATUS_BAD_TOKEN_TYPE = 0xc00000a8,
|
|
NT_STATUS_BAD_MASTER_BOOT_RECORD = 0xc00000a9,
|
|
NT_STATUS_INSTRUCTION_MISALIGNMENT = 0xc00000aa,
|
|
NT_STATUS_INSTANCE_NOT_AVAILABLE = 0xc00000ab,
|
|
NT_STATUS_PIPE_NOT_AVAILABLE = 0xc00000ac,
|
|
NT_STATUS_INVALID_PIPE_STATE = 0xc00000ad,
|
|
NT_STATUS_PIPE_BUSY = 0xc00000ae,
|
|
NT_STATUS_ILLEGAL_FUNCTION = 0xc00000af,
|
|
NT_STATUS_PIPE_DISCONNECTED = 0xc00000b0,
|
|
NT_STATUS_PIPE_CLOSING = 0xc00000b1,
|
|
NT_STATUS_PIPE_CONNECTED = 0xc00000b2,
|
|
NT_STATUS_PIPE_LISTENING = 0xc00000b3,
|
|
NT_STATUS_INVALID_READ_MODE = 0xc00000b4,
|
|
NT_STATUS_IO_TIMEOUT = 0xc00000b5,
|
|
NT_STATUS_FILE_FORCED_CLOSED = 0xc00000b6,
|
|
NT_STATUS_PROFILING_NOT_STARTED = 0xc00000b7,
|
|
NT_STATUS_PROFILING_NOT_STOPPED = 0xc00000b8,
|
|
NT_STATUS_COULD_NOT_INTERPRET = 0xc00000b9,
|
|
NT_STATUS_FILE_IS_A_DIRECTORY = 0xc00000ba,
|
|
NT_STATUS_NOT_SUPPORTED = 0xc00000bb,
|
|
NT_STATUS_REMOTE_NOT_LISTENING = 0xc00000bc,
|
|
NT_STATUS_DUPLICATE_NAME = 0xc00000bd,
|
|
NT_STATUS_BAD_NETWORK_PATH = 0xc00000be,
|
|
NT_STATUS_NETWORK_BUSY = 0xc00000bf,
|
|
NT_STATUS_DEVICE_DOES_NOT_EXIST = 0xc00000c0,
|
|
NT_STATUS_TOO_MANY_COMMANDS = 0xc00000c1,
|
|
NT_STATUS_ADAPTER_HARDWARE_ERROR = 0xc00000c2,
|
|
NT_STATUS_INVALID_NETWORK_RESPONSE = 0xc00000c3,
|
|
NT_STATUS_UNEXPECTED_NETWORK_ERROR = 0xc00000c4,
|
|
NT_STATUS_BAD_REMOTE_ADAPTER = 0xc00000c5,
|
|
NT_STATUS_PRINT_QUEUE_FULL = 0xc00000c6,
|
|
NT_STATUS_NO_SPOOL_SPACE = 0xc00000c7,
|
|
NT_STATUS_PRINT_CANCELLED = 0xc00000c8,
|
|
NT_STATUS_NETWORK_NAME_DELETED = 0xc00000c9,
|
|
NT_STATUS_NETWORK_ACCESS_DENIED = 0xc00000ca,
|
|
NT_STATUS_BAD_DEVICE_TYPE = 0xc00000cb,
|
|
NT_STATUS_BAD_NETWORK_NAME = 0xc00000cc,
|
|
NT_STATUS_TOO_MANY_NAMES = 0xc00000cd,
|
|
NT_STATUS_TOO_MANY_SESSIONS = 0xc00000ce,
|
|
NT_STATUS_SHARING_PAUSED = 0xc00000cf,
|
|
NT_STATUS_REQUEST_NOT_ACCEPTED = 0xc00000d0,
|
|
NT_STATUS_REDIRECTOR_PAUSED = 0xc00000d1,
|
|
NT_STATUS_NET_WRITE_FAULT = 0xc00000d2,
|
|
NT_STATUS_PROFILING_AT_LIMIT = 0xc00000d3,
|
|
NT_STATUS_NOT_SAME_DEVICE = 0xc00000d4,
|
|
NT_STATUS_FILE_RENAMED = 0xc00000d5,
|
|
NT_STATUS_VIRTUAL_CIRCUIT_CLOSED = 0xc00000d6,
|
|
NT_STATUS_NO_SECURITY_ON_OBJECT = 0xc00000d7,
|
|
NT_STATUS_CANT_WAIT = 0xc00000d8,
|
|
NT_STATUS_PIPE_EMPTY = 0xc00000d9,
|
|
NT_STATUS_CANT_ACCESS_DOMAIN_INFO = 0xc00000da,
|
|
NT_STATUS_CANT_TERMINATE_SELF = 0xc00000db,
|
|
NT_STATUS_INVALID_SERVER_STATE = 0xc00000dc,
|
|
NT_STATUS_INVALID_DOMAIN_STATE = 0xc00000dd,
|
|
NT_STATUS_INVALID_DOMAIN_ROLE = 0xc00000de,
|
|
NT_STATUS_NO_SUCH_DOMAIN = 0xc00000df,
|
|
NT_STATUS_DOMAIN_EXISTS = 0xc00000e0,
|
|
NT_STATUS_DOMAIN_LIMIT_EXCEEDED = 0xc00000e1,
|
|
NT_STATUS_OPLOCK_NOT_GRANTED = 0xc00000e2,
|
|
NT_STATUS_INVALID_OPLOCK_PROTOCOL = 0xc00000e3,
|
|
NT_STATUS_INTERNAL_DB_CORRUPTION = 0xc00000e4,
|
|
NT_STATUS_INTERNAL_ERROR = 0xc00000e5,
|
|
NT_STATUS_GENERIC_NOT_MAPPED = 0xc00000e6,
|
|
NT_STATUS_BAD_DESCRIPTOR_FORMAT = 0xc00000e7,
|
|
NT_STATUS_INVALID_USER_BUFFER = 0xc00000e8,
|
|
NT_STATUS_UNEXPECTED_IO_ERROR = 0xc00000e9,
|
|
NT_STATUS_UNEXPECTED_MM_CREATE_ERR = 0xc00000ea,
|
|
NT_STATUS_UNEXPECTED_MM_MAP_ERROR = 0xc00000eb,
|
|
NT_STATUS_UNEXPECTED_MM_EXTEND_ERR = 0xc00000ec,
|
|
NT_STATUS_NOT_LOGON_PROCESS = 0xc00000ed,
|
|
NT_STATUS_LOGON_SESSION_EXISTS = 0xc00000ee,
|
|
NT_STATUS_INVALID_PARAMETER_1 = 0xc00000ef,
|
|
NT_STATUS_INVALID_PARAMETER_2 = 0xc00000f0,
|
|
NT_STATUS_INVALID_PARAMETER_3 = 0xc00000f1,
|
|
NT_STATUS_INVALID_PARAMETER_4 = 0xc00000f2,
|
|
NT_STATUS_INVALID_PARAMETER_5 = 0xc00000f3,
|
|
NT_STATUS_INVALID_PARAMETER_6 = 0xc00000f4,
|
|
NT_STATUS_INVALID_PARAMETER_7 = 0xc00000f5,
|
|
NT_STATUS_INVALID_PARAMETER_8 = 0xc00000f6,
|
|
NT_STATUS_INVALID_PARAMETER_9 = 0xc00000f7,
|
|
NT_STATUS_INVALID_PARAMETER_10 = 0xc00000f8,
|
|
NT_STATUS_INVALID_PARAMETER_11 = 0xc00000f9,
|
|
NT_STATUS_INVALID_PARAMETER_12 = 0xc00000fa,
|
|
NT_STATUS_REDIRECTOR_NOT_STARTED = 0xc00000fb,
|
|
NT_STATUS_REDIRECTOR_STARTED = 0xc00000fc,
|
|
NT_STATUS_STACK_OVERFLOW = 0xc00000fd,
|
|
NT_STATUS_NO_SUCH_PACKAGE = 0xc00000fe,
|
|
NT_STATUS_BAD_FUNCTION_TABLE = 0xc00000ff,
|
|
NT_STATUS_DIRECTORY_NOT_EMPTY = 0xc0000101,
|
|
NT_STATUS_FILE_CORRUPT_ERROR = 0xc0000102,
|
|
NT_STATUS_NOT_A_DIRECTORY = 0xc0000103,
|
|
NT_STATUS_BAD_LOGON_SESSION_STATE = 0xc0000104,
|
|
NT_STATUS_LOGON_SESSION_COLLISION = 0xc0000105,
|
|
NT_STATUS_NAME_TOO_LONG = 0xc0000106,
|
|
NT_STATUS_FILES_OPEN = 0xc0000107,
|
|
NT_STATUS_CONNECTION_IN_USE = 0xc0000108,
|
|
NT_STATUS_MESSAGE_NOT_FOUND = 0xc0000109,
|
|
NT_STATUS_PROCESS_IS_TERMINATING = 0xc000010a,
|
|
NT_STATUS_INVALID_LOGON_TYPE = 0xc000010b,
|
|
NT_STATUS_NO_GUID_TRANSLATION = 0xc000010c,
|
|
NT_STATUS_CANNOT_IMPERSONATE = 0xc000010d,
|
|
NT_STATUS_IMAGE_ALREADY_LOADED = 0xc000010e,
|
|
NT_STATUS_ABIOS_NOT_PRESENT = 0xc000010f,
|
|
NT_STATUS_ABIOS_LID_NOT_EXIST = 0xc0000110,
|
|
NT_STATUS_ABIOS_LID_ALREADY_OWNED = 0xc0000111,
|
|
NT_STATUS_ABIOS_NOT_LID_OWNER = 0xc0000112,
|
|
NT_STATUS_ABIOS_INVALID_COMMAND = 0xc0000113,
|
|
NT_STATUS_ABIOS_INVALID_LID = 0xc0000114,
|
|
NT_STATUS_ABIOS_SELECTOR_NOT_AVAILABLE = 0xc0000115,
|
|
NT_STATUS_ABIOS_INVALID_SELECTOR = 0xc0000116,
|
|
NT_STATUS_NO_LDT = 0xc0000117,
|
|
NT_STATUS_INVALID_LDT_SIZE = 0xc0000118,
|
|
NT_STATUS_INVALID_LDT_OFFSET = 0xc0000119,
|
|
NT_STATUS_INVALID_LDT_DESCRIPTOR = 0xc000011a,
|
|
NT_STATUS_INVALID_IMAGE_NE_FORMAT = 0xc000011b,
|
|
NT_STATUS_RXACT_INVALID_STATE = 0xc000011c,
|
|
NT_STATUS_RXACT_COMMIT_FAILURE = 0xc000011d,
|
|
NT_STATUS_MAPPED_FILE_SIZE_ZERO = 0xc000011e,
|
|
NT_STATUS_TOO_MANY_OPENED_FILES = 0xc000011f,
|
|
NT_STATUS_CANCELLED = 0xc0000120,
|
|
NT_STATUS_CANNOT_DELETE = 0xc0000121,
|
|
NT_STATUS_INVALID_COMPUTER_NAME = 0xc0000122,
|
|
NT_STATUS_FILE_DELETED = 0xc0000123,
|
|
NT_STATUS_SPECIAL_ACCOUNT = 0xc0000124,
|
|
NT_STATUS_SPECIAL_GROUP = 0xc0000125,
|
|
NT_STATUS_SPECIAL_USER = 0xc0000126,
|
|
NT_STATUS_MEMBERS_PRIMARY_GROUP = 0xc0000127,
|
|
NT_STATUS_FILE_CLOSED = 0xc0000128,
|
|
NT_STATUS_TOO_MANY_THREADS = 0xc0000129,
|
|
NT_STATUS_THREAD_NOT_IN_PROCESS = 0xc000012a,
|
|
NT_STATUS_TOKEN_ALREADY_IN_USE = 0xc000012b,
|
|
NT_STATUS_PAGEFILE_QUOTA_EXCEEDED = 0xc000012c,
|
|
NT_STATUS_COMMITMENT_LIMIT = 0xc000012d,
|
|
NT_STATUS_INVALID_IMAGE_LE_FORMAT = 0xc000012e,
|
|
NT_STATUS_INVALID_IMAGE_NOT_MZ = 0xc000012f,
|
|
NT_STATUS_INVALID_IMAGE_PROTECT = 0xc0000130,
|
|
NT_STATUS_INVALID_IMAGE_WIN_16 = 0xc0000131,
|
|
NT_STATUS_LOGON_SERVER_CONFLICT = 0xc0000132,
|
|
NT_STATUS_TIME_DIFFERENCE_AT_DC = 0xc0000133,
|
|
NT_STATUS_SYNCHRONIZATION_REQUIRED = 0xc0000134,
|
|
NT_STATUS_DLL_NOT_FOUND = 0xc0000135,
|
|
NT_STATUS_OPEN_FAILED = 0xc0000136,
|
|
NT_STATUS_IO_PRIVILEGE_FAILED = 0xc0000137,
|
|
NT_STATUS_ORDINAL_NOT_FOUND = 0xc0000138,
|
|
NT_STATUS_ENTRYPOINT_NOT_FOUND = 0xc0000139,
|
|
NT_STATUS_CONTROL_C_EXIT = 0xc000013a,
|
|
NT_STATUS_LOCAL_DISCONNECT = 0xc000013b,
|
|
NT_STATUS_REMOTE_DISCONNECT = 0xc000013c,
|
|
NT_STATUS_REMOTE_RESOURCES = 0xc000013d,
|
|
NT_STATUS_LINK_FAILED = 0xc000013e,
|
|
NT_STATUS_LINK_TIMEOUT = 0xc000013f,
|
|
NT_STATUS_INVALID_CONNECTION = 0xc0000140,
|
|
NT_STATUS_INVALID_ADDRESS = 0xc0000141,
|
|
NT_STATUS_DLL_INIT_FAILED = 0xc0000142,
|
|
NT_STATUS_MISSING_SYSTEMFILE = 0xc0000143,
|
|
NT_STATUS_UNHANDLED_EXCEPTION = 0xc0000144,
|
|
NT_STATUS_APP_INIT_FAILURE = 0xc0000145,
|
|
NT_STATUS_PAGEFILE_CREATE_FAILED = 0xc0000146,
|
|
NT_STATUS_NO_PAGEFILE = 0xc0000147,
|
|
NT_STATUS_INVALID_LEVEL = 0xc0000148,
|
|
NT_STATUS_WRONG_PASSWORD_CORE = 0xc0000149,
|
|
NT_STATUS_ILLEGAL_FLOAT_CONTEXT = 0xc000014a,
|
|
NT_STATUS_PIPE_BROKEN = 0xc000014b,
|
|
NT_STATUS_REGISTRY_CORRUPT = 0xc000014c,
|
|
NT_STATUS_REGISTRY_IO_FAILED = 0xc000014d,
|
|
NT_STATUS_NO_EVENT_PAIR = 0xc000014e,
|
|
NT_STATUS_UNRECOGNIZED_VOLUME = 0xc000014f,
|
|
NT_STATUS_SERIAL_NO_DEVICE_INITED = 0xc0000150,
|
|
NT_STATUS_NO_SUCH_ALIAS = 0xc0000151,
|
|
NT_STATUS_MEMBER_NOT_IN_ALIAS = 0xc0000152,
|
|
NT_STATUS_MEMBER_IN_ALIAS = 0xc0000153,
|
|
NT_STATUS_ALIAS_EXISTS = 0xc0000154,
|
|
NT_STATUS_LOGON_NOT_GRANTED = 0xc0000155,
|
|
NT_STATUS_TOO_MANY_SECRETS = 0xc0000156,
|
|
NT_STATUS_SECRET_TOO_LONG = 0xc0000157,
|
|
NT_STATUS_INTERNAL_DB_ERROR = 0xc0000158,
|
|
NT_STATUS_FULLSCREEN_MODE = 0xc0000159,
|
|
NT_STATUS_TOO_MANY_CONTEXT_IDS = 0xc000015a,
|
|
NT_STATUS_LOGON_TYPE_NOT_GRANTED = 0xc000015b,
|
|
NT_STATUS_NOT_REGISTRY_FILE = 0xc000015c,
|
|
NT_STATUS_NT_CROSS_ENCRYPTION_REQUIRED = 0xc000015d,
|
|
NT_STATUS_DOMAIN_CTRLR_CONFIG_ERROR = 0xc000015e,
|
|
NT_STATUS_FT_MISSING_MEMBER = 0xc000015f,
|
|
NT_STATUS_ILL_FORMED_SERVICE_ENTRY = 0xc0000160,
|
|
NT_STATUS_ILLEGAL_CHARACTER = 0xc0000161,
|
|
NT_STATUS_UNMAPPABLE_CHARACTER = 0xc0000162,
|
|
NT_STATUS_UNDEFINED_CHARACTER = 0xc0000163,
|
|
NT_STATUS_FLOPPY_VOLUME = 0xc0000164,
|
|
NT_STATUS_FLOPPY_ID_MARK_NOT_FOUND = 0xc0000165,
|
|
NT_STATUS_FLOPPY_WRONG_CYLINDER = 0xc0000166,
|
|
NT_STATUS_FLOPPY_UNKNOWN_ERROR = 0xc0000167,
|
|
NT_STATUS_FLOPPY_BAD_REGISTERS = 0xc0000168,
|
|
NT_STATUS_DISK_RECALIBRATE_FAILED = 0xc0000169,
|
|
NT_STATUS_DISK_OPERATION_FAILED = 0xc000016a,
|
|
NT_STATUS_DISK_RESET_FAILED = 0xc000016b,
|
|
NT_STATUS_SHARED_IRQ_BUSY = 0xc000016c,
|
|
NT_STATUS_FT_ORPHANING = 0xc000016d,
|
|
NT_STATUS_PARTITION_FAILURE = 0xc0000172,
|
|
NT_STATUS_INVALID_BLOCK_LENGTH = 0xc0000173,
|
|
NT_STATUS_DEVICE_NOT_PARTITIONED = 0xc0000174,
|
|
NT_STATUS_UNABLE_TO_LOCK_MEDIA = 0xc0000175,
|
|
NT_STATUS_UNABLE_TO_UNLOAD_MEDIA = 0xc0000176,
|
|
NT_STATUS_EOM_OVERFLOW = 0xc0000177,
|
|
NT_STATUS_NO_MEDIA = 0xc0000178,
|
|
NT_STATUS_NO_SUCH_MEMBER = 0xc000017a,
|
|
NT_STATUS_INVALID_MEMBER = 0xc000017b,
|
|
NT_STATUS_KEY_DELETED = 0xc000017c,
|
|
NT_STATUS_NO_LOG_SPACE = 0xc000017d,
|
|
NT_STATUS_TOO_MANY_SIDS = 0xc000017e,
|
|
NT_STATUS_LM_CROSS_ENCRYPTION_REQUIRED = 0xc000017f,
|
|
NT_STATUS_KEY_HAS_CHILDREN = 0xc0000180,
|
|
NT_STATUS_CHILD_MUST_BE_VOLATILE = 0xc0000181,
|
|
NT_STATUS_DEVICE_CONFIGURATION_ERROR = 0xc0000182,
|
|
NT_STATUS_DRIVER_INTERNAL_ERROR = 0xc0000183,
|
|
NT_STATUS_INVALID_DEVICE_STATE = 0xc0000184,
|
|
NT_STATUS_IO_DEVICE_ERROR = 0xc0000185,
|
|
NT_STATUS_DEVICE_PROTOCOL_ERROR = 0xc0000186,
|
|
NT_STATUS_BACKUP_CONTROLLER = 0xc0000187,
|
|
NT_STATUS_LOG_FILE_FULL = 0xc0000188,
|
|
NT_STATUS_TOO_LATE = 0xc0000189,
|
|
NT_STATUS_NO_TRUST_LSA_SECRET = 0xc000018a,
|
|
NT_STATUS_NO_TRUST_SAM_ACCOUNT = 0xc000018b,
|
|
NT_STATUS_TRUSTED_DOMAIN_FAILURE = 0xc000018c,
|
|
NT_STATUS_TRUSTED_RELATIONSHIP_FAILURE = 0xc000018d,
|
|
NT_STATUS_EVENTLOG_FILE_CORRUPT = 0xc000018e,
|
|
NT_STATUS_EVENTLOG_CANT_START = 0xc000018f,
|
|
NT_STATUS_TRUST_FAILURE = 0xc0000190,
|
|
NT_STATUS_MUTANT_LIMIT_EXCEEDED = 0xc0000191,
|
|
NT_STATUS_NETLOGON_NOT_STARTED = 0xc0000192,
|
|
NT_STATUS_ACCOUNT_EXPIRED = 0xc0000193,
|
|
NT_STATUS_POSSIBLE_DEADLOCK = 0xc0000194,
|
|
NT_STATUS_NETWORK_CREDENTIAL_CONFLICT = 0xc0000195,
|
|
NT_STATUS_REMOTE_SESSION_LIMIT = 0xc0000196,
|
|
NT_STATUS_EVENTLOG_FILE_CHANGED = 0xc0000197,
|
|
NT_STATUS_NOLOGON_INTERDOMAIN_TRUST_ACCOUNT = 0xc0000198,
|
|
NT_STATUS_NOLOGON_WORKSTATION_TRUST_ACCOUNT = 0xc0000199,
|
|
NT_STATUS_NOLOGON_SERVER_TRUST_ACCOUNT = 0xc000019a,
|
|
NT_STATUS_DOMAIN_TRUST_INCONSISTENT = 0xc000019b,
|
|
NT_STATUS_FS_DRIVER_REQUIRED = 0xc000019c,
|
|
NT_STATUS_NO_USER_SESSION_KEY = 0xc0000202,
|
|
NT_STATUS_USER_SESSION_DELETED = 0xc0000203,
|
|
NT_STATUS_RESOURCE_LANG_NOT_FOUND = 0xc0000204,
|
|
NT_STATUS_INSUFF_SERVER_RESOURCES = 0xc0000205,
|
|
NT_STATUS_INVALID_BUFFER_SIZE = 0xc0000206,
|
|
NT_STATUS_INVALID_ADDRESS_COMPONENT = 0xc0000207,
|
|
NT_STATUS_INVALID_ADDRESS_WILDCARD = 0xc0000208,
|
|
NT_STATUS_TOO_MANY_ADDRESSES = 0xc0000209,
|
|
NT_STATUS_ADDRESS_ALREADY_EXISTS = 0xc000020a,
|
|
NT_STATUS_ADDRESS_CLOSED = 0xc000020b,
|
|
NT_STATUS_CONNECTION_DISCONNECTED = 0xc000020c,
|
|
NT_STATUS_CONNECTION_RESET = 0xc000020d,
|
|
NT_STATUS_TOO_MANY_NODES = 0xc000020e,
|
|
NT_STATUS_TRANSACTION_ABORTED = 0xc000020f,
|
|
NT_STATUS_TRANSACTION_TIMED_OUT = 0xc0000210,
|
|
NT_STATUS_TRANSACTION_NO_RELEASE = 0xc0000211,
|
|
NT_STATUS_TRANSACTION_NO_MATCH = 0xc0000212,
|
|
NT_STATUS_TRANSACTION_RESPONDED = 0xc0000213,
|
|
NT_STATUS_TRANSACTION_INVALID_ID = 0xc0000214,
|
|
NT_STATUS_TRANSACTION_INVALID_TYPE = 0xc0000215,
|
|
NT_STATUS_NOT_SERVER_SESSION = 0xc0000216,
|
|
NT_STATUS_NOT_CLIENT_SESSION = 0xc0000217,
|
|
NT_STATUS_CANNOT_LOAD_REGISTRY_FILE = 0xc0000218,
|
|
NT_STATUS_DEBUG_ATTACH_FAILED = 0xc0000219,
|
|
NT_STATUS_SYSTEM_PROCESS_TERMINATED = 0xc000021a,
|
|
NT_STATUS_DATA_NOT_ACCEPTED = 0xc000021b,
|
|
NT_STATUS_NO_BROWSER_SERVERS_FOUND = 0xc000021c,
|
|
NT_STATUS_VDM_HARD_ERROR = 0xc000021d,
|
|
NT_STATUS_DRIVER_CANCEL_TIMEOUT = 0xc000021e,
|
|
NT_STATUS_REPLY_MESSAGE_MISMATCH = 0xc000021f,
|
|
NT_STATUS_MAPPED_ALIGNMENT = 0xc0000220,
|
|
NT_STATUS_IMAGE_CHECKSUM_MISMATCH = 0xc0000221,
|
|
NT_STATUS_LOST_WRITEBEHIND_DATA = 0xc0000222,
|
|
NT_STATUS_CLIENT_SERVER_PARAMETERS_INVALID = 0xc0000223,
|
|
NT_STATUS_PASSWORD_MUST_CHANGE = 0xc0000224,
|
|
NT_STATUS_NOT_FOUND = 0xc0000225,
|
|
NT_STATUS_NOT_TINY_STREAM = 0xc0000226,
|
|
NT_STATUS_RECOVERY_FAILURE = 0xc0000227,
|
|
NT_STATUS_STACK_OVERFLOW_READ = 0xc0000228,
|
|
NT_STATUS_FAIL_CHECK = 0xc0000229,
|
|
NT_STATUS_DUPLICATE_OBJECTID = 0xc000022a,
|
|
NT_STATUS_OBJECTID_EXISTS = 0xc000022b,
|
|
NT_STATUS_CONVERT_TO_LARGE = 0xc000022c,
|
|
NT_STATUS_RETRY = 0xc000022d,
|
|
NT_STATUS_FOUND_OUT_OF_SCOPE = 0xc000022e,
|
|
NT_STATUS_ALLOCATE_BUCKET = 0xc000022f,
|
|
NT_STATUS_PROPSET_NOT_FOUND = 0xc0000230,
|
|
NT_STATUS_MARSHALL_OVERFLOW = 0xc0000231,
|
|
NT_STATUS_INVALID_VARIANT = 0xc0000232,
|
|
NT_STATUS_DOMAIN_CONTROLLER_NOT_FOUND = 0xc0000233,
|
|
NT_STATUS_ACCOUNT_LOCKED_OUT = 0xc0000234,
|
|
NT_STATUS_HANDLE_NOT_CLOSABLE = 0xc0000235,
|
|
NT_STATUS_CONNECTION_REFUSED = 0xc0000236,
|
|
NT_STATUS_GRACEFUL_DISCONNECT = 0xc0000237,
|
|
NT_STATUS_ADDRESS_ALREADY_ASSOCIATED = 0xc0000238,
|
|
NT_STATUS_ADDRESS_NOT_ASSOCIATED = 0xc0000239,
|
|
NT_STATUS_CONNECTION_INVALID = 0xc000023a,
|
|
NT_STATUS_CONNECTION_ACTIVE = 0xc000023b,
|
|
NT_STATUS_NETWORK_UNREACHABLE = 0xc000023c,
|
|
NT_STATUS_HOST_UNREACHABLE = 0xc000023d,
|
|
NT_STATUS_PROTOCOL_UNREACHABLE = 0xc000023e,
|
|
NT_STATUS_PORT_UNREACHABLE = 0xc000023f,
|
|
NT_STATUS_REQUEST_ABORTED = 0xc0000240,
|
|
NT_STATUS_CONNECTION_ABORTED = 0xc0000241,
|
|
NT_STATUS_BAD_COMPRESSION_BUFFER = 0xc0000242,
|
|
NT_STATUS_USER_MAPPED_FILE = 0xc0000243,
|
|
NT_STATUS_AUDIT_FAILED = 0xc0000244,
|
|
NT_STATUS_TIMER_RESOLUTION_NOT_SET = 0xc0000245,
|
|
NT_STATUS_CONNECTION_COUNT_LIMIT = 0xc0000246,
|
|
NT_STATUS_LOGIN_TIME_RESTRICTION = 0xc0000247,
|
|
NT_STATUS_LOGIN_WKSTA_RESTRICTION = 0xc0000248,
|
|
NT_STATUS_IMAGE_MP_UP_MISMATCH = 0xc0000249,
|
|
NT_STATUS_INSUFFICIENT_LOGON_INFO = 0xc0000250,
|
|
NT_STATUS_BAD_DLL_ENTRYPOINT = 0xc0000251,
|
|
NT_STATUS_BAD_SERVICE_ENTRYPOINT = 0xc0000252,
|
|
NT_STATUS_LPC_REPLY_LOST = 0xc0000253,
|
|
NT_STATUS_IP_ADDRESS_CONFLICT1 = 0xc0000254,
|
|
NT_STATUS_IP_ADDRESS_CONFLICT2 = 0xc0000255,
|
|
NT_STATUS_REGISTRY_QUOTA_LIMIT = 0xc0000256,
|
|
NT_STATUS_PATH_NOT_COVERED = 0xc0000257,
|
|
NT_STATUS_NO_CALLBACK_ACTIVE = 0xc0000258,
|
|
NT_STATUS_LICENSE_QUOTA_EXCEEDED = 0xc0000259,
|
|
NT_STATUS_PWD_TOO_SHORT = 0xc000025a,
|
|
NT_STATUS_PWD_TOO_RECENT = 0xc000025b,
|
|
NT_STATUS_PWD_HISTORY_CONFLICT = 0xc000025c,
|
|
NT_STATUS_PLUGPLAY_NO_DEVICE = 0xc000025e,
|
|
NT_STATUS_UNSUPPORTED_COMPRESSION = 0xc000025f,
|
|
NT_STATUS_INVALID_HW_PROFILE = 0xc0000260,
|
|
NT_STATUS_INVALID_PLUGPLAY_DEVICE_PATH = 0xc0000261,
|
|
NT_STATUS_DRIVER_ORDINAL_NOT_FOUND = 0xc0000262,
|
|
NT_STATUS_DRIVER_ENTRYPOINT_NOT_FOUND = 0xc0000263,
|
|
NT_STATUS_RESOURCE_NOT_OWNED = 0xc0000264,
|
|
NT_STATUS_TOO_MANY_LINKS = 0xc0000265,
|
|
NT_STATUS_QUOTA_LIST_INCONSISTENT = 0xc0000266,
|
|
NT_STATUS_FILE_IS_OFFLINE = 0xc0000267,
|
|
NT_STATUS_DS_NO_MORE_RIDS = 0xc00002a8,
|
|
NT_STATUS_NOT_A_REPARSE_POINT = 0xc0000275,
|
|
NT_STATUS_NO_SUCH_JOB = 0xc000EDE
|
|
}
|
|
|
|
for i, v in pairs(status_codes) do
|
|
status_names[v] = i
|
|
end
|
|
|
|
|
|
local NP_LIBRARY_NAME = "PIPE"
|
|
|
|
namedpipes =
|
|
{
|
|
get_pipe_subpath = function( pipeName, writeToDebugLog )
|
|
local status, pipeSubPath
|
|
if not pipeName then return false end
|
|
|
|
local _, _, match = pipeName:match( "^(\\+)(.-)\\pipe(\\.-)$" )
|
|
if match then
|
|
pipeSubPath = match
|
|
status = true
|
|
if writeToDebugLog then
|
|
stdnse.debug2("%s: Converting %s to subpath %s", NP_LIBRARY_NAME, pipeName, match )
|
|
end
|
|
else
|
|
status = false
|
|
pipeSubPath = pipeName
|
|
end
|
|
|
|
return status, pipeSubPath
|
|
end,
|
|
|
|
|
|
make_pipe_name = function( hostnameOrIp, pipeSubPath )
|
|
if pipeSubPath:sub(1,1) ~= "\\" then
|
|
pipeSubPath = "\\" .. pipeSubPath
|
|
end
|
|
|
|
return string.format( "\\\\%s\\pipe%s", hostnameOrIp, pipeSubPath )
|
|
end,
|
|
|
|
|
|
named_pipe = {
|
|
|
|
_smbstate = nil,
|
|
_host = nil,
|
|
_pipeSubPath = nil,
|
|
_overrides = nil,
|
|
name = nil,
|
|
|
|
new = function(self,o)
|
|
o = o or {}
|
|
setmetatable(o, self)
|
|
self.__index = self
|
|
return o
|
|
end,
|
|
|
|
|
|
connect = function( self, host, pipeSubPath, overrides )
|
|
|
|
stdnse.debug2("%s: connect() called with %s", NP_LIBRARY_NAME, tostring( pipeSubPath ) )
|
|
self._overrides = overrides or {}
|
|
self._host = host
|
|
self._pipeSubPath = pipeSubPath
|
|
if not host and not host.ip then return false, "host table is required" end
|
|
if not pipeSubPath then return false, "pipeSubPath is required" end
|
|
|
|
-- If we got a full pipe name, not a sub-path, fix it
|
|
if ( pipeSubPath:match( "^\\\\(.-)$" ) ) then
|
|
local status
|
|
status, self._pipeSubPath = namedpipes.get_pipe_subpath( self._pipeSubPath, true )
|
|
if ( not status ) then
|
|
stdnse.debug1("%s: Attempt to connect to invalid pipe name: %s", NP_LIBRARY_NAME, tostring( pipeSubPath ) )
|
|
return false, "Invalid pipe name"
|
|
end
|
|
end
|
|
self.name = namedpipes.make_pipe_name( self._host.ip, self._pipeSubPath )
|
|
|
|
stdnse.debug2("%s: Connecting to named pipe: %s", NP_LIBRARY_NAME, self.name )
|
|
local status, result, errorMessage
|
|
local bool_negotiate_protocol, bool_start_session, bool_disable_extended = true, true, false
|
|
status, result = start_ex( self._host, bool_negotiate_protocol, bool_start_session,
|
|
"IPC$", self._pipeSubPath, bool_disable_extended, self._overrides )
|
|
|
|
if status then
|
|
self._smbstate = result
|
|
else
|
|
errorMessage = string.format( "Connection failed: %s", result )
|
|
stdnse.debug2("%s: Connection to named pipe (%s) failed: %s",
|
|
NP_LIBRARY_NAME, self.name, errorMessage )
|
|
end
|
|
|
|
return status, errorMessage, result
|
|
end,
|
|
|
|
|
|
disconnect = function( self )
|
|
if ( self._smbstate ) then
|
|
stdnse.debug2("%s: Disconnecting named pipe: %s", NP_LIBRARY_NAME, self.name )
|
|
return stop( self._smbstate )
|
|
else
|
|
stdnse.debug2("%s: disconnect() called, but SMB connection is already closed: %s", NP_LIBRARY_NAME, self.name )
|
|
end
|
|
end,
|
|
|
|
|
|
send = function( self, messageData )
|
|
if not self._smbstate then
|
|
stdnse.debug2("%s: send() called on closed pipe (%s)", NP_LIBRARY_NAME, self.name )
|
|
return false, "Failed to send message on named pipe"
|
|
end
|
|
|
|
local offset = 0 -- offset is actually ignored for named pipes, but we'll define the argument for clarity
|
|
local status, result, errorMessage
|
|
|
|
status, result = write_file( self._smbstate, messageData, offset, self._overrides )
|
|
|
|
-- if status is true, result is data that we don't need to pay attention to
|
|
if not status then
|
|
stdnse.debug2("%s: Write to named pipe (%s) failed: %s",
|
|
NP_LIBRARY_NAME, self.name, result )
|
|
errorMessage = "Failed to send message on named pipe", result
|
|
end
|
|
|
|
return status, errorMessage
|
|
end,
|
|
|
|
|
|
receive = function( self )
|
|
if not self._smbstate then
|
|
stdnse.debug2("%s: receive() called on closed pipe (%s)", NP_LIBRARY_NAME, self.name )
|
|
return false, "Failed to read from named pipe"
|
|
end
|
|
|
|
local status, result, messageData
|
|
-- Packet header values
|
|
local offset = 0 -- offset is actually ignored for named pipes, but we'll define the argument for clarity
|
|
local MAX_BYTES_PER_READ = 4096
|
|
|
|
status, result = read_file( self._smbstate, offset, MAX_BYTES_PER_READ, self._overrides )
|
|
|
|
if status and result.data then
|
|
messageData = result.data
|
|
else
|
|
stdnse.debug2("%s: Read from named pipe (%s) failed: %s",
|
|
NP_LIBRARY_NAME, self.name, result )
|
|
return false, "Failed to read from named pipe", result
|
|
end
|
|
|
|
while (result["status"] == status_codes.NT_STATUS_BUFFER_OVERFLOW) do
|
|
status, result = read_file( self._smbstate, offset, MAX_BYTES_PER_READ, self._overrides )
|
|
|
|
if status and result.data then
|
|
messageData = messageData .. result.data
|
|
else
|
|
stdnse.debug2("%s: Read additional data from named pipe (%s) failed: %s",
|
|
NP_LIBRARY_NAME, self.name, result )
|
|
return false, "Failed to read from named pipe", result
|
|
end
|
|
end
|
|
|
|
return status, messageData
|
|
end,
|
|
}
|
|
|
|
}
|
|
|
|
filetype_codes =
|
|
{
|
|
FILE_TYPE_DISK = 0x00,
|
|
FILE_TYPE_BYTE_MODE_PIPE = 0x01,
|
|
FILE_TYPE_MESSAGE_MODE_PIPE = 0x02,
|
|
FILE_TYPE_PRINTER = 0x03,
|
|
FILE_TYPE_UNKNOWN = 0xFF
|
|
}
|
|
|
|
for i, v in pairs(filetype_codes) do
|
|
filetype_names[v] = i
|
|
end
|
|
|
|
return _ENV;
|