android_parser 2.5.1 → 2.7.0.beta1

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
  SHA256:
3
- metadata.gz: 8360af54a4093e8cc95844b8b7419e3190aed92f5aa6c832f0fd4f7e8ccd2f53
4
- data.tar.gz: 1b8a9172fdf2b4f18c43cb0a342aa5244f7ad109cbfc8b8995d74d6de9fc34a9
3
+ metadata.gz: fe3ead9fd9c4393e0cf3fb39fae3f4ab4a99e68e30cbceae79a8861d451f3266
4
+ data.tar.gz: 2d80687dc01a1541d4f0b049d499e448eeee4f7413834fd603b5c7560b40048b
5
5
  SHA512:
6
- metadata.gz: 393956811a1cfcb6bd043cdc8c6494a1760a0558d6d12ce157ee9e08b67035499f702e15cf8d6baa7971572eb616c0de28ebc92003a4e9a32c18541eb82b7f7b
7
- data.tar.gz: a7998f5d5e71b889c083f6b1d73f4d277bd7aecd6c412a5d4b14dc10bccf1104472dffde03c2a2280071f88c846906f15c80991d73689f2dd957a46270ac3fbf
6
+ metadata.gz: 98d6c6286ec3c02a7939e94bdeff0d0316db3997df828af79bb5b585e4f698ac36cb63c419d66da0da97ba48595e33e3d405e8296de95161cb3a9d8003931233
7
+ data.tar.gz: bd31cc2455cf30dcb001cc838fa48608910a20aaaa7b9e74f020c4c6fb3ea07f19cc6d0e598813bccfcfec30765f1775968cf4cba0e436a58bc6ddb2548eba00
@@ -10,12 +10,12 @@ jobs:
10
10
  strategy:
11
11
  fail-fast: false
12
12
  matrix:
13
- ruby: [ ruby-2.5, ruby-2.6, ruby-2.7, ruby-3.0, ruby-3.1, ruby-3.2 ]
13
+ ruby: [ ruby-3.1, ruby-3.2, ruby-3.3 ]
14
14
  os: [ ubuntu-latest, macos-latest ]
15
15
  runs-on: ${{ matrix.os }}
16
16
 
17
17
  steps:
18
- - uses: actions/checkout@v3
18
+ - uses: actions/checkout@v4
19
19
 
20
20
  - uses: ruby/setup-ruby@v1
21
21
  with:
@@ -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@v2
13
+ if: startsWith(github.ref, 'refs/tags/')
14
+ env:
15
+ GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
data/CHANGELOG.md CHANGED
@@ -1,5 +1,19 @@
1
1
  # ChangeLog
2
2
 
