Class: Android::Apk

Inherits:
Object
  • Object
show all
Defined in:
lib/android/apk.rb

Overview

apk object class

Constant Summary collapse

MANIFEST =

AndroidManifest file name

'AndroidManifest.xml'
DEX =

dex file name

'classes.dex'
RESOURCE =

resource file name

'resources.arsc'

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(filepath) ⇒ Apk

create new apk object

Parameters:

  • filepath (String)

    apk file path

Raises:



40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
# File 'lib/android/apk.rb', line 40

def initialize(filepath)
  Zip.warn_invalid_date = false

  @path = filepath
  raise NotFoundError, "'#{filepath}'" unless File.exist? @path

  @bindata = File.open(@path, 'rb') {|f| f.read }
  @bindata.force_encoding(Encoding::ASCII_8BIT)
  raise NotApkFileError, 'manifest file is not found.' if zip.find_entry(MANIFEST).nil?

  begin
    @resource = Android::Resource.new(self.file(RESOURCE))
  rescue => e
    logger.error "failed to parse resource: #{e} with backtrace"
    logger.error e.backtrace
  end

  begin
    @manifest = Android::Manifest.new(self.file(MANIFEST), @resource)
  rescue => e
    logger.error "failed to parse manifest:#{e} with backtrace"
    logger.error e.backtrace
  end

  begin
    @dex = Android::Dex.new(self.file(DEX))
  rescue => e
    logger.error "failed to parse dex:#{e} with backtrace"
    logger.error e.backtrace
  end
end

Instance Attribute Details

#bindataString (readonly)

Returns binary data of apk.

Returns:

  • (String)

    binary data of apk



24
25
26
# File 'lib/android/apk.rb', line 24

def bindata
  @bindata
end

#dexAndroid::Dex? (readonly)

Returns:

  • (Android::Dex)

    dex instance

  • (nil)

    when parsing dex is failed.



22
23
24
# File 'lib/android/apk.rb', line 22

def dex
  @dex
end

#manifestAndroid::Manifest? (readonly)

Returns:

  • (Android::Manifest)

    manifest instance

  • (nil)

    when parsing manifest is failed.



19
20
21
# File 'lib/android/apk.rb', line 19

def manifest
  @manifest
end

#pathString (readonly)

Returns apk file path.

Returns:

  • (String)

    apk file path



16
17
18
# File 'lib/android/apk.rb', line 16

def path
  @path
end

#resourceResource? (readonly)

Returns:

  • (Resource)

    resouce data

  • (nil)

    when parsing resource is failed.



27
28
29
# File 'lib/android/apk.rb', line 27

def resource
  @resource
end

Instance Method Details

#archsArray<String> Also known as: architectures

Return all architectures (all most for universal apk)

Returns:

  • (Array<String>)

Since:

  • 2.7.0



227
228
229
230
231
232
# File 'lib/android/apk.rb', line 227

def archs
  @archs ||= zip.glob('lib/**/*').each_with_object([]) do |entry, obj|
    arch = entry.name.split('/')[1]
    obj << arch unless obj.include?(arch)
  end
end

#certificatesHash{String => OpenSSL::X509::Certificate }

v1 certificate info which is used for signing

Returns:

  • (Hash{String => OpenSSL::X509::Certificate })

    key: sign file path, value: first certficate in the sign file

Since:

  • 0.7.0



220
221
222
# File 'lib/android/apk.rb', line 220

def certificates
  @certificates ||= Hash[self.signs.map{|path, sign| [path, sign.certificates.first] }]
end

#digest(type = :sha1) ⇒ String

return hex digest string of apk file

Parameters:

  • type (Symbol) (defaults to: :sha1)

    hash digest type(:sha1, sha256, :md5)

Returns:

  • (String)

    hex digest string

Raises:

  • (ArgumentError)

    type is knknown type



82
83
84
85
86
87
88
89
90
91
92
93
# File 'lib/android/apk.rb', line 82

def digest(type = :sha1)
  case type
  when :sha1
    Digest::SHA1.hexdigest(@bindata)
  when :sha256
    Digest::SHA256.hexdigest(@bindata)
  when :md5
    Digest::MD5.hexdigest(@bindata)
  else
    raise ArgumentError
  end
end

#each_entry {|entry| ... } ⇒ Object

Yields:

Yield Parameters:

  • entry (Zip::Entry)

    zip entry



121
122
123
124
125
126
# File 'lib/android/apk.rb', line 121

def each_entry
  zip.each do |entry|
    next unless entry.file?
    yield entry
  end
end

#each_file {|name, data| ... } ⇒ Object

Yields:

  • (name, data)

Yield Parameters:

  • name (String)

    file name in apk

  • data (String)

    file data in apk



104
105
106
107
108
109
# File 'lib/android/apk.rb', line 104

def each_file
  zip.each do |entry|
    next unless entry.file?
    yield entry.name, zip.read(entry)
  end
end

#entry(name) ⇒ Zip::Entry

find and return zip entry with name

Parameters:

  • name (String)

    file name in apk(fullpath)

