indis-macho 0.2.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,6 @@
1
+ [![Build Status](https://secure.travis-ci.org/indis/indis-macho.png?branch=master)](http://travis-ci.org/indis/indis-macho)
2
+
3
+ # Indis::MachO
4
+
5
+ This gem provides support for processing mach-o binaries in th indis framework.
6
+
@@ -0,0 +1,9 @@
1
+ #!/usr/bin/env rake
2
+
3
+ require "bundler/gem_tasks"
4
+ Bundler::GemHelper.install_tasks
5
+
6
+ require 'rspec/core/rake_task'
7
+ RSpec::Core::RakeTask.new('spec')
8
+
9
+ task :default => :spec
@@ -0,0 +1,21 @@
1
+ # -*- encoding: utf-8 -*-
2
+ require File.expand_path('../lib/indis-macho/version', __FILE__)
3
+
4
+ Gem::Specification.new do |gem|
5
+ gem.authors = ["Vladimir Pouzanov"]
6
+ gem.email = ["farcaller@gmail.com"]
7
+ gem.description = "Mach-o format processor for indis provides support for loading mach-o binaries for analysis"
8
+ gem.summary = "Mach-o format processor for indis"
9
+ gem.homepage = "http://www.indis.org/"
10
+ gem.license = "GPL-3"
11
+
12
+ gem.files = `git ls-files`.split($\)
13
+ gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
14
+ gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
15
+ gem.name = "indis-macho"
16
+ gem.require_paths = ["lib"]
17
+ gem.version = Indis::MachO::VERSION
18
+
19
+ gem.add_development_dependency 'rspec'
20
+ gem.add_runtime_dependency 'indis-core'
21
+ end
@@ -0,0 +1,224 @@
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
+ require 'indis-core/binary_format'
20
+ require 'indis-core/segment'
21
+ require 'indis-core/section'
22
+ require 'indis-core/symbol'
23
+ require 'indis-macho/version'
24
+ require 'indis-macho/command'
25
+ require 'indis-macho/symbol'
26
+
27
+ module Indis
28
+ module BinaryFormat
29
+
30
+ class MachO < Format
31
+ MH_MAGIC = 0xfeedface
32
+
33
+ CPUTYPE = {
34
+ 12 => :CPU_TYPE_ARM
35
+ }
36
+
37
+ CPUSUBTYPE = {
38
+ 5 => :CPU_SUBTYPE_ARM_V4T,
39
+ 6 => :CPU_SUBTYPE_ARM_V6,
40
+ 7 => :CPU_SUBTYPE_ARM_V5TEJ,
41
+ 8 => :CPU_SUBTYPE_ARM_XSCALE,
42
+ 9 => :CPU_SUBTYPE_ARM_V7
43
+ }
44
+
45
+ FILETYPE = {
46
+ 0x1 => :MH_OBJECT,
47
+ 0x2 => :MH_EXECUTE,
48
+ 0x3 => :MH_FVMLIB,
49
+ 0x4 => :MH_CORE,
50
+ 0x5 => :MH_PRELOAD,
51
+ 0x6 => :MH_DYLIB,
52
+ 0x7 => :MH_DYLINKER,
53
+ 0x8 => :MH_BUNDLE,
54
+ 0x9 => :MH_DYLIB_STUB,
55
+ 0xa => :MH_DSYM,
56
+ 0xb => :MH_KEXT_BUNDLE
57
+ }
58
+
59
+ FLAGS = {
60
+ 0x1 => :MH_NOUNDEFS,
61
+ 0x2 => :MH_INCRLINK,
62
+ 0x4 => :MH_DYLDLINK,
63
+ 0x8 => :MH_BINDATLOAD,
64
+ 0x10 => :MH_PREBOUND,
65
+ 0x20 => :MH_SPLIT_SEGS,
66
+ 0x40 => :MH_LAZY_INIT,
67
+ 0x80 => :MH_TWOLEVEL,
68
+ 0x100 => :MH_FORCE_FLAT,
69
+ 0x200 => :MH_NOMULTIDEFS,
70
+ 0x400 => :MH_NOFIXPREBINDING,
71
+ 0x800 => :MH_PREBINDABLE,
72
+ 0x1000 => :MH_ALLMODSBOUND,
73
+ 0x2000 => :MH_SUBSECTIONS_VIA_SYMBOLS,
74
+ 0x4000 => :MH_CANONICAL,
75
+ 0x8000 => :MH_WEAK_DEFINES,
76
+ 0x10000 => :MH_BINDS_TO_WEAK,
77
+ 0x20000 => :MH_ALLOW_STACK_EXECUTION,
78
+ 0x40000 => :MH_ROOT_SAFE,
79
+ 0x80000 => :MH_SETUID_SAFE,
80
+ 0x100000 => :MH_NO_REEXPORTED_DYLIBS,
81
+ 0x200000 => :MH_PIE,
82
+ 0x400000 => :MH_DEAD_STRIPPABLE_DYLIB,
83
+ 0x800000 => :MH_HAS_TLV_DESCRIPTORS,
84
+ 0x1000000 => :MH_NO_HEAP_EXECUTION,
85
+ }
86
+
87
+ attr_reader :cputype, :cpusubtype, :filetype, :commands
88
+
89
+ def self.magic
90
+ MH_MAGIC
91
+ end
92
+
93
+ def self.name
94
+ 'Mach-O'
95
+ end
96
+
97
+ def initialize(target, io)
98
+ super(target, io)
99
+
100
+ @commands = []
101
+
102
+ parse_header
103
+ parse_commands
104
+
105
+ build_segments
106
+ build_dylibs if self.flags.include? :MH_TWOLEVEL
107
+ build_symbols
108
+ end
109
+
110
+ def flags
111
+ f = []
112
+ FLAGS.each_pair do |k, v|
113
+ f << v if @flags_val & k == k
114
+ end
115
+ f
116
+ end
117
+
118
+ private
119
+ def validate_format
120
+ raise "Not a Mach-O" if @io.length < 4
121
+ magic = @io.read(4).unpack('V')[0]
122
+ raise "Bad magic" unless magic == MH_MAGIC
123
+ @io.seek(-4, IO::SEEK_CUR)
124
+ end
125
+
126
+ def parse_header
127
+ validate_format
128
+
129
+ @magic, @cputype, @cpusubtype, @filetype, @ncmds, @sizeofcmds, @flags_val = @io.read(7*4).unpack('VVVVVVV')
130
+
131
+ @cputype = CPUTYPE[@cputype]
132
+ raise "Unknown CPU type" unless @cputype
133
+
134
+ @cpusubtype = CPUSUBTYPE[@cpusubtype]
135
+ raise "Unknown CPU subtype" unless @cpusubtype
136
+
137
+ @filetype = FILETYPE[@filetype]
138
+ raise "Unknown file type" unless @filetype
139
+ end
140
+
141
+ def parse_commands
142
+ @ncmds.times do
143
+ cmd, size = @io.read(2*4).unpack('VV')
144
+
145
+ begin
146
+ c = Indis::MachO::Command.class_of_command(cmd).new(cmd, size, @io)
147
+ @commands << c
148
+ rescue Indis::MachO::UnknownCommandError
149
+ print "Unknown command #{cmd} size #{size}, skipping\n"
150
+ @io.read(size-8)
151
+ end
152
+ end
153
+ end
154
+
155
+ def build_segments
156
+ @indexed_sections = [nil]
157
+ segcommands = @commands.map{ |c| c if c.is_a?(Indis::MachO::SegmentCommand) }.compact
158
+
159
+ @target.segments = []
160
+
161
+ pos = @target.io.pos
162
+ segcommands.each do |cmd|
163
+ name = if cmd.segname.length > 0
164
+ cmd.segname
165
+ else
166
+ if cmd.sections.length > 0 && cmd.sections.first.segname.length > 0
167
+ cmd.sections.first.segname
168
+ else
169
+ name = '*NONAME*'
170
+ end
171
+ end
172
+
173
+ @target.io.pos = cmd.fileoff
174
+ seg = Indis::Segment.new(@target, name, cmd.vmaddr, cmd.vmsize, cmd.fileoff, @target.io.read(cmd.filesize))
175
+ @target.segments << seg
176
+
177
+ cmd.sections.each do |sec|
178
+ sec.index = @indexed_sections.length
179
+ s = Indis::Section.new(seg, sec.sectname, sec.addr, sec.size, sec.offset)
180
+ seg.sections << s
181
+ @indexed_sections << s
182
+ end
183
+ end
184
+ @target.io.pos = pos
185
+ end
186
+
187
+ def build_dylibs
188
+ @indexed_dylibs = [nil]
189
+ dylibcommands = @commands.map{ |c| c if c.is_a?(Indis::MachO::LoadDyLibCommand) }.compact
190
+
191
+ dylibcommands.each do |dy|
192
+ @indexed_dylibs << dy.name
193
+ end
194
+ end
195
+
196
+ def build_symbols
197
+ symtabcommand = @commands.map{ |c| c if c.is_a?(Indis::MachO::SymTabCommand) }.compact
198
+ return if symtabcommand.length == 0
199
+
200
+ @target.symbols = {}
201
+
202
+ cmd = symtabcommand.first
203
+ cmd.symbols.each do |sym|
204
+ sec = if sym.sect > 0
205
+ @indexed_sections[sym.sect]
206
+ else
207
+ nil
208
+ end
209
+
210
+ dy = if self.flags.include? :MH_TWOLEVEL
211
+ l2 = sym.twolevel_library_ordinal
212
+ @indexed_dylibs[l2] if l2.is_a? Fixnum
213
+ else
214
+ nil
215
+ end
216
+
217
+ s = Indis::Symbol.new(sym.name, sec, dy, sym.value, sym)
218
+ @target.symbols[sym.name] = s
219
+ end
220
+ end
221
+ end
222
+
223
+ end
224
+ end
@@ -0,0 +1,271 @@
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
+ require 'indis-macho/symbol'
20
+
21
+ module Indis
22
+ module MachO
23
+
24
+ class UnknownCommandError < RuntimeError; end
25
+
26
+ class Command
27
+ LC_REQ_DYLD = 0x80000000
28
+
29
+ CMD = {
30
+ 0x1 => :LC_SEGMENT,
31
+ 0x2 => :LC_SYMTAB,
32
+ 0x3 => :LC_SYMSEG,
33
+ 0x4 => :LC_THREAD,
34
+ 0x5 => :LC_UNIXTHREAD,
35
+ 0x6 => :LC_LOADFVMLIB,
36
+ 0x7 => :LC_IDFVMLIB,
37
+ 0x8 => :LC_IDENT,
38
+ 0x9 => :LC_FVMFILE,
39
+ 0xa => :LC_PREPAGE,
40
+ 0xb => :LC_DYSYMTAB,
41
+ 0xc => :LC_LOAD_DYLIB,
42
+ 0xd => :LC_ID_DYLIB,
43
+ 0xe => :LC_LOAD_DYLINKER,
44
+ 0xf => :LC_ID_DYLINKER,
45
+ 0x10 => :LC_PREBOUND_DYLIB,
46
+ 0x11 => :LC_ROUTINES,
47
+ 0x12 => :LC_SUB_FRAMEWORK,
48
+ 0x13 => :LC_SUB_UMBRELLA,
49
+ 0x14 => :LC_SUB_CLIENT,
50
+ 0x15 => :LC_SUB_LIBRARY,
51
+ 0x16 => :LC_TWOLEVEL_HINTS,
52
+ 0x17 => :LC_PREBIND_CKSUM,
53
+
54
+ 0x18 | LC_REQ_DYLD => :LC_LOAD_WEAK_DYLIB,
55
+ 0x19 => :LC_SEGMENT_64,
56
+ 0x1a => :LC_ROUTINES_64,
57
+ 0x1b => :LC_UUID,
58
+ 0x1c | LC_REQ_DYLD => :LC_RPATH,
59
+ 0x1d => :LC_CODE_SIGNATURE,
60
+ 0x1e => :LC_SEGMENT_SPLIT_INFO,
61
+ 0x1f | LC_REQ_DYLD => :LC_REEXPORT_DYLIB,
62
+ 0x20 => :LC_LAZY_LOAD_DYLIB,
63
+ 0x21 => :LC_ENCRYPTION_INFO,
64
+ 0x22 => :LC_DYLD_INFO,
65
+ 0x22|LC_REQ_DYLD => :LC_DYLD_INFO_ONLY,
66
+
67
+ 0x23 | LC_REQ_DYLD => :LC_LOAD_UPWARD_DYLIB,
68
+ 0x24 => :LC_VERSION_MIN_MACOSX,
69
+ 0x25 => :LC_VERSION_MIN_IPHONEOS,
70
+ 0x26 => :LC_FUNCTION_STARTS,
71
+ 0x27 => :LC_DYLD_ENVIRONMENT,
72
+ }
73
+
74
+ CMD_CLASS = {
75
+ LC_SEGMENT: :SegmentCommand,
76
+ LC_DYLD_INFO_ONLY: :DyldInfoOnlyCommand,
77
+ LC_SYMTAB: :SymTabCommand,
78
+ LC_DYSYMTAB: :DySymTabCommand,
79
+ LC_LOAD_DYLINKER: :LoadDyLinkerCommand,
80
+ LC_UUID: :UUIDCommand,
81
+ LC_UNIXTHREAD: :ARMUnixThreadCommand,
82
+ LC_ENCRYPTION_INFO: :EncryptionInfoCommand,
83
+ LC_LOAD_DYLIB: :LoadDyLibCommand,
84
+ LC_CODE_SIGNATURE: :CodeSignatureCommand,
85
+ LC_VERSION_MIN_IPHONEOS: :VersionMinIPhoneOSCommand,
86
+ LC_FUNCTION_STARTS: :FunctionStartsCommand,
87
+ }
88
+
89
+ attr_reader :cmd, :length
90
+
91
+ def initialize(cmd, length, payload)
92
+ @cmd = CMD[cmd]
93
+ @length = length
94
+ raise "Unknown mach-o command" unless @cmd
95
+
96
+ process(payload)
97
+ end
98
+
99
+ def self.class_of_command(c)
100
+ cmd = CMD[c]
101
+ raise UnknownCommandError, "Unknown mach-o command #{c.to_s(16)}" unless cmd
102
+ clsnm = CMD_CLASS[cmd]
103
+ raise "Unsupported mach-o command #{c.to_s(16)} (#{cmd})" unless clsnm
104
+ cls = Indis::MachO.const_get(clsnm)
105
+ end
106
+
107
+ private
108
+ def process(payload)
109
+ return unless self.class.fields
110
+ self.class.fields.each do |f|
111
+ case f[0]
112
+ when :string
113
+ s = payload.read(f[2]).strip.gsub("\0", "")
114
+ instance_variable_set("@#{f[1]}".to_sym, s)
115
+ when :uint32
116
+ instance_variable_set("@#{f[1]}".to_sym, payload.read(4).unpack('V')[0])
117
+ end
118
+ end
119
+ end
120
+
121
+ def self.f_string(name, sz)
122
+ @fields ||= []
123
+ @fields << [:string, name, sz]
124
+ attr_reader name.to_sym
125
+ end
126
+
127
+ def self.f_uint32(*names)
128
+ @fields ||= []
129
+ names.each do |nm|
130
+ @fields << [:uint32, nm]
131
+ attr_reader nm.to_sym
132
+ end
133
+ end
134
+
135
+ def self.fields
136
+ @fields
137
+ end
138
+ end
139
+
140
+ class SectionSubCommand < Command # LC_SEGMENT.sub
141
+ f_string :sectname, 16
142
+ f_string :segname, 16
143
+ f_uint32 :addr, :size, :offset, :align, :reloff, :nreloc, :flags, :reserved1, :reseved2
144
+ attr_accessor :index
145
+
146
+ def initialize(payload)
147
+ process(payload)
148
+ end
149
+ end
150
+
151
+ class SegmentCommand < Command # LC_SEGMENT
152
+ f_string :segname, 16
153
+ f_uint32 :vmaddr, :vmsize, :fileoff, :filesize, :maxprot, :initprot, :nsects, :flags
154
+ attr_reader :sections
155
+
156
+ def process(payload)
157
+ super(payload)
158
+
159
+ @sections = []
160
+ @nsects.times do
161
+ s = SectionSubCommand.new(payload)
162
+ @sections << s
163
+ end
164
+ end
165
+ end
166
+
167
+ class SymTabCommand < Command # LC_SYMTAB
168
+ f_uint32 :symoff, :nsyms, :stroff, :strsize
169
+ attr_reader :symbols
170
+
171
+ def process(payload)
172
+ super(payload)
173
+
174
+ pos = payload.pos
175
+
176
+ payload.pos = @stroff
177
+ strings = payload.read(@strsize)
178
+
179
+ payload.pos = @symoff
180
+ @symbols = []
181
+
182
+ @nsyms.times do |n|
183
+ s = Indis::MachO::Symbol.new(payload, strings)
184
+ @symbols << s
185
+ end
186
+ payload.pos = pos
187
+ end
188
+ end
189
+
190
+ class DySymTabCommand < Command # LC_DYSYMTAB
191
+ f_uint32 :ilocalsym, :nlocalsym, :iextdefsym, :nextdefsym, :iundefsym, :nundefsym, :tocoff,
192
+ :ntoc, :modtaboff, :nmodtab, :extrefsymoff, :nextrefsyms, :indirectsymoff, :nindirectsyms,
193
+ :extreloff, :nextrel, :locreloff, :nlocrel
194
+ end
195
+
196
+ class LoadDyLinkerCommand < Command # LC_LOAD_DYLINKER
197
+ attr_reader :name
198
+
199
+ def process(payload)
200
+ super(payload)
201
+ @name = payload.read(@length-8).strip
202
+ end
203
+ end
204
+
205
+ class UUIDCommand < Command # LC_UUID
206
+ attr_reader :UUID
207
+
208
+ def process(payload)
209
+ super(payload)
210
+ @UUID = payload.read(16)
211
+ end
212
+ end
213
+
214
+ class ARMUnixThreadCommand < Command # LC_UNIXTHREAD
215
+ REGISTERS = [:r0, :r1, :r2, :r3, :r4, :r5, :r6, :r7, :r8, :r9, :r10, :r11, :r12, :sp, :lr, :pc, :cpsr]
216
+ f_uint32 :flavor, :count
217
+ attr_reader :registers
218
+
219
+ def process(payload)
220
+ super(payload)
221
+ # XXX: this one parses only ARM thread state, need to get back to mach-o header to know the flavor
222
+
223
+ @registers = payload.read(4*17).unpack('V'*17)
224
+ end
225
+ end
226
+
227
+ class EncryptionInfoCommand < Command # LC_ENCRYPTION_INFO
228
+ f_uint32 :cryptoff, :cryptsize, :cryptid
229
+ end
230
+
231
+ class LoadDyLibCommand < Command # LC_LOAD_DYLIB
232
+ attr_reader :name, :timestamp, :current_version, :compatibility_version
233
+
234
+ def process(payload)
235
+ super(payload)
236
+
237
+ ofs_to_name = payload.read(4).unpack('V')[0]
238
+
239
+ @timestamp, @current_version, @compatibility_version = payload.read(4*3).unpack('VVV')
240
+
241
+ name_sz = @length - ofs_to_name - 4*3 + 8 + 4
242
+ @name = payload.read(name_sz).strip
243
+ end
244
+ end
245
+
246
+ class CodeSignatureCommand < Command # LC_CODE_SIGNATURE
247
+ f_uint32 :dataoff, :datasize
248
+ end
249
+
250
+ class VersionMinIPhoneOSCommand < Command # LC_VERSION_MIN_IPHONEOS
251
+ attr_reader :version, :sdk
252
+
253
+ def process(payload)
254
+ v, s = payload.read(8).unpack('VV')
255
+
256
+ @version = "#{(v & 0xffff0000) >> 16}.#{(v & 0xff00) >> 8}.#{v & 0xff}"
257
+ @sdk = s
258
+ end
259
+ end
260
+
261
+ class FunctionStartsCommand < Command # LC_FUNCTION_STARTS
262
+ f_uint32 :dataoff, :datasize
263
+ end
264
+
265
+ class DyldInfoOnlyCommand < Command # LC_DYLD_INFO_ONLY
266
+ f_uint32 :rebase_off, :rebase_size, :bind_off, :bind_size, :weak_bind_off, :weak_bind_size,
267
+ :lazy_bind_off, :lazy_bind_size, :export_off, :export_size
268
+ end
269
+
270
+ end
271
+ end