예제 #1
0
def run(request):
    """Generate downloads for apk, java and smali."""
    try:
        logger.info('Generating Downloads')
        md5 = request.GET['hash']
        file_type = request.GET['file_type']
        match = re.match('^[0-9a-f]{32}$', md5)
        if not match and file_type not in ['apk', 'smali', 'java']:
            logger.exception('Invalid options')
            return print_n_send_error_response(request, 'Invalid options')
        app_dir = os.path.join(settings.UPLD_DIR, md5)
        file_name = ''
        if file_type == 'java':
            # For Java
            file_name = md5 + '-java'
            directory = os.path.join(app_dir, 'java_source/')
            dwd_dir = os.path.join(settings.DWD_DIR, file_name)
            shutil.make_archive(dwd_dir, 'zip', directory)
            file_name = file_name + '.zip'
        elif file_type == 'smali':
            # For Smali
            file_name = md5 + '-smali'
            directory = os.path.join(app_dir, 'smali_source/')
            dwd_dir = os.path.join(settings.DWD_DIR, file_name)
            shutil.make_archive(dwd_dir, 'zip', directory)
            file_name = file_name + '.zip'
        elif file_type == 'apk':
            file_name = md5 + '.apk'
            src = os.path.join(app_dir, file_name)
            dst = os.path.join(settings.DWD_DIR, file_name)
            shutil.copy2(src, dst)
        return redirect('/download/' + file_name)
    except Exception:
        logger.exception('Generating Downloads')
        return print_n_send_error_response(request, 'Generating Downloads')
def logcat(request, api=False):
    logger.info('Starting Logcat streaming')
    try:
        pkg = request.GET.get('package')
        if pkg:
            if not strict_package_check(pkg):
                return print_n_send_error_response(request,
                                                   'Invalid package name', api)
            template = 'dynamic_analysis/android/logcat.html'
            return render(request, template, {'package': pkg})
        if api:
            app_pkg = request.POST['package']
        else:
            app_pkg = request.GET.get('app_package')
        if app_pkg:
            if not strict_package_check(app_pkg):
                return print_n_send_error_response(request,
                                                   'Invalid package name', api)
            adb = os.environ['MOBSF_ADB']
            g = proc.Group()
            g.run([adb, 'logcat', app_pkg + ':V', '*:*'])

            def read_process():
                while g.is_pending():
                    lines = g.readlines()
                    for _, line in lines:
                        yield 'data:{}\n\n'.format(line)

            return StreamingHttpResponse(read_process(),
                                         content_type='text/event-stream')
        return print_n_send_error_response(request, 'Invalid parameters', api)
    except Exception:
        logger.exception('Logcat Streaming')
        err = 'Error in Logcat streaming'
        return print_n_send_error_response(request, err, api)
def appsec_dashboard(request, checksum, api=False):
    """Provide data for appsec dashboard."""
    try:
        android_static_db = StaticAnalyzerAndroid.objects.filter(
            MD5=checksum)
        ios_static_db = StaticAnalyzerIOS.objects.filter(
            MD5=checksum)
        if android_static_db.exists():
            context = get_android_dashboard(android_static_db)
        elif ios_static_db.exists():
            context = get_ios_dashboard(ios_static_db)
        else:
            if api:
                return {'not_found': 'Report not found or supported'}
            else:
                msg = 'Report not found or supported'
                return print_n_send_error_response(request, msg)
        context['version'] = settings.MOBSF_VER
        context['title'] = 'AppSec Scorecard'
        context['efr01'] = True if settings.EFR_01 == '1' else False
        if api:
            return context
        else:
            return render(
                request,
                'static_analysis/appsec_dashboard.html',
                context)
    except Exception as exp:
        logger.exception('Error Generating Application Security Dashboard')
        msg = str(exp)
        exp = exp.__doc__
        if api:
            return print_n_send_error_response(request, msg, True, exp)
        else:
            return print_n_send_error_response(request, msg, False, exp)
def dynamic_analysis(request, api=False):
    """Android Dynamic Analysis Entry point."""
    try:
        scan_apps = []
        device_packages = {}
        and_ver = None
        and_sdk = None
        apks = StaticAnalyzerAndroid.objects.filter(
            APP_TYPE='apk')
        for apk in reversed(apks):
            temp_dict = {
                'ICON_FOUND': apk.ICON_FOUND,
                'MD5': apk.MD5,
                'APP_NAME': apk.APP_NAME,
                'VERSION_NAME': apk.VERSION_NAME,
                'FILE_NAME': apk.FILE_NAME,
                'PACKAGE_NAME': apk.PACKAGE_NAME,
            }
            scan_apps.append(temp_dict)
        try:
            identifier = get_device()
        except Exception:
            msg = ('Is Android VM running? MobSF cannot'
                   ' find android instance identifier.'
                   ' Please run an android instance and refresh'
                   ' this page. If this error persists,'
                   ' set ANALYZER_IDENTIFIER in '
                   f'{get_config_loc()}')
            return print_n_send_error_response(request, msg, api)
        try:
            if identifier:
                env = Environment(identifier)
                device_packages = env.get_device_packages()
                pkg_file = Path(settings.DWD_DIR) / 'packages.json'
                with pkg_file.open('w', encoding='utf-8') as target:
                    dump(device_packages, target)
                and_ver = env.get_android_version()
                and_sdk = env.get_android_sdk()
        except Exception:
            pass
        context = {'apps': scan_apps,
                   'identifier': identifier,
                   'android_version': and_ver,
                   'android_sdk': and_sdk,
                   'proxy_ip': get_proxy_ip(identifier),
                   'proxy_port': settings.PROXY_PORT,
                   'settings_loc': get_config_loc(),
                   'device_packages': device_packages,
                   'title': 'MobSF Dynamic Analysis',
                   'version': settings.MOBSF_VER}
        if api:
            return context
        template = 'dynamic_analysis/dynamic_analysis.html'
        return render(request, template, context)
    except Exception as exp:
        logger.exception('Dynamic Analysis')
        return print_n_send_error_response(request,
                                           exp,
                                           api)
def view_report(request, checksum, api=False):
    """Dynamic Analysis Report Generation."""
    logger.info('Dynamic Analysis Report Generation')
    try:
        droidmon = {}
        apimon = {}
        if not is_md5(checksum):
            # We need this check since checksum is not validated
            # in REST API
            return print_n_send_error_response(
                request,
                'Invalid Parameters',
                api)
        package = get_package_name(checksum)
        if not package:
            return print_n_send_error_response(
                request,
                'Invalid Parameters',
                api)
        app_dir = os.path.join(settings.UPLD_DIR, checksum + '/')
        download_dir = settings.DWD_DIR
        if not is_file_exists(os.path.join(app_dir, 'logcat.txt')):
            msg = ('Dynamic Analysis report is not available '
                   'for this app. Perform Dynamic Analysis '
                   'and generate the report.')
            return print_n_send_error_response(request, msg, api)
        fd_log = os.path.join(app_dir, 'mobsf_frida_out.txt')
        droidmon = droidmon_api_analysis(app_dir, package)
        apimon = apimon_analysis(app_dir)
        analysis_result = run_analysis(app_dir, checksum, package)
        generate_download(app_dir, checksum, download_dir, package)
        images = get_screenshots(checksum, download_dir)
        context = {'hash': checksum,
                   'emails': analysis_result['emails'],
                   'urls': analysis_result['urls'],
                   'domains': analysis_result['domains'],
                   'clipboard': analysis_result['clipboard'],
                   'xml': analysis_result['xml'],
                   'sqlite': analysis_result['sqlite'],
                   'others': analysis_result['other_files'],
                   'tls_tests': analysis_result['tls_tests'],
                   'screenshots': images['screenshots'],
                   'activity_tester': images['activities'],
                   'exported_activity_tester': images['exported_activities'],
                   'droidmon': droidmon,
                   'apimon': apimon,
                   'frida_logs': is_file_exists(fd_log),
                   'package': package,
                   'version': settings.MOBSF_VER,
                   'title': 'Dynamic Analysis'}
        template = 'dynamic_analysis/android/dynamic_report.html'
        if api:
            return context
        return render(request, template, context)
    except Exception as exp:
        logger.exception('Dynamic Analysis Report Generation')
        err = 'Error Geneating Dynamic Analysis Report. ' + str(exp)
        return print_n_send_error_response(request, err, api)
