rstruct 0.1.0 → 0.1.1
Sign up to get free protection for your applications and to get access to all the features.
- data/README.rdoc +10 -8
- data/Rakefile +1 -1
- data/TODO.rdoc +10 -0
- data/VERSION +1 -1
- data/lib/rstruct/base_types/array_type.rb +42 -0
- data/lib/rstruct/base_types/container_type.rb +20 -6
- data/lib/rstruct/base_types/packed_type.rb +2 -2
- data/lib/rstruct/base_types/type.rb +1 -1
- data/lib/rstruct/base_types.rb +1 -0
- data/lib/rstruct/field.rb +2 -3
- data/lib/rstruct/struct_builder.rb +6 -1
- data/lib/rstruct/structure.rb +6 -13
- data/lib/rstruct/types.rb +1 -1
- data/lib/rstruct.rb +1 -1
- data/samples/pe.rb +262 -0
- data/spec/array_spec.rb +24 -0
- data/spec/structure_spec.rb +55 -1
- data/spec/type_behaviors.rb +8 -48
- metadata +16 -11
data/README.rdoc
CHANGED
@@ -9,7 +9,7 @@ once, and use them in many ways as you see fit. Kind-of like... C structs in
|
|
9
9
|
system/API header files.
|
10
10
|
|
11
11
|
The structure declaration syntax emulates C structures syntax, although Rstruct
|
12
|
-
is not intended to be able to parse C structures from C header files. (
|
12
|
+
is not intended to be able to parse C structures from C header files. (however
|
13
13
|
an addon could conceivably be built to do so with minimal fuss)
|
14
14
|
|
15
15
|
Multiple approaches to parsing and building are supported and more can be added
|
@@ -21,10 +21,10 @@ as well as buffers.
|
|
21
21
|
Rstruct was written because the authors wanted something comparable to C
|
22
22
|
structures without having a strong need for extra 'magic' in parsing or
|
23
23
|
building structures. While there exist numerous options in this space, they
|
24
|
-
all seem to have suffered from a combination of performance and interface
|
25
|
-
which limit their potential (and in some cases, spotty maintenance).
|
26
|
-
tried pretty much all of these alternatives (and even contributed to a
|
27
|
-
in previous projects, the author still decided to write rstruct.
|
24
|
+
all seem to have suffered from a combination of performance and interface
|
25
|
+
issues which limit their potential (and in some cases, spotty maintenance).
|
26
|
+
Having, tried pretty much all of these alternatives (and even contributed to a
|
27
|
+
few) in previous projects, the author still decided to write rstruct.
|
28
28
|
|
29
29
|
That said, the author does not claim Rstruct to be superior to any others, it's
|
30
30
|
just another approach. There are several other excellent binary structure
|
@@ -58,7 +58,8 @@ Here is a trivial example defining and packing a raw structure
|
|
58
58
|
# => "\x00\x00\x00A\x00\x00\x00B\x00\x00\x00C" # on a big endian machine
|
59
59
|
|
60
60
|
|
61
|
-
Here is a fully functional Rstruct parser example using Apple's FAT file
|
61
|
+
Here is a fully functional Rstruct parser example using Apple's FAT file
|
62
|
+
structure.
|
62
63
|
|
63
64
|
# To compare the structs to their C counterparts, see:
|
64
65
|
# http://fxr.watson.org/fxr/source/EXTERNAL_HEADERS/mach-o/fat.h?v=xnu-1228
|
@@ -100,7 +101,7 @@ Here is a fully functional Rstruct parser example using Apple's FAT file structu
|
|
100
101
|
|
101
102
|
fat_arch = get_type(:fat_arch)
|
102
103
|
(head.nfat_arch).times do |i|
|
103
|
-
arch = fat_arch.read(f) # note, it reads to a
|
104
|
+
arch = fat_arch.read(f) # note, it reads to a new object on each arch
|
104
105
|
puts " Architecture #{i}:"
|
105
106
|
puts " " << dump(arch).join("\n ")
|
106
107
|
puts
|
@@ -128,7 +129,8 @@ Here is a fully functional Rstruct parser example using Apple's FAT file structu
|
|
128
129
|
align = 0x0000000c
|
129
130
|
|
130
131
|
|
131
|
-
Please refer to rdoc, the samples/ directory, and unit tests for more
|
132
|
+
Please refer to rdoc, the samples/ directory, and unit tests for more
|
133
|
+
information.
|
132
134
|
|
133
135
|
== Copyright
|
134
136
|
|
data/Rakefile
CHANGED
data/TODO.rdoc
ADDED
@@ -0,0 +1,10 @@
|
|
1
|
+
== TODO
|
2
|
+
|
3
|
+
* DOCUMENTATION!
|
4
|
+
* add a type for unions
|
5
|
+
* really no good reason why we can't support variable length data
|
6
|
+
* add types for odd byte-alignment values like 24-bit, 12-bit, 48-bit, etc.
|
7
|
+
* add a type for handling bitfields
|
8
|
+
* add instance helpers for resolving enums forward and back
|
9
|
+
|
10
|
+
|
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
0.1.
|
1
|
+
0.1.1
|
@@ -0,0 +1,42 @@
|
|
1
|
+
require 'rstruct/registry'
|
2
|
+
require 'rstruct/field'
|
3
|
+
require 'rstruct/base_types'
|
4
|
+
|
5
|
+
module Rstruct
|
6
|
+
class ArrayContainer < Array
|
7
|
+
include ContainerMixins
|
8
|
+
end
|
9
|
+
|
10
|
+
class ArrayType < ContainerType
|
11
|
+
attr_reader :count
|
12
|
+
|
13
|
+
def initialize(name, typ, count, opts={}, &block)
|
14
|
+
opts = opts.dup
|
15
|
+
lkupreg = (opts.delete(:fields_from) || opts.delete(:register) || Rstruct.default_registry)
|
16
|
+
super(name, opts.merge(:register => false), &block)
|
17
|
+
@count = count
|
18
|
+
unless @rtype = lkupreg[typ]
|
19
|
+
raise(InvalidTypeError, "invalid array type #{typ.inspect}")
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
def typ
|
24
|
+
self
|
25
|
+
end
|
26
|
+
|
27
|
+
def fields
|
28
|
+
@fields ||= Array.new(self.count){|i| Field.new(i, @rtype, nil, nil) }.freeze
|
29
|
+
end
|
30
|
+
|
31
|
+
def instance(*values)
|
32
|
+
vals = values.flatten
|
33
|
+
ary = ArrayContainer.new(self.count){|f| vals.shift }
|
34
|
+
ary.rstruct_type = self
|
35
|
+
return ary
|
36
|
+
end
|
37
|
+
|
38
|
+
def offset_of(idx)
|
39
|
+
rtype.sizeof * idx
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
@@ -30,19 +30,19 @@ module Rstruct
|
|
30
30
|
vals = (pvals.respond_to?(:values) ? pvals.values : pvals)
|
31
31
|
vals ||= self.values
|
32
32
|
|
33
|
-
opos = dst.pos
|
33
|
+
opos = dst.pos if dst.respond_to?(:pos)
|
34
34
|
typ.fields.each_with_index do |f, i|
|
35
35
|
fldval = vals[i]
|
36
36
|
if fldval.respond_to?(:write)
|
37
37
|
fldval.write(dst, fldval)
|
38
38
|
else
|
39
|
-
dst.write(f.typ.pack_value(fldval, self))
|
39
|
+
dst.write(f.typ.pack_value((fldval || 0), self))
|
40
40
|
end
|
41
41
|
end
|
42
42
|
if dst.is_a?(StringIO) and pvals.nil?
|
43
43
|
dst.pos = opos
|
44
44
|
return(dst.read)
|
45
|
-
|
45
|
+
elsif opos and dst.respond_to?(:pos)
|
46
46
|
return dst.pos - opos
|
47
47
|
end
|
48
48
|
end
|
@@ -81,22 +81,36 @@ module Rstruct
|
|
81
81
|
end
|
82
82
|
|
83
83
|
def field_names
|
84
|
-
|
84
|
+
self.fields.map{|f| f.name }
|
85
85
|
end
|
86
86
|
|
87
87
|
def field_types
|
88
|
-
|
88
|
+
self.fields.map{|f| f.typ }
|
89
89
|
end
|
90
90
|
|
91
91
|
def read(raw, obj=nil)
|
92
92
|
raw = StringIO.new(raw) if raw.is_a?(String)
|
93
93
|
obj = self.instance()
|
94
94
|
fields.each do |f|
|
95
|
-
obj[f.name] = f.read(raw, obj)
|
95
|
+
obj[f.name] = f.typ.read(raw, obj)
|
96
96
|
end
|
97
97
|
return obj
|
98
98
|
end
|
99
99
|
|
100
|
+
def claim_value(vals, obj=nil)
|
101
|
+
if @claim_cb
|
102
|
+
@claim_cb.call(vals, obj)
|
103
|
+
else
|
104
|
+
# create our struct container
|
105
|
+
s = instance()
|
106
|
+
|
107
|
+
# iterate through the fields assigning values in the
|
108
|
+
# container and pass it along with values to each
|
109
|
+
# field's claim_value method.
|
110
|
+
self.fields.do {|f| s[f.name] = f.typ.claim_value(vals,s) }
|
111
|
+
return s
|
112
|
+
end
|
113
|
+
end
|
100
114
|
end
|
101
115
|
|
102
116
|
end
|
@@ -61,10 +61,10 @@ module Rstruct
|
|
61
61
|
|
62
62
|
class PackedType < Type
|
63
63
|
include Packable
|
64
|
-
attr_reader :
|
64
|
+
attr_reader :sizeof, :format
|
65
65
|
|
66
66
|
def initialize(name, size, format, opts={}, &block)
|
67
|
-
@
|
67
|
+
@sizeof = size
|
68
68
|
@format = format
|
69
69
|
@groupable = true
|
70
70
|
super(name, opts, &block)
|
data/lib/rstruct/base_types.rb
CHANGED
data/lib/rstruct/field.rb
CHANGED
@@ -1,11 +1,10 @@
|
|
1
1
|
module Rstruct
|
2
2
|
class Field
|
3
|
-
attr_reader :name, :
|
3
|
+
attr_reader :name, :typ, :args, :block
|
4
4
|
|
5
|
-
def initialize(name, typ,
|
5
|
+
def initialize(name, typ, args, block)
|
6
6
|
@name=name
|
7
7
|
@typ=typ
|
8
|
-
@typ_name = typ_name || typ
|
9
8
|
@args=args
|
10
9
|
@block=block
|
11
10
|
end
|
@@ -1,5 +1,6 @@
|
|
1
1
|
require 'rstruct/registry'
|
2
2
|
require 'rstruct/field'
|
3
|
+
require 'rstruct/base_types/array_type'
|
3
4
|
|
4
5
|
module Rstruct
|
5
6
|
|
@@ -13,7 +14,11 @@ module Rstruct
|
|
13
14
|
end
|
14
15
|
|
15
16
|
def field(name, typ, typ_name, *args, &block)
|
16
|
-
@__fields << Field.new(name,typ,
|
17
|
+
@__fields << Field.new(name,typ,args,block)
|
18
|
+
end
|
19
|
+
|
20
|
+
def array(name, typ, count, opts={}, &block)
|
21
|
+
@__fields << ArrayType.new(name, typ, count, opts, &block)
|
17
22
|
end
|
18
23
|
|
19
24
|
def method_missing(typ_arg, *args, &block)
|
data/lib/rstruct/structure.rb
CHANGED
@@ -40,20 +40,13 @@ module Rstruct
|
|
40
40
|
return s
|
41
41
|
end
|
42
42
|
|
43
|
-
def
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
s = instance()
|
49
|
-
|
50
|
-
# iterate through the fields assigning values in the
|
51
|
-
# container and pass it along with values to each
|
52
|
-
# field's claim_value method.
|
53
|
-
self.fields.do {|f| s[f.name] = f.typ.claim_value(vals,s) }
|
54
|
-
|
55
|
-
return s
|
43
|
+
def offset_of(fld)
|
44
|
+
o = 0
|
45
|
+
self.fields.each do |f|
|
46
|
+
return o if f.name == fld
|
47
|
+
o += f.sizeof
|
56
48
|
end
|
49
|
+
raise(InvalidTypeError, "Invalid type: #{fld}")
|
57
50
|
end
|
58
51
|
end
|
59
52
|
end
|
data/lib/rstruct/types.rb
CHANGED
data/lib/rstruct.rb
CHANGED
data/samples/pe.rb
ADDED
@@ -0,0 +1,262 @@
|
|
1
|
+
$: << File.join(File.dirname(__FILE__), '..', 'lib')
|
2
|
+
require 'rstruct'
|
3
|
+
|
4
|
+
module Pe
|
5
|
+
extend Rstruct::ClassMethods
|
6
|
+
|
7
|
+
PE_REGISTRY = Rstruct::Registry.new(:portable_executable)
|
8
|
+
|
9
|
+
def self.registry
|
10
|
+
PE_REGISTRY
|
11
|
+
end
|
12
|
+
|
13
|
+
typedef :uint16le, :WORD, :register => registry()
|
14
|
+
typedef :uint32le, :LONG, :register => registry()
|
15
|
+
typedef :uint32le, :DWORD, :register => registry()
|
16
|
+
|
17
|
+
IMAGE_DOS_HEADER = struct(:IMAGE_DOS_HEADER, :register => registry()) {
|
18
|
+
WORD :magic # magic number
|
19
|
+
WORD :cblp # bytes on last page of file
|
20
|
+
WORD :cp # pages in file
|
21
|
+
WORD :crlc # relocations
|
22
|
+
WORD :cparhdr # size of header in paragraphs
|
23
|
+
WORD :minalloc # minimum extra paragraphs needed
|
24
|
+
WORD :maxalloc # maxumum extra paragraphs needed
|
25
|
+
WORD :ss # Initial (relative) SS value
|
26
|
+
WORD :sp # Initial SP value
|
27
|
+
WORD :csum # Checksum
|
28
|
+
WORD :ip # Initial IP value
|
29
|
+
WORD :cs # Initial (relative) CS value
|
30
|
+
WORD :lfarlc # File address of relocation table
|
31
|
+
WORD :ovno # overlay number
|
32
|
+
array :res1, :uint16le, 4
|
33
|
+
WORD :oemid # OEM identifier
|
34
|
+
WORD :oeminfo # OEM information; oem specific
|
35
|
+
array :res2, :uint16le, 10
|
36
|
+
LONG :lfanew # File address of new exe header (PE header)
|
37
|
+
}
|
38
|
+
|
39
|
+
|
40
|
+
|
41
|
+
IMAGE_FILE_MACHINE_TYPES = {
|
42
|
+
0 => :UNKNOWN,
|
43
|
+
0x014C => :I386,
|
44
|
+
0x8664 => :AMD64,
|
45
|
+
0x01C0 => :ARM,
|
46
|
+
0x0162 => :R3000,
|
47
|
+
0x0166 => :R4000,
|
48
|
+
0x0168 => :R10000,
|
49
|
+
0x0169 => :WCEMIPSV2,
|
50
|
+
0x0184 => :ALPHA,
|
51
|
+
0x01A2 => :SH3,
|
52
|
+
0x01A3 => :SH3DSP,
|
53
|
+
0x01A4 => :SH3E,
|
54
|
+
0x01A6 => :SH4,
|
55
|
+
0x01A8 => :SH5,
|
56
|
+
0x01C2 => :THUMB,
|
57
|
+
0x01D3 => :AM33,
|
58
|
+
0x01F0 => :POWERPC,
|
59
|
+
0x01F1 => :POWERPCFP,
|
60
|
+
0x0200 => :IA64,
|
61
|
+
0x0266 => :MIPS16,
|
62
|
+
0x0284 => :ALPHA64,
|
63
|
+
0x0366 => :MIPSFPU,
|
64
|
+
0x0466 => :MIPSFPU16,
|
65
|
+
0x0520 => :TRICORE,
|
66
|
+
0x0CEF => :CEF,
|
67
|
+
0x0EBC => :EBC,
|
68
|
+
0x9041 => :M32R,
|
69
|
+
0xC0EE => :CEE,
|
70
|
+
}
|
71
|
+
|
72
|
+
IMAGE_FILE_CHARACTERISTICS_FLAGS = {
|
73
|
+
:RELOCS_STRIPPED => 0x0001,
|
74
|
+
:EXECUTABLE_IMAGE => 0x0002,
|
75
|
+
:LINE_NUMS_STRIPPED => 0x0004,
|
76
|
+
:LOCAL_SYMS_STRIPPED => 0x0008,
|
77
|
+
:AGGRESIVE_WS_TRIM => 0x0010,
|
78
|
+
:LARGE_ADDRESS_AWARE => 0x0020,
|
79
|
+
:BYTES_REVERSED_LO => 0x0080,
|
80
|
+
:MACHINE_32BIT => 0x0100,
|
81
|
+
:DEBUG_STRIPPED => 0x0200,
|
82
|
+
:REMOVABLE_RUN_FROM_SWAP => 0x0400,
|
83
|
+
:NET_RUN_FROM_SWAP => 0x0800,
|
84
|
+
:SYSTEM => 0x1000,
|
85
|
+
:DLL => 0x2000,
|
86
|
+
:UNIPROC_SYSTEM_ONLY => 0x4000,
|
87
|
+
:BYTES_REVERSED_HI => 0x8000,
|
88
|
+
}
|
89
|
+
|
90
|
+
IMAGE_FILE_HEADER = struct(:IMAGE_FILE_HEADER, :register => registry()) {
|
91
|
+
WORD :Machine
|
92
|
+
WORD :NumberOfSections
|
93
|
+
DWORD :TimeDateStamp
|
94
|
+
DWORD :PointerToSymbolTable
|
95
|
+
DWORD :NumberOfSymbols
|
96
|
+
WORD :SizeOfOptionalHeader
|
97
|
+
WORD :Characteristics
|
98
|
+
}
|
99
|
+
|
100
|
+
IMAGE_DATA_DIRECTORY = struct(:IMAGE_DATA_DIRECTORY, :register => registry()) {
|
101
|
+
DWORD :VirtualAddress
|
102
|
+
DWORD :Size
|
103
|
+
}
|
104
|
+
|
105
|
+
IMAGE_NUMBEROF_DIRECTORY_ENTRIES = 16
|
106
|
+
|
107
|
+
IMAGE_NT_OPTIONAL_HDR_MAGIC = {
|
108
|
+
0x10b => :HDR32,
|
109
|
+
0x20b => :HDR64,
|
110
|
+
0x107 => :ROM,
|
111
|
+
}
|
112
|
+
|
113
|
+
IMAGE_NT_OPTIONAL_HDR_SUBSYSTEMS = [
|
114
|
+
:UNKNOWN,
|
115
|
+
:NATIVE,
|
116
|
+
:WINDOWS_GUI,
|
117
|
+
:WINDOWS_CONSOLE,
|
118
|
+
nil,
|
119
|
+
:OS2_CONSOLE,
|
120
|
+
nil,
|
121
|
+
:POSIX_CONSOLE,
|
122
|
+
:NATIVE_WIN9X_DRIVER,
|
123
|
+
:WINDOWS_CE_GUI,
|
124
|
+
:EFI_APPLICATION,
|
125
|
+
:EFI_BOOT_SERVICE_DRIVER,
|
126
|
+
:EFI_RUNTIME_DRIVER,
|
127
|
+
:EFI_ROM,
|
128
|
+
:XBOX,
|
129
|
+
nil,
|
130
|
+
:WINDOWS_BOOT_APPLICATION,
|
131
|
+
]
|
132
|
+
|
133
|
+
IMAGE_DLL_CHARACTERISTICS_FLAGS = {
|
134
|
+
:DYNAMIC_BASE => 0x0080,
|
135
|
+
:FORCE_INTEGRITY => 0x0080,
|
136
|
+
:NX_COMPAT => 0x0100,
|
137
|
+
:NO_ISOLATION => 0x0200,
|
138
|
+
:NO_SEH => 0x0400,
|
139
|
+
:NO_BIND => 0x0800,
|
140
|
+
:WDM_DRIVER => 0x2000,
|
141
|
+
:TERM_SVR_AWARE => 0x8000,
|
142
|
+
}
|
143
|
+
|
144
|
+
IMAGE_OPTIONAL_HEADER = struct(:IMAGE_OPTIONAL_HEADER, :register => registry()) {
|
145
|
+
WORD :Magic
|
146
|
+
BYTE :MajorLinkerVersion
|
147
|
+
BYTE :MinorLinkerVersion
|
148
|
+
DWORD :SizeOfCode
|
149
|
+
DWORD :SizeOfInitializedData
|
150
|
+
DWORD :SizeOfUninitializedData
|
151
|
+
DWORD :AddressOfEntryPoint
|
152
|
+
DWORD :BaseOfCode
|
153
|
+
DWORD :BaseOfData
|
154
|
+
DWORD :ImageBase
|
155
|
+
DWORD :SectionAlignment
|
156
|
+
DWORD :FileAlignment
|
157
|
+
WORD :MajorOperatingSystemVersion
|
158
|
+
WORD :MinorOperatingSystemVersion
|
159
|
+
WORD :MajorImageVersion
|
160
|
+
WORD :MinorImageVersion
|
161
|
+
WORD :MajorSubsystemVersion
|
162
|
+
WORD :MinorSubsystemVersion
|
163
|
+
DWORD :Win32VersionValue
|
164
|
+
DWORD :SizeOfImage
|
165
|
+
DWORD :SizeOfHeaders
|
166
|
+
DWORD :CheckSum
|
167
|
+
WORD :Subsystem
|
168
|
+
WORD :DllCharacteristics
|
169
|
+
DWORD :SizeOfStackReserve
|
170
|
+
DWORD :SizeOfStackCommit
|
171
|
+
DWORD :SizeOfHeapReserve
|
172
|
+
DWORD :SizeOfHeapCommit
|
173
|
+
DWORD :LoaderFlags
|
174
|
+
DWORD :NumberOfRvaAndSizes
|
175
|
+
|
176
|
+
IMAGE_DATA_DIRECTORY :DDExportTable
|
177
|
+
IMAGE_DATA_DIRECTORY :DDImportTable
|
178
|
+
IMAGE_DATA_DIRECTORY :DDResourceTable
|
179
|
+
IMAGE_DATA_DIRECTORY :DDExceptionTable
|
180
|
+
IMAGE_DATA_DIRECTORY :DDCertificatTable
|
181
|
+
IMAGE_DATA_DIRECTORY :DDBaseRelocationTable
|
182
|
+
IMAGE_DATA_DIRECTORY :DDDebuggingInformation
|
183
|
+
IMAGE_DATA_DIRECTORY :DDArchSpecificData
|
184
|
+
IMAGE_DATA_DIRECTORY :DDGlobalPointerRegisterRVA
|
185
|
+
IMAGE_DATA_DIRECTORY :DDTlsTable
|
186
|
+
IMAGE_DATA_DIRECTORY :DDLoadConfigurationTable
|
187
|
+
IMAGE_DATA_DIRECTORY :DDBoundImportTable
|
188
|
+
IMAGE_DATA_DIRECTORY :DDImportAddressTable
|
189
|
+
IMAGE_DATA_DIRECTORY :DDDelayImportDescriptor
|
190
|
+
IMAGE_DATA_DIRECTORY :DDClrHeader
|
191
|
+
IMAGE_DATA_DIRECTORY :DDReserved
|
192
|
+
}
|
193
|
+
|
194
|
+
IMAGE_NT_HEADERS = struct(:IMAGE_NT_HEADERS, :register => registry()) {
|
195
|
+
DWORD :Signature
|
196
|
+
IMAGE_FILE_HEADER :FileHeader
|
197
|
+
IMAGE_OPTIONAL_HEADER :OptionalHeader
|
198
|
+
}
|
199
|
+
|
200
|
+
IMAGE_SIZEOF_SHORT_NAME = 8
|
201
|
+
|
202
|
+
IMAGE_SECTION_HEADER = struct(:IMAGE_SECTION_HEADER, :register => registry()) {
|
203
|
+
array :Name, :char, 8
|
204
|
+
DWORD :PhysicalAddress
|
205
|
+
DWORD :VirtualAddress
|
206
|
+
DWORD :SizeOfRawData
|
207
|
+
DWORD :PointerToRawData
|
208
|
+
DWORD :PointerToRelocations
|
209
|
+
DWORD :PointerToLinenumbers
|
210
|
+
WORD :NumberOfRelocations
|
211
|
+
WORD :NumberOfLinenumbers
|
212
|
+
DWORD :Characteristics
|
213
|
+
}
|
214
|
+
|
215
|
+
def self.parse_header(*args)
|
216
|
+
Headers.new(*args)
|
217
|
+
end
|
218
|
+
|
219
|
+
class Headers
|
220
|
+
attr_reader :dos, :stub, :coff, :sections, :file_data
|
221
|
+
|
222
|
+
def initialize(input, param = {})
|
223
|
+
input = StringIO.new(input) if input.is_a?(String)
|
224
|
+
@dos = IMAGE_DOS_HEADER.read(input)
|
225
|
+
@stub = input.read(@dos.lfanew - IMAGE_DOS_HEADER.sizeof)
|
226
|
+
@coff = IMAGE_NT_HEADERS.read(input)
|
227
|
+
@sections = Array.new(@coff.FileHeader.NumberOfSections) do
|
228
|
+
IMAGE_SECTION_HEADER.read(input)
|
229
|
+
end
|
230
|
+
end
|
231
|
+
|
232
|
+
# Returns a Time object representation of the
|
233
|
+
# PE header timestamp.
|
234
|
+
def timestamp
|
235
|
+
Time.at(coff.FileHeader.TimeDateStamp)
|
236
|
+
end
|
237
|
+
|
238
|
+
# Returns the size of the PE file based on header
|
239
|
+
# information. Specifically, the last section's
|
240
|
+
# PointerToRawData is added to its SizeOfRawData
|
241
|
+
# which should be equal to the file size.
|
242
|
+
def file_size
|
243
|
+
lseg = self.sections.last
|
244
|
+
lseg.PointerToRawData + lseg.SizeOfRawData
|
245
|
+
end
|
246
|
+
|
247
|
+
def repack(io=nil)
|
248
|
+
io ||= StringIO.new
|
249
|
+
self.dos.write(io)
|
250
|
+
io.write(self.stub)
|
251
|
+
self.coff.write(io)
|
252
|
+
self.sections.each{ |s| s.write(io) }
|
253
|
+
io.string if io.is_a?(StringIO)
|
254
|
+
end
|
255
|
+
|
256
|
+
# the checksum value from the parsed header
|
257
|
+
def checksum
|
258
|
+
coff.OptionalHeader.CheckSum
|
259
|
+
end
|
260
|
+
|
261
|
+
end
|
262
|
+
end
|
data/spec/array_spec.rb
ADDED
@@ -0,0 +1,24 @@
|
|
1
|
+
require File.expand_path(File.dirname(__FILE__) + '/spec_helper')
|
2
|
+
require 'type_behaviors'
|
3
|
+
|
4
|
+
describe Rstruct::ArrayType do
|
5
|
+
context "a basic character array" do
|
6
|
+
before(:each) do
|
7
|
+
@struct = Rstruct.struct(:array_test, :register => false) {
|
8
|
+
array :test_array, :char, 8
|
9
|
+
}
|
10
|
+
|
11
|
+
@pack_format = "aaaaaaaa"
|
12
|
+
|
13
|
+
@instance = @struct.instance
|
14
|
+
|
15
|
+
@rawdata = "ABCDEFGH"
|
16
|
+
@populate = lambda { @rawdata.bytes.each_with_index{|b,i| @instance.test_array[i] = b.chr} }
|
17
|
+
@verify_unpack = lambda {|ret| @rawdata.bytes.each_with_index{|b,i| ret.test_array[i].should == b.chr } }
|
18
|
+
end
|
19
|
+
|
20
|
+
it_should_behave_like "a packable type"
|
21
|
+
it_should_behave_like "a groupable type"
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
data/spec/structure_spec.rb
CHANGED
@@ -1,6 +1,50 @@
|
|
1
1
|
require File.expand_path(File.dirname(__FILE__) + '/spec_helper')
|
2
2
|
require 'type_behaviors'
|
3
3
|
|
4
|
+
# Applies to structures
|
5
|
+
shared_examples_for "a structure" do
|
6
|
+
|
7
|
+
it "should return a value instance with a reference back to itself" do
|
8
|
+
s = @struct.instance()
|
9
|
+
s.rstruct_type.should == @struct
|
10
|
+
end
|
11
|
+
|
12
|
+
context "struct instance" do
|
13
|
+
it "should expose the same fields as the struct they belong to" do
|
14
|
+
@struct.field_names.each do |name|
|
15
|
+
name = name.to_s if RUBY_VERSION < '1.9'
|
16
|
+
@instance.members.should include(name)
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
it "should allow struct field values to be set and retrieved with accessors" do
|
21
|
+
@values.each do |k,v|
|
22
|
+
@instance.__send__(k).should be_nil
|
23
|
+
@instance.__send__("#{k}=", v).should == v
|
24
|
+
@instance.__send__(k).should == v
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
it "should allow field values to be set with arguments to instance creation" do
|
29
|
+
s=@struct.instance(@values)
|
30
|
+
s.rstruct_type.should == @struct
|
31
|
+
@values.each { |k,v| s.__send__(k).should == v }
|
32
|
+
end
|
33
|
+
|
34
|
+
it "should yield itself to a block on instance creation" do
|
35
|
+
i=@struct.instance do |s|
|
36
|
+
@values.each do |k,v|
|
37
|
+
s.__send__(k).should be_nil
|
38
|
+
s.__send__("#{k}=", v).should == v
|
39
|
+
s.__send__(k).should == v
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
@values.each { |k,v| i.__send__(k).should == v }
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
4
48
|
describe Rstruct::Structure do
|
5
49
|
context "initialization" do
|
6
50
|
it "requires a block" do
|
@@ -208,6 +252,16 @@ describe Rstruct::Structure do
|
|
208
252
|
it_should_behave_like "a structure"
|
209
253
|
it_should_behave_like "a packable type"
|
210
254
|
it_should_behave_like "a groupable type"
|
255
|
+
|
256
|
+
it "should return the correct offset for fields" do
|
257
|
+
@struct.offset_of(:magic).should == 0
|
258
|
+
@struct.offset_of(:cputype).should == 4
|
259
|
+
@struct.offset_of(:cpusubtype).should == 8
|
260
|
+
@struct.offset_of(:filetype).should == 12
|
261
|
+
@struct.offset_of(:ncmds).should == 16
|
262
|
+
@struct.offset_of(:sizeofcmds).should == 20
|
263
|
+
@struct.offset_of(:flags).should == 24
|
264
|
+
end
|
211
265
|
end
|
212
266
|
|
213
267
|
context "a simple fixed-length nested struct" do
|
@@ -278,7 +332,7 @@ describe Rstruct::Structure do
|
|
278
332
|
double_inner_values.each {|k,v| @instance.inner.double_inner[k] = v}
|
279
333
|
end
|
280
334
|
|
281
|
-
@pack_format = "
|
335
|
+
@pack_format = "NNccaa"
|
282
336
|
@rawdata = "\xde\xad\xbe\xef\xfa\xce\xfe\xeb\x01\x02\x41\x42"
|
283
337
|
|
284
338
|
@verify_unpack = lambda do |ret|
|
data/spec/type_behaviors.rb
CHANGED
@@ -7,23 +7,23 @@ shared_examples_for "a packable type" do
|
|
7
7
|
it "should write raw data and return a string when no output is specified" do
|
8
8
|
@populate.call()
|
9
9
|
ret = @instance.write()
|
10
|
-
ret.should == @rawdata
|
10
|
+
ret.bytes.to_a.should == @rawdata.bytes.to_a
|
11
11
|
end
|
12
12
|
|
13
13
|
it "should write raw data to a string object correctly" do
|
14
14
|
s = "test"
|
15
15
|
@populate.call()
|
16
16
|
ret = @instance.write(s)
|
17
|
-
ret.should == @rawdata
|
18
|
-
s.should == "test
|
17
|
+
ret.bytes.to_a.should == @rawdata.bytes.to_a
|
18
|
+
s.bytes.to_a.should == "test#{@rawdata}".bytes.to_a
|
19
19
|
end
|
20
20
|
|
21
21
|
it "should write raw data to a StringIO object correctly" do
|
22
22
|
sio = StringIO.new
|
23
23
|
@populate.call()
|
24
24
|
ret = @instance.write(sio)
|
25
|
-
ret.should == @rawdata
|
26
|
-
sio.string.should == @rawdata
|
25
|
+
ret.bytes.to_a.should == @rawdata.bytes.to_a
|
26
|
+
sio.string.bytes.to_a.should == @rawdata.bytes.to_a
|
27
27
|
end
|
28
28
|
|
29
29
|
it "should write raw data to a File IO object correctly" do
|
@@ -34,7 +34,7 @@ shared_examples_for "a packable type" do
|
|
34
34
|
ret = @instance.write(tempf)
|
35
35
|
ret.should == @rawdata.size
|
36
36
|
tempf.rewind
|
37
|
-
tempf.read.should == "test
|
37
|
+
tempf.read.bytes.to_a.should == "test#{@rawdata}".bytes.to_a
|
38
38
|
ensure
|
39
39
|
tempf.close if tempf
|
40
40
|
end
|
@@ -81,7 +81,7 @@ shared_examples_for "a packable type" do
|
|
81
81
|
else
|
82
82
|
@values.each {|k,v| ret.__send__(k).should == v }
|
83
83
|
end
|
84
|
-
fio.read().should == testend
|
84
|
+
fio.read().bytes.to_a.should == testend.bytes.to_a
|
85
85
|
ensure
|
86
86
|
fio.close if fio
|
87
87
|
end
|
@@ -96,7 +96,7 @@ shared_examples_for "a packable type" do
|
|
96
96
|
@values.each {|k,v| ret.__send__(k).should == v }
|
97
97
|
end
|
98
98
|
repacked = ret.write()
|
99
|
-
repacked.should == @rawdata
|
99
|
+
repacked.bytes.to_a.should == @rawdata.bytes.to_a
|
100
100
|
end
|
101
101
|
|
102
102
|
end
|
@@ -115,44 +115,4 @@ shared_examples_for "a groupable type" do
|
|
115
115
|
end
|
116
116
|
end
|
117
117
|
|
118
|
-
# Applies to structures
|
119
|
-
shared_examples_for "a structure" do
|
120
|
-
|
121
|
-
it "should return a value instance with a reference back to itself" do
|
122
|
-
s = @struct.instance()
|
123
|
-
s.rstruct_type.should == @struct
|
124
|
-
end
|
125
|
-
|
126
|
-
context "struct instance" do
|
127
|
-
it "should expose the same fields as the struct they belong to" do
|
128
|
-
@struct.field_names.each {|name| @instance.members.should include(name.to_s) }
|
129
|
-
end
|
130
|
-
|
131
|
-
it "should allow struct field values to be set and retrieved with accessors" do
|
132
|
-
@values.each do |k,v|
|
133
|
-
@instance.__send__(k).should be_nil
|
134
|
-
@instance.__send__("#{k}=", v).should == v
|
135
|
-
@instance.__send__(k).should == v
|
136
|
-
end
|
137
|
-
end
|
138
|
-
|
139
|
-
it "should allow field values to be set with arguments to instance creation" do
|
140
|
-
s=@struct.instance(@values)
|
141
|
-
s.rstruct_type.should == @struct
|
142
|
-
@values.each { |k,v| s.__send__(k).should == v }
|
143
|
-
end
|
144
|
-
|
145
|
-
it "should yield itself to a block on instance creation" do
|
146
|
-
i=@struct.instance do |s|
|
147
|
-
@values.each do |k,v|
|
148
|
-
s.__send__(k).should be_nil
|
149
|
-
s.__send__("#{k}=", v).should == v
|
150
|
-
s.__send__(k).should == v
|
151
|
-
end
|
152
|
-
end
|
153
|
-
|
154
|
-
@values.each { |k,v| i.__send__(k).should == v }
|
155
|
-
end
|
156
|
-
end
|
157
|
-
end
|
158
118
|
|
metadata
CHANGED
@@ -5,8 +5,8 @@ version: !ruby/object:Gem::Version
|
|
5
5
|
segments:
|
6
6
|
- 0
|
7
7
|
- 1
|
8
|
-
-
|
9
|
-
version: 0.1.
|
8
|
+
- 1
|
9
|
+
version: 0.1.1
|
10
10
|
platform: ruby
|
11
11
|
authors:
|
12
12
|
- Eric Monti
|
@@ -14,10 +14,12 @@ autorequire:
|
|
14
14
|
bindir: bin
|
15
15
|
cert_chain: []
|
16
16
|
|
17
|
-
date: 2011-
|
17
|
+
date: 2011-08-25 00:00:00 -05:00
|
18
18
|
default_executable:
|
19
19
|
dependencies:
|
20
20
|
- !ruby/object:Gem::Dependency
|
21
|
+
name: rspec
|
22
|
+
prerelease: false
|
21
23
|
requirement: &id001 !ruby/object:Gem::Requirement
|
22
24
|
requirements:
|
23
25
|
- - ~>
|
@@ -27,11 +29,11 @@ dependencies:
|
|
27
29
|
- 3
|
28
30
|
- 0
|
29
31
|
version: 2.3.0
|
30
|
-
prerelease: false
|
31
32
|
type: :development
|
32
|
-
name: rspec
|
33
33
|
version_requirements: *id001
|
34
34
|
- !ruby/object:Gem::Dependency
|
35
|
+
name: bundler
|
36
|
+
prerelease: false
|
35
37
|
requirement: &id002 !ruby/object:Gem::Requirement
|
36
38
|
requirements:
|
37
39
|
- - ~>
|
@@ -41,11 +43,11 @@ dependencies:
|
|
41
43
|
- 0
|
42
44
|
- 0
|
43
45
|
version: 1.0.0
|
44
|
-
prerelease: false
|
45
46
|
type: :development
|
46
|
-
name: bundler
|
47
47
|
version_requirements: *id002
|
48
48
|
- !ruby/object:Gem::Dependency
|
49
|
+
name: jeweler
|
50
|
+
prerelease: false
|
49
51
|
requirement: &id003 !ruby/object:Gem::Requirement
|
50
52
|
requirements:
|
51
53
|
- - ~>
|
@@ -55,11 +57,11 @@ dependencies:
|
|
55
57
|
- 5
|
56
58
|
- 2
|
57
59
|
version: 1.5.2
|
58
|
-
prerelease: false
|
59
60
|
type: :development
|
60
|
-
name: jeweler
|
61
61
|
version_requirements: *id003
|
62
62
|
- !ruby/object:Gem::Dependency
|
63
|
+
name: rcov
|
64
|
+
prerelease: false
|
63
65
|
requirement: &id004 !ruby/object:Gem::Requirement
|
64
66
|
requirements:
|
65
67
|
- - ">="
|
@@ -67,9 +69,7 @@ dependencies:
|
|
67
69
|
segments:
|
68
70
|
- 0
|
69
71
|
version: "0"
|
70
|
-
prerelease: false
|
71
72
|
type: :development
|
72
|
-
name: rcov
|
73
73
|
version_requirements: *id004
|
74
74
|
description: A library for working with Ruby binary structures in a way similar to c-structs
|
75
75
|
email: esmonti@gmail.com
|
@@ -87,9 +87,11 @@ files:
|
|
87
87
|
- LICENSE.txt
|
88
88
|
- README.rdoc
|
89
89
|
- Rakefile
|
90
|
+
- TODO.rdoc
|
90
91
|
- VERSION
|
91
92
|
- lib/rstruct.rb
|
92
93
|
- lib/rstruct/base_types.rb
|
94
|
+
- lib/rstruct/base_types/array_type.rb
|
93
95
|
- lib/rstruct/base_types/container_type.rb
|
94
96
|
- lib/rstruct/base_types/packed_type.rb
|
95
97
|
- lib/rstruct/base_types/type.rb
|
@@ -99,6 +101,8 @@ files:
|
|
99
101
|
- lib/rstruct/structure.rb
|
100
102
|
- lib/rstruct/types.rb
|
101
103
|
- samples/fatparse.rb
|
104
|
+
- samples/pe.rb
|
105
|
+
- spec/array_spec.rb
|
102
106
|
- spec/registry_behaviors.rb
|
103
107
|
- spec/registry_spec.rb
|
104
108
|
- spec/rstruct_spec.rb
|
@@ -136,6 +140,7 @@ signing_key:
|
|
136
140
|
specification_version: 3
|
137
141
|
summary: A library for working with Ruby binary structures in a way similar to c-structs
|
138
142
|
test_files:
|
143
|
+
- spec/array_spec.rb
|
139
144
|
- spec/registry_behaviors.rb
|
140
145
|
- spec/registry_spec.rb
|
141
146
|
- spec/rstruct_spec.rb
|