[NSE] SMB2/SMB3 library and scripts smb-protocols, smb2-capabilities and smb2-security-mode

Paulino Calderon <[email protected]>
Newsgroups gmane.comp.security.nmap.devel
Message-ID <[email protected]>
Hi list,

I've been working on the NSE library for handling SMB2/SMB3 packets and although it is still a work in progress, there are some modules ready!

Besides a new version of smb.lua and obviously smb2.lua, I'm attaching some new scripts:
* smb-protocols: Lists supported SMB1/SMB2/SMB3 protocols and dialects 
* smb2-capabilities: Lists the capabilities of SMB2/SMB3 servers
* smb2-security-mode: Reads the message signing configuration in SMB2/SMB3 servers.

My plan is to keep implementing all missing features as I go adding SMB2/SMB3 support to our existing SMB scripts. As this can take me some time, I'm planning on merging these new changes in the coming days if feedback does not bring up any important issues. I've tested this against all modern versions of Windows and Samba but I've found there are some strange implementations out there! Any comments or bug reports are greatly appreciated!

smb-protocols.nse
==============
description = [[
Attempts to list the supported protocols and dialects of a SMB server.

The script attempts to initiate a connection using the dialects:
* NT LM 0.12 (SMBv1)
* 2.02       (SMBv2)
* 2.10       (SMBv2)
* 3.00       (SMBv3)
* 3.02       (SMBv3)
* 3.11       (SMBv3)

Aditionally if SMBv1 is found enabled, it will mark it as insecure.
]]

---
-- @usage nmap -p445 --script smb-protocols <target>
-- @usage nmap -p139 --script smb-protocols <target>
--
-- @output
-- | smb-protocols: 
-- |   dialects: 
-- |     NT LM 0.12 (SMBv1) [dangerous, but default]
-- |     2.02
-- |     2.10
-- |     3.00
-- |     3.02
-- |_    3.11

smb2-capabilities.nse
=================
description = [[
Attempts to list the supported capabilities in a SMBv2 server for each
 enabled dialect.

The script sends a SMB2_COM_NEGOTIATE command and parses the response
 using the SMB dialects:
* 2.02
* 2.10
* 3.00
* 3.02
* 3.11

References:
* https://msdn.microsoft.com/en-us/library/cc246561.aspx
]]

---
-- @usage nmap -p 445 --script smb2-capabilities <target>
-- @usage nmap -p 139 --script smb2-capabilities <target>
--
-- @output
-- | smb2-capabilities: 
-- |   2.02: 
-- |     Distributed File System
-- |   2.10: 
-- |     Distributed File System
-- |     Leasing
-- |     Multi-credit operations

smb2-security-mode.nse
=================
description = [[
Determines the message signing configuration in SMBv2 servers
 for all supported dialects.

The script sends a SMB2_COM_NEGOTIATE request for each SMB2/SMB3 dialect
 and parses the security mode field to determine the message signing
 configuration of the SMB server.

References:
* https://msdn.microsoft.com/en-us/library/cc246561.aspx
]]

---
-- @usage nmap -p 445 --script smb2-security-mode <target>
-- @usage nmap -p 139 --script smb2-security-mode <target>
--
-- @output
-- | smb2-security-mode: 
-- |   3.11: 
-- |_    Message signing enabled but not required

Links
======
smb2.lua: https://github.com/cldrn/nmap/blob/smbv2/nselib/smb2.lua
smb.lua: https://github.com/cldrn/nmap/blob/smbv2/nselib/smb.lua
smb-protocols: https://github.com/cldrn/nmap/blob/smbv2/scripts/smb-protocols.nse
smb2-capabilities: https://github.com/cldrn/nmap/blob/smbv2/scripts/smb2-capabilities.nse
smb2-security-mode: https://github.com/cldrn/nmap/blob/smbv2/scripts/smb2-security-mode.nse
smb2 branch: https://github.com/cldrn/nmap/tree/smbv2



Paulino Calderon Pale || @calderpwn on Twitter || http://www.calderonpale.com

_______________________________________________
Sent through the dev mailing list
https://nmap.org/mailman/listinfo/dev
Archived at http://seclists.org/nmap-dev/
smb.lua.patch (application/octet-stream, 5.9 KB)
Index: nselib/smb.lua
===================================================================
--- nselib/smb.lua	(revision 36802)
+++ nselib/smb.lua	(working copy)
@@ -136,6 +136,7 @@
 local string = require "string"
 local table = require "table"
 local unicode = require "unicode"
+local smb2 = require "smb2"
 _ENV = stdnse.module("smb", stdnse.seeall)
 
 -- These arrays are filled in with constants at the bottom of this file
@@ -942,8 +943,9 @@
   return true, header, parameters, data
 end
 
---- Sends out <code>SMB_COM_NEGOTIATE</code>, which is typically the first SMB packet sent out.
---
+---
+-- Negotiates SMBv1 connections
+-- 
 -- Sends the following:
 -- * List of known protocols
 --
@@ -974,37 +976,30 @@
 --      * 'server_challenge' A random string used for challenge/response
 --      * 'domain'           The server's primary domain or workgroup
 --      * 'server'           The server's name
-function negotiate_protocol(smb, overrides)
+-- @param smb The SMB object associated with the connection.
+-- @param overrides Overrides table.
+-- @return (status, dialect) If status is true, the negotiated dialect in human readable form is returned as the second value.
+--                            If status is false, the error message is returned.
+---
+function negotiate_v1(smb, overrides)
   local header, parameters, data
