Commit 91d29b9b authored by Damien Octeau's avatar Damien Octeau

Initial commit.

parents
This diff is collapsed.
# Copyright (C) 2015 The Pennsylvania State University and the University of Wisconsin
# Systems and Internet Infrastructure Security Laboratory
#
# Author: Damien Octeau
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#exclude linking/*.pyx
include *.txt
include plot_links.py
include setup.py
include linking/*.c
include linking/*.pxd
include linking/*.pxi
include linking/*.py
To build:
python setup.py build
The script can be run directly from the top-level directory afterwards.
It can also be installed with python setup.py install
For further instructions, please see http://siis.cse.psu.edu/primo/
# Copyright (C) 2015 The Pennsylvania State University and the University of Wisconsin
# Systems and Internet Infrastructure Security Laboratory
#
# Author: Damien Octeau
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
cdef class Application(object):
cdef readonly list components
cdef readonly unicode name
cdef readonly tuple used_permissions
cdef readonly long version
cdef readonly unicode sample
cdef readonly list intents
cdef readonly tuple descriptor
cdef readonly tuple component_maps
cdef readonly tuple exported_component_maps
cdef long _hash
cdef int CountMatchingComponentsOfKind(self, int kind, unicode name)
cdef bint HasMatchingExportedComponentsOfKind(self, int kind, unicode name)
# Copyright (C) 2015 The Pennsylvania State University and the University of Wisconsin
# Systems and Internet Infrastructure Security Laboratory
#
# Author: Damien Octeau
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
"""Application class and factory."""
from linking.components cimport Component
from linking.components cimport MakeComponent
from linking.intents cimport ComponentIntent
from linking.attribute_matching cimport AttributeMap
from linking import ic3_data_pb2
cdef set SAMPLES = set()
def MakeApplication(application_pb):
"""Generates an Application object from a protobuf.
Args:
application_pb: An Application protobuf object.
Returns: A linking.Application object.
"""
cdef unicode name = application_pb.name
# In protobufs, fields of type RepeatedScalarFieldContainer (such as
# used_permissions) contain a weak reference to
# google.protobuf.internal.python_message._Listener. To avoid pickling
# issues, we copy the contents.
cdef tuple used_permissions = (tuple(sorted(application_pb.used_permissions))
if application_pb.used_permissions else None)
cdef long version = application_pb.version
cdef unicode sample = (application_pb.sample
if application_pb.HasField('sample') else None)
SAMPLES.add(sample)
cdef Application application = Application(name, used_permissions, version,
sample)
cdef list components = []
for pb_component in application_pb.components:
components.append(MakeComponent(pb_component, application))
application.components = components
cdef tuple component_maps
cdef tuple exported_component_maps
# This is relying on the fact that:
# ic3_data_pb2.Application.Component.ACTIVITY = 0
# ic3_data_pb2.Application.Component.SERVICE = 1
# ic3_data_pb2.Application.Component.RECEIVER = 2
# TODO Check C arrays.
component_maps = (AttributeMap(), AttributeMap(), AttributeMap())
exported_component_maps = (AttributeMap(), AttributeMap(), AttributeMap())
cdef unicode component_name
cdef int kind
cdef AttributeMap current_attribute_map
cdef Component component
cdef list intents = []
cdef list component_intents
for component in components:
kind = component.kind
if kind == ic3_data_pb2.Application.Component.PROVIDER:
continue
elif kind == ic3_data_pb2.Application.Component.DYNAMIC_RECEIVER:
kind = ic3_data_pb2.Application.Component.RECEIVER
component_name = component.name
current_attribute_map = component_maps[kind]
current_attribute_map.AddAttribute(component_name, component)
if component.exported:
current_attribute_map = exported_component_maps[kind]
current_attribute_map.AddAttribute(component_name, component)
component_intents = component.intents
if component_intents:
intents += component_intents
application.component_maps = component_maps
application.exported_component_maps = exported_component_maps
application.intents = intents
return application
cdef class Application(object):
"""A class that represents an application."""
def __cinit__(self, unicode name, tuple used_permissions, long version,
unicode sample):
self.name = name
self.used_permissions = used_permissions
self.version = version
self.sample = sample
self.descriptor = (self.name, self.version)
self._hash = hash(self.descriptor)
cdef int CountMatchingComponentsOfKind(self, int kind, unicode value):
"""Counts the number of components in this application matching a given
field value.
Args:
kind: A field name.
value: A field value.
Returns: The number of components matching the field value.
"""
cdef AttributeMap attribute_map = self.component_maps[kind]
return len(attribute_map.GetEndPointsForAttribute(value))
cdef bint HasMatchingExportedComponentsOfKind(self, int kind, unicode value):
"""Determines if any component in this application matches a given field
value.
Args:
kind: A field name.
value: A field value.
Returns: True if a component in this application matches the field value.
"""
return self.CountMatchingComponentsOfKind(kind, value) > 0
def HasOpenComponent(self):
"""Determines if this application has any component that is publicly
accessible.
Returns: True if this application has a component that is publicly
accessible.
"""
cdef Component component
for component in self.components:
if component.IsOpenComponent():
return True
return False
def SendsExternalIntent(self):
"""Determines if this application sends an Intent to another application.
Note that this does not take into account potentially imprecise Intents.
Returns: True if this application sends an Intent to another application.
"""
cdef ComponentIntent intent
if self.intents:
for intent in self.intents:
if not intent.HasInternalDestination():
return True
return False
property exit_point_count:
def __get__(self):
cdef int count = 0
cdef Component component
for component in self.components:
count += component.exit_point_count
return count
property intent_count:
def __get__(self):
return len(self.intents)
property intent_filter_count:
def __get__(self):
cdef int count = 0
cdef Component component
for component in self.components:
count += component.intent_filter_count
return count
def __hash__(self):
return self._hash
def __richcmp__(self, object other, int op):
if op == 2:
return (isinstance(other, Application)
and self.descriptor == other.descriptor)
raise NotImplementedError
# Copyright (C) 2015 The Pennsylvania State University and the University of Wisconsin
# Systems and Internet Infrastructure Security Laboratory
#
# Author: Damien Octeau
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
cdef class AttributeMap(object):
cdef dict _regexes
cdef dict _constants
cdef set _all_end_points
cdef set _end_points_with_regexes
cdef dict _cache
cdef void AddAttribute(self, unicode attribute, object end_point)
cdef set GetEndPointsForAttributeSet(
self, object attribute_set, set search_space=?, bint match_all=?)
cdef set GetEndPointsForAttribute(self, unicode attribute, set search_space=?)
cdef set GetEndPointsWithoutEmptySet(self, set search_space)
cdef set GetEndPointsForEmptySet(self, set search_space)
cdef bint NonEmptyIntersection(unicode regex1, unicode regex2)
# Copyright (C) 2015 The Pennsylvania State University and the University of Wisconsin
# Systems and Internet Infrastructure Security Laboratory
#
# Author: Damien Octeau
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
"""Utilities for matching attributes with end points."""
import re
cdef class AttributeMap(object):
"""Class that maps attributes to end points that contain them."""
def __cinit__(self):
self._regexes = {}
self._constants = {}
self._all_end_points = set()
self._end_points_with_regexes = set()
self._cache = {}
def __repr__(self):
return str(self._regexes) + ' - ' + str(self._constants)
cdef void AddAttribute(self, unicode attribute, object end_point):
"""Adds an attribute and the end point that contains it.
Args:
attribute: A field value.
end_point: The end point that contains the field value.
"""
if hasattr(attribute, '__iter__') and len(attribute) == 0:
# We want the empty set to use the None key.
attribute = None
cdef dict attribute_map = None
if (attribute is not None and '(.*)' in attribute):
attribute_map = self._regexes
self._end_points_with_regexes.add(end_point)
else:
attribute_map = self._constants
cdef set end_points = attribute_map.get(attribute)
if not end_points:
end_points = set()
attribute_map[attribute] = end_points
end_points.add(end_point)
self._all_end_points.add(end_point)
cdef set GetEndPointsForAttributeSet(
self, object attribute_set, set search_space=None, bint match_all=True):
"""Returns all end points matching attributes from a set.
Args:
attribute_set: An iterable of attributes.
search_space: The search space.
match_all: If set to True, then only end points matching all attributes
are returned. Otherwise, returned end points will match any attribute.
Returns: All matching end points.
"""
if not attribute_set:
return search_space
cdef list end_point_sets = [self.GetEndPointsForAttribute(attribute, None)
for attribute in attribute_set]
cdef set result = None
for end_points in end_point_sets:
if result is not None:
if match_all:
result = result & end_points
else:
result = result | end_points
else:
result = end_points
if search_space is not None:
result = result & search_space
return result
cdef set GetEndPointsForAttribute(self, unicode attribute,
set search_space=None):
"""Returns the end points that have a certain attribute among a set of end
points.
Args:
attribute: A field value.
search_space: The search space.
Returns: All matching end points.
"""
if attribute == u'(.*)':
return (self._all_end_points & search_space if search_space is not None
else self._all_end_points)
cdef set end_points
try:
end_points = self._cache[attribute]
except KeyError:
end_points = self._constants.get(attribute, set())
# Bypass regular expression matching if no end point in the search space
# is associated with a regex.
if attribute is not None and '(.*)' in attribute:
for candidate, end_points_for_attribute in self._constants.iteritems():
if candidate is not None and NonEmptyIntersection(candidate, attribute):
end_points = end_points | end_points_for_attribute
if self._end_points_with_regexes:
for candidate, end_points_for_attribute in self._regexes.iteritems():
if attribute is not None and NonEmptyIntersection(candidate, attribute):
end_points = end_points | end_points_for_attribute
self._cache[attribute] = end_points
# Only retain the ones that are in the search space.
if search_space is not None: end_points = end_points & search_space
return end_points
cdef set GetEndPointsWithoutEmptySet(self, set search_space):
"""Selects the end points that have a non-empty set of attributes.
Args:
search_space: The search space.
Returns: All end points that have a non-empty set of attributes.
"""
cdef set empty_set = self._constants.get(None, set())
return (search_space - empty_set)
cdef set GetEndPointsForEmptySet(self, set search_space):
"""Selects the end points with an empty set of attributes.
Args:
search_space: The search space.
Returns: All end points that have a non-empty set of attributes.
"""
return self.GetEndPointsForAttribute(None, search_space)
def __str__(self):
parts = ['%s: %s' % (key, value)
for (key, value) in self._constants.iteritems()]
parts += ['%s: %s' % (key, value)
for (key, value) in self._regexes.iteritems()]
return '\n'.join(parts)
cdef bint NonEmptyIntersection(unicode regex1, unicode regex2):
"""Determines if the languages described by two regular expressions have a
non empty intersection.
Args:
regex1: A regular expression.
regex2: Another regular expression.
Returns: True if the two regular expressions describe languages with non-empty
intersection.
"""
#comment this out if you want (.*) and (.*) to not match
#return regex1==regex2
if regex1 == u'(.*)':
# This matches everything.
return True
elif regex2 == u'(.*)':
# This matches everything.
return True
elif '(.*)' in regex1:
# At least one string is a regex.
if re.match(EscapeRegex(regex1), regex2):
return True
if '(.*)' in regex2:
# We need to consider this case as an attempt to find the intersection of
# two regular expressions.
# For example, if regex1 = 'ab.*d' and regex2 = 'a.*d', then this will return
# True.
return re.match(EscapeRegex(regex2), regex1)
return False
elif '(.*)' in regex2:
# If we come here, then only regex2 is a regex.
return re.match(EscapeRegex(regex2), regex1)
else:
# Neither is a regex.
return regex1 == regex2
cdef unicode EscapeRegex(unicode astr):
"""Turns a string into a proper regular expression.
This involves escaping characters such as . and also not escaping parts of
strings that are unknown, as indicated by (.*).
Args:
astr: A string.
Returns: The cleansed regular expression.
"""
return re.escape(astr.replace('(.*)', 'ddddddd')).replace('ddddddd', '.*')
# Copyright (C) 2015 The Pennsylvania State University and the University of Wisconsin
# Systems and Internet Infrastructure Security Laboratory
#
# Author: Damien Octeau
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
from linking.applications cimport Application
cdef int GetSkippedFilterCount()
cdef Component MakeComponent(object component_pb, Application application)
cdef class Component(object):
cdef readonly unicode name
cdef readonly int kind
cdef readonly unicode permission
cdef readonly tuple extras
cdef readonly bint exported
cdef readonly int exit_point_count
cdef readonly Application application
cdef public unicode _filters_attributes_string
cdef public unicode _intents_attributes_string
cdef readonly list intents
cdef readonly tuple descriptor
cdef readonly list filters
cdef long _hash
cdef readonly int id
cdef bint IsOpenComponent(self)
# Copyright (C) 2015 The Pennsylvania State University and the University of Wisconsin
# Systems and Internet Infrastructure Security Laboratory
#
# Author: Damien Octeau
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
"""A class and factory for Android application components."""
import gflags
from linking.applications cimport Application
from linking.target_data cimport AddComponent
from linking.intent_filters cimport IntentFilter
from linking.intent_filters cimport MakeIntentFilter
from linking.intents cimport MakeComponentIntent
FLAGS = gflags.FLAGS
cdef int _id = 0
cdef int _skipped_imprecise_filters = 0
cdef Component MakeComponent(object component_pb, Application application):
"""Generates a Component object from a protobuf.
Args:
component_pb: A Component protobuf object.
application: An Application object.
Returns: A linking.Component object.
"""
cdef unicode name = component_pb.name
cdef int kind = component_pb.kind
cdef unicode permission = (component_pb.permission
if component_pb.HasField('permission') else None)
cdef tuple extras = tuple(sorted(component_pb.extras))
cdef bint exported = component_pb.exported
cdef int exit_point_count = len(component_pb.exit_points)
cdef set intents = set()
cdef list filters = []
cdef IntentFilter intent_filter
global _id
cdef Component result = Component(name, kind, permission, extras, exported,
exit_point_count, application, filters, _id)
_id += 1
for exit_point in component_pb.exit_points:
for intent in exit_point.intents:
component_intent = MakeComponentIntent(intent, result, exit_point)
intents.add(component_intent)
result.intents = list(intents)
for intent_filter_pb in component_pb.intent_filters:
intent_filter = MakeIntentFilter(intent_filter_pb, result)
if intent_filter.IsPrecise() or not FLAGS.validate:
filters.append(intent_filter)
else:
global _skipped_imprecise_filters
_skipped_imprecise_filters += 1
AddComponent(result)
return result
cdef int GetSkippedFilterCount():
return _skipped_imprecise_filters
cdef class Component(object):
"""A class that represents an application component."""
def __cinit__(self, unicode name, int kind, unicode permission, tuple extras,
bint exported, int exit_point_count, Application application,
list filters, int _id):
self.name = name
self.kind = kind
self.permission = permission
self.extras = extras
self.exported = exported
self.exit_point_count = exit_point_count
self.application = application
self.descriptor = (application.descriptor, self.name)
self._hash = hash(self.descriptor)
self.filters = filters
self.id = _id
property intent_count:
def __get__(self):
return len(self.intents)
property intent_filter_count:
def __get__(self):
return len(self.filters)
cdef bint IsOpenComponent(self):
"""Determines if a component is open to other applications.
Returns:
True if the component is open to other applications."""
if not self.exported:
return False
for intent_filter in self.filters:
# We say that the main activity is not open to other applications
# because starting this component is simply starting the application.
if not intent_filter.IsMain():
return True
return False
@property
def application_id(self):
return self.application.name
def __hash__(self):
return self._hash
def __richcmp__(self, object other, int op):
if op == 2:
return (isinstance(other, Component) and
self.descriptor == other.descriptor)
raise NotImplementedError
def __repr__(self):
return 'Component(%r, %r)' % (self.name, self.application.name)
# Copyright (C) 2015 The Pennsylvania State University and the University of Wisconsin
# Systems and Internet Infrastructure Security Laboratory
#
# Author: Damien Octeau
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
cdef set EXPLICIT_ATTRS = set(['dclass', 'dpackage'])
cdef set IMPLICIT_ATTRS = set(['dpackage', 'action', 'categories', 'scheme',
'host', 'path', 'dtype', 'port'])
# Copyright (C) 2015 The Pennsylvania State University and the University of Wisconsin
# Systems and Internet Infrastructure Security Laboratory
#
# Author: Damien Octeau
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is dist