pliney 0.0.4 → 0.0.6

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 2bfa1e0dfc77474eaabb59345899e03103a99434
4
- data.tar.gz: aad5759a6adaec3bec3f80f4711fac40781ce558
3
+ metadata.gz: 9acf33365d64a3a900b1a620ebd8a638a45ef6aa
4
+ data.tar.gz: 253367edc5a548e08baefcf956af43b68884eaa9
5
5
  SHA512:
6
- metadata.gz: 22502e81aba65160256be4851bc4e77ab6b492c2862cfc57368e2c999951692cf3f1f1e5cca42ebadcb5187b5bc1ba368692ba816a4b5bc2ff969f63dd48107b
7
- data.tar.gz: 285a7560ece3b623ee9d7c2281b30ac78e4b90f47f01a590dedebe85ae48c2af673bbad06e014ad9462292dd7b027825500c6c8608f66acbc8a9152b360117cb
6
+ metadata.gz: e6c91f1f912f62dd8e679ff7c10cf228ff154e968882d4179e452364388a232e8542b4d452f15bba3ce620930a95ab2d666e6893e94d6181a294542f65680322
7
+ data.tar.gz: a90eebcabf9039cb47aef08dbbf4e425ea0a6e688eb7d2943fd5cc81de05855c2446ba08dff07505f4e133f13353063db35c418cea5fbe24cce10023e2a41d40
data/README.md CHANGED
@@ -50,7 +50,7 @@ Or install it yourself as:
50
50
 
51
51
  profile.developer_certificates
52
52
  # => [#<OpenSSL::X509::Certificate:...
53
-
53
+
54
54
  profile.expiration_date
55
55
  # => 2016-04-20 14:18:13 -0700
56
56
 
@@ -62,9 +62,3 @@ Or install it yourself as:
62
62
 
63
63
  ipa.close
64
64
 
65
-
66
- ## TODOS
67
-
68
- - macho parsing
69
- - entitlements extraction/parsing/serialization
70
- - ?
@@ -1,6 +1,6 @@
1
- require "pliney/version"
2
- require 'pliney/ipa'
3
- require 'pliney/provisioning_profile'
1
+ require_relative 'pliney/version'
2
+ require_relative 'pliney/ipa'
3
+ require_relative 'pliney/provisioning_profile'
4
4
 
5
5
  module Pliney
6
6
  end
@@ -0,0 +1,249 @@
1
+ require_relative 'io_helpers'
2
+
3
+ module Pliney
4
+ module AppleCodeSignature
5
+ class Blob
6
+ class Magic
7
+ attr_accessor :value
8
+ def initialize(num)
9
+ @value = (Magic === num)? num.value : num
10
+ end
11
+
12
+ def inspect
13
+ "0x%x" % value
14
+ end
15
+ end
16
+
17
+ attr_reader :magic, :size, :input
18
+
19
+ def initialize(magic, data)
20
+ @magic = Magic.new(magic)
21
+ @input = data.is_a?(StringStream)? data : StringStream.new(data)
22
+ @base = @input.pos-4
23
+ yield self if block_given?
24
+ end
25
+
26
+ def parse
27
+ if @input
28
+ @size = input.read_uint32
29
+ yield
30
+ end
31
+ @input=nil
32
+ return self
33
+ end
34
+
35
+ private
36
+ def rest
37
+ @input.read(size_left)
38
+ end
39
+
40
+ def size_left
41
+ @size-(@input.pos-@base)
42
+ end
43
+ end
44
+
45
+ class OpaqueBlob < Blob
46
+ attr_reader :data, :base
47
+ def parse
48
+ super() do
49
+ @data = rest()
50
+ end
51
+ end
52
+ end
53
+
54
+ class SuperBlob < Blob
55
+ class ContentInfo
56
+ attr_reader :unk, :offset
57
+ def initialize(unk, offset)
58
+ @unk = unk
59
+ @offset = offset
60
+ end
61
+ end
62
+ attr_reader :contents, :content_infos
63
+ def parse
64
+ super() do
65
+ ncontent = @input.read_uint32
66
+ @content_infos = Array.new(ncontent) {
67
+ ContentInfo.new(@input.read_uint32, @input.read_uint32)
68
+ }
69
+ @contents = []
70
+ @content_infos.each do |ci|
71
+ @input.pos = @base+ci.offset
72
+ @contents << AppleCodeSignature.parse(@input)
73
+ end
74
+ end
75
+ end
76
+ end
77
+
78
+ # SuperBlob containing all the signing components that are usually
79
+ # embedded within a main executable.
80
+ # This is what we embed in Mach-O images. It is also what we use for detached
81
+ # signatures for non-Mach-O binaries.
82
+ class EmbeddedSignature < SuperBlob
83
+ end
84
+
85
+ # SuperBlob that contains all the data for all architectures of a
86
+ # signature, including any data that is usually written to separate files.
87
+ # This is the format of detached signatures if the program is capable of
88
+ # having multiple architectures.
89
+ # A DetachedSignatureBlob collects multiple architectures' worth of
90
+ # EmbeddedSignatureBlobs into one, well, Super-SuperBlob.
91
+ # This is what is used for Mach-O detached signatures.
92
+ class DetachedSignature < SuperBlob
93
+ end
94
+
95
+ # A CodeDirectory
96
+ class CodeDirectory < Blob
97
+ # Types of cryptographic digests (hashes) used to hold code signatures
98
+ # together.
99
+ #
100
+ # Each combination of type, length, and other parameters is a separate
101
+ # hash type; we don't understand "families" here.
102
+ #
103
+ # These type codes govern the digest links that connect a CodeDirectory
104
+ # to its subordinate data structures (code pages, resources, etc.)
105
+ # They do not directly control other uses of hashes (such as the
106
+ # hash-of-CodeDirectory identifiers used in requirements).
107
+ HASHTYPES={
108
+ 0 => :NoHash, # null value
109
+ 1 => :HashSha1, # SHA-1
110
+ 2 => :HashSha256, # SHA-256
111
+ 32 => :HashPrestandardSkein160x256, # Skein, 160bits, 256bit pool
112
+ 33 => :HashPrestandardSkein256x512, # Skein, 256bits, 512bit pool
113
+ }
114
+
115
+ CURRENT_VERSION = 0x20100 # "version 2.1"
116
+ COMPATABILITY_LIMIT = 0x2F000 # "version 3 with wiggle room"
117
+ EARLIEST_VERSION = 0x20001 # earliest supported version
118
+ SUPPORTS_SCATTER = 0x20100 # first version to support scatter option
119
+
120
+ attr_reader :data
121
+ attr_reader :version # uint32 compatibility version
122
+ attr_reader :flags # uint32 setup and mode flags
123
+ attr_reader :hashOffset # uint32 offset of hash slot element at index zero
124
+ attr_reader :identOffset # uint32 offset of identifier string
125
+ attr_reader :nSpecialSlots # uint32 number of special hash slots
126
+ attr_reader :nCodeSlots # uint32 number of ordinary (code) hash slots
127
+ attr_reader :codeLimit # uint32 limit to main image signature range
128
+ attr_reader :hashSize # size of each hash digest (bytes)
129
+ attr_reader :hashType # type of hash (kSecCodeSignatureHash* constants)
130
+ attr_reader :spare1 # unused (must be zero)
131
+ attr_reader :pageSize # log2(page size in bytes); 0 => infinite
132
+ attr_reader :spare2 # uint32 unused (must be zero)
133
+ attr_reader :scatterOffset # uint32 offset of optional scatter vector (zero if absent)
134
+
135
+ def parse
136
+ super() do
137
+ @vers = @input.read_uint32
138
+ @flags = @input.read_uint32
139
+ @hashOffset = @input.read_uint32
140
+ @identOffset = @input.read_uint32
141
+ @nSpecialSlots = @input.read_uint32
142
+ @nCodeSlots = @input.read_uint32
143
+ @codeLimit = @input.read_uint32
144
+ @hashSize = @input.read_uint8
145
+ @hashType = @input.read_uint8
146
+ @spare1 = @input.read_uint8
147
+ @pageSize = @input.read_uint8
148
+ @spare2 = @input.read_uint32
149
+ @scatterOffset = @input.read_uint32
150
+ @data = rest()
151
+ end
152
+ end
153
+
154
+ def version
155
+ [@vers].pack("N").unpack("CCCC").join('.')
156
+ end
157
+
158
+ def hash_type
159
+ HASHTYPES[@hashType] || :unknown
160
+ end
161
+ end
162
+
163
+ # A collection of individual code requirements, indexed by requirement
164
+ # type. This is used for internal requirement sets.
165
+ class RequirementSet < SuperBlob
166
+ end
167
+
168
+ # An individual code requirement
169
+ # This is actlually a small compiled expression.
170
+ # csreq(1) can be used to decompile them
171
+ class Requirement < Blob
172
+ SYSTEM_HAS_CSREQ = system("which csreq > /dev/null")
173
+ attr_reader :data, :decompiled
174
+ def parse
175
+ super() do
176
+ @data=rest()
177
+ @decompiled = self.class.decompile([@magic.value, @size, @data].pack("NNA*"))
178
+ end
179
+ end
180
+
181
+ def self.decompile(data)
182
+ return nil unless SYSTEM_HAS_CSREQ
183
+ csreq=IO.popen("csreq -r- -t", "r+")
184
+ csreq.write(data)
185
+ decompiled = csreq.read()
186
+ csreq.close()
187
+ return decompiled
188
+ end
189
+ end
190
+
191
+ # The linkers produces a superblob of dependency records from its dylib inputs
192
+ class LibraryDependencyBlob < OpaqueBlob
193
+ end
194
+
195
+ # Program Entitlements Dictionary
196
+ class Entitlement < OpaqueBlob
197
+ end
198
+
199
+ class BlobWrapper < OpaqueBlob
200
+ end
201
+
202
+ class UnknownBlob < OpaqueBlob
203
+ end
204
+
205
+ FADEMAGIC = {
206
+ 0xfade0c00 => :Requirement,
207
+ 0xfade0c01 => :RequirementSet,
208
+ 0xfade0c02 => :CodeDirectory,
209
+ 0xfade0c05 => :LibraryDependencyBlob,
210
+ 0xfade0cc0 => :EmbeddedSignature,
211
+ 0xfade0cc1 => :DetachedSignature,
212
+ 0xfade0b01 => :BlobWrapper,
213
+ 0xfade7171 => :Entitlement,
214
+ }
215
+
216
+ def self.parse(data)
217
+ obj = data.is_a?(StringStream)? data : StringStream.new(data)
218
+ magic = obj.read_uint32
219
+ n=FADEMAGIC[magic]
220
+ if n.nil? and ((magic >> 16) == 0xFADE)
221
+ n = :UnknownBlob
222
+ end
223
+ unless n.nil?
224
+ blob=const_get(n).new(magic,obj) {|o| o.parse }
225
+ return blob
226
+ else
227
+ raise "Invalid magic value: 0x#{magic.to_s(16)}"
228
+ end
229
+ end
230
+
231
+ def self.from_stream(f)
232
+ obj = MachO::from_stream(f)
233
+ mh = if MachO::is_fat_magic(obj.magic)
234
+ obj.machos.first
235
+ elsif MachO::is_macho_magic(obj.magic)
236
+ obj
237
+ else
238
+ raise "Could not find mach-o object"
239
+ end
240
+
241
+ return mh.codesignature
242
+ end
243
+
244
+ def self.from_path(fname)
245
+ return from_stream(File.open(fname, 'rb'))
246
+ end
247
+ end
248
+ end
249
+
@@ -0,0 +1,64 @@
1
+ require 'stringio'
2
+
3
+ module Pliney
4
+ module IOHelpers
5
+ class StrictReadError < StandardError
6
+ end
7
+
8
+ def strictread(nbytes)
9
+ _pos = self.pos
10
+ res = read(nbytes)
11
+ if res.nil?
12
+ raise(StrictReadError, "read returned nil for read(#{nbytes}) at offset #{_pos}")
13
+ end
14
+ if res.bytesize != nbytes
15
+ raise(StrictReadError, "read returned only #{res.size} bytes for read(#{nbytes}) at offset #{_pos}")
16
+ end
17
+ return res
18
+ end
19
+
20
+ def read_uint8
21
+ getbyte
22
+ end
23
+
24
+ def read_uint16be
25
+ strictread(2).unpack("n").first
26
+ end
27
+
28
+ def read_uint32be
29
+ strictread(4).unpack("N").first
30
+ end
31
+
32
+ def read_uint64be
33
+ v = strictread(8).unpack("NN")
34
+ (v[0] << 32) | v[1]
35
+ end
36
+
37
+ alias read_uint16 read_uint16be
38
+ alias read_uint32 read_uint32be
39
+ alias read_uint64 read_uint64be
40
+
41
+ def read_uint16le
42
+ strictread(2).unpack("v").first
43
+ end
44
+
45
+ def read_uint32le
46
+ strictread(4).unpack("V").first
47
+ end
48
+
49
+ def read_uint64le
50
+ v = strictread(8).unpack("VV")
51
+ (v[1] << 32) | v[0]
52
+ end
53
+ end
54
+ end
55
+
56
+ class StringStream < StringIO
57
+ include Pliney::IOHelpers
58
+ end
59
+
60
+ class IO
61
+ include Pliney::IOHelpers
62
+ end
63
+
64
+
@@ -1,10 +1,16 @@
1
1
  require 'rubygems'
