Evading Detection With Nmap Part 2

Analyzing how Nmap -sV probes give your assessment away

bob van der staak
InfoSec Write-ups

--

This investigation was triggered during my last blog about Nmap. Where I mentioned how a specific URI gets sent to endpoints called nmaplowercheck. This could be a clear indication for the blue team that their application is scanned and could blow the cover for a red team investigation. This blog will uncover how the Service Probes will be a clear indication that your system is scanned.

Introduction

But first, how does Nmap service probes work? This is important information to better understand why and how you can research it yourself in the future for other ports and newer versions to come.

In the install folder of Nmap there is a file which is called nmap-service-probes. This is an important file because it holds all probes which will be performed by Nmap. Based upon three items:

  • Which ports are discovered as open
  • How aggressive is your scan is
  • If there was already a hard or soft match based on the previously performed probe on the specified port.

Let us take a look at 2 probes in the specified file.

The first one as mentioned in the provided command is the NULL based probe. This is the probe that is sent to all ports which are open. It just waits for 6 seconds and waits if the application behind the port is so nice to leak the header information on its own.

It shows the information Probe is a TCP based connection with the name NULL. Where it just sends nothing (seen at q||)

It afterwards tries to match it based on the received information. This you can see at the lines starting with match. followed by the information on which Nmap should match the results on.

The next example shows some extra information. It is a TLSv1.2 prob. Where it is specified that the Probe again is sent on TCP with the name TLSSessionReq followed by the message in hex. Important to note that the message contains random1random2random3random4. Already one extra clear indicator that Nmap is used for scanning!
a new property is included as well rarity: this indicates the rarity that that this message is sent. With rarity one being the highest. Indicating that it is the most common and rarity 9 that it is uncommon.

Important site information: By default, Nmap runs all probes when -sV is used till rarity 7. It starts with rarity 1 and the rarity keeps increasing till a hard or soft match is found!

It also specifies on which ports it will run: so this probe is sent to the following open ports:

ports 443,444,465,636,989,990,992,993,994,995,1241,1311,2252,3388,3389,4433,4444,5061,6679,6697,8443,8883,9001

Now that we know how Nmap will determine which probes to send let us investigate a server that has ports 80,443 and 3389 open.

We perform two scans by using the following commands. The big difference between both is the — version-all command. This forces Nmap to perform all Service-probe connections even when a soft match is found and even if the rarity is above 7, (so rarity 8 and 9 are also included)

nmap -sV <ip> -Pn --version-trace
nmap -sV --version-all <ip> -Pn --version-trace

Furthermore, we use the — version-trace property which shows more information about the probes being sent. It shows information about the probe being sent to which IP / port and if was successful or not.

There is a drastic amount of probes already sent with just -sV. In the case of open ports 80, 443 and 3389 it resulted in 36 probes being sent (including the 3 Null probes which are always sent when a port is open) If we compare that to running all possible probes even with high rarity these combined to 101 probes. These values can of course change based on the rarity of the service run against. As mentioned if a hard or soft match is found it will stop performing the assessment. In this case, Nmap couldn’t determine the service of 443. Resulting in the 36 probes.

Showing the amount of probes send with different settings
Showing a port of the service scope list

Now that we have the basic idea of how we can determine which service probes will be sent. Let us dive into how these could give away that an Nmap application was used.

Radom1Random2Random3random4

As mentioned above there is a TLS probe that sends a specific message. it adds for the request random1random2random3random4. This is something that you will probably never receive from a normal application.

Probe message showing a clear message random1random2random3random4 in the TCP probe request

If we look at Whireshark we can indeed see that a message was sent to the party inside an SSLv3 Client hello using these specific characters. While filtering within Whireshark searching for the random1random2random3random4 in a frame we get a hit.

The SSLv3 Client hello message is found containing the frame contains random1random2random3random4 message
frame contains random1random2random3random4

Kerberos

There is also a Kerberos message sent to the system. Which can be found by making use of the following frame contains message.

frame contains 00:00:71:6a:81:6e:30:81:6b:a1:03:02:01:05:a2:03:02:01:0a:a4:81:5e:30:5c:a0:07:03:05:00:50:80:00:10:a2:04:1b:02:4e:4d:a3:17:30:15:a0:03:02:01:00:a1:0e:30:0c:1b:06:6b:72:62:74:67:74:1b:02:4e:4d:a5:11:18:0f:31:39:37:30:30:31:30:31:30:30:30

It describes a Kerberos authentication request (AS_REQ)where the hex string is the contents of the Kerberos request packet.

The realm is identified as “NM” and the server name is “krbtgt/NM”. However, the client's name is unspecified. Checking for this information on your ports can also help you identify scans on your system.

A Kerberos message.

GIOP

Also, the GIOP protocol was sent: The CORBA General Inter-ORB Protocol (GIOP) is the standard protocol used to communicate between different CORBA systems that use different object request brokers (ORBs).

It contains a header and a body. Where in the body the message abcdef is sent. Also atypical for a normal system and again a clear indication of an Nmap scan

This can be found by making use of:

giop && frame contains abcdef

SIP

The SIP request also uses some specific takes which are also a clear indication of a strange request. The SIP call has a specific message: SIP:nm@nm and the tag is root and branch = foo. If we look into that.

SIP (Session Initiation Protocol) is a communication protocol used for setting up and managing voice or video calls over IP networks.

sip:nm@nm is a SIP URI (Uniform Resource Identifier) that specifies the destination of the SIP message. In this case, the URI consists of a username "nm" and a domain "nm".

tag=root is a parameter in the SIP message that identifies a unique identifier for the SIP message. The tag is used to match responses from servers and to indicate the dialog between SIP entities.

