indis-core 0.1.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,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: