This posting is based on the theory that if you poke a sleeping bear hard enough, it may get very exciting for a short period of time. So this is me Poking the Nessus Bear.
Tenable talks about the adaptability and customisation that is available for customers that use the Nessus product. The availability of all their plugins to be easily readable by end users apparently is enough to allow their customers to create their own plugins for their specific environment requirements. In the case that customers need additional guidance on the NASL programming language, we are provided the NASL2 reference guide that was” recently” written in 2009. I am periodically disappointed when I get the urge to actually extend my Nessus scanner with a custom script and I go searching for updated references and documented samples in the hope that Tenable was finally helping the customers by providing real support for writing custom plugins.
- Where is the documentation for the common functions/API’s used across plugins that have existed for a long period (e.g. smb*.inc)?
- Where is the community forum dedicated to discussing NASL coding, and sample sharing?
- Where is the community plugin repository so we can effectively share plugins that may be useful, but are not included in the standard product?
- Where is the real capability to produce output that can be ingested as input into other scripts and programs (aka Unix PIPEs style)?
- Where are the blog posts that walk through example plugin’s and what each part of the NASL script does?
Am I being antagonistic towards Tenable with the introduction to this blog. Yes, but remember I am trying to poke the Nessus Bear in the hope things may get exciting.
If you are a customer and have the same frustrations then let Tenable know. Yes I know they are a business, and they will focus on what is important to their customers. However, they need to know its important. One thought is if you agree some of my frustrations then you could log a support call with the subject (exactly) as “Poking the Nessus Bear”, and add any personal comments in the body. By using the same subject line, then Tenable can easily prioritise the call as a non-operational impacting call, but can also easily gather statistics on the numbers. The last thing I would advocate is causing a disruption to Tenable’s ability to support its customers.
Its very easy to post a blog entry and complain about what’s wrong without offering anything back to try improve the situation. So, in the hope to increase the NASL knowledge in the community I will attempt to document one of my custom NASL scripts in a way that may be useful as a reference to those who wish to try their hand at NASL.
The NASL script that I am going to use was created to read the registry of the scanned computers and print out the Services that are configured to run. This was to make it easier to check suspected infected computers to see if an unknown Service was configured during an infection event. Due to write permissions, you will find that many malware if they configure a new service will not try install itself in the %SYSTEMROOT%. Therefore, in the output of the script I separate the services into those services loaded outside of %SYSTEMROOT% and those installed in %SYSTEMROOT%.
Onto the meat of the script. I will skip over quickly the preamble of the script as this is one part that is defined by the available documentation on NASL writing.
script_version (“$Revision: 1.40 $”);
script_name(english:”Malware – Microsoft Windows SMB Service Enumeration”);
script_set_attribute(attribute:”synopsis”, value:”It is possible to enumerate remote services.” );
script_set_attribute(attribute:”description”, value: “This plugin implements the SvcOpenSCManager() and SvcEnumServices() calls to obtain, using the SMB protocol, the list of active and inactive services of the remote host. An attacker may use this feature to gain better knowledge of the remote host.” );
script_set_attribute(attribute:”solution”, value: “To prevent the listing of the services for being obtained, you should either have tight login restrictions, so that only trusted users can access your host, and/or you should filter incoming traffic to this port.” );
script_set_attribute(attribute:”cvss_vector”, value: “CVSS2#AV:N/AC:H/Au:N/C:P/I:N/A:N” );
script_set_attribute(attribute:”plugin_publication_date”, value: “2011/05/26”);
script_cvs_date(“$Date: 2011/05/26 18:32:20 $”);
script_summary(english:”Enumerates the list of remote services”);
script_copyright(english:”This script is Copyright (C) 2000-2011 Tenable Network Security, Inc.”);
script_dependencies(“netbios_name_get.nasl”, “smb_login.nasl”, “smb_registry_access.nasl”);
script_require_keys(“SMB/transport”, “SMB/name”, “SMB/login”, “SMB/password”, “SMB/registry_access”);
Some quick points on the preamble. Unlike what is documented, instead of using a script_id in the 50000 range I have added one above 95000. This is a good indication of how out of date the NASL documentation is. There are more than 50000 Tenable supplied plug-ins now. It would be nice to see the script_id keyspace to be increased by a power of 10. Then we can have something like 0-500,000 reserved for Tenable, 500,000-900,000 for community shared script_id’s, and 900,000+ private use only.
Some of the other attributes I did not change or update from the plug-in script I used as a base. In regards to the script_require_keys, this sets the required KB items to run in optimized mode. The problem is there is no overarching description on what the KnowledgeBase is, how it is expected to be used, and what are the naming conventions. If there are standard KB keys used, then when writing new scripts we can make sure we don’t pollute the KB namespace with similar/duplicate keys. The more the community shares NASL scripts, the bigger the issue this would be. This leads to another question. What exactly is optimized mode?
All other parts are documented in the NASL2 reference.
In the next section of my script I include smb_func.inc. This is a file that references a number of other files that define useful SMB protocol functions. These SMB functions are used for checks, and setting variables before starting the core of the script. Most of the functions are named so it is easy to understand what their purpose is. In order, my script will set the “port” variable, check if remote machine is Samba based, set the “name” variable to the SMB machine name, set the “login” and “password” variables and Windows domain to the “dom” variable. Most of the SMB functions used are actually defined in the “smb_internals.inc” file, which is included by “smb_func.inc”.
(NOTE: you will have to have a professional feed to have access to the *.inc function/api files I discuss in this post)
Interestingly, while the included SMB functions are loaded by including the “smb_func.inc” file, all the KB items that are used in the functions (e.g. SMB/domain) are set when “netbios_name_get.nasl” and “smb_login.nasl” are initially run by setting the script_dependencies in the preamble above.
port = kb_smb_transport();
if(!port)port = 139;
# Does not work against Samba
smb = get_kb_item(“SMB/samba”);
name = kb_smb_name();
login = kb_smb_login();
pass = kb_smb_password();
if(!login)login = “”;
if(!pass) pass = “”;
dom = kb_smb_domain();
The next section of the script opens a socket to the tcp port that was set using “port = kb_smb_transport();” the open_sock_tcp function is described in the NASL2 reference document. The “session_init” and “NetUseAdd” functions are defined when “smb_func.inc” is included in the script earlier. Specifically the “session_init” function is defined in the file “smb_internals.inc” and the “NetUseAdd” function is defined in “smb_net.inc”.The purpose of this section of the script is to establish a SMB session to the target host.
soc = open_sock_tcp(port);
ret = NetUseAdd (login:login, password:pass, domain:dom, share:”IPC$”);
if (ret != 1)
In the next section of the script we establish a connecting to SC Manager so that we can pull down a list of ACTIVE services running. The list of active services is then recorded into an array called “active_list”. The functions used to open a connection to the SC manager and then enumerate the service were defined when “smb_func.inc” was included. The “OpenSCManager” and “EnumServicesStatus” are specifically defined in “smb_svc.inc” file.
handle = OpenSCManager (access_mode:SC_MANAGER_ENUMERATE_SERVICE);
if (isnull (handle))
active_list = EnumServicesStatus (handle:handle, type:SERVICE_WIN32, state:SERVICE_ACTIVE);
if (isnull (active_list))
exit (1, “No services were detected.”);
This is the last bit of code used to prepare the main foreach loop that does most of the work. First we set up 4 variables that will be used later in the script. The “services” variable will be used at the end to hold the results we want printed out as a “security_note”. The variables “active_services” and “non_sys32_services” respectively will be used to hold services found in System32 directory and those outside. I should rename “active_services” as the name was set as part of the evolution of the script development and I did not get around to change it to be more reflective of its current purpose. Finally we have a variable “is_sys32_dir” that will be used in the foreach loop to determine if the service details have components outside the System32 directory.
In the second part of the following section of code we set a variable that is used to reference an open registry connection to the LOCAL_MACHINE hive of the remote machine. The “RegConnectRegistry” function was defined with the inclusion of “smb_func.inc” and its definition is specifically found in the “smb_reg.inc” file.
services = NULL;
active_services = NULL;
non_sys32_services = NULL;
is_sys32_dir = 0;
#Connect to the remote registry using the open SMB Session
hklm = RegConnectRegistry(hkey:HKEY_LOCAL_MACHINE);
if ( isnull(hklm) )
We are now at the core of the script were we loop across each ACTIVE service that was recorded in the array “active_list” earlier in the code. The variable “temp_services” will be used to hold output information in the loop that will be appended to either the “active_services” or “non_sys32_services” variables for final script output. We set the is_sys32_dir to zero at the start of each parse through the loop. The “GetService” function, which is defined in the file “smb_svc.inc”, returns an array of information on a given service which is stored in the array “parse” for the script. We only use the first two elements of the returned array from “GetService”, with the first index (0) being the service name from the SC manager, and the second index (1) the returned service description.
The other variables are used for the registry lookups. The “regdll” and “Imagenm” variables are used to hold the returned values from the registry queries the script does, and the key1/2 and item1/2 define the specific registry keys and values that we want queried.
foreach elem (active_list)
temp_services = NULL;
is_sys32_dir = 0;
parse = GetService (service:elem);
regdll = NULL;
Imagenm = NULL;
temp_services = parse + ” [ ” + parse + ‘ ] ‘;
key1 = “SYSTEM\CurrentControlSet\Services\” + parse ;
item1 = “ImagePath”;
key2 = “SYSTEM\CurrentControlSet\Services\” + parse +”\Parameters”;
item2 = “ServiceDll”;
This is where we finally query the remote machine for the registry details for the active services. We open two of the registry keys as defined in key1 and key2, and then query for the respective values. The key registry functions “RegOpenKey” and “RegQueryValue” are defined in the file “smb_reg.inc”.
In the first registry query we are looking for the queried services ImagePath. This is the executable that holds the service code. If the ImagePath contains “ystem32” (i.e. System32 service), then we set the “is_sys32_dir”. Similarly in the second registry query the active service is checked to see if there is an associated ServiceDll. If a ServiceDll is found, then it is checked to determine if the path of the DLL is within the System32 directory or not.
key_h = RegOpenKey(handle:hklm, key:key1, mode:MAXIMUM_ALLOWED);
if ( ! isnull(key_h) )
Imagenm = RegQueryValue(handle:key_h, item:item1);
if (!isnull (Imagenm)) temp_services += ‘\n\t’ + “ImagePath:” + Imagenm;
if (“ystem32” >< Imagenm) is_sys32_dir = 1;
key_h = RegOpenKey(handle:hklm, key:key2, mode:MAXIMUM_ALLOWED);
if ( ! isnull(key_h) )
regdll = RegQueryValue(handle:key_h, item:item2);
if (!isnull (regdll)) temp_services += ‘\n\t’ + “ServiceDLL:” + regdll;
# if imagename has not been set and regdll is in system32
if ((‘ystem32’ >< regdll) && !Imagenm)
is_sys32_dir = 1;
# Else if ImageName is set, and regdll not in system32 unset
if ( !isnull(regdll) && !(‘ystem32’ >< regdll))
is_sys32_dir = 0;
This is the last part of the main foreach loop where we assign what we held in the variable “temp_services” to the variables “non_sys32_services” or “active_services” depending on if the “is_sys32_dir” was set or not. The reason we keep the services separate is so that we can print them out separately depending on their location.
I also have created two new KB items that will record that the service as being active and the service display_name. This is where it would be nice to have some guidance and structure around the KB namespace. I am sure as people write their own scripts and create KB’s, that referencing those KB’s in other scripts would be helpful.
temp_services += ‘\n\n’;
if ( is_sys32_dir == 0 )
non_sys32_services += temp_services;
active_services += temp_services;
set_kb_item(name:”SMB/svc/” + parse, value:SERVICE_ACTIVE);
set_kb_item(name:”SMB/svc/” + parse + “/display_name”, value:parse);
# end of foreach elem (active_list)
And finally we get to the end of the script. Here the “services” variable is set up ready to print the output to for the nessus report using a “security_note” at the end. As per the NASL2 reference document, a security_note is used to report miscellaneous information. Also, a new KB is created that will dump a list of all the active services and their registry details.
if (max_index(active_list) > 0 )
services += ‘\nActive NON System32 Services :\n\n’ + non_sys32_services;
services += ‘\n\nActive System32 Services :\n\n’ + active_services;
fullsrv = active_services + non_sys32_services;
security_note(extra: services, port:port);
#end of script
I have been thinking that in the future I may be able to define a variable based on a policy file. Then depending on the variable I can modify the layout of the information printed by the security_note(). If I want a script to parse output at scan completion, then I could have a variable set and maybe print out a common separated list of services and details, or a name=value pair. What would be even better is if there was another function to store machine parsable output in the Nessus core library. This may mean in the final .nessus report we would only have to look for our section, or potentially we can call nessus from the command line with an option to only print the machine parsable output.