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 +4 -4
- data/README.md +1 -7
- data/lib/pliney.rb +3 -3
- data/lib/pliney/apple_code_signature.rb +249 -0
- data/lib/pliney/io_helpers.rb +64 -0
- data/lib/pliney/ipa.rb +131 -2
- data/lib/pliney/macho.rb +557 -0
- data/lib/pliney/provisioning_profile.rb +12 -3
- data/lib/pliney/util.rb +6 -0
- data/lib/pliney/version.rb +1 -1
- data/spec/ipa_spec.rb +55 -2
- metadata +5 -3
- data/lib/pliney/entitlements.rb +0 -13
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 9acf33365d64a3a900b1a620ebd8a638a45ef6aa
|
4
|
+
data.tar.gz: 253367edc5a548e08baefcf956af43b68884eaa9
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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
|
-
- ?
|
data/lib/pliney.rb
CHANGED
@@ -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
|
+
|
data/lib/pliney/ipa.rb
CHANGED
@@ -1,10 +1,16 @@
|
|
1
1
|
require 'rubygems'
|
2
2
|
require 'zip'
|
3
|
-
|
4
|
-
|
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
|
data/lib/pliney/macho.rb
ADDED
@@ -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
|
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
|
data/lib/pliney/util.rb
CHANGED
@@ -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
|
data/lib/pliney/version.rb
CHANGED
data/spec/ipa_spec.rb
CHANGED
@@ -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
|
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 "
|
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
|
+
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:
|
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/
|
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
|