android_parser 2.4.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,151 @@
1
+ require_relative 'dex_object'
2
+
3
+ module Android
4
+ class Dex
5
+ # class information in dex
6
+ # @!attribute [r] name
7
+ # @return [String] class name
8
+ # @!attribute [r] super_class
9
+ # @return [String] super class name
10
+ class ClassInfo
11
+ # no index flag
12
+ NO_INDEX = 0xffffffff
13
+
14
+ # @return [ClassAccessFlag]
15
+ attr_reader :access_flags
16
+ # @return [Array<FieldInfo>] static fields
17
+ attr_reader :static_fields
18
+ # @return [Array<FieldInfo>] instance fields
19
+ attr_reader :instance_fields
20
+ # @return [Array<MethodInfo>] direct methods
21
+ attr_reader :direct_methods
22
+ # @return [Array<MethodInfo>] virtual methods
23
+ attr_reader :virtual_methods
24
+
25
+ # @return [DexObject::ClassDataItem]
26
+ attr_reader :class_data
27
+ # @return [DexObject::ClassDefItem]
28
+ attr_reader :class_def
29
+
30
+ def name
31
+ @dex.type_resolve(@class_def[:class_idx])
32
+ end
33
+ def super_class
34
+ if @class_def[:superclass_idx] != NO_INDEX
35
+ @super_class = @dex.type_resolve(@class_def[:superclass_idx])
36
+ else
37
+ nil
38
+ end
39
+ end
40
+ # @param [Dex::ClassDefItem] class_def
41
+ # @param [Dex] dex dex class instance
42
+ def initialize(class_def, dex)
43
+ @class_def = class_def
44
+ @dex = dex
45
+ @access_flags = ClassAccessFlag.new(@class_def[:access_flags])
46
+ @class_data = @class_def.class_data_item
47
+ @static_fields = @instance_fields = @direct_methods = @virtual_methods = []
48
+ unless @class_data.nil?
49
+ @static_fields = cls2info(@class_data[:static_fields], FieldInfo, :field_idx_diff)
50
+ @instance_fields = cls2info(@class_data[:instance_fields], FieldInfo, :field_idx_diff)
51
+ @direct_methods = cls2info(@class_data[:direct_methods], MethodInfo, :method_idx_diff)
52
+ @virtual_methods = cls2info(@class_data[:virtual_methods], MethodInfo, :method_idx_diff)
53
+ end
54
+ end
55
+
56
+ # @return [String] class difinition
57
+ def definition
58
+ ret = "#{access_flags} class #{name}"
59
+ super_class.nil? ? ret : ret + " extends #{super_class}"
60
+ end
61
+
62
+ private
63
+ def cls2info(arr, cls, idx_key)
64
+ idx = 0
65
+ ret = []
66
+ arr.each do |item|
67
+ idx += item[idx_key]
68
+ ret << cls.new(item, idx, @dex)
69
+ end
70
+ ret
71
+ end
72
+ end
73
+
74
+ # field info object
75
+ # @!attribute [r] name
76
+ # @return [String] field name
77
+ # @!attribute [r] type
78
+ # @return [String] field type
79
+ class FieldInfo
80
+ # @return [ClassAccessFlag]
81
+ attr_reader :access_flags
82
+
83
+ def name
84
+ @dex.strings[@dex.field_ids[@field_id][:name_idx]]
85
+ end
86
+ def type
87
+ @dex.type_resolve(@dex.field_ids[@field_id][:type_idx])
88
+ end
89
+ def initialize(encoded_field, field_id, dex)
90
+ @dex = dex
91
+ @encoded_field = encoded_field
92
+ @field_id = field_id
93
+ @access_flags = ClassAccessFlag.new(encoded_field[:access_flags])
94
+ end
95
+
96
+ # @return [String] field definition
97
+ def definition
98
+ "#{@access_flags.to_s} #{type} #{name}"
99
+ end
100
+ end
101
+
102
+ # method info object
103
+ # @!attribute [r] name
104
+ # @return [String] method name
105
+ # @!attribute [r] ret_type
106
+ # @return [String] return type of the method
107
+ # @!attribute [r] parameters
108
+ # @return [Array<String>] method parameters
109
+ class MethodInfo
110
+ # @return [MethodAccessFlag]
111
+ attr_reader :access_flags
112
+
113
+ def initialize(encoded_method, method_id, dex)
114
+ @encoded_method = encoded_method
115
+ @method_id = method_id
116
+ @dex = dex
117
+ @access_flags = MethodAccessFlag.new(encoded_method[:access_flags])
118
+ end
119
+ def name
120
+ @dex.strings[@dex.method_ids[@method_id][:name_idx]]
121
+ end
122
+ def ret_type
123
+ @dex.type_resolve(proto[:return_type_idx])
124
+ end
125
+ def parameters
126
+ unless proto[:parameters_off] == 0
127
+ list = DexObject::TypeList.new(@dex.data, proto[:parameters_off])
128
+ list[:list].map { |item| @dex.type_resolve(item) }
129
+ else
130
+ []
131
+ end
132
+ end
133
+
134
+ # @return [String] method definition string
135
+ def definition
136
+ "#{access_flags.to_s} #{ret_type} #{name}(#{parameters.join(', ')});"
137
+ end
138
+
139
+ # @return [DexObject::CodeItem]
140
+ def code_item
141
+ @encoded_method.code_item
142
+ end
143
+
144
+ private
145
+ def proto
146
+ @dex.proto_ids[@dex.method_ids[@method_id][:proto_idx]]
147
+ end
148
+ end
149
+
150
+ end
151
+ end
@@ -0,0 +1,45 @@
1
+ module Android
2
+ class Dex
3
+ class << self
4
+ # parse uleb128(unsigned integer) data
5
+ # @param [String] data target byte data
6
+ # @param [Integer] offset
7
+ # @return [Integer, Integer] parsed value and parsed byte length
8
+ # @see http://en.wikipedia.org/wiki/LEB128
9
+ def uleb128(data, offset=0)
10
+ result = 0
11
+ shift = 0
12
+ d = data[offset...data.size]
13
+ (0..4).each do |i|
14
+ byte = d.getbyte(i)
15
+ result |= ((byte & 0x7f) << shift)
16
+ return result, i+1 if ((byte & 0x80) == 0)
17
+ shift += 7
18
+ end
19
+ end
20
+ # parse uleb128 + 1 data
21
+ # @param [String] data target byte data
22
+ # @param [Integer] offset
23
+ # @return [Integer, Integer] parsed value and parsed byte length
24
+ def uleb128p1(data, offset=0)
25
+ ret, len = self.uleb128(data, offset)
26
+ return (ret - 1), len
27
+ end
28
+ # parse sleb128(signed integer) data
29
+ # @param [String] data target byte data
30
+ # @param [Integer] offset
31
+ # @return [Integer, Integer] parsed value and parsed byte length
32
+ def sleb128(data, offset=0)
33
+ result = 0
34
+ shift = 0
35
+ d = data[offset...data.size]
36
+ (0..4).each do |i|
37
+ byte = d.getbyte(i)
38
+ result |=((byte & 0x7F) << shift)
39
+ return (0 == (byte & 0x40) ? result : result - (1 << (shift+7))), i+1 if ((byte & 0x80) == 0)
40
+ shift += 7
41
+ end
42
+ end
43
+ end
44
+ end
45
+ end
@@ -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,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
+
@@ -0,0 +1,350 @@
1
+ require 'rexml/document'
2
+
3
+ module Android
4
+ # parsed AndroidManifest.xml class
5
+ # @see http://developer.android.com/guide/topics/manifest/manifest-intro.html
6
+ class Manifest
7
+ APPLICATION_TAG = '/manifest/application'
8
+
9
+ # <activity>, <service>, <receiver> or <provider> element in <application> element of the manifest file.
10
+ class Component
11
+ # component types
12
+ TYPES = ['activity', 'activity-alias', 'service', 'receiver', 'provider', 'application']
13
+
14
+ # the element is valid Component element or not
15
+ # @param [REXML::Element] elem xml element
16
+ # @return [Boolean]
17
+ def self.valid?(elem)
18
+ TYPES.include?(elem.name.downcase)
19
+ rescue => e
20
+ false
21
+ end
22
+
23
+ # @return [String] type string in TYPES
24
+ attr_reader :type
25
+ # @return [String] component name
26
+ attr_reader :name
27
+ # @return [String] icon id - use apk.icon_by_id(icon_id) to retrieve it's corresponding data.
28
+ attr_reader :icon_id
29
+ # @return [Array<Manifest::IntentFilters<Manifest::IntentFilter>>]
30
+ attr_reader :intent_filters
31
+ # @return [Array<Manifest::Meta>]
32
+ attr_reader :metas
33
+ # @return [REXML::Element]
34
+ attr_reader :elem
35
+
36
+
37
+ # @param [REXML::Element] elem target element
38
+ # @raise [ArgumentError] when elem is invalid.
39
+ def initialize(elem)
40
+ raise ArgumentError unless Component.valid?(elem)
41
+ @elem = elem
42
+ @type = elem.name
43
+ @name = elem.attributes['name']
44
+ @icon_id = elem.attributes['icon']
45
+
46
+ @intent_filters = []
47
+ unless elem.elements['intent-filter'].nil?
48
+ elem.each_element('intent-filter') do |filters|
49
+ intent_filters = []
50
+ filters.elements.each do |filter|
51
+ next unless IntentFilter.valid?(filter)
52
+
53
+ intent_filters << IntentFilter.parse(filter)
54
+ end
55
+ @intent_filters << intent_filters
56
+ end
57
+ end
58
+
59
+ @metas = []
60
+ elem.each_element('meta-data') do |e|
61
+ @metas << Meta.new(e)
62
+ end
63
+ end
64
+ end
65
+
66
+ class Activity < Component
67
+ # the element is valid Activity element or not
68
+ # @param [REXML::Element] elem xml element
69
+ # @return [Boolean]
70
+ def self.valid?(elem)
71
+ ['activity', 'activity-alias'].include?(elem.name.downcase)
72
+ rescue => e
73
+ false
74
+ end
75
+
76
+ def launcher_activity?
77
+ intent_filters.flatten.any? do |filter|
78
+ filter.type == 'category' && filter.name == 'android.intent.category.LAUNCHER'
79
+ end
80
+ end
81
+
82
+ # @return whether this instance is the default main launcher activity.
83
+ def default_launcher_activity?
84
+ intent_filters.any? do |intent_filter|
85
+ intent_filter.any? { |f| f.type == 'category' && f.name == 'android.intent.category.LAUNCHER' } &&
86
+ intent_filter.any? { |f| f.type == 'category' && f.name == 'android.intent.category.DEFAULT' }
87
+ end
88
+ end
89
+ end
90
+
91
+ class ActivityAlias < Activity
92
+ # @return [String] target activity name
93
+ attr_reader :target_activity
94
+
95
+ # @param [REXML::Element] elem target element
96
+ # @raise [ArgumentError] when elem is invalid.
97
+ def initialize(elem)
98
+ super
99
+ @target_activity = elem.attributes['targetActivity']
100
+ end
101
+ end
102
+
103
+ class Service < Component
104
+ end
105
+
106
+ class Receiver < Component
107
+ end
108
+
109
+ class Provider < Component
110
+ end
111
+
112
+ class Application < Component
113
+ def self.valid?(elem)
114
+ elem&.name == 'application'
115
+ end
116
+ end
117
+
118
+ # intent-filter element in components
119
+ module IntentFilter
120
+ # filter types
121
+ TYPES = ['action', 'category', 'data']
122
+
123
+ # the element is valid IntentFilter element or not
124
+ # @param [REXML::Element] elem xml element
125
+ # @return [Boolean]
126
+ def self.valid?(elem)
127
+ TYPES.include?(elem.name.downcase)
128
+ rescue => e
129
+ false
130
+ end
131
+
132
+ # parse inside of intent-filter element
133
+ # @param [REXML::Element] elem target element
134
+ # @return [IntentFilter::Action, IntentFilter::Category, IntentFilter::Data]
135
+ # intent-filter element
136
+ def self.parse(elem)
137
+ case elem.name
138
+ when 'action'
139
+ Action.new(elem)
140
+ when 'category'
141
+ Category.new(elem)
142
+ when 'data'
143
+ Data.new(elem)
144
+ else
145
+ nil
146
+ end
147
+ end
148
+
149
+ # intent-filter action class
150
+ class Action
151
+ # @return [String] action name of intent-filter
152
+ attr_reader :name
153
+ # @return [String] action type of intent-filter
154
+ attr_reader :type
155
+
156
+ def initialize(elem)
157
+ @type = 'action'
158
+ @name = elem.attributes['name']
159
+ end
160
+ end
161
+
162
+ # intent-filter category class
163
+ class Category
164
+ # @return [String] category name of intent-filter
165
+ attr_reader :name
166
+ # @return [String] category type of intent-filter
167
+ attr_reader :type
168
+
169
+ def initialize(elem)
170
+ @type = 'category'
171
+ @name = elem.attributes['name']
172
+ end
173
+ end
174
+
175
+ # intent-filter data class
176
+ class Data
177
+ # @return [String]
178
+ attr_reader :type
179
+ # @return [String]
180
+ attr_reader :host
181
+ # @return [String]
182
+ attr_reader :mime_type
183
+ # @return [String]
184
+ attr_reader :path
185
+ # @return [String]
186
+ attr_reader :path_pattern
187
+ # @return [String]
188
+ attr_reader :path_prefix
189
+ # @return [String]
190
+ attr_reader :port
191
+ # @return [String]
192
+ attr_reader :scheme
193
+
194
+ def initialize(elem)
195
+ @type = 'data'
196
+ @host = elem.attributes['host']
197
+ @mime_type = elem.attributes['mimeType']
198
+ @path = elem.attributes['path']
199
+ @path_pattern = elem.attributes['pathPattern']
200
+ @path_prefix = elem.attributes['pathPrefix']
201
+ @port = elem.attributes['port']
202
+ @scheme = elem.attributes['scheme']
203
+ end
204
+ end
205
+ end
206
+
207
+ # meta information class
208
+ class Meta
209
+ # @return [String]
210
+ attr_reader :name
211
+ # @return [String]
212
+ attr_reader :resource
213
+ # @return [String]
214
+ attr_reader :value
215
+ def initialize(elem)
216
+ @name = elem.attributes['name']
217
+ @resource = elem.attributes['resource']
218
+ @value = elem.attributes['value']
219
+ end
220
+ end
221
+
222
+ #################################
223
+ # Manifest class definitions
224
+ #################################
225
+ #
226
+ # @return [REXML::Document] manifest xml
227
+ attr_reader :doc
228
+
229
+ # @param [String] data binary data of AndroidManifest.xml
230
+ def initialize(data, rsc=nil)
231
+ parser = AXMLParser.new(data)
232
+ @doc = parser.parse
233
+ @rsc = rsc
234
+ end
235
+
236
+ # used permission array
237
+ # @return [Array<String>] permission names
238
+ # @note return empty array when the manifest includes no use-parmission element
239
+ def use_permissions
240
+ perms = []
241
+ @doc.each_element('/manifest/uses-permission') do |elem|
242
+ perms << elem.attributes['name']
243
+ end
244
+ perms.uniq
245
+ end
246
+
247
+ # Returns the manifest's application element or nil, if there isn't any.
248
+ # @return [Android::Manifest::Application] the manifest's application element
249
+ def application
250
+ element = @doc.elements['//application']
251
+ Application.new(element) if Application.valid?(element)
252
+ end
253
+
254
+ # @return [Array<Android::Manifest::Component>] all components in apk
255
+ # @note return empty array when the manifest include no components
256
+ def components
257
+ components = []
258
+ unless @doc.elements['/manifest/application'].nil?
259
+ @doc.elements['/manifest/application'].each do |elem|
260
+ components << Component.new(elem) if Component.valid?(elem)
261
+ end
262
+ end
263
+ components
264
+ end
265
+
266
+ # @return [Array<Android::Manifest::Activity&ActivityAlias>] all activities in the apk
267
+ # @note return empty array when the manifest include no activities
268
+ def activities
269
+ activities = []
270
+ unless @doc.elements['/manifest/application'].nil?
271
+ @doc.elements['/manifest/application'].each do |elem|
272
+ next unless Activity.valid?(elem)
273
+
274
+ activities << (elem.name == 'activity-alias' ? ActivityAlias.new(elem) : Activity.new(elem))
275
+ end
276
+ end
277
+ activities
278
+ end
279
+
280
+ # @return [Array<Android::Manifest::Activity&ActivityAlias>] all activities that are launchers in the apk
281
+ # @note return empty array when the manifest include no activities
282
+ def launcher_activities
283
+ activities.select(&:launcher_activity?)
284
+ end
285
+
286
+ # application package name
287
+ # @return [String]
288
+ def package_name
289
+ @doc.root.attributes['package']
290
+ end
291
+
292
+ # application version code
293
+ # @return [Integer]
294
+ def version_code
295
+ @doc.root.attributes['versionCode'].to_i
296
+ end
297
+
298
+ # application version name
299
+ # @return [String]
300
+ def version_name(lang=nil)
301
+ vername = @doc.root.attributes['versionName']
302
+ unless @rsc.nil?
303
+ if /^@(\w+\/\w+)|(0x[0-9a-fA-F]{8})$/ =~ vername
304
+ opts = {}
305
+ opts[:lang] = lang unless lang.nil?
306
+ vername = @rsc.find(vername, opts)
307
+ end
308
+ end
309
+ vername
310
+ end
311
+
312
+ # @return [Integer] minSdkVersion in uses element
313
+ def min_sdk_ver
314
+ @doc.elements['/manifest/uses-sdk'].attributes['minSdkVersion'].to_i
315
+ end
316
+
317
+ # application label
318
+ # @param [String] lang language code like 'ja', 'cn', ...
319
+ # @return [String] application label string(if resouce is provided), or label resource id
320
+ # @return [nil] when label is not found
321
+ # @since 0.5.1
322
+ def label(lang=nil)
323
+ label = @doc.elements['/manifest/application'].attributes['label']
324
+ if label.nil?
325
+ # application element has no label attributes.
326
+ # so looking for activites that has label attribute.
327
+ activities = @doc.elements['/manifest/application'].find{|e| e.name == 'activity' && !e.attributes['label'].nil? }
328
+ label = activities.nil? ? nil : activities.first.attributes['label']
329
+ end
330
+ unless @rsc.nil?
331
+ if /^@(\w+\/\w+)|(0x[0-9a-fA-F]{8})$/ =~ label
332
+ opts = {}
333
+ opts[:lang] = lang unless lang.nil?
334
+ label = @rsc.find(label, opts)
335
+ end
336
+ end
337
+ label
338
+ end
339
+
340
+ # return xml as string format
341
+ # @param [Integer] indent size(bytes)
342
+ # @return [String] raw xml string
343
+ def to_xml(indent=4)
344
+ xml =''
345
+ formatter = REXML::Formatters::Pretty.new(indent)
346
+ formatter.write(@doc.root, xml)
347
+ xml
348
+ end
349
+ end
350
+ end