def file_size(app_path):
    """Return the size of the file."""
    return round(float(os.path.getsize(app_path)) / (1024 * 1024), 2) def print_n_send_error_response(request,
                                msg,
                                api=False,
                                exp='Description'):
    """Print and log errors."""
    logger.error(msg)
    if api:
        api_response = {'error': msg}
        return api_response
    else:
        context = {
            'title': 'Error',
            'exp': exp,
            'doc': msg,
            'version': settings.MOBSF_VER,
        }
        template = 'general/error.html'
        return render(request, template, context, status=500) class StaticAnalyzerIOS(models.Model):
    FILE_NAME = models.CharField(max_length=255, default='')
    APP_NAME = models.CharField(max_length=255, default='')
    APP_TYPE = models.CharField(max_length=20, default='')
    SIZE = models.CharField(max_length=50, default='')
    MD5 = models.CharField(max_length=32, default='', primary_key=True)
    SHA1 = models.CharField(max_length=40, default='')
    SHA256 = models.CharField(max_length=64, default='')
    BUILD = models.TextField(default='')
    APP_VERSION = models.CharField(max_length=100, default='')
    SDK_NAME = models.CharField(max_length=50, default='')
    PLATFORM = models.CharField(max_length=50, default='')
    MIN_OS_VERSION = models.CharField(max_length=50, default='')
    BUNDLE_ID = models.TextField(default='')
    BUNDLE_URL_TYPES = models.TextField(default=[])
    BUNDLE_SUPPORTED_PLATFORMS = models.CharField(max_length=50, default=[])
    ICON_FOUND = models.BooleanField(default=False)
    INFO_PLIST = models.TextField(default='')
    BINARY_INFO = models.TextField(default={})
    PERMISSIONS = models.TextField(default={})
    ATS_ANALYSIS = models.TextField(default=[])
    BINARY_ANALYSIS = models.TextField(default=[])
    MACHO_ANALYSIS = models.TextField(default={})
    IOS_API = models.TextField(default={})
    CODE_ANALYSIS = models.TextField(default={})
    FILE_ANALYSIS = models.TextField(default=[])
    LIBRARIES = models.TextField(default=[])
    FILES = models.TextField(default=[])
    URLS = models.TextField(default=[])
    DOMAINS = models.TextField(default={})
    EMAILS = models.TextField(default=[])
    STRINGS = models.TextField(default=[])
    FIREBASE_URLS = models.TextField(default=[])
    APPSTORE_DETAILS = models.TextField(default={})
    SECRETS = models.TextField(default=[])
    TRACKERS = models.TextField(default={}) def binary_analysis(src, tools_dir, app_dir, executable_name):
    """Binary Analysis of IPA."""
    bin_dict = {
        'checksec': {},
        'libraries': [],
        'bin_code_analysis': {},
        'strings': [],
        'bin_info': {},
        'bin_type': '',
    }
    try:
        binary_findings = {}
        logger.info('Starting Binary Analysis')
        dirs = Path(src).glob('*')
        dot_app_dir = ''
        bin_name = ''
        for dir_ in dirs:
            if dir_.suffix == '.app':
                dot_app_dir = dir_.as_posix()
                break
        # Bin Dir - Dir/Payload/x.app/
        bin_dir = Path(src) / dot_app_dir
        if not executable_name:
            bin_name = dot_app_dir.replace('.app', '')
        else:
            _bin = bin_dir / executable_name
            if _bin.exists():
                bin_name = executable_name
        # Bin Path - Dir/Payload/x.app/x
        bin_path = bin_dir / bin_name
        if not (bin_path.exists() or bin_path.is_file()):
            logger.warning(
                'MobSF Cannot find binary in %s', bin_path.as_posix())
            logger.warning('Skipping Binary analysis')
        else:
            macho = macho_analysis(bin_path)
            bin_info = get_bin_info(bin_path)
            bin_type = detect_bin_type(macho['libraries'])
            classdump = get_class_dump(
                tools_dir,
                bin_path,
                app_dir, bin_type)
            binary_rule_matcher(
                binary_findings,
                macho['symbols'], classdump)
            strings_in_ipa = strings_on_ipa(bin_path)
            bin_dict['checksec'] = macho['checksec']
            bin_dict['libraries'] = macho['libraries']
            bin_dict['bin_code_analysis'] = binary_findings
            bin_dict['strings'] = strings_in_ipa
            bin_dict['bin_info'] = bin_info
            bin_dict['bin_type'] = bin_type
    except Exception:
        logger.exception('IPA Binary Analysis')
    return bin_dict def ios_source_analysis(src):
    """IOS Objective-C and Swift Code Analysis."""
    try:
        logger.info('Starting iOS Source Code and PLIST Analysis')
        root = Path(settings.BASE_DIR) / 'StaticAnalyzer' / 'views'
        swift_rules = root / 'ios' / 'rules' / 'swift_rules.yaml'
        objective_c_rules = root / 'ios' / 'rules' / 'objective_c_rules.yaml'
        api_rules = root / 'ios' / 'rules' / 'ios_apis.yaml'
        code_findings = {}
        api_findings = {}
        email_n_file = []
        url_n_file = []
        url_list = []
        domains = {}
        source_type = ''
        source_types = set()

        # Code and API Analysis
        code_findings = scan(
            objective_c_rules.as_posix(),
            {'.m'},
            [src],
            settings.SKIP_CLASS_PATH)
        if code_findings:
            source_types.add(_SourceType.objc)
        code_findings.update(scan(
            swift_rules.as_posix(),
            {'.swift'},
            [src],
            settings.SKIP_CLASS_PATH))
        if code_findings:
            source_types.add(_SourceType.swift)
        api_findings = scan(
            api_rules.as_posix(),
            {'.m', '.swift'},
            [src],
            settings.SKIP_CLASS_PATH)

        # Extract URLs and Emails
        skp = settings.SKIP_CLASS_PATH
        for pfile in Path(src).rglob('*'):
            if (
                (pfile.suffix in ('.m', '.swift')
                    and any(skip_path in pfile.as_posix()
                            for skip_path in skp) is False
                    and pfile.is_dir() is False)
            ):
                relative_java_path = pfile.as_posix().replace(src, '')
                urls, urls_nf, emails_nf = url_n_email_extract(
                    pfile.read_text('utf-8', 'ignore'), relative_java_path)
                url_list.extend(urls)
                url_n_file.extend(urls_nf)
                email_n_file.extend(emails_nf)

        if not source_types:
            source_type = _SourceType.nocode.value
        elif len(source_types) > 1:
            source_type = _SourceType.swift_and_objc.value
        else:
            source_type = source_types.pop().value

        urls_list = list(set(url_list))
        # Domain Extraction and Malware Check
        logger.info('Performing Malware Check on extracted Domains')
        domains = MalwareDomainCheck().scan(urls_list)
        logger.info('Finished Code Analysis, Email and URL Extraction')
        code_analysis_dict = {
            'api': api_findings,
            'code_anal': code_findings,
            'urls_list': urls_list,
            'urlnfile': url_n_file,
            'domains': domains,
            'emailnfile': email_n_file,
            'source_type': source_type,
        }
        return code_analysis_dict

    except Exception:
        logger.exception('iOS Source Code Analysis') def get_context_from_analysis(app_dict,
                              info_dict,
                              code_dict,
                              bin_dict,
                              all_files):
    """Get the context for IPA/ZIP from analysis results."""
    try:
        context = {
            'version': settings.MOBSF_VER,
            'title': 'Static Analysis',
            'file_name': app_dict['file_name'],
            'app_name': info_dict['bin_name'],
            'app_type': bin_dict['bin_type'],
            'size': app_dict['size'],
            'md5': app_dict['md5_hash'],
            'sha1': app_dict['sha1'],
            'sha256': app_dict['sha256'],
            'build': info_dict['build'],
            'app_version': info_dict['bundle_version_name'],
            'sdk_name': info_dict['sdk'],
            'platform': info_dict['pltfm'],
            'min_os_version': info_dict['min'],
            'bundle_id': info_dict['id'],
            'bundle_url_types': info_dict['bundle_url_types'],
            'bundle_supported_platforms':
                info_dict['bundle_supported_platforms'],
            'icon_found': app_dict['icon_found'],
            'info_plist': info_dict['plist_xml'],
            'binary_info': bin_dict['bin_info'],
            'permissions': info_dict['permissions'],
            'ats_analysis': info_dict['inseccon'],
            'binary_analysis': bin_dict['bin_code_analysis'],
            'macho_analysis': bin_dict['checksec'],
            'ios_api': code_dict['api'],
            'code_analysis': code_dict['code_anal'],
            'file_analysis': all_files['special_files'],
            'libraries': bin_dict['libraries'],
            'files': all_files['files_short'],
            'urls': code_dict['urlnfile'],
            'domains': code_dict['domains'],
            'emails': code_dict['emailnfile'],
            'strings': bin_dict['strings'],
            'firebase_urls': code_dict['firebase'],
            'appstore_details': app_dict['appstore'],
            'secrets': app_dict['secrets'],
            'trackers': code_dict['trackers'],
        }
        return context
    except Exception:
        logger.exception('Rendering to Template') def get_context_from_db_entry(db_entry):
    """Return the context for IPA/ZIP from DB."""
    try:
        logger.info('Analysis is already Done. Fetching data from the DB...')
        context = {
            'version': settings.MOBSF_VER,
            'title': 'Static Analysis',
            'file_name': db_entry[0].FILE_NAME,
            'app_name': db_entry[0].APP_NAME,
            'app_type': db_entry[0].APP_TYPE,
            'size': db_entry[0].SIZE,
            'md5': db_entry[0].MD5,
            'sha1': db_entry[0].SHA1,
            'sha256': db_entry[0].SHA256,
            'build': db_entry[0].BUILD,
            'app_version': db_entry[0].APP_VERSION,
            'sdk_name': db_entry[0].SDK_NAME,
            'platform': db_entry[0].PLATFORM,
            'min_os_version': db_entry[0].MIN_OS_VERSION,
            'bundle_id': db_entry[0].BUNDLE_ID,
            'bundle_url_types': python_list(db_entry[0].BUNDLE_URL_TYPES),
            'bundle_supported_platforms':
                python_list(db_entry[0].BUNDLE_SUPPORTED_PLATFORMS),
            'icon_found': db_entry[0].ICON_FOUND,
            'info_plist': db_entry[0].INFO_PLIST,
            'binary_info': python_dict(db_entry[0].BINARY_INFO),
            'permissions': python_dict(db_entry[0].PERMISSIONS),
            'ats_analysis': python_list(db_entry[0].ATS_ANALYSIS),
            'binary_analysis': python_list(db_entry[0].BINARY_ANALYSIS),
            'macho_analysis': python_dict(db_entry[0].MACHO_ANALYSIS),
            'ios_api': python_dict(db_entry[0].IOS_API),
            'code_analysis': python_dict(db_entry[0].CODE_ANALYSIS),
            'file_analysis': python_list(db_entry[0].FILE_ANALYSIS),
            'libraries': python_list(db_entry[0].LIBRARIES),
            'files': python_list(db_entry[0].FILES),
            'urls': python_list(db_entry[0].URLS),
            'domains': python_dict(db_entry[0].DOMAINS),
            'emails': python_list(db_entry[0].EMAILS),
            'strings': python_list(db_entry[0].STRINGS),
            'firebase_urls': python_list(db_entry[0].FIREBASE_URLS),
            'appstore_details': python_dict(db_entry[0].APPSTORE_DETAILS),
            'secrets': python_list(db_entry[0].SECRETS),
            'trackers': python_dict(db_entry[0].TRACKERS),

        }
        return context
    except Exception:
        logger.exception('Fetching from DB') def save_or_update(update_type,
                   app_dict,
                   info_dict,
                   code_dict,
                   bin_dict,
                   all_files):
    """Save/Update an IPA/ZIP DB entry."""
    try:
        values = {
            'FILE_NAME': app_dict['file_name'],
            'APP_NAME': info_dict['bin_name'],
            'APP_TYPE': bin_dict['bin_type'],
            'SIZE': app_dict['size'],
            'MD5': app_dict['md5_hash'],
            'SHA1': app_dict['sha1'],
            'SHA256': app_dict['sha256'],
            'BUILD': info_dict['build'],
            'APP_VERSION': info_dict['bundle_version_name'],
            'SDK_NAME': info_dict['sdk'],
            'PLATFORM': info_dict['pltfm'],
            'MIN_OS_VERSION': info_dict['min'],
            'BUNDLE_ID': info_dict['id'],
            'BUNDLE_URL_TYPES': info_dict['bundle_url_types'],
            'BUNDLE_SUPPORTED_PLATFORMS':
                info_dict['bundle_supported_platforms'],
            'ICON_FOUND': app_dict['icon_found'],
            'INFO_PLIST': info_dict['plist_xml'],
            'BINARY_INFO': bin_dict['bin_info'],
            'PERMISSIONS': info_dict['permissions'],
            'ATS_ANALYSIS': info_dict['inseccon'],
            'BINARY_ANALYSIS': bin_dict['bin_code_analysis'],
            'MACHO_ANALYSIS': bin_dict['checksec'],
            'IOS_API': code_dict['api'],
            'CODE_ANALYSIS': code_dict['code_anal'],
            'FILE_ANALYSIS': all_files['special_files'],
            'LIBRARIES': bin_dict['libraries'],
            'FILES': all_files['files_short'],
            'URLS': code_dict['urlnfile'],
            'DOMAINS': code_dict['domains'],
            'EMAILS': code_dict['emailnfile'],
            'STRINGS': bin_dict['strings'],
            'FIREBASE_URLS': code_dict['firebase'],
            'APPSTORE_DETAILS': app_dict['appstore'],
            'SECRETS': app_dict['secrets'],
            'TRACKERS': code_dict['trackers'],
        }
        if update_type == 'save':
            db_entry = StaticAnalyzerIOS.objects.filter(
                MD5=app_dict['md5_hash'])
            if not db_entry.exists():
                StaticAnalyzerIOS.objects.create(**values)
        else:
            StaticAnalyzerIOS.objects.filter(
                MD5=app_dict['md5_hash']).update(**values)
    except Exception:
        logger.exception('Updating DB')
    try:
        values = {
            'APP_NAME': info_dict['bin_name'],
            'PACKAGE_NAME': info_dict['id'],
            'VERSION_NAME': info_dict['bundle_version_name'],
        }
        RecentScansDB.objects.filter(
            MD5=app_dict['md5_hash']).update(**values)
    except Exception:
        logger.exception('Updating RecentScansDB') def extract_urls_n_email(src, all_files, strings):
    """IPA URL and Email Extraction."""
    try:
        logger.info('Starting IPA URL and Email Extraction')
        email_n_file = []
        url_n_file = []
        url_list = []
        domains = {}
        all_files.append({'data': strings, 'name': 'IPA Strings Dump'})
        for file in all_files:
            if isinstance(file, dict):
                relative_src_path = file['name']
                dat = '\n'.join(file['data'])
            # Skip CodeResources and contents under Frameworks
            elif 'CodeResources' in file or '/Frameworks/' in file:
                continue
            elif file.endswith(('.nib', '.ttf', '.svg', '.woff2',
                                '.png', '.dylib', '.mobileprovision',
                                'Assets.car')):
                continue
            else:
                dat = ''
                relative_src_path = file.replace(src, '')
                with io.open(file,
                             mode='r',
                             encoding='utf8',
                             errors='ignore') as flip:
                    dat = flip.read()
            # Extract URLs and Emails from Plists
            urls, urls_nf, emails_nf = url_n_email_extract(
                dat, relative_src_path)
            url_list.extend(urls)
            url_n_file.extend(urls_nf)
            email_n_file.extend(emails_nf)
        # Unique URLs
        urls_list = list(set(url_list))
        # Domain Extraction and Malware Check
        logger.info('Performing Malware Check on extracted Domains')
        domains = MalwareDomainCheck().scan(urls_list)
        logger.info('Finished URL and Email Extraction')
        binary_recon = {
            'urls_list': urls_list,
            'urlnfile': url_n_file,
            'domains': domains,
            'emailnfile': email_n_file,
        }
        return binary_recon

    except Exception:
        logger.exception('IPA URL and Email Extraction') def get_plist_secrets(xml_string):
    """Get possible hardcoded secrets from plist."""
    result_list = []

    def _remove_tags(data):
        """Remove tags from input."""
        return sub('<[^<]+>', '', data).strip()

    xml_list = xml_string.split('\n')

    for index, line in enumerate(xml_list):
        if '<key>' in line and is_secret(_remove_tags(line)):
            nxt = index + 1
            value = (
                _remove_tags(xml_list[nxt])if nxt < len(xml_list) else False)
            if value and ' ' not in value:
                result_list.append(
                    f'{_remove_tags(line)} : {_remove_tags(value)}')
    return result_list def plist_analysis(src, is_source):
    """Plist Analysis."""
    try:
        logger.info('iOS Info.plist Analysis Started')
        plist_info = {
            'bin_name': '',
            'bin': '',
            'id': '',
            'version': '',
            'build': '',
            'sdk': '',
            'pltfm': '',
            'min': '',
            'plist_xml': '',
            'permissions': {},
            'inseccon': [],
            'bundle_name': '',
            'build_version_name': '',
            'bundle_url_types': [],
            'bundle_supported_platforms': [],
            'bundle_version_name': '',
        }
        plist_file = None
        plist_files = []
        if is_source:
            logger.info('Finding Info.plist in iOS Source')
            for dirpath, _dirnames, files in os.walk(src):
                for name in files:
                    if (not any(x in dirpath for x in ['__MACOSX', 'Pods'])
                            and name.endswith('.plist')):
                        plist_files.append(os.path.join(dirpath, name))
                        if name == 'Info.plist':
                            plist_file = os.path.join(dirpath, name)
        else:
            logger.info('Finding Info.plist in iOS Binary')
            dirs = os.listdir(src)
            dot_app_dir = ''
            for dir_ in dirs:
                if dir_.endswith('.app'):
                    dot_app_dir = dir_
                    break
            bin_dir = os.path.join(src, dot_app_dir)  # Full Dir/Payload/x.app
            plist_file = os.path.join(bin_dir, 'Info.plist')
            plist_files = [plist_file]

        # Skip Plist Analysis if there is no Info.plist
        if not plist_file or not is_file_exists(plist_file):
            logger.warning(
                'Cannot find Info.plist file. Skipping Plist Analysis.')
            return plist_info

        # Generic Plist Analysis
        plist_obj = {}
        with open(plist_file, 'rb') as fp:
            plist_obj = load(fp)
        plist_info['plist_xml'] = dumps(
            plist_obj).decode('utf-8', 'ignore')
        plist_info['bin_name'] = (plist_obj.get('CFBundleDisplayName', '')
                                  or plist_obj.get('CFBundleName', ''))
        if not plist_info['bin_name'] and not is_source:
            # For iOS IPA
            plist_info['bin_name'] = dot_app_dir.replace('.app', '')
        plist_info['bin'] = plist_obj.get('CFBundleExecutable', '')
        plist_info['id'] = plist_obj.get('CFBundleIdentifier', '')
        plist_info['build'] = plist_obj.get('CFBundleVersion', '')
        plist_info['sdk'] = plist_obj.get('DTSDKName', '')
        plist_info['pltfm'] = plist_obj.get('DTPlatformVersion', '')
        plist_info['min'] = plist_obj.get('MinimumOSVersion', '')
        plist_info['bundle_name'] = plist_obj.get('CFBundleName', '')
        plist_info['bundle_version_name'] = plist_obj.get(
            'CFBundleShortVersionString', '')
        btype = plist_obj.get('CFBundleURLTypes', [])
        if btype and isinstance(btype, dict):
            # Fixes bugs like # 1885
            btype = [btype]
        plist_info['bundle_url_types'] = btype
        plist_info['bundle_supported_platforms'] = plist_obj.get(
            'CFBundleSupportedPlatforms', [])
        logger.info('Checking Permissions')
        logger.info('Checking for Insecure Connections')
        for plist_file_ in plist_files:
            plist_obj_ = {}
            with open(plist_file_, 'rb') as fp:
                plist_obj_ = load(fp)
            # Check for app-permissions
            plist_info['permissions'].update(check_permissions(plist_obj_))
            # Check for ats misconfigurations
            plist_info['inseccon'] += check_transport_security(plist_obj_)
        return plist_info
    except Exception:
        logger.exception('Reading from Info.plist') def firebase_analysis(urls):
    # Detect Firebase URL
    firebase_db = []
    logger.info('Detecting Firebase URL(s)')
    for url in urls:
        if 'firebaseio.com' in url:
            returl, is_open = open_firebase(url)
            fbdic = {'url': returl, 'open': is_open}
            if fbdic not in firebase_db:
                firebase_db.append(fbdic)
    return firebase_db def get_avg_cvss(findings):
    # Average CVSS Score
    cvss_scores = []
    avg_cvss = 0
    for finding in findings.values():
        find = finding.get('metadata')
        if not find:
            # Hack to support iOS Binary Scan Results
            find = finding
        if find.get('cvss'):
            if find['cvss'] != 0:
                cvss_scores.append(find['cvss'])
    if cvss_scores:
        avg_cvss = round(sum(cvss_scores) / len(cvss_scores), 1)
    if not getattr(settings, 'CVSS_SCORE_ENABLED', False):
        avg_cvss = None
    return avg_cvss def hash_gen(app_path) -> tuple:
    """Generate and return sha1 and sha256 as a tuple."""
    try:
        logger.info('Generating Hashes')
        sha1 = hashlib.sha1()
        sha256 = hashlib.sha256()
        block_size = 65536
        with io.open(app_path, mode='rb') as afile:
            buf = afile.read(block_size)
            while buf:
                sha1.update(buf)
                sha256.update(buf)
                buf = afile.read(block_size)
        sha1val = sha1.hexdigest()
        sha256val = sha256.hexdigest()
        return sha1val, sha256val
    except Exception:
        logger.exception('Generating Hashes') def unzip(app_path, ext_path):
    logger.info('Unzipping')
    try:
        files = []
        with zipfile.ZipFile(app_path, 'r') as zipptr:
            for fileinfo in zipptr.infolist():
                filename = fileinfo.filename
                if not isinstance(filename, str):
                    filename = str(
                        filename, encoding='utf-8', errors='replace')
                files.append(filename)
                zipptr.extract(filename, ext_path)
        return files
    except Exception:
        logger.exception('Unzipping Error')
        if platform.system() == 'Windows':
            logger.info('Not yet Implemented.')
        else:
            logger.info('Using the Default OS Unzip Utility.')
            try:
                unzip_b = shutil.which('unzip')
                subprocess.call(
                    [unzip_b, '-o', '-q', app_path, '-d', ext_path])
                dat = subprocess.check_output([unzip_b, '-qq', '-l', app_path])
                dat = dat.decode('utf-8').split('\n')
                files_det = ['Length   Date   Time   Name']
                files_det = files_det + dat
                return files_det
            except Exception:
                logger.exception('Unzipping Error') def update_scan_timestamp(scan_hash):
    # Update the last scan time.
    tms = timezone.now()
    RecentScansDB.objects.filter(MD5=scan_hash).update(TIMESTAMP=tms) def get_ios_dashboard(context, from_ctx=False):
    """Get iOS AppSec Dashboard."""
    findings = {
        'high': [],
        'warning': [],
        'info': [],
        'secure': [],
        'hotspot': [],
        'total_trackers': None,
    }
    if from_ctx:
        data = context
    else:
        data = idb(context)
    # Transport Security
    for n in data['ats_analysis']:
        findings[n['severity']].append({
            'title': n['issue'],
            'description': n['description'],
            'section': 'network',
        })
    # Binary Code Analysis
    for issue, cd in data['binary_analysis'].items():
        if cd['severity'] == 'good':
            sev = 'secure'
        else:
            sev = cd['severity']
        findings[sev].append({
            'title': issue,
            'description': str(cd['detailed_desc']),
            'section': 'binary',
        })
    # Macho Analysis
    ma = data['macho_analysis']
    if ma:
        nx = ma['nx']
        if nx['severity'] in {'high', 'warning'}:
            findings[nx['severity']].append({
                'title': 'NX bit is not set properly for this application',
                'description': nx['description'],
                'section': 'macho',
            })
        pie = ma['pie']
        if pie['severity'] in {'high', 'warning'}:
            findings[pie['severity']].append({
                'title': (
                    'PIE flag is not configured securely'
                    ' for this application binary'),
                'description': pie['description'],
                'section': 'macho',
            })
        stack_canary = ma['stack_canary']
        if stack_canary['severity'] in {'high', 'warning'}:
            findings[stack_canary['severity']].append({
                'title': (
                    'Stack Canary is not properly '
                    'configured for this application'),
                'description': stack_canary['description'],
                'section': 'macho',
            })
        arc = ma['arc']
        if arc['severity'] in {'high', 'warning'}:
            findings[arc['severity']].append({
                'title': 'Application binary is not compiled with ARC flag',
                'description': arc['description'],
                'section': 'macho',
            })
        rpath = ma['rpath']
        if rpath['severity'] in {'high', 'warning'}:
            findings[rpath['severity']].append({
                'title': 'Application binary has rpath set',
                'description': rpath['description'],
                'section': 'macho',
            })
        symbol = ma['symbol']
        if symbol['severity'] in {'high', 'warning'}:
            findings[symbol['severity']].append({
                'title': 'Application binary does not have symbols stripped',
                'description': symbol['description'],
                'section': 'macho',
            })
    common_fields(findings, data)
    findings['version_name'] = data.get('app_version', '')
    return findings
import logging
import re
import mobsf.MalwareAnalyzer.views.Trackers as Trackers
import mobsf.MalwareAnalyzer.views.VirusTotal as VirusTotal
from pathlib import Path
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,
    unzip,
    update_scan_timestamp,
)
from mobsf.StaticAnalyzer.views.common.appsec import (
    get_ios_dashboard,
)
# -*- coding: utf_8 -*-
"""iOS Static Code Analysis."""




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)