ruby_apk 0.5.1 → 0.6.0

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