diff --git a/CHANGELOG b/CHANGELOG index 886e7ee8e..b662c605f 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,5 +1,14 @@ # Nmap Changelog ($Id$); -*-text-*- +o Added two new SMB/MSRPC scripts: + smb-brute.nse: Bruteforce to discover SMB accounts. Has advanced + features, such as lockout detection, username validation, + username enumeration, and optimized case detection. + smb-pwdump.nse: Uses executables from the Pwdump6 project to + dump password hashes from a remote machine (and optionally + crack them with Rainbow Crack). Pwdump6 files have to be + downloaded separately + o Fixed the install-zenmap make target for Solaris portability. Solaris /bin/sh does not have test(1) -e. [Daniel Roethlisberger] diff --git a/nselib/msrpc.lua b/nselib/msrpc.lua index 71e923fa2..ea59a9e28 100644 --- a/nselib/msrpc.lua +++ b/nselib/msrpc.lua @@ -75,6 +75,16 @@ 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 +-- The path, UUID, and version for SVCCTL +SVCCTL_PATH = "\\svcctl" +SVCCTL_UUID = string.char(0x81, 0xbb, 0x7a, 0x36, 0x44, 0x98, 0xf1, 0x35, 0xad, 0x32, 0x98, 0xf0, 0x38, 0x00, 0x10, 0x03) +SVCCTL_VERSION = 2 + +-- The path, UUID, and version for ATSVC +ATSVC_PATH = "\\atsvc" +ATSVC_UUID = string.char(0x82, 0x06, 0xf7, 0x1f, 0x51, 0x0a, 0xe8, 0x30, 0x07, 0x6d, 0x74, 0x0b, 0xe8, 0xce, 0xe9, 0x8b) +ATSVC_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) @@ -111,48 +121,11 @@ local LSA_MINEMPTY = 10 -- --@param host The host object. --@param path The path to the named pipe; for example, msrpc.SAMR_PATH or msrpc.SRVSVC_PATH. +--@param disable_extended [optional] If set to 'true', disables extended security negotiations. --@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 +function start_smb(host, path, disable_extended) + return smb.start_ex(host, true, true, "IPC$", path, disable_extended) end --- A wrapper around the smb.stop function. I only created it to add symmetry, so client code @@ -336,7 +309,7 @@ local function call_function(smbstate, opnum, arguments) 0x18 + string.len(arguments), -- Frag length (0x18 = the size of this data) 0x0000, -- Auth length 0x41414141, -- Call ID (I use 'AAAA' because it's easy to recognize) - 0x00000100, -- Alloc hint + 0x00000038, -- Alloc hint 0x0000, -- Context ID opnum, -- Opnum arguments @@ -1827,7 +1800,7 @@ function winreg_enumkey(smbstate, handle, index, name) -- [in,out,ref] winreg_StringBuf *name, -- NOTE: if the 'name' parameter here is set to 'nil', the service on a fully patched Windows 2000 system -- may crash. - arguments = arguments .. msrpctypes.marshall_winreg_StringBuf({name=nil}, 520) + arguments = arguments .. msrpctypes.marshall_winreg_StringBuf({name=""}, 520) -- [in,out,unique] winreg_StringBuf *keyclass, arguments = arguments .. msrpctypes.marshall_winreg_StringBuf_ptr({name=nil}) @@ -1952,7 +1925,7 @@ function winreg_queryinfokey(smbstate, handle) arguments = msrpctypes.marshall_policy_handle(handle) -- [in,out,ref] winreg_String *classname, - arguments = arguments .. msrpctypes.marshall_winreg_String("", 2048) + arguments = arguments .. msrpctypes.marshall_winreg_String({name=""}, 2048) -- [out,ref] uint32 *num_subkeys, -- [out,ref] uint32 *max_subkeylen, @@ -2082,8 +2055,10 @@ function winreg_queryvalue(smbstate, handle, value) _, result['value'] = msrpctypes.unicode_to_string(result['data'], 1, #result['data'] / 2) elseif(result['type'] == "REG_BINARY") then result['value'] = result['data'] + elseif(result['type'] == "REG_NONE") then + result['value'] = "" else - stdnse.print_debug("MSRPC ERROR: Unknown type: %s\n\n", result['type']) + stdnse.print_debug("MSRPC ERROR: Unknown type: %s", result['type']) result['value'] = result['type'] end else @@ -2154,6 +2129,592 @@ function winreg_closekey(smbstate, handle) return true, result end +--- Calls the function OpenSCManagerA, which gets a handle to the service manager. Should be closed with +-- CloseServiceHandle when finished. +-- +--@param smbstate The SMB state table +--@param machinename The name or IP of the machine. +--@return (status, result) If status is false, result is an error message. Otherwise, result is a table of values +-- representing the "out" parameters. +function svcctl_openscmanagera(smbstate, machinename) + local i, j + local status, result + local arguments + local pos, align + + stdnse.print_debug(2, "MSRPC: Calling OpenSCManagerA() [%s]", smbstate['ip']) + +-- [in] [string,charset(UTF16)] uint16 *MachineName, + arguments = msrpctypes.marshall_ascii_ptr("\\\\" .. machinename) + +-- [in] [string,charset(UTF16)] uint16 *DatabaseName, + arguments = arguments .. msrpctypes.marshall_ascii_ptr(nil) + +-- [in] uint32 access_mask, +-- arguments = arguments .. msrpctypes.marshall_int32(0x000f003f) + arguments = arguments .. msrpctypes.marshall_int32(0x00000002) + +-- [out,ref] policy_handle *handle + + -- Do the call + status, result = call_function(smbstate, 0x1b, arguments) + if(status ~= true) then + return false, result + end + + stdnse.print_debug(3, "MSRPC: OpenSCManagerA() returned successfully") + + -- Make arguments easier to use + arguments = result['arguments'] + pos = 1 + +-- [in] [string,charset(UTF16)] uint16 *MachineName, +-- [in] [string,charset(UTF16)] uint16 *DatabaseName, +-- [in] uint32 access_mask, +-- [out,ref] policy_handle *handle + pos, result['handle'] = msrpctypes.unmarshall_policy_handle(arguments, pos) + + pos, result['return'] = msrpctypes.unmarshall_int32(arguments, pos) + if(result['return'] == nil) then + return false, "Read off the end of the packet (svcctl.openscmanagera)" + end + if(result['return'] ~= 0) then + return false, smb.get_status_name(result['return']) .. " (svcctl.openscmanagera)" + end + + return true, result +end + + +--- Calls the function OpenSCManagerW, which gets a handle to the service manager. Should be closed with +-- CloseServiceHandle when finished. +-- +--@param smbstate The SMB state table +--@param machinename The name or IP of the machine. +--@return (status, result) If status is false, result is an error message. Otherwise, result is a table of values +-- representing the "out" parameters. +function svcctl_openscmanagerw(smbstate, machinename) + local i, j + local status, result + local arguments + local pos, align + +-- if(1 == 1) then +-- return svcctl_openscmanagera(smbstate, machinename) +-- end + + stdnse.print_debug(2, "MSRPC: Calling OpenSCManagerW() [%s]", smbstate['ip']) + +-- [in] [string,charset(UTF16)] uint16 *MachineName, + arguments = msrpctypes.marshall_unicode_ptr("\\\\" .. machinename, true) + +-- [in] [string,charset(UTF16)] uint16 *DatabaseName, + arguments = arguments .. msrpctypes.marshall_unicode_ptr(nil, true) + +-- [in] uint32 access_mask, + arguments = arguments .. msrpctypes.marshall_int32(0x000f003f) +-- arguments = arguments .. msrpctypes.marshall_int32(0x00000002) + +-- [out,ref] policy_handle *handle + + -- Do the call + status, result = call_function(smbstate, 0x0f, arguments) + if(status ~= true) then + return false, result + end + + stdnse.print_debug(3, "MSRPC: OpenSCManagerW() returned successfully") + + -- Make arguments easier to use + arguments = result['arguments'] + pos = 1 + +-- [in] [string,charset(UTF16)] uint16 *MachineName, +-- [in] [string,charset(UTF16)] uint16 *DatabaseName, +-- [in] uint32 access_mask, +-- [out,ref] policy_handle *handle + pos, result['handle'] = msrpctypes.unmarshall_policy_handle(arguments, pos) + + pos, result['return'] = msrpctypes.unmarshall_int32(arguments, pos) + if(result['return'] == nil) then + return false, "Read off the end of the packet (svcctl.openscmanagerw)" + end + if(result['return'] ~= 0) then + return false, smb.get_status_name(result['return']) .. " (svcctl.openscmanagerw)" + end + + return true, result +end + + +--- Calls the function CloseServiceHandle, which releases a handle. +-- +--@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 +-- representing the "out" parameters. +function svcctl_closeservicehandle(smbstate, handle) + local i, j + local status, result + local arguments + local pos, align + + stdnse.print_debug(2, "MSRPC: Calling CloseServiceHandle() [%s]", smbstate['ip']) + +-- [in,out,ref] policy_handle *handle + arguments = msrpctypes.marshall_policy_handle(handle) + + + -- Do the call + status, result = call_function(smbstate, 0x00, arguments) + if(status ~= true) then + return false, result + end + + stdnse.print_debug(3, "MSRPC: OpenSCManagerA() returned successfully") + + -- Make arguments easier to use + arguments = result['arguments'] + pos = 1 + +-- [in,out,ref] policy_handle *handle + pos, result['handle'] = msrpctypes.unmarshall_policy_handle(arguments, pos) + + pos, result['return'] = msrpctypes.unmarshall_int32(arguments, pos) + if(result['return'] == nil) then + return false, "Read off the end of the packet (svcctl.closeservicehandle)" + end + if(result['return'] ~= 0) then + return false, smb.get_status_name(result['return']) .. " (svcctl.closeservicehandle)" + end + + return true, result +end + +--- Calls the function CreateServiceW, which creates a service on the remote machine. This should +-- be deleted with DeleteService when finished. +-- +--@param smbstate The SMB state table +--@param handle The handle created by OpenSCManagerW +--@return (status, result) If status is false, result is an error message. Otherwise, result is a table of values +-- representing the "out" parameters. +function svcctl_createservicew(smbstate, handle, service_name, display_name, path) + local i, j + local status, result + local arguments + local pos, align + + stdnse.print_debug(2, "MSRPC: Calling CreateServiceW() [%s]", smbstate['ip']) + +-- [in,ref] policy_handle *scmanager_handle, + arguments = msrpctypes.marshall_policy_handle(handle) + +-- [in] [string,charset(UTF16)] uint16 ServiceName[], + arguments = arguments .. msrpctypes.marshall_unicode(service_name, true) + +-- [in] [string,charset(UTF16)] uint16 *DisplayName, + arguments = arguments .. msrpctypes.marshall_unicode_ptr(display_name, true) + +-- [in] uint32 desired_access, + arguments = arguments .. msrpctypes.marshall_int32(0x000f01ff) -- Access: Max + +-- [in] uint32 type, + arguments = arguments .. msrpctypes.marshall_int32(0x00000010) -- Type: own process + +-- [in] uint32 start_type, + arguments = arguments .. msrpctypes.marshall_int32(0x00000003) -- Start: Demand + +-- [in] uint32 error_control, + arguments = arguments .. msrpctypes.marshall_int32(0x00000000) -- Error: Ignore + +-- [in] [string,charset(UTF16)] uint16 binary_path[], + arguments = arguments .. msrpctypes.marshall_unicode(path, true) + +-- [in] [string,charset(UTF16)] uint16 *LoadOrderGroupKey, + arguments = arguments .. msrpctypes.marshall_unicode_ptr(nil) + +-- [in,out] uint32 *TagId, + arguments = arguments .. msrpctypes.marshall_int32_ptr(nil) + +-- [in,size_is(dependencies_size)] uint8 *dependencies, + arguments = arguments .. msrpctypes.marshall_int8_ptr(nil) + +-- [in] uint32 dependencies_size, + arguments = arguments .. msrpctypes.marshall_int32(0) + +-- [in] [string,charset(UTF16)] uint16 *service_start_name, + arguments = arguments .. msrpctypes.marshall_unicode_ptr(nil) + +-- [in,size_is(password_size)] uint8 *password, + arguments = arguments .. msrpctypes.marshall_int8_ptr(nil) + +-- [in] uint32 password_size, + arguments = arguments .. msrpctypes.marshall_int32(0) + +-- [out,ref] policy_handle *handle + + + + -- Do the call + status, result = call_function(smbstate, 0x0c, arguments) + if(status ~= true) then + return false, result + end + + stdnse.print_debug(3, "MSRPC: CreateServiceW() returned successfully") + + -- Make arguments easier to use + arguments = result['arguments'] + pos = 1 + +-- [in,ref] policy_handle *scmanager_handle, +-- [in] [string,charset(UTF16)] uint16 ServiceName[], +-- [in] [string,charset(UTF16)] uint16 *DisplayName, +-- [in] uint32 desired_access, +-- [in] uint32 type, +-- [in] uint32 start_type, +-- [in] uint32 error_control, +-- [in] [string,charset(UTF16)] uint16 binary_path[], +-- [in] [string,charset(UTF16)] uint16 *LoadOrderGroupKey, +-- [in,out] uint32 *TagId, + pos, result['TagId'] = msrpctypes.unmarshall_int32_ptr(arguments, pos) + +-- [in,size_is(dependencies_size)] uint8 *dependencies, +-- [in] uint32 dependencies_size, +-- [in] [string,charset(UTF16)] uint16 *service_start_name, +-- [in,size_is(password_size)] uint8 *password, +-- [in] uint32 password_size, +-- [out,ref] policy_handle *handle + pos, result['handle'] = msrpctypes.unmarshall_policy_handle(arguments, pos) + + pos, result['return'] = msrpctypes.unmarshall_int32(arguments, pos) + if(result['return'] == nil) then + return false, "Read off the end of the packet (svcctl.createservicew)" + end + if(result['return'] ~= 0) then + return false, smb.get_status_name(result['return']) .. " (svcctl.createservicew)" + end + + return true, result +end + +--- Calls the function DeleteService, which deletes a service on the remote machine. This service +-- has to opened with OpenServiceW or similar functions. +-- +--@param smbstate The SMB state table. +--@param handle The handle to delete, opened with OpenServiceW or similar. +--@return (status, result) If status is false, result is an error message. Otherwise, result is a table of values +-- representing the "out" parameters. +function svcctl_deleteservice(smbstate, handle) + local i, j + local status, result + local arguments + local pos, align + + stdnse.print_debug(2, "MSRPC: Calling DeleteService() [%s]", smbstate['ip']) + +-- [in,ref] policy_handle *handle + arguments = msrpctypes.marshall_policy_handle(handle) + + + -- Do the call + status, result = call_function(smbstate, 0x02, arguments) + if(status ~= true) then + return false, result + end + + stdnse.print_debug(3, "MSRPC: DeleteService() returned successfully") + + -- Make arguments easier to use + arguments = result['arguments'] + pos = 1 + + +-- [in,ref] policy_handle *handle + + + pos, result['return'] = msrpctypes.unmarshall_int32(arguments, pos) + if(result['return'] == nil) then + return false, "Read off the end of the packet (svcctl.deleteservice)" + end + if(result['return'] ~= 0) then + return false, smb.get_status_name(result['return']) .. " (svcctl.deleteservice)" + end + + return true, result +end + +--- Calls the function OpenServiceW, which gets a handle to the service. Should be closed with +-- CloseServiceHandle when finished. +-- +--@param smbstate The SMB state table. +--@param handle A handle to the policy manager, opened with OpenSCManagerW or similar. +--@param name The name of the service. +--@return (status, result) If status is false, result is an error message. Otherwise, result is a table of values +-- representing the "out" parameters. +function svcctl_openservicew(smbstate, handle, name) + local i, j + local status, result + local arguments + local pos, align + + stdnse.print_debug(2, "MSRPC: Calling OpenServiceW() [%s]", smbstate['ip']) + +-- [in,ref] policy_handle *scmanager_handle, + arguments = msrpctypes.marshall_policy_handle(handle) + +-- [in] [string,charset(UTF16)] uint16 ServiceName[], + arguments = arguments .. msrpctypes.marshall_unicode(name, true) + +-- [in] uint32 access_mask, + arguments = arguments .. msrpctypes.marshall_int32(0x000f01ff) +-- [out,ref] policy_handle *handle + + + -- Do the call + status, result = call_function(smbstate, 0x10, arguments) + if(status ~= true) then + return false, result + end + + stdnse.print_debug(3, "MSRPC: OpenServiceW() returned successfully") + + -- Make arguments easier to use + arguments = result['arguments'] + pos = 1 + +-- [in,ref] policy_handle *scmanager_handle, +-- [in] [string,charset(UTF16)] uint16 ServiceName[], +-- [in] uint32 access_mask, +-- [out,ref] policy_handle *handle + pos, result['handle'] = msrpctypes.unmarshall_policy_handle(arguments, pos) + + pos, result['return'] = msrpctypes.unmarshall_int32(arguments, pos) + if(result['return'] == nil) then + return false, "Read off the end of the packet (svcctl.openservicew)" + end + if(result['return'] ~= 0) then + return false, smb.get_status_name(result['return']) .. " (svcctl.openservicew)" + end + + return true, result +end + +--- Calls the function StartServiceW, which starts a service. Requires a handle +-- created by OpenServiceW. +-- +--@param smbstate The SMB state table. +--@param handle The handle, opened by OpenServiceW. +--@param args An array of strings representing the arguments. +--@return (status, result) If status is false, result is an error message. Otherwise, result is a table of values +-- representing the "out" parameters. +function svcctl_startservicew(smbstate, handle, args) + local i, j + local status, result + local arguments + local pos, align + stdnse.print_debug(2, "MSRPC: Calling StartServiceW() [%s]", smbstate['ip']) + +-- [in,ref] policy_handle *handle, + arguments = msrpctypes.marshall_policy_handle(handle) + +-- [in] uint32 NumArgs, + if(args == nil) then + arguments = arguments .. msrpctypes.marshall_int32(0) + else + arguments = arguments .. msrpctypes.marshall_int32(#args) + end + +-- [in/*FIXME:,length_is(NumArgs)*/] [string,charset(UTF16)] uint16 *Arguments + arguments = arguments .. msrpctypes.marshall_unicode_array_ptr(args, true) + + -- Do the call + status, result = call_function(smbstate, 0x13, arguments) + if(status ~= true) then + return false, result + end + + stdnse.print_debug(3, "MSRPC: StartServiceW() returned successfully") + + -- Make arguments easier to use + arguments = result['arguments'] + pos = 1 + +-- [in,ref] policy_handle *handle, +-- [in] uint32 NumArgs, +-- [in/*FIXME:,length_is(NumArgs)*/] [string,charset(UTF16)] uint16 *Arguments + + pos, result['return'] = msrpctypes.unmarshall_int32(arguments, pos) + if(result['return'] == nil) then + return false, "Read off the end of the packet (svcctl.startservicew)" + end + if(result['return'] ~= 0) then + return false, smb.get_status_name(result['return']) .. " (svcctl.startservicew)" + end + + return true, result + +end + +--- Calls the function ControlService, which can send various commands to the service. +-- +--@param smbstate The SMB state table. +--@param handle The handle, opened by OpenServiceW. +--@param control The command to send. See svcctl_ControlCode in msrpctypes.lua. +--@return (status, result) If status is false, result is an error message. Otherwise, result is a table of values +-- representing the "out" parameters. +function svcctl_controlservice(smbstate, handle, control) + local i, j + local status, result + local arguments + local pos, align + + stdnse.print_debug(2, "MSRPC: Calling ControlService() [%s]", smbstate['ip']) + +-- [in,ref] policy_handle *handle, + arguments = msrpctypes.marshall_policy_handle(handle) + +-- [in] uint32 control, + arguments = arguments .. msrpctypes.marshall_svcctl_ControlCode(control) + +-- [out,ref] SERVICE_STATUS *service_status + + + -- Do the call + status, result = call_function(smbstate, 0x01, arguments) + if(status ~= true) then + return false, result + end + + stdnse.print_debug(3, "MSRPC: ControlService() returned successfully") + + -- Make arguments easier to use + arguments = result['arguments'] + pos = 1 + +-- [in,ref] policy_handle *handle, +-- [in] uint32 control, +-- [out,ref] SERVICE_STATUS *service_status + pos, result['service_status'] = msrpctypes.unmarshall_SERVICE_STATUS(arguments, pos) + + pos, result['return'] = msrpctypes.unmarshall_int32(arguments, pos) + if(result['return'] == nil) then + return false, "Read off the end of the packet (svcctl.controlservice)" + end + if(result['return'] ~= 0) then + return false, smb.get_status_name(result['return']) .. " (svcctl.controlservice)" + end + + return true, result + +end + + +--- Calls the function QueryServiceStatus, which gets the state information about the service. +-- +--@param smbstate The SMB state table. +--@param handle The handle, opened by OpenServiceW. +--@return (status, result) If status is false, result is an error message. Otherwise, result is a table of values +-- representing the "out" parameters. +function svcctl_queryservicestatus(smbstate, handle, control) + local i, j + local status, result + local arguments + local pos, align + + stdnse.print_debug(2, "MSRPC: Calling QueryServiceStatus() [%s]", smbstate['ip']) + +-- [in,ref] policy_handle *handle, + arguments = msrpctypes.marshall_policy_handle(handle) + +-- [out,ref] SERVICE_STATUS *service_status + + + -- Do the call + status, result = call_function(smbstate, 0x06, arguments) + if(status ~= true) then + return false, result + end + + stdnse.print_debug(3, "MSRPC: QueryServiceStatus() returned successfully") + + -- Make arguments easier to use + arguments = result['arguments'] + pos = 1 + +-- [in,ref] policy_handle *handle, +-- [out,ref] SERVICE_STATUS *service_status + pos, result['service_status'] = msrpctypes.unmarshall_SERVICE_STATUS(arguments, pos) + + pos, result['return'] = msrpctypes.unmarshall_int32(arguments, pos) + if(result['return'] == nil) then + return false, "Read off the end of the packet (svcctl.queryservicestatus)" + end + if(result['return'] ~= 0) then + return false, smb.get_status_name(result['return']) .. " (svcctl.queryservicestatus)" + end + + return true, result +end + +---Calls the function JobAdd, which schedules a process to be run on the remote +-- machine. This requires administrator privileges to run, and the command itself runs as +-- 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) +--@param command The command to run on the remote machine. The appropriate file(s) already +-- have to be there, and this should be a full path. +--@param time (optional) The time at which to run the command. Default: 5 seconds from +-- when the user logged in. +function atsvc_jobadd(smbstate, server, command, time) + local i, j + local status, result + local arguments + local pos, align + + -- Set up the time + if(time == nil) then + -- TODO + end + + stdnse.print_debug(2, "MSRPC: Calling AddJob(%s) [%s]", command, smbstate['ip']) + +-- [in,unique,string,charset(UTF16)] uint16 *servername, + arguments = msrpctypes.marshall_unicode_ptr(server, true) + +-- [in] atsvc_JobInfo *job_info, + arguments = arguments .. msrpctypes.marshall_atsvc_JobInfo(command, time) +-- [out,ref] uint32 *job_id + + + -- Do the call + status, result = call_function(smbstate, 0x00, arguments) + if(status ~= true) then + return false, result + end + + stdnse.print_debug(3, "MSRPC: AddJob() returned successfully") + + -- Make arguments easier to use + arguments = result['arguments'] + pos = 1 + +-- [in,unique,string,charset(UTF16)] uint16 *servername, +-- [in] atsvc_JobInfo *job_info, +-- [out,ref] uint32 *job_id + pos, result['job_id'] = msrpctypes.unmarshall_int32(arguments, pos) + + pos, result['return'] = msrpctypes.unmarshall_int32(arguments, pos) + if(result['return'] == nil) then + return false, "Read off the end of the packet (atsvc.addjob())" + end + if(result['return'] ~= 0) then + return false, smb.get_status_name(result['return']) .. " (atsvc.addjob())" + end + + return true, result +end + ---Attempt to enumerate users using SAMR functions. -- --@param host The host object. @@ -2179,23 +2740,23 @@ function samr_enum_users(host) local response = {} -- Create the SMB session - status, smbstate = msrpc.start_smb(host, msrpc.SAMR_PATH) + status, smbstate = start_smb(host, SAMR_PATH, true) if(status == false) then return false, smbstate end -- Bind to SAMR service - status, bind_result = msrpc.bind(smbstate, msrpc.SAMR_UUID, msrpc.SAMR_VERSION, nil) + status, bind_result = bind(smbstate, SAMR_UUID, SAMR_VERSION, nil) if(status == false) then - msrpc.stop_smb(smbstate) + stop_smb(smbstate) return false, bind_result end -- Call connect4() - status, connect4_result = msrpc.samr_connect4(smbstate, host.ip) + status, connect4_result = samr_connect4(smbstate, host.ip) if(status == false) then - msrpc.stop_smb(smbstate) + stop_smb(smbstate) return false, connect4_result end @@ -2203,15 +2764,15 @@ function samr_enum_users(host) connect_handle = connect4_result['connect_handle'] -- Call EnumDomains() - status, enumdomains_result = msrpc.samr_enumdomains(smbstate, connect_handle) + status, enumdomains_result = samr_enumdomains(smbstate, connect_handle) if(status == false) then - msrpc.stop_smb(smbstate) + stop_smb(smbstate) return false, enumdomains_result end -- If no domains were returned, go back with an error if(#enumdomains_result['sam']['entries'] == 0) then - msrpc.stop_smb(smbstate) + stop_smb(smbstate) return false, "Couldn't find any domains" end @@ -2226,9 +2787,9 @@ function samr_enum_users(host) local opendomain_result, querydisplayinfo_result -- Call LookupDomain() - status, lookupdomain_result = msrpc.samr_lookupdomain(smbstate, connect_handle, domain) + status, lookupdomain_result = samr_lookupdomain(smbstate, connect_handle, domain) if(status == false) then - msrpc.stop_smb(smbstate) + stop_smb(smbstate) return false, lookupdomain_result end @@ -2236,9 +2797,9 @@ function samr_enum_users(host) sid = lookupdomain_result['sid'] -- Call OpenDomain() - status, opendomain_result = msrpc.samr_opendomain(smbstate, connect_handle, sid) + status, opendomain_result = samr_opendomain(smbstate, connect_handle, sid) if(status == false) then - msrpc.stop_smb(smbstate) + stop_smb(smbstate) return false, opendomain_result end @@ -2249,9 +2810,9 @@ function samr_enum_users(host) j = 0 repeat -- Call QueryDisplayInfo() - status, querydisplayinfo_result = msrpc.samr_querydisplayinfo(smbstate, domain_handle, j, SAMR_GROUPSIZE) + status, querydisplayinfo_result = samr_querydisplayinfo(smbstate, domain_handle, j, SAMR_GROUPSIZE) if(status == false) then - msrpc.stop_smb(smbstate) + stop_smb(smbstate) return false, querydisplayinfo_result end @@ -2275,7 +2836,7 @@ function samr_enum_users(host) -- Convert each element in the 'flags' array into the equivalent string for l = 1, #array['flags'], 1 do - array['flags'][l] = msrpc.samr_AcctFlags_tostr(array['flags'][l]) + array['flags'][l] = samr_AcctFlags_tostr(array['flags'][l]) end -- Add it to the array @@ -2286,15 +2847,15 @@ function samr_enum_users(host) until querydisplayinfo_result['return'] == 0 -- Close the domain handle - msrpc.samr_close(smbstate, domain_handle) + samr_close(smbstate, domain_handle) end -- Checking for 'builtin' end -- Domain loop -- Close the connect handle - msrpc.samr_close(smbstate, connect_handle) + samr_close(smbstate, connect_handle) -- Stop the SAMR SMB - msrpc.stop_smb(smbstate) + stop_smb(smbstate) stdnse.print_debug(3, "Leaving enum_samr()") @@ -2320,28 +2881,35 @@ function lsa_enum_users(host) stdnse.print_debug(3, "Entering enum_lsa()") -- Create the SMB session - status, smbstate = msrpc.start_smb(host, msrpc.LSA_PATH) + status, smbstate = start_smb(host, LSA_PATH, true) if(status == false) then return false, smbstate end -- Bind to LSA service - status, bind_result = msrpc.bind(smbstate, msrpc.LSA_UUID, msrpc.LSA_VERSION, nil) + status, bind_result = bind(smbstate, LSA_UUID, LSA_VERSION, nil) if(status == false) then - msrpc.stop_smb(smbstate) + stop_smb(smbstate) return false, bind_result end -- Open the LSA policy - status, openpolicy2_result = msrpc.lsa_openpolicy2(smbstate, host.ip) + status, openpolicy2_result = lsa_openpolicy2(smbstate, host.ip) if(status == false) then - msrpc.stop_smb(smbstate) + stop_smb(smbstate) return false, openpolicy2_result end -- Start with some common names, as well as the name returned by the negotiate call -- Vista doesn't like a 'null' after the server name, so fix that (TODO: the way I strip the null here feels hackish, is there a better way?) - names = {"administrator", "guest", "test", smbstate['domain'], string.sub(smbstate['server'], 1, #smbstate['server'] - 1) } + names = {"administrator", "guest", "test"} + -- These aren't always sent back (especially with 'extended security') + if(smbstate['domain'] ~= nil) then + names[#names + 1] = smbstate['domain'] + end + if(smbstate['server'] ~= nil) then + names[#names + 1] = string.sub(smbstate['server'], 1, #smbstate['server'] - 1) + end -- Get the server's name from nbstat local result, server_name = netbios.get_server_name(host.ip) @@ -2356,9 +2924,9 @@ function lsa_enum_users(host) end -- Look up the names, if any are valid than the server's SID will be returned - status, lookupnames2_result = msrpc.lsa_lookupnames2(smbstate, openpolicy2_result['policy_handle'], names) + status, lookupnames2_result = lsa_lookupnames2(smbstate, openpolicy2_result['policy_handle'], names) if(status == false) then - msrpc.stop_smb(smbstate) + stop_smb(smbstate) return false, lookupnames2_result end -- Loop through the domains returned and find the users in each @@ -2372,7 +2940,7 @@ function lsa_enum_users(host) sids[#sids + 1] = sid .. "-" .. j end - status, lookupsids2_result = msrpc.lsa_lookupsids2(smbstate, openpolicy2_result['policy_handle'], sids) + status, lookupsids2_result = lsa_lookupsids2(smbstate, openpolicy2_result['policy_handle'], sids) if(status == false) then stdnse.print_debug(1, string.format("Error looking up RIDs: %s", lookupsids2_result)) else @@ -2385,7 +2953,7 @@ function lsa_enum_users(host) result['rid'] = 500 + j - 1 result['domain'] = domain result['type'] = lookupsids2_result['names']['names'][j]['sid_type'] - result['typestr'] = msrpc.lsa_SidType_tostr(result['type']) + result['typestr'] = lsa_SidType_tostr(result['type']) result['source'] = "LSA Bruteforce" table.insert(response, result) end @@ -2406,24 +2974,34 @@ function lsa_enum_users(host) end -- Try converting this group of RIDs into names - status, lookupsids2_result = msrpc.lsa_lookupsids2(smbstate, openpolicy2_result['policy_handle'], sids) + status, lookupsids2_result = lsa_lookupsids2(smbstate, openpolicy2_result['policy_handle'], sids) if(status == false) then stdnse.print_debug(1, string.format("Error looking up RIDs: %s", lookupsids2_result)) else -- Put the details for each name into an array for j = 1, #lookupsids2_result['names']['names'], 1 do - if(lookupsids2_result['names']['names'][j]['sid_type'] ~= "SID_NAME_UNKNOWN") then - local result = {} - result['name'] = lookupsids2_result['names']['names'][j]['name'] - result['rid'] = start + j - 1 - result['domain'] = domain - result['type'] = lookupsids2_result['names']['names'][j]['sid_type'] - result['typestr'] = msrpc.lsa_SidType_tostr(result['type']) - result['source'] = "LSA Bruteforce" - table.insert(response, result) + -- Determine the RID + local name = lookupsids2_result['names']['names'][j]['name'] + local rid = start + j - 1 + local typenum = lookupsids2_result['names']['names'][j]['sid_type'] + local typestr = lsa_SidType_tostr(typenum) - -- Increment the number of names we've found - used_names = used_names + 1 + -- Check if the username matches the rid (one server we discovered returned every user as valid, + -- this is to prevent that infinite loop) + if(tonumber(name) ~= rid) then + if(lookupsids2_result['names']['names'][j]['sid_type'] ~= "SID_NAME_UNKNOWN") then + local result = {} + result['name'] = name + result['rid'] = rid + result['domain'] = domain + result['type'] = typenum + result['typestr'] = typestr + result['source'] = "LSA Bruteforce" + table.insert(response, result) + + -- Increment the number of names we've found + used_names = used_names + 1 + end end end end @@ -2442,9 +3020,9 @@ function lsa_enum_users(host) end -- Close the handle - msrpc.lsa_close(smbstate, openpolicy2_result['policy_handle']) + lsa_close(smbstate, openpolicy2_result['policy_handle']) - msrpc.stop_smb(smbstate) + stop_smb(smbstate) stdnse.print_debug(3, "Leaving enum_lsa()") @@ -2499,3 +3077,453 @@ function get_user_list(host) return true, response, names end +---Create a "service" on a remote machine. This service is linked to an executable that is already +-- on the system. The name of the service can be whatever you want it to be. The service is created +-- in the "stopped" state with "manual" startup, and it ignores errors. The 'servicename' is what +-- people will see on the system while the service is running, and what'll stay there is something +-- happens that the service can't be deleted properly. +-- +-- Note that this (and the other "service" functions) are highly invasive. They make configuration +-- changes to the machine that can potentially affect stability. +-- +-- The reason that this and the other "service" functions don't require a smbstate +-- object is that I wanted them to be independent. If a service fails to start, I don't want it +-- to affect the program's ability to stop and delete the service. Every service function is +-- independent. +-- +--@param host The host object. +--@param servicename The name of the service to create. +--@param path The path and filename on the remote system. +--@return (status, err) If status is false, err is an error message; +-- otherwise, err is undefined. +function service_create(host, servicename, path) + local status, smbstate, bind_result, open_result, create_result, close_result + + stdnse.print_debug(1, "Creating service: %s (%s)", servicename, path) + + -- Create the SMB session + status, smbstate = start_smb(host, SVCCTL_PATH) + if(status == false) then + return false, smbstate + end + + -- Bind to SVCCTL service + status, bind_result = bind(smbstate, SVCCTL_UUID, SVCCTL_VERSION, nil) + if(status == false) then + smb.stop(smbstate) + return false, bind_result + end + + -- Open the service manager + stdnse.print_debug(2, "Opening the remote service manager") + status, open_result = svcctl_openscmanagerw(smbstate, host.ip) + if(status == false) then + smb.stop(smbstate) + return false, open_result + end + + -- Create the service + stdnse.print_debug(2, "Creating the service", servicename) + status, create_result = svcctl_createservicew(smbstate, open_result['handle'], servicename, servicename, path) + if(status == false) then + smb.stop(smbstate) + return false, create_result + end + -- Close the handle to the service + status, close_result = svcctl_closeservicehandle(smbstate, create_result['handle']) + if(status == false) then + smb.stop(smbstate) + return false, close_result + end + + -- Close the service manager + status, close_result = svcctl_closeservicehandle(smbstate, open_result['handle']) + if(status == false) then + smb.stop(smbstate) + return false, close_result + end + + smb.stop(smbstate) + + return true +end + +---Start a service on the remote machine based on its name. For example, to start the registry +-- service, this can be called on "RemoteRegistry". +-- +-- If you start a service on a machine, you should also stop it when you're finished. Every service +-- running is extra attack surface for a potential attacker +-- +--@param host The host object. +--@param servicename The name of the service to start. +--@param args [optional] The arguments to pass to the service. Most built-in services don't +-- require arguments. +--@return (status, err) If status is false, err is an error message; +-- otherwise, err is undefined. +function service_start(host, servicename, args) + local status, smbstate, bind_result, open_result, open_service_result, start_result, close_result, query_result + + stdnse.print_debug(1, "Starting service: %s", servicename) + + -- Create the SMB session + status, smbstate = start_smb(host, SVCCTL_PATH) + if(status == false) then + return false, smbstate + end + + -- Bind to SVCCTL service + status, bind_result = bind(smbstate, SVCCTL_UUID, SVCCTL_VERSION, nil) + if(status == false) then + smb.stop(smbstate) + return false, bind_result + end + + -- Open the service manager + stdnse.print_debug(1, "Opening the remote service manager") + status, open_result = svcctl_openscmanagerw(smbstate, host.ip) + if(status == false) then + smb.stop(smbstate) + return false, open_result + end + + -- Get a handle to the service + stdnse.print_debug(2, "Getting a handle to the service") + status, open_service_result = svcctl_openservicew(smbstate, open_result['handle'], servicename) + if(status == false) then + smb.stop(smbstate) + return false, open_service_result + end + + -- Start it + stdnse.print_debug(2, "Starting the service") + status, start_result = svcctl_startservicew(smbstate, open_service_result['handle'], args) + if(status == false) then + smb.stop(smbstate) + return false, start_result + end + + -- Wait for it to start + stdnse.print_debug(2, "Waiting for the service to start") + repeat + status, query_result = svcctl_queryservicestatus(smbstate, open_service_result['handle']) + if(status == false) then + smb.stop(smbstate) + return false, query_result + end + until query_result['service_status']['controls_accepted'][1] == "SERVICE_CONTROL_STOP" + + -- Close the handle to the service + status, close_result = svcctl_closeservicehandle(smbstate, open_service_result['handle']) + if(status == false) then + smb.stop(smbstate) + return false, close_result + end + + -- Close the service manager + status, close_result = svcctl_closeservicehandle(smbstate, open_result['handle']) + if(status == false) then + smb.stop(smbstate) + return false, close_result + end + + smb.stop(smbstate) + + return true +end + +---Stop a service on the remote machine based on its name. For example, to stop the registry +-- service, this can be called on "RemoteRegistry". +-- +-- This can be called on a service that's already stopped without hurting anything (just keep in mind +-- that an error will be returned). +-- +--@param host The host object. +--@param servicename The name of the service to stop. +--@return (status, err) If status is false, err is an error message; +-- otherwise, err is undefined. +function service_stop(host, servicename) + local status, smbstate, bind_result, open_result, open_service_result, control_result, close_result, query_result + + stdnse.print_debug(1, "Stopping service: %s", servicename) + + -- Create the SMB session + status, smbstate = start_smb(host, SVCCTL_PATH) + if(status == false) then + return false, smbstate + end + + -- Bind to SVCCTL service + status, bind_result = bind(smbstate, SVCCTL_UUID, SVCCTL_VERSION, nil) + if(status == false) then + smb.stop(smbstate) + return false, bind_result + end + + -- Open the service manager + stdnse.print_debug(2, "Opening the remote service manager") + status, open_result = svcctl_openscmanagerw(smbstate, host.ip) + if(status == false) then + smb.stop(smbstate) + return false, open_result + end + + -- Get a handle to the service + stdnse.print_debug(2, "Getting a handle to the service") + status, open_service_result = svcctl_openservicew(smbstate, open_result['handle'], servicename) + if(status == false) then + smb.stop(smbstate) + return false, open_service_result + end + + -- Stop it + stdnse.print_debug(2, "Stopping the service") + status, control_result = svcctl_controlservice(smbstate, open_service_result['handle'], "SERVICE_CONTROL_STOP") + if(status == false) then + smb.stop(smbstate) + return false, control_result + end + + -- Wait for it to stop (TODO: Make this better) + stdnse.print_debug(2, "Waiting for the service to stop") + repeat + status, query_result = svcctl_queryservicestatus(smbstate, open_service_result['handle']) + if(status == false) then + smb.stop(smbstate) + return false, query_result + end + until query_result['service_status']['controls_accepted'][1] == nil + + -- Close the handle to the service + status, close_result = svcctl_closeservicehandle(smbstate, open_service_result['handle']) + if(status == false) then + smb.stop(smbstate) + return false, close_result + end + + -- Close the service manager + status, close_result = svcctl_closeservicehandle(smbstate, open_result['handle']) + if(status == false) then + smb.stop(smbstate) + return false, close_result + end + + smb.stop(smbstate) + + return true +end + +---Delete a service on the remote machine based on its name. I don't recommend deleting any services that +-- you didn't create. +-- +--@param host The host object. +--@param servicename The name of the service to delete. +--@return (status, err) If status is false, err is an error message; +-- otherwise, err is undefined. +function service_delete(host, servicename) + local status, smbstate, bind_result, open_result, open_service_result, delete_result, close_result + + stdnse.print_debug(1, "Deleting service: %s", servicename) + + -- Create the SMB session + status, smbstate = start_smb(host, SVCCTL_PATH) + if(status == false) then + return false, smbstate + end + + -- Bind to SVCCTL service + status, bind_result = bind(smbstate, SVCCTL_UUID, SVCCTL_VERSION, nil) + if(status == false) then + smb.stop(smbstate) + return false, bind_result + end + + -- Open the service manager + stdnse.print_debug(2, "Opening the remote service manager") + status, open_result = svcctl_openscmanagerw(smbstate, host.ip) + if(status == false) then + smb.stop(smbstate) + return false, open_result + end + + -- Get a handle to the service + stdnse.print_debug(2, "Getting a handle to the service: %s", servicename) + status, open_service_result = svcctl_openservicew(smbstate, open_result['handle'], servicename) + if(status == false) then + smb.stop(smbstate) + return false, open_service_result + end + + -- Delete the service + stdnse.print_debug(2, "Deleting the service") + status, delete_result = svcctl_deleteservice(smbstate, open_service_result['handle']) + if(status == false) then + smb.stop(smbstate) + return false, delete_result + end + + -- Close the handle to the service + status, close_result = svcctl_closeservicehandle(smbstate, open_service_result['handle']) + if(status == false) then + smb.stop(smbstate) + return false, close_result + end + + -- Close the service manager + status, close_result = svcctl_closeservicehandle(smbstate, open_result['handle']) + if(status == false) then + smb.stop(smbstate) + return false, close_result + end + + smb.stop(smbstate) + + return true +end + +---Retrieves statistical information about the given server. This function requires administrator privileges +-- to run, and is present on all Windows versions, so it's a useful way to check whether or not an account +-- is administrative. +--@param host The host object +--@return (status, data) If status is false, data is an error message; otherwise, data is a table of information +-- about the server. +function get_server_stats(host) + local stats + + -- Create the SMB session + status, smbstate = start_smb(host, SRVSVC_PATH) + if(status == false) then + return false, smbstate + end + + -- Bind to SRVSVC service + status, bind_result = bind(smbstate, SRVSVC_UUID, SRVSVC_VERSION, nil) + if(status == false) then + smb.stop(smbstate) + return false, bind_result + end + + -- Call netservergetstatistics for 'server' + status, netservergetstatistics_result = srvsvc_netservergetstatistics(smbstate, host.ip) + if(status == false) then + smb.stop(smbstate) + return false, netservergetstatistics_result + end + + -- Stop the session + smb.stop(smbstate) + + -- Build the response + local stats = netservergetstatistics_result['stat'] + + -- Convert the date to a string + stats['start_str'] = os.date("%Y-%m-%d %H:%M:%S", stats['start']) + + -- Get the period and convert it to a proper time offset + stats['period'] = os.time() - stats['start'] + if(stats['period'] > 60 * 60 * 24) then + stats['period_str'] = string.format("%dd%dh%02dm%02ds", stats['period'] / (60*60*24), (stats['period'] % (60*60*24)) / 3600, (stats['period'] % 3600) / 60, stats['period'] % 60) + elseif(stats['period'] > 60 * 60) then + stats['period_str'] = string.format("%dh%02dm%02ds", stats['period'] / 3600, (stats['period'] % 3600) / 60, stats['period'] % 60) + else + stats['period_str'] = string.format("%02dm%02ds", stats['period'] / 60, stats['period'] % 60) + end + + -- Combine the 64-bit values + stats['bytessent'] = bit.bor(bit.lshift(stats['bytessent_high'], 32), stats['bytessent_low']) + stats['bytesrcvd'] = bit.bor(bit.lshift(stats['bytesrcvd_high'], 32), stats['bytesrcvd_low']) + + -- Sidestep divide-by-zero errors (probabyl won't come up, but I'd rather be safe) + if(stats['period'] == 0) then + stats['period'] = 1 + end + + -- Get the bytes/second values + stats['bytessentpersecond'] = stats['bytessent'] / stats['period'] + stats['bytesrcvdpersecond'] = stats['bytesrcvd'] / stats['period'] + + return true, stats +end + +---Attempts to enumerate the shares on a remote system using MSRPC calls. Without a user account, +-- this will likely fail against a modern system, but will succeed against Windows 2000. +-- +--@param host The host object. +--@return Status (true or false). +--@return List of shares (if status is true) or an an error string (if status is false). +function enum_shares(host) + + local status, smbstate + local bind_result, netshareenumall_result + local shares + local i, v + + -- Create the SMB session + status, smbstate = start_smb(host, SRVSVC_PATH) + if(status == false) then + return false, smbstate + end + + -- Bind to SRVSVC service + status, bind_result = bind(smbstate, SRVSVC_UUID, SRVSVC_VERSION, nil) + if(status == false) then + smb.stop(smbstate) + return false, bind_result + end + + -- Call netsharenumall + status, netshareenumall_result = srvsvc_netshareenumall(smbstate, host.ip) + if(status == false) then + smb.stop(smbstate) + return false, netshareenumall_result + end + + -- Stop the SMB session + smb.stop(smbstate) + + -- Convert the share list to an array + shares = {} + for i, v in pairs(netshareenumall_result['ctr']['array']) do + shares[#shares + 1] = v['name'] + end + + return true, shares +end + + +---Attempts to retrieve additional information about a share. Will fail unless we have +-- administrative access. +-- +--@param host The host object. +--@return Status (true or false). +--@return A table of information about the share (if status is true) or an an error string (if +-- status is false). +function get_share_info(host, name) + local status, smbstate + local response = {} + + -- Create the SMB session + status, smbstate = start_smb(host, SRVSVC_PATH) + if(status == false) then + return false, smbstate + end + + -- Bind to SRVSVC service + status, bind_result = bind(smbstate, SRVSVC_UUID, SRVSVC_VERSION, nil) + if(status == false) then + smb.stop(smbstate) + return false, bind_result + end + + -- Call NetShareGetInfo + status, netsharegetinfo_result = srvsvc_netsharegetinfo(smbstate, host.ip, name, 2) + if(status == false) then + smb.stop(smbstate) + return false, netsharegetinfo_result + end + + smb.stop(smbstate) + + return true, netsharegetinfo_result +end + + diff --git a/nselib/msrpcperformance.lua b/nselib/msrpcperformance.lua index 95bb95a05..37c8b1823 100644 --- a/nselib/msrpcperformance.lua +++ b/nselib/msrpcperformance.lua @@ -27,11 +27,21 @@ require 'msrpctypes' -- message), and a table representing the datatype, if any. local function parse_perf_title_database(data, pos) local result = {} + local i = 1 repeat local number, name pos, number, name = bin.unpack("= #data return true, pos, result @@ -451,12 +461,12 @@ function get_performance_data(host, objects) -- Parse the title database pos = 1 status, pos, result['title_database'] = parse_perf_title_database(queryvalue_result['value'], pos) - result['title_database'][0] = "" - if(status == false) then msrpc.stop_smb(smbstate) return false, pos end + result['title_database'][0] = "" + if(objects ~= nil and #objects > 0) then -- Query for the objects diff --git a/nselib/msrpctypes.lua b/nselib/msrpctypes.lua index f5a4b4e7f..d29990a3b 100644 --- a/nselib/msrpctypes.lua +++ b/nselib/msrpctypes.lua @@ -124,7 +124,7 @@ function string_to_unicode(string, do_null) local i local result = "" - stdnse.print_debug(4, string.format("MSRPC: Entering string_to_unicode(string = %s)", string)) + stdnse.print_debug(4, "MSRPC: Entering string_to_unicode(string = %s)", string) if(do_null == nil) then do_null = false @@ -559,9 +559,40 @@ function marshall_unicode(str, do_null, max_length) return result end +--- Marshall a null-teriminated ascii string, with the length/maxlength prepended. Very similar +-- to marshall_unicode, except it's ascii and the null terminator is always used. +-- +--@param str The string to marshall. +--@param max_length [optional] The maximum length; default: actual length. +function marshall_ascii(str, max_length) + local buffer_length + local result + local padding = "" + + buffer_length = string.len(str) + 1 + + if(max_length == nil) then + max_length = buffer_length + end + + while((string.len(str .. string.char(0) .. padding) % 4) ~= 0) do + padding = padding .. string.char(0) + end + + result = bin.pack("[string,charset(UTF16)] uint16 *str -- @@ -626,6 +670,41 @@ function unmarshall_unicode_ptr(data, pos, do_null) return pos, result end +---Marshall an array of unicode strings. This is a perfect demonstration of how to use +-- marshall_array. +-- +--@param strings The array of strings to marshall +--@param do_null [optional] Appends a null to the end of the string. Default false. +--@return A string representing the marshalled data. +function marshall_unicode_array(strings, do_null) + local array = {} + local result + + for i = 1, #strings, 1 do + array[i] = {} + array[i]['func'] = marshall_ptr + array[i]['args'] = {marshall_unicode, {strings[i], do_null}, strings[i]} + end + + result = marshall_array(array) + + return result +end + +---Marshall a pointer to an array of unicode strings. See marshall_unicode_array +-- for more information. +-- +--@param strings The array of strings to marshall +--@param do_null [optional] Appends a null to the end of the string. Default false. +--@return A string representing the marshalled data. +function marshall_unicode_array_ptr(strings, do_null) + local result + + result = marshall_ptr(ALL, marshall_unicode_array, {strings, do_null}, strings) + + return result +end + --- Marshall an int64. This is simply an 8-byte integer inserted into the buffer, nothing fancy. --@param int64 The integer to insert --@return A string representing the marshalled data. @@ -1197,6 +1276,34 @@ local function unmarshall_Enum16(data, pos, table, default, pad) return pos, default end +---Marshall an entry in a table. Basically, converts the string to a number based on the entries in +-- table before sending. Multiple values can be ORed together (like flags) by separating +-- them with pipes ("|"). +-- +--@param val The value to look up. Can be multiple values with pipes between, eg, "A|B|C". +--@param table The table to use for lookups. The keys should be the names, and the values should be +-- the numbers. +--@param pad [optional] If set, will ensure that we end up on an even multiple of 4. Default: true. +--@return A string representing the marshalled data. +local function marshall_Enum8(val, table, pad) + local result = 0 + stdnse.print_debug(4, string.format("MSRPC: Entering marshall_Enum8()")) + + local vals = stdnse.strsplit("|", val) + local i + + for i = 1, #vals, 1 do + result = bit.bor(result, table[vals[i]]) + end + + result = marshall_int8(result, pad) + + stdnse.print_debug(4, string.format("MSRPC: Leaving marshall_Enum8()")) + return result +end + + + ---Similar to unmarshall_Enum32, except it'll return every value that could be ANDed together to -- create the resulting value (except a 0 value). This is effective for parsing flag data types. --@param data The data packet. @@ -2470,13 +2577,19 @@ function marshall_winreg_StringBuf(table, max_length) end end - if(name == nil) then + -- For some reason, 0-length strings are handled differently (no null terminator)... + if(name == "") then length = 0 + result = bin.pack("svcctl_ControlCode. This datatype is tied to the table above with that +-- name. +-- +--@param flags The value to marshall, as a string +--@return The marshalled integer representing the given value, or nil if it wasn't +-- found. +function marshall_svcctl_ControlCode(flags) + local result + stdnse.print_debug(4, string.format("MSRPC: Entering marshall_svcctl_ControlCode()")) + + result = marshall_Enum32(flags, svcctl_ControlCode) + + stdnse.print_debug(4, string.format("MSRPC: Leaving marshall_svcctl_ControlCode()")) + return result +end + +---Unmarshall a svcctl_ControlCode. This datatype is tied to the table with that name. +-- +--@param data The data packet. +--@param pos The position within the data. +--@return (pos, str) The new position, and the string representing the datatype. +function unmarshall_svcctl_ControlCode(data, pos) + local str + stdnse.print_debug(4, string.format("MSRPC: Entering unmarshall_svcctl_ControlCode()")) + + pos, str = unmarshall_Enum32_array(data, pos, svcctl_ControlCode) + + stdnse.print_debug(4, string.format("MSRPC: Leaving unmarshall_svcctl_ControlCode()")) + return pos, str +end + +---Convert a svcctl_ControlCode value to a string that can be shown to the user. This is +-- based on the _str table. +-- +--@param val The string value (returned by the unmarshall_ function) to convert. +--@return A string suitable for displaying to the user, or nil if it wasn't found. +function svcctl_ControlCode_tostr(val) + local result + stdnse.print_debug(4, string.format("MSRPC: Entering svcctl_ControlCode_tostr()")) + + result = svcctl_ControlCode_str[val] + + stdnse.print_debug(4, string.format("MSRPC: Leaving svcctl_ControlCode_tostr()")) + return result +end + +local svcctl_Type = +{ + SERVICE_TYPE_KERNEL_DRIVER = 0x01, + SERVICE_TYPE_FS_DRIVER = 0x02, + SERVICE_TYPE_ADAPTER = 0x04, + SERVICE_TYPE_RECOGNIZER_DRIVER = 0x08, + SERVICE_TYPE_DRIVER = 0x0B, + SERVICE_TYPE_WIN32_OWN_PROCESS = 0x10, + SERVICE_TYPE_WIN32_SHARE_PROCESS = 0x20, + SERVICE_TYPE_WIN32 = 0x30 +} + +---Marshall a svcctl_Type. This datatype is tied to the table above with that +-- name. +-- +--@param flags The value to marshall, as a string +--@return The marshalled integer representing the given value, or nil if it wasn't +-- found. +function marshall_svcctl_Type(flags) + local result + stdnse.print_debug(4, string.format("MSRPC: Entering marshall_svcctl_Type()")) + + result = marshall_Enum32(flags, svcctl_Type) + + stdnse.print_debug(4, string.format("MSRPC: Leaving marshall_svcctl_Type()")) + return result +end + +---Unmarshall a svcctl_Type. This datatype is tied to the table with that name. +-- +--@param data The data packet. +--@param pos The position within the data. +--@return (pos, str) The new position, and the string representing the datatype. +function unmarshall_svcctl_Type(data, pos) + local str + stdnse.print_debug(4, string.format("MSRPC: Entering unmarshall_svcctl_Type()")) + + pos, str = unmarshall_Enum32_array(data, pos, svcctl_Type) + + stdnse.print_debug(4, string.format("MSRPC: Leaving unmarshall_svcctl_Type()")) + return pos, str +end + +---Convert a svcctl_Type value to a string that can be shown to the user. This is +-- based on the _str table. +-- +--@param val The string value (returned by the unmarshall_ function) to convert. +--@return A string suitable for displaying to the user, or nil if it wasn't found. +function svcctl_Type_tostr(val) + local result + stdnse.print_debug(4, string.format("MSRPC: Entering svcctl_Type_tostr()")) + + result = svcctl_Type_str[val] + + stdnse.print_debug(4, string.format("MSRPC: Leaving svcctl_Type_tostr()")) + return result +end +local svcctl_State = +{ + SERVICE_STATE_ACTIVE = 0x01, + SERVICE_STATE_INACTIVE = 0x02, + SERVICE_STATE_ALL = 0x03 +} +---Marshall a svcctl_State. This datatype is tied to the table above with that +-- name. +-- +--@param flags The value to marshall, as a string +--@return The marshalled integer representing the given value, or nil if it wasn't +-- found. +function marshall_svcctl_State(flags) + local result + stdnse.print_debug(4, string.format("MSRPC: Entering marshall_svcctl_State()")) + + result = marshall_Enum32(flags, svcctl_State) + + stdnse.print_debug(4, string.format("MSRPC: Leaving marshall_svcctl_State()")) + return result +end + +---Unmarshall a svcctl_State. This datatype is tied to the table with that name. +-- +--@param data The data packet. +--@param pos The position within the data. +--@return (pos, str) The new position, and the string representing the datatype. +function unmarshall_svcctl_State(data, pos) + local str + stdnse.print_debug(4, string.format("MSRPC: Entering unmarshall_svcctl_State()")) + + pos, str = unmarshall_Enum32_array(data, pos, svcctl_State) + + stdnse.print_debug(4, string.format("MSRPC: Leaving unmarshall_svcctl_State()")) + return pos, str +end + +---Convert a svcctl_State value to a string that can be shown to the user. This is +-- based on the _str table. +-- +--@param val The string value (returned by the unmarshall_ function) to convert. +--@return A string suitable for displaying to the user, or nil if it wasn't found. +function svcctl_State_tostr(val) + local result + stdnse.print_debug(4, string.format("MSRPC: Entering svcctl_State_tostr()")) + + result = svcctl_State_str[val] + + stdnse.print_debug(4, string.format("MSRPC: Leaving svcctl_State_tostr()")) + return result +end + + +---Unmarshall a SERVICE_STATUS struct, converting it to a table. The structure is as +-- follows: +-- +-- +-- typedef struct { +-- uint32 type; +-- uint32 state; +-- uint32 controls_accepted; +-- WERROR win32_exit_code; +-- uint32 service_exit_code; +-- uint32 check_point; +-- uint32 wait_hint; +-- } SERVICE_STATUS; +-- +-- +--@param data The data packet. +--@param pos The position within the data. +--@return (pos, table) The new position, and the table of values. +function unmarshall_SERVICE_STATUS(data, pos) + local result = {} + + pos, result['type'] = unmarshall_svcctl_Type(data, pos) + pos, result['state'] = unmarshall_svcctl_State(data, pos) + pos, result['controls_accepted'] = unmarshall_svcctl_ControlCode(data, pos) + pos, result['win32_exit_code'] = unmarshall_int32(data, pos) + pos, result['service_exit_code'] = unmarshall_int32(data, pos) + pos, result['check_point'] = unmarshall_int32(data, pos) + pos, result['wait_hint'] = unmarshall_int32(data, pos) + + return pos, result +end + + + +local atsvc_DaysOfMonth = +{ + First = 0x00000001, + Second = 0x00000002, + Third = 0x00000004, + Fourth = 0x00000008, + Fifth = 0x00000010, + Sixth = 0x00000020, + Seventh = 0x00000040, + Eight = 0x00000080, + Ninth = 0x00000100, + Tenth = 0x00000200, + Eleventh = 0x00000400, + Twelfth = 0x00000800, + Thitteenth = 0x00001000, + Fourteenth = 0x00002000, + Fifteenth = 0x00004000, + Sixteenth = 0x00008000, + Seventeenth = 0x00010000, + Eighteenth = 0x00020000, + Ninteenth = 0x00040000, + Twentyth = 0x00080000, + Twentyfirst = 0x00100000, + Twentysecond = 0x00200000, + Twentythird = 0x00400000, + Twentyfourth = 0x00800000, + Twentyfifth = 0x01000000, + Twentysixth = 0x02000000, + Twentyseventh = 0x04000000, + Twentyeighth = 0x08000000, + Twentyninth = 0x10000000, + Thirtieth = 0x20000000, + Thirtyfirst = 0x40000000 +} + +---Marshall a atsvc_DaysOfMonth. This datatype is tied to the table above with that +-- name. +-- +--@param flags The value to marshall, as a string +--@return The marshalled integer representing the given value, or nil if it wasn't +-- found. +function marshall_atsvc_DaysOfMonth(flags) + local result + stdnse.print_debug(4, string.format("MSRPC: Entering marshall_atsvc_DaysOfMonth()")) + + result = marshall_Enum32(flags, atsvc_DaysOfMonth) + + stdnse.print_debug(4, string.format("MSRPC: Leaving marshall_atsvc_DaysOfMonth()")) + return result +end + + +local atsvc_Flags = +{ + JOB_RUN_PERIODICALLY = 0x01, + JOB_EXEC_ERROR = 0x02, + JOB_RUNS_TODAY = 0x04, + JOB_ADD_CURRENT_DATE = 0x08, + JOB_NONINTERACTIVE = 0x10 +} +---Marshall a atsvc_Flags. This datatype is tied to the table above with that +-- name. +-- +--@param flags The value to marshall, as a string +--@return The marshalled integer representing the given value, or nil if it wasn't +-- found. +function marshall_atsvc_Flags(flags) + local result + stdnse.print_debug(4, string.format("MSRPC: Entering marshall_atsvc_Flags()")) + + result = marshall_Enum8(flags, atsvc_Flags, false) + + stdnse.print_debug(4, string.format("MSRPC: Leaving marshall_atsvc_Flags()")) + return result +end + + +local atsvc_DaysOfWeek = +{ + DAYSOFWEEK_MONDAY = 0x01, + DAYSOFWEEK_TUESDAY = 0x02, + DAYSOFWEEK_WEDNESDAY = 0x04, + DAYSOFWEEK_THURSDAY = 0x08, + DAYSOFWEEK_FRIDAY = 0x10, + DAYSOFWEEK_SATURDAY = 0x20, + DAYSOFWEEK_SUNDAY = 0x40 +} +---Marshall a atsvc_DaysOfWeek. This datatype is tied to the table above with that +-- name. +-- +--@param flags The value to marshall, as a string +--@return The marshalled integer representing the given value, or nil if it wasn't +-- found. +function marshall_atsvc_DaysOfWeek(flags) + local result + stdnse.print_debug(4, string.format("MSRPC: Entering marshall_atsvc_DaysOfWeek()")) + + result = marshall_Enum8(flags, atsvc_DaysOfWeek, false) + + stdnse.print_debug(4, string.format("MSRPC: Leaving marshall_atsvc_DaysOfWeek()")) + return result +end + +---Marshall a JobInfo struct. The structure is as follows: +-- +-- +-- typedef struct { +-- uint32 job_time; +-- atsvc_DaysOfMonth days_of_month; +-- atsvc_DaysOfWeek days_of_week; +-- atsvc_Flags flags; +-- [string,charset(UTF16)] uint16 *command; +-- } atsvc_JobInfo; +-- +-- +--@param command The command to run. This has to be just the command, no parameters; if a +-- program requires parameters, then the best way to run it is through a batch +-- file. +--@param time The time at which to run the job, in milliseconds from midnight. +function marshall_atsvc_JobInfo(command, time) + local result = "" + + result = result .. marshall_int32(time) -- Job time + result = result .. marshall_int32(0) -- Day of month + result = result .. marshall_int8(0, false) -- Day of week +io.write("Length = " .. #result .. "\n") + result = result .. marshall_atsvc_Flags("JOB_NONINTERACTIVE") -- Flags +io.write("Length = " .. #result .. "\n\n\n") + result = result .. marshall_int16(0, false) -- Padding + result = result .. marshall_unicode_ptr(command, true) -- Command + + return result +end + diff --git a/nselib/netbios.lua b/nselib/netbios.lua index 43270af8b..bae98457b 100644 --- a/nselib/netbios.lua +++ b/nselib/netbios.lua @@ -255,10 +255,10 @@ function do_nbstat(host) local encoded_name = name_encode("*") local statistics - stdnse.print_debug(1, "Performing nbstat on host '%s'", host) + stdnse.print_debug(3, "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(3, " |_ [using cached value]") return true, nmap.registry["nbstat_names_" .. host], nmap.registry["nbstat_statistics_" .. host] end diff --git a/nselib/nsedebug.lua b/nselib/nsedebug.lua index 885ea9cf6..823a44b52 100644 --- a/nselib/nsedebug.lua +++ b/nselib/nsedebug.lua @@ -27,7 +27,7 @@ function tostr(data, indent) -- Check the type if(type(data) == "nil") then - str = str .. (" "):rep(indent) .. data .. "\n" + str = str .. (" "):rep(indent) .. "nil\n" elseif(type(data) == "string") then str = str .. (" "):rep(indent) .. data .. "\n" elseif(type(data) == "number") then diff --git a/nselib/smb.lua b/nselib/smb.lua index 85e36004a..f4f8071fe 100644 --- a/nselib/smb.lua +++ b/nselib/smb.lua @@ -1,21 +1,25 @@ ---- Server Message Block (SMB, also known as CIFS) traffic. +---Implements functionality related to Server Message Block (SMB, also known +-- as CIFS) traffic, which is a Windows protocol. -- --- SMB traffic is normally sent to/from ports 139 or 445 of Windows systems, some of them --- properly and many of them not. Samba implements it, as do many printers and other embedded --- devices. Although the protocol has been documented decently well by Samba and others, --- many 3rd party implementations are broken or make assumptions. +-- SMB traffic is normally sent to/from ports 139 or 445 of Windows systems. Other systems +-- implement SMB as well, including Samba and a lot of embedded devices. Some of them implement +-- it properly and many of them not. Although the protocol has been documented decently +-- well by Samba and others, many 3rd party implementations are broken or make assumptions. +-- Even Samba's and Windows' implementations aren't completely compatiable. As a result, +-- creating an implementation that accepts everything is a bit of a minefield. -- --- Naturally, I do the same; however, that being said, this has been tested against every --- broken implementation we could find, and will accept (or fail gracefully) against any --- bad implementations we could find. +-- Where possible, this implementation, since it's intended for scanning, will attempt to +-- accept any invalid implementations it can, and fail gracefully if it can't. This has +-- been tested against a great number of weird implementations, and it now works against +-- all of them. -- -- The intention of this library is to eventually handle all aspects of the SMB protocol. --- That being said, I'm only implementing the pieces that I find myself needing. If you +-- That being said, I'm only implementing the pieces that I (Ron Bowes) need. If you -- require something more, let me know and I'll put it on my todo list. -- --- A programmer using this library 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/logoff is this: +-- A programmer using this library should already have some knowledge of the SMB protocol, +-- although a lot isn't necessary. You can pick up a lot by looking at the code. The basic +-- login/logoff is this: -- -- -- [connect] @@ -30,6 +34,7 @@ -- S->C SMB_COM_TREE_DISCONNECT -- C->S SMB_COM_LOGOFF_ANDX -- S->C SMB_COM_LOGOFF_ANDX +-- [disconnect] -- -- -- In terms of functions here, the protocol is: @@ -61,76 +66,53 @@ -- "NT LM 0.12", which is the most commonly supported one. Among other things, the server's -- response contains the host's security level, the system time, and the computer/domain name. -- Some systems will refuse to use that protocol and return "-1" or "1" instead of 0. If that's --- detected, we kill the connection (because the protocol following will be unexpected). +-- detected, we kill the connection (because the protocol following won't work). -- -- 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 (nmap.registry[]['smbaccounts']) --- where it can be set by other scripts (for example, smb-brute.nse). However, they can also --- be passed as parameters to the function, which will override any other username/password set. +-- set when running a script, or from the registry where it can be set by other scripts (for +-- example, smb-brute.nse). 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). +-- If a username and password are set, they are used for the first login attempt. If a login fails, +-- or they weren't set, a connection as the 'GUEST' account with a blank password is attempted. If +-- that fails, then a NULL session is established, which should always work. The username/password +-- will give the highest access level, GUEST will give lower access, and NULL will give the lowest +-- (often, NULL will give no access). -- --- In terms of the login protocol, by default, we sent only NTLMv1 authentication, Lanman --- isn't sent at all. 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 for performing single scans (the major change in NTLMv2 is incorporating --- a client challenge). Lanman is somewhat insecure, though, so I don't send it at all. These options --- can, however, be overridden either through script parameters or registry settings. --- --- Lanman v1 is a fairly weak protocol, although it's still fairly difficult to break. 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 (maybe some day, I'll do an Nmap script to dump it). --- This means that if a password file is recovered, it doesn't even need to be cracked before it can be used here. +-- The actual login protocol used by SMB_COM_SESSION_SETUP_ANDX is explained in detail +-- in smbauth.lua. -- -- 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 +-- list of error codes for my constants. Although I don't believe they would be covered -- by GPL, since they're public now anyways, but I'm not a lawyer and, if somebody feels -- differently, let me know and we can sort this out. -- --- Scripts that use this module can use the script arguments --- smbusername, smbpassword, smbhash, +-- Scripts that use this module can use the script arguments listed below -- example of using these script arguments: -- --- nmap --script=smb-