ruby_android 0.0.2 → 0.7.2
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 +4 -4
- data/.idea/.name +1 -0
- data/.idea/.rakeTasks +7 -0
- data/.idea/encodings.xml +5 -0
- data/.idea/misc.xml +5 -0
- data/.idea/modules.xml +9 -0
- data/.idea/ruby_apk.iml +51 -0
- data/.idea/scopes/scope_settings.xml +5 -0
- data/.idea/vcs.xml +7 -0
- data/.idea/workspace.xml +508 -0
- data/.travis.yml +4 -0
- data/CHANGELOG.md +51 -0
- data/Gemfile +3 -3
- data/Gemfile.lock +73 -0
- data/LICENSE.txt +2 -2
- data/Rakefile +42 -1
- data/VERSION +1 -0
- data/lib/android/apk.rb +207 -0
- data/lib/android/axml_parser.rb +173 -0
- data/lib/android/dex/access_flag.rb +74 -0
- data/lib/android/dex/dex_object.rb +475 -0
- data/lib/android/dex/info.rb +151 -0
- data/lib/android/dex/utils.rb +45 -0
- data/lib/android/dex.rb +92 -0
- data/lib/android/layout.rb +44 -0
- data/lib/android/manifest.rb +249 -0
- data/lib/android/resource.rb +529 -0
- data/lib/android/utils.rb +55 -0
- data/lib/ruby_apk.rb +7 -0
- data/ruby_android.gemspec +103 -17
- data/spec/apk_spec.rb +301 -0
- data/spec/axml_parser_spec.rb +67 -0
- data/spec/data/sample.apk +0 -0
- data/spec/data/sample_AndroidManifest.xml +0 -0
- data/spec/data/sample_classes.dex +0 -0
- data/spec/data/sample_resources.arsc +0 -0
- data/spec/data/sample_resources_utf16.arsc +0 -0
- data/spec/data/str_resources.arsc +0 -0
- data/spec/dex/access_flag_spec.rb +42 -0
- data/spec/dex/dex_object_spec.rb +118 -0
- data/spec/dex/info_spec.rb +121 -0
- data/spec/dex/utils_spec.rb +56 -0
- data/spec/dex_spec.rb +59 -0
- data/spec/layout_spec.rb +27 -0
- data/spec/manifest_spec.rb +221 -0
- data/spec/resource_spec.rb +170 -0
- data/spec/ruby_apk_spec.rb +4 -0
- data/spec/spec_helper.rb +17 -0
- data/spec/utils_spec.rb +90 -0
- metadata +112 -27
- data/.gitignore +0 -14
- data/lib/ruby_android/version.rb +0 -3
- data/lib/ruby_android.rb +0 -7
data/.travis.yml
ADDED
data/CHANGELOG.md
ADDED
@@ -0,0 +1,51 @@
|
|
1
|
+
# ChangeLog
|
2
|
+
## 0.7.2
|
3
|
+
* updated rubyzip from wrong dependency '<1.0.0'
|
4
|
+
|
5
|
+
## 0.7.1
|
6
|
+
*
|
7
|
+
|
8
|
+
## 0.7.0
|
9
|
+
* implement Apk#signs, Apk#certificates and Manifest#version_name (#14, #15)
|
10
|
+
* bugfix
|
11
|
+
|
12
|
+
## 0.6.0
|
13
|
+
* implement Android::Apk#layouts(#10), Android::Apk#icon(#11), Android::Apk#label(#12),
|
14
|
+
* fix bug (#13)
|
15
|
+
|
16
|
+
## 0.5.1
|
17
|
+
* [#8] add Android::Manifest#label
|
18
|
+
* [#7] fix wrong boolean value in manifest parser
|
19
|
+
* [#6] add accessor Android::Manifest#doc
|
20
|
+
|
21
|
+
## 0.5.0
|
22
|
+
* [issue #1] implement Android::Resource#find, #res_readable_id, #res_hex_id methods
|
23
|
+
|
24
|
+
## 0.4.2
|
25
|
+
* fix bugs(#2, #3)
|
26
|
+
* divide change log from readme
|
27
|
+
|
28
|
+
## 0.4.1
|
29
|
+
* fix typo
|
30
|
+
* add document
|
31
|
+
|
32
|
+
## 0.4.0
|
33
|
+
* add resource parser
|
34
|
+
* enhance dex parser
|
35
|
+
|
36
|
+
## 0.3.0
|
37
|
+
* add and change name space
|
38
|
+
* add Android::Utils module and some util methods
|
39
|
+
* add Apk#entry, Apk#each_entry, and Apk#time methods,
|
40
|
+
|
41
|
+
## 0.2.0
|
42
|
+
* update documents
|
43
|
+
* add Apk::Dex#each_strings, Apk::Dex#each_class_names
|
44
|
+
|
45
|
+
## 0.1.2
|
46
|
+
* fix bug(improve android binary xml parser)
|
47
|
+
|
48
|
+
## 0.1.1
|
49
|
+
* fix bug(failed to initialize Apk::Manifest::Meta class)
|
50
|
+
* replace iconv to String#encode(for ruby1.9)
|
51
|
+
|
data/Gemfile
CHANGED
@@ -7,9 +7,9 @@ gem "rubyzip", ">= 1.1.6"
|
|
7
7
|
# Add dependencies to develop your gem here.
|
8
8
|
# Include everything needed to run rake, tests, features, etc.
|
9
9
|
group :development do
|
10
|
-
gem "rspec", "
|
11
|
-
gem "bundler", ">= 1.
|
12
|
-
gem "jeweler", "
|
10
|
+
gem "rspec", "~> 2.11.0"
|
11
|
+
gem "bundler", ">= 1.3.0"
|
12
|
+
gem "jeweler", "~> 1.8.7"
|
13
13
|
gem "yard", require: false
|
14
14
|
gem "redcarpet"
|
15
15
|
gem "simplecov", require: false
|
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
CHANGED
data/Rakefile
CHANGED
@@ -1,2 +1,43 @@
|
|
1
|
-
|
1
|
+
# encoding: utf-8
|
2
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.2
|
data/lib/android/apk.rb
ADDED
@@ -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
|
+
|
@@ -0,0 +1,173 @@
|
|
1
|
+
require 'rexml/document'
|
2
|
+
require 'stringio'
|
3
|
+
|
4
|
+
|
5
|
+
module Android
|
6
|
+
# binary AXML parser
|
7
|
+
# @see https://android.googlesource.com/platform/frameworks/base.git Android OS frameworks source
|
8
|
+
# @note
|
9
|
+
# refer to Android OS framework code:
|
10
|
+
#
|
11
|
+
# /frameworks/base/include/androidfw/ResourceTypes.h,
|
12
|
+
#
|
13
|
+
# /frameworks/base/libs/androidfw/ResourceTypes.cpp
|
14
|
+
class AXMLParser
|
15
|
+
def self.axml?(data)
|
16
|
+
(data[0..3] == "\x03\x00\x08\x00")
|
17
|
+
end
|
18
|
+
|
19
|
+
# axml parse error
|
20
|
+
class ReadError < StandardError; end
|
21
|
+
|
22
|
+
TAG_START_DOC = 0x00100100
|
23
|
+
TAG_END_DOC = 0x00100101
|
24
|
+
TAG_START = 0x00100102
|
25
|
+
TAG_END = 0x00100103
|
26
|
+
TAG_TEXT = 0x00100104
|
27
|
+
TAG_CDSECT = 0x00100105
|
28
|
+
TAG_ENTITY_REF= 0x00100106
|
29
|
+
|
30
|
+
VAL_TYPE_NULL =0
|
31
|
+
VAL_TYPE_REFERENCE =1
|
32
|
+
VAL_TYPE_ATTRIBUTE =2
|
33
|
+
VAL_TYPE_STRING =3
|
34
|
+
VAL_TYPE_FLOAT =4
|
35
|
+
VAL_TYPE_DIMENSION =5
|
36
|
+
VAL_TYPE_FRACTION =6
|
37
|
+
VAL_TYPE_INT_DEC =16
|
38
|
+
VAL_TYPE_INT_HEX =17
|
39
|
+
VAL_TYPE_INT_BOOLEAN =18
|
40
|
+
VAL_TYPE_INT_COLOR_ARGB8 =28
|
41
|
+
VAL_TYPE_INT_COLOR_RGB8 =29
|
42
|
+
VAL_TYPE_INT_COLOR_ARGB4 =30
|
43
|
+
VAL_TYPE_INT_COLOR_RGB4 =31
|
44
|
+
|
45
|
+
# @return [Array<String>] strings defined in axml
|
46
|
+
attr_reader :strings
|
47
|
+
|
48
|
+
# @param [String] axml binary xml data
|
49
|
+
def initialize(axml)
|
50
|
+
@io = StringIO.new(axml, "rb")
|
51
|
+
@strings = []
|
52
|
+
end
|
53
|
+
|
54
|
+
# parse binary xml
|
55
|
+
# @return [REXML::Document]
|
56
|
+
def parse
|
57
|
+
@doc = REXML::Document.new
|
58
|
+
@doc << REXML::XMLDecl.new
|
59
|
+
|
60
|
+
@num_str = word(4*4)
|
61
|
+
@xml_offset = word(3*4)
|
62
|
+
|
63
|
+
@parents = [@doc]
|
64
|
+
@ns = []
|
65
|
+
parse_strings
|
66
|
+
parse_tags
|
67
|
+
@doc
|
68
|
+
end
|
69
|
+
|
70
|
+
|
71
|
+
# read one word(4byte) as integer
|
72
|
+
# @param [Integer] offset offset from top position. current position is used if ofset is nil
|
73
|
+
# @return [Integer] little endian word value
|
74
|
+
def word(offset=nil)
|
75
|
+
@io.pos = offset unless offset.nil?
|
76
|
+
@io.read(4).unpack("V")[0]
|
77
|
+
end
|
78
|
+
|
79
|
+
# read 2byte as short integer
|
80
|
+
# @param [Integer] offset offset from top position. current position is used if ofset is nil
|
81
|
+
# @return [Integer] little endian unsign short value
|
82
|
+
def short(offset)
|
83
|
+
@io.pos = offset unless offset.nil?
|
84
|
+
@io.read(2).unpack("v")[0]
|
85
|
+
end
|
86
|
+
|
87
|
+
# relace string table parser
|
88
|
+
def parse_strings
|
89
|
+
strpool = Resource::ResStringPool.new(@io.string, 8) # ugh!
|
90
|
+
@strings = strpool.strings
|
91
|
+
end
|
92
|
+
|
93
|
+
# parse tag
|
94
|
+
def parse_tags
|
95
|
+
# skip until START_TAG
|
96
|
+
pos = @xml_offset
|
97
|
+
pos += 4 until (word(pos) == TAG_START) #ugh!
|
98
|
+
@io.pos -= 4
|
99
|
+
|
100
|
+
# read tags
|
101
|
+
#puts "start tag parse: %d(%#x)" % [@io.pos, @io.pos]
|
102
|
+
until @io.eof?
|
103
|
+
last_pos = @io.pos
|
104
|
+
tag, tag1, line, tag3, ns_id, name_id = @io.read(4*6).unpack("V*")
|
105
|
+
case tag
|
106
|
+
when TAG_START
|
107
|
+
tag6, num_attrs, tag8 = @io.read(4*3).unpack("V*")
|
108
|
+
elem = REXML::Element.new(@strings[name_id])
|
109
|
+
#puts "start tag %d(%#x): #{@strings[name_id]} attrs:#{num_attrs}" % [last_pos, last_pos]
|
110
|
+
@parents.last.add_element elem
|
111
|
+
num_attrs.times do
|
112
|
+
key, val = parse_attribute
|
113
|
+
elem.add_attribute(key, val)
|
114
|
+
end
|
115
|
+
@parents.push elem
|
116
|
+
when TAG_END
|
117
|
+
@parents.pop
|
118
|
+
when TAG_END_DOC
|
119
|
+
break
|
120
|
+
when TAG_TEXT
|
121
|
+
text = REXML::Text.new(@strings[ns_id])
|
122
|
+
@parents.last.text = text
|
123
|
+
dummy = @io.read(4*1).unpack("V*") # skip 4bytes
|
124
|
+
when TAG_START_DOC, TAG_CDSECT, TAG_ENTITY_REF
|
125
|
+
# not implemented yet.
|
126
|
+
else
|
127
|
+
raise ReadError, "pos=%d(%#x)[tag:%#x]" % [last_pos, last_pos, tag]
|
128
|
+
end
|
129
|
+
end
|
130
|
+
end
|
131
|
+
|
132
|
+
# parse attribute of a element
|
133
|
+
def parse_attribute
|
134
|
+
ns_id, name_id, val_str_id, flags, val = @io.read(4*5).unpack("V*")
|
135
|
+
key = @strings[name_id]
|
136
|
+
unless ns_id == 0xFFFFFFFF
|
137
|
+
ns = @strings[ns_id]
|
138
|
+
prefix = ns.sub(/.*\//,'')
|
139
|
+
unless @ns.include? ns
|
140
|
+
@ns << ns
|
141
|
+
@doc.root.add_namespace(prefix, ns)
|
142
|
+
end
|
143
|
+
key = "#{prefix}:#{key}"
|
144
|
+
end
|
145
|
+
value = convert_value(val_str_id, flags, val)
|
146
|
+
return key, value
|
147
|
+
end
|
148
|
+
|
149
|
+
|
150
|
+
def convert_value(val_str_id, flags, val)
|
151
|
+
unless val_str_id == 0xFFFFFFFF
|
152
|
+
value = @strings[val_str_id]
|
153
|
+
else
|
154
|
+
type = flags >> 24
|
155
|
+
case type
|
156
|
+
when VAL_TYPE_NULL
|
157
|
+
value = nil
|
158
|
+
when VAL_TYPE_REFERENCE
|
159
|
+
value = "@%#x" % val # refered resource id.
|
160
|
+
when VAL_TYPE_INT_DEC
|
161
|
+
value = val
|
162
|
+
when VAL_TYPE_INT_HEX
|
163
|
+
value = "%#x" % val
|
164
|
+
when VAL_TYPE_INT_BOOLEAN
|
165
|
+
value = ((val == 0xFFFFFFFF) || (val==1)) ? true : false
|
166
|
+
else
|
167
|
+
value = "[%#x, flag=%#x]" % [val, flags]
|
168
|
+
end
|
169
|
+
end
|
170
|
+
end
|
171
|
+
end
|
172
|
+
|
173
|
+
end
|
@@ -0,0 +1,74 @@
|
|
1
|
+
|
2
|
+
module Android
|
3
|
+
class Dex
|
4
|
+
# access flag object
|
5
|
+
class AccessFlag
|
6
|
+
# @return [Integer] flag value
|
7
|
+
attr_reader :flag
|
8
|
+
def initialize(flag)
|
9
|
+
@flag = flag
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
# access flag object for class in dex
|
14
|
+
class ClassAccessFlag < AccessFlag
|
15
|
+
ACCESSORS = [
|
16
|
+
{value:0x1, name:'public'},
|
17
|
+
{value:0x2, name:'private'},
|
18
|
+
{value:0x4, name:'protected'},
|
19
|
+
{value:0x8, name:'static'},
|
20
|
+
{value:0x10, name:'final'},
|
21
|
+
{value:0x20, name:'synchronized'},
|
22
|
+
{value:0x40, name:'volatile'},
|
23
|
+
{value:0x80, name:'transient'},
|
24
|
+
{value:0x100, name:'native'},
|
25
|
+
{value:0x200, name:'interface'},
|
26
|
+
{value:0x400, name:'abstract'},
|
27
|
+
{value:0x800, name:'strict'},
|
28
|
+
{value:0x1000, name:'synthetic'},
|
29
|
+
{value:0x2000, name:'annotation'},
|
30
|
+
{value:0x4000, name:'enum'},
|
31
|
+
#{value:0x8000, name:'unused'},
|
32
|
+
{value:0x10000, name:'constructor'},
|
33
|
+
{value:0x20000, name:'declared-synchronized'},
|
34
|
+
]
|
35
|
+
|
36
|
+
# convert access flag to string
|
37
|
+
# @return [String]
|
38
|
+
def to_s
|
39
|
+
ACCESSORS.select{|e| ((e[:value] & @flag) != 0) }.map{|e| e[:name] }.join(' ')
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
# access flag object for method in dex
|
44
|
+
class MethodAccessFlag < AccessFlag
|
45
|
+
ACCESSORS = [
|
46
|
+
{value: 0x1, name:'public'},
|
47
|
+
{value: 0x2, name:'private'},
|
48
|
+
{value: 0x4, name:'protected'},
|
49
|
+
{value: 0x8, name:'static'},
|
50
|
+
{value: 0x10, name:'final'},
|
51
|
+
{value: 0x20, name:'synchronized'},
|
52
|
+
{value: 0x40, name:'bridge'},
|
53
|
+
{value: 0x80, name:'varargs'},
|
54
|
+
{value: 0x100, name:'native'},
|
55
|
+
{value: 0x200, name:'interface'},
|
56
|
+
{value: 0x400, name:'abstract'},
|
57
|
+
{value: 0x800, name:'strict'},
|
58
|
+
{value: 0x1000, name:'synthetic'},
|
59
|
+
{value: 0x2000, name:'annotation'},
|
60
|
+
{value: 0x4000, name:'enum'},
|
61
|
+
#{value: 0x8000, name:'unused'},
|
62
|
+
{value: 0x10000, name:'constructor'},
|
63
|
+
{value: 0x20000, name:'declared-synchronized'},
|
64
|
+
]
|
65
|
+
# convert access flag to string
|
66
|
+
# @return [String]
|
67
|
+
def to_s
|
68
|
+
ACCESSORS.select{|e| ((e[:value] & @flag) != 0) }.map{|e| e[:name] }.join(' ')
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
|