android_parser 2.5.1 → 2.6.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.github/workflows/create_release.yml +15 -0
- data/Gemfile +4 -5
- data/README.md +1 -1
- data/android_parser.gemspec +1 -1
- data/lib/android/apk.rb +49 -11
- data/lib/android/dex/info.rb +23 -6
- data/lib/android/manifest.rb +101 -1
- metadata +3 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 5a455cf668b1e1fbae27b208a872c304ac7ff60987034c529aa93c676b3e4348
|
4
|
+
data.tar.gz: 5089aec3a63dec1766ae3c981442c2a6fa9500f1351d0e989552e84047309443
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: b4e6104d2a29750902a79871b5a3e2750ea3ea3a8fdcde709870950ca93ddeaa2299f481c393be9556e21378a88c843afe8b9123b338c7a7e7c204a2800c6b99
|
7
|
+
data.tar.gz: 9474805fe13212f5193817cf0bc22b33163211232ec01cf4ad60b65eb4713992d701f3883f87241eb2d8b532d00397d6c641c167b19db4b8d71fc314bf87d352
|
@@ -0,0 +1,15 @@
|
|
1
|
+
name: Create Release
|
2
|
+
on:
|
3
|
+
push:
|
4
|
+
tags:
|
5
|
+
- "v*"
|
6
|
+
|
7
|
+
jobs:
|
8
|
+
create:
|
9
|
+
runs-on: ubuntu-latest
|
10
|
+
steps:
|
11
|
+
- name: Create Release
|
12
|
+
uses: softprops/action-gh-release@v1
|
13
|
+
if: startsWith(github.ref, 'refs/tags/')
|
14
|
+
env:
|
15
|
+
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
data/Gemfile
CHANGED
@@ -3,8 +3,7 @@ source 'http://rubygems.org'
|
|
3
3
|
# Specify your gem's dependencies in android_parser.gemspec
|
4
4
|
gemspec
|
5
5
|
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
# end
|
6
|
+
group :development do
|
7
|
+
gem 'awesome_print'
|
8
|
+
gem 'debug'
|
9
|
+
end
|
data/README.md
CHANGED
data/android_parser.gemspec
CHANGED
@@ -5,7 +5,7 @@ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
|
5
5
|
|
6
6
|
Gem::Specification.new do |spec|
|
7
7
|
spec.name = 'android_parser'
|
8
|
-
spec.version = '2.
|
8
|
+
spec.version = '2.6.0'
|
9
9
|
spec.authors = ['SecureBrain', 'icyleaf']
|
10
10
|
spec.email = ['info@securebrain.co.jp', 'icyleaf.cn@gmail.com']
|
11
11
|
spec.platform = Gem::Platform::RUBY
|
data/lib/android/apk.rb
CHANGED
@@ -189,7 +189,7 @@ module Android
|
|
189
189
|
# @return [nil] when label is not found
|
190
190
|
# @deprecated move to {Android::Manifest#label}
|
191
191
|
# @since 0.6.0
|
192
|
-
def label(lang=nil)
|
192
|
+
def label(lang = nil)
|
193
193
|
@manifest.label
|
194
194
|
end
|
195
195
|
|
@@ -200,25 +200,63 @@ module Android
|
|
200
200
|
@layouts ||= Layout.collect_layouts(self) # lazy parse
|
201
201
|
end
|
202
202
|
|
203
|
-
# apk's signature information
|
203
|
+
# apk's v1 signature information
|
204
204
|
# @return [Hash{ String => OpenSSL::PKCS7 } ] key: sign file path, value: signature
|
205
205
|
# @since 0.7.0
|
206
206
|
def signs
|
207
|
-
signs
|
208
|
-
|
209
|
-
|
210
|
-
|
211
|
-
|
212
|
-
|
213
|
-
|
207
|
+
@signs ||= lambda {
|
208
|
+
signs = {}
|
209
|
+
self.each_file do |path, data|
|
210
|
+
# find META-INF/xxx.{RSA|DSA|EC}
|
211
|
+
next unless path =~ /^META-INF\// && data.unpack("CC") == [0x30, 0x82]
|
212
|
+
|
213
|
+
signs[path] = OpenSSL::PKCS7.new(data)
|
214
|
+
end
|
215
|
+
signs
|
216
|
+
}.call
|
214
217
|
end
|
215
218
|
|
216
|
-
# certificate info which is used for signing
|
219
|
+
# v1 certificate info which is used for signing
|
217
220
|
# @return [Hash{String => OpenSSL::X509::Certificate }] key: sign file path, value: first certficate in the sign file
|
218
221
|
# @since 0.7.0
|
219
222
|
def certificates
|
220
|
-
|
223
|
+
@certificates ||= Hash[self.signs.map{|path, sign| [path, sign.certificates.first] }]
|
224
|
+
end
|
225
|
+
|
226
|
+
# detect if use kotlin language (may be third-party sdk or not)
|
227
|
+
# @return [Boolean]
|
228
|
+
# @since 2.6.0
|
229
|
+
def kotlin?
|
230
|
+
@kotlin ||= kotlin_file? || kotlin_classes?
|
221
231
|
end
|
232
|
+
|
233
|
+
private
|
234
|
+
|
235
|
+
def kotlin_file?
|
236
|
+
KOTLIN_FILES.any? do |file|
|
237
|
+
begin
|
238
|
+
entry(file)
|
239
|
+
rescue
|
240
|
+
next
|
241
|
+
end
|
242
|
+
end
|
243
|
+
end
|
244
|
+
|
245
|
+
def kotlin_classes?
|
246
|
+
@dex.classes.any? do |class_info|
|
247
|
+
class_info.type.start_with?('kotlin.') ||
|
248
|
+
class_info.type.start_with?('kotlinx.')
|
249
|
+
end
|
250
|
+
end
|
251
|
+
|
252
|
+
KOTLIN_FILES = [
|
253
|
+
'kotlin-tooling-metadata.json',
|
254
|
+
'kotlin/kotlin.kotlin_builtins',
|
255
|
+
'META-INF/kotlinx_coroutines_android.version',
|
256
|
+
'META-INF/kotlinx_coroutines_core.version',
|
257
|
+
'META-INF/services/kotlinx.coroutines.CoroutineExceptionHandler',
|
258
|
+
'META-INF/services/kotlinx.coroutines.internal.MainDispatcherFactory'
|
259
|
+
].freeze
|
222
260
|
end
|
223
261
|
end
|
224
262
|
|
data/lib/android/dex/info.rb
CHANGED
@@ -29,16 +29,32 @@ module Android
|
|
29
29
|
# @return [DexObject::ClassDefItem]
|
30
30
|
attr_reader :class_def
|
31
31
|
|
32
|
+
# return full namespace of class
|
33
|
+
#
|
34
|
+
# sample value like `Landroidx/activity/Cancellable;`
|
35
|
+
#
|
36
|
+
# @return [String] class name
|
32
37
|
def name
|
33
|
-
@dex.type_resolve(@class_def[:class_idx])
|
38
|
+
@name ||= @dex.type_resolve(@class_def[:class_idx])
|
34
39
|
end
|
40
|
+
|
35
41
|
def super_class
|
36
|
-
if @class_def[:superclass_idx] != NO_INDEX
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
42
|
+
@super_class ||= if @class_def[:superclass_idx] != NO_INDEX
|
43
|
+
@dex.type_resolve(@class_def[:superclass_idx])
|
44
|
+
else
|
45
|
+
nil
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
# Convert name to human readable string
|
50
|
+
#
|
51
|
+
# From `Landroidx/activity/Cancellable;` to `androidx.activity.Cancellable`
|
52
|
+
#
|
53
|
+
# @return [String] human readable name
|
54
|
+
def type
|
55
|
+
@type ||= name.gsub('/', '.')[1..-2]
|
41
56
|
end
|
57
|
+
|
42
58
|
# @param [Dex::ClassDefItem] class_def
|
43
59
|
# @param [Dex] dex dex class instance
|
44
60
|
def initialize(class_def, dex)
|
@@ -62,6 +78,7 @@ module Android
|
|
62
78
|
end
|
63
79
|
|
64
80
|
private
|
81
|
+
|
65
82
|
def cls2info(arr, cls, idx_key)
|
66
83
|
idx = 0
|
67
84
|
ret = []
|
data/lib/android/manifest.rb
CHANGED
@@ -10,7 +10,7 @@ module Android
|
|
10
10
|
# <activity>, <service>, <receiver> or <provider> element in <application> element of the manifest file.
|
11
11
|
class Component
|
12
12
|
# component types
|
13
|
-
TYPES = ['activity', 'activity-alias', 'service', 'receiver', 'provider', 'application']
|
13
|
+
TYPES = ['activity', 'activity-alias', 'service', 'receiver', 'provider', 'application', 'queries']
|
14
14
|
|
15
15
|
# the element is valid Component element or not
|
16
16
|
# @param [REXML::Element] elem xml element
|
@@ -322,6 +322,99 @@ module Android
|
|
322
322
|
end
|
323
323
|
end
|
324
324
|
|
325
|
+
# <intent>, <service>, <receiver> or <provider> element in <application> element of the manifest file.
|
326
|
+
class QueriesComponent
|
327
|
+
# component types
|
328
|
+
TYPES = ['package', 'intent', 'provider']
|
329
|
+
|
330
|
+
# @return [String] type string in TYPES
|
331
|
+
attr_reader :type
|
332
|
+
# @return [Manifest::Queries::Package]
|
333
|
+
attr_reader :packages
|
334
|
+
# @return [Array<Manifest::Queries::Intent>]
|
335
|
+
attr_reader :intents
|
336
|
+
# @return [Array<Manifest::Queries::Provider>]
|
337
|
+
attr_reader :providers
|
338
|
+
# @return [REXML::Element]
|
339
|
+
attr_reader :elem
|
340
|
+
|
341
|
+
# @param [REXML::Element] elem target element
|
342
|
+
# @raise [ArgumentError] when elem is invalid.
|
343
|
+
def initialize(elem)
|
344
|
+
raise ArgumentError unless Component.valid?(elem)
|
345
|
+
|
346
|
+
@elem = elem
|
347
|
+
@type = elem.name
|
348
|
+
@packages = parse_packages
|
349
|
+
@intents = parse_intents
|
350
|
+
@providers = parse_providers
|
351
|
+
end
|
352
|
+
|
353
|
+
private
|
354
|
+
|
355
|
+
def parse_packages
|
356
|
+
packages = []
|
357
|
+
return packages if @elem.elements['package'].nil?
|
358
|
+
|
359
|
+
@elem.each_element('package') do |package|
|
360
|
+
packages << Android::Manifest::Queries::Package.new(package)
|
361
|
+
end
|
362
|
+
|
363
|
+
packages
|
364
|
+
end
|
365
|
+
|
366
|
+
def parse_intents
|
367
|
+
intents = []
|
368
|
+
return intents if @elem.elements['intent'].nil?
|
369
|
+
|
370
|
+
@elem.each_element('intent') do |intent|
|
371
|
+
next if intent&.elements&.empty?
|
372
|
+
next unless Android::Manifest::Queries::Intent.valid?(intent)
|
373
|
+
|
374
|
+
intent = Android::Manifest::Queries::Intent.new(intent)
|
375
|
+
intents << intent unless intent.empty?
|
376
|
+
end
|
377
|
+
|
378
|
+
intents
|
379
|
+
end
|
380
|
+
|
381
|
+
def parse_providers
|
382
|
+
providers = []
|
383
|
+
return providers if @elem.elements['provider'].nil?
|
384
|
+
|
385
|
+
@elem.each_element('provider') do |provider|
|
386
|
+
providers << Android::Manifest::Queries::Provider.new(provider)
|
387
|
+
end
|
388
|
+
|
389
|
+
providers
|
390
|
+
end
|
391
|
+
end
|
392
|
+
|
393
|
+
class Queries < QueriesComponent
|
394
|
+
class Intent < Android::Manifest::IntentFilter; end
|
395
|
+
class Provider < Android::Manifest::Provider
|
396
|
+
def authorities
|
397
|
+
@authorities ||= @elem.attributes['authorities']
|
398
|
+
end
|
399
|
+
end
|
400
|
+
|
401
|
+
class Package
|
402
|
+
# @return [String] action name of queries
|
403
|
+
attr_reader :name
|
404
|
+
# @return [String] action type of intent-filter
|
405
|
+
attr_reader :type
|
406
|
+
|
407
|
+
def initialize(elem)
|
408
|
+
@type = 'package'
|
409
|
+
@name = elem.attributes['name']
|
410
|
+
end
|
411
|
+
end
|
412
|
+
|
413
|
+
def self.valid?(elem)
|
414
|
+
elem&.name == 'queries'
|
415
|
+
end
|
416
|
+
end
|
417
|
+
|
325
418
|
#################################
|
326
419
|
# Manifest class definitions
|
327
420
|
#################################
|
@@ -370,6 +463,13 @@ module Android
|
|
370
463
|
components
|
371
464
|
end
|
372
465
|
|
466
|
+
# Returns the manifest's queries element or nil, if there isn't any.
|
467
|
+
# @return [Android::Manifest::Queries] the manifest's application element
|
468
|
+
def queries
|
469
|
+
element = @doc.elements['/manifest/queries']
|
470
|
+
Queries.new(element) if Queries.valid?(element)
|
471
|
+
end
|
472
|
+
|
373
473
|
# @return [Array<Android::Manifest::Activity&ActivityAlias>] all activities in the apk
|
374
474
|
# @note return empty array when the manifest include no activities
|
375
475
|
def activities
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: android_parser
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 2.
|
4
|
+
version: 2.6.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- SecureBrain
|
@@ -9,7 +9,7 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2023-03-
|
12
|
+
date: 2023-03-28 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: rubyzip
|
@@ -139,6 +139,7 @@ extra_rdoc_files: []
|
|
139
139
|
files:
|
140
140
|
- ".github/dependabot.yml"
|
141
141
|
- ".github/workflows/ci.yml"
|
142
|
+
- ".github/workflows/create_release.yml"
|
142
143
|
- ".gitignore"
|
143
144
|
- ".rspec"
|
144
145
|
- CHANGELOG.md
|