binstruct 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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