Nmap Development mailing list archives

NSE SNMPv1 Library


From: "Philip Pickering" <pgpickering () gmail com>
Date: Sat, 5 Jul 2008 03:26:19 +0200

Hi,

I've written a library to
- construct snmp packets
- encode snmp data
- decode snmp data
- get values out of snmp responses

It requires the binlib I wrote recently (see previous mail).

I'll describe it and try to provide some insight into
the implementation. If you have any questions,
complaints or requests, don't hesitate to comment.

I've attached a modified version of SNMPsysdesr.nse to demonstrate
how it works. The other script, SNMPcommunityprobe.nse, is new. It
tries to find a valid community string by wildly sending snmp packets
to a host and waiting for a response with a community string. If it gets
one, it puts it into the registry and the snmp-lib will use it. The runlevels
of both scripts are set to 1 and 2, to have the probing script look for a
working community string first and later have the SNMPsysdesr script
use it.

I hope it's self-explanatory, if not, just read on. But don't be scared about
the length of it, it really goes into detail. The interesting part for most
will be the example section at the end, so better skip the rest before you
get bored :)


_Data representation and encoding_

Most of the time the SNMP data is represented by tables and values.
For example, to create a sequence of two integers, just do:
local twoIntegers = {14, 42}

To encode it you simply call snmp.encode(twoIntegers)
and you'll receive a packed binary data string, ready to be sent.

Numbers will encode to ASN.1 integers, strings to strings,
booleans set to false become ASN.1 NULL values (because
using a Lua nil value would be a problem when iterating through
a table) and tables usually result in sequences (exceptions
will follow).

Of course, if you need a sequence of sequences of integers,
it works the same:
local seqseq = { {14, 42}, {84, 28} }


_Packet creation_

Since a SNMP packet is a sequence of a few datatypes, you
could basically construct it yourself, let snmp.encode do the
data conversion and send it:

-- version, communityString, PDU are already set
local packet = {version, communityString, PDU}
local encodedPacket = snmp.encode(packet)
socket:send(encodedPacket)


Because this isn't very exciting, there is snmp.buildPacket(PDU)
to do this. (It takes the version and the community string as
optional parameters.)
If another script has put a working community string
into the registry or the user supplied one as a script-argument,
buildPacket will use it. If not, it will use "public" as default.

Constructing a PDU isn't very exciting either, therefore there
are functions to create each SNMPv1 PDU. For example,
to build a GetRequest, use:

local PDU = snmp.buildGetRequest(options, oid1, oid2, ...)

where oid can be a string with the requested oid like
"1.3.6.1.2.1.1.1.0" for the SNMPv2-MIB::sysDescr.0

I'll explain the table you get as a result later (for the interested
readers).


_Fetching results from responses_

But first I'll describe, how you get the answer after sending
your request:
Let's assume you sent your packet and got a response.

status, response = socket:receive_bytes(1)

After checking status and response you decode it with
decodedResponse = snmp.dec(response)

Now you get a table, again, consisting of a sequence
of the version, the community string and a GetResponse-PDU.

Most of the time you are only interested in the variable bindings,
therefore the SNMP library provides two functions to get them.
snmp.fetchFirst and snmp.fetchResponseValues.

If you are only interested in the first value (because you only
requested one value), snmp.fetchFirst returns exactly what you
need. Just call snmp.fetchFirst(response)

If, on the other hand, you requested a few variables,
snmp.fetchResponseValues(response) returns a table
with each value together with it's oid. They are indexed
as they come in: [1] = {val1, oid1}, [2] = {val2, oid2}, ...


I guess, that's all you need to know. If you are still interested
in the promised result of buildGetResponse(options, oid),
keep on reading:

_what a constructed PDU table looks like_

The call we talked about before will result in the following table:

PDU._snmp = 'A0'
PDU[1] = 32523
PDU[2] = 0
PDU[3] = 0
PDU[4] = {{oid, false}}

I'll explain what each entry of the table is, but backwards:

PDU[4] are the variable bindings. They consist of a sequence
of sequences. Each sequence represents one requested variable,
where the OID is the requested variable and the false is a NULL
value, since we request a value and don't want to send one (that's
the way SNMP wants it :) )

PDU[1], [2] and [3] are the request number, the error number and the
error index, nothing too interesting right now.

But what is PDU._snmp = 'A0'?
It tells snmp.encode, that this table shouldn't be encoded into a
normal sequence but rather to a special data type. In this case to
a GetRequest-PDU (which is basically a sequence, just with the A0-tag
instead of the usual sequence tag).


_Additional datatypes_

Not only does each PDU have it's own tag, but there are also a few
datatypes defined by SNMP which need special attention. For example
counters and gauges. Although they are just regular integers they have
their own tag. To create a counter, you just put the value into a table and
set its _snmp to '41', which is the tag value for counters:

local counter = {}
counter._snmp = '41'
counter[1] = 3245


Another datatype are IP addresses: they are encoded with '40' as their tag
and their data consists of four bytes. You don't have to worry, how to
handle them, just use snmp.str2ip("127.0.0.1") and it creates a table, where
_snmp is set to '40' and its content is set to the ip.


_Examples_

Create a request for the system description ("1.3.6.1.2.1.1.1.0" is the OID,
the empty table is for not setting any options):

local packet = snmp.encode(snmp.buildPacket(snmp.buildGetRequest({},
"1.3.6.1.2.1.1.1.0")))
socket:send(packet)


Get the requested value of a received snmp packet:

status, response = socket:receive_bytes(1)
local description = snmp.fetchFirst(response)


_SNMPcommunityprobe.nse_

This script, as mentioned before, tries to find the community string
by brute force.
You can supply a wordlist with the script argument snmplist.

After receiving a response it sets snmpcommunity in the registry to the found
community string. buildPacket(...) checks the registry and uses it.

Before it starts, SNMPcommunityprobe checks if snmpcommunity has already
been set or if a user supplied snmpcommunity as a script argument.



cheers,
Philip

Attachment: SNMPcommunityprobe.nse
Description:

Attachment: SNMPsysdesr.nse
Description:

Attachment: snmp.lua
Description:


_______________________________________________
Sent through the nmap-dev mailing list
http://cgi.insecure.org/mailman/listinfo/nmap-dev
Archived at http://SecLists.Org

Current thread: