pliney 0.0.4 → 0.0.6

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.
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