def view_file(request, api=False):
    """View File."""
    logger.info('Viewing File')
    try:
        typ = ''
        rtyp = ''
        dat = ''
        sql_dump = {}
        if api:
            fil = request.POST['file']
            md5_hash = request.POST['hash']
            typ = request.POST['type']
        else:
            fil = request.GET['file']
            md5_hash = request.GET['hash']
            typ = request.GET['type']
        if not is_md5(md5_hash):
            return print_n_send_error_response(request, 'Invalid Parameters',
                                               api)
        src = os.path.join(settings.UPLD_DIR, md5_hash, 'DYNAMIC_DeviceData/')
        sfile = os.path.join(src, fil)
        if not is_safe_path(src, sfile) or is_path_traversal(fil):
            err = 'Path Traversal Attack Detected'
            return print_n_send_error_response(request, err, api)
        with io.open(
                sfile,  # lgtm [py/path-injection]
                mode='r',
                encoding='ISO-8859-1') as flip:
            dat = flip.read()
        if fil.endswith('.xml') and typ == 'xml':
            rtyp = 'xml'
        elif typ == 'db':
            dat = None
            sql_dump = read_sqlite(sfile)
            rtyp = 'asciidoc'
        elif typ == 'others':
            rtyp = 'asciidoc'
        else:
            err = 'File type not supported'
            return print_n_send_error_response(request, err, api)
        fil = escape(ntpath.basename(fil))
        context = {
            'title': fil,
            'file': fil,
            'data': dat,
            'sqlite': sql_dump,
            'type': rtyp,
            'version': settings.MOBSF_VER,
        }
        template = 'general/view.html'
        if api:
            return context
        return render(request, template, context)
    except Exception:
        logger.exception('Viewing File')
        return print_n_send_error_response(request, 'Error Viewing File', api)
def compare_apps(request, hash1: str, hash2: str, api=False):
    if hash1 == hash2:
        error_msg = 'Results with same hash cannot be compared'
        return print_n_send_error_response(request, error_msg, api)
    # Second Validation for REST API
    if not (is_md5(hash1) and is_md5(hash2)):
        error_msg = 'Invalid hashes'
        return print_n_send_error_response(request, error_msg, api)
    logger.info('Starting App compare for %s and %s', hash1, hash2)
    return generic_compare(request, hash1, hash2, api)
예제 #8
0
def run(request):
    """Find filename/content in source files (ajax response)."""
    try:
        match = re.match('^[0-9a-f]{32}$', request.POST['md5'])
        if not match:
            raise ValueError('Invalid MD5 hash')
        md5 = request.POST['md5']
        query = request.POST['q']
        code = request.POST['code']
        search_type = request.POST['search_type']
        if search_type not in ['content', 'filename']:
            return print_n_send_error_response(request,
                                               'Unknown search type',
                                               True)
        matches = set()
        base = Path(settings.UPLD_DIR) / md5
        if code == 'smali':
            src = base / 'smali_source'
        else:
            try:
                src = find_java_source_folder(base)[0]
            except StopIteration:
                msg = 'Invalid Directory Structure'
                return print_n_send_error_response(request, msg, True)

        exts = ['.java', '.kt', '.smali']
        files = [p for p in src.rglob('*') if p.suffix in exts]
        for fname in files:
            file_path = fname.as_posix()
            rpath = file_path.replace(src.as_posix(), '')
            rpath = rpath[1:]
            if search_type == 'content':
                dat = fname.read_text('utf-8', 'ignore')
                if query.lower() in dat.lower():
                    matches.add(escape(rpath))
            elif search_type == 'filename' and \
                    query.lower() in fname.name.lower():
                matches.add(escape(rpath))

        flz = len(matches)
        context = {
            'title': 'Search Results',
            'matches': list(matches),
            'term': query,
            'found': str(flz),
            'search_type': search_type,
            'version': settings.MOBSF_VER,
        }
        return JsonResponse(json.dumps(context), safe=False)
    except Exception:
        logger.exception('Searching Failed')
        return print_n_send_error_response(
            request,
            'Searching Failed',
            True)
예제 #9
0
def run(request, api=False):
    """View the source of a file."""
    try:
        logger.info('View Android Source File')
        exp = 'Error Description'
        if api:
            fil = request.POST['file']
            md5 = request.POST['hash']
            typ = request.POST['type']
            viewsource_form = ViewSourceAndroidApiForm(request.POST)
        else:
            fil = request.GET['file']
            md5 = request.GET['md5']
            typ = request.GET['type']
            viewsource_form = ViewSourceAndroidForm(request.GET)
        if not viewsource_form.is_valid():
            err = FormUtil.errors_message(viewsource_form)
            return print_n_send_error_response(request, err, api, exp)

        base = Path(settings.UPLD_DIR) / md5
        if typ == 'smali':
            src = base / 'smali_source'
            syntax = 'smali'
        else:
            try:
                src, syntax, _ = find_java_source_folder(base)
            except StopIteration:
                msg = 'Invalid Directory Structure'
                return print_n_send_error_response(request, msg, api)

        sfile = src / fil
        if not is_safe_path(src, sfile.as_posix()):
            msg = 'Path Traversal Detected!'
            return print_n_send_error_response(request, msg, api)
        context = {
            'title': escape(ntpath.basename(fil)),
            'file': escape(ntpath.basename(fil)),
            'data': sfile.read_text('utf-8', 'ignore'),
            'type': syntax,
            'sqlite': {},
            'version': settings.MOBSF_VER,
        }
        template = 'general/view.html'
        if api:
            return context
        return render(request, template, context)
    except Exception as exp:
        logger.exception('Error Viewing Source')
        msg = str(exp)
        exp = exp.__doc__
        return print_n_send_error_response(request, msg, api, exp)
예제 #10
0
def frida_logs(request, api=False):
    try:
        if api:
            apphash = request.POST['hash']
            stream = True
        else:
            apphash = request.GET.get('hash', '')
            stream = request.GET.get('stream', '')
        if not is_md5(apphash):
            return invalid_params(api)
        if stream:
            apk_dir = os.path.join(settings.UPLD_DIR, apphash + '/')
            frida_logs = os.path.join(apk_dir, 'mobsf_frida_out.txt')
            data = {}
            if not is_file_exists(frida_logs):
                data = {'status': 'failed', 'message': 'Data does not exist.'}
                return send_response(data, api)
            with open(frida_logs, 'r', encoding='utf8',
                      errors='ignore') as flip:
                data = {'data': flip.read()}
            return send_response(data, api)
        logger.info('Frida Logs live streaming')
        template = 'dynamic_analysis/android/frida_logs.html'
        return render(
            request, template, {
                'hash': apphash,
                'package': request.GET.get('package', ''),
                'version': settings.MOBSF_VER,
                'title': 'Live Frida logs'
            })
    except Exception:
        logger.exception('Frida log streaming')
        err = 'Error in Frida log streaming'
        return print_n_send_error_response(request, err, api)
