f1950c2fe1
Fixes #3289. This commit adds back the bit-struct library because in the end, it is useful for some modules, especially pello's. It's small and it has a nice license, so why not. After all, it /is/ useful for quicky application headers. Eventually, should be replaced by StructFu, but that requires some doc work on my part to get that transition in place. This also adds pello's DNS fuzzer module which makes use of BitStruct to create sometimes malformed-on-purpose DNS headers. Tested against 3 different DNS servers, caused one to reboot, so I'd say it works.
259 lines
7.8 KiB
Ruby
259 lines
7.8 KiB
Ruby
class BitStruct
|
|
# Class for signed integers in network order, 1-16 bits, or 8n bits.
|
|
# Declared with BitStruct.signed.
|
|
class SignedField < Field
|
|
# Used in describe.
|
|
def self.class_name
|
|
@class_name ||= "signed"
|
|
end
|
|
|
|
def add_accessors_to(cl, attr = name) # :nodoc:
|
|
offset_byte = offset / 8
|
|
offset_bit = offset % 8
|
|
|
|
length_bit = offset_bit + length
|
|
length_byte = (length_bit/8.0).ceil
|
|
last_byte = offset_byte + length_byte - 1
|
|
max = 2**length-1
|
|
mid = 2**(length-1)
|
|
max_unsigned = 2**length
|
|
to_signed = proc {|n| (n>=mid) ? n - max_unsigned : n}
|
|
# to_signed = proc {|n| (n>=mid) ? -((n ^ max) + 1) : n}
|
|
|
|
divisor = options[:fixed] || options["fixed"]
|
|
divisor_f = divisor && divisor.to_f
|
|
# if divisor and not divisor.is_a? Fixnum
|
|
# raise ArgumentError, "fixed-point divisor must be a fixnum"
|
|
# end
|
|
|
|
endian = (options[:endian] || options["endian"]).to_s
|
|
case endian
|
|
when "native"
|
|
ctl = length_byte <= 2 ? "s" : "l"
|
|
if length == 16 or length == 32
|
|
to_signed = proc {|n| n}
|
|
# with pack support, to_signed can be replaced with no-op
|
|
end
|
|
when "little"
|
|
ctl = length_byte <= 2 ? "v" : "V"
|
|
when "network", "big", ""
|
|
ctl = length_byte <= 2 ? "n" : "N"
|
|
else
|
|
raise ArgumentError,
|
|
"Unrecognized endian option: #{endian.inspect}"
|
|
end
|
|
|
|
data_is_big_endian =
|
|
([1234].pack(ctl) == [1234].pack(length_byte <= 2 ? "n" : "N"))
|
|
|
|
if length_byte == 1
|
|
rest = 8 - length_bit
|
|
mask = ["0"*offset_bit + "1"*length + "0"*rest].pack("B8")[0].ord
|
|
mask2 = ["1"*offset_bit + "0"*length + "1"*rest].pack("B8")[0].ord
|
|
|
|
cl.class_eval do
|
|
if divisor
|
|
define_method attr do ||
|
|
to_signed[(self[offset_byte] & mask) >> rest] / divisor_f
|
|
end
|
|
|
|
define_method "#{attr}=" do |val|
|
|
val = (val * divisor).round
|
|
self[offset_byte] =
|
|
(self[offset_byte] & mask2) | ((val<<rest) & mask)
|
|
end
|
|
|
|
else
|
|
define_method attr do ||
|
|
to_signed[(self[offset_byte] & mask) >> rest]
|
|
end
|
|
|
|
define_method "#{attr}=" do |val|
|
|
self[offset_byte] =
|
|
(self[offset_byte] & mask2) | ((val<<rest) & mask)
|
|
end
|
|
end
|
|
end
|
|
|
|
elsif offset_bit == 0 and length % 8 == 0
|
|
field_length = length
|
|
byte_range = offset_byte..last_byte
|
|
|
|
cl.class_eval do
|
|
case field_length
|
|
when 8
|
|
if divisor
|
|
define_method attr do ||
|
|
to_signed[self[offset_byte]] / divisor_f
|
|
end
|
|
|
|
define_method "#{attr}=" do |val|
|
|
val = (val * divisor).round
|
|
self[offset_byte] = val
|
|
end
|
|
|
|
else
|
|
define_method attr do ||
|
|
to_signed[self[offset_byte]]
|
|
end
|
|
|
|
define_method "#{attr}=" do |val|
|
|
self[offset_byte] = val
|
|
end
|
|
end
|
|
|
|
when 16, 32
|
|
if divisor
|
|
define_method attr do ||
|
|
to_signed[self[byte_range].unpack(ctl).first] / divisor_f
|
|
end
|
|
|
|
define_method "#{attr}=" do |val|
|
|
val = (val * divisor).round
|
|
self[byte_range] = [val].pack(ctl)
|
|
end
|
|
|
|
else
|
|
define_method attr do ||
|
|
to_signed[self[byte_range].unpack(ctl).first]
|
|
end
|
|
|
|
define_method "#{attr}=" do |val|
|
|
self[byte_range] = [val].pack(ctl)
|
|
end
|
|
end
|
|
|
|
else
|
|
reader_helper = proc do |substr|
|
|
bytes = substr.unpack("C*")
|
|
bytes.reverse! unless data_is_big_endian
|
|
bytes.inject do |sum, byte|
|
|
(sum << 8) + byte
|
|
end
|
|
end
|
|
|
|
writer_helper = proc do |val|
|
|
bytes = []
|
|
val += max_unsigned if val < 0
|
|
while val > 0
|
|
bytes.push val % 256
|
|
val = val >> 8
|
|
end
|
|
if bytes.length < length_byte
|
|
bytes.concat [0] * (length_byte - bytes.length)
|
|
end
|
|
|
|
bytes.reverse! if data_is_big_endian
|
|
bytes.pack("C*")
|
|
end
|
|
|
|
if divisor
|
|
define_method attr do ||
|
|
to_signed[reader_helper[self[byte_range]] / divisor_f]
|
|
end
|
|
|
|
define_method "#{attr}=" do |val|
|
|
self[byte_range] = writer_helper[(val * divisor).round]
|
|
end
|
|
|
|
else
|
|
define_method attr do ||
|
|
to_signed[reader_helper[self[byte_range]]]
|
|
end
|
|
|
|
define_method "#{attr}=" do |val|
|
|
self[byte_range] = writer_helper[val]
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
elsif length_byte == 2 # unaligned field that fits within two whole bytes
|
|
byte_range = offset_byte..last_byte
|
|
rest = 16 - length_bit
|
|
|
|
mask = ["0"*offset_bit + "1"*length + "0"*rest]
|
|
mask = mask.pack("B16").unpack(ctl).first
|
|
|
|
mask2 = ["1"*offset_bit + "0"*length + "1"*rest]
|
|
mask2 = mask2.pack("B16").unpack(ctl).first
|
|
|
|
cl.class_eval do
|
|
if divisor
|
|
define_method attr do ||
|
|
to_signed[(self[byte_range].unpack(ctl).first & mask) >> rest] /
|
|
divisor_f
|
|
end
|
|
|
|
define_method "#{attr}=" do |val|
|
|
val = (val * divisor).round
|
|
x = (self[byte_range].unpack(ctl).first & mask2) |
|
|
((val<<rest) & mask)
|
|
self[byte_range] = [x].pack(ctl)
|
|
end
|
|
|
|
else
|
|
define_method attr do ||
|
|
to_signed[(self[byte_range].unpack(ctl).first & mask) >> rest]
|
|
end
|
|
|
|
define_method "#{attr}=" do |val|
|
|
x = (self[byte_range].unpack(ctl).first & mask2) |
|
|
((val<<rest) & mask)
|
|
self[byte_range] = [x].pack(ctl)
|
|
end
|
|
end
|
|
end
|
|
|
|
elsif length_byte == 3 # unaligned field that fits within 3 whole bytes
|
|
byte_range = offset_byte..last_byte
|
|
rest = 32 - length_bit
|
|
|
|
mask = ["0"*offset_bit + "1"*length + "0"*rest]
|
|
mask = mask.pack("B32").unpack(ctl).first
|
|
|
|
mask2 = ["1"*offset_bit + "0"*length + "1"*rest]
|
|
mask2 = mask2.pack("B32").unpack(ctl).first
|
|
|
|
cl.class_eval do
|
|
if divisor
|
|
define_method attr do ||
|
|
bytes = self[byte_range]
|
|
bytes << 0
|
|
to_signed[((bytes.unpack(ctl).first & mask) >> rest)] /
|
|
divisor_f
|
|
end
|
|
|
|
define_method "#{attr}=" do |val|
|
|
val = (val * divisor).round
|
|
bytes = self[byte_range]
|
|
bytes << 0
|
|
x = (bytes.unpack(ctl).first & mask2) |
|
|
((val<<rest) & mask)
|
|
self[byte_range] = [x].pack(ctl)[0..2]
|
|
end
|
|
|
|
else
|
|
define_method attr do ||
|
|
bytes = self[byte_range]
|
|
bytes << 0
|
|
to_signed[(bytes.unpack(ctl).first & mask) >> rest]
|
|
end
|
|
|
|
define_method "#{attr}=" do |val|
|
|
bytes = self[byte_range]
|
|
bytes << 0
|
|
x = (bytes.unpack(ctl).first & mask2) |
|
|
((val<<rest) & mask)
|
|
self[byte_range] = [x].pack(ctl)[0..2]
|
|
end
|
|
end
|
|
end
|
|
|
|
else
|
|
raise "unsupported: #{inspect}"
|
|
end
|
|
end
|
|
end
|
|
end
|