1
0
mirror of https://github.com/nmap/nmap.git synced 2025-12-06 04:31:29 +00:00
Files
nmap/nselib/smb.lua
dmiller 2692746c42 NSEdoc cleanup
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>
2014-09-02 18:23:06 +00:00

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;