indis-macho 0.2.0

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