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
Script Writing Tutorial
| Chapter 9. Nmap Scripting Engine |
## Script Writing Tutorial Suppose that you are convinced of the power of NSE. How do you go about writing your own script? Let's say that you want to extract information from an identification server. Nmap used to have this functionality but it was removed because of inconsistencies in the code base. Fortunately, the protocol identd uses is pretty simple. Unfortunately, it is too complicated to be expressible in Nmap's version detection language. Let's look at how the identification protocol works. First you connect to the identification server. Next you send a query of the form ``port-on-server`, `port-on-client`` terminated with a new line character. The server should then respond with a string of the form ``port-on-server`, `port-on-client`:`response-type`:`address-information``. In case of an error the address information is omitted. This description is sufficient for our purposes, for more details refer to RFC 1413 The protocol cannot be modeled in Nmap's version detection language for two reasons. The first is that you need to know both the local and the remote port of a connection. Version detection does not provide this data. The second, more severe obstacle, is that you need two open connections to the target—one to the identification server and one to the port you want to query. Both obstacles are easily overcome with NSE. The anatomy of a script is described in the section called “Script Format” In this section we will show how the described structure is utilized. ### The Head The head of the script is essentially its meta information. This includes the fields `id`, `description`, `author`, `license` and `categories`. We are not going to change the run level for now. The `id` of a script should uniquely identify it. If it is absent, the path to the script will be used as an id. We recommend to choose an id which concisely identifies the purpose of the script, since the ID is printed before the script's results in Nmap output.
    id = "Service Owner"
The description field should contain a sentence or two describing what the script does. If anything about the script results might confuse or mislead users, and you can't eliminate the issue by improving the script or results text, it should be documented in the `description` string.
    description = "Opens a connection to the scanned port, opens a connection to \
    port 113, queries the owner of the service on the scanned port and prints it."
Users must tell the Lua interpreter that the string continues on the following line by ending the line with a backslash (‘`\`’). They must also decide what categories the script belongs to. This script is a good example of a script which cannot be categorized clearly. It is `safe` because we are not using the service for anything it was not intended for. On the other hand, it is `intrusive` because we connect to a service on the target and therefore potentially give out information about us. To solve this dilemma we will place our script in two categories:
    categories = {"safe", "intrusive"}
### The Rule The rule section is a Lua method which decides when the script's action should be performed and when it should be skipped. Usually this decision is based on the host and port information passed to the rule function. In the case of the identification script it is slightly more complicated than that. To decide whether to run the identification script on a given port we need to know if there is an identification server running on the target machine. Or more formally: the script should be run if (and only if) the currently scanned TCP port is open and TCP port 113 is also open. For now we will rely on the fact that identification servers listen on TCP port 113. Unfortunately NSE only gives us information about the currently scanned port. To find out if port 113 is open we are going to use the `nmap.get_port_state()` method. If the identd port was not scanned, the `get_port_state` function returns `nil`. So we need to make sure that the table is not `nil`. We also check if both ports are in the `open` state. If this is the case, the action is executed, otherwise we skip the action.
    portrule = function(host, port)
    local identd, decision

    local ident_port = { number=113, protocol="tcp" }
    identd = nmap.get_port_state(host, ident_port)

    if
     identd ~= nil and identd.state == "open" and port.state == "open"
    then
     decision = true
    else
     decision = false
    end

    return decision
    end
This rule is almost correct, but still slightly buggy. Can you find the bug? It is a pretty subtle one. The problem is that this script fires on any kind of open port, TCP or UDP. The `connect()` method on the other hand assumes a TCP protocol unless it is explicitly told to use another protocol. Since the identification service is only defined for TCP connections, we need to narrow down the range of ports which fire our script. Our new rule only runs the script if the port is open, we are looking at a TCP port, and TCP port 113 is open. Writing the new and improved port rule is left as an exercise to the reader (or peek at the script in the latest Nmap distribution). ### The Mechanism At last we implement the actual functionality. The script will first connect to the port on which we expect to find the identification server, then it will connect to the port we want information about. Afterward we construct a query string and parse the response. If we received a satisfactory response, we return the retrieved information. First we need to create two socket objects. These objects represent the sockets we are going to use. By using object methods like `PrivoxyWindowOpen()`, `close()`, `send()` or `receive()` we can operate on the network socket. To avoid excessive error checking code we use NSE's exception handling mechanism. We create a function which will be executed if an error occurs and call this function `catch`. Using this function we generate a `try` function. The `try` function will call the `catch` function whenever there is an error condition in the tried block. Note that we could have ignored the last two return values of `client_service:get_info()` like this:
    local localip, localport  = client_service:get_info()
This would have sufficed because we know that the remote port is stored in `port.number`. In this example we prefer not to tell the user if the query resulted in an error. To inform users of failed identification queries, simply uncomment the corresponding line. It is necessary that we assign the variable `owner` a `nil` value because returning `nil` is the only way to tell the script engine to suppress script output.
    action = function(host, port)
    local owner = ""

    local client_ident = nmap.new_socket()
    local client_service = nmap.new_socket()

    local catch = function()
      client_ident:close()
      client_service:close()
    end

    local try = nmap.newtry(catch)

    try(client_ident:connect(host.ip, 113))
    try(client_service:connect(host.ip, port.number))

    local localip, localport, remoteip,
    remoteport = client_service:get_info()

    local request = port.number .. ", " .. localport .. "\n"

    try(client_ident:send(request))

    owner = try(client_ident:receive_lines(1))

    if string.match(owner, "ERROR") then
      owner = nil
      --  owner = "Service owner could not be determined: " .. owner
    else
     owner = string.match(owner, "USERID : .+ : (.+)\n", 1)
    end

    try(client_ident:close())
    try(client_service:close())

    return owner
    end
Nmap API |  | Version Detection using NSE
| [ Nmap | Sec Tools | Mailing Lists | Site News | About/Contact | Advertising | Privacy ]