ruby_apk 0.4.2 → 0.5.0
Sign up to get free protection for your applications and to get access to all the features.
- data/CHANGELOG.md +3 -0
- data/README.md +17 -1
- data/VERSION +1 -1
- data/lib/android/resource.rb +398 -15
- data/ruby_apk.gemspec +4 -3
- data/spec/data/str_resources.arsc +0 -0
- data/spec/resource_spec.rb +69 -0
- metadata +5 -4
data/CHANGELOG.md
CHANGED
data/README.md
CHANGED
@@ -5,7 +5,7 @@ Android Apk static analysis library for Ruby.
|
|
5
5
|
- ruby(>=1.9.x)
|
6
6
|
|
7
7
|
## Install
|
8
|
-
|
8
|
+
$ gem install ruby_apk
|
9
9
|
|
10
10
|
## Usage
|
11
11
|
### Initialize
|
@@ -72,6 +72,22 @@ end
|
|
72
72
|
rsc = Android::Resource.new(rsc_data)
|
73
73
|
```
|
74
74
|
|
75
|
+
### Resolve resource id
|
76
|
+
This feature supports only srting resources for now.
|
77
|
+
|
78
|
+
```ruby
|
79
|
+
apk = Android::Apk.new('sample.apk')
|
80
|
+
# assigns readable resource id
|
81
|
+
puts rsc.find('@string/app_name') # => 'application name'
|
82
|
+
|
83
|
+
# assigns hex resource id
|
84
|
+
puts rsc.find('@0x7f040000') # => 'application name'
|
85
|
+
|
86
|
+
# you can set lang attribute.
|
87
|
+
puts rsc.find('@0x7f040000', :lang => 'ja')
|
88
|
+
```
|
89
|
+
|
90
|
+
|
75
91
|
### Dex
|
76
92
|
#### Extract dex information
|
77
93
|
```ruby
|
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
0.
|
1
|
+
0.5.0
|
data/lib/android/resource.rb
CHANGED
@@ -1,26 +1,22 @@
|
|
1
1
|
# encoding: utf-8
|
2
2
|
require 'stringio'
|
3
|
-
require 'csv'
|
4
|
-
require 'ruby_apk'
|
5
3
|
|
6
4
|
module Android
|
7
5
|
# based on Android OS source code
|
8
6
|
# /frameworks/base/include/utils/ResourceTypes.h
|
7
|
+
# @see http://justanapplication.wordpress.com/category/android/android-resources/
|
9
8
|
class Resource
|
10
|
-
class
|
11
|
-
attr_reader :type, :header_size, :size
|
9
|
+
class Chunk
|
12
10
|
def initialize(data, offset)
|
13
11
|
@data = data
|
14
12
|
@offset = offset
|
13
|
+
exec_parse
|
14
|
+
end
|
15
|
+
def exec_parse
|
15
16
|
@data_io = StringIO.new(@data, 'rb')
|
16
|
-
@data_io.seek(offset)
|
17
|
+
@data_io.seek(@offset)
|
17
18
|
parse
|
18
|
-
|
19
|
-
private
|
20
|
-
def parse
|
21
|
-
@type = read_int16
|
22
|
-
@header_size = read_int16
|
23
|
-
@size = read_int32
|
19
|
+
@data_io.close
|
24
20
|
end
|
25
21
|
def read_int32
|
26
22
|
@data_io.read(4).unpack('V')[0]
|
@@ -28,6 +24,22 @@ module Android
|
|
28
24
|
def read_int16
|
29
25
|
@data_io.read(2).unpack('v')[0]
|
30
26
|
end
|
27
|
+
def read_int8
|
28
|
+
@data_io.read(1).ord
|
29
|
+
end
|
30
|
+
def current_position
|
31
|
+
@data_io.pos
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
class ChunkHeader < Chunk
|
36
|
+
attr_reader :type, :header_size, :size
|
37
|
+
private
|
38
|
+
def parse
|
39
|
+
@type = read_int16
|
40
|
+
@header_size = read_int16
|
41
|
+
@size = read_int32
|
42
|
+
end
|
31
43
|
end
|
32
44
|
|
33
45
|
class ResTableHeader < ChunkHeader
|
@@ -37,6 +49,7 @@ module Android
|
|
37
49
|
@package_count = read_int32
|
38
50
|
end
|
39
51
|
end
|
52
|
+
|
40
53
|
class ResStringPool < ChunkHeader
|
41
54
|
SORTED_FLAG = 1 << 0
|
42
55
|
UTF8_FLAG = 1 << 8
|
@@ -95,37 +108,407 @@ module Android
|
|
95
108
|
end
|
96
109
|
end
|
97
110
|
|
111
|
+
class ResTablePackage < ChunkHeader
|
112
|
+
attr_reader :name
|
113
|
+
|
114
|
+
def global_string_pool=(pool)
|
115
|
+
@global_string_pool = pool
|
116
|
+
extract_res_strings
|
117
|
+
end
|
118
|
+
|
119
|
+
# find resource by resource id
|
120
|
+
# @param [String] res_id (like '@0x7f010001' or '@string/key')
|
121
|
+
# @param [Hash] opts option
|
122
|
+
# @option opts [String] :lang language code like 'ja', 'cn'...
|
123
|
+
# @option opts [String] :contry cantry code like 'jp'...
|
124
|
+
# @raise [ArgumentError] invalid id format
|
125
|
+
# @note
|
126
|
+
# This method only support string resource for now.
|
127
|
+
# @note
|
128
|
+
# Always return nil if assign not string type res id.
|
129
|
+
#
|
130
|
+
def find(res_id, opts={})
|
131
|
+
hex_id = strid2int(res_id)
|
132
|
+
tid = ((hex_id&0xff0000) >>16)
|
133
|
+
key = hex_id&0xffff
|
134
|
+
|
135
|
+
if type(tid) == 'string'
|
136
|
+
return find_res_string(key, opts)
|
137
|
+
else
|
138
|
+
nil
|
139
|
+
end
|
140
|
+
end
|
141
|
+
|
142
|
+
def find_res_string(key, opts={})
|
143
|
+
unless opts[:lang].nil?
|
144
|
+
string = @res_strings_lang[opts[:lang]]
|
145
|
+
end
|
146
|
+
unless opts[:contry].nil?
|
147
|
+
string = @res_strings_contry[opts[:contry]]
|
148
|
+
end
|
149
|
+
string = @res_strings_default if string.nil?
|
150
|
+
raise NotFoundError unless string.has_key? key
|
151
|
+
return string[key]
|
152
|
+
end
|
153
|
+
private :find_res_string
|
154
|
+
|
155
|
+
# convert string resource id to fixnum
|
156
|
+
# @param [String] res_id (like '@0x7f010001' or '@string/key')
|
157
|
+
# @return [Fixnum] integer id (like 0x7f010001)
|
158
|
+
# @raise [ArgumentError] invalid format
|
159
|
+
def strid2int(res_id)
|
160
|
+
case res_id
|
161
|
+
when /^@?0x[0-9a-fA-F]{8}$/
|
162
|
+
return res_id.sub(/^@/,'').to_i(16)
|
163
|
+
when /^@?\w+\/\w+/
|
164
|
+
return res_hex_id(res_id).sub(/^@/,'').to_i(16)
|
165
|
+
else
|
166
|
+
raise ArgumentError
|
167
|
+
end
|
168
|
+
end
|
169
|
+
|
170
|
+
def res_readable_id(hex_id)
|
171
|
+
if hex_id.kind_of? String
|
172
|
+
hex_id = hex_id.sub(/^@/,'').to_i(16)
|
173
|
+
end
|
174
|
+
tid = ((hex_id&0xff0000) >>16)
|
175
|
+
key = hex_id&0xffff
|
176
|
+
raise NotFoundError if !@types.has_key?(tid) || @types[tid][0][key].nil?
|
177
|
+
keyid= @types[tid][0][key].key # ugh!
|
178
|
+
"@#{type(tid)}/#{key(keyid)}"
|
179
|
+
end
|
180
|
+
def res_hex_id(readable_id, opt={})
|
181
|
+
dummy, typestr, keystr = readable_id.match(/^@?(\w+)\/(\w+)$/).to_a
|
182
|
+
tid = type_id(typestr)
|
183
|
+
raise NotFoundError unless @types.has_key?(tid)
|
184
|
+
keyid = @types[tid][0].keys[keystr]
|
185
|
+
raise NotFoundError if keyid.nil?
|
186
|
+
"@0x7f%02x%04x" % [tid, keyid]
|
187
|
+
end
|
188
|
+
|
189
|
+
def type_strings
|
190
|
+
@type_strings.strings
|
191
|
+
end
|
192
|
+
def type(id)
|
193
|
+
type_strings[id-1]
|
194
|
+
end
|
195
|
+
def type_id(str)
|
196
|
+
raise NotFoundError unless type_strings.include? str
|
197
|
+
type_strings.index(str) + 1
|
198
|
+
end
|
199
|
+
def key_strings
|
200
|
+
@key_strings.strings
|
201
|
+
end
|
202
|
+
def key(id)
|
203
|
+
key_strings[id]
|
204
|
+
end
|
205
|
+
def key_id(str)
|
206
|
+
raise NotFoundError unless key_strings.include? str
|
207
|
+
key_strings.index(str)
|
208
|
+
end
|
209
|
+
|
210
|
+
def parse
|
211
|
+
super
|
212
|
+
@id = read_int32
|
213
|
+
@name = @data_io.read(256).force_encoding(Encoding::UTF_16LE)
|
214
|
+
@name.encode!(Encoding::UTF_8).strip!
|
215
|
+
type_strings_offset = read_int32
|
216
|
+
@type_strings = ResStringPool.new(@data, @offset + type_strings_offset)
|
217
|
+
@last_public_type = read_int32
|
218
|
+
key_strings_offset = read_int32
|
219
|
+
@key_strings = ResStringPool.new(@data, @offset + key_strings_offset)
|
220
|
+
@last_public_key = read_int32
|
221
|
+
|
222
|
+
offset = @offset + key_strings_offset + @key_strings.size
|
223
|
+
|
224
|
+
@types = {}
|
225
|
+
@specs = {}
|
226
|
+
while offset < (@offset + @size)
|
227
|
+
type = @data[offset, 2].unpack('v')[0]
|
228
|
+
case type
|
229
|
+
when 0x0201 # RES_TABLE_TYPE_TYPE
|
230
|
+
type = ResTableType.new(@data, offset, self)
|
231
|
+
offset += type.size
|
232
|
+
@types[type.id] = [] if @types[type.id].nil?
|
233
|
+
@types[type.id] << type
|
234
|
+
when 0x0202 # RES_TABLE_TYPE_SPEC_TYPE`
|
235
|
+
spec = ResTableTypeSpec.new(@data, offset)
|
236
|
+
offset += spec.size
|
237
|
+
@specs[spec.id] = [] if @specs[spec.id].nil?
|
238
|
+
@specs[spec.id] << spec
|
239
|
+
else
|
240
|
+
raise "chunk type error: type:%#04x" % type
|
241
|
+
end
|
242
|
+
end
|
243
|
+
end
|
244
|
+
private :parse
|
245
|
+
|
246
|
+
def extract_res_strings
|
247
|
+
@res_strings_lang = {}
|
248
|
+
@res_strings_contry = {}
|
249
|
+
begin
|
250
|
+
type = type_id('string')
|
251
|
+
rescue NotFoundError
|
252
|
+
return
|
253
|
+
end
|
254
|
+
@types[type_id('string')].each do |type|
|
255
|
+
str_hash = {}
|
256
|
+
type.entry_count.times do |i|
|
257
|
+
entry = type[i]
|
258
|
+
if entry.nil?
|
259
|
+
str_hash[i] = nil
|
260
|
+
else
|
261
|
+
str_hash[i] = @global_string_pool.strings[type[i].val.data]
|
262
|
+
end
|
263
|
+
end
|
264
|
+
lang = type.config.locale_lang
|
265
|
+
contry = type.config.locale_contry
|
266
|
+
if lang.nil? && contry.nil?
|
267
|
+
@res_strings_default = str_hash
|
268
|
+
else
|
269
|
+
@res_strings_lang[lang] = str_hash unless lang.nil?
|
270
|
+
@res_strings_contry[contry] = str_hash unless contry.nil?
|
271
|
+
end
|
272
|
+
end
|
273
|
+
end
|
274
|
+
private :extract_res_strings
|
275
|
+
|
276
|
+
def inspect
|
277
|
+
"<ResTablePackage offset:%#08x, size:%#x, name:\"%s\">" % [@offset, @size, @name]
|
278
|
+
end
|
279
|
+
end
|
280
|
+
|
281
|
+
class ResTableType < ChunkHeader
|
282
|
+
attr_reader :id, :entry_count, :entry_start, :config
|
283
|
+
attr_reader :keys
|
284
|
+
|
285
|
+
def initialize(data, offset, pkg)
|
286
|
+
@pkg = pkg
|
287
|
+
super(data, offset)
|
288
|
+
end
|
289
|
+
# @param [String] index key name
|
290
|
+
# @param [Fixnum] index key index
|
291
|
+
# @return [ResTableEntry]
|
292
|
+
# @return [ResTableMapEntry]
|
293
|
+
# @return nil if entry index is NO_ENTRY(0xFFFFFFFF)
|
294
|
+
def [](index)
|
295
|
+
@entries[index]
|
296
|
+
end
|
297
|
+
|
298
|
+
def parse
|
299
|
+
super
|
300
|
+
@id = read_int8
|
301
|
+
res0 = read_int8 # must be 0.(maybe 4byte align)
|
302
|
+
res1 = read_int16 # must be 0.(maybe 4byte align)
|
303
|
+
@entry_count = read_int32
|
304
|
+
@entry_start = read_int32
|
305
|
+
@config = ResTableConfig.new(@data, current_position)
|
306
|
+
@data_io.seek(@config.size, IO::SEEK_CUR)
|
307
|
+
|
308
|
+
@entries = []
|
309
|
+
@keys = {}
|
310
|
+
@entry_count.times do |i|
|
311
|
+
entry_index = read_int32
|
312
|
+
if entry_index == ResTableEntry::NO_ENTRY
|
313
|
+
@entries << nil
|
314
|
+
else
|
315
|
+
entry = ResTableEntry.read_entry(@data, @offset + @entry_start + entry_index)
|
316
|
+
@entries << entry
|
317
|
+
@keys[@pkg.key(entry.key)] = i
|
318
|
+
end
|
319
|
+
end
|
320
|
+
end
|
321
|
+
private :parse
|
322
|
+
|
323
|
+
|
324
|
+
def inspect
|
325
|
+
"<ResTableType offset:0x#{@offset.to_s(16)}, id:#{@id}, " +
|
326
|
+
"count:#{@entry_count}, start:0x#{@entry_start.to_s(16)}>"
|
327
|
+
end
|
328
|
+
end
|
329
|
+
|
330
|
+
class ResTableConfig < Chunk
|
331
|
+
attr_reader :size, :imei, :locale_lang, :locale_contry, :input
|
332
|
+
attr_reader :screen_input, :version, :screen_config
|
333
|
+
def parse
|
334
|
+
@size = read_int32
|
335
|
+
@imei = read_int32
|
336
|
+
la = @data_io.read(2)
|
337
|
+
@locale_lang = la unless la == "\x00\x00"
|
338
|
+
cn = @data_io.read(2)
|
339
|
+
@locale_contry = cn unless cn == "\x00\x00"
|
340
|
+
@screen_type = read_int32
|
341
|
+
@input = read_int32
|
342
|
+
@screen_input = read_int32
|
343
|
+
@version = read_int32
|
344
|
+
@screen_config = read_int32
|
345
|
+
end
|
346
|
+
def inspect
|
347
|
+
"<ResTableConfig size:#{@size}, imei:#{@imei}, la:'#{@locale_lang}' cn:'#{@locale_contry}'"
|
348
|
+
end
|
349
|
+
end
|
350
|
+
|
351
|
+
class ResTableTypeSpec < ChunkHeader
|
352
|
+
attr_reader :id, :entry_count
|
353
|
+
|
354
|
+
def parse
|
355
|
+
super
|
356
|
+
@id = read_int8
|
357
|
+
res0 = read_int8 # must be 0.(maybe 4byte align)
|
358
|
+
res1 = read_int16 # must be 0.(maybe 4byte align)
|
359
|
+
@entry_count = read_int32
|
360
|
+
end
|
361
|
+
private :parse
|
362
|
+
|
363
|
+
def inspect
|
364
|
+
"<ResTableTypeSpec id:#{@id} entry count:#{@entry_count}>"
|
365
|
+
end
|
366
|
+
end
|
367
|
+
class ResTableEntry < Chunk
|
368
|
+
NO_ENTRY = 0xFFFFFFFF
|
369
|
+
|
370
|
+
# @return [ResTableEntry] if not set FLAG_COMPLEX
|
371
|
+
# @return [ResTableMapEntry] if not set FLAG_COMPLEX
|
372
|
+
def self.read_entry(data, offset)
|
373
|
+
flag = data[offset + 2, 2].unpack('v')[0]
|
374
|
+
if flag & ResTableEntry::FLAG_COMPLEX == 0
|
375
|
+
ResTableEntry.new(data, offset)
|
376
|
+
else
|
377
|
+
ResTableMapEntry.new(data, offset)
|
378
|
+
end
|
379
|
+
end
|
380
|
+
|
381
|
+
# If set, this is a complex entry, holding a set of name/value
|
382
|
+
# mappings. It is followed by an array of ResTable_map structures.
|
383
|
+
FLAG_COMPLEX = 0x01
|
384
|
+
# If set, this resource has been declared public, so libraries
|
385
|
+
# are allowed to reference it.
|
386
|
+
FLAG_PUBLIC = 0x02
|
387
|
+
|
388
|
+
attr_reader :size, :key, :val
|
389
|
+
def parse
|
390
|
+
@size = read_int16
|
391
|
+
@flag = read_int16
|
392
|
+
@key = read_int32 # RefStringPool_key
|
393
|
+
@val = ResValue.new(@data, current_position)
|
394
|
+
end
|
395
|
+
private :parse
|
396
|
+
|
397
|
+
def inspect
|
398
|
+
"<ResTableEntry @size=#{@size}, @key=#{@key} @flag=#{@flag}>"
|
399
|
+
end
|
400
|
+
end
|
401
|
+
class ResTableMapEntry < ResTableEntry
|
402
|
+
attr_reader :parent, :count
|
403
|
+
def parse
|
404
|
+
super
|
405
|
+
# resource identifier of the parent mapping, 0 if there is none.
|
406
|
+
@parent = read_int32
|
407
|
+
# number of name/value pairs that follw for FLAG_COMPLEX
|
408
|
+
@count = read_int32
|
409
|
+
# TODO: implement read ResTableMap object
|
410
|
+
end
|
411
|
+
private :parse
|
412
|
+
end
|
413
|
+
class ResTableMap < Chunk
|
414
|
+
def size
|
415
|
+
@val.size + 4
|
416
|
+
end
|
417
|
+
def parse
|
418
|
+
@name = read_int32
|
419
|
+
@val = ResValue.new(@data, current_position)
|
420
|
+
end
|
421
|
+
end
|
422
|
+
|
423
|
+
class ResValue < Chunk
|
424
|
+
attr_reader :size, :data_type, :data
|
425
|
+
def parse
|
426
|
+
@size = read_int16
|
427
|
+
res0 = read_int8 # Always set 0.
|
428
|
+
@data_type = read_int8
|
429
|
+
@data = read_int32
|
430
|
+
end
|
431
|
+
private :parse
|
432
|
+
end
|
433
|
+
|
98
434
|
######################################################################
|
435
|
+
# @returns [Hash] { name(String) => value(ResTablePackage) }
|
436
|
+
attr_reader :packages
|
437
|
+
|
99
438
|
def initialize(data)
|
100
439
|
data.force_encoding(Encoding::ASCII_8BIT)
|
101
440
|
@data = data
|
102
441
|
parse()
|
103
442
|
end
|
104
443
|
|
444
|
+
|
445
|
+
# @return [Array<String>] all strings defined in arsc.
|
105
446
|
def strings
|
106
447
|
@string_pool.strings
|
107
448
|
end
|
449
|
+
|
450
|
+
# @return [Fixnum] number of packages
|
108
451
|
def package_count
|
109
452
|
@res_table.package_count
|
110
453
|
end
|
111
454
|
|
455
|
+
# This method only support string resource for now.
|
456
|
+
# find resource by resource id
|
457
|
+
# @param [String] res_id (like '@0x7f010001' or '@string/key')
|
458
|
+
# @param [Hash] opts option
|
459
|
+
# @option opts [String] :lang language code like 'ja', 'cn'...
|
460
|
+
# @option opts [String] :contry cantry code like 'jp'...
|
461
|
+
# @raise [ArgumentError] invalid id format
|
462
|
+
# @note
|
463
|
+
# This method only support string resource for now.
|
464
|
+
# @note
|
465
|
+
# Always return nil if assign not string type res id.
|
466
|
+
# @since 0.5.0
|
467
|
+
def find(rsc_id, opt={})
|
468
|
+
first_pkg.find(rsc_id, opt)
|
469
|
+
end
|
470
|
+
|
471
|
+
# @param [String] hex_id hexoctet format resource id('@0x7f010001')
|
472
|
+
# @return [String] readable resource id ('@string/key')
|
473
|
+
# @since 0.5.0
|
474
|
+
def res_readable_id(hex_id)
|
475
|
+
first_pkg.res_readable_id(hex_id)
|
476
|
+
end
|
477
|
+
|
478
|
+
# convert readable resource id to hex id
|
479
|
+
# @param [String] readable_id readable resource id ('@string/key')
|
480
|
+
# @return [String] hexoctet format resource id('@0x7f010001')
|
481
|
+
# @since 0.5.0
|
482
|
+
def res_hex_id(readable_id)
|
483
|
+
first_pkg.res_hex_id(readable_id)
|
484
|
+
end
|
485
|
+
|
112
486
|
private
|
487
|
+
def first_pkg
|
488
|
+
@packages.first[1]
|
489
|
+
end
|
113
490
|
def parse
|
114
491
|
offset = 0
|
115
492
|
|
116
493
|
while offset < @data.size
|
117
494
|
type = @data[offset, 2].unpack('v')[0]
|
495
|
+
#print "[%#08x] " % offset
|
496
|
+
@packages = {}
|
118
497
|
case type
|
119
498
|
when 0x0001 # RES_STRING_POOL_TYPE
|
120
499
|
@string_pool = ResStringPool.new(@data, offset)
|
121
500
|
offset += @string_pool.size
|
501
|
+
#puts "RES_STRING_POOL_TYPE %#x, %#x" % [@string_pool.size, offset]
|
122
502
|
when 0x0002 # RES_TABLE_TYPE
|
503
|
+
#puts "RES_TABLE_TYPE"
|
123
504
|
@res_table = ResTableHeader.new(@data, offset)
|
124
505
|
offset += @res_table.header_size
|
125
|
-
when 0x0200
|
126
|
-
#
|
127
|
-
|
128
|
-
|
506
|
+
when 0x0200 # RES_TABLE_PACKAGE_TYPE
|
507
|
+
#puts "RES_TABLE_PACKAGE_TYPE"
|
508
|
+
pkg = ResTablePackage.new(@data, offset)
|
509
|
+
pkg.global_string_pool = @string_pool
|
510
|
+
offset += pkg.size
|
511
|
+
@packages[pkg.name] = pkg
|
129
512
|
else
|
130
513
|
raise "chunk type error: type:%#04x" % type
|
131
514
|
end
|
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.
|
8
|
+
s.version = "0.5.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 = "
|
12
|
+
s.date = "2013-01-04"
|
13
13
|
s.description = "static analysis tool for android apk"
|
14
14
|
s.email = "info@securebrain.co.jp"
|
15
15
|
s.extra_rdoc_files = [
|
@@ -44,6 +44,7 @@ Gem::Specification.new do |s|
|
|
44
44
|
"spec/data/sample_classes.dex",
|
45
45
|
"spec/data/sample_resources.arsc",
|
46
46
|
"spec/data/sample_resources_utf16.arsc",
|
47
|
+
"spec/data/str_resources.arsc",
|
47
48
|
"spec/dex/access_flag_spec.rb",
|
48
49
|
"spec/dex/dex_object_spec.rb",
|
49
50
|
"spec/dex/info_spec.rb",
|
@@ -58,7 +59,7 @@ Gem::Specification.new do |s|
|
|
58
59
|
s.homepage = "https://github.com/SecureBrain/ruby_apk/"
|
59
60
|
s.licenses = ["MIT"]
|
60
61
|
s.require_paths = ["lib"]
|
61
|
-
s.rubygems_version = "1.8.
|
62
|
+
s.rubygems_version = "1.8.23"
|
62
63
|
s.summary = "static analysis tool for android apk"
|
63
64
|
|
64
65
|
if s.respond_to? :specification_version then
|
Binary file
|
data/spec/resource_spec.rb
CHANGED
@@ -83,5 +83,74 @@ describe Android::Resource do
|
|
83
83
|
end
|
84
84
|
end
|
85
85
|
end
|
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 '#packages' do
|
91
|
+
subject {resource.packages}
|
92
|
+
it { should be_instance_of Hash}
|
93
|
+
it { subject.size.should eq 1 }
|
94
|
+
end
|
95
|
+
describe 'ResTablePackage' do
|
96
|
+
subject { resource.packages.first[1] }
|
97
|
+
it { subject.type(1).should eq 'attr' }
|
98
|
+
it { subject.type(4).should eq 'string' }
|
99
|
+
it { subject.name.should eq 'com.example.sample.ruby_apk' }
|
100
|
+
end
|
101
|
+
describe '#find' do
|
102
|
+
it '@0x7f040000 should return "sample.ruby_apk"' do
|
103
|
+
subject.find('@0x7f040000').should eq 'sample application'
|
104
|
+
end
|
105
|
+
it '@string/app_name should return "sample.ruby_apk"' do
|
106
|
+
subject.find('@string/app_name').should eq 'sample application'
|
107
|
+
end
|
108
|
+
it '@string/hello_world should return "Hello world!"' do
|
109
|
+
subject.find('@string/hello_world').should eq 'Hello world!'
|
110
|
+
end
|
111
|
+
it '@string/app_name should return "sample.ruby_apk"' do
|
112
|
+
subject.find('@string/app_name').should eq 'sample application'
|
113
|
+
end
|
114
|
+
it '@string/app_name with {:lang => "ja"} should return "サンプルアプリ"' do
|
115
|
+
subject.find('@string/app_name', :lang => 'ja').should eq 'サンプルアプリ'
|
116
|
+
end
|
117
|
+
it '@string/hello_world with {:lang => "ja"} should return nil' do
|
118
|
+
subject.find('@string/hello_world', :lang => 'ja').should be_nil
|
119
|
+
end
|
120
|
+
context 'assigns not exist string resource id' do
|
121
|
+
it { expect {subject.find('@string/not_exist') }.to raise_error Android::NotFoundError }
|
122
|
+
it { expect {subject.find('@0x7f040033') }.to raise_error Android::NotFoundError }
|
123
|
+
end
|
124
|
+
context 'assigns not string resource id' do
|
125
|
+
it { subject.find('@layout/activity_main').should be_nil }
|
126
|
+
end
|
127
|
+
context 'assigns invalid format id' do
|
128
|
+
it '"@xxyyxxyy" should raise ArgumentError' do
|
129
|
+
expect{ subject.find('@xxyyxxyy') }.to raise_error(ArgumentError)
|
130
|
+
end
|
131
|
+
it '"@0xff112233445566" should raise ArgumentError' do
|
132
|
+
expect{ subject.find('@0xff112233445566') }.to raise_error(ArgumentError)
|
133
|
+
end
|
134
|
+
end
|
135
|
+
end
|
136
|
+
describe '#res_readable_id' do
|
137
|
+
it { subject.res_readable_id('@0x7f040000').should eq '@string/app_name' }
|
138
|
+
context 'assigns invalid type' do
|
139
|
+
it { expect{subject.res_readable_id('@0x7f0f0000')}.to raise_error Android::NotFoundError }
|
140
|
+
end
|
141
|
+
context 'assigns invalid key' do
|
142
|
+
it { expect{subject.res_readable_id('@0x7f040033')}.to raise_error Android::NotFoundError }
|
143
|
+
end
|
144
|
+
end
|
145
|
+
describe '#res_hex_id' do
|
146
|
+
it { subject.res_hex_id('@string/app_name').should eq '@0x7f040000' }
|
147
|
+
context 'assigns invalid type' do
|
148
|
+
it { expect{subject.res_readable_id('@not_exist/xxxx')}.to raise_error Android::NotFoundError }
|
149
|
+
end
|
150
|
+
context 'assigns invalid key' do
|
151
|
+
it { expect{subject.res_readable_id('@string/not_exist')}.to raise_error Android::NotFoundError }
|
152
|
+
end
|
153
|
+
end
|
154
|
+
end
|
86
155
|
end
|
87
156
|
end
|
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.
|
4
|
+
version: 0.5.0
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -9,7 +9,7 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date:
|
12
|
+
date: 2013-01-04 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: rubyzip
|
@@ -158,6 +158,7 @@ files:
|
|
158
158
|
- spec/data/sample_classes.dex
|
159
159
|
- spec/data/sample_resources.arsc
|
160
160
|
- spec/data/sample_resources_utf16.arsc
|
161
|
+
- spec/data/str_resources.arsc
|
161
162
|
- spec/dex/access_flag_spec.rb
|
162
163
|
- spec/dex/dex_object_spec.rb
|
163
164
|
- spec/dex/info_spec.rb
|
@@ -183,7 +184,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
183
184
|
version: '0'
|
184
185
|
segments:
|
185
186
|
- 0
|
186
|
-
hash:
|
187
|
+
hash: 671022741244016522
|
187
188
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
188
189
|
none: false
|
189
190
|
requirements:
|
@@ -192,7 +193,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
192
193
|
version: '0'
|
193
194
|
requirements: []
|
194
195
|
rubyforge_project:
|
195
|
-
rubygems_version: 1.8.
|
196
|
+
rubygems_version: 1.8.23
|
196
197
|
signing_key:
|
197
198
|
specification_version: 3
|
198
199
|
summary: static analysis tool for android apk
|