ruby_android_apk 0.7.7.1
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 +7 -0
- data/.travis.yml +4 -0
- data/CHANGELOG.md +45 -0
- data/Gemfile +16 -0
- data/Gemfile.lock +73 -0
- data/LICENSE.txt +22 -0
- data/README.md +157 -0
- data/Rakefile +43 -0
- data/VERSION +1 -0
- data/lib/android/apk.rb +207 -0
- data/lib/android/axml_parser.rb +174 -0
- data/lib/android/dex.rb +92 -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/layout.rb +43 -0
- data/lib/android/manifest.rb +249 -0
- data/lib/android/resource.rb +531 -0
- data/lib/android/utils.rb +55 -0
- data/lib/ruby_apk.rb +7 -0
- data/spec/apk_spec.rb +332 -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 +41 -0
- data/spec/manifest_spec.rb +228 -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 +95 -0
- metadata +185 -0
@@ -0,0 +1,174 @@
|
|
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
|
+
#fix multi tag_end_doc
|
120
|
+
#break
|
121
|
+
when TAG_TEXT
|
122
|
+
text = REXML::Text.new(@strings[ns_id])
|
123
|
+
@parents.last.text = text
|
124
|
+
dummy = @io.read(4*1).unpack("V*") # skip 4bytes
|
125
|
+
when TAG_START_DOC, TAG_CDSECT, TAG_ENTITY_REF
|
126
|
+
# not implemented yet.
|
127
|
+
else
|
128
|
+
raise ReadError, "pos=%d(%#x)[tag:%#x]" % [last_pos, last_pos, tag]
|
129
|
+
end
|
130
|
+
end
|
131
|
+
end
|
132
|
+
|
133
|
+
# parse attribute of a element
|
134
|
+
def parse_attribute
|
135
|
+
ns_id, name_id, val_str_id, flags, val = @io.read(4*5).unpack("V*")
|
136
|
+
key = @strings[name_id]
|
137
|
+
unless ns_id == 0xFFFFFFFF
|
138
|
+
ns = @strings[ns_id]
|
139
|
+
prefix = ns.sub(/.*\//,'')
|
140
|
+
unless @ns.include? ns
|
141
|
+
@ns << ns
|
142
|
+
@doc.root.add_namespace(prefix, ns)
|
143
|
+
end
|
144
|
+
key = "#{prefix}:#{key}"
|
145
|
+
end
|
146
|
+
value = convert_value(val_str_id, flags, val)
|
147
|
+
return key, value
|
148
|
+
end
|
149
|
+
|
150
|
+
|
151
|
+
def convert_value(val_str_id, flags, val)
|
152
|
+
unless val_str_id == 0xFFFFFFFF
|
153
|
+
value = @strings[val_str_id]
|
154
|
+
else
|
155
|
+
type = flags >> 24
|
156
|
+
case type
|
157
|
+
when VAL_TYPE_NULL
|
158
|
+
value = nil
|
159
|
+
when VAL_TYPE_REFERENCE
|
160
|
+
value = "@%#x" % val # refered resource id.
|
161
|
+
when VAL_TYPE_INT_DEC
|
162
|
+
value = val
|
163
|
+
when VAL_TYPE_INT_HEX
|
164
|
+
value = "%#x" % val
|
165
|
+
when VAL_TYPE_INT_BOOLEAN
|
166
|
+
value = ((val == 0xFFFFFFFF) || (val==1)) ? true : false
|
167
|
+
else
|
168
|
+
value = "[%#x, flag=%#x]" % [val, flags]
|
169
|
+
end
|
170
|
+
end
|
171
|
+
end
|
172
|
+
end
|
173
|
+
|
174
|
+
end
|
data/lib/android/dex.rb
ADDED
@@ -0,0 +1,92 @@
|
|
1
|
+
require_relative 'dex/dex_object'
|
2
|
+
require_relative 'dex/info'
|
3
|
+
require_relative 'dex/access_flag'
|
4
|
+
require_relative 'dex/utils'
|
5
|
+
|
6
|
+
module Android
|
7
|
+
# parsed dex object
|
8
|
+
# @see http://source.android.com/devices/tech/dalvik/dex-format.html
|
9
|
+
# @attr_reader strings [Array<String>] strings in dex file.
|
10
|
+
class Dex
|
11
|
+
# @return [Dex::Header] dex header information
|
12
|
+
attr_reader :header
|
13
|
+
alias :h :header
|
14
|
+
|
15
|
+
# @return [String] dex binary data
|
16
|
+
attr_reader :data
|
17
|
+
# @return [Array<Dex::ClassInfo>] array of class information
|
18
|
+
attr_reader :classes
|
19
|
+
|
20
|
+
attr_reader :field_ids, :method_ids, :proto_ids
|
21
|
+
# @param [String] data dex binary data
|
22
|
+
def initialize(data)
|
23
|
+
@data = data
|
24
|
+
@data.force_encoding(Encoding::ASCII_8BIT)
|
25
|
+
@classes = []
|
26
|
+
parse()
|
27
|
+
end
|
28
|
+
|
29
|
+
def strings
|
30
|
+
@strings ||= @string_data_items.map{|item| item.to_s }
|
31
|
+
end
|
32
|
+
|
33
|
+
def inspect
|
34
|
+
"<Android::Dex @classes => #{@classes.size}, datasize => #{@data.size}>"
|
35
|
+
end
|
36
|
+
|
37
|
+
|
38
|
+
# @private
|
39
|
+
TYPE_DESCRIPTOR = {
|
40
|
+
'V' => 'void',
|
41
|
+
'Z' => 'boolean',
|
42
|
+
'B' => 'byte',
|
43
|
+
'S' => 'short',
|
44
|
+
'C' => 'short',
|
45
|
+
'I' => 'int',
|
46
|
+
'J' => 'long',
|
47
|
+
'F' => 'float',
|
48
|
+
'D' => 'double'
|
49
|
+
}
|
50
|
+
|
51
|
+
|
52
|
+
def type_resolve(typeid)
|
53
|
+
type = strings[@type_ids[typeid]]
|
54
|
+
if type.start_with? '['
|
55
|
+
type = type[1..type.size]
|
56
|
+
return TYPE_DESCRIPTOR.fetch(type, type) + "[]" # TODO: recursive
|
57
|
+
else
|
58
|
+
return TYPE_DESCRIPTOR.fetch(type, type)
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
|
63
|
+
private
|
64
|
+
def parse
|
65
|
+
@header = DexObject::Header.new(@data)
|
66
|
+
@map_list = DexObject::MapList.new(@data, h[:map_off])
|
67
|
+
|
68
|
+
# parse strings
|
69
|
+
@string_ids = DexObject::StringIdItem.new(@data, h[:string_ids_off], h[:string_ids_size])
|
70
|
+
@string_data_items = []
|
71
|
+
@string_ids[:string_data_off].each { |off| @string_data_items << DexObject::StringDataItem.new(@data, off) }
|
72
|
+
|
73
|
+
@type_ids = DexObject::TypeIdItem.new(@data, h[:type_ids_off], h[:type_ids_size])
|
74
|
+
@proto_ids = ids_list_array(DexObject::ProtoIdItem, h[:proto_ids_off], h[:proto_ids_size])
|
75
|
+
@field_ids = ids_list_array(DexObject::FieldIdItem, h[:field_ids_off], h[:field_ids_size])
|
76
|
+
@method_ids = ids_list_array(DexObject::MethodIdItem, h[:method_ids_off], h[:method_ids_size])
|
77
|
+
@class_defs = ids_list_array(DexObject::ClassDefItem, h[:class_defs_off], h[:class_defs_size])
|
78
|
+
|
79
|
+
@classes = []
|
80
|
+
@class_defs.each do |cls_def|
|
81
|
+
@classes << ClassInfo.new(cls_def, self)
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
def ids_list_array(cls, offset, size)
|
86
|
+
ret_array = []
|
87
|
+
size.times { |i| ret_array << cls.new(@data, offset + cls.size * i) }
|
88
|
+
ret_array
|
89
|
+
end
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
@@ -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
|
+
|
@@ -0,0 +1,475 @@
|
|
1
|
+
|
2
|
+
module Android
|
3
|
+
class Dex
|
4
|
+
# parsing dex object
|
5
|
+
# @see http://source.android.com/devices/tech/dalvik/dex-format.html
|
6
|
+
class DexObject
|
7
|
+
# @return [Integer] object size
|
8
|
+
attr_reader :size
|
9
|
+
|
10
|
+
def initialize(data, offset)
|
11
|
+
@data = data
|
12
|
+
@offset = offset
|
13
|
+
|
14
|
+
@params = {}
|
15
|
+
@parsing_off = 0 # parsing offset
|
16
|
+
parse()
|
17
|
+
@size = @parsing_off
|
18
|
+
end
|
19
|
+
|
20
|
+
# returns symbol keys
|
21
|
+
# @return [Array<Symbol>] header key
|
22
|
+
def symbols
|
23
|
+
@params.keys
|
24
|
+
end
|
25
|
+
|
26
|
+
# @param [Symbol] sym should be included in #symbols
|
27
|
+
# @return [Object] dex header value which is related with sym
|
28
|
+
def [](sym)
|
29
|
+
@params[sym.to_sym]
|
30
|
+
end
|
31
|
+
|
32
|
+
# @return [String]
|
33
|
+
def inspect
|
34
|
+
str = "<#{self.class}\n"
|
35
|
+
@params.each do |key,val|
|
36
|
+
str.concat " #{key}: #{val}\n"
|
37
|
+
end
|
38
|
+
str.concat '>'
|
39
|
+
end
|
40
|
+
|
41
|
+
private
|
42
|
+
def parse
|
43
|
+
raise 'this method should be overloaded.'
|
44
|
+
end
|
45
|
+
|
46
|
+
def read_value(type)
|
47
|
+
types = {
|
48
|
+
:byte => [1, 'c'],
|
49
|
+
:ubyte => [1, 'C'],
|
50
|
+
:short => [2, 's'], #ugh!:depend on machine endian
|
51
|
+
:ushort => [2, 'v'],
|
52
|
+
:int => [4, 'i'], #ugh!:depend on machine endian
|
53
|
+
:uint => [4, 'V'],
|
54
|
+
:long => [8, 'q'],
|
55
|
+
:ulong => [8, 'Q'],
|
56
|
+
}
|
57
|
+
len, pack_str = types.fetch(type)
|
58
|
+
value = @data[@offset+@parsing_off, len].unpack(pack_str)[0]
|
59
|
+
@parsing_off += len
|
60
|
+
return value
|
61
|
+
end
|
62
|
+
|
63
|
+
# read short int from data buffer
|
64
|
+
# @return [Integer] short value
|
65
|
+
def read_sleb
|
66
|
+
value, len = Dex::sleb128(@data, @offset + @parsing_off)
|
67
|
+
@parsing_off += len
|
68
|
+
value
|
69
|
+
end
|
70
|
+
# read integer from data buffer
|
71
|
+
# @return [Integer] integer value
|
72
|
+
def read_uleb
|
73
|
+
value, len = Dex::uleb128(@data, @offset + @parsing_off)
|
74
|
+
@parsing_off += len
|
75
|
+
value
|
76
|
+
end
|
77
|
+
# read integer from data buffer and plus 1
|
78
|
+
# @return [Integer] integer value
|
79
|
+
def read_uleb128p1
|
80
|
+
value, len = Dex::uleb128p1(@data, @offset + @parsing_off)
|
81
|
+
@parsing_off += len
|
82
|
+
value
|
83
|
+
end
|
84
|
+
# read various values from data buffer as array
|
85
|
+
# @param [Symbol] type
|
86
|
+
# @param [Integer] size num of data
|
87
|
+
# @return [Array] value array
|
88
|
+
def read_value_array(type, size)
|
89
|
+
ret_array = []
|
90
|
+
size.times { ret_array << read_value(type) }
|
91
|
+
ret_array
|
92
|
+
end
|
93
|
+
# read class values from data buffer as array
|
94
|
+
# @param [Class] cls target class
|
95
|
+
# @param [Integer] size num of data
|
96
|
+
# @return [Array<cls>] object array
|
97
|
+
def read_class_array(cls, size)
|
98
|
+
ret_array = []
|
99
|
+
size.times do
|
100
|
+
item = cls.new(@data, @offset + @parsing_off)
|
101
|
+
ret_array << item
|
102
|
+
@parsing_off += item.size
|
103
|
+
end
|
104
|
+
ret_array
|
105
|
+
end
|
106
|
+
|
107
|
+
public
|
108
|
+
# header_item
|
109
|
+
# @see http://source.android.com/devices/tech/dalvik/dex-format.html
|
110
|
+
class Header < DexObject
|
111
|
+
def initialize(data)
|
112
|
+
super(data, 0)
|
113
|
+
end
|
114
|
+
|
115
|
+
private
|
116
|
+
def parse
|
117
|
+
@params[:magic] = @data[0, 8]
|
118
|
+
@parsing_off += 8
|
119
|
+
@params[:checksum] = read_value(:uint)
|
120
|
+
@params[:signature] = @data[12, 20]
|
121
|
+
@parsing_off += 20
|
122
|
+
[
|
123
|
+
:file_size, :header_size, :endian_tag, :link_size, :link_off, :map_off,
|
124
|
+
:string_ids_size, :string_ids_off, :type_ids_size, :type_ids_off,
|
125
|
+
:proto_ids_size, :proto_ids_off, :field_ids_size, :field_ids_off,
|
126
|
+
:method_ids_size, :method_ids_off, :class_defs_size, :class_defs_off,
|
127
|
+
:data_size, :data_off
|
128
|
+
].each do |key|
|
129
|
+
@params[key] = read_value(:uint)
|
130
|
+
end
|
131
|
+
end
|
132
|
+
end
|
133
|
+
|
134
|
+
# map_list
|
135
|
+
# @see http://source.android.com/devices/tech/dalvik/dex-format.html
|
136
|
+
class MapList < DexObject
|
137
|
+
private
|
138
|
+
def parse
|
139
|
+
@params[:size] = read_value(:uint)
|
140
|
+
@params[:list] = read_class_array(MapItem, @params[:size])
|
141
|
+
end
|
142
|
+
end
|
143
|
+
|
144
|
+
# map_item
|
145
|
+
# @see http://source.android.com/devices/tech/dalvik/dex-format.html
|
146
|
+
class MapItem < DexObject
|
147
|
+
private
|
148
|
+
def parse
|
149
|
+
@params[:type] = read_value(:short)
|
150
|
+
@params[:unused] = read_value(:short)
|
151
|
+
@params[:size] = read_value(:uint)
|
152
|
+
@params[:offset] = read_value(:uint)
|
153
|
+
end
|
154
|
+
end
|
155
|
+
|
156
|
+
# id_list
|
157
|
+
# @see http://source.android.com/devices/tech/dalvik/dex-format.html
|
158
|
+
class IdsList < DexObject
|
159
|
+
attr_reader :ids_size
|
160
|
+
def initialize(data, off, ids_size)
|
161
|
+
@ids_size = ids_size
|
162
|
+
super(data, off)
|
163
|
+
end
|
164
|
+
end
|
165
|
+
|
166
|
+
# string_id_item
|
167
|
+
# @see http://source.android.com/devices/tech/dalvik/dex-format.html
|
168
|
+
class StringIdItem < IdsList
|
169
|
+
private
|
170
|
+
def parse
|
171
|
+
@params[:string_data_off] = read_value_array(:uint, @ids_size)
|
172
|
+
end
|
173
|
+
end
|
174
|
+
|
175
|
+
# string_data_item
|
176
|
+
# @see http://source.android.com/devices/tech/dalvik/dex-format.html
|
177
|
+
class StringDataItem < DexObject
|
178
|
+
def to_s
|
179
|
+
@params[:data]
|
180
|
+
end
|
181
|
+
private
|
182
|
+
def mutf8_to_utf8(data, off, ulen)
|
183
|
+
mi = 0 # index of mutf8 data
|
184
|
+
codepoints = []
|
185
|
+
while ulen > 0 do
|
186
|
+
b0 = data[off + mi].ord
|
187
|
+
bu = (b0 & 0xf0) # b0's upper nibble
|
188
|
+
if (b0 & 0x80) == 0 # single byte encoding (0b0xxx_xxxx)
|
189
|
+
c = b0
|
190
|
+
mi += 1
|
191
|
+
ulen -= 1
|
192
|
+
elsif bu == 0xc0 || bu == 0xd0 # two-byte encoding (0b110x_xxxx)
|
193
|
+
b1 = data[off + mi + 1].ord
|
194
|
+
c = (b0 & 0x1f) << 6 | (b1 & 0x3f)
|
195
|
+
mi += 2
|
196
|
+
ulen -= 1
|
197
|
+
elsif bu == 0xe0 # three-byte encoding (0b1110_xxxx)
|
198
|
+
b1 = data[off + mi + 1].ord
|
199
|
+
b2 = data[off + mi + 2].ord
|
200
|
+
c = (b0 & 0x0f) << 12 | (b1 & 0x3f) << 6 | (b2 & 0x3f)
|
201
|
+
mi += 3
|
202
|
+
ulen -= 1
|
203
|
+
if 0xD800 <= c && c <= 0xDBFF # this must be a surrogate pair
|
204
|
+
b4 = data[off + mi + 1].ord
|
205
|
+
b5 = data[off + mi + 2].ord
|
206
|
+
c = ((b1 & 0x0f) + 1) << 16 | (b2 & 0x3f) << 10 | (b4 & 0x0f) << 6 | (b5 & 0x3f)
|
207
|
+
mi += 3
|
208
|
+
ulen -= 1
|
209
|
+
end
|
210
|
+
else
|
211
|
+
STDERR.puts "unsupported byte: 0x#{'%02X' % b0} @#{mi}"
|
212
|
+
c = 0
|
213
|
+
mi += 1
|
214
|
+
next
|
215
|
+
end
|
216
|
+
if c != 0
|
217
|
+
codepoints << c
|
218
|
+
end
|
219
|
+
end
|
220
|
+
codepoints.pack("U*")
|
221
|
+
end
|
222
|
+
def parse
|
223
|
+
@params[:utf16_size] = read_uleb
|
224
|
+
@params[:data] = mutf8_to_utf8(@data, @offset + @parsing_off, @params[:utf16_size])
|
225
|
+
end
|
226
|
+
end
|
227
|
+
|
228
|
+
# type_id_item
|
229
|
+
# @see http://source.android.com/devices/tech/dalvik/dex-format.html
|
230
|
+
class TypeIdItem < IdsList
|
231
|
+
def [](idx)
|
232
|
+
raise ArgumentError if idx >= @params[:descriptor_idx].size or idx < 0
|
233
|
+
@params[:descriptor_idx][idx]
|
234
|
+
end
|
235
|
+
|
236
|
+
private
|
237
|
+
def parse
|
238
|
+
@params[:descriptor_idx] = read_value_array(:uint, @ids_size)
|
239
|
+
end
|
240
|
+
end
|
241
|
+
|
242
|
+
# proto_id_item
|
243
|
+
# @see http://source.android.com/devices/tech/dalvik/dex-format.html
|
244
|
+
class ProtoIdItem < DexObject
|
245
|
+
# return parse data size
|
246
|
+
# @return bytes
|
247
|
+
# @note this method for DexObject#read_class_array (private method)
|
248
|
+
def self.size
|
249
|
+
4 * 3
|
250
|
+
end
|
251
|
+
private
|
252
|
+
def parse
|
253
|
+
@params[:shorty_idx] = read_value(:uint)
|
254
|
+
@params[:return_type_idx] = read_value(:uint)
|
255
|
+
@params[:parameters_off] = read_value(:uint)
|
256
|
+
end
|
257
|
+
end
|
258
|
+
|
259
|
+
# field_id_item
|
260
|
+
# @see http://source.android.com/devices/tech/dalvik/dex-format.html
|
261
|
+
class FieldIdItem < DexObject
|
262
|
+
# return parse data size
|
263
|
+
# @return bytes
|
264
|
+
# @note this method for DexObject#read_class_array (private method)
|
265
|
+
def self.size
|
266
|
+
2 * 2 + 4
|
267
|
+
end
|
268
|
+
private
|
269
|
+
def parse
|
270
|
+
@params[:class_idx] = read_value(:ushort)
|
271
|
+
@params[:type_idx] = read_value(:ushort)
|
272
|
+
@params[:name_idx] = read_value(:uint)
|
273
|
+
end
|
274
|
+
end
|
275
|
+
|
276
|
+
# method_id_item
|
277
|
+
# @see http://source.android.com/devices/tech/dalvik/dex-format.html
|
278
|
+
class MethodIdItem < DexObject
|
279
|
+
# return parse data size
|
280
|
+
# @return bytes
|
281
|
+
# @note this method for DexObject#read_class_array (private method)
|
282
|
+
def self.size
|
283
|
+
2 * 2 + 4
|
284
|
+
end
|
285
|
+
def parse
|
286
|
+
@params[:class_idx] = read_value(:ushort)
|
287
|
+
@params[:proto_idx] = read_value(:ushort)
|
288
|
+
@params[:name_idx] = read_value(:uint)
|
289
|
+
end
|
290
|
+
end
|
291
|
+
|
292
|
+
# class_def_item
|
293
|
+
# @see http://source.android.com/devices/tech/dalvik/dex-format.html
|
294
|
+
# @!attribute [r] class_data_item
|
295
|
+
# @return [ClassDataItem] class_data_item of this class
|
296
|
+
class ClassDefItem < DexObject
|
297
|
+
# @return [Integer] bytes
|
298
|
+
def self.size
|
299
|
+
4 * 8
|
300
|
+
end
|
301
|
+
|
302
|
+
def class_data_item
|
303
|
+
# description of class_data_off of class_def_item.
|
304
|
+
# offset from the start of the file to the associated class data
|
305
|
+
# for this item, or 0 if there is no class data for this class.
|
306
|
+
if @params[:class_data_off] != 0
|
307
|
+
@class_data_item ||= ClassDataItem.new(@data, @params[:class_data_off])
|
308
|
+
else
|
309
|
+
nil
|
310
|
+
end
|
311
|
+
end
|
312
|
+
|
313
|
+
private
|
314
|
+
def parse
|
315
|
+
@params[:class_idx] = read_value(:uint)
|
316
|
+
@params[:access_flags] = read_value(:uint)
|
317
|
+
@params[:superclass_idx] = read_value(:uint)
|
318
|
+
@params[:interfaces_off] = read_value(:uint)
|
319
|
+
@params[:source_file_idx] = read_value(:uint)
|
320
|
+
@params[:annotations_off] = read_value(:uint)
|
321
|
+
@params[:class_data_off] = read_value(:uint)
|
322
|
+
@params[:static_values_off] = read_value(:uint) # TODO: not implemented encoded_array_item
|
323
|
+
end
|
324
|
+
end
|
325
|
+
|
326
|
+
# class_data_item
|
327
|
+
# @see http://source.android.com/devices/tech/dalvik/dex-format.html
|
328
|
+
class ClassDataItem < DexObject
|
329
|
+
private
|
330
|
+
def parse
|
331
|
+
@params[:static_fields_size] = read_uleb
|
332
|
+
@params[:instance_fields_size] = read_uleb
|
333
|
+
@params[:direct_methods_size] = read_uleb
|
334
|
+
@params[:virtual_methods_size] = read_uleb
|
335
|
+
@params[:static_fields] = read_class_array(EncodedField, @params[:static_fields_size])
|
336
|
+
@params[:instance_fields] = read_class_array(EncodedField, @params[:instance_fields_size])
|
337
|
+
@params[:direct_methods] = read_class_array(EncodedMethod, @params[:direct_methods_size])
|
338
|
+
@params[:virtual_methods] = read_class_array(EncodedMethod, @params[:virtual_methods_size])
|
339
|
+
end
|
340
|
+
end
|
341
|
+
|
342
|
+
# encoded_field
|
343
|
+
# @see http://source.android.com/devices/tech/dalvik/dex-format.html
|
344
|
+
class EncodedField < DexObject
|
345
|
+
private
|
346
|
+
def parse
|
347
|
+
@params[:field_idx_diff] = read_uleb
|
348
|
+
@params[:access_flags] = read_uleb
|
349
|
+
end
|
350
|
+
end
|
351
|
+
|
352
|
+
# encoded_method
|
353
|
+
# @see http://source.android.com/devices/tech/dalvik/dex-format.html
|
354
|
+
# @!attribute [r] code_item
|
355
|
+
# @return [CodeItem] code_item of the method
|
356
|
+
class EncodedMethod < DexObject
|
357
|
+
def code_item
|
358
|
+
# description of code_off in code_data_item.
|
359
|
+
# offset from the start of the file to the code structure for this method,
|
360
|
+
# or 0 if this method is either abstract or native.
|
361
|
+
unless @params[:code_off] == 0
|
362
|
+
@code_item ||= CodeItem.new(@data, @params[:code_off])
|
363
|
+
else
|
364
|
+
nil
|
365
|
+
end
|
366
|
+
end
|
367
|
+
|
368
|
+
private
|
369
|
+
def parse
|
370
|
+
@params[:method_idx_diff] = read_uleb
|
371
|
+
@params[:access_flags] = read_uleb
|
372
|
+
@params[:code_off] = read_uleb
|
373
|
+
end
|
374
|
+
end
|
375
|
+
|
376
|
+
|
377
|
+
# type_list
|
378
|
+
# @see http://source.android.com/devices/tech/dalvik/dex-format.html
|
379
|
+
class TypeList < DexObject
|
380
|
+
private
|
381
|
+
def parse
|
382
|
+
@params[:size] = read_value(:uint)
|
383
|
+
@params[:list] = read_value_array(:ushort, @params[:size])
|
384
|
+
end
|
385
|
+
end
|
386
|
+
|
387
|
+
# code_item
|
388
|
+
# @see http://source.android.com/devices/tech/dalvik/dex-format.html
|
389
|
+
# @!attribute [r] debug_info_item
|
390
|
+
# @return [DebugInfoItem] debug_info_item of this code
|
391
|
+
class CodeItem < DexObject
|
392
|
+
def debug_info_item
|
393
|
+
unless @params[:debug_info_off] == 0
|
394
|
+
@debug_info_item ||= DebugInfoItem.new(@data, @params[:debug_info_off])
|
395
|
+
else
|
396
|
+
nil
|
397
|
+
end
|
398
|
+
end
|
399
|
+
|
400
|
+
private
|
401
|
+
def parse
|
402
|
+
@params[:registers_size] = read_value(:ushort)
|
403
|
+
@params[:ins_size] = read_value(:ushort)
|
404
|
+
@params[:outs_size] = read_value(:ushort)
|
405
|
+
@params[:tries_size] = read_value(:ushort)
|
406
|
+
@params[:debug_info_off] = read_value(:uint)
|
407
|
+
@params[:insns_size] = read_value(:uint) # size of the instructions list
|
408
|
+
@params[:insns] = read_value_array(:ushort, @params[:insns_size])
|
409
|
+
read_value(:ushort) if ((@params[:insns_size] % 2) == 1) # for four-byte aligned
|
410
|
+
if @params[:tries_size] > 0
|
411
|
+
# This element is only present if tries_size is non-zero.
|
412
|
+
@params[:tries] = read_class_array(TryItem, @params[:tries_size])
|
413
|
+
# This element is only present if tries_size is non-zero.
|
414
|
+
@params[:handlers] = EncodedCatchHandlerList.new(@data, @offset + @parsing_off)
|
415
|
+
@parsing_off += @params[:handlers].size
|
416
|
+
end
|
417
|
+
end
|
418
|
+
end
|
419
|
+
|
420
|
+
# try_item
|
421
|
+
# @see http://source.android.com/devices/tech/dalvik/dex-format.html
|
422
|
+
class TryItem < DexObject
|
423
|
+
private
|
424
|
+
def parse
|
425
|
+
@params[:start_addr] = read_value(:uint)
|
426
|
+
@params[:insn_count] = read_value(:ushort)
|
427
|
+
@params[:handler_off] = read_value(:ushort)
|
428
|
+
end
|
429
|
+
end
|
430
|
+
|
431
|
+
# encoded_catch_handler_list
|
432
|
+
# @see http://source.android.com/devices/tech/dalvik/dex-format.html
|
433
|
+
class EncodedCatchHandlerList < DexObject
|
434
|
+
private
|
435
|
+
def parse
|
436
|
+
@params[:size] = read_uleb
|
437
|
+
@params[:list] = read_class_array(EncodedCatchHandler, @params[:size])
|
438
|
+
end
|
439
|
+
end
|
440
|
+
|
441
|
+
# encoded_catch_handler
|
442
|
+
# @see http://source.android.com/devices/tech/dalvik/dex-format.html
|
443
|
+
class EncodedCatchHandler < DexObject
|
444
|
+
private
|
445
|
+
def parse
|
446
|
+
@params[:size] = read_sleb
|
447
|
+
@params[:list] = read_class_array(EncodedTypeAddrPair, @params[:size].abs)
|
448
|
+
@params[:catch_all_addr] = read_uleb if @params[:size] <= 0
|
449
|
+
end
|
450
|
+
end
|
451
|
+
|
452
|
+
# encoded_type_addr_pair
|
453
|
+
# @see http://source.android.com/devices/tech/dalvik/dex-format.html
|
454
|
+
class EncodedTypeAddrPair < DexObject
|
455
|
+
private
|
456
|
+
def parse
|
457
|
+
@params[:type_idx] = read_uleb
|
458
|
+
@params[:addr] = read_uleb
|
459
|
+
end
|
460
|
+
end
|
461
|
+
|
462
|
+
# debug_info_item
|
463
|
+
# @see http://source.android.com/devices/tech/dalvik/dex-format.html
|
464
|
+
class DebugInfoItem < DexObject
|
465
|
+
private
|
466
|
+
def parse
|
467
|
+
@params[:line_start] = read_uleb
|
468
|
+
@params[:parameters_size] = read_uleb
|
469
|
+
@params[:parameter_names] = []
|
470
|
+
@params[:parameters_size].times { @params[:parameter_names] << read_uleb128p1 }
|
471
|
+
end
|
472
|
+
end
|
473
|
+
end
|
474
|
+
end
|
475
|
+
end
|