예제 #11
0
def live_api(request, api=False):
    try:
        if api:
            apphash = request.POST['hash']
            stream = True
        else:
            apphash = request.GET.get('hash', '')
            stream = request.GET.get('stream', '')
        if not is_md5(apphash):
            return invalid_params(api)
        if stream:
            apk_dir = os.path.join(settings.UPLD_DIR, apphash + '/')
            apimon_file = os.path.join(apk_dir, 'mobsf_api_monitor.txt')
            data = {}
            if not is_file_exists(apimon_file):
                data = {'status': 'failed', 'message': 'Data does not exist.'}
                return send_response(data, api)
            with open(apimon_file, 'r', encoding='utf8',
                      errors='ignore') as flip:
                api_list = json.loads('[{}]'.format(flip.read()[:-1]))
            data = {'data': api_list}
            return send_response(data, api)
        logger.info('Starting API monitor streaming')
        template = 'dynamic_analysis/android/live_api.html'
        return render(
            request, template, {
                'hash': apphash,
                'package': request.GET.get('package', ''),
                'version': settings.MOBSF_VER,
                'title': 'Live API Monitor'
            })
    except Exception:
        logger.exception('API monitor streaming')
        err = 'Error in API monitor streaming'
        return print_n_send_error_response(request, err, api)
예제 #12
0
def delete_scan(request, api=False):
    """Delete Scan from DB and remove the scan related files."""
    try:
        if request.method == 'POST':
            if api:
                md5_hash = request.POST['hash']
            else:
                md5_hash = request.POST['md5']
            data = {'deleted': 'scan hash not found'}
            if re.match('[0-9a-f]{32}', md5_hash):
                # Delete DB Entries
                scan = RecentScansDB.objects.filter(MD5=md5_hash)
                if scan.exists():
                    RecentScansDB.objects.filter(MD5=md5_hash).delete()
                    StaticAnalyzerAndroid.objects.filter(MD5=md5_hash).delete()
                    StaticAnalyzerIOS.objects.filter(MD5=md5_hash).delete()
                    StaticAnalyzerWindows.objects.filter(MD5=md5_hash).delete()
                    # Delete Upload Dir Contents
                    app_upload_dir = os.path.join(settings.UPLD_DIR, md5_hash)
                    if is_dir_exists(app_upload_dir):
                        shutil.rmtree(app_upload_dir)
                    # Delete Download Dir Contents
                    dw_dir = settings.DWD_DIR
                    for item in os.listdir(dw_dir):
                        item_path = os.path.join(dw_dir, item)
                        valid_item = item.startswith(md5_hash + '-')
                        # Delete all related files
                        if is_file_exists(item_path) and valid_item:
                            os.remove(item_path)
                        # Delete related directories
                        if is_dir_exists(item_path) and valid_item:
                            shutil.rmtree(item_path)
                    data = {'deleted': 'yes'}
            if api:
                return data
            else:
                ctype = 'application/json; charset=utf-8'
                return HttpResponse(json.dumps(data), content_type=ctype)
    except Exception as exp:
        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)
예제 #13
0
def dynamic_analysis(request, api=False):
    """Android Dynamic Analysis Entry point."""
    try:
        scan_apps = []
        apks = StaticAnalyzerAndroid.objects.filter(
            APP_TYPE='apk').order_by('-id')
        for apk in apks:
            temp_dict = {
                'ICON_FOUND': apk.ICON_FOUND,
                'MD5': apk.MD5,
                'APP_NAME': apk.APP_NAME,
                'VERSION_NAME': apk.VERSION_NAME,
                'FILE_NAME': apk.FILE_NAME,
                'PACKAGE_NAME': apk.PACKAGE_NAME,
            }
            scan_apps.append(temp_dict)
        try:
            identifier = get_device()
        except Exception:
            msg = ('Is Android VM running? MobSF cannot'
                   ' find android instance identifier.'
                   ' Please run an android instance and refresh'
                   ' this page. If this error persists,'
                   ' set ANALYZER_IDENTIFIER in '
                   f'{get_config_loc()}')
            return print_n_send_error_response(request, msg, api)
        proxy_ip = get_proxy_ip(identifier)
        context = {
            'apps': scan_apps,
            'identifier': identifier,
            'proxy_ip': proxy_ip,
            'proxy_port': settings.PROXY_PORT,
            'settings_loc': get_config_loc(),
            'title': 'MobSF Dynamic Analysis',
            'version': settings.MOBSF_VER
        }
        if api:
            return context
        template = 'dynamic_analysis/dynamic_analysis.html'
        return render(request, template, context)
    except Exception as exp:
        logger.exception('Dynamic Analysis')
        return print_n_send_error_response(request, exp, api)
def search(request):
    """Search Scan by MD5 Route."""
    md5 = request.GET['md5']
    if re.match('[0-9a-f]{32}', md5):
        db_obj = RecentScansDB.objects.filter(MD5=md5)
        if db_obj.exists():
            return HttpResponseRedirect('/' + db_obj[0].URL)
        else:
            return HttpResponseRedirect('/not_found/')
    return print_n_send_error_response(request, 'Invalid Scan Hash')
def trigger_static_analysis(request, checksum):
    """On device APK Static Analysis."""
    try:
        identifier = None
        if not is_md5(checksum):
            return print_n_send_error_response(
                request,
                'Invalid MD5')
        package = get_package_name(checksum)
        if not package:
            return print_n_send_error_response(
                request,
                'Cannot get package name from checksum')
        try:
            identifier = get_device()
        except Exception:
            pass
        if not identifier:
            err = 'Cannot connect to Android Runtime'
            return print_n_send_error_response(request, err)
        env = Environment(identifier)
        apk_file = env.get_apk(checksum, package)
        if not apk_file:
            err = 'Failed to download APK file'
            return print_n_send_error_response(request, err)
        data = {
            'analyzer': 'static_analyzer',
            'status': 'success',
            'hash': checksum,
            'scan_type': 'apk',
            'file_name': f'{package}.apk',
        }
        add_to_recent_scan(data)
        return HttpResponseRedirect(
            f'/static_analyzer/?name='  # lgtm [py/url-redirection]
            f'{package}.apk&checksum={checksum}'
            f'&type=apk')
    except Exception:
        msg = 'On device APK Static Analysis'
        logger.exception(msg)
        return print_n_send_error_response(request, msg)
예제 #16
0
def search(request):
    """Search Scan by MD5 Route."""
    md5 = request.GET['md5']
    if re.match('[0-9a-f]{32}', md5):
        db_obj = RecentScansDB.objects.filter(MD5=md5)
        if db_obj.exists():
            e = db_obj[0]
            url = (f'/{e.ANALYZER }/?name={e.FILE_NAME}&'
                   f'checksum={e.MD5}&type={e.SCAN_TYPE}')
            return HttpResponseRedirect(url)
        else:
            return HttpResponseRedirect('/not_found/')
    return print_n_send_error_response(request, 'Invalid Scan Hash')