2
2
  require 'zip'
3
- require 'pliney/util'
4
- require 'pliney/entitlements'
3
+ require_relative 'util'
4
+ require_relative 'macho'
5
+ require_relative 'apple_code_signature'
5
6
 
6
7
  module Pliney
7
8
  class IPA
9
+ class ZipExtractError < StandardError
10
+ end
11
+
12
+ SYSTEM_HAS_UNZIP = system("which unzip > /dev/null")
13
+
8
14
  def self.from_path(path)
9
15
  ipa = new(Zip::File.open(path))
10
16
  if block_given?
@@ -90,5 +96,128 @@ module Pliney
90
96
 
91
97
  ProvisioningProfile.from_asn1(profile_data)
92
98
  end
99
+
100
+ def with_macho_for_entry(file_entry)
101
+ with_extracted_tmpfile(file_entry) do |tmpfile|
102
+ yield ::Pliney::MachO.from_stream(tmpfile)
103
+ end
104
+ end
105
+
106
+ def with_executable_macho(&block)
107
+ with_macho_for_entry(self.executable_entry, &block)
108
+ end
109
+
110
+ def codesignature_for_entry(file_entry)
111
+ _with_tmpdir do |tmp_path|
112
+ tmpf = tmp_path.join("executable")
113
+ file_entry.extract(tmpf.to_s)
114
+ return ::Pliney::AppleCodeSignature.from_path(tmpf.to_s)
115
+ end
116
+ end
117
+
118
+ def executable_codesignature
119
+ return codesignature_for_entry(self.executable_entry)
120
+ end
121
+
122
+ def entitlements_data_for_entry(file_entry)
123
+ cs = self.codesignature_for_entry(file_entry)
124
+ if cs
125
+ ents_blob = cs.contents.find{|c| c.is_a? ::Pliney::AppleCodeSignature::Entitlement}
126
+ if ents_blob
127
+ return ents_blob.data
128
+ end
129
+ end
130
+ end
131
+
132
+ def entitlements_for_entry(file_entry)
133
+ dat = entitlements_data_for_entry(file_entry)
134
+ if dat
135
+ return ::Pliney.parse_plist(dat)
136
+ end
137
+ end
138
+
139
+ def executable_entitlements_data
140
+ return entitlements_data_for_entry(self.executable_entry)
141
+ end
142
+
143
+ def executable_entitlements
144
+ return entitlements_for_entry(self.executable_entry)
145
+ end
146
+
147
+ def team_identifier
148
+ entitlements = executable_entitlements()
149
+ if entitlements
150
+ return entitlements["com.apple.developer.team-identifier"]
151
+ end
152
+ end
153
+
154
+ def extract(path)
155
+ if SYSTEM_HAS_UNZIP
156
+ ret = system("unzip", "-qd", path.to_s, self.zipfile.name.to_s)
157
+ unless ret
158
+ raise(ZipExtractError, "'unzip' command returned non-zero status: #{$?.inspect}")
159
+ end
160
+ else
161
+ path = Pathname(path)
162
+ zipfile.each do |ent|
163
+ extract_path = path.join(ent.name)
164
+ FileUtils.mkdir_p(extract_path.dirname)
165
+ ent.extract(extract_path.to_s)
166
+ extract_path.chmod(ent.unix_perms & 0777)
167
+ end
168
+ end
169
+ return path
170
+ end
171
+
172
+ def with_extracted_tmpdir(&block)
173
+ _with_tmpdir {|tmp_path| yield(extract(tmp_path)) }
174
+ end
175
+
176
+ def with_extracted_tmpfile(ent, &block)
177
+ tmpf = Tempfile.new("ent")
178
+ begin
179
+ Zip::IOExtras.copy_stream(tmpf, ent.get_input_stream)
180
+ tmpf.rewind
181
+ yield(tmpf)
182
+ ensure
183
+ tmpf.unlink()
184
+ end
185
+ end
186
+
187
+ def each_file_entry
188
+ zipfile.entries.select do |ent|
189
+ not ent.name_is_directory?
190
+ end.each do |ent|
191
+ yield(ent)
192
+ end
193
+ return nil
194
+ end
195
+
196
+ def each_executable_entry
197
+ each_file_entry do |entry|
198
+ next if (zipstream = entry.get_input_stream).nil?
199
+ next if (magicbytes = zipstream.read(4)).nil?
200
+ next if (magic = magicbytes.unpack("N").first).nil?
201
+ if ::Pliney::MachO::is_macho_magic(magic) or ::Pliney::MachO::is_fat_magic(magic)
202
+ yield(entry)
203
+ end
204
+ end
205
+ end
206
+
207
+ def executable_entries
208
+ a = []
209
+ each_executable_entry{|e| a << e}
210
+ return a
211
+ end
212
+
213
+ def canonical_name
214
+ return "#{self.team_identifier}.#{self.bundle_identifier}.v#{self.bundle_short_version}"
215
+ end
216
+
217
+ private
218
+
219
+ def _with_tmpdir
220
+ Dir.mktmpdir {|tmpd| yield(Pathname(tmpd)) }
221
+ end
93
222
  end
