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
| Chapter 9. Nmap Scripting Engine |
  ## 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
  Name | Description
  --- | ---
  `name` | Contains the service name Nmap will use for the port.
  `name_confidence` | Evaluates how confident the version detection is is about the accuracy of `name`, from one (least confident) to 10.
  `product`, `version`, `extrainfo`, `hostname`, `ostype`, `devicetype` | These five variables are described in the versioninfo section of our version scanning documentation.
  `service_tunnel` | Contains the string `none` or `ssl` based on whether or not Nmap used SSL tunnelling to detect the service.
  `service_fp` | The service fingerprint, if any, is provided in this value. This is described in our version detection documentation
  `rpc_status` | Contains 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_highver` | The 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.
Lua Extensions |  | Script Writing Tutorial
| [ Nmap | Sec Tools | Mailing Lists | Site News | About/Contact | Advertising | Privacy ]