예제 #17
0
def download(request):
    """Download from mobsf.MobSF Route."""
    msg = 'Error Downloading File '
    if request.method == 'GET':
        allowed_exts = settings.ALLOWED_EXTENSIONS
        filename = request.path.replace('/download/', '', 1)
        # Security Checks
        if '../' in filename:
            msg = 'Path Traversal Attack Detected'
            return print_n_send_error_response(request, msg)
        ext = os.path.splitext(filename)[1]
        if ext in allowed_exts:
            dwd_file = os.path.join(settings.DWD_DIR, filename)
            if os.path.isfile(dwd_file):
                wrapper = FileWrapper(open(dwd_file, 'rb'))
                response = HttpResponse(wrapper,
                                        content_type=allowed_exts[ext])
                response['Content-Length'] = os.path.getsize(dwd_file)
                return response
    if ('screen/screen.png' not in filename and '-icon.png' not in filename):
        msg += filename
        return print_n_send_error_response(request, msg)
    return HttpResponse('')
def run(request):
    """Source Tree - Java/Smali view."""
    try:
        logger.info('Listing Source files')
        match = re.match('^[0-9a-f]{32}$', request.GET['md5'])
        if not match:
            return print_n_send_error_response(request, 'Scan hash not found')
        md5 = request.GET['md5']
        typ = request.GET['type']
        base = Path(settings.UPLD_DIR) / md5
        if typ == 'smali':
            src = base / 'smali_source'
        else:
            try:
                src = find_java_source_folder(base)[0]
            except StopIteration:
                return print_n_send_error_response(
                    request,
                    'Invalid Directory Structure')

        tree_index = tree_index_maker(src, len(src.as_posix()))
        context = {
            'subfiles': tree_index,
            'title': f'{typ.capitalize()} Source',
            'hash': md5,
            'source_type': typ,
            'version': settings.MOBSF_VER,
            'api_key': api_key(),
        }
        template = 'static_analysis/source_tree.html'
        return render(request, template, context)
    except Exception:
        logger.exception('Getting Source Files')
        return print_n_send_error_response(
            request,
            'Error Getting Source Files')
def httptools_start(request):
    """Start httprools UI."""
    logger.info('Starting httptools Web UI')
    try:
        stop_httptools(settings.PROXY_PORT)
        start_httptools_ui(settings.PROXY_PORT)
        time.sleep(3)
        logger.info('httptools UI started')
        if request.GET['project']:
            project = request.GET['project']
        else:
            project = ''
        url = ('http://localhost:{}'
               '/dashboard/{}'.format(str(settings.PROXY_PORT), project))
        return HttpResponseRedirect(url)  # lgtm [py/reflective-xss]
    except Exception:
        logger.exception('Starting httptools Web UI')
        err = 'Error Starting httptools UI'
        return print_n_send_error_response(request, err)
예제 #20
0
def httptools_start(request):
    """Start httprools UI."""
    logger.info('Starting httptools Web UI')
    try:
        httptools_url = get_http_tools_url(request)
        stop_httptools(httptools_url)
        start_httptools_ui(settings.PROXY_PORT)
        time.sleep(3)
        logger.info('httptools UI started')
        if request.GET['project']:
            project = request.GET['project']
        else:
            project = ''
        url = f'{httptools_url}/dashboard/{project}'
        return HttpResponseRedirect(url)  # lgtm [py/reflective-xss]
    except Exception:
        logger.exception('Starting httptools Web UI')
        err = 'Error Starting httptools UI'
        return print_n_send_error_response(request, err)
예제 #21
0
def download(request):
    """Download from mobsf.MobSF Route."""
    if request.method == 'GET':
        root = settings.DWD_DIR
        allowed_exts = settings.ALLOWED_EXTENSIONS
        filename = request.path.replace('/download/', '', 1)
        dwd_file = os.path.join(root, filename)
        # Security Checks
        if '../' in filename or not is_safe_path(root, dwd_file):
            msg = 'Path Traversal Attack Detected'
            return print_n_send_error_response(request, msg)
        ext = os.path.splitext(filename)[1]
        if ext in allowed_exts:
            if os.path.isfile(dwd_file):
                wrapper = FileWrapper(open(dwd_file,
                                           'rb'))  # lgtm [py/path-injection]
                response = HttpResponse(wrapper,
                                        content_type=allowed_exts[ext])
                response['Content-Length'] = os.path.getsize(dwd_file)
                return response
        if filename.endswith(('screen/screen.png', '-icon.png')):
            return HttpResponse('')
    return HttpResponse(status=404)
예제 #22
0
def staticanalyzer_windows(request, api=False):
    """Analyse a windows app."""
    try:
        # Input validation
        logger.info('Windows Static Analysis Started')
        rescan = False
        app_dic = {}  # Dict to store the binary attributes
        if api:
            typ = request.POST['scan_type']
            re_scan = request.POST.get('re_scan', 0)
            checksum = request.POST['hash']
            filename = request.POST['file_name']
        else:
            typ = request.GET['type']
            re_scan = request.GET.get('rescan', 0)
            checksum = request.GET['checksum']
            filename = request.GET['name']
        if re_scan == '1':
            rescan = True
        md5_regex = re.match('^[0-9a-f]{32}$', checksum)
        if (md5_regex) and (typ in ['appx']):
            app_dic['app_name'] = filename  # APP ORIGINAL NAME
            app_dic['md5'] = checksum
            app_dic['app_dir'] = os.path.join(
                settings.UPLD_DIR, app_dic['md5'] + '/')
            app_dic['tools_dir'] = os.path.join(
                settings.BASE_DIR, 'StaticAnalyzer/tools/windows/')
            if typ == 'appx':
                # DB
                db_entry = StaticAnalyzerWindows.objects.filter(
                    MD5=app_dic['md5'],
                )
                if db_entry.exists() and not rescan:
                    logger.info(
                        'Analysis is already Done.'
                        ' Fetching data from the DB...')
                    context = get_context_from_db_entry(db_entry)
                else:
                    logger.info('Windows Binary Analysis Started')
                    app_dic['app_path'] = os.path.join(
                        app_dic['app_dir'], app_dic['md5'] + '.appx')
                    # ANALYSIS BEGINS
                    app_dic['size'] = str(
                        file_size(app_dic['app_path'])) + 'MB'
                    # Generate hashes
                    app_dic['sha1'], app_dic[
                        'sha256'] = hash_gen(app_dic['app_path'])
                    # EXTRACT APPX
                    logger.info('Extracting APPX')
                    app_dic['files'] = unzip(
                        app_dic['app_path'], app_dic['app_dir'])
                    xml_dic = _parse_xml(app_dic['app_dir'])
                    bin_an_dic = _binary_analysis(app_dic)
                    # Saving to db
                    logger.info('Connecting to DB')
                    if rescan:
                        logger.info('Updating Database...')
                        save_or_update('update',
                                       app_dic,
                                       xml_dic,
                                       bin_an_dic)
                        update_scan_timestamp(app_dic['md5'])
                    else:
                        logger.info('Saving to Database')
                        save_or_update('save',
                                       app_dic,
                                       xml_dic,
                                       bin_an_dic)
                    context = get_context_from_analysis(app_dic,
                                                        xml_dic,
                                                        bin_an_dic)
                context['virus_total'] = None
                if settings.VT_ENABLED:
                    vt = VirusTotal.VirusTotal()
                    context['virus_total'] = vt.get_result(
                        os.path.join(app_dic['app_dir'], app_dic[
                                     'md5']) + '.appx',
                        app_dic['md5'])
                template = 'static_analysis/windows_binary_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'
            if api:
                return print_n_send_error_response(request, msg, True)
            else:
                return print_n_send_error_response(request, msg, False)
    except Exception as exception:
        logger.exception('Error Performing Static Analysis')
        msg = str(exception)
        exp_doc = exception.__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)
