CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutSign UpSign In
rapid7

CoCalc provides the best real-time collaborative environment for Jupyter Notebooks, LaTeX documents, and SageMath, scalable from individual users to large groups and classes!

GitHub Repository: rapid7/metasploit-framework
Path: blob/master/lib/snmp/ber.rb
Views: 1904
1
#
2
# Copyright (c) 2004 David R. Halliday
3
# All rights reserved.
4
#
5
# This SNMP library is free software. Redistribution is permitted under the
6
# same terms and conditions as the standard Ruby distribution. See the
7
# COPYING file in the Ruby distribution for details.
8
#
9
10
#
11
# Add ord method to Integer for forward compatibility with Ruby 1.9
12
#
13
if "a"[0].kind_of? Integer
14
unless Integer.methods.include? :ord
15
class Integer
16
def ord; self; end
17
end
18
end
19
end
20
21
#
22
# This module implements methods for encoding and decoding SNMP packets
23
# using the ASN.1 BER (Basic Encoding Rules).
24
#
25
module SNMP
26
module BER #:nodoc:all
27
28
# SNMP version codes
29
SNMP_V1 = 0
30
SNMP_V2C = 1
31
SNMP_V3 = 3 # not supported
32
33
# SNMP context-specific data types
34
# See RFC 1157 for SNMPv1
35
# See RFC 1905 for SNMPv2c
36
GetRequest_PDU_TAG = 0xa0
37
GetNextRequest_PDU_TAG = 0xa1
38
Response_PDU_TAG = 0xa2
39
SetRequest_PDU_TAG = 0xa3
40
SNMPv1_Trap_PDU_TAG = 0xa4 # Note: valid for SNMPv1 only
41
GetBulkRequest_PDU_TAG = 0xa5
42
InformRequest_PDU_TAG = 0xa6
43
SNMPv2_Trap_PDU_TAG = 0xa7
44
Report_PDU_TAG = 0xa8 # Note: Usage not defined - not supported
45
46
# Primitive ASN.1 data types
47
INTEGER_TAG = 0x02
48
OCTET_STRING_TAG = 0x04
49
NULL_TAG = 0x05
50
OBJECT_IDENTIFIER_TAG = 0x06
51
52
# Constructed ASN.1 data type
53
SEQUENCE_TAG = 0x30
54
55
# SNMP application data types
56
# See RFC 1155 for SNMPv1
57
# See RFC 1902 for SNMPv2c
58
IpAddress_TAG = 0x40
59
Counter32_TAG = 0x41 # Counter in SNMPv1
60
Gauge32_TAG = 0x42 # Gauge in SNMPv1
61
Unsigned32_TAG = 0x42 # Note: same as Gauge32
62
TimeTicks_TAG = 0x43
63
Opaque_TAG = 0x44
64
Counter64_TAG = 0x46
65
66
# VarBind response exceptions
67
NoSuchObject_TAG = 0x80
68
NoSuchInstance_TAG = 0x81
69
EndOfMibView_TAG = 0x82
70
71
# Exceptions thrown in this module
72
class OutOfData < RuntimeError; end
73
class InvalidLength < RuntimeError; end
74
class InvalidTag < RuntimeError; end
75
class InvalidObjectId < RuntimeError; end
76
77
def assert_no_remainder(remainder)
78
raise ParseError, remainder.inspect if (remainder and remainder != "")
79
end
80
81
#
82
# Decode tag-length-value data. The data is assumed to be a string of
83
# bytes in network byte order. This format is returned by Socket#recv.
84
#
85
# Returns a tuple containing the tag, the value, and any remaining
86
# unprocessed data.
87
#
88
# The data is not interpreted by this method. Use one of the other
89
# decoding methods to interpret the data.
90
#
91
# Note that ASN.1 supports an indefinite length format where the end of
92
# content is marked by a pair of 0 octets. SNMP does not support this
93
# format, so only the two definite forms are implemented (single byte and
94
# multi-byte).
95
#
96
def decode_tlv(data)
97
raise OutOfData if (data.length == 2 && data[1].ord != 0) || data.length < 2
98
tag = data[0].ord
99
length = data[1].ord
100
if length < 0x80
101
value = data[2, length]
102
remainder = data[length+2..-1]
103
else
104
# ASN.1 says this octet can't be 0xff
105
raise InvalidLength, length.to_s if length == 0xff
106
num_octets = length & 0x7f
107
length = build_integer(data, 2, num_octets)
108
value = data[num_octets+2, length]
109
remainder = data[num_octets+2+length..-1]
110
end
111
return tag, value, remainder
112
end
113
114
#
115
# Decode TLV data for an ASN.1 integer.
116
#
117
# Throws an InvalidTag exception if the tag is incorrect.
118
#
119
# Returns a tuple containing an integer and any remaining unprocessed data.
120
#
121
def decode_integer(data)
122
tag, value, remainder = decode_tlv(data)
123
raise InvalidTag, tag.to_s if tag != INTEGER_TAG
124
return decode_integer_value(value), remainder
125
end
126
127
def decode_timeticks(data)
128
tag, value, remainder = decode_tlv(data)
129
raise InvalidTag, tag.to_s if tag != TimeTicks_TAG
130
return decode_uinteger_value(value), remainder
131
end
132
133
def decode_integer_value(value)
134
result = build_integer(value, 0, value.length)
135
if value[0].ord[7] == 1
136
result -= (1 << (8 * value.length))
137
end
138
result
139
end
140
141
##
142
# Decode an integer, ignoring the sign bit. Some agents insist on
143
# encoding 32 bit unsigned integers with four bytes even though it
144
# should be 5 bytes (at least the way I read it).
145
#
146
def decode_uinteger_value(value)
147
build_integer(value, 0, value.length)
148
end
149
150
def build_integer(data, start, num_octets)
151
number = 0
152
num_octets.times { |i| number = number<<8 | data[start+i].ord }
153
return number
154
end
155
156
#
157
# Decode TLV data for an ASN.1 octet string.
158
#
159
# Throws an InvalidTag exception if the tag is incorrect.
160
#
161
# Returns a tuple containing a string and any remaining unprocessed data.
162
#
163
def decode_octet_string(data)
164
tag, value, remainder = decode_tlv(data)
165
raise InvalidTag, tag.to_s if tag != OCTET_STRING_TAG
166
return value, remainder
167
end
168
169
def decode_ip_address(data)
170
tag, value, remainder = decode_tlv(data)
171
raise InvalidTag, tag.to_s if tag != IpAddress_TAG
172
raise InvalidLength, tag.to_s if value.length != 4
173
return value, remainder
174
end
175
176
#
177
# Decode TLV data for an ASN.1 sequence.
178
#
179
# Throws an InvalidTag exception if the tag is incorrect.
180
#
181
# Returns a tuple containing the sequence data and any remaining
182
# unprocessed data that follows the sequence.
183
#
184
def decode_sequence(data)
185
tag, value, remainder = decode_tlv(data)
186
raise InvalidTag, tag.to_s if tag != SEQUENCE_TAG
187
return value, remainder
188
end
189
190
#
191
# Unwrap TLV data for an ASN.1 object identifier. This method extracts
192
# the OID value as a character string but does not decode it further.
193
#
194
# Throws an InvalidTag exception if the tag is incorrect.
195
#
196
# Returns a tuple containing the object identifier (OID) and any
197
# remaining unprocessed data. The OID is represented as an array
198
# of integers.
199
#
200
def decode_object_id(data)
201
tag, value, remainder = decode_tlv(data)
202
raise InvalidTag, tag.to_s if tag != OBJECT_IDENTIFIER_TAG
203
return decode_object_id_value(value), remainder
204
end
205
206
def decode_object_id_value(value)
207
if value.length == 0
208
object_id = []
209
else
210
value0 = value[0].ord
211
if value0 == 0x2b
212
object_id = [1,3]
213
else
214
second = value0 % 40
215
first = (value0 - second) / 40
216
raise InvalidObjectId, value.to_s if first > 2
217
object_id = [first, second]
218
end
219
n = 0
220
for i in 1...value.length
221
n = (n<<7) + (value[i].ord & 0x7f)
222
if value[i].ord < 0x80
223
object_id << n
224
n = 0
225
end
226
end
227
end
228
return object_id
229
end
230
231
#
232
# Encode the length field for TLV data. Returns the length octets
233
# as a string.
234
#
235
def encode_length(length)
236
raise InvalidLength, length.to_s if length < 0
237
if length < 0x80
238
length.chr
239
else
240
data = integer_to_octets(length)
241
(data.size | 0x80).chr << data
242
end
243
end
244
245
#
246
# Encode integer
247
#
248
def encode_integer(value)
249
encode_tagged_integer(INTEGER_TAG, value)
250
end
251
252
def encode_tagged_integer(tag, value)
253
if value > 0 && value < 0x80
254
data = value.chr
255
else
256
data = integer_to_octets(value)
257
if value > 0 && data[0].ord > 0x7f
258
data = "\000" << data
259
elsif value < 0 && data[0].ord < 0x80
260
data = "\377" << data
261
end
262
end
263
encode_tlv(tag, data)
264
end
265
266
#
267
# Helper method for encoding integer-like things.
268
#
269
def integer_to_octets(i)
270
if i >= 0
271
done = 0
272
else
273
done = -1
274
end
275
octets = ""
276
begin
277
octets = (i & 0xff).chr << octets
278
i = i >> 8
279
end until i == done
280
octets
281
end
282
283
def encode_null
284
NULL_TAG.chr << "\000"
285
end
286
287
#
288
# Encode an exception. The encoding is simply the exception tag with
289
# no data, similar to NULL.
290
#
291
def encode_exception(tag)
292
tag.chr << "\000"
293
end
294
295
#
296
# Wraps value in a tag and length. This method expects an
297
# integer tag and a string value.
298
#
299
def encode_tlv(tag, value)
300
data = tag.chr << encode_length(value.length)
301
data = data << value if value.length > 0
302
data
303
end
304
305
#
306
# Wrap string in a octet string tag and length.
307
#
308
def encode_octet_string(value)
309
encode_tlv(OCTET_STRING_TAG, value)
310
end
311
312
#
313
# Wrap value in a sequence tag and length.
314
#
315
def encode_sequence(value)
316
encode_tlv(SEQUENCE_TAG, value)
317
end
318
319
#
320
# Encode an object id. The input is assumed to be an array of integers
321
# representing the object id.
322
#
323
def encode_object_id(value)
324
raise InvalidObjectId, value.to_s if value.length < 1
325
raise InvalidObjectId, value.to_s if value[0] > 2
326
data = ""
327
if (value.length > 1)
328
raise InvalidObjectId if value[0] < 2 && value[1] > 40
329
data << (40 * value[0] + value[1]).chr
330
for i in 2...value.length
331
if value[i] < 0x80
332
data << value[i].chr
333
else
334
octets = ""
335
n = value[i]
336
begin
337
octets = (n & 0x7f | 0x80).chr << octets
338
n = n >> 7
339
end until n == 0
340
octets[-1] = (octets[-1].ord & 0x7f).chr
341
data << octets
342
end
343
end
344
elsif (value.length == 1)
345
data << (40 * value[0]).chr
346
end
347
encode_tlv(OBJECT_IDENTIFIER_TAG, data)
348
end
349
350
end
351
end
352
353
354