--- Call various MSRPC functions. -- -- This library gives support for calling various MSRPC functions, making heavy -- use of the smb library. -- 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. -- -- 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. -- -- 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, 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. -- -- Something to note is that these functions, for the most part, return a whole ton -- 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. -- -- 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 ----------------------------------------------------------------------- module(... or "msrpc", package.seeall) require 'bit' require 'bin' require 'netbios' require 'smb' require 'stdnse' -- The path, UUID, and version for SAMR SAMR_PATH = "\\samr" 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 = 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 = 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 = 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 --- 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. --@return The unicode version of the string. local function string_to_unicode(string, do_null, do_align) local i local result = "" if(do_null == nil) then do_null = false end if(do_align == nil) then do_align = false end -- Loop through the string, adding each character followed by a char(0) for i = 1, string.len(string), 1 do result = result .. string.sub(string, i, i) .. string.char(0) end -- Add a null, if the caller requestd it if(do_null == true) then result = result .. string.char(0) .. string.char(0) end -- Align it to a multiple of 4, if necessary if(do_align) then if(string.len(result) % 4 ~= 0) then result = result .. string.char(0) .. string.char(0) end end return result end --- 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 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. 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(3, "MSRPC: Entering unicode_to_string(pos = %d, length = %d)", pos, length) if(do_null == nil) then do_null = false end if(do_align == nil) then do_align = false end if(do_null == true) then length = length - 1 end for j = 1, length, 1 do pos, ch, dummy = bin.unpack("--). -- --@param sid A SID object. --@return A string representing the SID. function sid_to_string(sid) local i local str local authority = bit.bor(bit.lshift(sid['authority_high'], 32), sid['authority']) str = string.format("S-%u-%u", sid['revision'], sid['authority']) for i = 1, sid['count'], 1 do str = str .. string.format("-%u", sid['subauthorities'][i]) end return str end ---Convert a SID string in the standard representation to the equivalent table. -- --This code is sensible, but a little obtuse -- I'm open to suggestions on how to improve it. -- --@param sid A SID string --@return A table representing the SID function string_to_sid(sid) local i local pos, pos_next local result = {} if(string.find(sid, "^S-") == nil) then return nil end if(string.find(sid, "-%d+$") == nil) then return nil end pos = 3 pos_next = string.find(sid, "-", pos) result['revision'] = string.sub(sid, pos, pos_next - 1) pos = pos_next + 1 pos_next = string.find(sid, "-", pos) result['authority_high'] = bit.rshift(string.sub(sid, pos, pos_next - 1), 32) result['authority'] = bit.band(string.sub(sid, pos, pos_next - 1), 0xFFFFFFFF) result['subauthorities'] = {} i = 1 repeat pos = pos_next + 1 pos_next = string.find(sid, "-", pos) if(pos_next == nil) then result['subauthorities'][i] = string.sub(sid, pos) else result['subauthorities'][i] = string.sub(sid, pos, pos_next - 1) end i = i + 1 until pos_next == nil result['count'] = i - 1 return result end --- This is a wrapper around the SMB class, designed to get SMB going quickly for MSRPC calls. This will -- connect to the SMB server, negotiate the protocol, open a session, connect to the IPC$ share, and -- open the named pipe given by 'path'. When this successfully returns, the 'smbstate' table can be immediately -- used for MSRPC (the 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, smbstate) if status is false, smbstate is an error message. Otherwise, smbstate is -- required for all further calls. function start_smb(host, path) local smbstate local status, err -- Begin the SMB session status, smbstate = smb.start(host) if(status == false) then return false, smbstate end -- Negotiate the protocol status, err = smb.negotiate_protocol(smbstate) if(status == false) then smb.stop(smbstate) return false, err end -- Start up a null session status, err = smb.start_session(smbstate) if(status == false) then smb.stop(smbstate) return false, err end -- Connect to IPC$ share status, err = smb.tree_connect(smbstate, "IPC$") if(status == false) then smb.stop(smbstate) return false, err end -- Try to connect to requested pipe status, err = smb.create_file(smbstate, path) if(status == false) then smb.stop(smbstate) return false, err end -- Return everything return true, smbstate end --- 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 state 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 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, -- 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 -- just set this parameter to 'nil'. --@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(smbstate, interface_uuid, interface_version, transfer_syntax) local i local status, result local parameters, data local pos, align local response = {} stdnse.print_debug(2, "MSRPC: Sending Bind() request") -- Use the only transfer_syntax value I know of. if(transfer_syntax == nil) then transfer_syntax = TRANSFER_SYNTAX end data = bin.pack("IIBind response. Pull out some more parameters. pos, response['max_transmit_frag'], response['max_receive_frag'], response['assoc_group'], response['secondary_address_length'] = bin.unpack("SSIS", data, pos) -- 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. --@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(smbstate, opnum, arguments) local i local status, result local parameters, data local pos, align local response = {} data = bin.pack("IInetshareenumall on the remote system. This function basically returns a list of all the shares -- on the system. -- --@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(smbstate, server) local i, j local status, result local arguments local pos, align local level local ctr, referent, count, max_count local response = {} stdnse.print_debug(2, "MSRPC: Calling NetShareEnumAll() [%s]", smbstate['ip']) server = "\\\\" .. server -- [in] [string,charset(UTF16)] uint16 *server_unc arguments = bin.pack("netsharegetinfo on the remote system. This function retrieves extra information about a share -- on the system. -- --@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_netsharegetinfo(smbstate, server, share, level) local i, j local status, result local arguments local pos, align local response = {} 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("ERROR_INVALID_PARAMETER. -- -- Note that the srvsvc.exe process occasionally crashes when attempting this. -- --@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) --@param path1 The first path to compare --@param path2 The second path to compare --@param pathtype The pathtype to pass to the function (I always use '1') --@param pathflags The pathflags to pass to the function (I always use '0') --@return (status, result) If status is false, result is an error message. Otherwise, result is a table of values containing -- 'return'. function srvsvc_netpathcompare(smbstate, server, path1, path2, pathtype, pathflags) local i, j local status, result local arguments local pos, align local response = {} stdnse.print_debug(2, "MSRPC: Calling NetPathCompare(%s, %s) [%s]", path1, path2, 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 -- [in,string,charset(UTF16)] uint16 *system_name, arguments = bin.pack("enumdomains function, which returns a list of all domains in use by the system. -- --@param smbstate The SMB state table. --@param connect_handle The connect_handle, returned by samr_connect4. --@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(smbstate, connect_handle) local i, j local status, result local arguments local result local pos, align local response = {} 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 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(smbstate, connect_handle, domain) local i, j local status, result local arguments local pos, align local referent_id local response = {} stdnse.print_debug(2, "MSRPC: Calling LookupDomain(%s) [%s]", domain, smbstate['ip']) -- [in,ref] policy_handle *connect_handle, arguments = bin.pack("SI<", arguments) response['sid']['subauthorities'] = {} for i = 1, response['sid']['count'], 1 do pos, response['sid']['subauthorities'][i] = bin.unpack("OpenDomain, which returns a handle to the domain identified by the given sid. -- This is required before calling certain functions. -- --@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(smbstate, connect_handle, sid) local i, j local status, result local arguments local pos, align local response = {} stdnse.print_debug(2, "MSRPC: Calling OpenDomain(%s) [%s]", sid_to_string(sid), smbstate['ip']) -- [in,ref] policy_handle *connect_handle, arguments = bin.pack("SI<", sid['count'], sid['revision'], sid['count'], sid['authority_high'], sid['authority']) for i = 1, sid['count'], 1 do arguments = arguments .. bin.pack("EnumDomainUsers, which returns a list of users only. To get more information about the users, the -- QueryDisplayInfo function can be used. -- --@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 local pos, align local response = {} 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. -- --@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(smbstate, domain_handle) local i, j local status, result local arguments local pos, align local function_return local response = {} response['names'] = {} response['details'] = {} -- This loop is because, in my testing, if I asked for all the results at once, it would blow up (ERR_BUFFER_OVERFLOW). So, instead, -- I put a little loop here and grab the names individually. i = 0 repeat 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 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 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: -- 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 local pos, align if(response == nil) then response = {} end 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(smbstate, handle) local i, j local status, result local arguments local pos, align local response = {} 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 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(smbstate, server) local i, j local status, result local arguments local pos, align local response = {} stdnse.print_debug(2, "MSRPC: Calling LsarOpenPolicy2() [%s]", smbstate['ip']) -- [in,unique] [string,charset(UTF16)] uint16 *system_name, arguments = bin.pack("LsarLookupNames2 function, to convert the server's name into a sid. -- --@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(smbstate, policy_handle, names) local i, j local status, result local arguments local result local pos, align local response = {} stdnse.print_debug(2, "MSRPC: Calling LsarLookupNames2(%s) [%s]", stdnse.strjoin(", ", names), 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("LsarLookupSids2 function, to convert a list of SIDs to their names -- --@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(smbstate, policy_handle, sid, rids) local i, j local status, result local arguments local result local pos, align local response = {} 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<", sid['count'] + 1, sid['revision'], sid['count'] + 1, sid['authority_high'], sid['authority']) for j = 1, sid['count'], 1 do arguments = arguments .. bin.pack("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 = 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(smbstate, handle) local i, j local status, result local arguments local pos, align local response = {} 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("