3
+ ## 2.7.0.beta1
4
+
5
+ * Dropped Ruby 2.5~3.0 support, now requires Ruby 3.1+.
6
+ * Bugfix: [#7] parse library type to fix unknown chunk type 0x0203.
7
+ * Bugfix: fix 3bits of lang and country in locales.
8
+ * add fetch locales support.
9
+ * add architectures support.
10
+ * add detect universal apk.
11
+
12
+ ## 2.6.0
13
+
14
+ * [#2] add queries component support.
15
+ * [#3] add detect enable kotlin code.
16
+
3
17
  ## 2.5.1
4
18
 
5
19
  * Bugfix: Handle string resources referencing other resources.
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
- # group :development do
7
- # gem 'awesome_print'
8
- # gem 'debase'
9
- # gem 'ruby-debug-ide'
10
- # end
6
+ group :development do
7
+ gem 'awesome_print'
8
+ gem 'debug'
9
+ end
data/README.md CHANGED
@@ -55,7 +55,7 @@ icons.each do |name, data|
55
55
  end
56
56
  ```
57
57
 
58
- #### Extract signature and certificate information from Apk (since v0.7.0)
58
+ #### Extract v1 signature and certificate information from Apk (since v0.7.0)
59
59
 
60
60
  ```ruby
61
61
  apk = Android::Apk.new('sample.apk')
@@ -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.5.1'
8
+ spec.version = '2.7.0.beta1'
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
@@ -15,13 +15,10 @@ Gem::Specification.new do |spec|
15
15
  spec.license = 'MIT'
16
16
  spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
17
17
  spec.require_paths = ['lib']
18
- spec.required_ruby_version = '>= 2.5'
18
+ spec.required_ruby_version = '>= 3.1'
19
19
 
20
20
  spec.add_dependency 'rubyzip', '>= 1.0', '< 3.0'
21
-
22
- if Gem::Version.new(RUBY_VERSION) >= Gem::Version.new('3.0.0') then
23
- spec.add_dependency 'rexml', '> 3.0'
24
- end
21
+ spec.add_dependency 'rexml', '> 3.0' # requires for Ruby 3.0+
25
22
 
26
23
  spec.add_development_dependency 'rspec', '~> 3.0'
27
24
  spec.add_development_dependency 'rspec-its', '>= 1.2.0'
Binary file
data/lib/android/apk.rb CHANGED
@@ -12,7 +12,6 @@ module Android
12
12
 
13
13
  # apk object class
14
14
  class Apk
15
-
16
15
  # @return [String] apk file path
17
16
  attr_reader :path
18
17
  # @return [Android::Manifest] manifest instance
@@ -43,32 +42,30 @@ module Android
43
42
 
44
43
  @path = filepath
45
44
  raise NotFoundError, "'#{filepath}'" unless File.exist? @path
46
- begin
47
- @zip = Zip::File.open(@path)
48
- rescue Zip::Error => e
49
- raise NotApkFileError, e.message
50
- end
51
45
 
52
46
  @bindata = File.open(@path, 'rb') {|f| f.read }
53
47
  @bindata.force_encoding(Encoding::ASCII_8BIT)
54
- raise NotApkFileError, "manifest file is not found." if @zip.find_entry(MANIFEST).nil?
48
+ raise NotApkFileError, 'manifest file is not found.' if zip.find_entry(MANIFEST).nil?
49
+
55
50
  begin
56
51
  @resource = Android::Resource.new(self.file(RESOURCE))
57
52
  rescue => e
58
- $stderr.puts "failed to parse resource: #{e}"
59
- #$stderr.puts e.backtrace
53
+ logger.error "failed to parse resource: #{e} with backtrace"
54
+ logger.error e.backtrace
60
55
  end
56
+
61
57
  begin
62
58
  @manifest = Android::Manifest.new(self.file(MANIFEST), @resource)
63
59
  rescue => e
64
- $stderr.puts "failed to parse manifest:#{e}"
65
- #$stderr.puts e.backtrace
60
+ logger.error "failed to parse manifest:#{e} with backtrace"
61
+ logger.error e.backtrace
66
62
  end
63
+
67
64
  begin
68
65
  @dex = Android::Dex.new(self.file(DEX))
69
66
  rescue => e
70
- $stderr.puts "failed to parse dex:#{e}"
71
- #$stderr.puts e.backtrace
67
+ logger.error "failed to parse dex:#{e} with backtrace"
68
+ logger.error e.backtrace
72
69
  end
73
70
  end
74
71
 
@@ -105,9 +102,9 @@ module Android
105
102
  # @yieldparam [String] name file name in apk
106
103
  # @yieldparam [String] data file data in apk
107
104
  def each_file
108
- @zip.each do |entry|
105
+ zip.each do |entry|
109
106
  next unless entry.file?
110
- yield entry.name, @zip.read(entry)
107
+ yield entry.name, zip.read(entry)
111
108
  end
112
109
  end
113
110
 
@@ -116,13 +113,13 @@ module Android
116
113
  # @return [String] binary data
117
114
  # @raise [NotFoundError] when 'name' doesn't exist in the apk
118
115
  def file(name) # get data by entry name(path)
119
- @zip.read(entry(name))
116
+ zip.read(entry(name))
120
117
  end
121
118
 
122
119
  # @yield [entry]
123
120
  # @yieldparam [Zip::Entry] entry zip entry
124
121
  def each_entry
125
- @zip.each do |entry|
122
+ zip.each do |entry|
126
123
  next unless entry.file?
127
124
  yield entry
128
125
  end
@@ -133,7 +130,7 @@ module Android
133
130
  # @return [Zip::Entry] zip entry object
134
131
  # @raise [NotFoundError] when 'name' doesn't exist in the apk
135
132
  def entry(name)
136
- entry = @zip.find_entry(name)
133
+ entry = zip.find_entry(name)
137
134
  raise NotFoundError, "'#{name}'" if entry.nil?
138
135
  return entry
139
136
  end
@@ -189,7 +186,7 @@ module Android
189
186
  # @return [nil] when label is not found
190
187
  # @deprecated move to {Android::Manifest#label}
191
188
  # @since 0.6.0
192
- def label(lang=nil)
189
+ def label(lang = nil)
193
190
  @manifest.label
194
191
  end
195
192
 
@@ -200,25 +197,89 @@ module Android
200
197
  @layouts ||= Layout.collect_layouts(self) # lazy parse
201
198
  end
202
199
 
203
- # apk's signature information
200
+ # apk's v1 signature information
204
201
  # @return [Hash{ String => OpenSSL::PKCS7 } ] key: sign file path, value: signature
205
202
  # @since 0.7.0
206
203
  def signs
207
- signs = {}
208
- self.each_file do |path, data|
209
- # find META-INF/xxx.{RSA|DSA}
210
- next unless path =~ /^META-INF\// && data.unpack("CC") == [0x30, 0x82]
211
- signs[path] = OpenSSL::PKCS7.new(data)
212
- end
213
- signs
204
+ @signs ||= lambda {
205
+ signs = {}
206
+ self.each_file do |path, data|
207
+ # find META-INF/xxx.{RSA|DSA|EC}
208
+ next unless path =~ /^META-INF\// && data.unpack("CC") == [0x30, 0x82]
209
+
210
+ signs[path] = OpenSSL::PKCS7.new(data)
211
+ end
212
+ signs
213
+ }.call
214
214
  end
215
215
 
216
- # certificate info which is used for signing
216
+ # v1 certificate info which is used for signing
217
217
  # @return [Hash{String => OpenSSL::X509::Certificate }] key: sign file path, value: first certficate in the sign file
218
218
  # @since 0.7.0
219
219
  def certificates
220
- return Hash[self.signs.map{|path, sign| [path, sign.certificates.first] }]
220
+ @certificates ||= Hash[self.signs.map{|path, sign| [path, sign.certificates.first] }]
221
+ end
222
+
223
+ # Return all architectures (all most for universal apk)
224
+ # @return [Array<String>]
225
+ # @since 2.7.0
226
+ def archs
227
+ @archs ||= zip.glob('lib/**/*').each_with_object([]) do |entry, obj|
228
+ arch = entry.name.split('/')[1]
229
+ obj << arch unless obj.include?(arch)
230
+ end
231
+ end
232
+
233
+ # detect if contains multi-platforms (native machine code)
234
+ # @return [Boolean]
235
+ # @since 2.7.0
236
+ def universal?
237
+ archs.size > 1
238
+ end
239
+
240
+ # detect if use kotlin language (may be third-party sdk or not)
241
+ # @return [Boolean]
242
+ # @since 2.6.0
243
+ def kotlin?
244
+ @kotlin ||= kotlin_file? || kotlin_classes?
245
+ end
246
+
247
+ private
248
+
249
+ def kotlin_file?
250
+ KOTLIN_FILES.any? do |file|
251
+ begin
252
+ entry(file)
253
+ rescue
254
+ next
255
+ end
256
+ end
257
+ end
258
+
259
+ def kotlin_classes?
260
+ @dex.classes.any? do |class_info|
261
+ class_info.type.start_with?('kotlin.') ||
262
+ class_info.type.start_with?('kotlinx.')
263
+ end
264
+ end
265
+
266
+ KOTLIN_FILES = [
267
+ 'kotlin-tooling-metadata.json',
268
+ 'kotlin/kotlin.kotlin_builtins',
269
+ 'META-INF/kotlinx_coroutines_android.version',
270
+ 'META-INF/kotlinx_coroutines_core.version',
271
+ 'META-INF/services/kotlinx.coroutines.CoroutineExceptionHandler',
272
+ 'META-INF/services/kotlinx.coroutines.internal.MainDispatcherFactory'
273
+ ].freeze
274
+
275
+ def zip
276
+ @zip ||= Zip::File.open(@path)
277
+ rescue Zip::Error => e
278
+ raise NotApkFileError, e.message
279
+ end
280
+
281
+ def logger
282
+ Android.logger
221
283
  end
222
284
  end
223
285
  end
224
-
@@ -0,0 +1,13 @@
1
+ module Android
2
+ module Configuration
3
+ attr_writer :logger
4
+
5
+ def configure
6
+ yield self
7
+ end
8
+
9
+ def logger
10
+ @logger ||= NullLogger.new
11
+ end
12
+ end
13
+ end
@@ -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
- @super_class = @dex.type_resolve(@class_def[:superclass_idx])
38
- else
39
- nil
40
- end
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 = []
@@ -10,14 +10,14 @@ 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
17
17
  # @return [Boolean]
18
18
  def self.valid?(elem)
19
19
  TYPES.include?(elem.name.downcase)
20
- rescue => e
20
+ rescue
21
21
  false
22
22
  end
23
23
 
@@ -83,7 +83,7 @@ module Android
83
83
  # @return [Boolean]
84
84
  def self.valid?(elem)
85
85
  ['activity', 'activity-alias'].include?(elem.name.downcase)
86
- rescue => e
86
+ rescue
87
87
  false
88
88
  end
89
89
 
@@ -145,7 +145,7 @@ module Android
145
145
  filter&.elements&.any? do |elem|
146
146
  TYPES.include?(elem&.name&.downcase)
147
147
  end
148
- rescue => e
148
+ rescue
149
149
  false
150
150
  end
151
151
 
@@ -322,10 +322,103 @@ 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
  #################################
328
- #
421
+
329
422
  # @return [REXML::Document] manifest xml
330
423
  attr_reader :doc
331
424
 
@@ -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
@@ -0,0 +1,65 @@
1
+
2
+ module Android
3
+ class NullLogger
4
+ # @param _args Anything that we want to ignore
5
+ # @return [nil]
6
+ def unknown(*_args)
7
+ nil
8
+ end
9
+
10
+ # @param _args Anything that we want to ignore
11
+ # @return [nil]
12
+ def fatal(*_args)
13
+ nil
14
+ end
15
+
16
+ # @return [FALSE]
17
+ def fatal?
18
+ false
19
+ end
20
+
21
+ # @param _args Anything that we want to ignore
22
+ # @return [nil]
23
+ def error(*_args)
24
+ nil
25
+ end
26
+
27
+ # @return [FALSE]
28
+ def error?
29
+ false
30
+ end
31
+
32
+ # @param _args Anything that we want to ignore
33
+ # @return [nil]
34
+ def warn(*_args)
35
+ nil
36
+ end
37
+
38
+ # @return [FALSE]
39
+ def warn?
40
+ false
41
+ end
42
+
43
+ # @param _args Anything that we want to ignore
44
+ # @return [nil]
45
+ def info(*_args)
46
+ nil
47
+ end
48
+
49
+ # @return [FALSE]
50
+ def info?
51
+ false
52
+ end
53
+
54
+ # @param _args Anything that we want to ignore
55
+ # @return [nil]
56
+ def debug(*_args)
57
+ nil
58
+ end
59
+
60
+ # @return [FALSE]
61
+ def debug?
62
+ false
63
+ end
64
+ end
65
+ end
@@ -6,6 +6,8 @@ module Android
6
6
  # /frameworks/base/include/utils/ResourceTypes.h
7
7
  # @see http://justanapplication.wordpress.com/category/android/android-resources/
8
8
  class Resource
9
+ class UnknownChunkType < StandardError; end
10
+
9
11
  class Chunk
10
12
  def initialize(data, offset)
11
13
  data.force_encoding(Encoding::ASCII_8BIT)
@@ -198,7 +200,7 @@ module Android
198
200
  end
199
201
 
200
202
  class ResTablePackage < ChunkHeader
201
- attr_reader :name
203
+ attr_reader :name, :locales
202
204
 
203
205
  def global_string_pool=(pool)
204
206
  @global_string_pool = pool
@@ -279,7 +281,7 @@ module Android
279
281
  "@#{type(tid)}/#{key(keyid)}"
280
282
  end
281
283
  def res_hex_id(readable_id, opt={})
282
- dummy, typestr, keystr = readable_id.match(/^@?(\w+)\/(\w+)$/).to_a
284
+ _dummy, typestr, keystr = readable_id.match(/^@?(\w+)\/(\w+)$/).to_a
283
285
  tid = type_id(typestr)
284
286
  raise NotFoundError unless @types.has_key?(tid)
285
287
  keyid = @types[tid][0].keys[keystr]
@@ -324,21 +326,26 @@ module Android
324
326
 
325
327
  @types = {}
326
328
  @specs = {}
329
+ @libraries = []
327
330
  while offset < (@offset + @size)
328
331
  type = @data[offset, 2].unpack('v')[0]
329
332
  case type
330
- when 0x0201 # RES_TABLE_TYPE_TYPE
333
+ when TYPE_TYPE
331
334
  type = ResTableType.new(@data, offset, self)
332
335
  offset += type.size
333
336
  @types[type.id] = [] if @types[type.id].nil?
334
337
  @types[type.id] << type
335
- when 0x0202 # RES_TABLE_TYPE_SPEC_TYPE`
338
+ when TYPE_SPEC_TYPE
336
339
  spec = ResTableTypeSpec.new(@data, offset)
337
340
  offset += spec.size
338
341
  @specs[spec.id] = [] if @specs[spec.id].nil?
339
342
  @specs[spec.id] << spec
343
+ when TYPE_LIBRARY
344
+ library = ResTableLibraryType.new(@data, offset)
345
+ offset += library.size
346
+ @libraries.concat(library.libraries)
340
347
  else
341
- raise "chunk type error: type:%#04x" % type
348
+ raise UnknownChunkType, "chunk type error: type:%#04x" % type
342
349
  end
343
350
  end
344
351
  end
@@ -348,7 +355,7 @@ module Android
348
355
  @res_strings_lang = {}
349
356
  @res_strings_contry = {}
350
357
  begin
351
- type = type_id('string')
358
+ _type = type_id('string')
352
359
  rescue NotFoundError
353
360
  return
354
361
  end
@@ -367,6 +374,8 @@ module Android
367
374
  @res_strings_contry[contry] = str_hash unless contry.nil?
368
375
  end
369
376
  end
377
+
378
+ @locales = @res_strings_lang.keys
370
379
  end
371
380
  private :extract_res_strings
372
381
 
@@ -409,8 +418,8 @@ module Android
409
418
  def parse
410
419
  super
411
420
  @id = read_int8
412
- res0 = read_int8 # must be 0.(maybe 4byte align)
413
- res1 = read_int16 # must be 0.(maybe 4byte align)
421
+ _res0 = read_int8 # must be 0.(maybe 4byte align)
422
+ _res1 = read_int16 # must be 0.(maybe 4byte align)
414
423
  @entry_count = read_int32
415
424
  @entry_start = read_int32
416
425
  @config = ResTableConfig.new(@data, current_position)
@@ -444,10 +453,8 @@ module Android
444
453
  def parse
445
454
  @size = read_int32
446
455
  @imei = read_int32
447
- la = @data_io.read(2)
448
- @locale_lang = la unless la == "\x00\x00"
449
- cn = @data_io.read(2)
450
- @locale_contry = cn unless cn == "\x00\x00"
456
+ @locale_lang = unpack_locale(@data_io.read(1), @data_io.read(1), 'a')
457
+ @locale_contry = unpack_locale(@data_io.read(1), @data_io.read(1), '0')
451
458
  @screen_type = read_int32
452
459
  @input = read_int32
453
460
  @screen_input = read_int32
@@ -457,6 +464,27 @@ module Android
457
464
  def inspect
458
465
  "<ResTableConfig size:#{@size}, imei:#{@imei}, la:'#{@locale_lang}' cn:'#{@locale_contry}'"
459
466
  end
467
+
468
+ private
469
+
470
+ # Convert from https://github.com/LoyieKing/Apktool/blob/e0e6cfd03f10d710e666c29456659688fb5aeea2/brut.apktool/apktool-lib/src/main/java/brut/androlib/res/decoder/ARSCDecoder.java#L474
471
+ def unpack_locale(in0, in1, base)
472
+ return if in0 == "\x00" && in1 == "\x00"
473
+
474
+ in0_value = in0.ord
475
+ in1_value = in1.ord
476
+ # check high bit, if so we have a packed 3 letter code
477
+ return [(in0_value).chr, (in1_value).chr].join('') unless ((in0_value >> 7) & 1) == 1
478
+
479
+ first = in1_value & 0x1F
480
+ second = ((in1_value & 0xE0) >> 5) + ((in0_value & 0x03) << 3)
481
+ third = (in0_value & 0x7C) >> 2
482
+
483
+ base_value = base.ord
484
+ # since this function handles languages & regions, we add the value(s) to the base char
485
+ # which is usually 'a' or '0' depending on language or region.
486
+ [(first + base_value).chr, (second + base_value).chr, (third + base_value).chr].join('')
487
+ end
460
488
  end
461
489
 
462
490
  class ResTableTypeSpec < ChunkHeader
@@ -465,8 +493,8 @@ module Android
465
493
  def parse
466
494
  super
467
495
  @id = read_int8
468
- res0 = read_int8 # must be 0.(maybe 4byte align)
469
- res1 = read_int16 # must be 0.(maybe 4byte align)
496
+ _res0 = read_int8 # must be 0.(maybe 4byte align)
497
+ _res1 = read_int16 # must be 0.(maybe 4byte align)
470
498
  @entry_count = read_int32
471
499
  end
472
500
  private :parse
@@ -531,29 +559,54 @@ module Android
531
559
  end
532
560
  end
533
561
 
562
+ class ResTableLibraryType < ChunkHeader
563
+ attr_reader :libraries
564
+
565
+ def parse
566
+ super
567
+ @libraries = []
568
+ library_count = read_int32
569
+ library_count.times do
570
+ package_id = read_int32
571
+ package_name = @data_io.read(128).force_encoding(Encoding::UTF_16LE).strip
572
+ @libraries << ResTableLibraryValue.new(package_id, package_name)
573
+ end
574
+ end
575
+ private :parse
576
+
577
+ def inspect
578
+ "<ResTableLibraryType library_count:#{@libraries.size}>"
579
+ end
580
+
581
+ ResTableLibraryValue = Struct.new(:package_id, :package_name)
582
+ end
583
+
534
584
  class ResValue < Chunk
535
585
  TYPE_REFERENCE = 0x01
536
586
 
537
587
  attr_reader :size, :data_type, :data
538
588
  def parse
539
589
  @size = read_int16
540
- res0 = read_int8 # Always set 0.
590
+ _res0 = read_int8 # Always set 0.
541
591
  @data_type = read_int8
542
592
  @data = read_int32
543
593
  end
544
594
  private :parse
545
595
  end
546
596
 
547
- ######################################################################
597
+ #################################
598
+ # Resource class definitions
599
+ #################################
600
+
548
601
  # @returns [Hash] { name(String) => value(ResTablePackage) }
549
602
  attr_reader :packages
550
603
 
551
604
  def initialize(data)
552
605
  data.force_encoding(Encoding::ASCII_8BIT)
553
606
  @data = data
554
- parse()
555
- end
556
607
 
608
+ parse
609
+ end
557
610
 
558
611
  # @return [Array<String>] all strings defined in arsc.
559
612
  def strings
@@ -565,7 +618,7 @@ module Android
565
618
  @res_table.package_count
566
619
  end
567
620
 
568
- # This method only support string resource for now.
621
+ # This method only support string resource for now.
569
622
  # find resource by resource id
570
623
  # @param [String] res_id (like '@0x7f010001' or '@string/key')
571
624
  # @param [Hash] opts option
@@ -596,6 +649,13 @@ module Android
596
649
  first_pkg.res_hex_id(readable_id)
597
650
  end
598
651
 
652
+ # Return resources locales
653
+ # @return [Array<String>] all strings of locales.
654
+ # @since 2.7.0
655
+ def locales
656
+ first_pkg.locales
657
+ end
658
+
599
659
  def first_pkg
600
660
  @packages.first[1]
601
661
  end
@@ -607,27 +667,38 @@ module Android
607
667
 
608
668
  while offset < @data.size
609
669
  type = @data[offset, 2].unpack('v')[0]
610
- #print "[%#08x] " % offset
670
+ logger.debug "[%#08x] " % offset
611
671
  @packages = {}
612
672
  case type
613
- when 0x0001 # RES_STRING_POOL_TYPE
614
- @string_pool = ResStringPool.new(@data, offset)
615
- offset += @string_pool.size
616
- #puts "RES_STRING_POOL_TYPE %#x, %#x" % [@string_pool.size, offset]
617
- when 0x0002 # RES_TABLE_TYPE
618
- #puts "RES_TABLE_TYPE"
673
+ when TYPE_TABLE
619
674
  @res_table = ResTableHeader.new(@data, offset)
620
675
  offset += @res_table.header_size
621
- when 0x0200 # RES_TABLE_PACKAGE_TYPE
622
- #puts "RES_TABLE_PACKAGE_TYPE"
676
+ logger.debug "RES_TABLE_TYPE"
677
+ when TYPE_STRING_POOL_TYPE
678
+ @string_pool = ResStringPool.new(@data, offset)
679
+ offset += @string_pool.size
680
+ logger.debug("RES_STRING_POOL_TYPE %#x, %#x" % [@string_pool.size, offset])
681
+ when TYPE_PACKAGE
623
682
  pkg = ResTablePackage.new(@data, offset)
624
683
  pkg.global_string_pool = @string_pool
625
684
  offset += pkg.size
626
685
  @packages[pkg.name] = pkg
686
+ logger.debug "RES_TABLE_PACKAGE_TYPE"
627
687
  else
628
- raise "chunk type error: type:%#04x" % type
688
+ raise UnknownChunkType, "chunk type error: type:%#04x" % type
629
689
  end
630
690
  end
631
691
  end
692
+
693
+ def logger
694
+ Android.logger
695
+ end
696
+
697
+ TYPE_STRING_POOL_TYPE = 0x0001 # RES_STRING_POOL_TYPE
698
+ TYPE_TABLE = 0x0002 # RES_TABLE_TYPE
699
+ TYPE_PACKAGE = 0x0200 # RES_TABLE_PACKAGE_TYPE
700
+ TYPE_TYPE = 0x0201 # RES_TABLE_TYPE_TYPE
701
+ TYPE_SPEC_TYPE = 0x0202 # RES_TABLE_TYPE_SPEC_TYPE
702
+ TYPE_LIBRARY = 0x0203 # RES_TABLE_LIBRARY_TYPE
632
703
  end
633
704
  end
data/lib/android/utils.rb CHANGED
@@ -8,9 +8,9 @@ module Android
8
8
  # @return [Boolean]
9
9
  def self.apk?(path)
10
10
  begin
11
- apk = Apk.new(path)
11
+ Apk.new(path)
12
12
  return true
13
- rescue => e
13
+ rescue
14
14
  return false
15
15
  end
16
16
  end
@@ -20,7 +20,7 @@ module Android
20
20
  # @return [Boolean]
21
21
  def self.elf?(data)
22
22
  data[0..3] == "\x7f\x45\x4c\x46"
23
- rescue => e
23
+ rescue
24
24
  false
25
25
  end
26
26
 
@@ -29,7 +29,7 @@ module Android
29
29
  # @return [Boolean]
30
30
  def self.cert?(data)
31
31
  data[0..1] == "\x30\x82"
32
- rescue => e
32
+ rescue
33
33
  false
34
34
  end
35
35
 
@@ -38,7 +38,7 @@ module Android
38
38
  # @return [Boolean]
39
39
  def self.dex?(data)
40
40
  data[0..7] == "\x64\x65\x78\x0a\x30\x33\x35\x00" # "dex\n035\0"
41
- rescue => e
41
+ rescue
42
42
  false
43
43
  end
44
44
 
@@ -48,9 +48,8 @@ module Android
48
48
  def self.valid_dex?(data)
49
49
  Android::Dex.new(data)
50
50
  true
51
- rescue => e
51
+ rescue
52
52
  false
53
53
  end
54
54
  end
55
55
  end
56
-
@@ -1,5 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require_relative 'android/null_logger'
4
+ require_relative 'android/configuration'
3
5
  require_relative 'android/apk'
4
6
  require_relative 'android/manifest'
5
7
  require_relative 'android/axml_parser'
@@ -8,3 +10,7 @@ require_relative 'android/dex'
8
10
  require_relative 'android/resource'
9
11
  require_relative 'android/utils'
10
12
  require_relative 'android/layout'
13
+
14
+ module Android
15
+ extend Configuration
16
+ end
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.5.1
4
+ version: 2.7.0.beta1
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-09 00:00:00.000000000 Z
12
+ date: 2024-06-27 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
@@ -147,9 +148,11 @@ files:
147
148
  - README.md
148
149
  - Rakefile
149
150
  - android_parser.gemspec
151
+ - docs/resources-arsc-reference.png
150
152
  - lib/android/apk.rb
151
153
  - lib/android/axml_parser.rb
152
154
  - lib/android/axml_writer.rb
155
+ - lib/android/configuration.rb
153
156
  - lib/android/dex.rb
154
157
  - lib/android/dex/access_flag.rb
155
158
  - lib/android/dex/dex_object.rb
@@ -157,10 +160,10 @@ files:
157
160
  - lib/android/dex/utils.rb
158
161
  - lib/android/layout.rb
159
162
  - lib/android/manifest.rb
163
+ - lib/android/null_logger.rb
160
164
  - lib/android/resource.rb
161
165
  - lib/android/utils.rb
162
166
  - lib/android_parser.rb
163
- - lib/ruby_apk.rb
164
167
  homepage: https://github.com/icyleaf/android_parser
165
168
  licenses:
166
169
  - MIT
@@ -173,14 +176,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
173
176
  requirements:
174
177
  - - ">="
175
178
  - !ruby/object:Gem::Version
176
- version: '2.5'
179
+ version: '3.1'
177
180
  required_rubygems_version: !ruby/object:Gem::Requirement
178
181
  requirements:
179
182
  - - ">="
180
183
  - !ruby/object:Gem::Version
181
184
  version: '0'
182
185
  requirements: []
183
- rubygems_version: 3.4.1
186
+ rubygems_version: 3.5.9
184
187
  signing_key:
185
188
  specification_version: 4
186
189
  summary: Static analysis tool for android apk since 2021
data/lib/ruby_apk.rb DELETED
@@ -1,4 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- # Compatible with older versions
4
- require File.dirname(__FILE__) + '/android_parser'