Working with NSE threads, condition variables, and mutexes in NSE

The Nmap Scripting Engine offers finer control over script parallelism by implementing threads, condition variables, and mutexes. Each NSE script is normally executed inside a Lua coroutine or thread but it may yield additional worker threads if the programmer decides to do so.

This recipe will teach you how to deal with parallelism in NSE.

How to do it...

NSE threads are recommended for scripts that need to perform network operations in parallel. Let's see how to deal with parallelism in our scripts:

  1. To create a new NSE thread, use the function new_thread() from the library stdnse:
    local co = stdnse.new_thread(worker_main_function, arg1, arg2, arg3, ...)
  2. To synchronize access to a network resource, create a mutex on an object:
    local my_mutex = nmap.mutex(object)
  3. Then the function returned by nmap.mutex(object) can be locked as follows:
    my_mutex("trylock")
  4. After you are done working with it, you should release it with the function "done":
    my_mutex("done")
  5. NSE supports condition variables to help you synchronize the execution of threads. To create a condition variable, use the function nmap.condvar(object):
    local o = {} 
    local my_condvar = nmap.condvar(o)
  6. After that you may wait on, signal, or broadcast the condition variable:
    my_condvar("signal")

How it works...

NSE scripts transparently yield when a network operation occurs. Script writers may want to perform parallel networking tasks, like the script http-slowloris which opens several sockets and keeps them open concurrently. NSE threads solve this problem by allowing script writers to yield parallel network operations.

The function stdnse.new_thread receives as the first argument the new worker's main function. This function will be executed after the new thread is created. Script writers may pass any additional arguments as optional parameters in stdnse.new_thread().

local co = stdnse.new_thread(worker_main_function, arg1, arg2, arg3, ...)

The worker's return values are ignored by NSE and they can't report script output. The official documentation recommends using upvalues, function parameters, or environments to report results back to the base thread.

After execution, it returns the base coroutine and a status query function. This status query function returns up to two values: the results of coroutine.status using the base coroutine and, if an error occurs, an error object.

Mutexes or mutual exclusive objects were implemented to protect resources such as NSE sockets. The following operations can be performed on a mutex:

  • lock: Locks the mutex. If the mutex is taken, the worker thread will yield and wait until it is released.
  • trylock: Attempts to lock the mutex in a non-blocking way. If the mutex is taken, it will return false. (It will not yield as in the function lock.)
  • done: Releases the mutex. Other threads can lock it after this.
  • running: This function should not be used at all other than for debugging, because it affects the thread collection of finished threads.

Condition variables were implemented to help developers coordinate the communication between threads. The following operations can be performed on a conditional variable:

  • broadcast: Resumes all threads in the condition variable queue
  • wait: Adds the current thread to the waiting queue on the condition variable
  • signal: Signals a thread from the waiting queue

To read implementations of script parallelism, I recommend that you read the source code of the NSE scripts broadcast-ping, ssl-enum-ciphers, firewall-bypass, http-slowloris, or broadcast-dhcp-discover.

There's more...

Lua provides an interesting feature called coroutines. Each coroutine has its own execution stack. The most important part is that we can suspend and resume the execution via coroutine.resume() and coroutine.yield(). The function stdnse.base() was introduced to help identify if the main script thread is still running. It returns the base coroutine of the running script.

You can learn more about coroutines from Lua's official documentation:

Debugging NSE scripts

If something unexpected happens, turn on debugging to get additional information. Nmap uses the flag -d for debugging and you can set any integer between 0 and 9:

$ nmap -p80 --script http-google-email -d4 <target>

Exception handling

The library nmap provides an exception handling mechanism for NSE scripts that is designed to help with networking I/O tasks.

The exception handling mechanism from the nmap library works as expected. We wrap the code that we want to monitor for exceptions inside a nmap.try() call. The first value returned by the function indicates the completion status. If it returns false or nil, the second returned value must be an error string. The rest of the return values in a successful execution can be set and used as you wish. The catch function defined by nmap.new_try() will execute when an exception is raised.

The following example is a code snippet of the script mysql-vuln-cve2012-2122.nse (http://nmap.org/nsedoc/scripts/mysql-vuln-cve2012-2122.html). In this script a catch function performs some simple garbage collection if a socket is left open:

local catch = function()  socket:close() end
local try = nmap.new_try(catch)
…
  try( socket:connect(host, port) )
  response = try( mysql.receiveGreeting(socket) )

The official documentation of the NSE library nmap can be found at http://nmap.org/nsedoc/lib/nmap.html.

See also

  • The Making HTTP requests to identify vulnerable Trendnet webcams recipe
  • The Sending UDP payloads by using NSE sockets recipe
  • The Exploiting a path traversal vulnerability with NSE recipe
  • The Writing a brute force script recipe
  • The Working with the web crawling library recipe
  • The Reporting vulnerabilities correctly in NSE scripts recipe
..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
52.15.80.101