Returns:

  • (Zip::Entry)

    zip entry object

Raises:



132
133
134
135
136
# File 'lib/android/apk.rb', line 132

def entry(name)
  entry = zip.find_entry(name)
  raise NotFoundError, "'#{name}'" if entry.nil?
  return entry
end

#file(name) ⇒ String

find and return binary data with name

Parameters:

  • name (String)

    file name in apk(fullpath)

Returns:

  • (String)

    binary data

Raises:



115
116
117
# File 'lib/android/apk.rb', line 115

def file(name) # get data by entry name(path)
  zip.read(entry(name))
end

#find {|name, data| ... } ⇒ Array

find files which is matched with block condition

Examples:

apk = Apk.new(path)
elf_files = apk.find  { |name, data|  data[0..3] == [0x7f, 0x45, 0x4c, 0x46] } # ELF magic number

Yields:

  • (name, data)

    find condition

Yield Parameters:

  • name (String)

    file name in apk

  • data (String)

    file data in apk

Yield Returns:

  • (Array)

    Array of matched entry name

Returns:

  • (Array)

    Array of matched entry name



147
148
149
150
151
152
153
154
# File 'lib/android/apk.rb', line 147

def find(&block)
  found = []
  self.each_file do |name, data|
    ret = block.call(name, data)
    found << name if ret
  end
  found
end

#iconHash{ String => String }

extract application icon data from AndroidManifest and resource.

Returns:

  • (Hash{ String => String })

    hash key is icon filename. value is image data

Raises:

Since:

  • 0.6.0



160
161
162
163
# File 'lib/android/apk.rb', line 160

def icon
  icon_id = @manifest.doc.elements['/manifest/application'].attributes['icon']
  icon_by_id(icon_id)
end

#icon_by_id(icon_id) ⇒ Hash{ String => String }

extract icon data from AndroidManifest and resource by a given icon id.

Parameters:

  • icon_id (String)

    to be searched in the resource.

Returns:

  • (Hash{ String => String })

    hash key is icon filename. value is image data

Raises:

Since:

  • 0.6.0



170
171
172
173
174
175
176
177
178
179
180
181
# File 'lib/android/apk.rb', line 170

def icon_by_id(icon_id)
  if /^@(\w+\/\w+)|(0x[0-9a-fA-F]{8})$/ =~ icon_id
    drawables = @resource.find(icon_id)
    Hash[drawables.map {|name| [name, file(name)] }]
  else
    begin
      { icon_id => file(icon_id) } # ugh!: not tested!!
    rescue NotFoundError
      {}
    end
  end
end

#kotlin?Boolean

detect if use kotlin language (may be third-party sdk or not)

Returns:

  • (Boolean)

Since:

  • 2.6.0



245
246
247
# File 'lib/android/apk.rb', line 245

def kotlin?
  @kotlin ||= kotlin_file? || kotlin_classes?
end

#label(lang = nil) ⇒ String?

Deprecated.

move to Manifest#label

get application label from AndroidManifest and resources.

Parameters:

  • lang (String) (defaults to: nil)

    language code like ‘ja’, ‘cn’, …

Returns:

  • (String)

    application label string

  • (nil)

    when label is not found

Since:

  • 0.6.0



189
190
191
# File 'lib/android/apk.rb', line 189

def label(lang = nil)
  @manifest.label
end

#layoutsHash{ String => Android::Layout }

get screen layout xml datas

Returns:

  • (Hash{ String => Android::Layout })

    key: laytout file path, value: layout object

Since:

  • 0.6.0



196
197
198
# File 'lib/android/apk.rb', line 196

def layouts
  @layouts ||= Layout.collect_layouts(self) # lazy parse
end

#signsHash{ String => OpenSSL::PKCS7 } Also known as: signatures

apk’s v1 signature information

Returns:

  • (Hash{ String => OpenSSL::PKCS7 })

    key: sign file path, value: signature

Since:

  • 0.7.0



203
204
205
206
207
208
209
210
211
212
213
214
# File 'lib/android/apk.rb', line 203

def signs
  @signs ||= lambda {
    signs = {}
    self.each_file do |path, data|
      # find META-INF/xxx.{RSA|DSA|EC}
      next unless path =~ /^META-INF\// && data.unpack("CC") == [0x30, 0x82]

      signs[path] = OpenSSL::PKCS7.new(data)
    end
    signs
  }.call
end

#sizeInteger

return apk file size

Returns:

  • (Integer)

    bytes



74
75
76
# File 'lib/android/apk.rb', line 74

def size
  @bindata.size
end

#timeTime

returns date of AndroidManifest.xml as Apk date

Returns:

  • (Time)


97
98
99
# File 'lib/android/apk.rb', line 97

def time
  entry(MANIFEST).time
end

#universal?Boolean

detect if contains multi-platforms (native machine code)

Returns:

  • (Boolean)

Since:

  • 2.7.0



238
239
240
# File 'lib/android/apk.rb', line 238

def universal?
  archs.size > 1
end