Nmap Security Scanner
*Ref Guide
Security Lists
Security Tools
Site News
Advertising
About/Contact
Credits
Sponsors





Intro Reference Guide Book Install Guide
Download Changelog Zenmap GUI Docs
Bug Reports OS Detection Propaganda Related Projects
In the Movies In the News
Nmap API
Prev Chapter 9. Nmap Scripting Engine Next

Nmap API

NSE scripts have access to several Nmap facilities for writing flexible and elegant scripts. The API provides target host details such as port states and version detection results. It also offers an interface to the Nsock library for efficient network I/O.

Information Passed to a Script

An effective Nmap scripting engine requires more than just a Lua interpreter. Users need easy access to the information Nmap has learned about the target hosts. This data is passed as arguments to the NSE action method. The arguments, host and port, are Lua tables which contain information on the target against which the script is executed. The following list describes each variable in the host and port tables.

host

This table is passed as a parameter to the rule and action functions. It contains information on the operating system run by the host (if the -O switch was supplied), the IP address and the host name of the scanned target.

host.os

The os entry in the host table is an array of strings. The strings (maximally 8) are the names of the operating systems the target is possibly running. Strings are only entered in this array if the target machine is a perfect match for one or more OS database entries. If Nmap was run without the -O option, then host.os is nil.

host.ip

Contains a string representation of the IP address of the target host. If the scan was run against a host name and the reverse DNS query returned more than one IP addresses then the same IP address is used as the one chosen for the scan.

host.name

Contains the reverse DNS entry of the scanned target host represented as a string. If the host has no reverse DNS entry, the value of the field is an empty string.

host.targetname

Contains the name of the host as specified on the commandline. If the target given on the commandline contains a netmask or is an IP address the value of the field is nil.

host.directly_connected

A boolean value indicating whether or not the target host is directly connected (i.e. on the same network segment).

host.mac_addr

MAC address of the destination host (6-byte long binary string) or nil, if the host is not directly connected.

host.mac_addr_src

