binstruct 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,4 @@
1
+ === 1.0.0 / 2009-04-01
2
+
3
+ * Although an unfortunate date, first public release.
4
+
@@ -0,0 +1,9 @@
1
+ History.txt
2
+ Manifest.txt
3
+ README.txt
4
+ Rakefile
5
+ bin/binstruct
6
+ lib/binstruct.rb
7
+ lib/fields.rb
8
+ lib/objhax.rb
9
+ test/test_binstruct.rb
@@ -0,0 +1,106 @@
1
+ = binstruct
2
+
3
+ http://metafuzz.rubyforge.org/binstruct
4
+
5
+ == DESCRIPTION:
6
+
7
+ BinStruct is a small class for defining structures that can be used to parse /
8
+ manipulate binary data. It is similar in concept to BitStruct, but supports
9
+ some things that are difficult to do with a pure pack / unpack approach. It is
10
+ mainly designed to work with the Metafuzz fuzzing tools, but would possibly be
11
+ useful to anyone working with binary data.
12
+
13
+ == FEATURES/PROBLEMS:
14
+
15
+ * Parsing is done 'as you go' which means you can refer to previous fields or
16
+ values when making parser decisions (variable length fields etc)
17
+ * Supports byte-swapped multi-byte bitfields for little endian file formats and
18
+ substructs for nested parsing
19
+ * Full Ruby inside the class definition, with a little bit of syntactic sugar
20
+ for field type definitions
21
+ * All field data remains in the original binary internally, but get / setters
22
+ are available for signed, unsigned, string, octets, hexstring and bitstring
23
+ and can be easily extended.
24
+ * Key instance methods are get / setters for the fields (defined when the
25
+ subclass is constructed), directly access the raw field object with #[:afield]
26
+ #each {|item| (item may be a substruct), #deep_each {|field| (for nested
27
+ structs), #flatten and of course #to_s to re-pack as a string.
28
+ * Because this class was designed for use with a fuzzer it does not do very
29
+ stringent checking of alignment, length matching etc, and raw contents are
30
+ available where possible. This is either a bug or a feature, depending on
31
+ your point of view.
32
+
33
+ == SYNOPSIS:
34
+
35
+ This example is from some Word binary file work...
36
+
37
+ class WordDgg < BinStruct
38
+ parse{ |bitbuf|
39
+ endian :little
40
+ # These two bytes will be swapped before they are carved up, and
41
+ # swapped back whenever to_s is called. The bitfield itself cannot
42
+ # be directly accessed, just the fields inside.
43
+ bitfield(bitbuf, 16) do |buf|
44
+ unsigned buf, :recInstance, 12, "Object Identifier"
45
+ unsigned buf, :recVer, 4, "Object version, 0xF for container"
46
+ end
47
+ # Normal field definitions take effect immediately, and consume
48
+ # the portion of the bit buffer they used.
49
+ unsigned bitbuf, :recType, 16, "RecType"
50
+ unsigned bitbuf, :recLen, 32, "Content length"
51
+ # Context sensitive parsing...
52
+ if self.recVer==0xF
53
+ substruct_name=:substruct0
54
+ # Manually manipulate the buffer parameter, if you like.
55
+ mychunk=[bitbuf.slice!(0,self.recLen*8)].pack('B*')
56
+ while mychunk.length > 0
57
+ # Nested parsing, here...
58
+ substruct(mychunk, substruct_name, mychunk.length, WordDgg)
59
+ substruct_name=substruct_name.succ
60
+ end
61
+ else
62
+ string bitbuf, :contents, self.recLen*8, "Contents"
63
+ end
64
+ # groups link sets of fields, using a given name. The groups are used
65
+ # by Metafuzz to create cartestian product output of the fuzz items
66
+ # for all fields in the group.
67
+ if self[:contents]
68
+ group :tv, :recType, :contents
69
+ else
70
+ group :tl, :recType, :recLen
71
+ end
72
+ }
73
+ end
74
+
75
+ == REQUIREMENTS:
76
+
77
+ None.
78
+
79
+ == INSTALL:
80
+
81
+ Just install the gem.
82
+
83
+ == LICENSE:
84
+
85
+ (The MIT License)
86
+
87
+ Copyright (c) 2006 Ben Nagy
88
+
89
+ Permission is hereby granted, free of charge, to any person obtaining
90
+ a copy of this software and associated documentation files (the
91
+ 'Software'), to deal in the Software without restriction, including
92
+ without limitation the rights to use, copy, modify, merge, publish,
93
+ distribute, sublicense, and/or sell copies of the Software, and to
94
+ permit persons to whom the Software is furnished to do so, subject to
95
+ the following conditions:
96
+
97
+ The above copyright notice and this permission notice shall be
98
+ included in all copies or substantial portions of the Software.
99
+
100
+ THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
101
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
102
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
103
+ IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
104
+ CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
105
+ TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
106
+ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,12 @@
1
+ # -*- ruby -*-
2
+
3
+ require 'rubygems'
4
+ require 'hoe'
5
+ require './lib/binstruct.rb'
6
+
7
+ Hoe.new('binstruct', Binstruct::VERSION) do |p|
8
+ p.rubyforge_name = 'metafuzz' # if different than lowercase project name
9
+ p.developer('Ben Nagy', 'metafuzz@ben.iagu.net')
10
+ end
11
+
12
+ # vim: syntax=Ruby
@@ -0,0 +1,3 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ abort "you need to write me"
@@ -0,0 +1,252 @@
1
+ require 'fields'
2
+ require 'objhax'
3
+
4
+ # There are two main classes of methods for a Binstruct, but because of the
5
+ # implementation they are all actually instance methods. The "construction"
6
+ # methods are +endian+, +bitfield+, +substruct+ and all the builders for field
7
+ # subtypes. Each class in the Fields module has a builder, so UnsignedField is
8
+ # just unsigned. Those methods are created at runtime, so they can't be
9
+ # seen by rdoc, but they all look like:
10
+ # unsigned, buffer_name, :accessor_sym, length_in_bits, "Description"
11
+ # Inside a parse block, the buffer name used for the field builders should match
12
+ # the block parameter, unless you are manually messing with the buffer. The
13
+ # same applies to fields created inside the blocks for substructs and bitfields.
14
+ #
15
+ # The other methods are 'proper' instance methods, to work with the structure
16
+ # once it is created and filled with data. Each field creates a getter and
17
+ # setter method matching its symbol, and also a direct access via [:field_sym]
18
+ # that returns the field itself, which gives you access to the field's own
19
+ # instance methods like +set_raw+ (see Fields).
20
+ class Binstruct
21
+ VERSION="1.0.0"
22
+
23
+ class Bitfield < Binstruct # :nodoc:
24
+ end
25
+ attr_reader :groups
26
+ attr_accessor :fields, :endian
27
+ #---------------CONSTRUCTION-------------------
28
+
29
+ # Set the endianness for the whole structure. Default is +:big+, options
30
+ # are +:big+ or +:little+. Fields created after this method is invoked in
31
+ # a construction block will be created with the new endianness. You can also
32
+ # set the endianness after construction with <code>somestruct.endian=:little
33
+ # </code>.
34
+ def endian( sym )
35
+ unless sym==:little || sym==:big
36
+ raise RuntimeError, "Binstruct: Construction: Unknown endianness #{sym.to_s}"
37
+ end
38
+ @endian=sym
39
+ meta_def :endian do @endian end
40
+ meta_def :endianness do @endian end
41
+ end
42
+
43
+ # In little endian structures, byte-swaps 16, 32 or 64 bits and presents
44
+ # them for carving into fields. Only really neccessary for non-byte aligned
45
+ # field sets in little endian structures. The bitfield itself is invisible
46
+ # but the fields are created with the usual accessors.
47
+ def bitfield(bitbuf, len, &blk)
48
+ if @endian==:little
49
+ unless len==16||len==32||len==64
50
+ raise RuntimeError, "Binstruct: Bitfield: Don't know how to endian swap #{len} bits. :("
51
+ end
52
+ instr=bitbuf.slice!(0,len).scan(/.{8}/).reverse.join
53
+ else
54
+ instr=bitbuf.slice!(0,len)
55
+ end
56
+ new=Bitfield.new([instr].pack('B*'), &blk)
57
+ if @endian==:little
58
+ # This is so we know to flip the bytes back in #to_s
59
+ new.instance_variable_set :@endian_flip_hack, true
60
+ end
61
+ @fields << new
62
+ # Add direct references and accessor methods to the containing Binstruct
63
+ new.fields.each {|f|
64
+ unless f.is_a? Fields::Field
65
+ raise RuntimeError, "Binstruct: Construction: Illegal content #{f.class} in bitfield - use only Fields"
66
+ end
67
+ @hash_references[f.name]=f
68
+ meta_def f.name do f.get_value end
69
+ meta_def (f.name.to_s + '=').to_sym do |val| f.set_value(val) end
70
+ }
71
+ end
72
+
73
+ # Creates a nested structure, and a direct accesor for it that returns the
74
+ # structure itself, so accessors like <code>main.sub1.sub2.some_val</code>
75
+ # are possible.
76
+ # When iterating over the Binstruct contents, see +#each+, which will pass
77
+ # substructs to the block and +#deep_each+ which recursively enters
78
+ # substructs and passes only Fields.
79
+ def substruct(strbuf, name, len, klass)
80
+ new=klass.new(strbuf)
81
+ @fields << new
82
+ @hash_references[name]=new
83
+ meta_def name do new end
84
+ # More informative than the NoMethodError they would normally get.
85
+ meta_def (name.to_s + '=').to_sym do raise NoMethodError, "Binstruct: Illegal call of '=' on a substruct." end
86
+ end
87
+
88
+ #fieldtype builders
89
+ Fields::Field_Subtypes.each {|fieldname|
90
+ field_klass=Fields.const_get(fieldname.capitalize.to_s+"Field")
91
+ define_method fieldname do |*args|
92
+ bitbuf, name, len, desc=args
93
+ @fields << thisfield=field_klass.new(bitbuf.slice!(0,len),name,len,desc,nil,@endian||:big)
94
+ @hash_references[name.to_sym]=thisfield
95
+ meta_def name do thisfield.get_value end
96
+ meta_def (name.to_s + '=').to_sym do |val| thisfield.set_value(val) end
97
+ end
98
+ }
99
+
100
+ # Groups a list of fields under +:groupname+. Designed for use in Metafuzz.
101
+ # +somestruct.groups+ will return the hash of <code>{:group_sym=>[field1,
102
+ # field2...]}</code>
103
+ def group( groupname, *fieldsyms )
104
+ @groups[groupname] << fieldsyms
105
+ end
106
+
107
+ class << self
108
+ attr_reader :init_block
109
+ end
110
+
111
+ # There are two ways to create a Binstruct subclass, one is by calling
112
+ # parse inside the structure definition:
113
+ # class Foo < Binstruct
114
+ # parse {|buffer_as_binary|
115
+ # #definitions here
116
+ # }
117
+ # end
118
+ # and the other is by just supplying a block to new:
119
+ # quick_struct=Binstruct.new {|b| string, b, :foo, 32, "Some String"}
120
+ # Otherwise, +Binstruct.new+ will just create a blank structure (this can
121
+ # be useful if you want to fill in the fields at runtime).
122
+ def self.parse( &blk )
123
+ @init_block=blk
124
+ end
125
+
126
+ #------------END CONSTRUCTION------------------
127
+
128
+ def initialize(buffer=nil, &blk)
129
+ @fields=[]
130
+ @hash_references={}
131
+ @endian_flip_hack=false
132
+ @groups=Hash.new {|h,k| h[k]=[]}
133
+ buffer||=""
134
+ @bitbuf=buffer.unpack('B*').join
135
+ if block_given?
136
+ instance_exec(@bitbuf, &blk)
137
+ elsif self.class.init_block
138
+ instance_exec(@bitbuf, &self.class.init_block)
139
+ else
140
+ # do nothing, user probably just wants a blank struct to manually add fields.
141
+ end
142
+ endian :big unless @endian
143
+ @groups.each {|group, contents|
144
+ unless contents.flatten.all? {|sym| @hash_references.keys.any? {|othersym| othersym==sym}}
145
+ raise RuntimeError, "Binstruct: Construction: group #{group} contains invalid field name(s)"
146
+ end
147
+ }
148
+ # This is not ideal for structures that aren't byte aligned, but raising an exception
149
+ # would be less flexible.
150
+ buffer.replace @bitbuf.scan(/.{8}/).map {|e| e.to_i(2).chr}.join unless buffer.nil?
151
+ end
152
+
153
+ #----------------INSTANCE----------------------
154
+
155
+ # return an object, specified by symbol. May be a field or a substruct.
156
+ # not designed for bitfields, since they're supposed to be invisible
157
+ # containers.
158
+ def []( sym )
159
+ @hash_references[sym]
160
+ end
161
+
162
+ # yield each object to the block. This is a little messy, because
163
+ # substructs are not Fields::Field types. For Bitfields, just silently
164
+ # yield each component, not the container field. The upshot of all this
165
+ # is that the caller needs to be prepared for a Field or a Binstruct in the
166
+ # block. This is the 'shallow' each.
167
+ def each( &blk ) #:yields: a Field or a Bitstruct
168
+ @fields.each {|atom|
169
+ if atom.is_a? Bitfield
170
+ atom.fields.each {|f| yield f}
171
+ else
172
+ yield atom
173
+ end
174
+ }
175
+
176
+ end
177
+
178
+ # yield all fields in the structure, entering nested substructs as necessary
179
+ def deep_each( &blk ) #:yields: a Field
180
+ @fields.each {|atom|
181
+ if atom.is_a? Binstruct
182
+ atom.deep_each &blk unless atom.fields.empty?
183
+ else
184
+ yield atom
185
+ end
186
+ }
187
+ end
188
+
189
+ # Searches a Binstruct, recursing through nested structures as neccessary,
190
+ # and replaces a given object with a new object. Note that this replaces
191
+ # the object that ==oldthing, so a reference to it is needed first.
192
+ def replace(oldthing, newthing)
193
+ k,v=@hash_references.select {|k,v| v==oldthing}.flatten
194
+ @hash_references[k]=newthing
195
+ @fields.map! {|atom|
196
+ if atom==oldthing
197
+ newthing
198
+ else
199
+ if atom.is_a? Binstruct
200
+ atom.replace(oldthing,newthing)
201
+ end
202
+ atom
203
+ end
204
+ }
205
+ end
206
+
207
+ # Flattens all fields in nested structures into an array, preserving order.
208
+ # In some cases (eg little endian structures with bitfields) this will mean
209
+ # that struct.flatten.join will not be the same as struct.to_s.
210
+ def flatten
211
+ a=[]
212
+ self.deep_each {|f| a << f}
213
+ a
214
+ end
215
+
216
+ #pack current struct as a string - for Fields, it will use the bitstring,
217
+ #for anything else (including Bitfields and Binstructs) it will use
218
+ #<code>to_s.unpack('B*')</code>. Via a filthy internal hack, bitfields
219
+ #get byte-swapped
220
+ #back in here. Finally, once the bitstring is assembled, it is right-padded
221
+ #with 0 and packed as a string.
222
+ def to_s
223
+ bits=@fields.inject("") {|str,field|
224
+ field.kind_of?(Fields::Field) ? str << field.bitstring : str << field.to_s.unpack('B*').join
225
+ }
226
+ unless bits.length % 8 == 0
227
+ #puts "Warning, structure not byte aligned, right padding with 0"
228
+ end
229
+ return "" if bits.empty?
230
+ bytearray=bits.scan(/.{1,8}/)
231
+ # If not byte aligned, right pad with 0
232
+ bytearray.last << '0'*(8-bytearray.last.length) if bytearray.last.length < 8
233
+ # This only happens for Binstructs that have the endian_flip_hack ivar
234
+ # set, so only inside the to_s of a Bitfield when little endian.
235
+ bytearray=bytearray.reverse if @endian_flip_hack
236
+ bytearray.map {|byte| "" << byte.to_i(2)}.join
237
+ end
238
+
239
+ # Packed length in bytes.
240
+ def length
241
+ self.to_s.length
242
+ end
243
+
244
+ # Returns an array of terse field descriptions which show the index, field
245
+ # class, name and length in bits, plus a hexdumped snippet of the contents.
246
+ def inspect
247
+ # This could possibly be more readable...
248
+ self.flatten.map {|field| "<IDX:#{self.flatten.index(field)}><#{field.class.to_s.match(/::(.+)Field/)[1]}><#{field.name}><#{field.length}><#{field.to_s[0..12].each_byte.to_a.map {|b| "%.2x" % b}.join(' ') + (field.to_s.length>12?"...":"")}>"}
249
+ end
250
+ end
251
+
252
+
@@ -0,0 +1,310 @@
1
+ #If you add new Field subclasses, you need to name them with a capitalized 'Field' at the end, eg CrazyField, and use it in Binstruct by declaring the
2
+ #first half in lower case. Like this:
3
+ # module Fields
4
+ # class CrazyField < StringField
5
+ # # I act just like a StringField, only crunchy!
6
+ # end
7
+ # end
8
+ #
9
+ # class MyStruct < Binstruct
10
+ # crazy :field_name, 32, "A Crazy Field!"
11
+ # [...]
12
+ #
13
+ #Take a look at the documentation for Fields::Field for some other guidlines on creating new Field subclasses that can't trivially
14
+ #inherit one of the base classes like Fields::StringField or Fields::HexstringField.
15
+ module Fields
16
+
17
+ #Subclasses of Fields::Field should probably at least overload <tt>input_to_bitstring</tt> and <tt>get_value</tt> and
18
+ #set <tt>@length_type</tt>. Check the source code for the base subclasses for ideas. Remember that for the
19
+ #purposes of fuzzing you can probably get away with
20
+ #inheriting from one of the base classes a lot of the time - an EmailField can probably just inherit StringField unless you want
21
+ #stringent checking in your parser.
22
+ class Field
23
+
24
+ attr_reader :bitstring, :desc, :length, :type, :default_value, :name, :length_type, :endianness
25
+
26
+ def initialize(bitstring, name, length, desc, default, endian)
27
+ @name=name
28
+ @length=length
29
+ @desc=desc
30
+ default&&=self.input_to_bitstring(default)
31
+ @default_value=default # can still be nil
32
+ @bitstring=self.parse_buffer(bitstring)
33
+ @type=self.class.to_s[/\w+(?=Field)/].downcase #fricking ugly
34
+ @endianness=endian
35
+ unless @endianness.downcase==:big or @endianness.downcase==:little
36
+ raise ArgumentError, "#{self.class.to_s[/\w+$/]} (#{@name}): Unknown endianness #{endianness}, use :little or :big (default)."
37
+ end
38
+ end
39
+
40
+ # Provided for classes that require strict lengths. Left pads with 0 to @length
41
+ # if the input buffer is incomplete. Truncates at @length if too long. Called by <tt>parse_buffer</tt> internally.
42
+ def parse_buffer_strict( bitstring )
43
+ return "" unless bitstring
44
+ bitstring||=""
45
+ unless bitstring.is_a? String and bitstring=~/^[10]*$/
46
+ raise ArgumentError, "Field: <Internal> bitstring buffer borked?? #{bitstring.inspect}"
47
+ end
48
+ if bitstring.length <= @length
49
+ "0"*(@length-bitstring.length)+bitstring
50
+ elsif bitstring.length > @length
51
+ bitstring.slice(0,@length)
52
+ else
53
+ raise RuntimeError, "Universe Broken Error: value neither less/equal nor greater"
54
+ end
55
+ end
56
+
57
+ # Provided for classes that don't care about length matching
58
+ # when assigning the contents. Called by <tt>parse_buffer</tt> internally.
59
+ def parse_buffer_lazy( bitstring )
60
+ return "" unless bitstring
61
+ bitstring||=""
62
+ unless bitstring.is_a? String and bitstring=~/^[10]*$/
63
+ raise ArgumentError, "#{self.class.to_s[/\w+$/]} (#{@name}): <Internal> bitstring buffer borked??"
64
+ end
65
+ bitstring
66
+ end
67
+
68
+ # Placeholder, subclasses should probably redefine for clarity. Defaults to calling parse_buffer_strict, but uses
69
+ # parse_buffer_lazy for variable length fields.
70
+ def parse_buffer( bitstring )
71
+ return self.parse_buffer_lazy( bitstring ) if self.length_type=="variable"
72
+ self.parse_buffer_strict( bitstring ) # default to this for subclasses that forget to define @length_type
73
+ end
74
+
75
+ #Sets the raw field contents. Can be useful if the set_value method does inconvenient checking when you want
76
+ #to set crazy values.
77
+ def set_raw( bitstring )
78
+ @bitstring=self.parse_buffer( bitstring )
79
+ end
80
+
81
+ # Placeholder, classes should override as neccessary.
82
+ # Parse a value in whatever format is determined by the class
83
+ # and return a bitstring.
84
+ # This default does zero checking, so expects a bitstring as input
85
+ def input_to_bitstring( value )
86
+ value
87
+ end
88
+
89
+ # Randomize the bitstring for this field, bit by bit
90
+ def randomize!
91
+ random_bitstring=Array.new(self.length).map {|e| e=rand(2).to_s}.join
92
+ if self.length_type=="variable"
93
+ slice=random_bitstring[0,(rand((self.length/8)+1)*8)]
94
+ set_raw(slice)
95
+ else
96
+ set_raw(random_bitstring)
97
+ end
98
+ end
99
+
100
+ #Set the field value. Calls self.input_to_bitstring which is expected to return a binary string.
101
+ def set_value(new_val)
102
+ @bitstring=self.input_to_bitstring(new_val)
103
+ end
104
+
105
+ #Subclasses should override this. This default returns the raw bitstring.
106
+ def get_value
107
+ @bitstring
108
+ end
109
+
110
+ # This really only makes sense for fields that are byte aligned, but what the hey. For the rest it effectively
111
+ # packs them as left padded with zeroes to a byte boundary (so a 3 bit field "110" will pack as \006)
112
+ def to_s
113
+ @bitstring.reverse.scan(/.{1,8}/).map {|s| s.reverse}.reverse.map {|bin| "" << bin.to_i(2)}.join
114
+ end
115
+
116
+ end # Field
117
+
118
+ #Accepts negative numbers on assignment, but stores them as 2s complement.
119
+ class UnsignedField < Field
120
+
121
+ def initialize *args
122
+ @length_type="fixed"
123
+ super
124
+ end
125
+
126
+ def input_to_bitstring( value )
127
+ unless value.kind_of? Integer
128
+ raise ArgumentError, "UnsignedField (#{@name}): attempted to assign non-integer"
129
+ end
130
+ if value.to_s(2).length > @length
131
+ # works for negative assignments as well, since to_s(2) adds a '-' to the start of the binary string
132
+ raise ArgumentError, "UnsignedField (#{@name}): value too big for field length"
133
+ end
134
+ # accept negative numbers, but store them as their two's complement representation for this bitlength
135
+ # (get_value will return the positive interpretation)
136
+ unpadded=value < 0 ? (("1"+"0"*@length).to_i(2)-value.abs).to_s(2) : value.to_s(2)
137
+ value="0"*(@length-unpadded.length)+unpadded # left pad with zeroes to full length
138
+ if self.endianness==:little
139
+ if value.length > 8 && value.length % 8 ==0
140
+ value=value.scan(/.{8}/).reverse.join
141
+ end
142
+ end
143
+ value
144
+ end
145
+
146
+ def get_value
147
+ tempstring=@bitstring
148
+ if self.endianness==:little
149
+ if tempstring.length > 8 && tempstring.length % 8 ==0
150
+ tempstring=tempstring.scan(/.{8}/).reverse.join
151
+ end
152
+ end
153
+ tempstring.to_i(2)
154
+ end
155
+
156
+ end #UnsignedField
157
+
158
+ #For IP addresses etc
159
+ class OctetstringField < Field
160
+
161
+ def initialize(*args)
162
+ raise ArgumentError, "OctetstringField (#{args[1]})(CREATE): Length must be a multiple of 8 bits" unless args[2]%8==0
163
+ @length_type="fixed"
164
+ super
165
+ end
166
+
167
+ def input_to_bitstring( value )
168
+ # Expects [0-255][.0-255] .....
169
+ begin
170
+ unless value.split('.').all? {|elem| Integer(elem)<256 && Integer(elem) >= 0}
171
+ raise ArgumentError, "OctetstringField (#{@name})(PARSE): Unable to parse input value"
172
+ end
173
+ rescue
174
+ raise ArgumentError, "OctetstringField (#{@name})(PARSE): Unable to parse input value"
175
+ end
176
+ octets=value.split('.')
177
+ raise ArgumentError, "OctetstringField (#{@name}): Not enough octets?" if (octets.length)*8 < @length
178
+ raise ArgumentError, "OctetstringField (#{@name}): Too many octets?" if (octets.length*8) > @length
179
+ octets.inject("") do |str,num|
180
+ str << "%.8b"%num
181
+ end
182
+ end
183
+
184
+ def get_value
185
+ @bitstring.scan(/.{8}/).map {|e| e.to_i(2).to_s}.join('.')
186
+ end
187
+ end #OctetstringField
188
+
189
+
190
+ #For getting and setting via hex strings.
191
+ class HexstringField < Field
192
+
193
+ def initialize *args
194
+ @length_type="variable"
195
+ super
196
+ end
197
+
198
+ def input_to_bitstring( value )
199
+ if (value.respond_to? :to_int) and value >= 0
200
+ bs=value.to_int.to_s(2)
201
+ elsif (value.respond_to? :to_str) and value.to_str=~/^[a-f0-9\s]*$/
202
+ bs=value.to_str.gsub(/\s/,'').to_i(16).to_s(2)
203
+ else
204
+ raise ArgumentError, "HexstringField (#{@name}): Unable to parse input value."
205
+ end
206
+ (bs.length % 8 != 0) && bs=("0"*(8-bs.length%8) + bs)
207
+ bs
208
+ end
209
+
210
+ def get_value
211
+ return '' if @bitstring==""
212
+ unless @bitstring.length > 8 && @bitstring.length % 8 == 0
213
+ #do the best we can..
214
+ hexstring=@bitstring.to_i(2).to_s(16)
215
+ (hexstring.length % 2 == 1) && hexstring="0"+hexstring
216
+ else
217
+ hexstring=bitstring.scan(/.{8}/).map {|e| "%.2x" % e.to_i(2)}.join
218
+ end
219
+ hexstring
220
+ end
221
+
222
+ end #HexstringField
223
+
224
+ class SignedField < Field
225
+
226
+ def initialize *args
227
+ @length_type="fixed"
228
+ super
229
+ end
230
+
231
+ def input_to_bitstring( value )
232
+ unless value.kind_of? Integer
233
+ raise ArgumentError, "SignedField (#{@name}): attempted to assign non-integer"
234
+ end
235
+ if value < 0 and value.to_s(2).length > @length
236
+ # int.to_s(2) will return "-1001001" etc for negative numbers in binary, so this length check will work.
237
+ raise ArgumentError, "SignedField (#{@name}): negative value too long for field length"
238
+ end
239
+ if value > 0 and value.to_s(2).length > @length-1 # positive integers shouldn't overflow the sign bit
240
+ raise ArgumentError, "SignedField (#{@name}): positive value too long for field length"
241
+ end
242
+ unpadded=value <= 0 ? (("1"+"0"*@length).to_i(2)-value.abs).to_s(2) : value.to_s(2)
243
+ value="0"*(@length-unpadded.length)+unpadded # left pad with zeroes to full length
244
+ if self.endianness==:little
245
+ if value.length > 8 && value.length % 8==0
246
+ value=value.scan(/.{8}/).reverse.join
247
+ end
248
+ end
249
+ value
250
+ end
251
+
252
+ def get_value
253
+ tempstring=@bitstring
254
+ if self.endianness==:little
255
+ if tempstring.length > 8 && tempstring.length % 8 ==0
256
+ tempstring=tempstring.scan(/.{8}/).reverse.join
257
+ end
258
+ end
259
+ if tempstring.slice(0,1)=="1" #sign bit set
260
+ 0-(("1"+"0"*@length).to_i(2)-tempstring.to_i(2))
261
+ elsif @bitstring.slice(0,1)=="0"
262
+ tempstring.to_i(2)
263
+ else
264
+ raise RuntimeError, "SignedField: Ouch, internal contents screwed somehow."
265
+ end
266
+ end
267
+
268
+ end # SignedField
269
+
270
+ class StringField < Field
271
+ def initialize *args
272
+ @length_type="variable"
273
+ super
274
+ end
275
+
276
+ def input_to_bitstring( value )
277
+ unless value.respond_to? :to_str
278
+ raise ArgumentError, "StringField(#{@name}): Input value not a string."
279
+ end
280
+ value.to_str.unpack('B*').join
281
+ end
282
+
283
+ def get_value
284
+ return @bitstring if @bitstring==""
285
+ [@bitstring].pack('B*')
286
+ end
287
+ end # StringField
288
+
289
+ #For getting and setting via binary strings
290
+ class BitstringField < Field
291
+
292
+ def initialize *args
293
+ @length_type="fixed"
294
+ super
295
+ end
296
+
297
+ def input_to_bitstring( value )
298
+ unless value.respond_to? :to_str and value.to_str=~/^[01\s]*$/
299
+ raise ArgumentError, "BitstringField(#{@name}) (PARSE): Input value not a bitstring."
300
+ end
301
+ parse_buffer(value.to_str.gsub(/\s/,''))
302
+ end
303
+
304
+ def get_value
305
+ @bitstring
306
+ end
307
+ end #BitstringField
308
+
309
+ Field_Subtypes=self.constants.map {|const| const.to_s.sub(/Field/,'').downcase.to_sym if const=~/^.+Field/}.compact
310
+ end # Fields
@@ -0,0 +1,42 @@
1
+ # A collection of useful code bits that have been pilfered from various sources
2
+ # on the Interweb.
3
+
4
+ class Object # :nodoc: all
5
+ #Return the metaclass of an object.
6
+ def metaclass
7
+ class << self
8
+ self
9
+ end
10
+ end
11
+
12
+ #Run a block in the context of the metaclass.
13
+ def meta_eval &blk; metaclass.instance_eval &blk; end
14
+
15
+ # Adds methods to a metaclass
16
+ def meta_def name, &blk
17
+ meta_eval { define_method name, &blk }
18
+ end
19
+
20
+ # Defines an instance method within a class
21
+ def class_def name, &blk
22
+ class_eval { define_method name, &blk }
23
+ end
24
+
25
+ # Deep copy of objects that can be handled with Marshal
26
+ def deep_copy
27
+ Marshal.load(Marshal.dump(self))
28
+ end
29
+
30
+ #Define, run and then undefine a method to fake instance_eval with arguments
31
+ #(not needed on 1.9)
32
+ def instance_exec(*args, &block)
33
+ mname = "__instance_exec_#{Thread.current.object_id.abs}"
34
+ class << self; self end.class_eval{ define_method(mname, &block) }
35
+ begin
36
+ ret = send(mname, *args)
37
+ ensure
38
+ class << self; self end.class_eval{ undef_method(mname) } rescue nil
39
+ end
40
+ ret
41
+ end
42
+ end
@@ -0,0 +1,8 @@
1
+ require "test/unit"
2
+ require "binstruct"
3
+
4
+ class TestBinstruct < Test::Unit::TestCase
5
+ def test_sanity
6
+ flunk "write tests or I will kneecap you"
7
+ end
8
+ end
metadata ADDED
@@ -0,0 +1,74 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: binstruct
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.0.0
5
+ platform: ruby
6
+ authors:
7
+ - Ben Nagy
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+
12
+ date: 2009-04-02 00:00:00 +08:00
13
+ default_executable:
14
+ dependencies:
15
+ - !ruby/object:Gem::Dependency
16
+ name: hoe
17
+ type: :development
18
+ version_requirement:
19
+ version_requirements: !ruby/object:Gem::Requirement
20
+ requirements:
21
+ - - ">="
22
+ - !ruby/object:Gem::Version
23
+ version: 1.12.1
24
+ version:
25
+ description: BinStruct is a small class for defining structures that can be used to parse / manipulate binary data. It is similar in concept to BitStruct, but supports some things that are difficult to do with a pure pack / unpack approach. It is mainly designed to work with the Metafuzz fuzzing tools, but would possibly be useful to anyone working with binary data.
26
+ email:
27
+ - metafuzz@ben.iagu.net
28
+ executables:
29
+ - binstruct
30
+ extensions: []
31
+
32
+ extra_rdoc_files:
33
+ - History.txt
34
+ - Manifest.txt
35
+ - README.txt
36
+ files:
37
+ - History.txt
38
+ - Manifest.txt
39
+ - README.txt
40
+ - Rakefile
41
+ - bin/binstruct
42
+ - lib/binstruct.rb
43
+ - lib/fields.rb
44
+ - lib/objhax.rb
45
+ - test/test_binstruct.rb
46
+ has_rdoc: true
47
+ homepage: http://metafuzz.rubyforge.org/binstruct
48
+ post_install_message:
49
+ rdoc_options:
50
+ - --main
51
+ - README.txt
52
+ require_paths:
53
+ - lib
54
+ required_ruby_version: !ruby/object:Gem::Requirement
55
+ requirements:
56
+ - - ">="
57
+ - !ruby/object:Gem::Version
58
+ version: "0"
59
+ version:
60
+ required_rubygems_version: !ruby/object:Gem::Requirement
61
+ requirements:
62
+ - - ">="
63
+ - !ruby/object:Gem::Version
64
+ version: "0"
65
+ version:
66
+ requirements: []
67
+
68
+ rubyforge_project: metafuzz
69
+ rubygems_version: 1.3.1
70
+ signing_key:
71
+ specification_version: 2
72
+ summary: BinStruct is a small class for defining structures that can be used to parse / manipulate binary data
73
+ test_files:
74
+ - test/test_binstruct.rb