def test_track_detail_app_removed_tracker(self): tracker1 = Tracker( id=1, name='Teemo', ) tracker1.save() application_handle1 = "com.handle.one" report1 = Report() report1.save() application1 = Application( handle=application_handle1, report=report1 ) application1.save() report1.found_trackers = [tracker1.id] report1.save() report2 = Report() report2.save() application2 = Application( handle=application_handle1, report=report2 ) application2.save() # Removing trackers in the version of the app report2.found_trackers = [] report2.save() c = Client() url = self.TRACKER_DETAIL_PATH.format(tracker1.id) response = c.get(url) self.assertEqual(response.status_code, 200) self.assertEqual(response.context['tracker'].id, tracker1.id) self.assertEqual(len(response.context['reports']), 0)
def test_track_detail_app_multiple_reports(self): tracker2 = Tracker( id=2, name='Exodus Super Tracker', ) tracker2.save() application_handle2 = "com.handle.two" report2 = Report() report2.save() application2 = Application( handle=application_handle2, report=report2 ) application2.save() report2.found_trackers = [tracker2.id] report2.save() report3 = Report() report3.save() application3 = Application( handle=application_handle2, report=report3 ) application3.save() report3.found_trackers = [tracker2.id] report3.save() c = Client() url = self.TRACKER_DETAIL_PATH.format(tracker2.id) response = c.get(url) self.assertEqual(response.status_code, 200) self.assertEqual(response.context['tracker'].id, tracker2.id) self.assertEqual(len(response.context['reports']), 1) self.assertEqual(response.context['reports'][0].application.handle, application_handle2) self.assertEqual(response.context['reports'][0], report3)
def test_returns_applications_with_report_last_update(self): report = Report() report.save() application = Application(name='app_name', handle='handle', report=report) application.save() client = APIClient() response = client.get('/api/applications') self.assertEqual(response.status_code, 200) self.assertContains(response, application.name, 1) report_updated_at = application.report.updated_at response_application = response.json()['applications'][0] self.assertEqual(response_application['name'], application.name) self.assertEqual(response_application['handle'], application.handle) self.assertEqual(response_application['report_updated_at'], report_updated_at.timestamp())
def test_should_return_stats_with_single_report_one_application(self): tracker1 = Tracker( id=1, name='Teemo', ) tracker1.save() tracker2 = Tracker( id=2, name='Exodus Super Tracker', ) tracker2.save() application_handle = "com.exodus.one" report1 = Report() report1.save() application1 = Application( handle=application_handle, report=report1 ) application1.save() report1.found_trackers = [tracker2.id] report1.save() report2 = Report() report2.save() application2 = Application( handle=application_handle, report=report2 ) application2.save() report2.found_trackers = [] report2.save() report3 = Report() report3.save() application3 = Application( handle=application_handle, report=report3 ) application3.save() report3.found_trackers = [tracker1.id, tracker2.id] report3.save() c = Client() response = c.get(self.STATS_PATH) self.assertEqual(response.status_code, 200) self.assertContains(response, tracker1.name, 1) self.assertContains(response, tracker2.name, 1) self.assertEqual(response.context['trackers'][0].name, tracker2.name) # Only recent for an application is considered self.assertEqual(response.context['trackers'][0].count, 1) self.assertEqual(response.context['trackers'][0].score, 100) self.assertEqual(response.context['trackers'][1].name, tracker1.name) self.assertEqual(response.context['trackers'][1].count, 1) self.assertEqual(response.context['trackers'][1].score, 100)
def start_static_analysis(params): """ Compute the entire static analysis :param params: a StaticAnalysisParameters instance """ request = AnalysisRequest.objects.get(pk=params.query.id) request.description = _('Your request is running') request.save() storage_helper = RemoteStorageHelper(params.bucket) if request.apk: if not os.path.exists(params.tmp_dir): os.mkdir(params.tmp_dir) with open(params.apk_tmp, 'wb') as out: out.write(request.apk.read()) storage_helper.put_file(params.apk_tmp, params.apk_name) request.apk.delete() else: if params.source == "google": if is_paid_app(request.handle): logging.warn("'{}' is a paid application".format( request.handle)) msg = _('εxodus cannot scan paid applications') save_error(storage_helper, params, request, msg) return EXIT_CODE_PAID_APP_ERROR # Download APK and put it on Minio storage dl_r = download_apk(storage_helper, request.handle, params.tmp_dir, params.apk_name, params.apk_tmp, params.source) if not dl_r: logging.error("Could not download '{}'".format(request.handle)) msg = _('Unable to download the APK') save_error(storage_helper, params, request, msg) return EXIT_CODE_DOWNLOAD_APK_ERROR change_description(request, _('Download APK: success')) # Decode the APK file try: static_analysis = StaticAnalysis(params.apk_tmp) static_analysis.load_apk() except Exception as e: logging.info(e) msg = _('Unable to decode the APK') save_error(storage_helper, params, request, msg) return EXIT_CODE_DECODE_APK_ERROR change_description(request, _('Decode APK: success')) # List and save embedded classes try: with tempfile.NamedTemporaryFile(delete=True) as fp: static_analysis.save_embedded_classes_in_file(fp.name) storage_helper.put_file(fp.name, params.class_list_file) except Exception as e: logging.info(e) msg = _('Unable to compute the class list') save_error(storage_helper, params, request, msg) return EXIT_CODE_COMPUTE_CLASS_LIST_ERROR change_description(request, _('List embedded classes: success')) # APK shasum = static_analysis.get_sha256() # Application handle = static_analysis.get_package() version = static_analysis.get_version() version_code = static_analysis.get_version_code() app_name = static_analysis.get_app_name() # TODO: increase character limit in DB (see #300) if not version or not version_code or not app_name or \ len(version) > 50 or len(version_code) > 50 or len(app_name) > 200: msg = _('Unable to create the analysis report') save_error(storage_helper, params, request, msg) return EXIT_CODE_CREATE_REPORT_ERROR # If a report exists for the same handle, version & version_code, return it existing_report = Report.objects.filter( application__handle=handle, application__source=params.source, application__version=version, application__version_code=version_code).order_by( '-creation_date').first() if existing_report is not None: clear_analysis_files(storage_helper, params.tmp_dir, params.bucket, True) request.description = _( 'A report already exists for this application version') request.processed = True request.report_id = existing_report.id request.save() return existing_report.id # APK try: certificates = static_analysis.get_certificates() except Exception as e: logging.info(e) msg = _('Unable to get certificates') save_error(storage_helper, params, request, msg) return EXIT_CODE_GET_CERTFICATES_ERROR # Fingerprint try: perms = static_analysis.get_permissions() app_uid = static_analysis.get_application_universal_id() if len(app_uid) < 16: raise Exception('Unable to compute the Universal Application ID') icon_phash = static_analysis.get_icon_and_phash( storage_helper, params.icon_name, params.source) if len(str(icon_phash)) < 16 and not request.apk: raise Exception('Unable to compute the icon perceptual hash') except Exception as e: logging.info(e) msg = _('Unable to compute APK fingerprint') save_error(storage_helper, params, request, msg) return EXIT_CODE_COMPUTE_APK_FINGERPRINT_ERROR # Application details try: app_info = static_analysis.get_app_info() except Exception as e: logging.info(e) msg = _('Unable to get application details from Google Play') save_error(storage_helper, params, request, msg) return EXIT_CODE_GET_DETAILS_APP_ERROR change_description(request, _('Get application details: success')) # Find trackers trackers = static_analysis.detect_trackers() change_description(request, _('Tracker analysis: success')) report = Report(apk_file=params.apk_name, storage_path='', bucket=request.bucket, class_list_file=params.class_list_file) report.save() app = Application( report=report, handle=handle, version=version, version_code=version_code, name=app_name, icon_phash=icon_phash, app_uid=app_uid, source=params.source, icon_path=params.icon_name, ) if app_info is not None: app.name = app_info['title'] app.creator = app_info['creator'] app.downloads = app_info['downloads'] app.save(force_insert=True) apk = Apk(application=app, name=params.apk_name, sum=shasum) apk.save(force_insert=True) for certificate in certificates: c = Certificate(apk=apk, issuer=certificate.issuer, fingerprint=certificate.fingerprint, subject=certificate.subject, serial_number=certificate.serial) c.save(force_insert=True) for perm in perms: p = Permission(application=app, name=perm) p.save(force_insert=True) report.found_trackers.set(trackers) change_description(request, _('Static analysis complete')) clear_analysis_files(storage_helper, params.tmp_dir, params.bucket, False) request.processed = True request.report_id = report.id request.save() return report.id
def start_static_analysis(analysis): """ Compute the entire static analysis :param analysis: a StaticAnalysis instance """ request = AnalysisRequest.objects.get(pk=analysis.query.id) request.description = _('Your request is running') request.save() storage_helper = RemoteStorageHelper(analysis.bucket) # Download APK and put it on Minio storage dl_r = download_apk(storage_helper, request.handle, analysis.tmp_dir, analysis.apk_name, analysis.apk_tmp) if not dl_r: msg = _('Unable to download the APK') exit_code = save_error(storage_helper, analysis, request, msg) return exit_code change_description(request, _('Download APK: success')) # Decode the APK file try: static_analysis = StaticAnalysis(analysis.apk_tmp) static_analysis.load_apk() except Exception as e: logging.info(e) msg = _('Unable to decode the APK') exit_code = save_error(storage_helper, analysis, request, msg) return exit_code change_description(request, _('Decode APK: success')) # List and save embedded classes try: with tempfile.NamedTemporaryFile(delete=True) as fp: static_analysis.save_embedded_classes_in_file(fp.name) storage_helper.put_file(fp.name, analysis.class_list_file) except Exception as e: logging.info(e) msg = _('Unable to compute the class list') exit_code = save_error(storage_helper, analysis, request, msg) return exit_code change_description(request, _('List embedded classes: success')) # APK shasum = static_analysis.get_sha256() # Application handle = static_analysis.get_package() version = static_analysis.get_version() version_code = static_analysis.get_version_code() # If a report exists for the same handle, version & version_code, return it existing_report = Report.objects.filter( application__handle=handle, application__version=version, application__version_code=version_code ).order_by('-creation_date').first() if existing_report is not None: clear_analysis_files(storage_helper, analysis.tmp_dir, analysis.bucket, True) request.description = _('A report already exists for this application version') request.processed = True request.report_id = existing_report.id request.save() return existing_report.id # APK try: certificates = static_analysis.get_certificates() except Exception as e: logging.info(e) msg = _('Unable to get certificates') exit_code = save_error(storage_helper, analysis, request, msg) return exit_code # Fingerprint try: perms = static_analysis.get_permissions() app_uid = static_analysis.get_application_universal_id() if len(app_uid) < 16: raise Exception('Unable to compute the Universal Application ID') icon_file, icon_phash = static_analysis.get_icon_and_phash(storage_helper, analysis.icon_name) if len(str(icon_phash)) < 16: raise Exception('Unable to compute the icon perceptual hash') except Exception as e: logging.info(e) msg = _('Unable to compute APK fingerprint') exit_code = save_error(storage_helper, analysis, request, msg) return exit_code # Application details try: app_info = static_analysis.get_app_info() except Exception as e: logging.info(e) msg = _('Unable to get application details from Google Play') exit_code = save_error(storage_helper, analysis, request, msg) return exit_code change_description(request, _('Get application details: success')) # Find trackers trackers = static_analysis.detect_trackers() change_description(request, _('Tracker analysis: success')) report = Report( apk_file=analysis.apk_name, storage_path='', bucket=request.bucket, class_list_file=analysis.class_list_file ) report.save() net_analysis = NetworkAnalysis(report=report) net_analysis.save() app = Application( report=report, handle=handle, version=version, version_code=version_code, name=static_analysis.get_app_name(), icon_phash=icon_phash, app_uid=app_uid ) if app_info is not None: app.name = app_info['title'] app.creator = app_info['creator'] app.downloads = app_info['downloads'] if icon_file != '': app.icon_path = analysis.icon_name app.save(force_insert=True) apk = Apk( application=app, name=analysis.apk_name, sum=shasum ) apk.save(force_insert=True) for certificate in certificates: c = Certificate( apk=apk, issuer=certificate.issuer, fingerprint=certificate.fingerprint, subject=certificate.subject, serial_number=certificate.serial ) c.save(force_insert=True) for perm in perms: p = Permission( application=app, name=perm ) p.save(force_insert=True) report.found_trackers = trackers report.save() change_description(request, _('Static analysis complete')) clear_analysis_files(storage_helper, analysis.tmp_dir, analysis.bucket, False) request.processed = True request.report_id = report.id request.save() return report.id