ruby_apk 0.5.1 → 0.6.0

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.
data/.travis.yml ADDED
@@ -0,0 +1,4 @@
1
+ language: ruby
2
+ rvm:
3
+ - 1.9.3
4
+ - 2.0.0
data/CHANGELOG.md CHANGED
@@ -1,6 +1,10 @@
1
1
  # ChangeLog
2
+ ## 0.6.0
3
+ * implement Android::Apk#layouts(#10), Android::Apk#icon(#11), Android::Apk#label(#12),
4
+ * fix bug (#13)
5
+
2
6
  ## 0.5.1
3
- * [#8] ass Android::Manifest#label
7
+ * [#8] add Android::Manifest#label
4
8
  * [#7] fix wrong boolean value in manifest parser
5
9
  * [#6] add accessor Android::Manifest#doc
6
10
 
data/README.md CHANGED
@@ -1,6 +1,8 @@
1
1
  # ruby_apk
2
2
  Android Apk static analysis library for Ruby.
3
3
 
4
+ [![Build Status](https://travis-ci.org/SecureBrain/ruby_apk.png)](https://travis-ci.org/SecureBrain/ruby_apk)
5
+
4
6
  ## Requirements
5
7
  - ruby(>=1.9.x)
6
8
 
@@ -30,6 +32,20 @@ end
30
32
  elf_files = apk.find{|name, data| data[0..3] == [0x7f, 0x45, 0x4c, 0x46] } # ELF magic number
31
33
  ```
32
34
 
35
+ #### Extract icon data in Apk (since 0.6.0)
36
+ ```ruby
37
+ apk = Android::Apk.new('sample.apk')
38
+ icons = apk.icon # { "res/drawable-hdpi/ic_launcher.png" => "\x89PNG\x0D\x0A...", ... }
39
+ icons.each do |name, data|
40
+ File.open(File.basename(name), 'wb') {|f| f.write data } # save to file.
41
+ end
42
+ ```
43
+ #### Extract application label string (since 0.6.0)
44
+ ```ruby
45
+ apk = Android::Apk.new('sample.apk')
46
+ puts apk.label
47
+ ```
48
+
33
49
  ### Manifest
34
50
  #### Get readable xml
35
51
  ```ruby
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.5.1
1
+ 0.6.0
data/lib/android/apk.rb CHANGED
@@ -150,6 +150,43 @@ module Android
150
150
  end
151
151
  found
152
152
  end
153
+
154
+ # extract icon data from AndroidManifest and resource.
155
+ # @return [Hash{ String => String }] hash key is icon filename. value is image data
156
+ # @raise [NotFoundError]
157
+ # @since 0.6.0
158
+ def icon
159
+ icon_id = @manifest.doc.elements['/manifest/application'].attributes['icon']
160
+ if /^@(\w+\/\w+)|(0x[0-9a-fA-F]{8})$/ =~ icon_id
161
+ drawables = @resource.find(icon_id)
162
+ Hash[drawables.map {|name| [name, file(name)] }]
163
+ else
164
+ { icon_id => file(icon_id) } # ugh!: not tested!!
165
+ end
166
+ end
167
+
168
+ # get application label from AndroidManifest and resources.
169
+ # @param [String] lang language code like 'ja', 'cn', ...
170
+ # @return [String] application label string
171
+ # @return [nil] when label is not found
172
+ # @since 0.6.0
173
+ def label(lang=nil)
174
+ label_id = @manifest.label
175
+ if /^@(\w+\/\w+)|(0x[0-9a-fA-F]{8})$/ =~ label_id
176
+ opts = {}
177
+ opts[:lang] = lang unless lang.nil?
178
+ @resource.find(label_id, opts)
179
+ else
180
+ label_id # include nil
181
+ end
182
+ end
183
+
184
+ # get screen layout xml datas
185
+ # @return [Hash{ name => Android::Layout }]
186
+ # @since 0.6.0
187
+ def layouts
188
+ @layouts ||= Layout.collect_layouts(self) # lazy parse
189
+ end
153
190
  end
154
191
  end
155
192
 
@@ -12,6 +12,10 @@ module Android
12
12
  #
13
13
  # /frameworks/base/libs/androidfw/ResourceTypes.cpp
14
14
  class AXMLParser
15
+ def self.axml?(data)
16
+ (data[0..3] == "\x03\x00\x08\x00")
17
+ end
18
+
15
19
  # axml parse error
16
20
  class ReadError < StandardError; end
17
21
 
@@ -81,18 +85,10 @@ module Android
81
85
  @io.read(2).unpack("v")[0]
82
86
  end
83
87
 
84
- # parse string table
88
+ # relace string table parser
85
89
  def parse_strings
86
- sit_off = 0x24 # string index table offset
87
- st_off = sit_off + @num_str * 4 # string table offset
88
- @strings = []
89
- @num_str.times do |i|
90
- pos = st_off + word(sit_off + (4 * i)) # get position from string index table
91
- len = short(pos) # read string length(not bytes)
92
- str = @io.read(len*2) # read string(UTF-16LE)
93
- str.force_encoding(Encoding::UTF_16LE)
94
- @strings[i] = str.encode(Encoding::UTF_8)
95
- end
90
+ strpool = Resource::ResStringPool.new(@io.string, 8) # ugh!
91
+ @strings = strpool.strings
96
92
  end
97
93
 
98
94
  # parse tag
@@ -0,0 +1,44 @@
1
+ # encoding: utf-8
2
+ require 'ruby_apk'
3
+ require 'rexml/document'
4
+
5
+ module Android
6
+ class Layout
7
+ # @return [Hash] { path => Layout }
8
+ def self.collect_layouts(apk)
9
+ targets = apk.find {|name, data| name =~ /^res\/layout\/*/ }
10
+ ret = {}
11
+ targets.each do |path|
12
+ data = apk.file(path)
13
+ data.force_encoding(Encoding::ASCII_8BIT)
14
+ ret[path] = nil
15
+ begin
16
+ ret[path] = Layout.new(data, path) if AXMLParser.axml?(data)
17
+ rescue => e
18
+ $stderr.puts e
19
+ end
20
+ end
21
+ ret
22
+ end
23
+
24
+ # @return [String] layout file path
25
+ attr_reader :path
26
+ # @return [REXML::Document] xml document object
27
+ attr_reader :doc
28
+
29
+ def initialize(data, path=nil)
30
+ @data = data
31
+ @path = path
32
+ @doc = AXMLParser.new(data).parse
33
+ end
34
+
35
+ # @return [String] xml string
36
+ def to_xml(indent=4)
37
+ xml = ''
38
+ formatter = REXML::Formatters::Pretty.new(indent)
39
+ formatter.write(@doc.root, xml)
40
+ xml
41
+ end
42
+ end
43
+ end
44
+
@@ -206,8 +206,16 @@ module Android
206
206
 
207
207
  # application label
208
208
  # @return [String] application label string or resource id (like @0x7f04001)
209
+ # @since 0.5.1
209
210
  def label
210
- @doc.elements['/manifest/application'].attributes['label']
211
+ label = @doc.elements['/manifest/application'].attributes['label']
212
+ if label.nil?
213
+ # application element has no label attributes.
214
+ # so looking for activites that has label attribute.
215
+ activities = @doc.elements['/manifest/application'].find{|e| e.name == 'activity' && !e.attributes['label'].nil? }
216
+ label = activities.nil? ? nil : activities.first.attributes['label']
217
+ end
218
+ label
211
219
  end
212
220
 
213
221
  # return xml as string format
@@ -123,7 +123,7 @@ module Android
123
123
  # @option opts [String] :contry cantry code like 'jp'...
124
124
  # @raise [ArgumentError] invalid id format
125
125
  # @note
126
- # This method only support string resource for now.
126
+ # This method only support string and drawable resource for now.
127
127
  # @note
128
128
  # Always return nil if assign not string type res id.
129
129
  #
@@ -132,13 +132,24 @@ module Android
132
132
  tid = ((hex_id&0xff0000) >>16)
133
133
  key = hex_id&0xffff
134
134
 
135
- if type(tid) == 'string'
135
+ case type(tid)
136
+ when 'string'
136
137
  return find_res_string(key, opts)
138
+ when 'drawable'
139
+ drawables = []
140
+ @types[tid].each do |type|
141
+ unless type[key].nil?
142
+ drawables << @global_string_pool.strings[type[key].val.data]
143
+ end
144
+ end
145
+ return drawables
137
146
  else
138
147
  nil
139
148
  end
140
149
  end
141
150
 
151
+ def res_types
152
+ end
142
153
  def find_res_string(key, opts={})
143
154
  unless opts[:lang].nil?
144
155
  string = @res_strings_lang[opts[:lang]]
@@ -483,10 +494,10 @@ module Android
483
494
  first_pkg.res_hex_id(readable_id)
484
495
  end
485
496
 
486
- private
487
497
  def first_pkg
488
498
  @packages.first[1]
489
499
  end
500
+ private
490
501
  def parse
491
502
  offset = 0
492
503
 
data/lib/ruby_apk.rb CHANGED
@@ -4,3 +4,4 @@ require_relative 'android/axml_parser'
4
4
  require_relative 'android/dex'
5
5
  require_relative 'android/resource'
6
6
  require_relative 'android/utils'
7
+ require_relative 'android/layout'
data/ruby_apk.gemspec CHANGED
@@ -5,11 +5,11 @@
5
5
 
6
6
  Gem::Specification.new do |s|
7
7
  s.name = "ruby_apk"
8
- s.version = "0.5.1"
8
+ s.version = "0.6.0"
9
9
 
10
10
  s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
11
11
  s.authors = ["SecureBrain"]
12
- s.date = "2013-05-01"
12
+ s.date = "2013-05-05"
13
13
  s.description = "static analysis tool for android apk"
14
14
  s.email = "info@securebrain.co.jp"
15
15
  s.extra_rdoc_files = [
@@ -17,7 +17,7 @@ Gem::Specification.new do |s|
17
17
  "README.md"
18
18
  ]
19
19
  s.files = [
20
- ".rspec",
20
+ ".travis.yml",
21
21
  "CHANGELOG.md",
22
22
  "Gemfile",
23
23
  "Gemfile.lock",
@@ -32,6 +32,7 @@ Gem::Specification.new do |s|
32
32
  "lib/android/dex/dex_object.rb",
33
33
  "lib/android/dex/info.rb",
34
34
  "lib/android/dex/utils.rb",
35
+ "lib/android/layout.rb",
35
36
  "lib/android/manifest.rb",
36
37
  "lib/android/resource.rb",
37
38
  "lib/android/utils.rb",
@@ -50,6 +51,7 @@ Gem::Specification.new do |s|
50
51
  "spec/dex/info_spec.rb",
51
52
  "spec/dex/utils_spec.rb",
52
53
  "spec/dex_spec.rb",
54
+ "spec/layout_spec.rb",
53
55
  "spec/manifest_spec.rb",
54
56
  "spec/resource_spec.rb",
55
57
  "spec/ruby_apk_spec.rb",
@@ -59,7 +61,7 @@ Gem::Specification.new do |s|
59
61
  s.homepage = "https://github.com/SecureBrain/ruby_apk/"
60
62
  s.licenses = ["MIT"]
61
63
  s.require_paths = ["lib"]
62
- s.rubygems_version = "1.8.25"
64
+ s.rubygems_version = "1.8.16"
63
65
  s.summary = "static analysis tool for android apk"
64
66
 
65
67
  if s.respond_to? :specification_version then
data/spec/apk_spec.rb CHANGED
@@ -265,4 +265,27 @@ describe Android::Apk do
265
265
  end
266
266
  end
267
267
  end
268
+
269
+ describe "#icon" do
270
+ context "with real apk file" do
271
+ let(:tmp_path){ File.expand_path(File.dirname(__FILE__) + '/data/sample.apk') }
272
+ subject { apk.icon }
273
+ it { should be_a Hash }
274
+ it { should have(3).items }
275
+ it { subject.keys.should =~ ["res/drawable-hdpi/ic_launcher.png", "res/drawable-ldpi/ic_launcher.png", "res/drawable-mdpi/ic_launcher.png"]
276
+ }
277
+ end
278
+ end
279
+
280
+ describe "#label" do
281
+ context "with real apk file" do
282
+ let(:tmp_path){ File.expand_path(File.dirname(__FILE__) + '/data/sample.apk') }
283
+ subject { apk.label }
284
+ it { should eq 'Sample' }
285
+ context 'when assign lang code' do
286
+ subject { apk.label('ja') }
287
+ it { should eq 'Sample' }
288
+ end
289
+ end
290
+ end
268
291
  end
@@ -3,13 +3,15 @@ require File.expand_path(File.dirname(__FILE__) + '/../spec_helper')
3
3
  describe Android::Dex do
4
4
  describe Android::Dex::DexObject::Header do
5
5
  let(:header_sample) {
6
- "\x64\x65\x78\x0A\x30\x33\x35\x00\x3F\x14\x98\x2C\x25\x77\x9B\x8D" +
7
- "\x7C\xF0\x0B\xFA\x4D\x7B\x03\xAD\x4C\x15\xBC\x31\x4F\xD3\x4B\x71" +
8
- "\x58\x18\x00\x00\x70\x00\x00\x00\x78\x56\x34\x12\x00\x00\x00\x00" +
9
- "\x00\x00\x00\x00\x88\x17\x00\x00\x7A\x00\x00\x00\x70\x00\x00\x00" +
10
- "\x23\x00\x00\x00\x58\x02\x00\x00\x0E\x00\x00\x00\xE4\x02\x00\x00" +
11
- "\x10\x00\x00\x00\x8C\x03\x00\x00\x2C\x00\x00\x00\x0C\x04\x00\x00" +
12
- "\x0A\x00\x00\x00\x6C\x05\x00\x00\xAC\x11\x00\x00\xAC\x06\x00\x00"
6
+ sample =
7
+ "\x64\x65\x78\x0A\x30\x33\x35\x00\x3F\x14\x98\x2C\x25\x77\x9B\x8D" +
8
+ "\x7C\xF0\x0B\xFA\x4D\x7B\x03\xAD\x4C\x15\xBC\x31\x4F\xD3\x4B\x71" +
9
+ "\x58\x18\x00\x00\x70\x00\x00\x00\x78\x56\x34\x12\x00\x00\x00\x00" +
10
+ "\x00\x00\x00\x00\x88\x17\x00\x00\x7A\x00\x00\x00\x70\x00\x00\x00" +
11
+ "\x23\x00\x00\x00\x58\x02\x00\x00\x0E\x00\x00\x00\xE4\x02\x00\x00" +
12
+ "\x10\x00\x00\x00\x8C\x03\x00\x00\x2C\x00\x00\x00\x0C\x04\x00\x00" +
13
+ "\x0A\x00\x00\x00\x6C\x05\x00\x00\xAC\x11\x00\x00\xAC\x06\x00\x00"
14
+ sample.force_encoding(Encoding::ASCII_8BIT)
13
15
  }
14
16
  let(:header) { Android::Dex::DexObject::Header.new(header_sample) }
15
17
  describe "#symbols" do
@@ -0,0 +1,27 @@
1
+ require File.expand_path(File.dirname(__FILE__) + '/spec_helper')
2
+
3
+ describe Android::Layout do
4
+ context 'with real apk sample file' do
5
+ let(:apk_path){ File.expand_path(File.dirname(__FILE__) + '/data/sample.apk') }
6
+ let(:apk){ Android::Apk.new(apk_path) }
7
+ let(:layouts) { apk.layouts }
8
+ subject { layouts }
9
+ it { should be_a Hash }
10
+ it { should have_key "res/layout/main.xml" }
11
+ it { should have(1).item }
12
+ context 'about first item' do
13
+ subject { layouts['res/layout/main.xml'] }
14
+ it { should be_a Android::Layout }
15
+ describe '#path' do
16
+ it { subject.path.should eq 'res/layout/main.xml' }
17
+ end
18
+ describe '#doc' do
19
+ it { subject.doc.should be_a REXML::Document }
20
+ end
21
+ describe '#to_xml' do
22
+ it { subject.to_xml.should be_a String }
23
+ end
24
+ end
25
+ end
26
+ end
27
+
@@ -84,6 +84,20 @@ describe Android::Resource do
84
84
  end
85
85
  end
86
86
 
87
+ context 'with str_resources.arsc data' do
88
+ let(:res_data) { File.read(File.expand_path(File.dirname(__FILE__) + '/data/str_resources.arsc')) }
89
+ subject { resource }
90
+ describe 'about drawable resource' do
91
+ it 'hoge' do
92
+ table = resource.packages.first[1]
93
+ p table
94
+ table.type_strings.each_with_index do |type, id|
95
+ puts "[0x#{(id+1).to_s(16)}] #{type}"
96
+ end
97
+ puts "readable id:" + table.res_readable_id('@0x7f020000')
98
+ end
99
+ end
100
+ end
87
101
  context 'with str_resources.arsc data' do
88
102
  let(:res_data) { File.read(File.expand_path(File.dirname(__FILE__) + '/data/str_resources.arsc')) }
89
103
  subject { resource }
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: ruby_apk
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.5.1
4
+ version: 0.6.0
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -9,11 +9,11 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2013-05-01 00:00:00.000000000 Z
12
+ date: 2013-05-05 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: rubyzip
16
- requirement: !ruby/object:Gem::Requirement
16
+ requirement: &70114833627600 !ruby/object:Gem::Requirement
17
17
  none: false
18
18
  requirements:
19
19
  - - ! '>='
@@ -21,15 +21,10 @@ dependencies:
21
21
  version: '0'
22
22
  type: :runtime
23
23
  prerelease: false
24
- version_requirements: !ruby/object:Gem::Requirement
25
- none: false
26
- requirements:
27
- - - ! '>='
28
- - !ruby/object:Gem::Version
29
- version: '0'
24
+ version_requirements: *70114833627600
30
25
  - !ruby/object:Gem::Dependency
31
26
  name: rspec
32
- requirement: !ruby/object:Gem::Requirement
27
+ requirement: &70114833625800 !ruby/object:Gem::Requirement
33
28
  none: false
34
29
  requirements:
35
30
  - - ~>
@@ -37,15 +32,10 @@ dependencies:
37
32
  version: 2.11.0
38
33
  type: :development
39
34
  prerelease: false
40
- version_requirements: !ruby/object:Gem::Requirement
41
- none: false
42
- requirements:
43
- - - ~>
44
- - !ruby/object:Gem::Version
45
- version: 2.11.0
35
+ version_requirements: *70114833625800
46
36
  - !ruby/object:Gem::Dependency
47
37
  name: bundler
48
- requirement: !ruby/object:Gem::Requirement
38
+ requirement: &70114833624840 !ruby/object:Gem::Requirement
49
39
  none: false
50
40
  requirements:
51
41
  - - ~>
@@ -53,15 +43,10 @@ dependencies:
53
43
  version: 1.1.5
54
44
  type: :development
55
45
  prerelease: false
56
- version_requirements: !ruby/object:Gem::Requirement
57
- none: false
58
- requirements:
59
- - - ~>
60
- - !ruby/object:Gem::Version
61
- version: 1.1.5
46
+ version_requirements: *70114833624840
62
47
  - !ruby/object:Gem::Dependency
63
48
  name: jeweler
64
- requirement: !ruby/object:Gem::Requirement
49
+ requirement: &70114833622960 !ruby/object:Gem::Requirement
65
50
  none: false
66
51
  requirements:
67
52
  - - ~>
@@ -69,15 +54,10 @@ dependencies:
69
54
  version: 1.6.4
70
55
  type: :development
71
56
  prerelease: false
72
- version_requirements: !ruby/object:Gem::Requirement
73
- none: false
74
- requirements:
75
- - - ~>
76
- - !ruby/object:Gem::Version
77
- version: 1.6.4
57
+ version_requirements: *70114833622960
78
58
  - !ruby/object:Gem::Dependency
79
59
  name: yard
80
- requirement: !ruby/object:Gem::Requirement
60
+ requirement: &70114833622080 !ruby/object:Gem::Requirement
81
61
  none: false
82
62
  requirements:
83
63
  - - ! '>='
@@ -85,15 +65,10 @@ dependencies:
85
65
  version: '0'
86
66
  type: :development
87
67
  prerelease: false
88
- version_requirements: !ruby/object:Gem::Requirement
89
- none: false
90
- requirements:
91
- - - ! '>='
92
- - !ruby/object:Gem::Version
93
- version: '0'
68
+ version_requirements: *70114833622080
94
69
  - !ruby/object:Gem::Dependency
95
70
  name: redcarpet
96
- requirement: !ruby/object:Gem::Requirement
71
+ requirement: &70114833637060 !ruby/object:Gem::Requirement
97
72
  none: false
98
73
  requirements:
99
74
  - - ! '>='
@@ -101,15 +76,10 @@ dependencies:
101
76
  version: '0'
102
77
  type: :development
103
78
  prerelease: false
104
- version_requirements: !ruby/object:Gem::Requirement
105
- none: false
106
- requirements:
107
- - - ! '>='
108
- - !ruby/object:Gem::Version
109
- version: '0'
79
+ version_requirements: *70114833637060
110
80
  - !ruby/object:Gem::Dependency
111
81
  name: simplecov
112
- requirement: !ruby/object:Gem::Requirement
82
+ requirement: &70114833634900 !ruby/object:Gem::Requirement
113
83
  none: false
114
84
  requirements:
115
85
  - - ! '>='
@@ -117,12 +87,7 @@ dependencies:
117
87
  version: '0'
118
88
  type: :development
119
89
  prerelease: false
120
- version_requirements: !ruby/object:Gem::Requirement
121
- none: false
122
- requirements:
123
- - - ! '>='
124
- - !ruby/object:Gem::Version
125
- version: '0'
90
+ version_requirements: *70114833634900
126
91
  description: static analysis tool for android apk
127
92
  email: info@securebrain.co.jp
128
93
  executables: []
@@ -131,7 +96,7 @@ extra_rdoc_files:
131
96
  - LICENSE.txt
132
97
  - README.md
133
98
  files:
134
- - .rspec
99
+ - .travis.yml
135
100
  - CHANGELOG.md
136
101
  - Gemfile
137
102
  - Gemfile.lock
@@ -146,6 +111,7 @@ files:
146
111
  - lib/android/dex/dex_object.rb
147
112
  - lib/android/dex/info.rb
148
113
  - lib/android/dex/utils.rb
114
+ - lib/android/layout.rb
149
115
  - lib/android/manifest.rb
150
116
  - lib/android/resource.rb
151
117
  - lib/android/utils.rb
@@ -164,6 +130,7 @@ files:
164
130
  - spec/dex/info_spec.rb
165
131
  - spec/dex/utils_spec.rb
166
132
  - spec/dex_spec.rb
133
+ - spec/layout_spec.rb
167
134
  - spec/manifest_spec.rb
168
135
  - spec/resource_spec.rb
169
136
  - spec/ruby_apk_spec.rb
@@ -184,7 +151,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
184
151
  version: '0'
185
152
  segments:
186
153
  - 0
187
- hash: 1470903068031812848
154
+ hash: -3061789134736537466
188
155
  required_rubygems_version: !ruby/object:Gem::Requirement
189
156
  none: false
190
157
  requirements:
@@ -193,7 +160,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
193
160
  version: '0'
194
161
  requirements: []
195
162
  rubyforge_project:
196
- rubygems_version: 1.8.25
163
+ rubygems_version: 1.8.16
197
164
  signing_key:
198
165
  specification_version: 3
199
166
  summary: static analysis tool for android apk
data/.rspec DELETED
@@ -1 +0,0 @@
1
- --color --format d