예제 #23
0
def run(request, api=False):
    """View iOS Files."""
    try:
        logger.info('View iOS Source File')
        exp = 'Error Description'
        file_format = None
        if api:
            fil = request.POST['file']
            md5_hash = request.POST['hash']
            mode = request.POST['type']
            viewsource_form = ViewSourceIOSApiForm(request.POST)
        else:
            fil = request.GET['file']
            md5_hash = request.GET['md5']
            mode = request.GET['type']
            viewsource_form = ViewSourceIOSForm(request.GET)
        typ = set_ext_api(fil)
        if not viewsource_form.is_valid():
            err = FormUtil.errors_message(viewsource_form)
            if api:
                return err
            return print_n_send_error_response(request, err, False, exp)
        base = Path(settings.UPLD_DIR) / md5_hash
        if mode == 'ipa':
            src1 = base / 'payload'
            src2 = base / 'Payload'
            if src1.exists():
                src = src1
            elif src2.exists():
                src = src2
            else:
                raise Exception('MobSF cannot find Payload directory')
        elif mode == 'ios':
            src = base
        sfile = src / fil
        sfile = sfile.as_posix()
        if not is_safe_path(src, sfile):
            msg = 'Path Traversal Detected!'
            if api:
                return {'error': 'Path Traversal Detected!'}
            return print_n_send_error_response(request, msg, False, exp)
        dat = ''
        sql_dump = {}
        if typ == 'm':
            file_format = 'cpp'
            with io.open(sfile, mode='r', encoding='utf8',
                         errors='ignore') as flip:
                dat = flip.read()
        elif typ == 'xml':
            file_format = 'xml'
            with io.open(sfile, mode='r', encoding='utf8',
                         errors='ignore') as flip:
                dat = flip.read()
        elif typ == 'plist':
            file_format = 'json'
            dat = biplist.readPlist(sfile)
            try:
                dat = json.dumps(dat, indent=4, sort_keys=True)
            except Exception:
                pass
        elif typ == 'db':
            file_format = 'asciidoc'
            sql_dump = read_sqlite(sfile)
        elif typ == 'txt' and fil == 'classdump.txt':
            file_format = 'cpp'
            app_dir = os.path.join(settings.UPLD_DIR, md5_hash + '/')
            cls_dump_file = os.path.join(app_dir, 'classdump.txt')
            if is_file_exists(cls_dump_file):
                with io.open(
                        cls_dump_file,  # lgtm [py/path-injection]
                        mode='r',
                        encoding='utf8',
                        errors='ignore') as flip:
                    dat = flip.read()
            else:
                dat = 'Class Dump result not Found'
        elif typ == 'txt':
            file_format = 'text'
            with io.open(sfile, mode='r', encoding='utf8',
                         errors='ignore') as flip:
                dat = flip.read()
        else:
            if api:
                return {'error': 'Invalid Parameters'}
            return HttpResponseRedirect('/error/')
        context = {
            'title': escape(ntpath.basename(fil)),
            'file': escape(ntpath.basename(fil)),
            'type': file_format,
            'data': dat,
            'sqlite': sql_dump,
            'version': settings.MOBSF_VER,
        }
        template = 'general/view.html'
        if api:
            return context
        return render(request, template, context)
    except Exception as exp:
        logger.exception('Error Viewing Source')
        msg = str(exp)
        exp = exp.__doc__
        if api:
            return print_n_send_error_response(request, msg, True, exp)
        return print_n_send_error_response(request, msg, False, exp)