Our own MAC address, which was used to connect to the host (either our network card's, or (with --spoof-mac) the spoofed address).

host.interface

A string containing the interface name (dnet-style) through which packets to the host are sent.

host.bin_ip

The hosts IP as 4 byte long binary value.

host.bin_ip_src

Our hosts IP as 4 byte long binary value.

port

The port table is passed to the Lua script in the same fashion as the host table. It contains information about the port against which the script is running. If the script is run according to a host rule, then no port table is passed to the script. Port states on the target can still be requested from Nmap using the nmap.get_port_state() call.

port.number

Contains the number of the currently scanned port.

port.protocol

Defines the protocol of the port. Valid values are tcp and udp.

port.service

Contains a string representation of the service running on port.number as detected by the Nmap service detection. If the port.version field is nil then Nmap has guessed the service based only on the port number. Otherwise this field is equal to port.version.name.

port.version

This entry is a table which contains information retrieved by the Nmap version scanning engine. Some of the values (like service name, service type confidence, RPC related values) may be retrieved by Nmap even if a version scan was not required. Values which were not retrieved default to nil. The meaning of each value is given in the following table:

Table 9.1. port.version values

NameDescription
nameContains the service name Nmap will use for the port.
name_confidenceEvaluates how confident the version detection is is about the accuracy of name, from one (least confident) to 10.
product, version, extrainfo, hostname, ostype, devicetypeThese five variables are described in the versioninfo section of our version scanning documentation.
service_tunnelContains the string none or ssl based on whether or not Nmap used SSL tunnelling to detect the service.
service_fpThe service fingerprint, if any, is provided in this value. This is described in our version detection documentation
rpc_statusContains a string value of good_prog if we were able to determine the program number of an RPC service listening on the port, unknown if the port appears to be RPC but we couldn't determine the program number, not_rpc if the port doesn't appear be RPC, or untested if we haven't checked for RPC status. The rpc_program, rpc_lowver, and rpc_highver variables are nil unless rpc_status is good_prog.
rpc_program, rpc_lowver, rpc_highverThe detected RPC program number and the range of version numbers supported by that program. These will be nil if rpc_status is anything other than good_prog.

port.state

Contains information on the state of the port. Service scripts are only run against ports in the open or open|filtered states, so port.state generally contains one of those values. Other values might appear if the port table is a result of the get_port_state function. You can adjust the port state using the nmap.set_port_state() call. This is normally done when an open|filtered port is determined to be open.

Scripts also have access to some of Nmap’s functions and state variables that are exposed through functions in the nmap table.

nmap.debugging()

Returns the debugging level as a non-negative integer. The debugging level can be set with the -d option.

nmap.have_ssl()

Returns true if Nmap was compiled with SSL support, false otherwise. This can be used to avoid sending SSL probes when SSL is not available.

nmap.verbosity()

Returns the verbosity level as a non-negative integer. The verbosity level can be set with the -v option.

nmap.fetchfile(filename)

Allows access to Nmap's data files. fetchfile() searches for the specified file and returns a string containing it's path if it is found and readable (to the process). If the file is not found, not readable, or is a directory, nil is returned. The call

nmap.fetchfile("nmap-rpc")

will search for the data file nmap-rpc and, assuming it's found (which it should be), return a location like /usr/local/share/nmap/nmap-rpc.

Target Information Retrieving by a Script

Often the information passed to the script is not enough. Sometimes a script might want to correct target information or set it in the first place. The following API methods handle this.

nmap.get_port_state(host, port, protocol)

The get_port_state() call takes a host table, a port table and a protocol (tcp or udp) and returns a port table for the queried port. The host and port table are similar in structure to the ones passed to the rule and action functions. The host table should have an IP address field. The port table needs a port number and a protocol field. A call could look like this:

nmap.get_port_state({ip="127.0.0.1"}, {number="80", protocol="tcp"})

You can of course reuse the host and port tables passed to the port rule function. The purpose of this call is to be able to match scripts against more than one open port. For example if the target host has an open port 22 and a running identd server, then you can write a script which will only fire if both ports are open and there is an identification server on port 113. While it is possible to specify IP addresses different to the currently scanned target, the result will only be correct if the target is in the currently scanned group of hosts.

nmap.set_port_state(host, port, state)

The set_port_state() call takes a host table, a port table, and a port state (open or closed). With this method the final port state can be changed. This is useful when Nmap detects a port as open|filtered but the script successfully connects to it. In this case the port state can be set to open. Note that the port.state value, which was passed to the script's action function will not be changed by this call.

nmap.set_port_version(host, port, probestate)

To provide a flexible extension to Nmap's version detection NSE scripts can set the version and service variables of a port. The method takes a host and a port table as arguments. The third argument describes the state in which the script completed. It is a string which is one of: hardmatched, softmatched, nomatch, tcpwrapped, or incomplete. A hard match will almost always be used, as it means that the script was able to determine the protocol. You can pass in nomatch if the script fails to match the target port, but it is probably already set that way anyway. One of the other states should only be used if you know exactly what you are doing.

The host and port arguments to this function should either be the tables passed to the action method or they should have the same structure. The version detection fields this function looks at are name, product, version, extrainfo, hostname, ostype, devicetype, and service_tunnel. All values in this table are optional. It is possible to pass a table in which all these values are set to nil or not to set the values at all.

Various Utility Functions for Raw Packet Support

NSE has support for sending raw ethernet frames and capturing packets. The following two functions may be handy in this context:

nmap.clock_ms()

Returns a number representing the current time as milliseconds since the start of the epoch (on most systems this is 01/01/1970).

nmap.get_interface_link("interface_name")

For the provided dnet-style interface_name, nmap.get_interface_link() returns what kind of link level hardware the interface belongs. Return values are: ethernet, loopback or p2p. If the provided interface_name is not one of those types, nil is returned.

Network I/O API

To allow for efficient and parallelizable network I/O, NSE provides an interface to Nsock, the Nmap socket library. The smart callback mechanism Nsock uses is fully transparent to NSE scripts. The main benefit of Nmap-NSE sockets is that they never block on I/O operations, allowing many scripts to be run in parallel. The I/O parallelism is fully transparent to authors of NSE scripts. In NSE you can either program as if you were using a single non blocking socket or you can program as if your connection is blocking. Seemingly blocking I/O calls still return once a specified timeout has been exceeded. Two flavors of Network I/O are supported:

Connect-style network I/O

This part of the network API should be suitable for most classical network uses: Users create a socket, connect it to a remote address, send and receive data and close the socket again. Everything up to the Transport layer (which is either TCP, UDP or SSL) is handled by the library. The following socket API methods are supported:

nmap.new_socket()

The new_socket() Nmap call returns an Nmap-NSE socket object which is the recommended method for network I/O. It provides facilities to perform communication using the UDP, TCP and SSL protocol in a uniform manner.

status, error = socket_object:connect(hostid, port, [protocol])

The connect method of Nmap-NSE socket objects will put the socket in a state ready for communication. It takes as arguments a host descriptor (either an IP address or a host name), a port number and optionally a protocol. The protocol must be one of "tcp", "udp" or "ssl". By default the connect call will attempt to open a TCP connection. On success the returned value of status is true. If the connection attempt has failed, the error value contains a description of the error condition stored as a string. Those strings are taken from the gai_strerror() C function. They are (with the errorcode in parentheses):

  • “Address family for hostname not supported” (EAI_ADDRFAMILY)

  • “Temporary failure in name resolution” (EAI_AGAIN)

  • “Bad value for ai_flags” (EAI_BADFLAGS)

  • “Non-recoverable failure in name resolution” (EAI_FAIL)

  • “ai_family not supported” (EAI_FAMILY)

  • “Memory allocation failure” (EAI_MEMORY)

  • “No address associated with hostname” (EAI_NODATA)

  • “Name or service not known” (EAI_NONAME)

  • “Servname not supported for ai_socktype” (EAI_SERVICE)

  • “ai_socktype not supported” (EAI_SOCKTYPE)

  • “System error” (EAI_SYSTEM)

In addition to these standard system error based messages are the following two NSE-specific errors:

  • “Sorry, you don't have OpenSSL.” occurs if ssl is passed as third argument, but Nmap was compiled without OpenSSL support.

  • “invalid connection method” occurs if the second parameter is not one of tcp, udp, ssl.

status, error = socket_object:send(data)

The send method sends the data contained in the data string through an open connection. On success the returned value of status is true. If the send operation has failed, the error value contains a description of the error condition stored as a string. The error strings are:

  • “Trying to send through a closed socket”—if there was no call to socket_object:connect before the send operation.

  • “TIMEOUT”—if the operation took longer than the specified timeout for the socket.

  • “ERROR”—if an error occurred inside the underlying Nsock library.

  • “CANCELLED”—if the operation was cancelled.

  • “KILL”—if for example the script scan is aborted due to a faulty script.

  • “EOF”—if an EOF was read—will probably not occur for a send operation.

status, value = socket_object:receive()

The receive method does a non-blocking receive operation on an open socket. On success the returned value of status is true and the received data is stored in value. If receiving data has failed, value contains a description of the error condition stored as a string. A failure occurs for example if receive is called on a closed socket. The receive call returns to the NSE script all the data currently stored in the receive buffer of the socket. Error conditions are the same as for the send operation.

status, value = socket_object:receive_lines(n)

Tries to receive at least n lines from an open connection. A line is a string delimited with “\n” characters. If it was not possible to receive at least n lines before the operation times out a TIMEOUT error occurs. On the other hand, if more than n lines were received, all are returned, not just n. On success the returned value of status is true and the received data is stored in value. If the connection attempt has failed, value contains a description of the error condition stored as string. Error conditions are the same as for the send operation.

status, value = socket_object:receive_bytes(n)

Tries to receive at least n bytes from an open connection. On success the returned value of status is true and the received data is stored in value. If operation fails, value contains a description of the error condition stored as a string. Similarly to receive_lines() n is the minimum amount of characters we would like to receive. If more arrive, we get all of them. If less than n characters arrive before the operation times out, a TIMEOUT error occurs. Other error conditions are the same as for the send operation.

status, value = socket_object:receive_buf(func/"string", keeppattern)

receive_buf tries to circumvent several limitations in the other receive* functions. receive_line(n), for example, tries to ensure that there are at least n lines received and returns everything it has already read from the connection (even though there may be much more data than requested). It also leaves line-parsing to the user.

receive_buf on the other hand returns only the part of the received data until the first match of a delimiter, with the rest being saved inside a buffer for later calls to receive_buf. This buffer gets cleared on calls to other functions inside the Network I/O API. Should the data not contain the delimiter another read request is sent and the buffer is checked again when more data is present.

receive_buf takes two arguments. The first one is either a string or a function. If it is a string it gets passed to Lua's string.find function as the (second) pattern parameter, with the buffer data being searched. If it is a function it is expected to take exactly one parameter (the buffer) and its return values have to be like those of string.find (i.e. offsets of the start and the end of the delimiter inside the buffer, or nil, if the delimiter is not found).

The second argument is a boolean value which indicates whether the delimiting pattern should be returned along with the received data or discarded.

A module inside the nselib match.lua (the section called “Buffered Network I/O Helper Functions”) provides functions for matching received data against regular expressions or for receiving a defined number of bytes. receive_buf's return values behave exactly as the return values of the other receive* functions. Two values are returned (status,val)— the first indicating whether the request was successful, the other containing the returned data (or the case of a failure, an error message).

Possible error messages are those of the other receive* functions and, in addition, the following:

  • “Error inside splitting-function”—if the first argument was a function which caused an error while being called.

  • “Error in string.find (nsockobj:receive_buf)!”—if a string was provided as the first argument, and string.find() yielded an error while being called.

  • “Expected either a function or a string!”—if the first argument was neither a function nor a string.

  • “Delimiter has negative size!”—if the returned start offset is greater than the end offset.

status, err = socket_object:close()

Closes an open connection. On success the returned value of status is true. If the connection attempt has failed, value contains a description of the error condition stored as a string. Currently the only error message is: “Trying to close a closed socket”, which is issued if the socket has already been closed. Sockets are subject to garbage collection. Should you forget to close a socket, it will get closed before it gets deleted (on the next occasion Lua's garbage collector is run). However since garbage collection cycles are difficult to predict, it is considered good practice to close opened sockets.

status,localip,localport,remoteip,remoteport=socket_object:get_info()

This function returns information about the socket object. It returns 5 values. If an error occurred, the first value is nil and the second value describes the error condition. Otherwise the first value describes the success of the operation and the remaining 4 values describe both endpoints of the TCP connection. If you put the call in a try() statement the status value is consumed. The call can be used for example if you want to query an authentication server.

socket_object:set_timeout(t)

Sets the time, in milliseconds, after which input and output operations on a socket should time out and return. The default value is 30,000 (30 seconds). The lowest allowed value is 10ms, since this is the granularity of NSE network I/O.

Raw packet network I/O

For those cases where the connection oriented approach is too inflexible, NSE provides script developers with a more powerful option: raw packet network I/O. The greater flexibility comes, however, at the cost of a slightly more complex API. Receiving raw packets is accomplished via a wrapper around Libpcap inside the Nsock library. In order to keep the capturing efficient it works in a three tiered approach: Opening a device for capturing, registering listeners to it and receiving packets. With each call to pcap_open() you have to provide a callback function, which receives the packet (along with it's layer 2 and 3 headers) and is used to compute a so-called packet hash. Each call to pcap_register() takes a binary string as argument. For every packet captured the computed hash is matched against all registered strings. Those scripts for which the compare yields true are then provided with the packet as a return value to pcap_receive(). The more general the packet hash computing function is kept, the more scripts may receive the packet and proceed with their execution. To use the packet capturing inside your script you have to create (and afterwards close) a socket with nmap.newsocket() (or socket_object:close() respectively)—just like with the connection-based network I/O. A more detailed description of the functions for packet capturing follows:

socket_object:pcap_open(device, snaplen, promisc, test_function, bpf)

The pcap_open() call opens the socket for packet capturing. The parameters are:

  • device—the dnet-style interface name of the device you want to capture from.

  • snaplen—defines the length of each packet you want to capture (similar to the -s option to tcpdump)

  • promisc—should be set to 1 if the interface should activate promiscuous mode, and zero otherwise.

  • test_function—callback function used to compute the packet-hash

  • bpf—a string describing a Berkeley packet filter expression (like those provided to tcpdump)

socket_object:pcap_register(packet-hash)

Starts the listening for incoming packages. The provided packet-hash is a binary string which has to match the hash returned by the test_function parameter provided to pcap_open(). If you want to receive all packets, just provide the empty string (""). There has to be a call to pcap_register() before a call to pcap_receive().

status, packet_len, l2_data, l3_data = socket_object:pcap_receive()

Receives a captured packet. If successful, the return values are:

  • status—a boolean with the value true.

  • packet_len—the length of the captured packet (note, that you could have received less data if the snaplen parameter was smaller than the packet length)

  • l2_data—data from the second OSI layer (e.g. ethernet headers)

  • l3_data—data from the third OSI layer (e.g. IPv4 headers).

Should an error or timeout occur, while waiting for a packet the return values are: nil,error_message,nil,nil, where error_message describes the occurred error.

socket_object:pcap_close()

Closes the pcap device.

Receiving raw packets is a great feature, but it is also only the half job. Now for sending raw packets: To accomplish this NSE has access to a wrapper around the dnet library. Currently NSE has the ability to send raw ethernet frames via the following API:

dnet_object=nmap.new_dnet()

Creates and returns a new dnet_object, which can be used to send raw packets.

dnet_object:ethernet_open(interface_name)

Opens the interface defined by the provided interface_name for sending ethernet frames through it. An error (“device is not valid ethernet interface”) is thrown in case the provided argument is not valid.

dnet_object:ethernet_send(packet)

Sends the provided data as ethernet frame across the previously opened interface. Note that you have to provide the packet including IP header and ethernet header. If there was no previous valid call to ethernet_open() an error is thrown (“dnet is not valid opened ethernet interface”).

dnet_object:ethernet_close()

Closes the interface. The only error which may be thrown is the same as for the ethernet_send() operation.

Exception Handling

NSE provides an exception handling mechanism not present in the plain Lua language. The exception handling is tailored specifically for network I/O operations. The mechanism follows a functional programming paradigm rather than an object oriented programming paradigm. To create an exception handler the nmap.new_try() API method is used. This method returns a function, which takes a function as an argument. If the function passed as an argument raises an exception, then the script execution is aborted and no output is produced. Optionally you can pass a function to the new_try() method which will be called if an exception is caught. In this function you can perform required clean up operations.

Example 9.2, “Exception handling example” shows cleanup exception handling at work. A new function named catch is defined to simply close the newly created socket in case of an error. It is then used to protect connection and communication attempts on that socket. If no catch function is specified, execution of the script aborts without further ado—open sockets will remain open. If the verbosity level is at least one or if the scan is performed in debugging mode a description of the uncaught error condition is printed on standard output. Note that it is currently not easily possible to group several statements in one try block. It is also important to remember that if the socket is not closed it will occupy memory until the next run of Lua's garbage collector.

Example 9.2. Exception handling example

local result, socket, try, catch

result = ""
socket = nmap.new_socket()
catch = function() 
socket:close() 
end
try = nmap.newtry(catch)

try(socket:connect(host.ip, port.number))
result = try(socket:receive_lines(1));
try(socket:send(result))

Writing a function which is treated properly by the try/catch mechanism is straightforward. The function should return multiple values. The first value should be a boolean which is true upon successful completion of the function and false otherwise. If the function completed successfully the try construct consumes the indicator value and returns the remaining values. If the function failed then the second returned value must be a string describing the error condition. Note that that if the value is not nil it is treated as true so you can return your value in the normal case and return nil, error description if an error occurs.

The Registry

The registry is a normal Lua table. What is special about it is that it is visible by all scripts and it retains its state between script executions. Nmap does not scan every host specified on the command line at the same time, it puts them in smaller groups and these groups are scanned in parallel. The registry is rebuilt for every group, so information stored there is only deleted after NSE finishes processing the current target group. This implies of course that the registry is transient—it is not stored between Nmap executions. Every script can read the registry and write to it. If a script is running after another script, it can read some information in the registry which was left by the first script. This feature is particularly powerful in combination with the run level concept. A script with a higher run level can rely on entries left behind for it by scripts with lower run levels. Remember however that the registry can be written by all scripts equally, so choose the keys for your entries wisely. The registry is stored in nmap.registry. The behavior of the registry allows caching of already calculated data. The cache can be seen by all scripts until the registry is rebuilt with the next target group.


Prev Up Next
Lua Extensions Home Script Writing Tutorial
[ Nmap | Sec Tools | Mailing Lists | Site News | About/Contact | Advertising | Privacy ]