Files
Mobile-Security-Framework-M…/mobsf/StaticAnalyzer/views/ios/static_analyzer.py
T
Ajin Abraham 2854114b28 [HOTFIX][EFR06] Independent Shared Object (.so) Scan and Improved String search (#2228)
* String extraction from APK, Source, AAR, JAR, SO
* Strings sections to show source of strings extracted
* Strings Refactor
* Support for independent .SO scan
* Android SCA rules update
* Entropies scan support for strings
* URLs/Email extraction refactor
* Bug Fixes
  * iOS Source Report Fix
  * Frida APK Patcher (WIP)
  * Dynamic Analyzer identifier not available
  * Settings env var not working fix for enabled by default features
  * AppSec Score fix
  * Recent `scan not completed` fix for iOS zip
2023-08-14 11:47:55 -07:00

309 lines
14 KiB
Python
Executable File

# -*- coding: utf_8 -*-
"""iOS Static Code Analysis."""
import logging
import re
from pathlib import Path
import mobsf.MalwareAnalyzer.views.Trackers as Trackers
import mobsf.MalwareAnalyzer.views.VirusTotal as VirusTotal
from django.conf import settings
from django.shortcuts import render
from mobsf.MobSF.utils import (
file_size,
print_n_send_error_response,
)
from mobsf.StaticAnalyzer.models import StaticAnalyzerIOS
from mobsf.StaticAnalyzer.views.ios.appstore import app_search
from mobsf.StaticAnalyzer.views.ios.binary_analysis import binary_analysis
from mobsf.StaticAnalyzer.views.ios.code_analysis import ios_source_analysis
from mobsf.StaticAnalyzer.views.ios.db_interaction import (
get_context_from_analysis,
get_context_from_db_entry,
save_or_update)
from mobsf.StaticAnalyzer.views.ios.file_analysis import ios_list_files
from mobsf.StaticAnalyzer.views.ios.file_recon import extract_urls_n_email
from mobsf.StaticAnalyzer.views.ios.icon_analysis import (
get_icon,
get_icon_source,
)
from mobsf.StaticAnalyzer.views.ios.plist_analysis import (
get_plist_secrets,
plist_analysis,
)
from mobsf.StaticAnalyzer.views.common.shared_func import (
firebase_analysis,
get_avg_cvss,
hash_gen,
strings_and_entropies,
unzip,
update_scan_timestamp,
)
from mobsf.StaticAnalyzer.views.common.appsec import (
get_ios_dashboard,
)
logger = logging.getLogger(__name__)
##############################################################
# iOS Static Code Analysis IPA and Source Code
##############################################################
def static_analyzer_ios(request, api=False):
"""Module that performs iOS IPA/ZIP Static Analysis."""
try:
logger.info('iOS Static Analysis Started')
rescan = False
if api:
file_type = request.POST['scan_type']
checksum = request.POST['hash']
re_scan = request.POST.get('re_scan', 0)
filename = request.POST['file_name']
else:
file_type = request.GET['type']
checksum = request.GET['checksum']
re_scan = request.GET.get('rescan', 0)
filename = request.GET['name']
if re_scan == '1':
rescan = True
md5_match = re.match('^[0-9a-f]{32}$', checksum)
if ((md5_match)
and (filename.lower().endswith('.ipa')
or filename.lower().endswith('.zip'))
and (file_type in ['ipa', 'ios'])):
app_dict = {}
app_dict['directory'] = Path(settings.BASE_DIR) # BASE DIR
app_dict['file_name'] = filename # APP ORIGINAL NAME
app_dict['md5_hash'] = checksum # MD5
app_dir = Path(settings.UPLD_DIR) / checksum
tools_dir = app_dict[
'directory'] / 'StaticAnalyzer' / 'tools' / 'ios'
tools_dir = tools_dir.as_posix()
if file_type == 'ipa':
app_dict['app_file'] = app_dict[
'md5_hash'] + '.ipa' # NEW FILENAME
app_dict['app_path'] = app_dir / app_dict['app_file']
app_dict['app_path'] = app_dict['app_path'].as_posix()
# DB
ipa_db = StaticAnalyzerIOS.objects.filter(
MD5=app_dict['md5_hash'])
if ipa_db.exists() and not rescan:
context = get_context_from_db_entry(ipa_db)
else:
logger.info('iOS Binary (IPA) Analysis Started')
app_dict['app_dir'] = app_dir.as_posix() + '/'
app_dict['size'] = str(
file_size(app_dict['app_path'])) + 'MB' # FILE SIZE
app_dict['sha1'], app_dict['sha256'] = hash_gen(
app_dict['app_path']) # SHA1 & SHA256 HASHES
logger.info('Extracting IPA')
# EXTRACT IPA
unzip(app_dict['app_path'], app_dict['app_dir'])
# Identify Payload directory
dirs = app_dir.glob('**/*')
for _dir in dirs:
if 'payload' in _dir.as_posix().lower():
app_dict['bin_dir'] = app_dict['app_dir'] / _dir
break
else:
msg = ('IPA is malformed! '
'MobSF cannot find Payload directory')
if api:
return print_n_send_error_response(
request,
msg,
True)
else:
return print_n_send_error_response(
request,
msg,
False)
app_dict['bin_dir'] = app_dict['bin_dir'].as_posix() + '/'
# Get Files
all_files = ios_list_files(
app_dict['bin_dir'], app_dict['md5_hash'], True, 'ipa')
infoplist_dict = plist_analysis(app_dict['bin_dir'], False)
app_dict['appstore'] = app_search(infoplist_dict.get('id'))
app_dict['secrets'] = get_plist_secrets(
infoplist_dict['plist_xml'])
bin_analysis_dict = binary_analysis(
app_dict['bin_dir'],
tools_dir,
app_dict['app_dir'],
infoplist_dict.get('bin'))
# Get Icon
app_dict['icon_found'] = get_icon(
app_dict['md5_hash'],
app_dict['bin_dir'],
infoplist_dict.get('bin'))
# IPA URL and Email Extract
recon = extract_urls_n_email(app_dict['bin_dir'],
all_files['files_long'],
bin_analysis_dict['strings'])
# Extract Trackers from Domains
trk = Trackers.Trackers(
None, tools_dir)
trackers = trk.get_trackers_domains_or_deps(
recon['domains'], [])
code_dict = {
'api': {},
'code_anal': {},
'urlnfile': recon['urlnfile'],
'domains': recon['domains'],
'emailnfile': recon['emailnfile'],
'firebase': firebase_analysis(recon['urls_list']),
'trackers': trackers,
}
# Saving to DB
logger.info('Connecting to DB')
if rescan:
logger.info('Updating Database...')
save_or_update(
'update',
app_dict,
infoplist_dict,
code_dict,
bin_analysis_dict,
all_files)
update_scan_timestamp(app_dict['md5_hash'])
else:
logger.info('Saving to Database')
save_or_update(
'save',
app_dict,
infoplist_dict,
code_dict,
bin_analysis_dict,
all_files)
context = get_context_from_analysis(
app_dict,
infoplist_dict,
code_dict,
bin_analysis_dict,
all_files)
context['virus_total'] = None
if settings.VT_ENABLED:
vt = VirusTotal.VirusTotal()
context['virus_total'] = vt.get_result(
app_dict['app_path'],
app_dict['md5_hash'])
context['appsec'] = get_ios_dashboard(context, True)
context['average_cvss'] = get_avg_cvss(
context['binary_analysis'])
template = 'static_analysis/ios_binary_analysis.html'
if api:
return context
else:
return render(request, template, context)
elif file_type == 'ios':
ios_zip_db = StaticAnalyzerIOS.objects.filter(
MD5=app_dict['md5_hash'])
if ios_zip_db.exists() and not rescan:
context = get_context_from_db_entry(ios_zip_db)
else:
logger.info('iOS Source Code Analysis Started')
app_dict['app_file'] = app_dict[
'md5_hash'] + '.zip' # NEW FILENAME
app_dict['app_path'] = app_dir / app_dict['app_file']
app_dict['app_path'] = app_dict['app_path'].as_posix()
app_dict['app_dir'] = app_dir.as_posix() + '/'
# ANALYSIS BEGINS - Already Unzipped
app_dict['size'] = str(
file_size(app_dict['app_path'])) + 'MB' # FILE SIZE
app_dict['sha1'], app_dict['sha256'] = hash_gen(
app_dict['app_path']) # SHA1 & SHA256 HASHES
all_files = ios_list_files(
app_dict['app_dir'],
app_dict['md5_hash'],
False,
'ios')
infoplist_dict = plist_analysis(app_dict['app_dir'], True)
app_dict['appstore'] = app_search(infoplist_dict.get('id'))
app_dict['secrets'] = get_plist_secrets(
infoplist_dict['plist_xml'])
code_analysis_dic = ios_source_analysis(
app_dict['app_dir'])
ios_strs = strings_and_entropies(
Path(app_dict['app_dir']),
['.swift', '.m', '.h', '.plist'])
if ios_strs['secrets']:
app_dict['secrets'].extend(list(ios_strs['secrets']))
# Get App Icon
app_dict['icon_found'] = get_icon_source(
app_dict['md5_hash'],
app_dict['app_dir'])
# Firebase DB Check
code_analysis_dic['firebase'] = firebase_analysis(
list(set(code_analysis_dic['urls_list'])))
# Extract Trackers from Domains
trk = Trackers.Trackers(
None, tools_dir)
trackers = trk.get_trackers_domains_or_deps(
code_analysis_dic['domains'], [])
code_analysis_dic['trackers'] = trackers
fake_bin_dict = {
'checksec': {},
'libraries': [],
'bin_code_analysis': {},
'strings': list(ios_strs['strings']),
'bin_info': {},
'bin_type': code_analysis_dic['source_type'],
}
# Saving to DB
logger.info('Connecting to DB')
if rescan:
logger.info('Updating Database...')
save_or_update(
'update',
app_dict,
infoplist_dict,
code_analysis_dic,
fake_bin_dict,
all_files)
update_scan_timestamp(app_dict['md5_hash'])
else:
logger.info('Saving to Database')
save_or_update(
'save',
app_dict,
infoplist_dict,
code_analysis_dic,
fake_bin_dict,
all_files)
context = get_context_from_analysis(
app_dict,
infoplist_dict,
code_analysis_dic,
fake_bin_dict,
all_files)
context['appsec'] = get_ios_dashboard(context, True)
context['average_cvss'] = get_avg_cvss(
context['code_analysis'])
template = 'static_analysis/ios_source_analysis.html'
if api:
return context
else:
return render(request, template, context)
else:
msg = 'File Type not supported!'
if api:
return print_n_send_error_response(request, msg, True)
else:
return print_n_send_error_response(request, msg, False)
else:
msg = 'Hash match failed or Invalid file extension or file type'
if api:
return print_n_send_error_response(request, msg, True)
else:
return print_n_send_error_response(request, msg, False)
except Exception as exp:
logger.exception('Error Performing Static Analysis')
msg = str(exp)
exp_doc = exp.__doc__
if api:
return print_n_send_error_response(request, msg, True, exp_doc)
else:
return print_n_send_error_response(request, msg, False, exp_doc)