94
223
  end
@@ -0,0 +1,557 @@
1
+ require_relative 'io_helpers'
2
+ require_relative 'apple_code_signature'
3
+
4
+ # Note this implementation only works with little-endian mach-o binaries
5
+ # such as ARM and X86. Older PPC mach-o files are big-endian. Support could
6
+ # be pretty easily added just by conditionally swapping in the IOHelper addons
7
+ # for read_uintXXbe instead of read_uintXXle where appropriate
8
+
9
+ module Pliney
10
+ module MachO
11
+ FAT_MAGIC = 0xCAFEBABE
12
+ MACHO_MAGIC32 = 0xCEFAEDFE
13
+ MACHO_MAGIC64 = 0xCFFAEDFE
14
+
15
+ LC_REQ_DYLD = 0x80000000
16
+
17
+ def self.is_fat_magic(magicval)
18
+ return (magicval == FAT_MAGIC)
19
+ end
20
+
21
+ def self.is_macho_magic(magicval)
22
+ return (is_macho32_magic(magicval) or is_macho64_magic(magicval))
23
+ end
24
+
25
+ def self.is_macho32_magic(magicval)
26
+ return (magicval == MACHO_MAGIC32)
27
+ end
28
+
29
+ def self.is_macho64_magic(magicval)
30
+ return (magicval == MACHO_MAGIC64)
31
+ end
32
+
33
+ def self.lcmap
34
+ @LCMAP ||= Hash[
35
+ LoadCommandConst.constants.map do |lc|
36
+ [lc, LoadCommandConst.const_get(lc)]
37
+ end
38
+ ]
39
+ end
40
+
41
+ def self.resolve_lc(lcnum)
42
+ lcmap.invert[lcnum]
43
+ end
44
+
45
+ def self.reader_for_lc(lcnum)
46
+ lcsym = lcmap.invert[lcnum]
47
+ if lcsym
48
+ klname = "#{lcsym}_Reader"
49
+ if MachO.const_defined?(klname)
50
+ return MachO.const_get(klname)
51
+ end
52
+ end
53
+ return UndefinedLCReader
54
+ end
55
+
56
+ def self.reader_for_filemagic(magic)
57
+ case magic
58
+ when FAT_MAGIC
59
+ return FatHeaderReader
60
+ when MACHO_MAGIC32
61
+ return MachHeaderReader
62
+ when MACHO_MAGIC64
63
+ return MachHeader64Reader
64
+ else
65
+ raise(ReaderError, "Unrecognized magic value: 0x%0.8x" % magic)
66
+ end
67
+ end
68
+
69
+ def self.read_stream(fh)
70
+ magic = fh.read_uint32
71
+ fh.pos -= 4
72
+ return reader_for_filemagic(magic).parse(fh)
73
+ end
74
+ singleton_class.send(:alias_method, :from_stream, :read_stream)
75
+
76
+ module LoadCommandConst
77
+ LC_SEGMENT = 0x1
78
+ LC_SYMTAB = 0x2
79
+ LC_SYMSEG = 0x3
80
+ LC_THREAD = 0x4
81
+ LC_UNIXTHREAD = 0x5
82
+ LC_LOADFVMLIB = 0x6
83
+ LC_IDFVMLIB = 0x7
84
+ LC_IDENT = 0x8
85
+ LC_FVMFILE = 0x9
86
+ LC_PREPAGE = 0xa
87
+ LC_DYSYMTAB = 0xb
88
+ LC_LOAD_DYLIB = 0xc
89
+ LC_ID_DYLIB = 0xd
90
+ LC_LOAD_DYLINKER = 0xe
91
+ LC_ID_DYLINKER = 0xf
92
+ LC_PREBOUND_DYLIB = 0x10
93
+ LC_ROUTINES = 0x11
94
+ LC_SUB_FRAMEWORK = 0x12
95
+ LC_SUB_UMBRELLA = 0x13
96
+ LC_SUB_CLIENT = 0x14
97
+ LC_SUB_LIBRARY = 0x15
98
+ LC_TWOLEVEL_HINTS = 0x16
99
+ LC_PREBIND_CKSUM = 0x17
100
+ LC_LOAD_WEAK_DYLIB = (0x18 | LC_REQ_DYLD)
101
+ LC_SEGMENT_64 = 0x19
102
+ LC_ROUTINES_64 = 0x1a
103
+ LC_UUID = 0x1b
104
+ LC_RPATH = (0x1c | LC_REQ_DYLD)
105
+ LC_CODE_SIGNATURE = 0x1d
106
+ LC_SEGMENT_SPLIT_INFO = 0x1e
107
+ LC_REEXPORT_DYLIB = (0x1f | LC_REQ_DYLD)
108
+ LC_LAZY_LOAD_DYLIB = 0x20
109
+ LC_ENCRYPTION_INFO = 0x21
110
+ LC_DYLD_INFO = 0x22
111
+ LC_DYLD_INFO_ONLY = (0x22 | LC_REQ_DYLD)
112
+ LC_LOAD_UPWARD_DYLIB = (0x23 | LC_REQ_DYLD)
113
+ LC_VERSION_MIN_MACOSX = 0x24
114
+ LC_VERSION_MIN_IPHONEOS = 0x25
115
+ LC_FUNCTION_STARTS = 0x26
116
+ LC_DYLD_ENVIRONMENT = 0x27
117
+ LC_MAIN = (0x28 | LC_REQ_DYLD)
118
+ LC_DATA_IN_CODE = 0x29
119
+ LC_SOURCE_VERSION = 0x2A
120
+ LC_DYLIB_CODE_SIGN_DRS = 0x2B
121
+ LC_ENCRYPTION_INFO_64 = 0x2C
122
+ LC_LINKER_OPTION = 0x2D
123
+ LC_LINKER_OPTIMIZATION_HINT = 0x2E
124
+ LC_VERSION_MIN_TVOS = 0x2F
125
+ LC_VERSION_MIN_WATCHOS = 0x30
126
+ LC_NOTE = 0x31
127
+ LC_BUILD_VERSION = 0x32
128
+ end
129
+
130
+ include LoadCommandConst
131
+
132
+ class ReaderError < StandardError
133
+ end
134
+
135
+ class Reader
136
+ attr_reader :fh, :startpos
137
+
138
+ def self.parse(f)
139
+ ob = new(f)
140
+ ob.parse()
141
+ return ob
142
+ end
143
+
144
+ def initialize(f)
145
+ @fh = f
146
+ @startpos = @fh.pos
147
+ end
148
+
149
+ def parse()
150
+ @fh.pos = @startpos
151
+ end
152
+
153
+ def rewind()
154
+ @fh.pos = @startpos
155
+ end
156
+ end
157
+
158
+ class FatHeaderReader < Reader
159
+ attr_reader :magic, :nfat_arch, :fat_arches
160
+
161
+ def parse()
162
+ super()
163
+ @magic = @fh.read_uint32
164
+ @nfat_arch = @fh.read_uint32
165
+ @fat_arches = Array.new(@nfat_arch) { FatArchReader.parse(@fh) }
166
+
167
+ unless MachO::is_fat_magic(@magic)
168
+ raise(ReaderError, "Unexpected magic value for FAT header: 0x%0.8x" % @magic)
169
+ end
170
+ end
171
+
172
+ def machos()
173
+ a = []
174
+ each_macho {|mh| a << mh}
175
+ return a
176
+ end
177
+
178
+ def each_macho()
179
+ @fat_arches.each do |arch|
180
+ @fh.pos = @startpos + arch.offset
181
+ yield arch.macho_reader.parse(@fh)
182
+ end
183
+ end
184
+ end
185
+
186
+ class FatArchReader < Reader
187
+ CPU_ARCH_ABI64 = 0x01000000
188
+
189
+ attr_reader :cputype, :cpusubtype, :offset, :size, :align
190
+
191
+ def parse()
192
+ super()
193
+ @cputype = @fh.read_uint32
194
+ @cpusubtype = @fh.read_uint32
195
+ @offset = @fh.read_uint32
196
+ @size = @fh.read_uint32
197
+ @align = @fh.read_uint32
198
+ end
199
+
200
+ def macho_reader
201
+ if (@cputype & CPU_ARCH_ABI64) == 0
202
+ return MachHeaderReader
203
+ else
204
+ return MachHeader64Reader
205
+ end
206
+ end
207
+ end
208
+
209
+ class CommonMachHeaderReader < Reader
210
+ attr_reader :magic, :cputype, :cpusubtype, :filetype, :ncmds, :sizeofcmds, :flags
211
+
212
+ attr_reader :load_commands
213
+
214
+ def parse()
215
+ super()
216
+ @magic = @fh.read_uint32
217
+ unless MachO::is_macho_magic(@magic)
218
+ raise(ReaderError, "Unrecognized magic value for mach header: 0x%0.8x" % @magic)
219
+ end
220
+ @cputype = @fh.read_uint32le
221
+ @cpusubtype = @fh.read_uint32le
222
+ @filetype = @fh.read_uint32le
223
+ @ncmds = @fh.read_uint32le
224
+ @sizeofcmds = @fh.read_uint32le
225
+ @flags = @fh.read_uint32le
226
+ end
227
+
228
+ def all_load_commands_of_type(val)
229
+ v = _normalize_lc_lookup_type(val)
230
+ return [] if v.nil?
231
+ load_commands.select{|x| x.cmd == v }
232
+ end
233
+
234
+ def find_load_command_of_type(val)
235
+ v = _normalize_lc_lookup_type(val)
236
+ return nil if v.nil?
237
+ load_commands.find{|x| x.cmd == v }
238
+ end
239
+
240
+ def loaded_libraries()
241
+ all_load_commands_of_type(:LC_LOAD_DYLIB).map {|lc| lc.dylib_name }
242
+ end
243
+
244
+ def rpaths()
245
+ all_load_commands_of_type(:LC_RPATH).map{|lc| lc.rpath}.uniq
246
+ end
247
+
248
+ def read_at(offset, size)
249
+ @fh.pos = @startpos + offset
250
+ return @fh.read(size)
251
+ end
252
+
253
+ def codesignature_data()
254
+ lc = find_load_command_of_type(:LC_CODE_SIGNATURE)
255
+ return nil if lc.nil?
256
+ read_at(lc.dataoff, lc.datasize)
257
+ end
258
+
259
+ def codesignature()
260
+ cs = codesignature_data
261
+ return nil if cs.nil?
262
+ return AppleCodeSignature::parse(cs)
263
+ end
264
+
265
+ def is_32?()
266
+ return (@magic == MACHO_MAGIC32)
267
+ end
268
+
269
+ def is_64?()
270
+ return (@magic == MACHO_MAGIC64)
271
+ end
272
+
273
+ def encryption_info
274
+ ectyp = (is_64?)? :LC_ENCRYPTION_INFO_64 : :LC_ENCRYPTION_INFO
275
+ return find_load_command_of_type(ectyp)
276
+ end
277
+
278
+ def is_encrypted?
279
+ ec = encryption_info
280
+ return (ec and ec.cryptid != 0)
281
+ end
282
+
283
+ def segment_load_commands()
284
+ segtyp = (is_64?)? :LC_SEGMENT_64 : :LC_SEGMENT
285
+ return all_load_commands_of_type(segtyp)
286
+ end
287
+
288
+ private
289
+ # called privately by subclasses after parse()
290
+ def _parse_load_commands()
291
+ @load_commands = Array.new(@ncmds) do
292
+ cmd = @fh.read_uint32le
293
+ @fh.pos -= 4
294
+ klass = MachO.reader_for_lc(cmd)
295
+ klass.parse(@fh)
296
+ end
297
+ end
298
+
299
+ def _normalize_lc_lookup_type(val)
300
+ if val.is_a?(Integer)
301
+ return val
302
+ elsif val.is_a?(Symbol)
303
+ return MachO::lcmap[val]
304
+ elsif val.is_a?(String)
305
+ return MachO::lcmap[val.to_sym]
306
+ else
307
+ raise(ArgumentError, "Invalid load command lookup type: #{typ.class}")
308
+ end
309
+ end
310
+ end
311
+
312
+ class MachHeaderReader < CommonMachHeaderReader
313
+ def parse()
314
+ super()
315
+ unless MachO::is_macho32_magic(@magic)
316
+ raise(ReaderError, "Unexpected magic value for Mach header: 0x%0.8x" % @magic)
317
+ end
318
+ _parse_load_commands()
319
+ end
320
+ end
321
+
322
+ class MachHeader64Reader < CommonMachHeaderReader
323
+ attr_reader :_reserved
324
+ def parse()
325
+ super()
326
+ @_reserved = @fh.read_uint32le
327
+ unless MachO::is_macho64_magic(@magic)
328
+ raise(ReaderError, "Unexpected magic value for Mach 64 header: 0x%0.8x" % @magic)
329
+ end
330
+ _parse_load_commands()
331
+ end
332
+ end
333
+
334
+ class CommonLCReader < Reader
335
+ attr_reader :cmd, :cmdsize
336
+ def parse()
337
+ super()
338
+ @cmd = @fh.read_uint32le
339
+ @cmdsize = @fh.read_uint32le
340
+ if @cmdsize < 8
341
+ raise(ReaderError, "Load command size too small (#{@cmdsize} bytes) at offset #{@fh.pos - 4}")
342
+ end
343
+ end
344
+
345
+ def resolve_type()
346
+ MachO::resolve_lc(@cmd)
347
+ end
348
+ end
349
+
350
+ class UndefinedLCReader < CommonLCReader
351
+ attr_reader :cmd_data
352
+ def parse()
353
+ super()
354
+ @cmd_data = StringStream.new(@fh.strictread(@cmdsize - 8))
355
+ end
356
+ end
357
+
358
+ class CommonSegmentReader < CommonLCReader
359
+ attr_reader :segname, :vmaddr, :vmsize, :fileoff, :filesize, :maxprot, :initprot, :nsects, :flags
360
+
361
+ attr_reader :sections
362
+
363
+ def parse()
364
+ super()
365
+ @segname = @fh.strictread(16)
366
+ end
367
+
368
+ def segment_name()
369
+ @segname.unpack("Z16").first
370
+ end
371
+ end
372
+
373
+ class CommonSectionReader < Reader
374
+ attr_reader :sectname, :segname, :addr, :size, :offset, :align, :reloff, :nreloc, :flags, :_reserved1, :_reserved2
375
+ def parse()
376
+ super()
377
+ @sectname = @fh.strictread(16)
378
+ @segname = @fh.strictread(16)
379
+ end
380
+
381
+ def segment_name()
382
+ @segname.unpack("Z16").first
383
+ end
384
+
385
+ def section_name()
386
+ @sectname.unpack("Z16").first
387
+ end
388
+ end
389
+
390
+ class SectionReader < CommonSectionReader
391
+ def parse()
392
+ super()
393
+ @addr = @fh.read_uint32le
394
+ @size = @fh.read_uint32le
395
+ @offset = @fh.read_uint32le
396
+ @align = @fh.read_uint32le
397
+ @reloff = @fh.read_uint32le
398
+ @nreloc = @fh.read_uint32le
399
+ @flags = @fh.read_uint32le
400
+ @_reserved1 = @fh.read_uint32le
401
+ @_reserved2 = @fh.read_uint32le
402
+ end
403
+ end
404
+
405
+ class Section64Reader < CommonSectionReader
406
+ attr_reader :_reserved3
407
+ def parse()
408
+ super()
409
+ @addr = @fh.read_uint64le
410
+ @size = @fh.read_uint64le
411
+ @offset = @fh.read_uint32le
412
+ @align = @fh.read_uint32le
413
+ @reloff = @fh.read_uint32le
414
+ @nreloc = @fh.read_uint32le
415
+ @flags = @fh.read_uint32le
416
+ @_reserved1 = @fh.read_uint32le
417
+ @_reserved2 = @fh.read_uint32le
418
+ @_reserved3 = @fh.read_uint32le
419
+ end
420
+ end
421
+
422
+ class LC_SEGMENT_Reader < CommonSegmentReader
423
+ def parse()
424
+ super()
425
+ @vmaddr = @fh.read_uint32le
426
+ @vmsize = @fh.read_uint32le
427
+ @fileoff = @fh.read_uint32le
428
+ @filesize = @fh.read_uint32le
429
+ @maxprot = @fh.read_uint32le
430
+ @initprot = @fh.read_uint32le
431
+ @nsects = @fh.read_uint32le
432
+ @flags = @fh.read_uint32le
433
+
434
+ @sections = Array.new(@nsects) { SectionReader.parse(@fh) }
435
+ end
436
+ end
437
+
438
+ class LC_SEGMENT_64_Reader < CommonSegmentReader
439
+ def parse()
440
+ super()
441
+ @vmaddr = @fh.read_uint64le
442
+ @vmsize = @fh.read_uint64le
443
+ @fileoff = @fh.read_uint64le
444
+ @filesize = @fh.read_uint64le
445
+ @maxprot = @fh.read_uint32le
446
+ @initprot = @fh.read_uint32le
447
+ @nsects = @fh.read_uint32le
448
+ @flags = @fh.read_uint32le
449
+
450
+ @sections = Array.new(@nsects) { Section64Reader.parse(@fh) }
451
+ end
452
+ end
453
+
454
+ class CommonLinkeditDataCommandReader < CommonLCReader
455
+ attr_reader :dataoff, :datasize
456
+
457
+ def parse()
458
+ super()
459
+ @dataoff = @fh.read_uint32le
460
+ @datasize = @fh.read_uint32le
461
+ end
462
+ end
463
+
464
+ class LC_CODE_SIGNATURE_Reader < CommonLinkeditDataCommandReader
465
+ end
466
+
467
+ class LC_SEGMENT_SPLIT_INFO_Reader < CommonLinkeditDataCommandReader
468
+ end
469
+
470
+ class LC_FUNCTION_STARTS_Reader < CommonLinkeditDataCommandReader
471
+ end
472
+
473
+ class LC_DATA_IN_CODE_Reader < CommonLinkeditDataCommandReader
474
+ end
475
+
476
+ class LC_DYLIB_CODE_SIGN_DRS_Reader < CommonLinkeditDataCommandReader
477
+ end
478
+
479
+ class LC_LINKER_OPTIMIZATION_HINT_Reader < CommonLinkeditDataCommandReader
480
+ end
481
+
482
+ class CommonEncryptionInfoReader < CommonLCReader
483
+ attr_reader :cryptoff, :cryptsize, :cryptid
484
+
485
+ def parse()
486
+ super()
487
+ @cryptoff = @fh.read_uint32le
488
+ @cryptsize = @fh.read_uint32le
489
+ @cryptid = @fh.read_uint32le
490
+ end
491
+ end
492
+
493
+ class LC_ENCRYPTION_INFO_Reader < CommonEncryptionInfoReader
494
+ end
495
+
496
+ class LC_ENCRYPTION_INFO_64_Reader < CommonEncryptionInfoReader
497
+ attr_reader :_pad
498
+
499
+ def parse()
500
+ super()
501
+ @_pad = @fh.read_uint32le
502
+ end
503
+ end
504
+
505
+ class DylibStructReader < Reader
506
+ attr_reader :offset, :timestamp, :current_version, :compatibility_version
507
+
508
+ def parse()
509
+ super()
510
+ @offset = @fh.read_uint32le
511
+ @timestamp = @fh.read_uint32le
512
+ @current_version = @fh.read_uint32le
513
+ @compatibility_version = @fh.read_uint32le
514
+ end
515
+ end
516
+
517
+ class CommonDylibCommandReader < CommonLCReader
518
+ attr_reader :dylib_struct, :dylib_name_data
519
+
520
+ def parse()
521
+ super()
522
+ @dylib_struct = DylibStructReader.parse(@fh)
523
+ @dylib_name_data = @fh.strictread(self.cmdsize - self.dylib_struct.offset)
524
+ end
525
+
526
+ def dylib_name()
527
+ @dylib_name_data.unpack("Z*").first
528
+ end
529
+ end
530
+
531
+ class LC_ID_DYLIB_Reader < CommonDylibCommandReader
532
+ end
533
+
534
+ class LC_LOAD_DYLIB_Reader < CommonDylibCommandReader
535
+ end
536
+
537
+ class LC_LOAD_WEAK_DYLIB_Reader < CommonDylibCommandReader
538
+ end
539
+
540
+ class LC_REEXPORT_DYLIB_Reader < CommonDylibCommandReader
541
+ end
542
+
543
+ class LC_RPATH_Reader < CommonLCReader
544
+ attr_reader :offset, :rpath_data
545
+
546
+ def parse()
547
+ super()
548
+ @offset = @fh.read_uint32le
549
+ @rpath_data = @fh.strictread(@cmdsize - @offset)
550
+ end
551
+
552
+ def rpath()
553
+ @rpath_data.unpack("Z*").first
554
+ end
555
+ end
556
+ end
557
+ end
@@ -1,10 +1,19 @@
1
- require 'pliney/util'
2
- require 'pliney/entitlements'
3
1
  require 'openssl'
