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:
- NSE SNMPv1 Library Philip Pickering (Jul 04)