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("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-