4
2
  require 'digest/sha1'
5
3
 
4
+ require_relative 'util'
5
+
6
6
  module Pliney
7
- class EntitlementsMask < Entitlements
7
+ class EntitlementsMask
8
+ def self.from_data(data)
9
+ new(Pliney.parse_plist(data))
10
+ end
11
+
12
+ attr_reader :ents
13
+ def initialize(ents)
14
+ @ents = ents
15
+ end
16
+
8
17
  end
9
18
 
10
19
  class ProvisioningProfile
@@ -7,4 +7,10 @@ module Pliney
7
7
  plist = CFPropertyList::List.new(data: rawdat)
8
8
  return CFPropertyList.native_types(plist.value)
9
9
  end
10
+
11
+ def self.write_plist(data, outpath, format = CFPropertyList::List::FORMAT_XML)
12
+ plist = CFPropertyList::List.new
13
+ plist.value = CFPropertyList.guess(data)
14
+ plist.save(outpath, format)
15
+ end
10
16
  end
@@ -1,3 +1,3 @@
1
1
  module Pliney
2
- VERSION = "0.0.4"
2
+ VERSION = "0.0.6"
3
3
  end
@@ -75,9 +75,62 @@ describe Pliney::IPA do
75
75
  @ipa.bundle_short_version.should == "1.0"
