--- [ Nmap/Ncrack ] ---





Initial Draft

Core Engine

Command Line

Status Reports 2009

Status Reports 2010

GSoC/Ncrack presentation

Ncrack Developer's Guide

Network Exploitation with Ncrack

Network Exploitation with Ncrack video at AthCon2011

  --- [ Ncrack engine ] ---

  A brief overview of Ncrack's architecture.
  Ncrack is based on a modularized architecture, where each protocol/service
  corresponds to the equivalent module that handles all the authentication
  steps. Ncrack's architecture is thus built in a way so that a module is
  separated as much as possible from the more low level details of timing and
  connection management which are handled by the core engine. 
  Ncrack utilizes the venerable Nsock, a library which was written by Fyodor a long time
  ago and has been refined and tested thoroughly throughout all the years
  of Nmap development. Nsock is a parallel sockets library which internally uses
  select(2) to poll through the registered socket descriptors and which upon a
  new network event (read/write/timeout etc) jumps to a preregistered callback
  handler which is responsible for doing something about that particular event.
  This is more or less an event-driven API.
  Ncrack's core engine resides in ncrack.cc which includes definitions for all
  these callback handlers:
  void ncrack_connect_handler(nsock_pool nsp, nsock_event nse, void *mydata);
  void ncrack_write_handler(nsock_pool nsp, nsock_event nse, void *mydata);
  void ncrack_read_handler(nsock_pool nsp, nsock_event nse, void *mydata);
  void ncrack_timer_handler(nsock_pool nsp, nsock_event nse, void *mydata);
  void ncrack_module_end(nsock_pool nsp, void *mydata);
  void ncrack_connection_end(nsock_pool nsp, void *mydata);
  After main() finishes parsing the user's host specifications and options, it
  stores all the information it needs inside a ServiceGroup object
  --- ServiceGroup / Service ---
  The ServiceGroup object holds all services (which are actually Service
  objects) that are going to be cracked. The Service (Service.h) class consists
  of variables that hold timing and statistical information, user-specified
  options that apply to that particular service/host, a pointer to a Target object
  (that is nearly the same as the known Target class from Nmap - only stripped of
  some unneeded fields like those that are related to network interfaces),
  functions that handle the username/password list iteration and a list of the
  active connections taking place at that moment.
  --- Connection ---
  A connection (Connection.h) is an instance that holds information pertaining to
  that particular TCP session between us and
  the service we are trying to crack. A connection must always belong to a Service
  class for obvious reasons. Usually, during a connection more than one
  authentication attempts are going to be carried out, depending on the service.
  --- module state machine ---
  The most important thing about a connection, is the 'state' it currently is in.
  The 'state' actually describes a specific step of the authentication procedure
  that is needed by the service we are cracking. Thus the number and names of
  states are defined in each module separately. For example, the authentication
  step, where at the beginning of a connection we need to wait and read the initial
  banner of the service, is specified by a particular 'state'. Another example, is
  the 'state' where we just need to write the password on the wire or the 'state'
  where in a service like telnet we have to make the option negotiation. It is
  pretty important that each 'state' performs a micro-action of the authentication
  procedure which will usually involve a certain nsock event to be registered.
  Of course, this might not be always possible to happen (see telnet).
  Ncrack core engine
  The main nsock loop resides in function ncrack() @ ncrack.cc and it is
  responsible for polling for new events that result in calling back one of the
  registered handlers mentioned above. ncrack_probes() is called in the end of
  every loop iteration and checks if it can initiate new connections against any
  one of the targets in ServiceGroup.
  To understand how ncrack_probes() work, we first need to see the way that
  ServiceGroup handles its services lists. In the beginning, every user-specified
  service is stored inside the 'services_active' list. For every service residing
  there, we can automatically initiate a new connection at will. ServiceGroup
  keeps a lot more additional lists which hold services that for one reason or
  another cannot perform additional actions (like start a new connection) except
  for the connections having already started. For example, 'services_full' holds
  all services that cannot start another connection due to the fact that the total
  number of active connections taking place at that moment has reached the maximum
  allowed limit (connection limit). The list 'services_wait' keeps all services
  that need to wait a time of 'connection_delay' (usually specified by the user)
  before they can send another connection probe. The list 'services_finished'
  keeps all services that have been marked as finished, either because a critical
  error has occcured (like we got an RST at the first connection attempt or we
  kept getting too many timeouts for a prolonged time) or the username/password
  list iteration finished. The notion of keeping separate lists whose name imply
  the reason that the elements of the list are there, is also used (although to a
  lesser and simpler extent) by Nmap's service scanning engine.
  --- ncrack_probes() ---
  ncrack_probes() iterates the ServiceGroup 'services_active' list, initiating new
  connections until every service has been moved inside a different ServiceGroup
  list. Note that it doesn't wait for any connection to actually finish the 3way
  handshake, since nsock uses non-blocking connect(2)s and ncrack only needs to
  register the event and the callback handler (ncrack_connect_handler).
  --- ncrack_connect_handler() ---
  Upon connection success ncrack_connect_handler() gives control to call_module()
  which calls the service module function corresponding to the particular service
  this connection is up against. If the connection times out or we get an RST and
  this is our first connection attempt, then we mark the service as 'dead' moving
  it to 'services_finished' list. This is particularly useful when the user
  specifies the targets in a wildmask or netmask notation, blindly searching for
  services to crack. It is very probable that some hosts will not even have that
  service listening and thus we will cease trying to crack them. It is important
  to note that this first connection probe (boolean 'just_started' @ Service
  class) also collects valuable timing information like how many authentication
  attempts the server allows to make per connection. That is why ncrack doesn't
  open more than 1 connection probes against a service before that first timing
  probe finishes its job (which will entail exhausting all allowed authentication
  attempts during that connection).
  --- ncrack_write_handler() ---
  The write handler is probably the simplest one. The only thing it needs to do is
  check the nsock return status and report on us in case of error. The case of a
  write failing is the most improbable one. It can happen though in case we write
  on a closed socket (which won't normally happen since we always check if the
  socket is still active or the peer closed on us) or if the kernel's socket
  buffers are full (which can only occur on very old systems with a small amount
  of RAM).
  --- ncrack_read_handler() ---
  The read handler is responsible for filling in the Connection's auxiliary
  buffer upon a successful nsock read event. We also use a trick to check whether
  or not the peer is still active or it has sent us a FIN closing the connection.
  Whenever the boolean 'check_closed' is true, if nsock produces a TIMEOUT instead
  of an EOF error, then it means we are still online. This happens because the
  caller that wants to check the connection state, registers a read event with a
  very small timeout. This is a hack that allows us to check in a portable way if
  we have moved to the CLOSE_WAIT state from ESTABLISHED. 
  --- ncrack_module_end() ---
  This function should be called by a module whenever it knows that it has
  completely finished an authentication attempt. It updates statistical variables
  for the service, like how many attempts in total have been made and also
  currently implements part of the dynamic timing engine. Every 500 msecs it
  checks whether the current authentication rate is less than the last calculated
  one and takes appropriate steps to increase it. Since the 'ideal_parallelism'
  variable which is the dominating connection metric can change, we also check if
  we can move our service from 'services_full' to 'services_active' and call
  ncrack_probes() to potentially initiate new connections. Finally, if we need to
  check if our peer is alive (variable 'peer_alive' is false), we do the read
  timeout trick mentioned above.
  --- ncrack_connection_end() ---
  One of the most complex functions. It takes all necessary actions whenever a
  connection is ended - either normally or by an error. Firstly, it checks if
  we received a FIN from our peer, in which case one of the following could have
  i) The peer might have closed on us 'unexpectedly': this happens with services
  like telnet that can close the connection immediately after giving the final
  results of the last authentication attempt. For services like these we need to
  always set the variable 'peer_might_close' inside the module immediately after
  the state that is responsible for writing the password on the wire and before
  the state that registers the next read call. If we are the first 'timing' probe
  then we increase the number of supported authentication attempts per connection
  for this service.
  ii) The peer might have closed on us normally in which case we don't do
  iii) The peer might have closed on us in the middle of the authentication. This
  shouldn't normally happen and it is an indication of a really strange error,
  usually due to extreme network conditions.
  If the above or just a timeout in the middle of the authentication happens, then
  we adapt the dynamic timing engine to drop the 'ideal_parallelism' limit. 
  Next, if we are the first timing probe, depending on the timing template, we
  calculate our initial ideal parallelism.
  We also update the authentication rate meters accordingly.
  In the end of the function we also call ncrack_probes() since we might have
  changed 'ideal_parallelism'.