android_parser 2.5.1 → 2.7.0.beta1

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  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'