call-id 50000 is another parameter in the SIP message that identifies a unique identifier for the SIP call. The call-id header field is used to relate a particular SIP message to a specific call.

So, branch=foo would typically be used as part of a Via header field in an SIP message to identify the specific session to which the message belongs.

again a not default message that could clearly indicate that a Nmap scan took place:

This could have been found by using the following search parameter:

sip && frame contains "branch=foo" && frame contains "<sip:nm@nm>;tag=root" && frame contains "To: <sip:nm2@nm2>"

The probe could also have been discovered from the nmap-probe-scan file

The nmap-probe-scan file lists al probes the SIP Options call is one of them.

RDP

Strangely there is also a cookie being set on port 443. When performing an rdp request. This can be found in the rdp.lua code. The cookie name mstshash with the value nmap is also a clear indication that an Nmap scan is being performed.

and the code in the rdp.lua file containing the reference to Nmap in the cookie string:

ConnectionRequest = {
new = function(self, proto)
local o = { proto = proto }
setmetatable(o, self)
self.__index = self
return o
end,
__tostring = function(self)
local cookie = "mstshash=nmap"
local data = string.pack(">I2I2B",
0x0000, -- dst reference
0x0000, -- src reference
0x00) -- class and options
.. ("Cookie: %s\r\n"):format(cookie)
if ( self.proto ) then
data = data .. string.pack("<BBI2I4",
0x01, -- TYPE_RDP_NEG_REQ
0x00, -- flags
0x0008, -- length
self.proto -- protocol
)
end
return tostring(Packet.TPKT:new(Packet.ITUT:new(0xE0, data)))
end
},

If we look at the logs from the version trace we see that the TerminalServerCookie is being performed and that it afterward matches with the ms-wbt-server. I don’t know why it is sent on port 443 though. In the logs, it does state that it had a successful write on both 443 and 3389.

Trinity.bak

I came across another URI specific for Nmap. GET /nice%20ports%2C/Tri%6Eity.txt%2ebak HTTP/1.0

Again a clear indication that an Nmap scan has been performed.

The code

If we look at the code of Nmap. Where we can find some logic that could leak information. For example in versant.lua if a user is not specified the default Nmap user will be used.

Versant.lua

Versant = {  
-- fallback to these constants when version and user are not given
USER = "nmap",
VERSION = "8.0.2", -- Creates an instance of the Versant class
-- @param host table
-- @param port table
-- @return o new instance of Versant
new = function(self, host, port)
local o = { host = host, port = port, socket = nmap.new_socket() }
setmetatable(o, self)
self.__index = self
return o
end, -- Connects a socket to the Versant server
-- @return status true on success, false on failure
-- @return err string containing the error message if status is false
connect = function(self)
return self.socket:connect(self.host, self.port)
end, -- Closes the socket
-- @return status true on success, false on failure
-- @return err string containing the error message if status is false
close = function(self)
return self.socket:close()
end,
-- Sends command to the server
-- @param cmd string containing the command to run
-- @param arg string containing any arguments
-- @param user [optional] string containing the user name
-- @param ver [optional] string containing the version number
-- @return status true on success, false on failure
-- @return data opaque string containing the response
sendCommand = function(self, cmd, arg, user, ver)
user = user or Versant.USER
ver = ver or Versant.VERSION
arg = arg or ""
local data = stdnse.fromhex("000100000000000000020002000000010000000000000000000000000000000000010000")
.. string.pack("zzz",
cmd,
user,
ver
)
-- align to even 4 bytes
data = data .. string.rep("\0", 4 - ((#data % 4) or 0))

tns.lua

The same with Tns.lua code. In the connection string also the Nmap user will be used when no specific user is specified.

- Initiates the connection to the listener
Packet.Connect = {
CONN_STR = [[
(DESCRIPTION=(ADDRESS=(PROTOCOL=TCP)(HOST=%s)(PORT=%d))
(CONNECT_DATA=(SERVER=DEDICATED)(SERVICE_NAME=%s)(CID=
(PROGRAM=sqlplus)(HOST=%s)(USER=nmap))))]],
version = 314,
version_comp = 300,

Network

Also, a clear giveaway is that every request always contains the following network settings:

  • MSS (Maximum Segment Size): 1460
  • TCP window size: 64240
  • SACK_perm (Selective Acknowledgement Permitted): 1 (which means this option is enabled and the receiver can acknowledge multiple non-consecutive segments as well)
  • WS (Window Scaling): 128 (which means the receiver is using window scaling option and the window size should be multiplied by 2¹²⁸)

This is important information because normally the first connection request sent to a remote host uses a window size of 16K (16,384 bytes).

If there are problems it will be scaled up 4 times to eventually reach 64Kb. Starting with 64 Kb each therefore uncommon and can especially with short succession on different ports be an indicator that someone is scanning your application.

Wireshark image showing the TCP windows size and other information on SYN messages.

Conclusion:

The last blog motivated me to perform a deep dive into this tool. Interesting how much you can learn if you sit and watch what your tool is doing. Nmap has some great documentation which was a real help.

This information can be used by the Blue team to create better triggers for security alarms. And it clearly shows for red teamers that you probably should never use -sV. It just leaks too much information or you should change the probes.

I should really learn more about YARA or snort and how this could be detected for verification. In my opinion, it would really help my red teaming/malware writing knowledge. In the case of Nmap I would advise looking at the specific sends probes and writing a detection script for every unique message with a rarity below or equal to 7.

Happy testing!

If you want to discuss anything related to infosec I’m on LinkedIn: https://www.linkedin.com/in/bobvanderstaak/

Resources:

https://nmap.org/book/man.html

https://learn.microsoft.com/en-us/troubleshoot/windows-server/networking/description-tcp-features

--

--