def static_analyzer(request, api=False):
    """Do static analysis on an request and save to db."""
    try:
        rescan = False
        if api:
            typ = request.POST['scan_type']
            checksum = request.POST['hash']
            filename = request.POST['file_name']
            re_scan = request.POST.get('re_scan', 0)
        else:
            typ = request.GET['type']
            checksum = request.GET['checksum']
            filename = request.GET['name']
            re_scan = request.GET.get('rescan', 0)
        if re_scan == '1':
            rescan = True
        # Input validation
        app_dic = {}
        match = re.match('^[0-9a-f]{32}$', checksum)
        if (match and filename.lower().endswith(
            ('.apk', '.xapk', '.zip', '.apks'))
                and typ in ['zip', 'apk', 'xapk', 'apks']):
            app_dic['dir'] = Path(settings.BASE_DIR)  # BASE DIR
            app_dic['app_name'] = filename  # APP ORIGINAL NAME
            app_dic['md5'] = checksum  # MD5
            # APP DIRECTORY
            app_dic['app_dir'] = Path(settings.UPLD_DIR) / checksum
            app_dic['tools_dir'] = app_dic['dir'] / 'StaticAnalyzer' / 'tools'
            app_dic['tools_dir'] = app_dic['tools_dir'].as_posix()
            logger.info('Starting Analysis on: %s', app_dic['app_name'])
            if typ == 'xapk':
                # Handle XAPK
                # Base APK will have the MD5 of XAPK
                if not handle_xapk(app_dic):
                    raise Exception('Invalid XAPK File')
                typ = 'apk'
            elif typ == 'apks':
                # Handle Split APK
                if not handle_split_apk(app_dic):
                    raise Exception('Invalid Split APK File')
                typ = 'apk'
            if typ == 'apk':
                app_dic['app_file'] = app_dic['md5'] + '.apk'  # NEW FILENAME
                app_dic['app_path'] = (app_dic['app_dir'] /
                                       app_dic['app_file']).as_posix()
                app_dic['app_dir'] = app_dic['app_dir'].as_posix() + '/'
                # Check if in DB
                # pylint: disable=E1101
                db_entry = StaticAnalyzerAndroid.objects.filter(
                    MD5=app_dic['md5'])
                if db_entry.exists() and not rescan:
                    context = get_context_from_db_entry(db_entry)
                else:
                    # ANALYSIS BEGINS
                    app_dic['size'] = str(file_size(
                        app_dic['app_path'])) + 'MB'  # FILE SIZE
                    app_dic['sha1'], app_dic['sha256'] = hash_gen(
                        app_dic['app_path'])
                    app_dic['files'] = unzip(app_dic['app_path'],
                                             app_dic['app_dir'])
                    logger.info('APK Extracted')
                    if not app_dic['files']:
                        # Can't Analyze APK, bail out.
                        msg = 'APK file is invalid or corrupt'
                        if api:
                            return print_n_send_error_response(
                                request, msg, True)
                        else:
                            return print_n_send_error_response(
                                request, msg, False)
                    app_dic['certz'] = get_hardcoded_cert_keystore(
                        app_dic['files'])
                    # Manifest XML
                    mani_file, mani_xml = get_manifest(
                        app_dic['app_path'],
                        app_dic['app_dir'],
                        app_dic['tools_dir'],
                        '',
                        True,
                    )
                    app_dic['manifest_file'] = mani_file
                    app_dic['parsed_xml'] = mani_xml

                    # get app_name
                    app_dic['real_name'] = get_app_name(
                        app_dic['app_path'],
                        app_dic['app_dir'],
                        app_dic['tools_dir'],
                        True,
                    )

                    # Get icon
                    res_path = os.path.join(app_dic['app_dir'], 'res')
                    app_dic['icon_hidden'] = True
                    # Even if the icon is hidden, try to guess it by the
                    # default paths
                    app_dic['icon_found'] = False
                    app_dic['icon_path'] = ''
                    # TODO: Check for possible different names for resource
                    # folder?
                    if os.path.exists(res_path):
                        icon_dic = get_icon(app_dic['app_path'], res_path)
                        if icon_dic:
                            app_dic['icon_hidden'] = icon_dic['hidden']
                            app_dic['icon_found'] = bool(icon_dic['path'])
                            app_dic['icon_path'] = icon_dic['path']

                    # Set Manifest link
                    app_dic['mani'] = ('../manifest_view/?md5=' +
                                       app_dic['md5'] + '&type=apk&bin=1')
                    man_data_dic = manifest_data(app_dic['parsed_xml'])
                    app_dic['playstore'] = get_app_details(
                        man_data_dic['packagename'])
                    man_an_dic = manifest_analysis(
                        app_dic['parsed_xml'],
                        man_data_dic,
                        '',
                        app_dic['app_dir'],
                    )
                    elf_dict = elf_analysis(app_dic['app_dir'])
                    cert_dic = cert_info(app_dic['app_dir'],
                                         app_dic['app_file'])
                    apkid_results = apkid_analysis(app_dic['app_dir'],
                                                   app_dic['app_path'],
                                                   app_dic['app_name'])
                    tracker = Trackers.Trackers(app_dic['app_dir'],
                                                app_dic['tools_dir'])
                    tracker_res = tracker.get_trackers()

                    apk_2_java(app_dic['app_path'], app_dic['app_dir'],
                               app_dic['tools_dir'])

                    dex_2_smali(app_dic['app_dir'], app_dic['tools_dir'])

                    code_an_dic = code_analysis(app_dic['app_dir'], 'apk',
                                                app_dic['manifest_file'])

                    quark_results = quark_analysis(app_dic['app_dir'],
                                                   app_dic['app_path'])

                    # Get the strings from android resource and shared objects
                    string_res = strings_from_apk(app_dic['app_file'],
                                                  app_dic['app_dir'],
                                                  elf_dict['elf_strings'])
                    if string_res:
                        app_dic['strings'] = string_res['strings']
                        app_dic['secrets'] = string_res['secrets']
                        code_an_dic['urls_list'].extend(
                            string_res['urls_list'])
                        code_an_dic['urls'].extend(string_res['url_nf'])
                        code_an_dic['emails'].extend(string_res['emails_nf'])
                    else:
                        app_dic['strings'] = []
                        app_dic['secrets'] = []
                    # Firebase DB Check
                    code_an_dic['firebase'] = firebase_analysis(
                        list(set(code_an_dic['urls_list'])))
                    # Domain Extraction and Malware Check
                    logger.info(
                        'Performing Malware Check on extracted Domains')
                    code_an_dic['domains'] = MalwareDomainCheck().scan(
                        list(set(code_an_dic['urls_list'])))
                    # Copy App icon
                    copy_icon(app_dic['md5'], app_dic['icon_path'])
                    app_dic['zipped'] = 'apk'

                    logger.info('Connecting to Database')
                    try:
                        # SAVE TO DB
                        if rescan:
                            logger.info('Updating Database...')
                            save_or_update(
                                'update',
                                app_dic,
                                man_data_dic,
                                man_an_dic,
                                code_an_dic,
                                cert_dic,
                                elf_dict['elf_analysis'],
                                apkid_results,
                                quark_results,
                                tracker_res,
                            )
                            update_scan_timestamp(app_dic['md5'])
                        else:
                            logger.info('Saving to Database')
                            save_or_update(
                                'save',
                                app_dic,
                                man_data_dic,
                                man_an_dic,
                                code_an_dic,
                                cert_dic,
                                elf_dict['elf_analysis'],
                                apkid_results,
                                quark_results,
                                tracker_res,
                            )
                    except Exception:
                        logger.exception('Saving to Database Failed')
                    context = get_context_from_analysis(
                        app_dic,
                        man_data_dic,
                        man_an_dic,
                        code_an_dic,
                        cert_dic,
                        elf_dict['elf_analysis'],
                        apkid_results,
                        quark_results,
                        tracker_res,
                    )
                context['average_cvss'], context['security_score'] = score(
                    context['code_analysis'])
                context['dynamic_analysis_done'] = is_file_exists(
                    os.path.join(app_dic['app_dir'], 'logcat.txt'))

                context['virus_total'] = None
                if settings.VT_ENABLED:
                    vt = VirusTotal.VirusTotal()
                    context['virus_total'] = vt.get_result(
                        app_dic['app_path'], app_dic['md5'])
                template = 'static_analysis/android_binary_analysis.html'
                if api:
                    return context
                else:
                    return render(request, template, context)
            elif typ == 'zip':
                ret = ('/static_analyzer_ios/?name=' + app_dic['app_name'] +
                       '&type=ios&checksum=' + app_dic['md5'])
                # Check if in DB
                # pylint: disable=E1101
                cert_dic = {
                    'certificate_info': '',
                    'certificate_status': '',
                    'description': '',
                }
                app_dic['strings'] = []
                app_dic['secrets'] = []
                app_dic['zipped'] = ''
                # Above fields are only available for APK and not ZIP
                app_dic['app_file'] = app_dic['md5'] + '.zip'  # NEW FILENAME
                app_dic['app_path'] = (app_dic['app_dir'] /
                                       app_dic['app_file']).as_posix()
                app_dic['app_dir'] = app_dic['app_dir'].as_posix() + '/'
                db_entry = StaticAnalyzerAndroid.objects.filter(
                    MD5=app_dic['md5'])
                ios_db_entry = StaticAnalyzerIOS.objects.filter(
                    MD5=app_dic['md5'])
                if db_entry.exists() and not rescan:
                    context = get_context_from_db_entry(db_entry)
                elif ios_db_entry.exists() and not rescan:
                    if api:
                        return {'type': 'ios'}
                    else:
                        return HttpResponseRedirect(ret)
                else:
                    logger.info('Extracting ZIP')
                    app_dic['files'] = unzip(app_dic['app_path'],
                                             app_dic['app_dir'])
                    # Check if Valid Directory Structure and get ZIP Type
                    pro_type, valid = valid_source_code(app_dic['app_dir'])
                    logger.info('Source code type - %s', pro_type)
                    if valid and pro_type == 'ios':
                        logger.info('Redirecting to iOS Source Code Analyzer')
                        if api:
                            return {'type': 'ios'}
                        else:
                            ret += f'&rescan={str(int(rescan))}'
                            return HttpResponseRedirect(ret)
                    app_dic['certz'] = get_hardcoded_cert_keystore(
                        app_dic['files'])
                    app_dic['zipped'] = pro_type
                    if valid and (pro_type in ['eclipse', 'studio']):
                        # ANALYSIS BEGINS
                        app_dic['size'] = str(file_size(
                            app_dic['app_path'])) + 'MB'  # FILE SIZE
                        app_dic['sha1'], app_dic['sha256'] = hash_gen(
                            app_dic['app_path'])

                        # Manifest XML
                        mani_file, mani_xml = get_manifest(
                            '',
                            app_dic['app_dir'],
                            app_dic['tools_dir'],
                            pro_type,
                            False,
                        )
                        app_dic['manifest_file'] = mani_file
                        app_dic['parsed_xml'] = mani_xml

                        # get app_name
                        app_dic['real_name'] = get_app_name(
                            app_dic['app_path'],
                            app_dic['app_dir'],
                            app_dic['tools_dir'],
                            False,
                        )

                        # Set manifest view link
                        app_dic['mani'] = ('../manifest_view/?md5=' +
                                           app_dic['md5'] + '&type=' +
                                           pro_type + '&bin=0')

                        man_data_dic = manifest_data(app_dic['parsed_xml'])
                        app_dic['playstore'] = get_app_details(
                            man_data_dic['packagename'])
                        man_an_dic = manifest_analysis(
                            app_dic['parsed_xml'],
                            man_data_dic,
                            pro_type,
                            app_dic['app_dir'],
                        )
                        # Get icon
                        eclipse_res_path = os.path.join(
                            app_dic['app_dir'], 'res')
                        studio_res_path = os.path.join(app_dic['app_dir'],
                                                       'app', 'src', 'main',
                                                       'res')
                        if os.path.exists(eclipse_res_path):
                            res_path = eclipse_res_path
                        elif os.path.exists(studio_res_path):
                            res_path = studio_res_path
                        else:
                            res_path = ''

                        app_dic['icon_hidden'] = man_an_dic['icon_hidden']
                        app_dic['icon_found'] = False
                        app_dic['icon_path'] = ''
                        if res_path:
                            app_dic['icon_path'] = find_icon_path_zip(
                                res_path, man_data_dic['icons'])
                            if app_dic['icon_path']:
                                app_dic['icon_found'] = True

                        if app_dic['icon_path']:
                            if os.path.exists(app_dic['icon_path']):
                                shutil.copy2(
                                    app_dic['icon_path'],
                                    os.path.join(settings.DWD_DIR,
                                                 app_dic['md5'] + '-icon.png'))

                        code_an_dic = code_analysis(app_dic['app_dir'],
                                                    pro_type,
                                                    app_dic['manifest_file'])
                        # Firebase DB Check
                        code_an_dic['firebase'] = firebase_analysis(
                            list(set(code_an_dic['urls_list'])))
                        # Domain Extraction and Malware Check
                        logger.info(
                            'Performing Malware Check on extracted Domains')
                        code_an_dic['domains'] = MalwareDomainCheck().scan(
                            list(set(code_an_dic['urls_list'])))
                        logger.info('Connecting to Database')
                        try:
                            # SAVE TO DB
                            if rescan:
                                logger.info('Updating Database...')
                                save_or_update(
                                    'update',
                                    app_dic,
                                    man_data_dic,
                                    man_an_dic,
                                    code_an_dic,
                                    cert_dic,
                                    [],
                                    {},
                                    [],
                                    {},
                                )
                                update_scan_timestamp(app_dic['md5'])
                            else:
                                logger.info('Saving to Database')
                                save_or_update(
                                    'save',
                                    app_dic,
                                    man_data_dic,
                                    man_an_dic,
                                    code_an_dic,
                                    cert_dic,
                                    [],
                                    {},
                                    [],
                                    {},
                                )
                        except Exception:
                            logger.exception('Saving to Database Failed')
                        context = get_context_from_analysis(
                            app_dic,
                            man_data_dic,
                            man_an_dic,
                            code_an_dic,
                            cert_dic,
                            [],
                            {},
                            [],
                            {},
                        )
                    else:
                        msg = 'This ZIP Format is not supported'
                        if api:
                            return print_n_send_error_response(
                                request, msg, True)
                        else:
                            print_n_send_error_response(request, msg, False)
                            ctx = {
                                'title': 'Invalid ZIP archive',
                                'version': settings.MOBSF_VER,
                            }
                            template = 'general/zip.html'
                            return render(request, template, ctx)
                context['average_cvss'], context['security_score'] = score(
                    context['code_analysis'])
                template = 'static_analysis/android_source_analysis.html'
                if api:
                    return context
                else:
                    return render(request, template, context)
            else:
                err = ('Only APK,IPA and Zipped '
                       'Android/iOS Source code supported now!')
                logger.error(err)
        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 excep:
        logger.exception('Error Performing Static Analysis')
        msg = str(excep)
        exp = excep.__doc__
        if api:
            return print_n_send_error_response(request, msg, True, exp)
        else:
            return print_n_send_error_response(request, msg, False, exp)