76
76
  end
77
77
 
78
- it "reads the executable object"
78
+ it "reads the codesignature for the main exe" do
79
+ cs = @ipa.executable_codesignature
80
+ cs.should be_a Pliney::AppleCodeSignature::EmbeddedSignature
81
+ end
82
+
83
+ it "reads the raw entitlements data" do
84
+ ents_data = @ipa.executable_entitlements_data
85
+ ents_data.should be_a String
86
+ ents_data.should =~ /^<\?xml/
87
+ end
88
+
89
+ it "reads the parsed entitlements" do
90
+ ents = @ipa.executable_entitlements
91
+ ents.should be_a Hash
92
+ ents["application-identifier"].should == "UL736KYQR9.computer.versus.pliney-test"
93
+ end
94
+
95
+ it "gets the team_identifier" do
96
+ @ipa.team_identifier == "UL736KYQR9"
97
+ end
98
+
99
+ it "extracts ipa contents" do
100
+ Dir.mktmpdir do |tmpd|
101
+ @ipa.extract(tmpd)
102
+ tmp_path = Pathname(tmpd)
103
+ @ipa.each_file_entry do |ent|
104
+ tmp_path.join(ent.name).exist?.should == true
105
+ end
106
+ end
107
+ end
79
108
 
80
- it "reads the entitlements"
109
+ it "extracts ipa contents to a temp directory yielded to a block" do
110
+ block_was_called = false
111
+ @ipa.with_extracted_tmpdir do |tmp_path|
112
+ block_was_called = true
113
+ @ipa.each_file_entry do |ent|
114
+ tmp_path.join(ent.name).exist?.should == true
115
+ end
116
+ end
117
+ block_was_called.should == true
118
+ end
119
+
120
+ it "reads the executable object" do
121
+ block_was_called = false
122
+ @ipa.with_executable_macho do |exe_obj|
123
+ block_was_called = true
124
+ exe_obj.should be_a Pliney::MachO::FatHeaderReader
125
+ exe_obj.fat_arches.count.should == 2
126
+ machos = exe_obj.machos
127
+ machos.map{|x| x.magic}.should == [
128
+ Pliney::MachO::MACHO_MAGIC32,
129
+ Pliney::MachO::MACHO_MAGIC64,
130
+ ]
131
+ end
132
+ block_was_called.should == true
133
+ end
81
134
 
82
135
  end
83
136
 
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: pliney
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.4
4
+ version: 0.0.6
5
5
  platform: ruby
6
6
  authors:
7
7
  - Eric Monti
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2016-02-19 00:00:00.000000000 Z
11
+ date: 2018-04-21 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rubyzip
@@ -124,8 +124,10 @@ files:
124
124
  - README.md
125
125
  - Rakefile
126
126
  - lib/pliney.rb
127
- - lib/pliney/entitlements.rb
127
+ - lib/pliney/apple_code_signature.rb
128
+ - lib/pliney/io_helpers.rb
128
129
  - lib/pliney/ipa.rb
130
+ - lib/pliney/macho.rb
129
131
  - lib/pliney/provisioning_profile.rb
130
132
  - lib/pliney/util.rb
131
133
  - lib/pliney/version.rb
@@ -1,13 +0,0 @@
1
-
2
- module Pliney
3
- class Entitlements
4
- def self.from_data(data)
5
- new(Pliney.parse_plist(data))
6
- end
7
-
8
- attr_reader :ents
9
- def initialize(ents)
10
- @ents = ents
11
- end
12
- end
13
- end