ruby_android_apk 0.7.7.1

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 ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: c8ad476b82feac524f3903758b93a3b7195de4c4
4
+ data.tar.gz: 72fa394d1656548782acea431219ab880e1c9233
5
+ SHA512:
6
+ metadata.gz: 5c55b568704956d934a9ced5bc84d9e731f95dcd8ac98ab24e2172b2cf8365e132cdf54782fc293969ef664f29968819234d492f85013392687332fe4628cee2
7
+ data.tar.gz: 95ca9385bdbfb6b885cc170402837e368f31bdeb1b0e4cdab06c3f7742af7056dccc84482c62678b27599a9c8755c3ce26e2e5ac0752171d9e8cf8e4fee00e34
data/.travis.yml ADDED
@@ -0,0 +1,4 @@
1
+ language: ruby
2
+ rvm:
3
+ - 1.9.3
4
+ - 2.0.0
data/CHANGELOG.md ADDED
@@ -0,0 +1,45 @@
1
+ # ChangeLog
2
+ ## 0.7.0
3
+ * implement Apk#signs, Apk#certificates and Manifest#version_name (#14, #15)
4
+ * bugfix
5
+
6
+ ## 0.6.0
7
+ * implement Android::Apk#layouts(#10), Android::Apk#icon(#11), Android::Apk#label(#12),
8
+ * fix bug (#13)
9
+
10
+ ## 0.5.1
11
+ * [#8] add Android::Manifest#label
12
+ * [#7] fix wrong boolean value in manifest parser
13
+ * [#6] add accessor Android::Manifest#doc
14
+
15
+ ## 0.5.0
16
+ * [issue #1] implement Android::Resource#find, #res_readable_id, #res_hex_id methods
17
+
18
+ ## 0.4.2
19
+ * fix bugs(#2, #3)
20
+ * divide change log from readme
21
+
22
+ ## 0.4.1
23
+ * fix typo
24
+ * add document
25
+
26
+ ## 0.4.0
27
+ * add resource parser
28
+ * enhance dex parser
29
+
30
+ ## 0.3.0
31
+ * add and change name space
32
+ * add Android::Utils module and some util methods
33
+ * add Apk#entry, Apk#each_entry, and Apk#time methods,
34
+
35
+ ## 0.2.0
36
+ * update documents
37
+ * add Apk::Dex#each_strings, Apk::Dex#each_class_names
38
+
39
+ ## 0.1.2
40
+ * fix bug(improve android binary xml parser)
41
+
42
+ ## 0.1.1
43
+ * fix bug(failed to initialize Apk::Manifest::Meta class)
44
+ * replace iconv to String#encode(for ruby1.9)
45
+
data/Gemfile ADDED
@@ -0,0 +1,16 @@
1
+ source "http://rubygems.org"
2
+ # Add dependencies required to use your gem here.
3
+ # Example:
4
+ # gem "activesupport", ">= 2.3.5"
5
+ gem "rubyzip", ">= 1.1.6"
6
+
7
+ # Add dependencies to develop your gem here.
8
+ # Include everything needed to run rake, tests, features, etc.
9
+ group :development do
10
+ gem "rspec", "~> 2.11.0"
11
+ gem "bundler", ">= 1.3.0"
12
+ gem "jeweler", "~> 1.8.7"
13
+ gem "yard", require: false
14
+ gem "redcarpet"
15
+ gem "simplecov", require: false
16
+ end
data/Gemfile.lock ADDED
@@ -0,0 +1,73 @@
1
+ GEM
2
+ remote: http://rubygems.org/
3
+ specs:
4
+ addressable (2.3.5)
5
+ builder (3.2.2)
6
+ diff-lcs (1.1.3)
7
+ faraday (0.8.8)
8
+ multipart-post (~> 1.2.0)
9
+ git (1.2.6)
10
+ github_api (0.10.1)
11
+ addressable
12
+ faraday (~> 0.8.1)
13
+ hashie (>= 1.2)
14
+ multi_json (~> 1.4)
15
+ nokogiri (~> 1.5.2)
16
+ oauth2
17
+ hashie (2.0.5)
18
+ highline (1.6.19)
19
+ httpauth (0.2.0)
20
+ jeweler (1.8.7)
21
+ builder
22
+ bundler (~> 1.0)
23
+ git (>= 1.2.5)
24
+ github_api (= 0.10.1)
25
+ highline (>= 1.6.15)
26
+ nokogiri (= 1.5.10)
27
+ rake
28
+ rdoc
29
+ json (1.8.0)
30
+ jwt (0.1.8)
31
+ multi_json (>= 1.5)
32
+ multi_json (1.8.0)
33
+ multi_xml (0.5.5)
34
+ multipart-post (1.2.0)
35
+ nokogiri (1.5.10)
36
+ oauth2 (0.9.2)
37
+ faraday (~> 0.8)
38
+ httpauth (~> 0.2)
39
+ jwt (~> 0.1.4)
40
+ multi_json (~> 1.0)
41
+ multi_xml (~> 0.5)
42
+ rack (~> 1.2)
43
+ rack (1.5.2)
44
+ rake (10.1.0)
45
+ rdoc (4.0.1)
46
+ json (~> 1.4)
47
+ redcarpet (3.0.0)
48
+ rspec (2.11.0)
49
+ rspec-core (~> 2.11.0)
50
+ rspec-expectations (~> 2.11.0)
51
+ rspec-mocks (~> 2.11.0)
52
+ rspec-core (2.11.1)
53
+ rspec-expectations (2.11.3)
54
+ diff-lcs (~> 1.1.3)
55
+ rspec-mocks (2.11.3)
56
+ rubyzip (1.1.6)
57
+ simplecov (0.7.1)
58
+ multi_json (~> 1.0)
59
+ simplecov-html (~> 0.7.1)
60
+ simplecov-html (0.7.1)
61
+ yard (0.8.7.2)
62
+
63
+ PLATFORMS
64
+ ruby
65
+
66
+ DEPENDENCIES
67
+ bundler (>= 1.3.0)
68
+ jeweler (~> 1.8.7)
69
+ redcarpet
70
+ rspec (~> 2.11.0)
71
+ rubyzip (>= 1.1.6)
72
+ simplecov
73
+ yard
data/LICENSE.txt ADDED
@@ -0,0 +1,22 @@
1
+ (The MIT License)
2
+
3
+ Copyright (c) 2012 Securebrain
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,157 @@
1
+ # ruby_apk
2
+ Android Apk static analysis library for Ruby.
3
+
4
+ [![Gem Version](https://badge.fury.io/rb/ruby_apk.png)](http://badge.fury.io/rb/ruby_apk)
5
+ [![Build Status](https://travis-ci.org/SecureBrain/ruby_apk.png)](https://travis-ci.org/SecureBrain/ruby_apk)
6
+
7
+ ## Requirements
8
+ - ruby(>=1.9.x)
9
+
10
+ ## Install
11
+ $ gem install ruby_apk
12
+
13
+ ## Usage
14
+ ### Initialize
15
+ ```ruby
16
+ require 'ruby_apk'
17
+ apk = Android::Apk.new('sample.apk') # set apk file path
18
+ ```
19
+
20
+ ### Apk
21
+ #### Listing files in Apk
22
+ ```ruby
23
+ # listing files in apk
24
+ apk = Android::Apk.new('sample.apk')
25
+ apk.each_file do |name, data|
26
+ puts "#{name}: #{data.size}bytes" # puts file name and data size
27
+ end
28
+ ```
29
+
30
+ #### Find files in Apk
31
+ ```ruby
32
+ apk = Android::Apk.new('sample.apk')
33
+ elf_files = apk.find{|name, data| data[0..3] == [0x7f, 0x45, 0x4c, 0x46] } # ELF magic number
34
+ ```
35
+
36
+ #### Extract icon data in Apk (since 0.6.0)
37
+ ```ruby
38
+ apk = Android::Apk.new('sample.apk')
39
+ icons = apk.icon # { "res/drawable-hdpi/ic_launcher.png" => "\x89PNG\x0D\x0A...", ... }
40
+ icons.each do |name, data|
41
+ File.open(File.basename(name), 'wb') {|f| f.write data } # save to file.
42
+ end
43
+ ```
44
+
45
+ #### Extract signature and certificate information from Apk (since v0.7.0)
46
+ ```ruby
47
+ apk = Android::Apk.new('sample.apk')
48
+ signs = apk.signs # retrun Hash(key: signature file path, value: OpenSSL::PKCS7)
49
+ signs.each do |path, sign|
50
+ puts path # => "MATA-INF/CERT.RSA" or ...
51
+ puts sign # => "-----BEGIN PKCS7-----\n..." PKCS7 object
52
+ end
53
+
54
+ certs = apk.certificates # retrun Hash(key: signature file path, value: OpenSSL::X509::Certificate)
55
+ certs.each do |path, cert|
56
+ puts path # => "MATA-INF/CERT.RSA" or ...
57
+ puts cert # => "-----BEGIN CERTIFICATE-----\n..." # X509::Certificate object
58
+ end
59
+ ```
60
+ Note: Most apks have only one signature and cerficate.
61
+
62
+ ### Manifest
63
+ #### Get readable xml
64
+ ```ruby
65
+ apk = Android::Apk.new('sample.apk')
66
+ manifest = apk.manifest
67
+ puts manifest.to_xml
68
+ ```
69
+
70
+ #### Listing components and permissions
71
+ ```ruby
72
+ apk = Android::Apk.new('sample.apk')
73
+ manifest = apk.manifest
74
+ # listing components
75
+ manifest.components.each do |c| # 'c' is Android::Manifest::Component object
76
+ puts "#{c.type}: #{c.name}"
77
+ c.intent_filters.each do |filter|
78
+ puts "\t#{filter.type}"
79
+ end
80
+ end
81
+
82
+ # listing use-permission tag
83
+ manifest.use_permissions.each do |permission|
84
+ puts permission
85
+ end
86
+ ```
87
+
88
+ #### Extract application label string
89
+ ```ruby
90
+ apk = Android::Apk.new('sample.apk')
91
+ puts apk.manifest.label
92
+ ```
93
+
94
+ ### Resource
95
+ #### Extract resource strings from apk
96
+ ```ruby
97
+ apk = Android::Apk.new('sample.apk')
98
+ rsc = apk.resource
99
+ rsc.strings.each do |str|
100
+ puts str
101
+ end
102
+ ```
103
+
104
+ #### Parse resource file directly
105
+ ```ruby
106
+ rsc_data = File.open('resources.arsc', 'rb').read{|f| f.read }
107
+ rsc = Android::Resource.new(rsc_data)
108
+ ```
109
+
110
+ ### Resolve resource id
111
+ This feature supports only srting resources for now.
112
+
113
+ ```ruby
114
+ apk = Android::Apk.new('sample.apk')
115
+ rsc = apk.resource
116
+
117
+ # assigns readable resource id
118
+ puts rsc.find('@string/app_name') # => 'application name'
119
+
120
+ # assigns hex resource id
121
+ puts rsc.find('@0x7f040000') # => 'application name'
122
+
123
+ # you can set lang attribute.
124
+ puts rsc.find('@0x7f040000', :lang => 'ja')
125
+ ```
126
+
127
+
128
+ ### Dex
129
+ #### Extract dex information
130
+ ```ruby
131
+ apk = Android::Apk.new('sample.apk')
132
+ dex = apk.dex
133
+ # listing string table in dex
134
+ dex.strings.each do |str|
135
+ puts str
136
+ end
137
+
138
+ # listing all class names
139
+ dex.classes.each do |cls| # cls is Android::Dex::ClassInfo
140
+ puts "class: #{cls.name}"
141
+ cls.virtual_methods.each do |m| # Android::Dex::MethodInfo
142
+ puts "\t#{m.definition}" # puts method definition
143
+ end
144
+ end
145
+ ```
146
+
147
+ #### Parse dex file directly
148
+ ```ruby
149
+ dex_data = File.open('classes.dex','rb').read{|f| f.read }
150
+ dex = Android::Dex.new(dex_data)
151
+ ```
152
+
153
+
154
+ ## Copyright
155
+
156
+ Copyright (c) 2012 SecureBrain. See LICENSE.txt for further details.
157
+
data/Rakefile ADDED
@@ -0,0 +1,43 @@
1
+ # encoding: utf-8
2
+
3
+ require 'rubygems'
4
+ require 'bundler'
5
+ begin
6
+ Bundler.setup(:default, :development)
7
+ rescue Bundler::BundlerError => e
8
+ $stderr.puts e.message
9
+ $stderr.puts "Run `bundle install` to install missing gems"
10
+ exit e.status_code
11
+ end
12
+ require 'rake'
13
+
14
+ require 'jeweler'
15
+ Jeweler::Tasks.new do |gem|
16
+ # gem is a Gem::Specification... see http://docs.rubygems.org/read/chapter/20 for more options
17
+ gem.name = "ruby_android"
18
+ gem.homepage = "https://github.com/tajchert/ruby_apk"
19
+ gem.license = "MIT"
20
+ gem.summary = %Q{static analysis tool for android apk}
21
+ gem.description = %Q{static analysis tool for android apk}
22
+ gem.email = "thetajchert@gmail.com"
23
+ gem.authors = ["Michal Tajchert","SecureBrain"]
24
+ # dependencies defined in Gemfile
25
+ end
26
+ Jeweler::RubygemsDotOrgTasks.new
27
+
28
+ require 'rspec/core'
29
+ require 'rspec/core/rake_task'
30
+ RSpec::Core::RakeTask.new(:spec) do |spec|
31
+ spec.pattern = FileList['spec/**/*_spec.rb']
32
+ end
33
+
34
+
35
+ task :default => :spec
36
+
37
+ require 'yard'
38
+ require 'yard/rake/yardoc_task'
39
+ YARD::Rake::YardocTask.new do |t|
40
+ t.files = ['lib/**/*.rb']
41
+ t.options = []
42
+ t.options << '--debug' << '--verbose' if $trace
43
+ end
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 0.7.3
@@ -0,0 +1,207 @@
1
+ require 'zip' # need rubyzip gem -> doc: http://rubyzip.sourceforge.net/
2
+ require 'digest/md5'
3
+ require 'digest/sha1'
4
+ require 'digest/sha2'
5
+ require 'openssl'
6
+
7
+ module Android
8
+ class NotApkFileError < StandardError; end
9
+ class NotFoundError < StandardError; end
10
+
11
+ # apk object class
12
+ class Apk
13
+
14
+ # @return [String] apk file path
15
+ attr_reader :path
16
+ # @return [Android::Manifest] manifest instance
17
+ # @return [nil] when parsing manifest is failed.
18
+ attr_reader :manifest
19
+ # @return [Android::Dex] dex instance
20
+ # @return [nil] when parsing dex is failed.
21
+ attr_reader :dex
22
+ # @return [String] binary data of apk
23
+ attr_reader :bindata
24
+ # @return [Resource] resouce data
25
+ # @return [nil] when parsing resource is failed.
26
+ attr_reader :resource
27
+
28
+ # AndroidManifest file name
29
+ MANIFEST = 'AndroidManifest.xml'
30
+ # dex file name
31
+ DEX = 'classes.dex'
32
+ # resource file name
33
+ RESOURCE = 'resources.arsc'
34
+
35
+ # create new apk object
36
+ # @param [String] filepath apk file path
37
+ # @raise [Android::NotFoundError] path file does'nt exist
38
+ # @raise [Android::NotApkFileError] path file is not Apk file.
39
+ def initialize(filepath)
40
+ @path = filepath
41
+ raise NotFoundError, "'#{filepath}'" unless File.exist? @path
42
+ begin
43
+ @zip = Zip::File.open(@path)
44
+ rescue Zip::Error => e
45
+ raise NotApkFileError, e.message
46
+ end
47
+
48
+ @bindata = File.open(@path, 'rb') {|f| f.read }
49
+ @bindata.force_encoding(Encoding::ASCII_8BIT)
50
+ raise NotApkFileError, "manifest file is not found." if @zip.find_entry(MANIFEST).nil?
51
+ begin
52
+ @resource = Android::Resource.new(self.file(RESOURCE))
53
+ rescue => e
54
+ $stderr.puts "failed to parse resource:#{e}"
55
+ #$stderr.puts e.backtrace
56
+ end
57
+ begin
58
+ @manifest = Android::Manifest.new(self.file(MANIFEST), @resource)
59
+ rescue => e
60
+ $stderr.puts "failed to parse manifest:#{e}"
61
+ #$stderr.puts e.backtrace
62
+ end
63
+ begin
64
+ @dex = Android::Dex.new(self.file(DEX))
65
+ rescue => e
66
+ $stderr.puts "failed to parse dex:#{e}"
67
+ #$stderr.puts e.backtrace
68
+ end
69
+ end
70
+
71
+ # return apk file size
72
+ # @return [Integer] bytes
73
+ def size
74
+ @bindata.size
75
+ end
76
+
77
+ # return hex digest string of apk file
78
+ # @param [Symbol] type hash digest type(:sha1, sha256, :md5)
79
+ # @return [String] hex digest string
80
+ # @raise [ArgumentError] type is knknown type
81
+ def digest(type = :sha1)
82
+ case type
83
+ when :sha1
84
+ Digest::SHA1.hexdigest(@bindata)
85
+ when :sha256
86
+ Digest::SHA256.hexdigest(@bindata)
87
+ when :md5
88
+ Digest::MD5.hexdigest(@bindata)
89
+ else
90
+ raise ArgumentError
91
+ end
92
+ end
93
+
94
+ # returns date of AndroidManifest.xml as Apk date
95
+ # @return [Time]
96
+ def time
97
+ entry(MANIFEST).time
98
+ end
99
+
100
+ # @yield [name, data]
101
+ # @yieldparam [String] name file name in apk
102
+ # @yieldparam [String] data file data in apk
103
+ def each_file
104
+ @zip.each do |entry|
105
+ next unless entry.file?
106
+ yield entry.name, @zip.read(entry)
107
+ end
108
+ end
109
+
110
+ # find and return binary data with name
111
+ # @param [String] name file name in apk(fullpath)
112
+ # @return [String] binary data
113
+ # @raise [NotFoundError] when 'name' doesn't exist in the apk
114
+ def file(name) # get data by entry name(path)
115
+ @zip.read(entry(name))
116
+ end
117
+
118
+ # @yield [entry]
119
+ # @yieldparam [Zip::Entry] entry zip entry
120
+ def each_entry
121
+ @zip.each do |entry|
122
+ next unless entry.file?
123
+ yield entry
124
+ end
125
+ end
126
+
127
+ # find and return zip entry with name
128
+ # @param [String] name file name in apk(fullpath)
129
+ # @return [Zip::ZipEntry] zip entry object
130
+ # @raise [NotFoundError] when 'name' doesn't exist in the apk
131
+ def entry(name)
132
+ entry = @zip.find_entry(name)
133
+ raise NotFoundError, "'#{name}'" if entry.nil?
134
+ return entry
135
+ end
136
+
137
+ # find files which is matched with block condition
138
+ # @yield [name, data] find condition
139
+ # @yieldparam [String] name file name in apk
140
+ # @yieldparam [String] data file data in apk
141
+ # @yieldreturn [Array] Array of matched entry name
142
+ # @return [Array] Array of matched entry name
143
+ # @example
144
+ # apk = Apk.new(path)
145
+ # elf_files = apk.find { |name, data| data[0..3] == [0x7f, 0x45, 0x4c, 0x46] } # ELF magic number
146
+ def find(&block)
147
+ found = []
148
+ self.each_file do |name, data|
149
+ ret = block.call(name, data)
150
+ found << name if ret
151
+ end
152
+ found
153
+ end
154
+
155
+ # extract icon data from AndroidManifest and resource.
156
+ # @return [Hash{ String => String }] hash key is icon filename. value is image data
157
+ # @raise [NotFoundError]
158
+ # @since 0.6.0
159
+ def icon
160
+ icon_id = @manifest.doc.elements['/manifest/application'].attributes['icon']
161
+ if /^@(\w+\/\w+)|(0x[0-9a-fA-F]{8})$/ =~ icon_id
162
+ drawables = @resource.find(icon_id)
163
+ Hash[drawables.map {|name| [name, file(name)] }]
164
+ else
165
+ { icon_id => file(icon_id) } # ugh!: not tested!!
166
+ end
167
+ end
168
+
169
+ # get application label from AndroidManifest and resources.
170
+ # @param [String] lang language code like 'ja', 'cn', ...
171
+ # @return [String] application label string
172
+ # @return [nil] when label is not found
173
+ # @deprecated move to {Android::Manifest#label}
174
+ # @since 0.6.0
175
+ def label(lang=nil)
176
+ @manifest.label
177
+ end
178
+
179
+ # get screen layout xml datas
180
+ # @return [Hash{ String => Android::Layout }] key: laytout file path, value: layout object
181
+ # @since 0.6.0
182
+ def layouts
183
+ @layouts ||= Layout.collect_layouts(self) # lazy parse
184
+ end
185
+
186
+ # apk's signature information
187
+ # @return [Hash{ String => OpenSSL::PKCS7 } ] key: sign file path, value: signature
188
+ # @since 0.7.0
189
+ def signs
190
+ signs = {}
191
+ self.each_file do |path, data|
192
+ # find META-INF/xxx.{RSA|DSA}
193
+ next unless path =~ /^META-INF\// && data.unpack("CC") == [0x30, 0x82]
194
+ signs[path] = OpenSSL::PKCS7.new(data)
195
+ end
196
+ signs
197
+ end
198
+
199
+ # certificate info which is used for signing
200
+ # @return [Hash{String => OpenSSL::X509::Certificate }] key: sign file path, value: first certficate in the sign file
201
+ # @since 0.7.0
202
+ def certificates
203
+ return Hash[self.signs.map{|path, sign| [path, sign.certificates.first] }]
204
+ end
205
+ end
206
+ end
207
+