indis-core 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,21 @@
1
+ ##############################################################################
2
+ # Indis framework #
3
+ # Copyright (C) 2012 Vladimir "Farcaller" Pouzanov <farcaller@gmail.com> #
4
+ # #
5
+ # This program is free software: you can redistribute it and/or modify #
6
+ # it under the terms of the GNU General Public License as published by #
7
+ # the Free Software Foundation, either version 3 of the License, or #
8
+ # (at your option) any later version. #
9
+ # #
10
+ # This program is distributed in the hope that it will be useful, #
11
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of #
12
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the #
13
+ # GNU General Public License for more details. #
14
+ # #
15
+ # You should have received a copy of the GNU General Public License #
16
+ # along with this program. If not, see <http://www.gnu.org/licenses/>. #
17
+ ##############################################################################
18
+
19
+ module Indis
20
+ VERSION = "0.1.0"
21
+ end
@@ -0,0 +1,141 @@
1
+ ##############################################################################
2
+ # Indis framework #
3
+ # Copyright (C) 2012 Vladimir "Farcaller" Pouzanov <farcaller@gmail.com> #
4
+ # #
5
+ # This program is free software: you can redistribute it and/or modify #
6
+ # it under the terms of the GNU General Public License as published by #
7
+ # the Free Software Foundation, either version 3 of the License, or #
8
+ # (at your option) any later version. #
9
+ # #
10
+ # This program is distributed in the hope that it will be useful, #
11
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of #
12
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the #
13
+ # GNU General Public License for more details. #
14
+ # #
15
+ # You should have received a copy of the GNU General Public License #
16
+ # along with this program. If not, see <http://www.gnu.org/licenses/>. #
17
+ ##############################################################################
18
+
19
+ module Indis
20
+
21
+ # VMMap provides access to target's virtaul memory address space
22
+ class VMMap
23
+ def initialize(target)
24
+ @target = target
25
+ @blocks = {}
26
+ end
27
+
28
+ # @return [Indis::Segment] a segment at given address or nil
29
+ def segment_at(ofs)
30
+ @target.segments.each.find { |s| (s.vmaddr...s.vmaddr+s.vmsize).include? ofs }
31
+ end
32
+
33
+ # @return [Indis::Section] a section at given address or nil
34
+ def section_at(ofs)
35
+ seg = segment_at(ofs)
36
+ return nil unless seg
37
+
38
+ seg.sections.each.find { |s| (s.vmaddr...s.vmaddr+s.vmsize).include? ofs }
39
+ end
40
+
41
+ # @return True if the address belongs to some segment
42
+ def address_valid?(ofs)
43
+ segment_at(ofs) != nil
44
+ end
45
+
46
+ # @return [Fixnum] a byte value at given virtual address
47
+ def byte_at(ofs)
48
+ seg = segment_at(ofs)
49
+ return nil unless seg
50
+
51
+ sect = section_at(ofs) # TODO: optimize here
52
+ s = sect || seg
53
+
54
+ s.bytes[ofs-s.vmaddr].ord
55
+ end
56
+
57
+ # @return [Array] a list of bytes at given virtual address span
58
+ def bytes_at(ofs, size)
59
+ seg = segment_at(ofs)
60
+ return nil unless seg
61
+
62
+ sect = section_at(ofs) # TODO: optimize here
63
+ s = sect || seg
64
+
65
+ st = ofs-s.vmaddr
66
+ ed = st + size
67
+ s.bytes[st...ed].unpack('C*')
68
+ end
69
+
70
+ # @return [Indis::Entity] an entity mapped to given address
71
+ def entity_at(ofs)
72
+ @blocks[ofs]
73
+ end
74
+
75
+ # @return True if the given range contains no entities
76
+ def has_unmapped(ofs, size)
77
+ size.times { |o| return false if entity_at(ofs+o) }
78
+ true
79
+ end
80
+
81
+ # Maps an {Indis::Entity entity} (based on its offset).
82
+ # @raise [ArgumentError] if the range is occupied by another entity
83
+ def map(e)
84
+ raise ArgumentError unless has_unmapped(e.vmaddr, e.size)
85
+ @blocks[e.vmaddr] = e
86
+ end
87
+
88
+ # Forcefully maps an {Indis::Entity entity} (based on its offset) unmapping
89
+ # any other entities in the same address range
90
+ def map!(e)
91
+ (e.vmaddr...(e.vmaddr+e.size)).each do |ofs|
92
+ b = @blocks[ofs]
93
+ if b
94
+ b.unmap
95
+ @blocks[ofs] = nil
96
+ end
97
+ end
98
+ map(e)
99
+ end
100
+
101
+ # @overload [](ofs)
102
+ # Returns an entity at given address, same as {VMMap#entity_at}
103
+ # @todo should also return one-byte
104
+ # @param [Fixnum] range offset for entity
105
+ # @return [Indis::Entity] an entity mapped to given address
106
+ # @overload [](range)
107
+ # Returns a list of entities and bytes at given range. The resulting Array
108
+ # contains all bytes in range. For each {Indis::Entity entity} it is mapped
109
+ # "as-is" and the following bytes up to {Indis::Entity#size} are filled with
110
+ # nil's. For each unmapped byte it is returned as a Fixnum
111
+ #
112
+ # @param [Range] range range
113
+ # @return [Array] mapped entities
114
+ # @raise [ArgumentError] if there is no segment at given range or the range spans several segments
115
+ def [](range)
116
+ return entity_at(range) if range.is_a?(Fixnum)
117
+
118
+ raise ArgumentError unless range.is_a?(Range)
119
+ seg = segment_at(range.begin)
120
+ raise ArgumentError unless seg
121
+ raise ArgumentError unless seg == segment_at(range.max)
122
+
123
+ a = []
124
+ ofs = range.begin
125
+
126
+ begin
127
+ b = @blocks[ofs]
128
+ if b
129
+ a << b
130
+ (b.size-1).times { a << nil }
131
+ ofs += b.size
132
+ else
133
+ a << seg.bytes[ofs-seg.vmaddr].ord
134
+ ofs += 1
135
+ end
136
+ end while ofs <= range.max
137
+ a
138
+ end
139
+ end
140
+
141
+ end
Binary file
@@ -0,0 +1,7 @@
1
+ require 'indis-core/binary_format'
2
+
3
+ describe Indis::BinaryFormat do
4
+ it "provides no formats on its own" do
5
+ Indis::BinaryFormat.known_formats.length.should == 0
6
+ end
7
+ end
@@ -0,0 +1,13 @@
1
+ require 'indis-core/data_entity'
2
+
3
+ describe Indis::DataEntity do
4
+ it "should load its value from vmmap" do
5
+ map = double('VMMap', bytes_at: [1, 2, 3, 4])
6
+ e = Indis::DataEntity.new(0, 4, map)
7
+ e.to_s.should == "DCD\t04030201"
8
+ end
9
+
10
+ it "should raise an erorr if the size is bad" do
11
+ expect { Indis::DataEntity.new(0, 3, nil) }.to raise_error(ArgumentError)
12
+ end
13
+ end
@@ -0,0 +1,14 @@
1
+ require 'indis-core/section'
2
+
3
+ describe Indis::Section do
4
+ it "should provide a correct range for its vm region" do
5
+ sect = Indis::Section.new(double('Segment'), '__text', 0x4096, 120, 0)
6
+ sect.to_vmrange.should == (0x4096..0x410e)
7
+ end
8
+
9
+ it "should provide bytes value from segment based on io offset" do
10
+ seg = double('Segment', bytes: 'qwertyuiop', iooff: 100)
11
+ sect = Indis::Section.new(seg, '__text', 0x4096, 4, 102)
12
+ sect.bytes.should == 'erty'
13
+ end
14
+ end
@@ -0,0 +1,19 @@
1
+ require 'indis-core/segment'
2
+
3
+ describe Indis::Segment do
4
+ it "should provide a correct range for its vm region" do
5
+ seg = Indis::Segment.new(double('Target'), '__TEXT', 0x4096, 120, 0, '')
6
+ seg.to_vmrange.should == (0x4096..0x410e)
7
+ end
8
+
9
+ it "should provide bytes value based on io offset" do
10
+ seg = Indis::Segment.new(double('Target'), '__TEXT', 0x4096, 10, 0, 'qwertyuiop')
11
+ seg.bytes.should == 'qwertyuiop'
12
+ end
13
+
14
+ it "should zero-pad bytes if the vm size is bigger" do
15
+ seg = Indis::Segment.new(double('Target'), '__TEXT', 0x4096, 120, 0, 'qwertyuiop')
16
+ seg.bytes.length.should == 120
17
+ seg.bytes.should == 'qwertyuiop' + ("\0"*110)
18
+ end
19
+ end
@@ -0,0 +1,5 @@
1
+ require 'indis-core/symbol'
2
+
3
+ describe Indis::Symbol do
4
+
5
+ end
@@ -0,0 +1,18 @@
1
+ require 'indis-core/target'
2
+
3
+ describe Indis::Target do
4
+ it "should require an existing file to operate" do
5
+ expect { Indis::Target.new("qwerty") }.to raise_error
6
+ end
7
+
8
+ it "should load file for known format" do
9
+ fmt = double('MachO', magic: 0xfeedface, name: 'Mach-O', new: nil)
10
+ Indis::BinaryFormat.stub(:known_formats).and_return([fmt])
11
+ t = Indis::Target.new("spec/fixtures/single-object.o")
12
+ t.io.length.should_not == 0
13
+ end
14
+
15
+ it "should raise if there is no known format to process binary" do
16
+ expect { Indis::Target.new("spec/fixtures/single-object.o") }.to raise_error(RuntimeError)
17
+ end
18
+ end
@@ -0,0 +1,168 @@
1
+ require 'indis-core/vmmap'
2
+ require 'indis-core/entity'
3
+ require 'indis-core/data_entity'
4
+
5
+ def vmmap_target_double
6
+ double('Target', segments: [
7
+ double('Segment', name: '__PAGEZERO', vmaddr: 0, vmsize: 4096, sections: [],
8
+ bytes: ("\x00"*4096).force_encoding('BINARY'), iooff: 0, to_vmrange: 0...4096),
9
+ double('Segment', name: '__TEXT', vmaddr: 4096, vmsize: 8192, sections: [
10
+ double('Section', name: '__text', vmaddr: 8584, vmsize: 1104, bytes: ("\x0"*1104).force_encoding('BINARY')),
11
+ double('Section', name: '__stub_helper', vmaddr: 9688, vmsize: 156, bytes: ("\x0"*156).force_encoding('BINARY')),
12
+ double('Section', name: '__objc_methname', vmaddr: 9844, vmsize: 1371, bytes: ("\x0"*1371).force_encoding('BINARY')),
13
+ double('Section', name: '__cstring', vmaddr: 11215, vmsize: 148, bytes: ("\x0"*148).force_encoding('BINARY')),
14
+ double('Section', name: '__objc_classname', vmaddr: 11363, vmsize: 62, bytes: ("\x0"*62).force_encoding('BINARY')),
15
+ double('Section', name: '__objc_methtype', vmaddr: 11425, vmsize: 821, bytes: ("\x0"*821).force_encoding('BINARY')),
16
+ double('Section', name: '__symbolstub1', vmaddr: 12248, vmsize: 40, bytes: ("\x0"*40).force_encoding('BINARY'))
17
+ ], bytes: ("\x1\x2\x3\x4\x5\x6\x7\x8\x9"*(8192/8)).force_encoding('BINARY'), iooff: 0, to_vmrange: 4096...12288),
18
+ double('Segment', name: '__DATA', vmaddr: 12288, vmsize: 4096, sections: [
19
+ double('Section', name: '__lazy_symbol', vmaddr: 12288, vmsize: 40, bytes: ("\xbb"*40).force_encoding('BINARY')),
20
+ double('Section', name: '__program_vars', vmaddr: 12336, vmsize: 20, bytes: ("\xbb"*20).force_encoding('BINARY')),
21
+ double('Section', name: '__nl_symbol_ptr', vmaddr: 12356, vmsize: 8, bytes: ("\xbb"*8).force_encoding('BINARY')),
22
+ double('Section', name: '__objc_classlist', vmaddr: 12364, vmsize: 8, bytes: ("\xbb"*8).force_encoding('BINARY')),
23
+ double('Section', name: '__objc_protolist', vmaddr: 12372, vmsize: 8, bytes: ("\xbb"*8).force_encoding('BINARY')),
24
+ double('Section', name: '__objc_imageinfo', vmaddr: 12380, vmsize: 8, bytes: ("\xbb"*8).force_encoding('BINARY')),
25
+ double('Section', name: '__objc_const', vmaddr: 12392, vmsize: 1176, bytes: ("\xbb"*1176).force_encoding('BINARY')),
26
+ double('Section', name: '__objc_selrefs', vmaddr: 13568, vmsize: 72, bytes: ("\xbb"*72).force_encoding('BINARY')),
27
+ double('Section', name: '__objc_classrefs', vmaddr: 13640, vmsize: 16, bytes: ("\xbb"*16).force_encoding('BINARY')),
28
+ double('Section', name: '__objc_superrefs', vmaddr: 13656, vmsize: 8, bytes: ("\xbb"*8).force_encoding('BINARY')),
29
+ double('Section', name: '__objc_data', vmaddr: 13664, vmsize: 80, bytes: ("\xbb"*80).force_encoding('BINARY')),
30
+ double('Section', name: '__objc_ivar', vmaddr: 13744, vmsize: 8, bytes: ("\xbb"*8).force_encoding('BINARY')),
31
+ double('Section', name: '__cfstring', vmaddr: 13752, vmsize: 48, bytes: ("\xbb"*48).force_encoding('BINARY')),
32
+ double('Section', name: '__data', vmaddr: 13800, vmsize: 88, bytes: ("\xbb"*88).force_encoding('BINARY')),
33
+ double('Section', name: '__common', vmaddr: 13888, vmsize: 16, bytes: ("\xbb"*16).force_encoding('BINARY'))
34
+ ], bytes: ("\xBB"*4096).force_encoding('BINARY'), iooff: 0, to_vmrange: 12288...16384),
35
+ double('Segment', name: '__LINKEDIT', vmaddr: 16384, vmsize: 12288, sections: [],
36
+ bytes: ("\xCC"*12288).force_encoding('BINARY'), iooff: 0, to_vmrange: 16384...28672)
37
+ ])
38
+ end
39
+
40
+ describe Indis::VMMap do
41
+ it "should parse existing segments/sections and provide a linear map" do
42
+ map = Indis::VMMap.new(vmmap_target_double)
43
+
44
+ map.segment_at(0).name.should == '__PAGEZERO'
45
+ map.segment_at(4095).name.should == '__PAGEZERO'
46
+ map.section_at(4095).should be_nil
47
+
48
+ map.segment_at(4096).name.should == '__TEXT'
49
+ map.section_at(11215).name.should == '__cstring'
50
+ map.segment_at(16400).name.should == '__LINKEDIT'
51
+ end
52
+
53
+ it "should return nil for address out of known segment bounds" do
54
+ map = Indis::VMMap.new(vmmap_target_double)
55
+
56
+ map.address_valid?(13664).should == true
57
+ map.address_valid?(16384+12288).should == false
58
+
59
+ map.byte_at(0).should == 0x00
60
+ map.byte_at(4096).should == 0x01
61
+ map.byte_at(16384+12288).should be_nil
62
+ end
63
+
64
+ it "should allow to create entities" do
65
+ map = Indis::VMMap.new(vmmap_target_double)
66
+
67
+ map[13800].should be_nil
68
+ de = Indis::DataEntity.new(13800, 4, map)
69
+ map.map(de)
70
+ map[13800].should_not be_nil
71
+ map[13800].should == de
72
+ map[13800...13804].should == [de, nil, nil, nil]
73
+ end
74
+
75
+ it "should allow to force-map entities" do
76
+ map = Indis::VMMap.new(vmmap_target_double)
77
+
78
+ de4 = Indis::DataEntity.new(13800, 4, map)
79
+ map.map(de4)
80
+
81
+ de2 = Indis::DataEntity.new(13800, 2, map)
82
+ expect { map.map(de2) }.to raise_error(ArgumentError)
83
+ map.map!(de2)
84
+ map[13800].should == de2
85
+ map[13800...13804].should == [de2, nil, 0xbb, 0xbb]
86
+ end
87
+
88
+ it "should return an entity when one has been defined previously" do
89
+ map = Indis::VMMap.new(vmmap_target_double)
90
+
91
+ map[13800].should be_nil
92
+ map.map(Indis::DataEntity.new(13800, 4, map))
93
+
94
+ b = map[13800]
95
+ b.should be_a(Indis::DataEntity)
96
+ b.size.should == 4
97
+ b.kind.should == :dword
98
+ b.value.should == 0xbbbbbbbb
99
+ end
100
+
101
+ it "should return correct size for slice" do
102
+ t = vmmap_target_double
103
+ text_seg = t.segments[1]
104
+ map = Indis::VMMap.new(t)
105
+
106
+ map[text_seg.vmaddr..(text_seg.vmaddr+3)].length.should == 4
107
+ map[text_seg.vmaddr...(text_seg.vmaddr+3)].length.should == 3
108
+ end
109
+
110
+ it "should return raw bytes in slice" do
111
+ t = vmmap_target_double
112
+ text_seg = t.segments[1]
113
+ map = Indis::VMMap.new(t)
114
+
115
+ map[text_seg.vmaddr..(text_seg.vmaddr+3)].should == [1, 2, 3, 4]
116
+ end
117
+
118
+ it "should return entities with nil padding in slice" do
119
+ t = vmmap_target_double
120
+ text_seg = t.segments[1]
121
+ map = Indis::VMMap.new(t)
122
+
123
+ b = Indis::DataEntity.new(text_seg.vmaddr+1, 2, map)
124
+ map.map(b)
125
+
126
+ map[text_seg.vmaddr..(text_seg.vmaddr+3)].should == [1, b, nil, 4]
127
+ end
128
+
129
+ it "should return segment map as an array" do
130
+ t = vmmap_target_double
131
+ text_seg = t.segments[1]
132
+ map = Indis::VMMap.new(t)
133
+
134
+ a = map[text_seg.to_vmrange]
135
+ a.should_not be_nil
136
+ a.length.should == 8192
137
+ end
138
+
139
+ it "should map segment bytes to Fixnum when not resolved" do
140
+ t = vmmap_target_double
141
+ text_seg = t.segments[1]
142
+ map = Indis::VMMap.new(t)
143
+
144
+ a = map[text_seg.to_vmrange]
145
+ a.each { |v| v.class.should == Fixnum }
146
+ end
147
+
148
+ it "should map segment bytes to entities when resolved" do
149
+ t = vmmap_target_double
150
+ data_seg = t.segments[2]
151
+ map = Indis::VMMap.new(t)
152
+
153
+ map.map(Indis::DataEntity.new(13800, 4, map))
154
+ a = map[data_seg.to_vmrange]
155
+ a.each_with_index do |v, idx|
156
+ case idx
157
+ when 1512
158
+ v.should be_a(Indis::DataEntity)
159
+ when 1513
160
+ when 1514
161
+ when 1515
162
+ v.should be_nil
163
+ else
164
+ v.should be_a(Fixnum)
165
+ end
166
+ end
167
+ end
168
+ end
@@ -0,0 +1,8 @@
1
+ require 'rubygems'
2
+ require 'bundler/setup'
3
+
4
+ RSpec.configure do |config|
5
+ config.treat_symbols_as_metadata_keys_with_true_values = true
6
+ config.run_all_when_everything_filtered = true
7
+ config.filter_run :focus
8
+ end
metadata ADDED
@@ -0,0 +1,100 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: indis-core
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Vladimir Pouzanov
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2012-04-16 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: rspec
16
+ requirement: !ruby/object:Gem::Requirement
17
+ none: false
18
+ requirements:
19
+ - - ! '>='
20
+ - !ruby/object:Gem::Version
21
+ version: '0'
22
+ type: :development
23
+ prerelease: false
24
+ version_requirements: !ruby/object:Gem::Requirement
25
+ none: false
26
+ requirements:
27
+ - - ! '>='
28
+ - !ruby/object:Gem::Version
29
+ version: '0'
30
+ description: Core components of indis, the intelligent disassembler framework
31
+ email:
32
+ - farcaller@gmail.com
33
+ executables: []
34
+ extensions: []
35
+ extra_rdoc_files: []
36
+ files:
37
+ - .gitignore
38
+ - .rspec
39
+ - .travis.yml
40
+ - Gemfile
41
+ - Gemfile.ci
42
+ - LICENSE
43
+ - README.md
44
+ - Rakefile
45
+ - indis-core.gemspec
46
+ - lib/indis-core.rb
47
+ - lib/indis-core/binary_format.rb
48
+ - lib/indis-core/data_entity.rb
49
+ - lib/indis-core/entity.rb
50
+ - lib/indis-core/section.rb
51
+ - lib/indis-core/segment.rb
52
+ - lib/indis-core/symbol.rb
53
+ - lib/indis-core/target.rb
54
+ - lib/indis-core/version.rb
55
+ - lib/indis-core/vmmap.rb
56
+ - spec/fixtures/single-object.o
57
+ - spec/indis-core/binary_format_spec.rb
58
+ - spec/indis-core/data_entity_spec.rb
59
+ - spec/indis-core/section_spec.rb
60
+ - spec/indis-core/segment_spec.rb
61
+ - spec/indis-core/symbol_spec.rb
62
+ - spec/indis-core/target_spec.rb
63
+ - spec/indis-core/vmmap_spec.rb
64
+ - spec/spec_helper.rb
65
+ homepage: http://www.indis.org/
66
+ licenses:
67
+ - GPL-3
68
+ post_install_message:
69
+ rdoc_options: []
70
+ require_paths:
71
+ - lib
72
+ required_ruby_version: !ruby/object:Gem::Requirement
73
+ none: false
74
+ requirements:
75
+ - - ! '>='
76
+ - !ruby/object:Gem::Version
77
+ version: '0'
78
+ required_rubygems_version: !ruby/object:Gem::Requirement
79
+ none: false
80
+ requirements:
81
+ - - ! '>='
82
+ - !ruby/object:Gem::Version
83
+ version: '0'
84
+ requirements: []
85
+ rubyforge_project:
86
+ rubygems_version: 1.8.21
87
+ signing_key:
88
+ specification_version: 3
89
+ summary: Core indis components
90
+ test_files:
91
+ - spec/fixtures/single-object.o
92
+ - spec/indis-core/binary_format_spec.rb
93
+ - spec/indis-core/data_entity_spec.rb
94
+ - spec/indis-core/section_spec.rb
95
+ - spec/indis-core/segment_spec.rb
96
+ - spec/indis-core/symbol_spec.rb
97
+ - spec/indis-core/target_spec.rb
98
+ - spec/indis-core/vmmap_spec.rb
99
+ - spec/spec_helper.rb
100
+ has_rdoc: