D7net
Home
Console
Upload
information
Create File
Create Folder
About
Tools
:
/
usr
/
share
/
nmap
/
nselib
/
Filename :
smb.lua
back
Copy
--- -- 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" _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.print_debug(2, "SMB: Resolved netbios name from cache") state['name'] = host.registry['netbios_name'] end nbcache_mutex "done" stdnse.print_debug(2, "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.print_debug(2, "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.print_debug(1, "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.print_debug(3, "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.print_debug(3, "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.print_debug(3, "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.print_debug(3, "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.print_debug(1, "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.print_debug(1, "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.print_debug(3, "SMB: Not signing message (missing mac_key)") return body elseif(nmap.registry.args.smbsign == "disable") then stdnse.print_debug(3, "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.print_debug(3, "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.print_debug(3, "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.print_debug(3, "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.print_debug(3, "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.print_debug(1, "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.print_debug(3, "SMB: Receiving SMB packet") -- Receive the response -- we make sure to receive at least 4 bytes, the length of the NetBIOS length 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.print_debug(1, "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.print_debug(1, "SMB: ERROR: Received wrong number of bytes, there will likely be issues (recieved %d, expected %d)", #result, length) return false, string.format("SMB: ERROR: Didn't receive the expected number of bytes; recieved %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.print_debug(3, "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, ehader4, 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.print_debug(2, "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, header4 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 domain as a Unicode string local ch, dummy smb['domain'] = "" smb['server'] = "" pos, ch, dummy = bin.unpack("<CC", data, pos) -- if(dummy == nil) then -- return false, "SMB: ERROR: Server returned less data than it was supposed to (one or more fields are missing); aborting [13]" -- end while ch ~= nil and ch ~= 0 do smb['domain'] = smb['domain'] .. string.char(ch) pos, ch, dummy = bin.unpack("<CC", data, pos) if(ch == nil or dummy == nil) then return false, "SMB: ERROR: Server returned less data than it was supposed to (one or more fields are missing); aborting [14]" end end -- Get the server name as a Unicode string -- Note: This can be nil, Samba leaves this off pos, ch, dummy = bin.unpack("<CC", data, pos) while ch ~= nil and ch ~= 0 do smb['server'] = smb['server'] .. string.char(ch) pos, ch, dummy = bin.unpack("<CC", data, 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.print_debug(2, "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.print_debug(1, "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.print_debug(1, "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.print_debug(1, "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.print_debug(2, "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.print_debug(1, "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.print_debug(1, "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.print_debug(1, "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.print_debug(2, "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.print_debug(1, "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.print_debug(1, "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.print_debug(1, string.format("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.print_debug(2, string.format("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.print_debug(1, "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.print_debug(1, "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.print_debug(1, "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 servers'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.print_debug(2, "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.print_debug(2, "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.print_debug(2, "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.print_debug(1, "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.print_debug(2, "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.print_debug(1, "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.print_debug(2, "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.print_debug(2, "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.print_debug(2, "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.print_debug(2, "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.print_debug(2, "SMB: Sending SMB_COM_TRANSACTION2") local result, err = smb_send(smb, header, parameters, data, overrides) if(result == false) then return false, err end 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.print_debug(2, "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.print_debug(2, "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.print_debug(1, "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 sessioan 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 sessioan 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 sessioan 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.print_debug(1, "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 retreiving the next result function find_files(smbstate, fname, options) local TRANS2_FIND_FIRST2, TRANS2_FIND_NEXT2 = 1, 2 options = options or {} if (not(options.srch_attrs)) then options.srch_attrs = { ro = true, hidden = true, system = true, dir = true} end local nattrs = ( options.srch_attrs.ro and 1 or 0 ) + ( options.srch_attrs.hidden and 2 or 0 ) + ( options.srch_attrs.hidden and 2 or 0 ) + ( options.srch_attrs.system and 4 or 0 ) + ( options.srch_attrs.volid and 8 or 0 ) + ( options.srch_attrs.dir and 16 or 0 ) + ( options.srch_attrs.archive and 32 or 0 ) if ( not(fname) ) then fname = '\\*' elseif( fname:sub(1,1) ~= '\\' ) then fname = '\\' .. fname end 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.print_debug(1, "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.print_debug(1, "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.print_debug(1, "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.print_debug(1, "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.print_debug(1, "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.print_debug(1, "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.print_debug(1, "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 administive privileges are required here) stdnse.print_debug(1, "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.print_debug(1, "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.print_debug(1, "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.print_debug(1, "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.print_debug(1, "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.print_debug(1, "SMB: Share doesn't exist: %s", shares[i]) elseif(status == false) then stdnse.print_debug(1, "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.print_debug("SMB: Checking if %s is an administrator", username) status, smbstate = start(host) if(status == false) then stdnse.print_debug("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.print_debug("SMB; is_admin: Failed to negotiatie protocol: %s [%s]", err, username) stop(smbstate) return false end status, err = start_session(smbstate, overrides) if(status == false) then stdnse.print_debug("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.print_debug("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.print_debug("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.print_debug("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.print_debug("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.print_debug( 2, "%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.print_debug( 2, "%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.print_debug( 1, "%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.print_debug( 2, "%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.print_debug( 2, "%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.print_debug( 2, "%s: Disconnecting named pipe: %s", NP_LIBRARY_NAME, self.name ) return stop( self._smbstate ) else stdnse.print_debug( 2, "%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.print_debug( 2, "%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.print_debug( 2, "%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.print_debug( 2, "%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.print_debug( 2, "%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.print_debug( 2, "%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;