diff --git a/nselib/msrpc.lua b/nselib/msrpc.lua
index 8cd9e4de0..d467ac576 100644
--- a/nselib/msrpc.lua
+++ b/nselib/msrpc.lua
@@ -1,33 +1,38 @@
---- Call various MSRPC functions.
---
--- By making heavy use of the smb library, this library will call various MSRPC
+--- By making heavy use of the 'smb' library, this library will call various MSRPC
-- functions. The functions used here can be accessed over TCP ports 445 and 139,
-- with an established session. A NULL session (the default) will work for some
--- functions and operating systems (or configurations), but not for others. \n
---\n
+-- functions and operating systems (or configurations), but not for others.
+--
-- To make use of these function calls, a SMB session with the server has to be
--- established. This can be done manually with the 'smb' library, or the function
--- start_smb() can be called. \n
---\n
--- Next, the interface has to be bound. The bind() function will take care of that. \n
---\n
+-- established. This can be done manually with the smb library, or the function
+-- start_smb() can be called.
+--
+-- Next, the interface has to be bound. The bind() function will take care of that.
+--
-- After that, you're free to call any function that's part of that interface. In
-- other words, if you bind to the SAMR interface, you can only call the samr_
--- functions.\n
---\n
+-- functions, for lsa_ functions, bind to the LSA interface, etc.
+--
-- Although functions can be called in any order, many functions depend on the
-- value returned by other functions. I indicate those in the function comments,
--- so keep an eye out. \n
---\n
+-- so keep an eye out.
+--
-- Something to note is that these functions, for the most part, return a whole ton
--- of stuff in an array. I basically wrote them to return every possible value
+-- of stuff in a table. I basically wrote them to return every possible value
-- they get their hands on. I don't expect that most of them will be used, and I'm
-- not going to document them all in the function header; rather, I will document
-- the elements in the table that are more useful, you'll have to look at the actual
--- code (or the table) to see what else is available. \n
+-- code (or the table) to see what else is available.
+--
+-- When implementing this, I used Wireshark's output significantly, as well as Samba's
+-- "idl" files for reference:
+-- http://websvn.samba.org/cgi-bin/viewcvs.cgi/branches/SAMBA_4_0/source/librpc/idl/
+-- I'm not a lawyer, but I don't expect that this is a breach of Samba's copyright --
+-- if it is, please talk to me and I'll make arrangements to re-license this or to
+-- remove references to Samba.
--
--@author Ron Bowes
--- @copyright Same as Nmap--See http://nmap.org/book/man-legal.html
+--@copyright See nmap's COPYING for licence
-----------------------------------------------------------------------
module(... or "msrpc", package.seeall)
@@ -39,65 +44,34 @@ require 'stdnse'
-- The path, UUID, and version for SAMR
SAMR_PATH = "\\samr"
-SAMR_UUID = bin.pack("CCCCCCCCCCCCCCCC", 0x78, 0x57, 0x34, 0x12, 0x34, 0x12, 0xcd, 0xab, 0xef, 0x00, 0x01, 0x23, 0x45, 0x67, 0x89, 0xac)
+SAMR_UUID = string.char(0x78, 0x57, 0x34, 0x12, 0x34, 0x12, 0xcd, 0xab, 0xef, 0x00, 0x01, 0x23, 0x45, 0x67, 0x89, 0xac)
SAMR_VERSION = 0x01
-- The path, UUID, and version for SRVSVC
SRVSVC_PATH = "\\srvsvc"
-SRVSVC_UUID = bin.pack("CCCCCCCCCCCCCCCC", 0xc8, 0x4f, 0x32, 0x4b, 0x70, 0x16, 0xd3, 0x01, 0x12, 0x78, 0x5a, 0x47, 0xbf, 0x6e, 0xe1, 0x88)
+SRVSVC_UUID = string.char(0xc8, 0x4f, 0x32, 0x4b, 0x70, 0x16, 0xd3, 0x01, 0x12, 0x78, 0x5a, 0x47, 0xbf, 0x6e, 0xe1, 0x88)
SRVSVC_VERSION = 0x03
-- The path, UUID, and version for LSA
LSA_PATH = "\\lsarpc"
-LSA_UUID = bin.pack("CCCCCCCCCCCCCCCC", 0x78, 0x57, 0x34, 0x12, 0x34, 0x12, 0xcd, 0xab, 0xef, 0x00, 0x01, 0x23, 0x45, 0x67, 0x89, 0xab)
+LSA_UUID = string.char(0x78, 0x57, 0x34, 0x12, 0x34, 0x12, 0xcd, 0xab, 0xef, 0x00, 0x01, 0x23, 0x45, 0x67, 0x89, 0xab)
LSA_VERSION = 0
+-- The path, UUID, and version for WINREG
+WINREG_PATH = "\\winreg"
+WINREG_UUID = string.char(0x01, 0xd0, 0x8c, 0x33, 0x44, 0x22, 0xf1, 0x31, 0xaa, 0xaa, 0x90, 0x00, 0x38, 0x00, 0x10, 0x03)
+WINREG_VERSION = 1
+
-- This is the only transfer syntax I've seen in the wild, not that I've looked hard. It seems to work well.
-TRANSFER_SYNTAX = bin.pack("CCCCCCCCCCCCCCCC", 0x04, 0x5d, 0x88, 0x8a, 0xeb, 0x1c, 0xc9, 0x11, 0x9f, 0xe8, 0x08, 0x00, 0x2b, 0x10, 0x48, 0x60)
+TRANSFER_SYNTAX = string.char(0x04, 0x5d, 0x88, 0x8a, 0xeb, 0x1c, 0xc9, 0x11, 0x9f, 0xe8, 0x08, 0x00, 0x2b, 0x10, 0x48, 0x60)
-- The 'referent_id' value is ignored, as far as I can tell, so this value is passed for it. No, it isn't random. :)
REFERENT_ID = 0x50414d4e
--- A few error codes
-STATUS_SOME_NOT_MAPPED = 0x00000107
-STATUS_INVALID_PARAMETER = 0xC000000D
-STATUS_ACCESS_DENIED = 0xC0000022
-STATUS_BUFFER_TOO_SMALL = 0xC0000023
-STATUS_NONE_MAPPED = 0xC0000073
-STATUS_INSUFFICIENT_RESOURCES = 0xC000009A
-STATUS_MORE_ENTRIES = 0x00000105
-STATUS_TOO_MANY_CONTEXT_IDS = 0xC000015A
-
----Convert the return status of a function into a string. This isn't nearly an
--- exhaustive list, if I decide to go all out then I'll probably move this into
--- its own library.
---@param status the status to convert
---@return The string equivalent to the status.
-local function status_to_string(status)
- if(status == STATUS_ACCESS_DENIED) then
- return "STATUS_ACCESS_DENIED"
- elseif(status == STATUS_MORE_ENTRIES) then
- return "STATUS_MORE_ENTRIES"
- elseif(status == STATUS_TOO_MANY_CONTEXT_IDS) then
- return "STATUS_TOO_MANY_CONTEXT_IDS"
- elseif(status == STATUS_INSUFFICIENT_RESOURCES) then
- return "STATUS_INSUFFICIENT_RESOURCES"
- elseif(status == STATUS_BUFFER_TOO_SMALL) then
- return "STATUS_BUFFER_TOO_SMALL"
- elseif(status == STATUS_INVALID_PARAMETER) then
- return "STATUS_INVALID_PARAMETER"
- elseif(status == STATUS_SOME_NOT_MAPPED) then
- return "STATUS_SOME_NOT_MAPPED"
- elseif(status == STATUS_NONE_MAPPED) then
- return "STATUS_NONE_MAPPED"
- else
- return string.format("STATUS_UNKNOWN_ERROR (0x%08x)", status)
- end
-end
-
--- Convert a string to fake unicode (ascii with null characters between them), optionally add a null terminator,
-- and optionally align it to 4-byte boundaries. This is frequently used in MSRPC calls, so I put it here, but
-- it might be a good idea to move this function (and the converse one below) into a separate library.
+--
--@param string The string to convert.
--@param do_null [optional] Add a null-terminator to the unicode string. Default false.
--@param do_align [optional] Align the string to a multiple of 4 bytes. Default false.
@@ -133,19 +107,21 @@ local function string_to_unicode(string, do_null, do_align)
return result
end
---- Read a unicode string from a buffer, similar to how bin.unpack() would, optionally eat the null terminator,
+--- Read a unicode string from a buffer, similar to how bin.unpack() would, optionally eat the null terminator,
-- and optionally align it to 4-byte boundaries.
+--
--@param buffer The buffer to read from, typically the full 'arguments' value for MSRPC
---@param pos The position in the buffer to start (just like bin.unpack())
+--@param pos The position in the buffer to start (just like bin.unpack())
--@param length The number of ascii characters that will be read (including the null, if do_null is set).
--@param do_null [optional] Remove a null terminator from the string as the last character. Default false.
--@param do_align [optional] Ensure that the number of bytes removed is a multiple of 4.
---@return (pos, string) The new position and the string read, again imitating bin.unpack().
+--@return (pos, string) The new position and the string read, again imitating bin.unpack(). If there was an
+-- attempt to read off the end of the string, then 'nil' is returned for both parameters.
local function unicode_to_string(buffer, pos, length, do_null, do_align)
local i, ch, dummy
local string = ""
-stdnse.print_debug(5, "MSRPC: Entering unicode_to_string(pos = %d, length = %d)", pos, length)
+ stdnse.print_debug(3, "MSRPC: Entering unicode_to_string(pos = %d, length = %d)", pos, length)
if(do_null == nil) then
do_null = false
@@ -161,7 +137,13 @@ stdnse.print_debug(5, "MSRPC: Entering unicode_to_string(pos = %d, length = %d)"
for j = 1, length, 1 do
pos, ch, dummy = bin.unpack("bind() function should be called right after).
+--
+-- Note that the smbstate table is the same one used in the SMB files (obviously), so it will contain
+-- the various responses/information places in there by SMB functions.
--
--@param host The host object.
--@param path The path to the named pipe; for example, msrpc.SAMR_PATH or msrpc.SRVSVC_PATH.
---@return (status, socket, uid, tid, fid, negotiate_result, session_result, tree_result, create_result)
--- if status is false, socket is an error message. Otherwise, the rest of the results are
--- returned.
+--@return (status, smbstate) if status is false, smbstate is an error message. Otherwise, smbstate is
+-- required for all further calls.
function start_smb(host, path)
- local status, socket, negotiate_result, session_result, tree_result, create_result
+ local smbstate
+ local status, err
-- Begin the SMB session
- status, socket = smb.start(host)
+ status, smbstate = smb.start(host)
if(status == false) then
- return false, socket
+ return false, smbstate
end
-- Negotiate the protocol
- status, negotiate_result = smb.negotiate_protocol(socket)
+ status, err = smb.negotiate_protocol(smbstate)
if(status == false) then
- smb.stop(socket)
- return false, negotiate_result
+ smb.stop(smbstate)
+ return false, err
end
-- Start up a null session
- status, session_result = smb.start_session(socket, "", negotiate_result['session_key'], negotiate_result['capabilities'])
+ status, err = smb.start_session(smbstate)
if(status == false) then
- smb.stop(socket)
- return false, session_result
+ smb.stop(smbstate)
+ return false, err
end
-- Connect to IPC$ share
- status, tree_result = smb.tree_connect(socket, "IPC$", session_result['uid'])
+ status, err = smb.tree_connect(smbstate, "IPC$")
if(status == false) then
- smb.stop(socket, session_result['uid'])
- return false, tree_result
+ smb.stop(smbstate)
+ return false, err
end
-- Try to connect to requested pipe
- status, create_result = smb.create_file(socket, path, session_result['uid'], tree_result['tid'])
+ status, err = smb.create_file(smbstate, path)
if(status == false) then
- smb.stop(socket, session_result['uid'], tree_result['tid'])
- return false, create_result
+ smb.stop(smbstate)
+ return false, err
end
-- Return everything
- return true, socket, session_result['uid'], tree_result['tid'], create_result['fid'], negotiate_result, session_result, tree_result, create_result
+ return true, smbstate
end
---- A wrapper around the smb.stop() function. I only created it to add symmetry, so the code uses
--- the same class to start/stop the session. In the future, this may be expanded to close
--- handles before exiting.
+--- A wrapper around the smb.stop() function. I only created it to add symmetry, so client code
+-- doesn't have to call both msrpc and smb functions.
--
---@param socket The socket to close.
---@param uid The UserID, which will be logged off before closing the socket.
---@param tid The TreeID, which will be disconnected before closing the socket.
-function stop_smb(socket, uid, tid)
- smb.stop(socket, uid, tid)
+--@param smbstate The SMB state table.
+function stop_smb(state)
+ smb.stop(state)
end
--- Bind to a MSRPC interface. Two common interfaces are SAML and SRVSVC, and can be found as
-- constants at the top of this file. Once this function has successfully returned, any MSRPC
-- call can be made (provided it doesn't depend on results from other MSRPC calls).
--
---@param socket The socket in the appropriate state
---@param interface_uuid The interface to bind to. There are constants defined for these (SAMR_UUID,
+--@param smbstate The SMB state table
+--@param interface_uuid The interface to bind to. There are constants defined for these (SAMR_UUID,
-- etc.)
---@param interface_version The interface version to use. There are constants at the top (SAMR_VERSION,
+--@param interface_version The interface version to use. There are constants at the top (SAMR_VERSION,
-- etc.)
--@param transfer_syntax The transfer syntax to use. I don't really know what this is, but the value
--- was always the same on my tests. You can use the constant at the top (TRANSFER_SYNTAX), or
+-- was always the same on my tests. You can use the constant at the top (TRANSFER_SYNTAX), or
-- just set this parameter to 'nil'.
---@param uid The UserID we're sending the packets as
---@param tid The TreeID we're sending the packets to
---@param fid The FileID we're sending the packets to
--@return (status, result) If status is false, result is an error message. Otherwise, result is a
-- table of values, none of which are especially useful.
-function bind(socket, interface_uuid, interface_version, transfer_syntax, uid, tid, fid)
+function bind(smbstate, interface_uuid, interface_version, transfer_syntax)
local i
local status, result
local parameters, data
@@ -325,12 +350,12 @@ function bind(socket, interface_uuid, interface_version, transfer_syntax, uid, t
2 -- Syntax version
)
- status, result = smb.send_transaction(socket, 0x0026, "", data, uid, tid, fid)
+ status, result = smb.send_transaction(smbstate, 0x0026, "", data)
if(status ~= true) then
return false, result
end
- stdnse.print_debug(2, "MSRPC: Received Bind() response")
+ stdnse.print_debug(3, "MSRPC: Received Bind() response")
-- Make these easier to access.
parameters = result['parameters']
@@ -369,7 +394,7 @@ function bind(socket, interface_uuid, interface_version, transfer_syntax, uid, t
-- Read the secondary address
pos, response['secondary_address'] = bin.unpack(string.format("SMB_COM_TRANSACTION packet, and parses the response. Once the SMB stuff has been stripped off the response, it's
+-- passed down here, cleaned up some more, and returned to the caller.
+--
+-- There's a reason that SMB is sometimes considered to be between layer 4 and 7 on the OSI model. :)
+--
+--@param smbstate The SMB state table (after bind() has been called).
--@param opnum The operating number (ie, the function). Find this in the MSRPC documentation or with a packet logger.
--@param arguments The marshalled arguments to pass to the function. Currently, marshalling is all done manually.
---@param uid The UserID we're sending the packets as
---@param tid The TreeID we're sending the packets to
---@param fid The FileID we're sending the packets to
--@return (status, result) If status is false, result is an error message. Otherwise, result is a table of values, the most
-- useful one being 'arguments', which are the values returned by the server.
-local function call_function(socket, opnum, arguments, uid, tid, fid)
+local function call_function(smbstate, opnum, arguments)
local i
local status, result
local parameters, data
@@ -429,7 +451,7 @@ local function call_function(socket, opnum, arguments, uid, tid, fid)
stdnse.print_debug(3, "MSRPC: Calling function 0x%02x with %d bytes of arguments", string.len(arguments), opnum)
-- Pass the information up to the smb layer
- status, result = smb.send_transaction(socket, 0x0026, "", data, uid, tid, fid)
+ status, result = smb.send_transaction(smbstate, 0x0026, "", data)
if(status ~= true) then
return false, result
end
@@ -472,17 +494,14 @@ local function call_function(socket, opnum, arguments, uid, tid, fid)
end
----Call the MSRPC function netshareenumall() on the remote system. This function basically returns a list of all the shares
+---Call the MSRPC function netshareenumall() on the remote system. This function basically returns a list of all the shares
-- on the system.
--
---@param socket The socket, with a proper MSRPC connection
---@param server The IP or Hostname of the server (seems to be ignored but it's a good idea to have it)
---@param uid The UserID we're sending the packets as
---@param tid The TreeID we're sending the packets to
---@param fid The FileID we're sending the packets to
+--@param smbstate The SMB state table
+--@param server The IP or Hostname of the server (seems to be ignored but it's a good idea to have it)
--@return (status, result) If status is false, result is an error message. Otherwise, result is a table of values, the most
-- useful one being 'shares', which is a list of the system's shares.
-function srvsvc_netshareenumall(socket, server, uid, tid, fid)
+function srvsvc_netshareenumall(smbstate, server)
local i, j
local status, result
local arguments
@@ -493,7 +512,7 @@ function srvsvc_netshareenumall(socket, server, uid, tid, fid)
local response = {}
- stdnse.print_debug(2, "MSRPC: Calling NetShareEnumAll()")
+ stdnse.print_debug(2, "MSRPC: Calling NetShareEnumAll() [%s]", smbstate['ip'])
server = "\\\\" .. server
-- [in] [string,charset(UTF16)] uint16 *server_unc
@@ -525,12 +544,12 @@ function srvsvc_netshareenumall(socket, server, uid, tid, fid)
-- Do the call
- status, result = call_function(socket, 0x0F, arguments, uid, tid, fid)
+ status, result = call_function(smbstate, 0x0F, arguments)
if(status ~= true) then
return false, result
end
- stdnse.print_debug(2, "MSRPC: NetShareEnumAll() returned successfully")
+ stdnse.print_debug(3, "MSRPC: NetShareEnumAll() returned successfully")
-- Make arguments easier to use
arguments = result['arguments']
@@ -542,7 +561,7 @@ function srvsvc_netshareenumall(socket, server, uid, tid, fid)
-- [in,out,switch_is(level)] srvsvc_NetShareCtr ctr
local ctr, referent_id, count, max_count
pos, ctr, referent_id, count, referent_id, max_count = bin.unpack("netsharegetinfo() on the remote system. This function retrieves extra information about a share
+-- on the system.
--
---@param socket The socket, with a proper MSRPC connection
---@param server The IP or Hostname of the server (seems to be ignored but it's a good idea to have it)
---@param uid The UserID we're sending the packets as
---@param tid The TreeID we're sending the packets to
---@param fid The FileID we're sending the packets to
+--@param smbstate The SMB state table
+--@param server The IP or Hostname of the server (seems to be ignored but it's a good idea to have it)
--@return (status, result) If status is false, result is an error message. Otherwise, result is a table of values, the most
--- useful one being 'connect_handle', which is required to call other functions.
-function samr_connect4(socket, server, uid, tid, fid)
+-- useful one being 'shares', which is a list of the system's shares.
+function srvsvc_netsharegetinfo(smbstate, server, share, level)
local i, j
local status, result
local arguments
@@ -598,7 +618,417 @@ function samr_connect4(socket, server, uid, tid, fid)
local response = {}
- stdnse.print_debug(2, "MSRPC: Calling Connect4()")
+ server = "\\\\" .. server
+
+-- [in] [string,charset(UTF16)] uint16 *server_unc,
+ arguments = bin.pack(""
+ end
+
+
+ pos, ptr_comment = bin.unpack("NetSessEnum() function, which gets a list of active sessions on the host. For this function,
+-- a session is defined as a connection to a file share.
+--
+--@param smbstate The SMB state table
+--@param server The IP or Hostname of the server (seems to be ignored but it's a good idea to have it)
+--@return (status, result) If status is false, result is an error message. Otherwise, result is an array of tables.
+-- Each table contains the elements 'user', 'client', 'active', and 'idle'.
+function srvsvc_netsessenum(smbstate, server)
+ local i, j
+ local status, result
+ local arguments
+ local pos, align
+
+ local response = {}
+
+ stdnse.print_debug(2, "MSRPC: Calling NetSessEnum() [%s]", smbstate['ip'])
+
+-- [in] [string,charset(UTF16)] uint16 *server_unc,
+ arguments = bin.pack("NetServerGetStatistics() function, which grabs a bunch of statistics on the server.
+-- This function requires administrator access to call.
+--
+-- Note: Wireshark 1.0.3 doesn't parse this packet properly.
+--
+--@param smbstate The SMB state table
+--@param server The IP or name of the server (I don't think this is actually used, but it's
+-- good practice to send it).
+--
+--@return A table containing the following values:
+-- * 'start' The time when statistics collection started (or when the statistics were last cleared). The value is
+-- stored as the number of seconds that have elapsed since 00:00:00, January 1, 1970, GMT. To calculate
+-- the length of time that statistics have been collected, subtract the value of this member from the
+-- present time. 'start_date' is the date as a string.
+-- * 'fopens' The number of times a file is opened on a server. This includes the number of times named pipes are opened.
+-- * 'devopens' The number of times a server device is opened.
+-- * 'jobsqueued' The number of server print jobs spooled.
+-- * 'sopens' The number of times the server session started.
+-- * 'stimedout' The number of times the server session automatically disconnected.
+-- * 'serrorout' The number of times the server sessions failed with an error.
+-- * 'pwerrors' The number of server password violations.
+-- * 'permerrors' The number of server access permission errors.
+-- * 'syserrors' The number of server system errors.
+-- * 'bytessent' The number of server bytes sent to the network.
+-- * 'bytesrcvd' The number of server bytes received from the network.
+-- * 'avresponse' The average server response time (in milliseconds).
+-- * 'reqbufneed' The number of times the server required a request buffer but failed to allocate one. This value indicates that the server parameters may need adjustment.
+-- * 'bigbufneed' The number of times the server required a big buffer but failed to allocate one. This value indicates that the server parameters may need adjustment.
+function srvsvc_netservergetstatistics(smbstate, server)
+ local i, j
+ local status, result
+ local arguments
+ local pos, align
+
+ local response = {}
+
+ local service = "SERVICE_SERVER"
+
+ stdnse.print_debug(2, "MSRPC: Calling NetServerGetStatistics() [%s]", smbstate['ip'])
+
+-- [in] [string,charset(UTF16)] uint16 *server_unc,
+ arguments = bin.pack("connect4() function, to obtain a "connect handle". This must be done before calling many
+-- of the SAMR functions.
+--
+--@param smbstate The SMB state table
+--@param server The IP or Hostname of the server (seems to be ignored but it's a good idea to have it)
+--@return (status, result) If status is false, result is an error message. Otherwise, result is a table of values, the most
+-- useful one being 'connect_handle', which is required to call other functions.
+function samr_connect4(smbstate, server)
+ local i, j
+ local status, result
+ local arguments
+ local pos, align
+
+ local response = {}
+
+ stdnse.print_debug(2, "MSRPC: Calling Connect4() [%s]", smbstate['ip'])
server = "\\\\" .. server
@@ -620,41 +1050,38 @@ function samr_connect4(socket, server, uid, tid, fid)
-- Do the call
- status, result = call_function(socket, 0x3E, arguments, uid, tid, fid)
+ status, result = call_function(smbstate, 0x3E, arguments)
if(status ~= true) then
return false, result
end
- stdnse.print_debug(2, "MSRPC: Connect4() returned successfully")
+ stdnse.print_debug(3, "MSRPC: Connect4() returned successfully")
-- Make arguments easier to use
arguments = result['arguments']
-
+ pos = 1
-- [in,string,charset(UTF16)] uint16 *system_name,
-- [in] uint32 unknown,
-- [in] samr_ConnectAccessMask access_mask,
-- [out,ref] policy_handle *connect_handle
- pos, response['connect_handle'], response['return'] = bin.unpack("enumdomains() function, which returns a list of all domains in use by the system.
--
---@param socket The socket, with a proper MSRPC connection
+--@param smbstate The SMB state table
--@param connect_handle The connect_handle, returned by samr_connect4()
---@param uid The UserID we're sending the packets as
---@param tid The TreeID we're sending the packets to
---@param fid The FileID we're sending the packets to
--@return (status, result) If status is false, result is an error message. Otherwise, result is a table of values, the most
-- useful one being 'domains', which is a list of the domains.
-function samr_enumdomains(socket, connect_handle, uid, tid, fid)
+function samr_enumdomains(smbstate, connect_handle)
local i, j
local status, result
local arguments
@@ -663,7 +1090,7 @@ function samr_enumdomains(socket, connect_handle, uid, tid, fid)
local response = {}
- stdnse.print_debug(2, "MSRPC: Calling EnumDomains()")
+ stdnse.print_debug(2, "MSRPC: Calling EnumDomains() [%s]", smbstate['ip'])
-- [in,ref] policy_handle *connect_handle,
arguments = bin.pack("LookupDomain() function, which converts a domain's name into its sid, which is
-- required to do operations on the domain.
--
---@param socket The socket, with a proper MSRPC connection
---@param connect_handle The connect_handle, returned by samr_connect4()
---@param domain The name of the domain (all domain names can be obtained with samr_enumdomains())
---@param uid The UserID we're sending the packets as
---@param tid The TreeID we're sending the packets to
---@param fid The FileID we're sending the packets to
+--@param smbstate The SMB state table
+--@param connect_handle The connect_handle, returned by samr_connect4()
+--@param domain The name of the domain (all domain names can be obtained with samr_enumdomains())
--@return (status, result) If status is false, result is an error message. Otherwise, result is a table of values, the most
-- useful one being 'sid', which is required to call other functions.
-function samr_lookupdomain(socket, connect_handle, domain, uid, tid, fid)
+function samr_lookupdomain(smbstate, connect_handle, domain)
local i, j
local status, result
local arguments
@@ -743,7 +1171,7 @@ function samr_lookupdomain(socket, connect_handle, domain, uid, tid, fid)
local response = {}
- stdnse.print_debug(2, "MSRPC: Calling LookupDomain()")
+ stdnse.print_debug(2, "MSRPC: Calling LookupDomain(%s) [%s]", domain, smbstate['ip'])
-- [in,ref] policy_handle *connect_handle,
arguments = bin.pack("OpenDomain(), which returns a handle to the domain identified by the given sid.
-- This is required before calling certain functions.
--
---@param socket The socket, with a proper MSRPC connection
---@param connect_handle The connect_handle, returned by samr_connect4()
---@param sid The sid for the domain, returned by samr_lookupdomain()
---@param uid The UserID we're sending the packets as
---@param tid The TreeID we're sending the packets to
---@param fid The FileID we're sending the packets to
+--@param smbstate The SMB state table
+--@param connect_handle The connect_handle, returned by samr_connect4()
+--@param sid The sid for the domain, returned by samr_lookupdomain()
--@return (status, result) If status is false, result is an error message. Otherwise, result is a table of values, the most
-- useful one being 'domain_handle', which is used to call other functions.
-function samr_opendomain(socket, connect_handle, sid, uid, tid, fid)
+function samr_opendomain(smbstate, connect_handle, sid)
local i, j
local status, result
local arguments
@@ -814,7 +1239,7 @@ function samr_opendomain(socket, connect_handle, sid, uid, tid, fid)
local response = {}
- stdnse.print_debug(2, "MSRPC: Calling OpenDomain()")
+ stdnse.print_debug(2, "MSRPC: Calling OpenDomain(%s) [%s]", sid_to_string(sid), smbstate['ip'])
-- [in,ref] policy_handle *connect_handle,
arguments = bin.pack("EnumDomainUsers(), which returns a list of users only. To get more information about the users, the
-- QueryDisplayInfo() function can be used.
-function samr_enumdomainusers(socket, domain_handle, uid, tid, fid)
+--
+--@param smbstate The SMB state table
+--@param domain_handle The domain_handle, returned by samr_opendomain()
+--@return (status, result) If status is false, result is an error message. Otherwise, result is a table of values, the most
+-- useful one being 'names', which is a list of usernames in that domain.
+function samr_enumdomainusers(smbstate, domain_handle)
local i, j
local status, result
local arguments
@@ -869,7 +1299,7 @@ function samr_enumdomainusers(socket, domain_handle, uid, tid, fid)
local response = {}
- stdnse.print_debug(2, "MSRPC: Calling EnumDomainUsers()")
+ stdnse.print_debug(2, "MSRPC: Calling EnumDomainUsers() [%s]", smbstate['ip'])
-- [in,ref] policy_handle *domain_handle,
arguments = bin.pack("QueryDisplayInfo(), which returns a list of users with accounts on the system, as well as extra information about
+-- them (their full name and description).
+--
-- I found in testing that trying to get all the users at once is a mistake, it returns ERR_BUFFER_OVERFLOW, so instead I'm
-- only reading one user at a time, in a loop. So one call to this will actually send out a number of packets equal to the
--- number of users on the system. \n
+-- number of users on the system.
--
---@param socket The socket, with a proper MSRPC connection
---@param domain_handle The domain handle, returned by samr_opendomain()
---@param uid The UserID we're sending the packets as
---@param tid The TreeID we're sending the packets to
---@param fid The FileID we're sending the packets to
+--@param smbstate The SMB state table
+--@param domain_handle The domain handle, returned by samr_opendomain()
--@return (status, result) If status is false, result is an error message. Otherwise, result is a table of values, the most
-- useful ones being 'names', a list of all the usernames, and 'details', a further list of tables with the elements
-- 'name', 'fullname', and 'description' (note that any of them can be nil if the server didn't return a value). Finally,
-- 'flags' is the numeric flags for the user, while 'flags_list' is an array of strings, representing the flags.
-function samr_querydisplayinfo(socket, domain_handle, uid, tid, fid)
+function samr_querydisplayinfo(smbstate, domain_handle)
local i, j
local status, result
local arguments
@@ -965,14 +1395,14 @@ function samr_querydisplayinfo(socket, domain_handle, uid, tid, fid)
-- I put a little loop here and grab the names individually.
i = 0
repeat
- stdnse.print_debug(2, "MSRPC: Calling QueryDisplayInfo() [index = %d]", i)
+ stdnse.print_debug(2, "MSRPC: Calling QueryDisplayInfo(%d) [%s]", i, smbstate['ip'])
-- [in,ref] policy_handle *domain_handle,
arguments = bin.pack("QueryDomainInfo2(), which grabs various data about a domain.
--
---@param socket The socket, with a proper MSRPC connection
---@param domain_handle The domain_handle, returned by samr_opendomain()
+--@param smbstate The SMB state table
+--@param domain_handle The domain_handle, returned by samr_opendomain()
--@param level The level, which determines which type of information to query for. See the @return section
-- for details.
---@param uid The UserID we're sending the packets as
---@param tid The TreeID we're sending the packets to
---@param fid The FileID we're sending the packets to
--@param response [optional] A 'result' to add the entries to. This lets us call this function multiple times,
-- for multiple levels, and keep the results in one place.
--@return (status, result) If status is false, result is an error message. Otherwise, result is a table of values,
--- and the values that are returned are dependent on the 'level' settings:\n
--- Level 1:\n
--- 'min_password_length' (in characters)\n
--- 'password_history_length' (in passwords)\n
--- 'password_properties'\n
--- 'password_properties_list' (array of strings)\n
--- 'max_password_age' (in days)\n
--- 'min_password_age' (in days)\n
--- Level 8\n
--- 'create_time' (1/10ms since 1601)\n
--- 'create_date' (string)\n
--- Level 12\n
--- 'lockout_duration' (in minutes)\n
--- 'lockout_window' (in minutes)\n
--- 'lockout_threshold' (in attempts)\n
-function samr_querydomaininfo2(socket, domain_handle, level, uid, tid, fid, response)
+-- and the values that are returned are dependent on the 'level' settings:
+-- Level 1:
+-- 'min_password_length' (in characters)
+-- 'password_history_length' (in passwords)
+-- 'password_properties'
+-- 'password_properties_list' (array of strings)
+-- 'max_password_age' (in days)
+-- 'min_password_age' (in days)
+-- Level 8
+-- 'create_time' (1/10ms since 1601)
+-- 'create_date' (string)
+-- Level 12
+-- 'lockout_duration' (in minutes)
+-- 'lockout_window' (in minutes)
+-- 'lockout_threshold' (in attempts)
+function samr_querydomaininfo2(smbstate, domain_handle, level, response)
local i, j
local status, result
local arguments
@@ -1120,7 +1559,7 @@ function samr_querydomaininfo2(socket, domain_handle, level, uid, tid, fid, resp
response = {}
end
- stdnse.print_debug(2, "MSRPC: Calling QueryDomainInfo2()")
+ stdnse.print_debug(2, "MSRPC: Calling QueryDomainInfo2(%d) [%s]", level, smbstate['ip'])
-- [in,ref] policy_handle *domain_handle,
arguments = bin.pack("close() function, which closes a handle of any type (for example, domain_handle or connect_handle)
+--@param smbstate The SMB state table
+--@param handle The handle to close
--@return (status, result) If status is false, result is an error message. Otherwise, result is potentially
-- a table of values, none of which are likely to be used.
-function samr_close(socket, handle, uid, tid, fid)
+function samr_close(smbstate, handle)
local i, j
local status, result
local arguments
@@ -1221,18 +1657,18 @@ function samr_close(socket, handle, uid, tid, fid)
local response = {}
- stdnse.print_debug(2, "MSRPC: Calling Close()")
+ stdnse.print_debug(2, "MSRPC: Calling Close() [%s]", smbstate['ip'])
-- [in,out,ref] policy_handle *handle
arguments = bin.pack("LsarOpenPolicy2() function, to obtain a "policy handle". This must be done before calling many
-- of the LSA functions.
--
---@param socket The socket, with a proper MSRPC connection
---@param server The IP or Hostname of the server (seems to be ignored but it's a good idea to have it)
---@param uid The UserID we're sending the packets as
---@param tid The TreeID we're sending the packets to
---@param fid The FileID we're sending the packets to
+--@param smbstate The SMB state table
+--@param server The IP or Hostname of the server (seems to be ignored but it's a good idea to have it)
--@return (status, result) If status is false, result is an error message. Otherwise, result is a table of values, the most
-- useful one being 'policy_handle', which is required to call other functions.
-function lsa_openpolicy2(socket, server, uid, tid, fid)
+function lsa_openpolicy2(smbstate, server)
local i, j
local status, result
local arguments
@@ -1269,7 +1702,7 @@ function lsa_openpolicy2(socket, server, uid, tid, fid)
local response = {}
- stdnse.print_debug(2, "MSRPC: Calling LsarOpenPolicy2()")
+ stdnse.print_debug(2, "MSRPC: Calling LsarOpenPolicy2() [%s]", smbstate['ip'])
-- [in,unique] [string,charset(UTF16)] uint16 *system_name,
@@ -1301,12 +1734,12 @@ function lsa_openpolicy2(socket, server, uid, tid, fid)
-- [out] policy_handle *handle
-- Do the call
- status, result = call_function(socket, 0x2C, arguments, uid, tid, fid)
+ status, result = call_function(smbstate, 0x2C, arguments)
if(status ~= true) then
return false, result
end
- stdnse.print_debug(2, "MSRPC: LsarOpenPolicy2() returned successfully")
+ stdnse.print_debug(3, "MSRPC: LsarOpenPolicy2() returned successfully")
-- Make arguments easier to use
arguments = result['arguments']
@@ -1317,28 +1750,25 @@ function lsa_openpolicy2(socket, server, uid, tid, fid)
-- [out] policy_handle *handle
pos, response['policy_handle'], response['return'] = bin.unpack("LsarLookupNames2() function, to convert the server's name into a sid.
--
---@param socket The socket, with a proper MSRPC connection
---@param policy_handle The policy handle returned by lsa_openpolicy2()
---@param names An array of names to look up. To get a SID, only one of the names needs to be valid.
---@param uid The UserID we're sending the packets as
---@param tid The TreeID we're sending the packets to
---@param fid The FileID we're sending the packets to
+--@param smbstate The SMB state table
+--@param policy_handle The policy handle returned by lsa_openpolicy2()
+--@param names An array of names to look up. To get a SID, only one of the names needs to be valid.
--@return (status, result) If status is false, result is an error message. Otherwise, result is a table of values.
-- The most useful result is 'domains', which is a list of domains known to the server. And, for each of the
-- domains, there is a 'name' entry, which is a string, and a 'sid' entry, which is yet another object which
-- can be passed to functions that understand SIDs.
-function lsa_lookupnames2(socket, policy_handle, names, uid, tid, fid)
+function lsa_lookupnames2(smbstate, policy_handle, names)
local i, j
local status, result
local arguments
@@ -1347,7 +1777,7 @@ function lsa_lookupnames2(socket, policy_handle, names, uid, tid, fid)
local response = {}
- stdnse.print_debug(2, "MSRPC: Calling LsarLookupNames2()")
+ stdnse.print_debug(2, "MSRPC: Calling LsarLookupNames2(%s) [%s]", stdnse.strjoin(", ", names), smbstate['ip'])
-- [in] policy_handle *handle,
@@ -1401,12 +1831,12 @@ function lsa_lookupnames2(socket, policy_handle, names, uid, tid, fid)
-- Do the call
- status, result = call_function(socket, 0x3a, arguments, uid, tid, fid)
+ status, result = call_function(smbstate, 0x3a, arguments)
if(status ~= true) then
return false, result
end
- stdnse.print_debug(2, "MSRPC: LsarLookupNames2() returned successfully")
+ stdnse.print_debug(3, "MSRPC: LsarLookupNames2() returned successfully")
-- Make arguments easier to use
arguments = result['arguments']
@@ -1417,51 +1847,67 @@ function lsa_lookupnames2(socket, policy_handle, names, uid, tid, fid)
-- [in,size_is(num_names)] lsa_String names[],
-- [out,unique] lsa_RefDomainList *domains,
local referent_id, count, max_count
- pos, referent_id, count, referent_id, max_count = bin.unpack("SI<", arguments, pos)
- sid['subauthorities'] = {}
- for i = 1, sid['count'], 1 do
- pos, sid['subauthorities'][i] = bin.unpack("SI<", arguments, pos)
+ sid['subauthorities'] = {}
+ for i = 1, sid['count'], 1 do
+ pos, sid['subauthorities'][i] = bin.unpack("LsarLookupSids2() function, to convert a list of SIDs to their names
--
---@param socket The socket, with a proper MSRPC connection
---@param policy_handle The policy handle returned by lsa_openpolicy2()
---@param sid The SID object for the server
---@param rids The RIDs of users to look up
---@param uid The UserID we're sending the packets as
---@param tid The TreeID we're sending the packets to
---@param fid The FileID we're sending the packets to
+--@param smbstate The SMB state table
+--@param policy_handle The policy handle returned by lsa_openpolicy2()
+--@param sid The SID object for the server
+--@param rids The RIDs of users to look up
--@return (status, result) If status is false, result is an error message. Otherwise, result is a table of values.
-- The element 'domains' is identical to the lookupnames2() element called 'domains'. The element 'names' is a
-- list of strings, for the usernames (not necessary a 1:1 mapping with the RIDs), and the element 'details' is
-- a table containing more information about each name, even if the name wasn't found (this one is a 1:1 mapping
-- with the RIDs).
-function lsa_lookupsids2(socket, policy_handle, sid, rids, uid, tid, fid)
+function lsa_lookupsids2(smbstate, policy_handle, sid, rids)
local i, j
local status, result
local arguments
@@ -1510,7 +1953,7 @@ function lsa_lookupsids2(socket, policy_handle, sid, rids, uid, tid, fid)
local response = {}
- stdnse.print_debug(2, "MSRPC: Calling LsarLookupSids2()")
+ stdnse.print_debug(2, "MSRPC: Calling LsarLookupSids2(%s, %s) [%s]", sid_to_string(sid), stdnse.strjoin(", ", rids), smbstate['ip'])
-- [in] policy_handle *handle,
arguments = bin.pack("SI<", arguments, pos)
- sid['subauthorities'] = {}
- for i = 1, sid['count'], 1 do
- pos, sid['subauthorities'][i] = bin.unpack("SI<", arguments, pos)
+ sid['subauthorities'] = {}
+ for i = 1, sid['count'], 1 do
+ pos, sid['subauthorities'][i] = bin.unpack("", "Alias", "Well known group", "Deleted account", "", "Not found" }
- pos, count, referent_id, max_count = bin.unpack("close() function, which closes a session created with a lsa_openpolicy()-style function
+--@param smbstate The SMB state table
+--@param handle The handle to close
--@return (status, result) If status is false, result is an error message. Otherwise, result is potentially
-- a table of values, none of which are likely to be used.
-function lsa_close(socket, handle, uid, tid, fid)
+function lsa_close(smbstate, handle)
local i, j
local status, result
local arguments
@@ -1666,18 +2136,18 @@ function lsa_close(socket, handle, uid, tid, fid)
local response = {}
- stdnse.print_debug(2, "MSRPC: Calling LsaClose()")
+ stdnse.print_debug(2, "MSRPC: Calling LsaClose() [%s]", smbstate['ip'])
-- [in,out] policy_handle *handle
arguments = bin.pack("OpenHKU() function, to obtain a handle to the HKEY_USERS hive
+--
+--@param smbstate The SMB state table
+--@return (status, result) If status is false, result is an error message. Otherwise, result is a table of values, the most
+-- useful one being 'handle', which is required to call other winreg functions.
+function winreg_openhku(smbstate)
+ local i, j
+ local status, result
+ local arguments
+ local pos, align
+
+ local response = {}
+
+ stdnse.print_debug(2, "MSRPC: Calling OpenHKU() [%s]", smbstate['ip'])
+
+-- [in] uint16 *system_name,
+ arguments = bin.pack("OpenHKLM() function, to obtain a handle to the HKEY_LOCAL_MACHINE hive
+--
+--@param smbstate The SMB state table
+--@return (status, result) If status is false, result is an error message. Otherwise, result is a table of values, the most
+-- useful one being 'handle', which is required to call other winreg functions.
+function winreg_openhklm(smbstate)
+ local i, j
+ local status, result
+ local arguments
+ local pos, align
+
+ local response = {}
+
+ stdnse.print_debug(2, "MSRPC: Calling OpenHKLM() [%s]", smbstate['ip'])
+
+-- [in] uint16 *system_name,
+ arguments = bin.pack("EnumKey(), which returns a single key
+-- under the given handle, at the index of 'index'.
+--
+--@param smbstate The SMB state table
+--@param handle A handle to hive or key. winreg_openhku() provides a useable key, for example.
+--@param index The index of the key to return. Generally you'll start at 0 and increment until
+-- an error is returned.
+--@return (status, result) If status is false, result is an error message. Otherwise, result is a table of values, the most
+-- useful one being 'name', which is the name of the current key
+function winreg_enumkey(smbstate, handle, index)
+ local i, j
+ local status, result
+ local arguments
+ local pos, align
+
+ local response = {}
+
+ stdnse.print_debug(2, "MSRPC: Calling EnumKey(%d) [%s]", index, smbstate['ip'])
+
+-- [in,ref] policy_handle *handle,
+ arguments = bin.pack(" 0) then
+ pos, response['name'] = unicode_to_string(arguments, pos, response['length'], true, true)
+ if(pos == nil) then
+ return false, "Read off the end of the packet (winreg.enumkey)"
+ end
+ else
+ response['name'] = "" -- Not sure why the name could have a 0 length, but who knows?
+ end
+ end
+
+-- [in,out,unique] winreg_StringBuf *keyclass,
+ pos, referent_id = bin.unpack("OpenKey(), which obtains a handle to a named key.
+--
+--@param smbstate The SMB state table
+--@param handle A handle to hive or key. winreg_openhku() provides a useable key, for example.
+--@param keyname The name of the key to open.
+--@return (status, result) If status is false, result is an error message. Otherwise, result is a table of values, the most
+-- useful one being 'handle', which is a handle to the newly opened key.
+function winreg_openkey(smbstate, handle, keyname)
+ local i, j
+ local status, result
+ local arguments
+ local pos, align
+
+ local response = {}
+
+ stdnse.print_debug(2, "MSRPC: Calling OpenKey(%s) [%s]", keyname, smbstate['ip'])
+
+-- [in,ref] policy_handle *parent_handle,
+ arguments = bin.pack("QueryInfoKey(), which obtains information about an opened key.
+--
+--@param smbstate The SMB state table
+--@param handle A handle to the key that's being queried.
+--@return (status, result) If status is false, result is an error message. Otherwise, result is a table of values, the most
+-- useful one, at least for me, being 'last_changed_time'/'last_changed_date', which are the date and time that the
+-- key was changed.
+function winreg_queryinfokey(smbstate, handle)
+ local i, j
+ local status, result
+ local arguments
+ local pos, align
+
+ local response = {}
+
+ stdnse.print_debug(2, "MSRPC: Calling QueryInfoKey() [%s]", smbstate['ip'])
+
+-- [in,ref] policy_handle *handle,
+ arguments = bin.pack("<20A", handle)
+
+-- [in,out,ref] winreg_String *classname,
+ arguments = arguments .. bin.pack("QueryValue(), which returns the value of the requested key.
+--
+--@param smbstate The SMB state table
+--@param handle A handle to the key that's being queried.
+--@param value The value we're looking for.
+--@return (status, result) If status is false, result is an error message. Otherwise, result is a table of values, the most
+-- useful one, at least for me, being 'last_changed_time'/'last_changed_date', which are the date and time that the
+-- key was changed.
+function winreg_queryvalue(smbstate, handle, value)
+ local i, j
+ local status, result
+ local arguments
+ local pos, align
+
+ local response = {}
+
+ stdnse.print_debug(2, "MSRPC: Calling QueryValue(%s) [%s]", value, smbstate['ip'])
+
+
+-- [in,ref] policy_handle *handle,
+ arguments = bin.pack("<20A", handle)
+-- [in] winreg_String value_name,
+ arguments = arguments .. bin.pack("CloseKey(), which closes an opened handle. Strictly speaking, this doesn't have to be called (Windows
+-- will close the key for you), but it's good manners to clean up after yourself.
+--
+--@param smbstate The SMB state table
+--@param handle the handle to be closed.
+--@return (status, result) If status is false, result is an error message. Otherwise, result is a table of values, none of
+-- which are especially useful.
+function winreg_closekey(smbstate, handle)
+ local i, j
+ local status, result
+ local arguments
+ local pos, align
+
+ local response = {}
+
+ stdnse.print_debug(2, "MSRPC: Calling CloseKey() [%s]", smbstate['ip'])
+
+-- [in,out,ref] policy_handle *handle
+ arguments = bin.pack("
--- @copyright Same as Nmap--See http://nmap.org/book/man-legal.html
+--@copyright See nmaps COPYING for licence
-----------------------------------------------------------------------
module(... or "netbios", package.seeall)
@@ -16,12 +16,12 @@ require 'stdnse'
-- character, and converted it to all uppercase characters (so it can, for example,
-- pass case-sensitive data in a case-insensitive way)
--
--- There are two levels of encoding performed:\n
--- L1: Pad the string to 16 characters withs spaces (or NULLs if it's the
+-- There are two levels of encoding performed:
+-- * L1: Pad the string to 16 characters withs spaces (or NULLs if it's the
-- wildcard "*") and replace each byte with two bytes representing each
--- of its nibbles, plus 0x41. \n
--- L2: Prepend the length to the string, and to each substring in the scope
--- (separated by periods). \n
+-- of its nibbles, plus 0x41.
+-- * L2: Prepend the length to the string, and to each substring in the scope
+-- (separated by periods).
--@param name The name that will be encoded (eg. "TEST1").
--@param scope [optional] The scope to encode it with. I've never seen scopes used
-- in the real world (eg, "insecure.org").
@@ -76,7 +76,7 @@ end
-- the string representation. If the encoding is invalid, it will still attempt
-- to decode the string as best as possible.
--@param encoded_name The L2-encoded name
---@return the decoded name and the scope. The name will still be padded, and the
+--@returns the decoded name and the scope. The name will still be padded, and the
-- scope will never be nil (empty string is returned if no scope is present)
function name_decode(encoded_name)
local name = ""
@@ -208,38 +208,41 @@ end
-- script has already performed a nbstat query, the result can be re-used.
--
-- The NetBIOS request's header looks like this:
--- --------------------------------------------------\n
--- | 15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0 |\n
--- | NAME_TRN_ID |\n
--- | R | OPCODE | NM_FLAGS | RCODE | (FLAGS)\n
--- | QDCOUNT |\n
--- | ANCOUNT |\n
--- | NSCOUNT |\n
--- | ARCOUNT |\n
--- --------------------------------------------------\n
+--
+-- --------------------------------------------------
+-- | 15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0 |
+-- | NAME_TRN_ID |
+-- | R | OPCODE | NM_FLAGS | RCODE | (FLAGS)
+-- | QDCOUNT |
+-- | ANCOUNT |
+-- | NSCOUNT |
+-- | ARCOUNT |
+-- --------------------------------------------------
+--
--
-- In this case, the TRN_ID is a constant (0x1337, what else?), the flags
-- are 0, and we have one question. All fields are network byte order.
--
-- The body of the packet is a list of names to check for in the following
-- format:
--- (ntstring) encoded name
--- (2 bytes) query type (0x0021 = NBSTAT)
--- (2 bytes) query class (0x0001 = IN)
+-- * (ntstring) encoded name
+-- * (2 bytes) query type (0x0021 = NBSTAT)
+-- * (2 bytes) query class (0x0001 = IN)
--
-- The response header is the exact same, except it'll have some flags set
-- (0x8000 for sure, since it's a response), and ANCOUNT will be 1. The format
--- of the answer is:\n
--- (ntstring) requested name\n
--- (2 bytes) query type\n
--- (2 bytes) query class\n
--- (2 bytes) time to live\n
--- (2 bytes) record length\n
--- (1 byte) number of names\n
--- [for each name]\n
--- (16 bytes) padded name, with a 1-byte suffix\n
--- (2 bytes) flags\n
--- (variable) statistics (usually mac addres)
+-- of the answer is:
+--
+-- * (ntstring) requested name
+-- * (2 bytes) query type
+-- * (2 bytes) query class
+-- * (2 bytes) time to live
+-- * (2 bytes) record length
+-- * (1 byte) number of names
+-- * [for each name]
+-- * (16 bytes) padded name, with a 1-byte suffix
+-- * (2 bytes) flags
+-- * (variable) statistics (usually mac address)
--
--@param host The IP or hostname of the system.
--@return (status, names, statistics) If status is true, then the servers names are
@@ -255,7 +258,7 @@ function do_nbstat(host)
stdnse.print_debug(1, "Performing nbstat on host '%s'", host)
-- Check if it's cased in the registry for this host
if(nmap.registry["nbstat_names_" .. host] ~= nil) then
- stdnse.print_debug(1, " [using cached value]")
+ stdnse.print_debug(1, " |_ [using cached value]")
return true, nmap.registry["nbstat_names_" .. host], nmap.registry["nbstat_statistics_" .. host]
end
diff --git a/nselib/smb.lua b/nselib/smb.lua
index 212fab7a8..f97de537c 100644
--- a/nselib/smb.lua
+++ b/nselib/smb.lua
@@ -1,15 +1,13 @@
---- A library for SMB (Server Message Block) (aka CIFS) traffic.
---
--- This traffic is normally
+--- A library for SMB (Server Message Block) (aka CIFS) traffic. This traffic is normally
-- sent to/from ports 139 or 445 of Windows systems, although it's also implemented by
--- others (the most notable one being Samba). \n
---\n
--- The intention of this library is toe ventually handle all aspects of the SMB protocol,
+-- others (the most notable one being Samba).
+--
+-- The intention of this library is to eventually handle all aspects of the SMB protocol,
-- A programmer using this library must 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 that uses
--- this. The basic login is this:\n
---\n
---
+-- this. The basic login/logoff is this:
+--
+--
-- [connect]
-- C->S SMB_COM_NEGOTIATE
-- S->C SMB_COM_NEGOTIATE
@@ -22,54 +20,128 @@
-- S->C SMB_COM_TREE_DISCONNECT
-- C->S SMB_COM_LOGOFF_ANDX
-- S->C SMB_COM_LOGOFF_ANDX
---
---\n\n
--- In terms of functions here, the protocol is:\n
---
--- status, socket = smb.start(host)
--- status, negotiate_result = smb.negotiate_protocol(socket)
--- status, session_result = smb.start_session(socket, username, negotiate_result['session_key'], negotiate_result['capabilities'])
--- status, tree_result = smb.tree_connect(socket, path, session_result['uid'])
--- status, disconnect_result = smb.tree_disconnect(socket, session_result['uid'], tree_result['tid'])
--- status, logoff_result = smb.logoff(socket, session_result['uid'])
--- status, err = smb.stop(socket)
---
---\n
--- Optionally, the stop function can also call tree_disconnect and logoff, by giving it extra parameters:\n
---
--- status, err = smb.stop(socket, session_result['uid'], tree_result['tid'])
---
+--
+--
+-- In terms of functions here, the protocol is:
+--
+--
+-- 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)
+--
+--
+-- The stop() function will automatically call tree_disconnect and logoff,
+-- cleaning up the session.
--
--- To initially begin the connection, there are two options:\n
--- 1) Attempt to start a raw session over 445, if it's open. \n
+-- 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 "session request" packet.
+-- protocol's the same, it requires a session request packet.
-- That packet requires the computer's name, which is requested
--- using a NBSTAT probe over UDP port 137. \n
+-- using a NBSTAT probe over UDP port 137.
--
-- Once it's connected, a SMB_COM_NEGOTIATE 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.\n
---\n
+-- name.
+--
-- If that's successful, SMB_COM_SESSION_SETUP_ANDX 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 [TODO: Where?], which are set by other
+-- scripts. However, they can also be passed as parameters to the function, which will
+-- override any other username/password set.
+--
+-- If a username is set without a password, then a NULL session is started. If a login fails,
+-- we attempt to log in as the 'GUEST' account with a blank password. If that fails, we try
+-- setting up a NULL session. Starting a NULL session will always work, but we may not get
+-- any further (tree_connect() might fail).
+--
+-- In terms of the login protocol, by default, we sent only NTLMv1 authentication, Lanman
+-- isn't set. The reason for this is, NTLMv2 isn't supported by every system (and I don't know
+-- how to do message signing on the v2 protocols), and doesn't have a significant security
+-- advantage over NTLMv1 (the major change in NTLMv2 is incorporating a client challenge).
+-- Lanman is horribly insecure, though, so I don't send it at all. These options can, however,
+-- be overridden either through script parameters or registry settings [TODO].
+--
+-- Lanman v1 is a fairly weak protocol, although it's still fairly difficult to reverse. NTLMv1 is a slightly more secure
+-- protocol (although not much) -- it's also fairly difficult to reverse, though. Windows clients, by default send LMv1 and
+-- NTLMv1 together, but every modern Windows server will accept NTLM alone, so I opted to use that. LMv2 and NTLMv2 are
+-- slightly more secure, and they let the client specify random data (to help fight malicious servers with pre-
+-- generated tables). LMv2 and NTLMv2 are identical, except that NTLMv2 has a longer client challenge. LMv2 can be sent
+-- alone, but NTLMv2 can't.
+--
+-- Another interesting aspect of the password hashing is that the original password isn't even necessary, the
+-- password's hash can be used instead. This hash can be dumped from memory of a live system by tools such as
+-- pwdump and fgdump, or read straight from the SAM file. This means that if a password file is recovered,
+-- it doesn't even need to be cracked before it can be used here.
+--
-- The response to SMB_COM_SESSION_SETUP_ANDX is fairly simple, containing a boolean for
--- success, along with the operating system and the lan manager name. \n
---\n
+-- success, along with the operating system and the lan manager name.
+--
-- After a successful SMB_COM_SESSION_SETUP_ANDX has been made, a
-- SMB_COM_TREE_CONNECT_ANDX packet can be sent. This is what connects to a share.
--- The server responds to this with a boolean answer, and little more information. \n
---\n
--- Each share will either return STATUS_BAD_NETWORK_NAME if the share doesn't exist, STATUS_ACCESS_DENIED if it exists but we don't have access, or
--- STATUS_SUCCESS if exists and we do have access. \n
---\n
--- Thanks go to Christopher R. Hertel and Implementing CIFS, which
--- taught me everything I know about Microsoft's protocols. \n
+-- The server responds to this with a boolean answer, and little more information.
+--
+-- Each share will either return STATUS_BAD_NETWORK_NAME if the share doesn't
+-- exist, STATUS_ACCESS_DENIED if it exists but we don't have access, or
+-- STATUS_SUCCESS if exists and we do have access. STATUS_ACCESS_DENIED is also returned
+-- if the server requires message signing and we don't return a valid signature.
+--
+-- Once we're connected to a share, we can start doing other operations like reading/writing files
+-- or calling RPC functions. Calling RPC functions is the interesting part, and it's done through
+-- the SMB_TRANS packet. The actual RPC protocol is built on top of the SMB protocol.
+--
+-- 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.
+--
+-- The following arguments are understood by this script. I don't know if putting them in the nselib file
+-- is the right thing to do, but they're here for now anyways.
+--
+-- Here's an example of a script with parameters:
+-- nmap --script=smb-