def dynamic_analyzer(request, checksum, api=False):
    """Android Dynamic Analyzer Environment."""
    logger.info('Creating Dynamic Analysis Environment')
    try:
        no_device = False
        if not is_md5(checksum):
            # We need this check since checksum is not validated
            # in REST API
            return print_n_send_error_response(request, 'Invalid Parameters',
                                               api)
        package = get_package_name(checksum)
        if not package:
            return print_n_send_error_response(request, 'Invalid Parameters',
                                               api)
        try:
            identifier = get_device()
        except Exception:
            no_device = True
        if no_device or not identifier:
            msg = ('Is the android instance running? MobSF cannot'
                   ' find android instance identifier. '
                   'Please run an android instance and refresh'
                   ' this page. If this error persists,'
                   ' set ANALYZER_IDENTIFIER in '
                   f'{get_config_loc()}')
            return print_n_send_error_response(request, msg, api)
        env = Environment(identifier)
        if not env.connect_n_mount():
            msg = 'Cannot Connect to ' + identifier
            return print_n_send_error_response(request, msg, api)
        version = env.get_android_version()
        logger.info('Android Version identified as %s', version)
        xposed_first_run = False
        if not env.is_mobsfyied(version):
            msg = ('This Android instance is not MobSfyed/Outdated.\n'
                   'MobSFying the android runtime environment')
            logger.warning(msg)
            if not env.mobsfy_init():
                return print_n_send_error_response(
                    request, 'Failed to MobSFy the instance', api)
            if version < 5:
                xposed_first_run = True
        if xposed_first_run:
            msg = ('Have you MobSFyed the instance before'
                   ' attempting Dynamic Analysis?'
                   ' Install Framework for Xposed.'
                   ' Restart the device and enable'
                   ' all Xposed modules. And finally'
                   ' restart the device once again.')
            return print_n_send_error_response(request, msg, api)
        # Clean up previous analysis
        env.dz_cleanup(checksum)
        # Configure Web Proxy
        env.configure_proxy(package)
        # Supported in Android 5+
        env.enable_adb_reverse_tcp(version)
        # Apply Global Proxy to device
        env.set_global_proxy(version)
        # Start Clipboard monitor
        env.start_clipmon()
        # Get Screen Resolution
        screen_width, screen_height = env.get_screen_res()
        apk_path = Path(settings.UPLD_DIR) / checksum / f'{checksum}.apk'
        # Install APK
        status, output = env.install_apk(apk_path.as_posix(), package)
        if not status:
            # Unset Proxy
            env.unset_global_proxy()
            msg = (f'This APK cannot be installed. Is this APK '
                   f'compatible the Android VM/Emulator?\n{output}')
            return print_n_send_error_response(request, msg, api)
        logger.info('Testing Environment is Ready!')
        context = {
            'screen_witdth': screen_width,
            'screen_height': screen_height,
            'package': package,
            'hash': checksum,
            'android_version': version,
            'version': settings.MOBSF_VER,
            'title': 'Dynamic Analyzer'
        }
        template = 'dynamic_analysis/android/dynamic_analyzer.html'
        if api:
            return context
        return render(request, template, context)
    except Exception:
        logger.exception('Dynamic Analyzer')
        return print_n_send_error_response(request, 'Dynamic Analysis Failed.',
                                           api)
예제 #26
0
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'])
                    code_dict = {
                        'api': {},
                        'code_anal': {},
                        'urlnfile': recon['urlnfile'],
                        'domains': recon['domains'],
                        'emailnfile': recon['emailnfile'],
                        'firebase': firebase_analysis(recon['urls_list']),
                    }
                    # 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['average_cvss'], context['security_score'] = score(
                    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'])
                    # 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'])))
                    fake_bin_dict = {
                        'checksec': {},
                        'libraries': [],
                        'bin_code_analysis': {},
                        '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['average_cvss'], context['security_score'] = score(
                    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)