-  local pos
-  local header1, header2, header3, header4, command, status, flags, flags2, pid_high, signature, unused, pid, mid
+  local result, err
+  local pos, header1, header2, header3, header4, command, status, flags, flags2, pid_high, signature, unused, pid, uid, tid, mid
 
-  header     = smb_encode_header(smb, command_codes['SMB_COM_NEGOTIATE'], overrides)
-
+  header = smb_encode_header(smb, command_codes['SMB_COM_NEGOTIATE'], overrides)
   -- Make sure we have overrides
   overrides = overrides or {}
 
   -- Parameters are blank
   parameters = ""
+  data = bin.pack("<CzCz", 2, (overrides['dialect'] or "NT LM 0.12"), 2, "")
 
-  -- Data is a list of strings, terminated by a blank one.
-  if(overrides['dialects'] == nil) then
-    data       = bin.pack("<CzCz", 2, (overrides['dialect'] or "NT LM 0.12"), 2, "")
-  else
-    data = ""
-    for _, v in ipairs(overrides['dialects']) do
-      data = data .. bin.pack("<Cz", 2, v)
-    end
-    data = data .. bin.pack("Cz", 2, "")
-  end
-
   -- Send the negotiate request
   stdnse.debug2("SMB: Sending SMB_COM_NEGOTIATE")
-  local result, err = smb_send(smb, header, parameters, data, overrides)
+  result, err = smb_send(smb, header, parameters, data, overrides)
   if(status == false) then
     return false, err
   end
-
   -- Read the result
   status, header, parameters, data = smb_read(smb)
   if(status ~= true) then
@@ -1012,7 +1007,6 @@
   end
 
   -- Parse out the header
-  local uid, tid
   pos, header1, header2, header3, header4, command, status, flags, flags2, pid_high, signature, unused, tid, pid, uid, mid = bin.unpack("<CCCCCICSSlSSSSS", header)
 
   -- Get the protocol version
@@ -1043,6 +1037,7 @@
   end
   -- Check if the server didn't like our requested protocol
   if(smb['dialect'] ~= 0) then
+    stdnse.debug2("Server negotiated an unknown protocol (#%d) -- aborting", smb['dialect'])
     return false, string.format("Server negotiated an unknown protocol (#%d) -- aborting", smb['dialect'])
   end
 
@@ -1118,10 +1113,78 @@
     end
   end
 
-  return true
+  stdnse.debug2("SMB_COM_NEGOTIATE got status:%s", status)
+  if status == 0 then
+    return true, overrides['dialect'] or "NT LM 0.12"
+  end
 end
 
+---
+-- Wrapper function to negotiate the protocol to use in the SMB connection.
+-- By default it attempts to negotiate with using following dialects:
+-- * NT LM 12.0 (SMBv1)
+---
+function negotiate_protocol(smb, overrides)
+  local status, dialect
+  status, dialect = negotiate_v1(smb, overrides)
+  if status then
+    return true
+  else 
+    stdnse.debug1("Couldn't negotiate a SMBv1 connection:%s", dialect)
+    -- TODO: Try SMB2/SMB3 dialects if SMBv1 failed.
+    return false, string.format("Could not negotiate a connection:%s", dialect)
+  end
+end
 
+---
+-- Returns list of supported dialects for SMBv1, SMBv2 and SMBv3.
+-- @param host       The SMB host to connect to.
+-- @param overrides [optional] Overrides for various fields.
+-- @return (status, result) If status is false, result is an error message. Otherwise, result is table of dialects
+---
+function list_dialects(host, overrides)
+  local smb2_dialects = {0x0202, 0x0210, 0x0300, 0x0302, 0x0311}
+  local supported_dialects = {}
+  local status, smb1_dialects 
+  local smbstate
+
+  -- Check for SMBv1 first
+  stdnse.debug2("Checking if SMBv1 is supported")
+  status, smbstate = start(host)
+  if(status == false) then
+    return false, smbstate
+  end
+  
+  status, smb1_dialects = negotiate_v1(smbstate, overrides)
+  if status then --Add SMBv1 as a dialect
+    table.insert(supported_dialects, smb1_dialects)
+  end
+  stop(smbstate)
+  status = false -- Finish SMBv1 and close connection
+
+  -- Check SMB2 and SMB3 dialects
+  for i, dialect in pairs(smb2_dialects) do
+    local dialect_human = stdnse.tohex(dialect, {separator = ".", group = 2})
+    -- we need a clean connection for each negotiate request
+    status, smbstate = start(host)
+    if(status == false) then
+      return false, smbstate
+    end
+    stdnse.debug2("Checking if dialect '%s' is supported", dialect_human)
+    overrides['Dialects'] = {dialect}
+    status, dialect = smb2.negotiate_v2(smbstate, overrides)
+    if status then
+      stdnse.debug2("SMB2: Dialect '%s' is supported", dialect_human)
+      table.insert(supported_dialects, dialect_human)
+    end
+    --clean smb connection
+    stop(smbstate)
+    status = false
+  end 
+
+  return true, supported_dialects
+end
+
 --- This is an internal function and should not be called externally. Use
 --  the start_session() function instead.
 local function start_session_basic(smb, log_errors, overrides)
smb2-capabilities.nse (application/octet-stream, 3.5 KB) - not displayed
smb-protocols.nse (application/octet-stream, 1.8 KB) - not displayed
smb.lua (application/octet-stream, 177.4 KB) - not displayed
smb2.lua (application/octet-stream, 16.4 KB) - not displayed
smb2-security-mode.nse (application/octet-stream, 3.1 KB) - not displayed
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.