ruby_android_apk 0.7.7.1

Sign up to get free protection for your applications and to get access to all the features.
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
+