예제 #27
0
def generic_compare(request,
                    first_hash: str,
                    second_hash: str,
                    api: bool = False):
    # This context consists of specific lists and analysis
    # that is done on the classic ones
    # it will be filled during the different diff analysis
    context = {
        'title': 'Compare report',
        'version': settings.MOBSF_VER,
        'first_app': {},
        'second_app': {},
        'urls': {},
        'android_api': {},
        'permissions': {},
        'browsable_activities': {},
        'common_browsable_activities': {},
        'apkid': {},
    }
    static_fields = [
        'md5', 'file_name', 'size', 'icon_found', 'icon_hidden', 'activities',
        'services', 'providers', 'receivers', 'exported_count', 'apkid'
    ]

    # For now - support only android
    db_entry = StaticAnalyzerAndroid.objects.filter(MD5=first_hash)
    db_entry2 = StaticAnalyzerAndroid.objects.filter(MD5=second_hash)

    if not (db_entry.exists() and db_entry2.exists()):
        return print_n_send_error_response(
            request,
            'Currently you can only diff/compare android apps. '
            'One of the app has not completed static analysis or'
            ' they are not both android APK/ZIP.',
            api,
        )

    # First fetch the already done analysis on each of the apps
    # We don't want to return this whole context back to the user
    # because its a lot of data we don't use
    # it should help the performance I guess
    first_app = deepcopy(get_context_from_db_entry(db_entry))
    second_app = deepcopy(get_context_from_db_entry(db_entry2))

    # Second, fill the common static parts that
    # are missing in the classic analysis
    for curr_app, db_context in [('first_app', first_app),
                                 ('second_app', second_app)]:

        # format informative title
        context[curr_app]['name_ver'] = '{0} - {1}'.format(
            db_context['package_name'], db_context['version_name'])

        # Fill all the static information
        for static_attr in static_fields:
            context[curr_app][static_attr] = db_context[static_attr]

        # Get only the subject of the cert
        subject_regex = re.compile(r'Subject: .*')
        match = subject_regex.search(
            db_context['certificate_analysis']['certificate_info'])
        if match:
            context[curr_app]['cert_subject'] = match.group()
        else:
            context[curr_app]['cert_subject'] = 'No subject'

        # Some preparations so we have some sort of same structures
        # (urls are lists inside the list which mess things up...)
        tmp_list = []
        for url_obj in db_context['urls']:
            for url in url_obj['urls']:
                # urls can mess up the table because they can be really long,
                # so let's cut them
                # escape url
                url = escape(url)
                tmp_url = url[:70]
                while len(url) > 70:
                    url = url[70:]
                    tmp_url += '<br />'
                    tmp_url += url[:70]
                tmp_list.append(tmp_url)

        db_context['urls'] = list(set(deepcopy(tmp_list)))
        tmp_list.clear()

    # apkid check - we do it here just because
    # its really ugly inside the template
    # it has a dedicated function because the result
    # is more complicated then the others...
    diff_apkid(context)

    # Third, calculate some diffs
    for section, is_tuples in [
        ('permissions', True),
        ('android_api', True),
        ('browsable_activities', True),
        ('urls', False),
    ]:
        if is_tuples:
            context[section]['common'] = [
                (x, y) for (x, y) in first_app[section].items()
                if x in second_app[section].keys()
            ]

            # Only first
            context[section]['only_first'] = [
                (x, y) for (x, y) in first_app[section].items()
                if x not in second_app[section].keys()
            ]

            # Only Second
            context[section]['only_second'] = [
                (x, y) for (x, y) in second_app[section].items()
                if x not in first_app[section].keys()
            ]
        else:
            context[section]['common'] = [
                x for x in first_app[section] if x in second_app[section]
            ]

            context[section]['only_first'] = [
                x for x in first_app[section] if x not in second_app[section]
            ]

            context[section]['only_second'] = [
                x for x in second_app[section] if x not in first_app[section]
            ]

    diff_browsable_activities(context, first_app, second_app)

    template = 'static_analysis/compare.html'
    if api:
        return context
    else:
        return render(request, template, context)
def pdf(request, api=False, jsonres=False):
    try:
        if api:
            checksum = request.POST['hash']
        else:
            checksum = request.GET['md5']
        hash_match = re.match('^[0-9a-f]{32}$', checksum)
        if not hash_match:
            if api:
                return {'error': 'Invalid scan hash'}
            else:
                return HttpResponse(json.dumps({'md5': 'Invalid scan hash'}),
                                    content_type=ctype,
                                    status=500)
        # Do Lookups
        android_static_db = StaticAnalyzerAndroid.objects.filter(MD5=checksum)
        ios_static_db = StaticAnalyzerIOS.objects.filter(MD5=checksum)
        win_static_db = StaticAnalyzerWindows.objects.filter(MD5=checksum)

        if android_static_db.exists():
            context, template = handle_pdf_android(android_static_db)
        elif ios_static_db.exists():
            context, template = handle_pdf_ios(ios_static_db)
        elif win_static_db.exists():
            context, template = handle_pdf_win(win_static_db)
        else:
            if api:
                return {'report': 'Report not Found'}
            else:
                return HttpResponse(json.dumps({'report': 'Report not Found'}),
                                    content_type=ctype,
                                    status=500)
        # Do VT Scan only on binaries
        context['virus_total'] = None
        ext = os.path.splitext(context['file_name'].lower())[1]
        if settings.VT_ENABLED and ext != '.zip':
            app_bin = os.path.join(settings.UPLD_DIR, checksum + '/',
                                   checksum + ext)
            vt = VirusTotal.VirusTotal()
            context['virus_total'] = vt.get_result(app_bin, checksum)
        # Get Local Base URL
        proto = 'file://'
        host_os = 'nix'
        if platform.system() == 'Windows':
            proto = 'file:///'
            host_os = 'windows'
        context['base_url'] = proto + settings.BASE_DIR
        context['dwd_dir'] = proto + settings.DWD_DIR
        context['host_os'] = host_os
        context['timestamp'] = RecentScansDB.objects.get(
            MD5=checksum).TIMESTAMP
        try:
            if api and jsonres:
                return {'report_dat': context}
            else:
                options = {
                    'page-size': 'Letter',
                    'quiet': '',
                    'enable-local-file-access': '',
                    'no-collate': '',
                    'margin-top': '0.50in',
                    'margin-right': '0.50in',
                    'margin-bottom': '0.50in',
                    'margin-left': '0.50in',
                    'encoding': 'UTF-8',
                    'custom-header': [
                        ('Accept-Encoding', 'gzip'),
                    ],
                    'no-outline': None,
                }
                # Added proxy support to wkhtmltopdf
                proxies, _ = upstream_proxy('https')
                if proxies['https']:
                    options['proxy'] = proxies['https']
                html = template.render(context)
                pdf_dat = pdfkit.from_string(html, False, options=options)
                if api:
                    return {'pdf_dat': pdf_dat}
                return HttpResponse(pdf_dat, content_type='application/pdf')
        except Exception as exp:
            logger.exception('Error Generating PDF Report')
            if api:
                return {
                    'error': 'Cannot Generate PDF/JSON',
                    'err_details': str(exp)
                }
            else:
                err = {
                    'pdf_error': 'Cannot Generate PDF',
                    'err_details': str(exp)
                }
                return HttpResponse(
                    json.dumps(err),  # lgtm [py/stack-trace-exposure]
                    content_type=ctype,
                    status=500)
    except Exception as exp:
        logger.exception('Error Generating PDF Report')
        msg = str(exp)
        exp = exp.__doc__
        if api:
            return print_n_send_error_response(request, msg, True, exp)
        else:
            return print_n_send_error_response(request, msg, False, exp)