mirror of
https://github.com/MobSF/Mobile-Security-Framework-MobSF.git
synced 2026-10-12 02:06:59 +00:00
183 lines
6.4 KiB
Python
183 lines
6.4 KiB
Python
import glob
|
|
import io
|
|
import itertools
|
|
import json
|
|
import logging
|
|
import os
|
|
import re
|
|
import subprocess
|
|
from collections import namedtuple
|
|
|
|
from django.conf import settings
|
|
|
|
from mobsf.MobSF.utils import (
|
|
find_java_binary,
|
|
is_file_exists,
|
|
is_internet_available,
|
|
update_local_db,
|
|
)
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
|
|
class Trackers:
|
|
def __init__(self, apk_dir, tools_dir):
|
|
self.apk = None
|
|
self.apk_dir = apk_dir
|
|
self.tracker_db = os.path.join(
|
|
settings.SIGNATURE_DIR,
|
|
'exodus_trackers')
|
|
self.signatures = None
|
|
self.nb_trackers_signature = 0
|
|
self.compiled_tracker_signature = None
|
|
self.classes = None
|
|
self.tools_dir = tools_dir
|
|
self._update_tracker_db()
|
|
|
|
def _update_tracker_db(self):
|
|
"""Update Trackers DB."""
|
|
try:
|
|
if not is_internet_available():
|
|
logger.warning('No Internet Connection. '
|
|
'Skipping Trackers Database Update.')
|
|
return
|
|
exodus_db = '{}/api/trackers'.format(settings.EXODUS_URL)
|
|
resp = update_local_db('Trackers',
|
|
exodus_db,
|
|
self.tracker_db)
|
|
# Check1: SHA256 Change
|
|
if resp:
|
|
# DB needs update
|
|
# Check2: DB Syntax Changed
|
|
data = json.loads(resp.decode('utf-8', 'ignore'))
|
|
is_db_format_good = False
|
|
if 'trackers' in data:
|
|
if '1' in data['trackers']:
|
|
if 'code_signature' in data['trackers']['1']:
|
|
is_db_format_good = True
|
|
if is_db_format_good:
|
|
# DB Format is not changed. Let's update DB
|
|
logger.info('Updating Trackers Database....')
|
|
with open(self.tracker_db, 'wb') as wfp:
|
|
wfp.write(resp)
|
|
else:
|
|
logger.info('Trackers Database format from '
|
|
'reports.exodus-privacy.eu.org has changed.'
|
|
' Database is not updated. '
|
|
'Please report to: https://github.com/MobSF/'
|
|
'Mobile-Security-Framework-MobSF/issues')
|
|
except Exception:
|
|
logger.exception('[ERROR] Trackers DB Update')
|
|
|
|
def _compile_signatures(self):
|
|
"""
|
|
Compile Signatures.
|
|
|
|
Compiles the regex associated to each signature, in order to speed up
|
|
the trackers detection.
|
|
:return: A compiled list of signatures.
|
|
"""
|
|
self.compiled_tracker_signature = []
|
|
try:
|
|
self.compiled_tracker_signature = [re.compile(track.code_signature)
|
|
for track in self.signatures]
|
|
except TypeError:
|
|
logger.exception('compiling tracker signature failed')
|
|
|
|
def load_trackers_signatures(self):
|
|
"""
|
|
Load trackers signatures from the official Exodus database.
|
|
|
|
:return: a dictionary of signatures.
|
|
"""
|
|
self.signatures = []
|
|
with io.open(self.tracker_db,
|
|
mode='r',
|
|
encoding='utf8',
|
|
errors='ignore') as flip:
|
|
data = json.loads(flip.read())
|
|
for elm in data['trackers']:
|
|
self.signatures.append(
|
|
namedtuple('tracker',
|
|
data['trackers'][elm].keys())(
|
|
*data['trackers'][elm].values()))
|
|
self._compile_signatures()
|
|
self.nb_trackers_signature = len(self.signatures)
|
|
|
|
def get_embedded_classes(self):
|
|
"""
|
|
Get the list of Java classes from all DEX files.
|
|
|
|
:return: list of Java classes
|
|
"""
|
|
if self.classes is not None:
|
|
return self.classes
|
|
for dex_file in glob.iglob(os.path.join(self.apk_dir, '*.dex')):
|
|
if (len(settings.BACKSMALI_BINARY) > 0
|
|
and is_file_exists(settings.BACKSMALI_BINARY)):
|
|
bs_path = settings.BACKSMALI_BINARY
|
|
else:
|
|
bs_path = os.path.join(self.tools_dir, 'baksmali-2.4.0.jar')
|
|
args = [find_java_binary(), '-jar',
|
|
bs_path, 'list', 'classes', dex_file]
|
|
classes = subprocess.check_output(
|
|
args, universal_newlines=True).splitlines()
|
|
if self.classes is not None:
|
|
self.classes = self.classes + classes
|
|
else:
|
|
self.classes = classes
|
|
return self.classes
|
|
|
|
def detect_trackers_in_list(self, class_list):
|
|
"""
|
|
Detect embedded trackers in the provided classes list.
|
|
|
|
:return: list of embedded trackers
|
|
"""
|
|
if self.signatures is None:
|
|
self.load_trackers_signatures()
|
|
|
|
def _detect_tracker(sig, tracker, class_list):
|
|
for clazz in class_list:
|
|
if sig.search(clazz):
|
|
return tracker
|
|
return None
|
|
|
|
results = []
|
|
args = [(self.compiled_tracker_signature[index], tracker, class_list)
|
|
for (index, tracker) in enumerate(self.signatures) if
|
|
len(tracker.code_signature) > 3]
|
|
|
|
for res in itertools.starmap(_detect_tracker, args):
|
|
if res:
|
|
results.append(res)
|
|
|
|
trackers = [t for t in results if t is not None]
|
|
trackers = sorted(trackers, key=lambda trackers: trackers.name)
|
|
return trackers
|
|
|
|
def detect_trackers(self):
|
|
"""
|
|
Detect embedded trackers.
|
|
|
|
:return: list of embedded trackers
|
|
"""
|
|
if self.signatures is None:
|
|
self.load_trackers_signatures()
|
|
eclasses = self.get_embedded_classes()
|
|
if eclasses:
|
|
return self.detect_trackers_in_list(eclasses)
|
|
return []
|
|
|
|
def get_trackers(self):
|
|
"""Get Trackers."""
|
|
logger.info('Detecting Trackers')
|
|
trackers = self.detect_trackers()
|
|
tracker_dict = {'detected_trackers': len(trackers),
|
|
'total_trackers': self.nb_trackers_signature,
|
|
'trackers': []}
|
|
for trk in trackers:
|
|
trk_url = '{}/trackers/{}'.format(settings.EXODUS_URL, trk.id)
|
|
tracker_dict['trackers'].append({trk.name: trk_url})
|
|
return tracker_dict
|