mirror of
https://github.com/MobSF/Mobile-Security-Framework-MobSF.git
synced 2026-10-11 22:50:09 +00:00
[HOTFIX][EFR06] Independent Shared Object (.so) Scan and Improved String search (#2228)
* String extraction from APK, Source, AAR, JAR, SO * Strings sections to show source of strings extracted * Strings Refactor * Support for independent .SO scan * Android SCA rules update * Entropies scan support for strings * URLs/Email extraction refactor * Bug Fixes * iOS Source Report Fix * Frida APK Patcher (WIP) * Dynamic Analyzer identifier not available * Settings env var not working fix for enabled by default features * AppSec Score fix * Recent `scan not completed` fix for iOS zip
This commit is contained in:
@@ -0,0 +1,97 @@
|
||||
"""Inject Frida Gadget to an APK."""
|
||||
import subprocess
|
||||
from pathlib import Path
|
||||
from lzma import LZMAFile
|
||||
from shutil import copyfileobj
|
||||
from tempfile import gettempdir
|
||||
import logging
|
||||
|
||||
import requests
|
||||
|
||||
from django.conf import settings
|
||||
|
||||
from mobsf.MobSF.utils import (
|
||||
find_java_binary,
|
||||
is_file_exists,
|
||||
is_internet_available,
|
||||
upstream_proxy,
|
||||
)
|
||||
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class GadgetPatcher:
|
||||
|
||||
def __init__(self, app_dir, tools_dir, apk_file) -> None:
|
||||
self.app_dir = app_dir
|
||||
self.tools_dir = tools_dir
|
||||
self.apk_file = apk_file
|
||||
self.output_dir = Path(self.app_dir) / 'apktool_repack'
|
||||
if (len(settings.APKTOOL_BINARY) > 0
|
||||
and is_file_exists(settings.APKTOOL_BINARY)):
|
||||
self.apktool_path = settings.APKTOOL_BINARY
|
||||
else:
|
||||
self.apktool_path = Path(self.tools_dir) / 'apktool_2.7.0.jar'
|
||||
|
||||
def decompile_apk(self):
|
||||
"""Decompile APK."""
|
||||
args = [find_java_binary(),
|
||||
'-jar',
|
||||
self.apktool_path,
|
||||
'--frame-path',
|
||||
gettempdir(),
|
||||
'-f', '-r', '-s', 'd',
|
||||
self.apk_file,
|
||||
'-o',
|
||||
self.output_dir]
|
||||
manifest = self.output_dir / 'AndroidManifest.xml'
|
||||
if not is_file_exists(manifest):
|
||||
# Skip if already decompiled
|
||||
subprocess.check_output(args)
|
||||
|
||||
def recompile_apk(self):
|
||||
"""Recompile APK."""
|
||||
args = [find_java_binary(),
|
||||
'-jar',
|
||||
self.apktool_path,
|
||||
'-f', 'b',
|
||||
'-o',
|
||||
'.patched.apk'.join(
|
||||
self.apk_file.rsplit('.apk', 1)),
|
||||
self.output_dir]
|
||||
subprocess.check_output(args)
|
||||
|
||||
def download_frida_gadget(self, frida_arch, aarch, version):
|
||||
"""Download Frida Gadget."""
|
||||
gadget_bin = self.output_dir / 'lib' / {aarch} / 'libfrida-gadget.so'
|
||||
fgadget = f'frida-gadget-{version}-android-{frida_arch}.so'
|
||||
if gadget_bin.is_file():
|
||||
return True
|
||||
if not is_internet_available():
|
||||
return None
|
||||
try:
|
||||
proxies, verify = upstream_proxy('https')
|
||||
except Exception:
|
||||
logger.exception('[ERROR] Setting upstream proxy')
|
||||
return None
|
||||
try:
|
||||
response = requests.get(f'{settings.FRIDA_SERVER}{version}',
|
||||
timeout=3,
|
||||
proxies=proxies,
|
||||
verify=verify)
|
||||
for item in response.json()['assets']:
|
||||
if item['name'] == f'{fgadget}.xz':
|
||||
url = item['browser_download_url']
|
||||
break
|
||||
if not url:
|
||||
return None
|
||||
logger.info('Downloading frida-gadget %s', fgadget)
|
||||
with requests.get(url, stream=True) as r:
|
||||
with LZMAFile(r.raw) as f:
|
||||
with open(gadget_bin, 'wb') as flip:
|
||||
copyfileobj(f, flip)
|
||||
return True
|
||||
except Exception:
|
||||
logger.exception('[ERROR] Fetching Frida Gadget Release')
|
||||
return None
|
||||
@@ -71,6 +71,8 @@ class Environment:
|
||||
|
||||
def connect(self):
|
||||
"""ADB Connect."""
|
||||
if not self.identifier:
|
||||
return False
|
||||
logger.info('Connecting to Android %s', self.identifier)
|
||||
self.run_subprocess_verify_output([get_adb(),
|
||||
'connect',
|
||||
@@ -78,6 +80,8 @@ class Environment:
|
||||
|
||||
def connect_n_mount(self):
|
||||
"""Test ADB Connection."""
|
||||
if not self.identifier:
|
||||
return False
|
||||
self.adb_command(['kill-server'])
|
||||
self.adb_command(['start-server'])
|
||||
logger.info('ADB Restarted')
|
||||
|
||||
@@ -16,6 +16,7 @@ import IP2Location
|
||||
|
||||
from mobsf.MobSF.utils import (
|
||||
is_internet_available,
|
||||
settings_enabled,
|
||||
update_local_db,
|
||||
)
|
||||
|
||||
@@ -178,7 +179,7 @@ class MalwareDomainCheck:
|
||||
'Skipping Malware Database Update.')
|
||||
|
||||
def scan(self, urls):
|
||||
if not settings.DOMAIN_MALWARE_SCAN:
|
||||
if not settings_enabled('DOMAIN_MALWARE_SCAN'):
|
||||
logger.info('Domain Malware Check disabled in settings')
|
||||
return self.result
|
||||
self.domainlist = get_domains(urls)
|
||||
|
||||
@@ -4,12 +4,16 @@ import os
|
||||
|
||||
from django.conf import settings
|
||||
|
||||
from mobsf.MobSF.utils import (
|
||||
settings_enabled,
|
||||
)
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def apkid_analysis(app_dir, apk_file, apk_name):
|
||||
"""APKID Analysis of DEX files."""
|
||||
if not settings.APKID_ENABLED:
|
||||
if not settings_enabled('APKID_ENABLED'):
|
||||
return {}
|
||||
try:
|
||||
import apkid
|
||||
|
||||
@@ -2,11 +2,10 @@
|
||||
import logging
|
||||
from pathlib import Path
|
||||
|
||||
from django.conf import settings
|
||||
|
||||
from mobsf.MobSF.utils import (
|
||||
disable_print,
|
||||
enable_print,
|
||||
settings_enabled,
|
||||
)
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
@@ -14,7 +13,7 @@ logger = logging.getLogger(__name__)
|
||||
|
||||
def quark_analysis(app_dir, apk_file):
|
||||
"""QUARK Analysis of APK files."""
|
||||
if not getattr(settings, 'QUARK_ENABLED', True):
|
||||
if not settings_enabled('QUARK_ENABLED'):
|
||||
return []
|
||||
try:
|
||||
import quark
|
||||
|
||||
+1
-1
@@ -10,7 +10,7 @@ from mobsf.install.windows.setup import windows_config_local
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
VERSION = '3.7.2'
|
||||
VERSION = '3.7.3'
|
||||
BANNER = """
|
||||
__ __ _ ____ _____ _____ _____
|
||||
| \/ | ___ | |__/ ___|| ___|_ _|___ /|___ |
|
||||
|
||||
@@ -71,6 +71,7 @@ ALLOWED_EXTENSIONS = {
|
||||
'.ipa': 'application/octet-stream',
|
||||
'.jar': 'application/java-archive',
|
||||
'.aar': 'application/octet-stream',
|
||||
'.so': 'application/octet-stream',
|
||||
}
|
||||
# =============ALLOWED MIMETYPES=================
|
||||
APK_MIME = [
|
||||
@@ -324,18 +325,18 @@ else:
|
||||
# Disable CVSSV2 Score by default
|
||||
CVSS_SCORE_ENABLED = bool(os.getenv('MOBSF_CVSS_SCORE_ENABLED', ''))
|
||||
# NIAP Scan
|
||||
NIAP_ENABLED = bool(os.getenv('MOBSF_NIAP_ENABLED', '1'))
|
||||
NIAP_ENABLED = os.getenv('MOBSF_NIAP_ENABLED', '1')
|
||||
# Dex 2 Smali Conversion
|
||||
DEX2SMALI_ENABLED = bool(os.getenv('MOBSF_DEX2SMALI_ENABLED', '1'))
|
||||
DEX2SMALI_ENABLED = os.getenv('MOBSF_DEX2SMALI_ENABLED', '1')
|
||||
# Android Shared Object Binary Analysis
|
||||
SO_ANALYSIS_ENABLED = bool(os.getenv('MOBSF_SO_ANALYSIS_ENABLED', '1'))
|
||||
SO_ANALYSIS_ENABLED = os.getenv('MOBSF_SO_ANALYSIS_ENABLED', '1')
|
||||
# =================================================
|
||||
# --------------------------
|
||||
# MALWARE ANALYZER SETTINGS
|
||||
# --------------------------
|
||||
|
||||
DOMAIN_MALWARE_SCAN = bool(os.getenv('MOBSF_DOMAIN_MALWARE_SCAN', '1'))
|
||||
APKID_ENABLED = bool(os.getenv('MOBSF_APKID_ENABLED', '1'))
|
||||
DOMAIN_MALWARE_SCAN = os.getenv('MOBSF_DOMAIN_MALWARE_SCAN', '1')
|
||||
APKID_ENABLED = os.getenv('MOBSF_APKID_ENABLED', '1')
|
||||
QUARK_ENABLED = bool(os.getenv('MOBSF_QUARK_ENABLED', ''))
|
||||
# ==================================================
|
||||
# ======WINDOWS STATIC ANALYSIS SETTINGS ===========
|
||||
@@ -402,8 +403,8 @@ else:
|
||||
# If you are behind a Proxy
|
||||
UPSTREAM_PROXY_ENABLED = bool(os.getenv(
|
||||
'MOBSF_UPSTREAM_PROXY_ENABLED', ''))
|
||||
UPSTREAM_PROXY_SSL_VERIFY = bool(os.getenv(
|
||||
'MOBSF_UPSTREAM_PROXY_SSL_VERIFY', '1'))
|
||||
UPSTREAM_PROXY_SSL_VERIFY = os.getenv(
|
||||
'MOBSF_UPSTREAM_PROXY_SSL_VERIFY', '1')
|
||||
UPSTREAM_PROXY_TYPE = os.getenv('MOBSF_UPSTREAM_PROXY_TYPE', 'http')
|
||||
UPSTREAM_PROXY_IP = os.getenv('MOBSF_UPSTREAM_PROXY_IP', '127.0.0.1')
|
||||
UPSTREAM_PROXY_PORT = int(os.getenv('MOBSF_UPSTREAM_PROXY_PORT', '3128'))
|
||||
|
||||
+34
-2
@@ -61,7 +61,7 @@ def upstream_proxy(flaw_type):
|
||||
proxies = {flaw_type: proxy_host}
|
||||
else:
|
||||
proxies = {flaw_type: None}
|
||||
verify = bool(settings.UPSTREAM_PROXY_SSL_VERIFY)
|
||||
verify = settings.UPSTREAM_PROXY_SSL_VERIFY in ('1', '"1"')
|
||||
return proxies, verify
|
||||
|
||||
|
||||
@@ -367,7 +367,8 @@ def get_device():
|
||||
out = subprocess.check_output([get_adb(), 'devices']).splitlines()
|
||||
if len(out) > 2:
|
||||
dev_id = out[1].decode('utf-8').split('\t')[0]
|
||||
return docker_translate_localhost(dev_id)
|
||||
if 'daemon started successfully' not in dev_id:
|
||||
return docker_translate_localhost(dev_id)
|
||||
logger.error(get_android_dm_exception_msg())
|
||||
|
||||
|
||||
@@ -626,6 +627,13 @@ def is_zip_magic(file_obj):
|
||||
return bool(magic == b'\x50\x4B\x03\x04')
|
||||
|
||||
|
||||
def is_elf_so_magic(file_obj):
|
||||
magic = file_obj.read(4)
|
||||
file_obj.seek(0, 0)
|
||||
# ELF/SO Magic
|
||||
return bool(magic == b'\x7F\x45\x4C\x46')
|
||||
|
||||
|
||||
def disable_print():
|
||||
sys.stdout = open(os.devnull, 'w')
|
||||
|
||||
@@ -680,3 +688,27 @@ def get_android_dm_exception_msg():
|
||||
' set ANALYZER_IDENTIFIER in '
|
||||
f'{get_config_loc()} or via environment variable'
|
||||
' MOBSF_ANALYZER_IDENTIFIER')
|
||||
|
||||
|
||||
def get_android_src_dir(app_dir, typ):
|
||||
"""Get Android source code location."""
|
||||
if typ == 'apk':
|
||||
src = app_dir / 'java_source'
|
||||
elif typ == 'studio':
|
||||
src = app_dir / 'app' / 'src' / 'main' / 'java'
|
||||
kt = app_dir / 'app' / 'src' / 'main' / 'kotlin'
|
||||
if not src.exists() and kt.exists():
|
||||
src = kt
|
||||
elif typ == 'eclipse':
|
||||
src = app_dir / 'src'
|
||||
return src
|
||||
|
||||
|
||||
def settings_enabled(attr):
|
||||
"""Get settings state if present."""
|
||||
disabled = ('', ' ', '""', '" "', '0', '"0"')
|
||||
if not getattr(settings, attr, True):
|
||||
return False
|
||||
if getattr(settings, attr) in disabled:
|
||||
return False
|
||||
return True
|
||||
|
||||
@@ -1,7 +1,10 @@
|
||||
"""Helpers."""
|
||||
import functools
|
||||
|
||||
from mobsf.MobSF.utils import is_zip_magic
|
||||
from mobsf.MobSF.utils import (
|
||||
is_elf_so_magic,
|
||||
is_zip_magic,
|
||||
)
|
||||
|
||||
from django.conf import settings
|
||||
from django.http import HttpRequest, HttpResponseNotAllowed
|
||||
@@ -16,6 +19,7 @@ class FileType(object):
|
||||
self.file_type = file_obj.content_type
|
||||
self.file_name_lower = file_obj.name.lower()
|
||||
self.zip = is_zip_magic(file_obj)
|
||||
self.so = is_elf_so_magic(file_obj)
|
||||
|
||||
def is_allow_file(self):
|
||||
"""
|
||||
@@ -23,6 +27,8 @@ class FileType(object):
|
||||
|
||||
return bool
|
||||
"""
|
||||
if self.so and self.is_so():
|
||||
return True
|
||||
if self.zip and (
|
||||
self.is_apk()
|
||||
or self.is_xapk()
|
||||
@@ -55,6 +61,10 @@ class FileType(object):
|
||||
return (self.file_type in settings.APK_MIME
|
||||
and self.file_name_lower.endswith('.aar'))
|
||||
|
||||
def is_so(self):
|
||||
return (self.file_type in settings.APK_MIME
|
||||
and self.file_name_lower.endswith('.so'))
|
||||
|
||||
def is_zip(self):
|
||||
return (self.file_type in settings.ZIP_MIME
|
||||
and self.file_name_lower.endswith('.zip'))
|
||||
|
||||
@@ -141,6 +141,8 @@ class Upload(object):
|
||||
return scanning.scan_jar()
|
||||
elif self.file_type.is_aar():
|
||||
return scanning.scan_aar()
|
||||
elif self.file_type.is_so():
|
||||
return scanning.scan_so()
|
||||
elif self.file_type.is_zip():
|
||||
return scanning.scan_zip()
|
||||
elif self.file_type.is_ipa():
|
||||
|
||||
@@ -116,6 +116,15 @@ class Scanning(object):
|
||||
logger.info('Performing Static Analysis of Android AAR')
|
||||
return self.data
|
||||
|
||||
def scan_so(self):
|
||||
"""Shared object file."""
|
||||
md5 = handle_uploaded_file(self.file, '.so')
|
||||
self.data['hash'] = md5
|
||||
self.data['scan_type'] = 'so'
|
||||
add_to_recent_scan(self.data)
|
||||
logger.info('Performing Static Analysis of Shared Object')
|
||||
return self.data
|
||||
|
||||
def scan_zip(self):
|
||||
"""Android /iOS Zipped Source."""
|
||||
md5 = handle_uploaded_file(self.file, '.zip')
|
||||
|
||||
@@ -66,7 +66,8 @@ class AndroidChecks(forms.Form):
|
||||
('smali', 'smali'),
|
||||
('apk', 'apk'),
|
||||
('jar', 'jar'),
|
||||
('aar', 'aar')))
|
||||
('aar', 'aar'),
|
||||
('so', 'so')))
|
||||
|
||||
def clean_file(self):
|
||||
"""Safe Extension."""
|
||||
|
||||
@@ -50,7 +50,7 @@ class StaticAnalyzerAndroid(models.Model):
|
||||
URLS = models.TextField(default=[])
|
||||
DOMAINS = models.TextField(default={})
|
||||
EMAILS = models.TextField(default=[])
|
||||
STRINGS = models.TextField(default=[])
|
||||
STRINGS = models.TextField(default={})
|
||||
FIREBASE_URLS = models.TextField(default=[])
|
||||
FILES = models.TextField(default=[])
|
||||
EXPORTED_COUNT = models.TextField(default={})
|
||||
|
||||
@@ -3,10 +3,12 @@
|
||||
import logging
|
||||
from pathlib import Path
|
||||
|
||||
from django.conf import settings
|
||||
|
||||
import lief
|
||||
|
||||
from mobsf.MobSF.utils import (
|
||||
settings_enabled,
|
||||
)
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
@@ -192,14 +194,16 @@ def elf_analysis(app_dir: str) -> dict:
|
||||
"""Perform elf analysis on shared object."""
|
||||
elf = {'elf_analysis': [], 'elf_strings': []}
|
||||
try:
|
||||
if not getattr(settings, 'SO_ANALYSIS_ENABLED', True):
|
||||
if not settings_enabled('SO_ANALYSIS_ENABLED'):
|
||||
return elf
|
||||
logger.info('Binary Analysis Started')
|
||||
# Supports APK, AAR and JAR
|
||||
sdir = Path(app_dir)
|
||||
# Supports APK, SO, AAR and JAR
|
||||
libs = [
|
||||
Path(app_dir) / 'lib',
|
||||
Path(app_dir) / 'libs',
|
||||
Path(app_dir) / 'jni']
|
||||
sdir,
|
||||
sdir / 'lib',
|
||||
sdir / 'libs',
|
||||
sdir / 'jni']
|
||||
for lib_dir in libs:
|
||||
if not lib_dir.is_dir():
|
||||
continue
|
||||
|
||||
@@ -6,7 +6,10 @@ from pathlib import Path
|
||||
|
||||
from django.conf import settings
|
||||
|
||||
from mobsf.MobSF.utils import filename_from_path
|
||||
from mobsf.MobSF.utils import (
|
||||
filename_from_path,
|
||||
get_android_src_dir,
|
||||
)
|
||||
from mobsf.StaticAnalyzer.views.common.shared_func import (
|
||||
url_n_email_extract,
|
||||
)
|
||||
@@ -31,16 +34,7 @@ def code_analysis(app_dir, typ, manifest_file):
|
||||
url_n_file = []
|
||||
url_list = []
|
||||
app_dir = Path(app_dir)
|
||||
if typ == 'apk':
|
||||
src = app_dir / 'java_source'
|
||||
elif typ == 'studio':
|
||||
src = app_dir / 'app' / 'src' / 'main' / 'java'
|
||||
kt = app_dir / 'app' / 'src' / 'main' / 'kotlin'
|
||||
if not src.exists() and kt.exists():
|
||||
src = kt
|
||||
elif typ == 'eclipse':
|
||||
src = app_dir / 'src'
|
||||
src = src.as_posix() + '/'
|
||||
src = get_android_src_dir(app_dir, typ).as_posix() + '/'
|
||||
skp = settings.SKIP_CLASS_PATH
|
||||
logger.info('Code Analysis Started on - %s',
|
||||
filename_from_path(src))
|
||||
|
||||
@@ -16,6 +16,7 @@ from mobsf.MobSF.utils import (
|
||||
filename_from_path,
|
||||
find_java_binary,
|
||||
is_file_exists,
|
||||
settings_enabled,
|
||||
)
|
||||
|
||||
|
||||
@@ -31,7 +32,7 @@ def get_dex_files(app_dir):
|
||||
def dex_2_smali(app_dir, tools_dir):
|
||||
"""Run dex2smali."""
|
||||
try:
|
||||
if not getattr(settings, 'DEX2SMALI_ENABLED', True):
|
||||
if not settings_enabled('DEX2SMALI_ENABLED'):
|
||||
return
|
||||
logger.info('DEX -> SMALI')
|
||||
dexes = get_dex_files(app_dir)
|
||||
|
||||
@@ -69,7 +69,7 @@ def get_context_from_db_entry(db_entry: QuerySet) -> dict:
|
||||
'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),
|
||||
'strings': python_dict(db_entry[0].STRINGS),
|
||||
'firebase_urls': python_list(db_entry[0].FIREBASE_URLS),
|
||||
'files': python_list(db_entry[0].FILES),
|
||||
'exported_count': python_dict(db_entry[0].EXPORTED_COUNT),
|
||||
@@ -140,7 +140,7 @@ def get_context_from_analysis(app_dic,
|
||||
'urls': code_an_dic['urls'],
|
||||
'domains': code_an_dic['domains'],
|
||||
'emails': code_an_dic['emails'],
|
||||
'strings': app_dic['strings'],
|
||||
'strings': code_an_dic['strings'],
|
||||
'firebase_urls': code_an_dic['firebase'],
|
||||
'files': app_dic['files'],
|
||||
'exported_count': man_an_dic['exported_cnt'],
|
||||
@@ -148,7 +148,7 @@ def get_context_from_analysis(app_dic,
|
||||
'quark': quark_report,
|
||||
'trackers': trackers,
|
||||
'playstore_details': app_dic['playstore'],
|
||||
'secrets': app_dic['secrets'],
|
||||
'secrets': code_an_dic['secrets'],
|
||||
}
|
||||
return context
|
||||
except Exception:
|
||||
@@ -202,7 +202,7 @@ def save_or_update(update_type,
|
||||
'URLS': code_an_dic['urls'],
|
||||
'DOMAINS': code_an_dic['domains'],
|
||||
'EMAILS': code_an_dic['emails'],
|
||||
'STRINGS': app_dic['strings'],
|
||||
'STRINGS': code_an_dic['strings'],
|
||||
'FIREBASE_URLS': code_an_dic['firebase'],
|
||||
'FILES': app_dic['files'],
|
||||
'EXPORTED_COUNT': man_an_dic['exported_cnt'],
|
||||
@@ -211,7 +211,7 @@ def save_or_update(update_type,
|
||||
'TRACKERS': trackers,
|
||||
'PLAYSTORE_DETAILS': app_dic['playstore'],
|
||||
'NETWORK_SECURITY': man_an_dic['network_security'],
|
||||
'SECRETS': app_dic['secrets'],
|
||||
'SECRETS': code_an_dic['secrets'],
|
||||
}
|
||||
if update_type == 'save':
|
||||
db_entry = StaticAnalyzerAndroid.objects.filter(
|
||||
|
||||
@@ -15,9 +15,9 @@ logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def run(request):
|
||||
"""Generate downloads for apk, jar, aar, java zip and smali zip."""
|
||||
"""Generate downloads for apk, jar, aar, so, java zip and smali zip."""
|
||||
try:
|
||||
allowed = ('apk', 'smali', 'java', 'jar', 'aar')
|
||||
allowed = ('apk', 'smali', 'java', 'jar', 'aar', 'so')
|
||||
logger.info('Generating Downloads')
|
||||
md5 = request.GET['hash']
|
||||
file_type = request.GET['file_type']
|
||||
@@ -44,7 +44,7 @@ def run(request):
|
||||
shutil.make_archive(
|
||||
dwd_file.as_posix(), 'zip', directory.as_posix())
|
||||
file_name = f'{md5}-smali.zip'
|
||||
elif file_type in ('apk', 'ipa', 'jar', 'aar'):
|
||||
elif file_type in ('apk', 'ipa', 'jar', 'aar', 'so'):
|
||||
# Binaries
|
||||
file_name = f'{md5}.{file_type}'
|
||||
src = app_dir / file_name
|
||||
|
||||
@@ -25,7 +25,9 @@ from mobsf.StaticAnalyzer.views.android.manifest_analysis import (
|
||||
manifest_analysis,
|
||||
manifest_data,
|
||||
)
|
||||
from mobsf.StaticAnalyzer.views.android.strings import strings_from_apk
|
||||
from mobsf.StaticAnalyzer.views.android.strings import (
|
||||
get_strings_metadata,
|
||||
)
|
||||
from mobsf.StaticAnalyzer.views.android.binary_analysis import elf_analysis
|
||||
from mobsf.StaticAnalyzer.views.android.cert_analysis import (
|
||||
cert_info,
|
||||
@@ -151,29 +153,24 @@ def common_analysis(request, app_dic, rescan, api, analysis_type):
|
||||
|
||||
quark_results = []
|
||||
|
||||
# Get the strings from android resource and shared objects
|
||||
string_res = strings_from_apk(
|
||||
# Get the strings and metadata
|
||||
get_strings_metadata(
|
||||
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'] = []
|
||||
elf_dict['elf_strings'],
|
||||
'apk',
|
||||
['.java'],
|
||||
code_an_dic)
|
||||
|
||||
# Firebase DB Check
|
||||
code_an_dic['firebase'] = firebase_analysis(
|
||||
list(set(code_an_dic['urls_list'])))
|
||||
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'])))
|
||||
code_an_dic['urls_list'])
|
||||
|
||||
app_dic['zipped'] = analysis_type
|
||||
app_dic['icon_hidden'] = True
|
||||
app_dic['icon_found'] = False
|
||||
|
||||
@@ -561,10 +561,11 @@
|
||||
masvs: crypto-3
|
||||
ref: https://github.com/MobSF/owasp-mstg/blob/master/Document/0x04g-Testing-Cryptography.md#mobile-app-cryptography
|
||||
- id: android_world_writable
|
||||
message: The file is World Writable. Any App can write to the file
|
||||
message: The file or SharedPreference is World Writable. Any App can write to the file
|
||||
type: RegexOr
|
||||
pattern:
|
||||
- MODE_WORLD_WRITABLE
|
||||
- \.getSharedPreferences\(.{0,50}2\)
|
||||
- 'openFileOutput\(\s*".{1,48}"\s*,\s*2\s*\)'
|
||||
severity: high
|
||||
input_case: exact
|
||||
@@ -575,10 +576,11 @@
|
||||
masvs: storage-2
|
||||
ref: https://github.com/MobSF/owasp-mstg/blob/master/Document/0x05d-Testing-Data-Storage.md#testing-local-storage-for-sensitive-data-mstg-storage-1-and-mstg-storage-2
|
||||
- id: android_world_readable
|
||||
message: The file is World Readable. Any App can read from the file
|
||||
message: The file or SharedPreference is World Readable. Any App can read from the file
|
||||
type: RegexOr
|
||||
pattern:
|
||||
- MODE_WORLD_READABLE
|
||||
- \.getSharedPreferences\(.{0,50}1\)
|
||||
- 'openFileOutput\(\s*".{1,48}"\s*,\s*1\s*\)'
|
||||
severity: high
|
||||
input_case: exact
|
||||
|
||||
@@ -0,0 +1,184 @@
|
||||
"""Handle (.so) Shared Object file."""
|
||||
import logging
|
||||
|
||||
from django.conf import settings
|
||||
from django.shortcuts import render
|
||||
|
||||
import mobsf.MalwareAnalyzer.views.VirusTotal as VirusTotal
|
||||
from mobsf.MobSF.utils import (
|
||||
file_size,
|
||||
)
|
||||
from mobsf.StaticAnalyzer.views.common.shared_func import (
|
||||
firebase_analysis,
|
||||
hash_gen,
|
||||
update_scan_timestamp,
|
||||
)
|
||||
from mobsf.StaticAnalyzer.views.android.binary_analysis import (
|
||||
elf_analysis,
|
||||
)
|
||||
from mobsf.StaticAnalyzer.views.android.strings import (
|
||||
get_strings_metadata,
|
||||
)
|
||||
from mobsf.StaticAnalyzer.models import (
|
||||
StaticAnalyzerAndroid,
|
||||
)
|
||||
from mobsf.StaticAnalyzer.views.android.db_interaction import (
|
||||
get_context_from_analysis,
|
||||
get_context_from_db_entry,
|
||||
save_or_update,
|
||||
)
|
||||
from mobsf.MalwareAnalyzer.views.MalwareDomainCheck import MalwareDomainCheck
|
||||
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def so_analysis(request, app_dic, rescan, api):
|
||||
app_dic['app_file'] = f'{app_dic["md5"]}.so' # 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'])
|
||||
if db_entry.exists() and not rescan:
|
||||
context = get_context_from_db_entry(db_entry)
|
||||
else:
|
||||
app_dic['size'] = f'{str(file_size(app_dic["app_path"]))}MB'
|
||||
app_dic['sha1'], app_dic['sha256'] = hash_gen(app_dic['app_path'])
|
||||
app_dic['files'] = []
|
||||
app_dic['certz'] = []
|
||||
app_dic['playstore'] = {'error': True}
|
||||
app_dic['manifest_file'] = None
|
||||
app_dic['parsed_xml'] = ''
|
||||
app_dic['mani'] = ''
|
||||
man_data_dic = {
|
||||
'services': [],
|
||||
'activities': [],
|
||||
'receivers': [],
|
||||
'providers': [],
|
||||
'libraries': [],
|
||||
'categories': [],
|
||||
'perm': {},
|
||||
'packagename': app_dic['app_name'],
|
||||
'mainactivity': '',
|
||||
'min_sdk': '',
|
||||
'max_sdk': '',
|
||||
'target_sdk': '',
|
||||
'androver': '',
|
||||
'androvername': '',
|
||||
'icons': [],
|
||||
}
|
||||
man_an_dic = {
|
||||
'manifest_anal': [],
|
||||
'exported_act': [],
|
||||
'exported_cnt': {
|
||||
'exported_activities': 0,
|
||||
'exported_services': 0,
|
||||
'exported_receivers': 0,
|
||||
'exported_providers': 0,
|
||||
},
|
||||
'browsable_activities': {},
|
||||
'permissions': {},
|
||||
'icon_hidden': True,
|
||||
'network_security': {
|
||||
'network_findings': [],
|
||||
'network_summary': {},
|
||||
},
|
||||
}
|
||||
cert_dic = {
|
||||
'certificate_info': '',
|
||||
'certificate_findings': [],
|
||||
'certificate_summary': {},
|
||||
}
|
||||
app_dic['real_name'] = ''
|
||||
elf_dict = elf_analysis(app_dic['app_dir'])
|
||||
apkid_results = {}
|
||||
tracker_res = {}
|
||||
code_an_dic = {
|
||||
'api': {},
|
||||
'findings': {},
|
||||
'niap': {},
|
||||
'urls_list': [],
|
||||
'urls': [],
|
||||
'emails': [],
|
||||
}
|
||||
quark_results = []
|
||||
|
||||
# Get the strings and metadata from shared object
|
||||
get_strings_metadata(
|
||||
None,
|
||||
None,
|
||||
elf_dict['elf_strings'],
|
||||
None,
|
||||
None,
|
||||
code_an_dic)
|
||||
|
||||
# Firebase DB Check
|
||||
code_an_dic['firebase'] = firebase_analysis(
|
||||
code_an_dic['urls_list'])
|
||||
# Domain Extraction and Malware Check
|
||||
logger.info(
|
||||
'Performing Malware Check on extracted Domains')
|
||||
code_an_dic['domains'] = MalwareDomainCheck().scan(
|
||||
code_an_dic['urls_list'])
|
||||
|
||||
app_dic['zipped'] = 'so'
|
||||
app_dic['icon_hidden'] = True
|
||||
app_dic['icon_found'] = False
|
||||
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['appsec'] = {}
|
||||
context['average_cvss'] = None
|
||||
context['dynamic_analysis_done'] = False
|
||||
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)
|
||||
@@ -55,7 +55,9 @@ from mobsf.StaticAnalyzer.views.android.manifest_analysis import (
|
||||
manifest_data,
|
||||
)
|
||||
from mobsf.StaticAnalyzer.views.android.playstore import get_app_details
|
||||
from mobsf.StaticAnalyzer.views.android.strings import strings_from_apk
|
||||
from mobsf.StaticAnalyzer.views.android.strings import (
|
||||
get_strings_metadata,
|
||||
)
|
||||
from mobsf.StaticAnalyzer.views.android.xapk import (
|
||||
handle_split_apk,
|
||||
handle_xapk,
|
||||
@@ -64,6 +66,9 @@ from mobsf.StaticAnalyzer.views.android.jar_aar import (
|
||||
aar_analysis,
|
||||
jar_analysis,
|
||||
)
|
||||
from mobsf.StaticAnalyzer.views.android.so import (
|
||||
so_analysis,
|
||||
)
|
||||
from mobsf.StaticAnalyzer.views.common.shared_func import (
|
||||
firebase_analysis,
|
||||
get_avg_cvss,
|
||||
@@ -105,8 +110,8 @@ def static_analyzer(request, api=False):
|
||||
match = re.match('^[0-9a-f]{32}$', checksum)
|
||||
if (match
|
||||
and filename.lower().endswith(
|
||||
('.apk', '.xapk', '.zip', '.apks', '.jar', '.aar'))
|
||||
and typ in ['zip', 'apk', 'xapk', 'apks', 'jar', 'aar']):
|
||||
('.apk', '.xapk', '.zip', '.apks', '.jar', '.aar', '.so'))
|
||||
and typ in ['zip', 'apk', 'xapk', 'apks', 'jar', 'aar', 'so']):
|
||||
app_dic['dir'] = Path(settings.BASE_DIR) # BASE DIR
|
||||
app_dic['app_name'] = filename # APP ORIGINAL NAME
|
||||
app_dic['md5'] = checksum # MD5
|
||||
@@ -213,29 +218,24 @@ def static_analyzer(request, api=False):
|
||||
app_dic['app_dir'],
|
||||
app_dic['app_path'])
|
||||
|
||||
# Get the strings from android resource and shared objects
|
||||
string_res = strings_from_apk(
|
||||
# Get the strings and metadata
|
||||
get_strings_metadata(
|
||||
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'] = []
|
||||
elf_dict['elf_strings'],
|
||||
'apk',
|
||||
['.java'],
|
||||
code_an_dic)
|
||||
|
||||
# Firebase DB Check
|
||||
code_an_dic['firebase'] = firebase_analysis(
|
||||
list(set(code_an_dic['urls_list'])))
|
||||
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'])))
|
||||
code_an_dic['urls_list'])
|
||||
|
||||
app_dic['zipped'] = 'apk'
|
||||
|
||||
logger.info('Connecting to Database')
|
||||
@@ -304,6 +304,8 @@ def static_analyzer(request, api=False):
|
||||
return jar_analysis(request, app_dic, rescan, api)
|
||||
elif typ == 'aar':
|
||||
return aar_analysis(request, app_dic, rescan, api)
|
||||
elif typ == 'so':
|
||||
return so_analysis(request, app_dic, rescan, api)
|
||||
elif typ == 'zip':
|
||||
ret = (
|
||||
'/static_analyzer_ios/?name='
|
||||
@@ -424,14 +426,25 @@ def static_analyzer(request, api=False):
|
||||
app_dic['app_dir'],
|
||||
pro_type,
|
||||
app_dic['manifest_file'])
|
||||
|
||||
# Get the strings and metadata
|
||||
get_strings_metadata(
|
||||
None,
|
||||
app_dic['app_dir'],
|
||||
None,
|
||||
pro_type,
|
||||
['.java', '.kt'],
|
||||
code_an_dic)
|
||||
|
||||
# Firebase DB Check
|
||||
code_an_dic['firebase'] = firebase_analysis(
|
||||
list(set(code_an_dic['urls_list'])))
|
||||
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'])))
|
||||
code_an_dic['urls_list'])
|
||||
|
||||
# Extract Trackers from Domains
|
||||
trk = Trackers.Trackers(
|
||||
None, app_dic['tools_dir'])
|
||||
@@ -506,7 +519,7 @@ def static_analyzer(request, api=False):
|
||||
else:
|
||||
return render(request, template, context)
|
||||
else:
|
||||
err = ('Only APK, JAR, AAR, IPA and Zipped '
|
||||
err = ('Only APK, JAR, AAR, SO, IPA and Zipped '
|
||||
'Android/iOS Source code supported now!')
|
||||
logger.error(err)
|
||||
else:
|
||||
|
||||
@@ -2,27 +2,59 @@
|
||||
"""Module for strings-method for java."""
|
||||
import logging
|
||||
import os
|
||||
from pathlib import Path
|
||||
|
||||
from androguard.core.bytecodes import apk
|
||||
|
||||
from mobsf.StaticAnalyzer.views.common.shared_func import (
|
||||
is_secret,
|
||||
is_secret_key,
|
||||
strings_and_entropies,
|
||||
url_n_email_extract,
|
||||
)
|
||||
from mobsf.StaticAnalyzer.views.common.entropy import (
|
||||
get_entropies,
|
||||
)
|
||||
from mobsf.MobSF.utils import (
|
||||
get_android_src_dir,
|
||||
)
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
logging.getLogger('androguard').setLevel(logging.ERROR)
|
||||
|
||||
|
||||
def strings_from_apk(app_file, app_dir, elf_strings):
|
||||
"""Extract the strings from an app."""
|
||||
def strings_from_so(elf_strings):
|
||||
"""Extract Strings from so file."""
|
||||
sos = []
|
||||
try:
|
||||
logger.info('Extracting Strings from APK')
|
||||
dat = []
|
||||
secrets = []
|
||||
urls = []
|
||||
urls_nf = []
|
||||
emails_nf = []
|
||||
for solib in elf_strings:
|
||||
for so, str_list in solib.items():
|
||||
if not str_list:
|
||||
continue
|
||||
# extract url, email
|
||||
so_str = ' '.join(str_list)
|
||||
so_urls, so_urls_nf, so_emails_nf = url_n_email_extract(
|
||||
so_str, so)
|
||||
sos.append({so: {
|
||||
'secrets': list(get_entropies(so_str)),
|
||||
'strings': list(set(str_list)),
|
||||
'urls_list': so_urls,
|
||||
'urls_nf': so_urls_nf,
|
||||
'emails_nf': so_emails_nf,
|
||||
}})
|
||||
except Exception:
|
||||
logger.exception('Extracting Data from SO')
|
||||
return sos
|
||||
|
||||
|
||||
def strings_from_apk(app_file, app_dir):
|
||||
"""Extract Strings from an APK."""
|
||||
dat = []
|
||||
secrets = []
|
||||
urls = []
|
||||
urls_nf = []
|
||||
emails_nf = []
|
||||
try:
|
||||
logger.info('Extracting Data from APK')
|
||||
apk_file = os.path.join(app_dir, app_file)
|
||||
and_a = apk.APK(apk_file)
|
||||
rsrc = and_a.get_android_resources()
|
||||
@@ -31,35 +63,85 @@ def strings_from_apk(app_file, app_dir, elf_strings):
|
||||
rsrc.get_strings_resources()
|
||||
for i in rsrc.values[pkg].keys():
|
||||
res_string = rsrc.values[pkg][i].get('string')
|
||||
if res_string:
|
||||
for duo in res_string:
|
||||
cap_str = '"' + duo[0] + '" : "' + duo[1] + '"'
|
||||
if is_secret(duo[0] + '"') and ' ' not in duo[1]:
|
||||
secrets.append(cap_str)
|
||||
dat.append(cap_str)
|
||||
data_string = ''.join(dat)
|
||||
if not res_string:
|
||||
continue
|
||||
for duo in res_string:
|
||||
cap_str = '"' + duo[0] + '" : "' + duo[1] + '"'
|
||||
# Extract possible secret holding keys
|
||||
if is_secret_key(duo[0] + '"') and ' ' not in duo[1]:
|
||||
secrets.append(cap_str)
|
||||
dat.append(cap_str)
|
||||
# Extract URLs and Emails from Android String Resources
|
||||
urls, urls_nf, emails_nf = url_n_email_extract(
|
||||
data_string, 'Android String Resource')
|
||||
if elf_strings:
|
||||
for solib in elf_strings:
|
||||
for so, str_list in solib.items():
|
||||
# add to strings from jar
|
||||
dat.extend(str_list)
|
||||
# extract url, email
|
||||
so_str = ' '.join(str_list)
|
||||
su, suf, sem = url_n_email_extract(
|
||||
so_str, so)
|
||||
urls.extend(su)
|
||||
urls_nf.extend(suf)
|
||||
emails_nf.extend(sem)
|
||||
strings_dat = list(set(dat))
|
||||
return {
|
||||
'strings': strings_dat,
|
||||
'urls_list': urls,
|
||||
'url_nf': urls_nf,
|
||||
'emails_nf': emails_nf,
|
||||
'secrets': secrets,
|
||||
}
|
||||
''.join(dat), 'Android String Resource')
|
||||
except Exception:
|
||||
logger.exception('Extracting Strings from APK')
|
||||
return {}
|
||||
logger.exception('Extracting Data from APK')
|
||||
return {
|
||||
'strings': list(set(dat)),
|
||||
'urls_list': urls,
|
||||
'urls_nf': urls_nf,
|
||||
'emails_nf': emails_nf,
|
||||
'secrets': secrets,
|
||||
}
|
||||
|
||||
|
||||
def strings_from_code(src_dir, typ, exts):
|
||||
"""Extract Strings and Secrets from Java/Kotlin code."""
|
||||
data = {
|
||||
'strings': set(),
|
||||
'secrets': set(),
|
||||
}
|
||||
try:
|
||||
src_dir = get_android_src_dir(Path(src_dir), typ)
|
||||
data = strings_and_entropies(src_dir, exts)
|
||||
except Exception:
|
||||
logger.exception('Extracting Data from Code')
|
||||
return data
|
||||
|
||||
|
||||
def get_strings_metadata(app_file, app_dir, elf_strings, typ, exts, code_dic):
|
||||
"""Get Strings, secrets, entropies, URLs, emails."""
|
||||
strings = {
|
||||
'strings_apk_res': {},
|
||||
'strings_so': [],
|
||||
'strings_code': {},
|
||||
}
|
||||
urls_list = []
|
||||
urls_n_files = []
|
||||
emails_n_files = []
|
||||
secrets = []
|
||||
if app_file:
|
||||
# APK
|
||||
apk_res = strings_from_apk(app_file, app_dir)
|
||||
strings['strings_apk_res'] = apk_res['strings']
|
||||
urls_list.extend(apk_res['urls_list'])
|
||||
urls_n_files.extend(apk_res['urls_nf'])
|
||||
emails_n_files.extend(apk_res['emails_nf'])
|
||||
secrets.extend(apk_res['secrets'])
|
||||
if elf_strings:
|
||||
# ELF (.so) by file
|
||||
sos = strings_from_so(elf_strings)
|
||||
so_strings = []
|
||||
for so in sos:
|
||||
for so_file, s in so.items():
|
||||
so_strings.append({so_file: s['strings']})
|
||||
urls_list.extend(s['urls_list'])
|
||||
urls_n_files.extend(s['urls_nf'])
|
||||
emails_n_files.extend(s['emails_nf'])
|
||||
secrets.extend(s['secrets'])
|
||||
secrets = list(set(secrets))
|
||||
strings['strings_so'] = so_strings
|
||||
|
||||
if exts:
|
||||
# Source Code
|
||||
code_res = strings_from_code(app_dir, typ, exts)
|
||||
strings['strings_code'] = list(code_res['strings'])
|
||||
secrets.extend(code_res['secrets'])
|
||||
|
||||
code_dic['strings'] = strings
|
||||
code_dic['secrets'] = list(secrets)
|
||||
# Code Analysis has urls, urlsnfiles and emailsnfiles
|
||||
code_dic['urls'].extend(urls_n_files)
|
||||
code_dic['emails'].extend(emails_n_files)
|
||||
code_dic['urls_list'].extend(urls_list)
|
||||
code_dic['urls_list'] = list(set(code_dic['urls_list']))
|
||||
|
||||
@@ -52,7 +52,7 @@ def run(request, api=False):
|
||||
try:
|
||||
src, syntax, _ = find_java_source_folder(base)
|
||||
except StopIteration:
|
||||
msg = 'Invalid Directory Structure'
|
||||
msg = 'Invalid directory or file extension'
|
||||
return print_n_send_error_response(request, msg, api)
|
||||
|
||||
sfile = src / fil
|
||||
|
||||
@@ -176,8 +176,10 @@ def common_fields(findings, data):
|
||||
warn = len(findings.get('warning'))
|
||||
sec = len(findings.get('secure'))
|
||||
total = high + warn + sec
|
||||
score = int(100 - (
|
||||
((high * 1) + (warn * .5) - (sec * .2)) / total) * 100)
|
||||
score = 0
|
||||
if total > 0:
|
||||
score = int(100 - (
|
||||
((high * 1) + (warn * .5) - (sec * .2)) / total) * 100)
|
||||
if score > 100:
|
||||
score = 100
|
||||
findings['security_score'] = score
|
||||
|
||||
@@ -0,0 +1,63 @@
|
||||
|
||||
# -*- coding: utf_8 -*-
|
||||
"""Entropy Scanner."""
|
||||
import re
|
||||
import math
|
||||
|
||||
MAX_LENGTH = 21
|
||||
ENTROPY_PATTERNS = [
|
||||
{
|
||||
# Base64
|
||||
'pattern': re.compile(r'(?m)[a-zA-Z\d+/=]{20,}'),
|
||||
'charset': ('ABCDEFGHIJKLMNOPQRSTUVWXYZabcdef'
|
||||
'ghijklmnopqrstuvwxyz0123456789+/='),
|
||||
'score': 4.5,
|
||||
},
|
||||
{
|
||||
# Hex
|
||||
'pattern': re.compile(r'(?m)[a-fA-F\d-]{20,}'),
|
||||
'charset': '1234567890abcdefABCDEF',
|
||||
'score': 3.0,
|
||||
},
|
||||
]
|
||||
|
||||
|
||||
def entropy(data, charset):
|
||||
"""Shannon Entropy score calculations."""
|
||||
# Based on
|
||||
# http://blog.dkbza.org/2007/05/
|
||||
# scanning-data-for-entropy-anomalies.html
|
||||
entropy = 0
|
||||
for char in charset:
|
||||
p_x = float(data.count(char)) / len(data)
|
||||
if p_x > 0:
|
||||
entropy += -p_x * math.log2(p_x)
|
||||
return entropy
|
||||
|
||||
|
||||
def exclude(secret):
|
||||
"""Exclude entropies."""
|
||||
excludes = ('abcdefghi', 'kotlin/')
|
||||
if secret.startswith('L') and '/' in secret:
|
||||
return True
|
||||
if any(i in secret.lower() for i in excludes):
|
||||
return True
|
||||
if secret.count('/') > 1:
|
||||
# URLs/paths, ignore them
|
||||
return True
|
||||
if secret.isalpha():
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
def get_entropies(data):
|
||||
patterns = set()
|
||||
for ascii_string in data:
|
||||
if len(ascii_string) < MAX_LENGTH:
|
||||
continue
|
||||
for p in ENTROPY_PATTERNS:
|
||||
for i in re.findall(p['pattern'], ascii_string):
|
||||
score = p['score']
|
||||
if entropy(i, p['charset']) > score and not exclude(i):
|
||||
patterns.add(i)
|
||||
return patterns
|
||||
@@ -27,9 +27,26 @@ from mobsf.MobSF.utils import (
|
||||
upstream_proxy,
|
||||
)
|
||||
from mobsf.StaticAnalyzer.models import RecentScansDB
|
||||
from mobsf.StaticAnalyzer.views.comparer import generic_compare
|
||||
from mobsf.StaticAnalyzer.views.comparer import (
|
||||
generic_compare,
|
||||
)
|
||||
from mobsf.StaticAnalyzer.views.common.entropy import (
|
||||
get_entropies,
|
||||
)
|
||||
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
# Regex to capture strings between quotes or <string> tag
|
||||
STRINGS_REGEX = re.compile(r'(?<=\")(.+?)(?=\")|(?<=\<string>)(.+?)(?=\<)')
|
||||
# MobSF Custom regex to catch maximum URI like strings
|
||||
URL_REGEX = re.compile(
|
||||
(
|
||||
r'((?:https?://|s?ftps?://|'
|
||||
r'file://|javascript:|data:|www\d{0,3}[.])'
|
||||
r'[\w().=/;,#:@?&~*+!$%\'{}-]+)'
|
||||
),
|
||||
re.UNICODE)
|
||||
EMAIL_REGEX = re.compile(r'[\w.-]{1,20}@[\w-]{1,20}\.[\w]{2,10}')
|
||||
|
||||
|
||||
def hash_gen(app_path) -> tuple:
|
||||
@@ -91,15 +108,8 @@ def url_n_email_extract(dat, relative_path):
|
||||
urllist = []
|
||||
url_n_file = []
|
||||
email_n_file = []
|
||||
# URLs Extraction My Custom regex
|
||||
pattern = re.compile(
|
||||
(
|
||||
r'((?:https?://|s?ftps?://|'
|
||||
r'file://|javascript:|data:|www\d{0,3}[.])'
|
||||
r'[\w().=/;,#:@?&~*+!$%\'{}-]+)'
|
||||
),
|
||||
re.UNICODE)
|
||||
urllist = re.findall(pattern, dat)
|
||||
# URL Extraction
|
||||
urllist = URL_REGEX.findall(dat.lower())
|
||||
uflag = 0
|
||||
for url in urllist:
|
||||
if url not in urls:
|
||||
@@ -109,10 +119,9 @@ def url_n_email_extract(dat, relative_path):
|
||||
url_n_file.append(
|
||||
{'urls': urls, 'path': escape(relative_path)})
|
||||
|
||||
# Email Extraction Regex
|
||||
regex = re.compile(r'[\w.-]{1,20}@[\w-]{1,20}\.[\w]{2,10}')
|
||||
# Email Extraction
|
||||
eflag = 0
|
||||
for email in regex.findall(dat.lower()):
|
||||
for email in EMAIL_REGEX.findall(dat.lower()):
|
||||
if (email not in emails) and (not email.startswith('//')):
|
||||
emails.append(email)
|
||||
eflag = 1
|
||||
@@ -207,8 +216,8 @@ def find_java_source_folder(base_folder: Path):
|
||||
if p[0].exists())
|
||||
|
||||
|
||||
def is_secret(inp):
|
||||
"""Check if captures string is a possible secret."""
|
||||
def is_secret_key(inp):
|
||||
"""Check if the key in the key/value pair is interesting."""
|
||||
inp = inp.lower()
|
||||
iden = (
|
||||
'api"', 'key"', 'api_', 'key_', 'secret"',
|
||||
@@ -231,3 +240,30 @@ def is_secret(inp):
|
||||
)
|
||||
not_str = any(i in inp for i in not_string)
|
||||
return any(i in inp for i in iden) and not not_str
|
||||
|
||||
|
||||
def strings_and_entropies(src, exts):
|
||||
"""Get Strings and Entropies."""
|
||||
logger.info('Extracting Data from Source Code')
|
||||
data = {
|
||||
'strings': set(),
|
||||
'secrets': set(),
|
||||
}
|
||||
try:
|
||||
if not src.exists():
|
||||
return data
|
||||
for p in src.rglob('*'):
|
||||
if p.suffix not in exts or not p.exists():
|
||||
continue
|
||||
matches = STRINGS_REGEX.finditer(
|
||||
p.read_text(encoding='utf-8', errors='ignore'),
|
||||
re.MULTILINE)
|
||||
for match in matches:
|
||||
if len(match.group()) < 4:
|
||||
continue
|
||||
data['strings'].add(match.group())
|
||||
if data['strings']:
|
||||
data['secrets'] = get_entropies(data['strings'])
|
||||
except Exception:
|
||||
logger.exception('Extracting Data from Code')
|
||||
return data
|
||||
|
||||
@@ -30,7 +30,7 @@ from mobsf.StaticAnalyzer.views.ios.app_transport_security import (
|
||||
check_transport_security,
|
||||
)
|
||||
from mobsf.StaticAnalyzer.views.common.shared_func import (
|
||||
is_secret,
|
||||
is_secret_key,
|
||||
)
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
@@ -234,11 +234,11 @@ def get_plist_secrets(xml_string):
|
||||
xml_list = xml_string.split('\n')
|
||||
|
||||
for index, line in enumerate(xml_list):
|
||||
if '<key>' in line and is_secret(_remove_tags(line)):
|
||||
if '<key>' in line and is_secret_key(_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)}')
|
||||
f'{_remove_tags(line)} : {_remove_tags(value)}')
|
||||
return result_list
|
||||
|
||||
@@ -36,6 +36,7 @@ from mobsf.StaticAnalyzer.views.common.shared_func import (
|
||||
firebase_analysis,
|
||||
get_avg_cvss,
|
||||
hash_gen,
|
||||
strings_and_entropies,
|
||||
unzip,
|
||||
update_scan_timestamp,
|
||||
)
|
||||
@@ -224,6 +225,11 @@ def static_analyzer_ios(request, api=False):
|
||||
infoplist_dict['plist_xml'])
|
||||
code_analysis_dic = ios_source_analysis(
|
||||
app_dict['app_dir'])
|
||||
ios_strs = strings_and_entropies(
|
||||
Path(app_dict['app_dir']),
|
||||
['.swift', '.m', '.h', '.plist'])
|
||||
if ios_strs['secrets']:
|
||||
app_dict['secrets'].extend(list(ios_strs['secrets']))
|
||||
# Get App Icon
|
||||
app_dict['icon_found'] = get_icon_source(
|
||||
app_dict['md5_hash'],
|
||||
@@ -241,7 +247,7 @@ def static_analyzer_ios(request, api=False):
|
||||
'checksec': {},
|
||||
'libraries': [],
|
||||
'bin_code_analysis': {},
|
||||
'strings': [],
|
||||
'strings': list(ios_strs['strings']),
|
||||
'bin_info': {},
|
||||
'bin_type': code_analysis_dic['source_type'],
|
||||
}
|
||||
|
||||
@@ -2,10 +2,12 @@
|
||||
"""SAST engine."""
|
||||
import logging
|
||||
|
||||
from django.conf import settings
|
||||
|
||||
from libsast import Scanner
|
||||
|
||||
from mobsf.MobSF.utils import (
|
||||
settings_enabled,
|
||||
)
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
@@ -28,7 +30,7 @@ def scan(rule, extensions, paths, ignore_paths=None):
|
||||
|
||||
def niap_scan(rule, extensions, paths, apath, ignore_paths=None):
|
||||
"""NIAP scan."""
|
||||
if not getattr(settings, 'NIAP_ENABLED', True):
|
||||
if not settings_enabled('NIAP_ENABLED'):
|
||||
return {}
|
||||
try:
|
||||
logger.info('Running NIAP Analyzer')
|
||||
|
||||
@@ -225,10 +225,10 @@
|
||||
// Is valid file extensions
|
||||
function isValidExt(file_name){
|
||||
var val = file_name.toLowerCase();
|
||||
var regex = new RegExp("^(.{1,300}?)\.(ipa|apk|apks|xapk|jar|aar|zip|appx)$");
|
||||
var regex = new RegExp("^(.{1,300}?)\.(ipa|apk|apks|xapk|jar|aar|so|zip|appx)$");
|
||||
val = val.replace(/^.*[\\\/]/, '');
|
||||
if (!(regex.test(val))) {
|
||||
_('status').innerText = "Please upload APK/APKS/XAPK/JAR/AAR/IPA/ZIP/APPX only!";
|
||||
_('status').innerText = "Please upload APK/APKS/XAPK/JAR/AAR/SO/IPA/ZIP/APPX only!";
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
|
||||
@@ -55,10 +55,12 @@
|
||||
<tr>
|
||||
<td style="text-align: center;">
|
||||
<img id="app_icon" src="/download/{{ e.MD5 }}-icon.png" onerror="this.src='{% static 'img/no_icon.png' %}'">
|
||||
{% if e.PACKAGE_NAME %}
|
||||
{% if e.PACKAGE_NAME or e.APP_NAME %}
|
||||
<br/><strong>{{ e.APP_NAME }} {% if e.VERSION_NAME %} - {{ e.VERSION_NAME }} {% endif %}</strong>
|
||||
</br>{{ e.PACKAGE_NAME }}
|
||||
{% if '.so' != e.FILE_NAME|slice:"-3:"%}
|
||||
<p> <a href="../appsec_dashboard/{{ e.MD5 }}/" class="btn btn-sm btn-outline-primary" role="button"><i class="fas fa-user-shield"></i> MobSF Scorecard</a></p>
|
||||
{% endif %}
|
||||
<p><a class="btn btn-primary btn-sm" href="../{{ e.ANALYZER }}/?name={{e.FILE_NAME | urlencode}}&checksum={{e.MD5}}&type={{e.SCAN_TYPE}}"><i class="fas fa-eye"></i> Static Report</a>
|
||||
{% if '.apk' == e.FILE_NAME|slice:"-4:" or '.xapk' == e.FILE_NAME|slice:"-5:" or '.apks' == e.FILE_NAME|slice:"-5:"%}
|
||||
<a class="btn btn-success btn-sm {% if not e.DYNAMIC_REPORT_EXISTS %}disabled{% endif %}" href="../dynamic_report/{{ e.MD5 }}"><i class="fa fa-mobile"></i> Dynamic Report</a>
|
||||
@@ -76,6 +78,7 @@
|
||||
{% elif '.apks' == e.FILE_NAME|slice:"-5:"%}<i class="fab fa-android fa-3x"></i>
|
||||
{% elif '.jar' == e.FILE_NAME|slice:"-4:"%}<i class="fab fa-java fa-3x"></i>
|
||||
{% elif '.aar' == e.FILE_NAME|slice:"-4:"%}<i class="fas fa-table fa-3x"></i>
|
||||
{% elif '.so' == e.FILE_NAME|slice:"-3:"%}<i class="fa fa-th-large fa-3x"></i>
|
||||
{% elif '.ipa' == e.FILE_NAME|slice:"-4:"%}<i class="fab fa-apple fa-3x"></i>
|
||||
{% elif '.zip' == e.FILE_NAME|slice:"-4:"%}<i class="fas fa-file-archive fa-3x"></i>
|
||||
{% elif '.appx' == e.FILE_NAME|slice:"-5:"%}<i class="fab fa-windows fa-3x"></i>
|
||||
|
||||
@@ -64,6 +64,7 @@
|
||||
<td class="no_border"><h3>Scan Date:</h3></td>
|
||||
<td class="no_border"><h3>{{ timestamp }}</h3></td>
|
||||
</tr>
|
||||
{% if app_type not in 'so' %}
|
||||
{% if average_cvss %}
|
||||
<tr>
|
||||
<td class="no_border"><h3>Average CVSS Score:</h3></td>
|
||||
@@ -122,6 +123,7 @@
|
||||
<td class="no_border"><h3><span {% if trackers.detected_trackers > 0 %} class="warning" {% endif %}>{{ trackers.detected_trackers }}/{{ trackers.total_trackers }}</span></h3></td>
|
||||
</tr>
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
{% if virus_total and virus_total.items|length > 9 and 'request successfully queued' not in virus_total.verbose_msg %}
|
||||
<tr>
|
||||
<td class="no_border"><h3>VirusTotal Detection:</h3></td>
|
||||
@@ -130,6 +132,7 @@
|
||||
{% endif %}
|
||||
|
||||
</table>
|
||||
{% if app_type not in 'so' %}
|
||||
</br></br>
|
||||
</br></br>
|
||||
</br></br>
|
||||
@@ -159,7 +162,7 @@
|
||||
</div>
|
||||
|
||||
</article>
|
||||
|
||||
{% endif %}
|
||||
<article id="contents">
|
||||
|
||||
|
||||
@@ -171,7 +174,7 @@
|
||||
<h5>SHA1:</h5> {{ sha1 }}</br>
|
||||
<h5>SHA256:</h5> {{ sha256 }}</br>
|
||||
|
||||
|
||||
{% if app_type not in 'so' %}
|
||||
<h2><i class="fas fa-info"></i> APP INFORMATION</h2>
|
||||
|
||||
<h5>App Name:</h5> {{ app_name }}</br>
|
||||
@@ -182,8 +185,9 @@
|
||||
<h5>Max SDK:</h5> {{ max_sdk }}</br>
|
||||
<h5>Android Version Name:</h5> {{ version_name }}</br>
|
||||
<h5>Android Version Code:</h5> {{ version_code }}</br>
|
||||
|
||||
{% if app_type not in 'jar,aar' %}
|
||||
{% endif %}
|
||||
|
||||
{% if app_type not in 'jar,aar,so' %}
|
||||
<h2><i class="fas fa-th-large"></i> APP COMPONENTS </h2>
|
||||
|
||||
<h5>Activities:</h5> {{ activities | length }}</br>
|
||||
@@ -195,6 +199,7 @@
|
||||
<h5>Exported Receivers:</h5> <span {% if exported_count.exported_receivers > 0 %} class="danger" {% endif %}> {{ exported_count.exported_receivers }}</span></br>
|
||||
<h5>Exported Providers:</h5> <span {% if exported_count.exported_providers > 0 %} class="danger" {% endif %}> {{ exported_count.exported_providers }}</span></br>
|
||||
{% endif %}
|
||||
{% if app_type not in 'so' %}
|
||||
<h2><i class="fa fa-certificate"></i> CERTIFICATE INFORMATION</h2>
|
||||
{% if certificate_analysis.certificate_info %}
|
||||
<div class="panel-body">
|
||||
@@ -203,6 +208,7 @@
|
||||
{% else %}
|
||||
<span class="danger">Failed to read Code Signing Certificate or none available.</span>
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
{% if permissions %}
|
||||
<h2><i class="fa fa-list"></i> APPLICATION PERMISSIONS</h2>
|
||||
|
||||
@@ -400,7 +406,7 @@
|
||||
</tbody>
|
||||
</table>
|
||||
{% endif %}
|
||||
|
||||
{% if app_type not in 'so' %}
|
||||
<h2><i class="fas fa-lock"></i> NETWORK SECURITY</h2>
|
||||
{% if network_security and 'network_summary' in network_security and network_security.network_summary|length > 0 %}
|
||||
<span class="danger">HIGH: </span>
|
||||
@@ -617,7 +623,7 @@
|
||||
{% endif %}
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
{% endif %}
|
||||
|
||||
{% if binary_analysis %}
|
||||
<h2><i class="fa fa-flag"></i> SHARED LIBRARY BINARY ANALYSIS</h2>
|
||||
@@ -672,7 +678,7 @@
|
||||
</tbody>
|
||||
</table>
|
||||
{% endif %}
|
||||
|
||||
{% if app_type not in 'so' %}
|
||||
<h2><i class="fa fa-id-badge"></i> NIAP ANALYSIS v1.3</h2>
|
||||
<table class="basic">
|
||||
<thead>
|
||||
@@ -704,7 +710,7 @@
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
{% endif %}
|
||||
<!-- Added for anyone who needs QUARK Results in Report
|
||||
{% if quark %}
|
||||
<h2><i class="fab fa-quora"></i> QUARK </h2>
|
||||
@@ -802,7 +808,8 @@
|
||||
</tbody>
|
||||
</table>
|
||||
{% endif %}
|
||||
<!--
|
||||
<!-- Display urls only for so files-->
|
||||
{% if app_type in 'so' %}
|
||||
{% if urls %}
|
||||
<h2><i class="fas fa-globe"></i> URLS </h2>
|
||||
<table class="basic">
|
||||
@@ -828,7 +835,7 @@
|
||||
</tbody>
|
||||
</table>
|
||||
{% endif %}
|
||||
-->
|
||||
{% endif %}
|
||||
{% if firebase_urls %}
|
||||
<h2><i class="fas fa-database"></i> FIREBASE DATABASES</h2>
|
||||
<table class="basic">
|
||||
|
||||
@@ -75,6 +75,7 @@
|
||||
</p>
|
||||
</a>
|
||||
</li>
|
||||
{% if app_type not in 'so' %}
|
||||
<li class="nav-item">
|
||||
<a href="#certificate" class="nav-link">
|
||||
<i class="nav-icon fas fa-certificate"></i>
|
||||
@@ -107,7 +108,7 @@
|
||||
</p>
|
||||
</a>
|
||||
</li>
|
||||
|
||||
{% endif %}
|
||||
<li class="nav-item has-treeview">
|
||||
<a href="#" class="nav-link">
|
||||
<i class="nav-icon fas fa-shield-alt"></i>
|
||||
@@ -117,6 +118,7 @@
|
||||
</p>
|
||||
</a>
|
||||
<ul class="nav nav-treeview">
|
||||
{% if app_type not in 'so' %}
|
||||
<li class="nav-item">
|
||||
<a href="#network_security" class="nav-link">
|
||||
<i class="nav-icon fas fa-lock"></i>
|
||||
@@ -145,12 +147,14 @@
|
||||
<p>Code Analysis</p>
|
||||
</a>
|
||||
</li>
|
||||
{% endif %}
|
||||
<li class="nav-item">
|
||||
<a href="#binary_analysis" class="nav-link">
|
||||
<i class="nav-icon fas fa-flag"></i>
|
||||
<p>Binary Analysis</p>
|
||||
</a>
|
||||
</li>
|
||||
{% if app_type not in 'so' %}
|
||||
<li class="nav-item">
|
||||
<a href="#niap_analysis" class="nav-link">
|
||||
<i class="fa fa-id-badge nav-icon"></i>
|
||||
@@ -163,6 +167,7 @@
|
||||
<p>File Analysis</p>
|
||||
</a>
|
||||
</li>
|
||||
{% endif %}
|
||||
</ul>
|
||||
</li>
|
||||
<li class="nav-item has-treeview">
|
||||
@@ -174,6 +179,7 @@
|
||||
</p>
|
||||
</a>
|
||||
<ul class="nav nav-treeview">
|
||||
{% if app_type not in 'so' %}
|
||||
<li class="nav-item">
|
||||
<a href="#apkid" class="nav-link">
|
||||
<i class="fas fa-fingerprint nav-icon"></i>
|
||||
@@ -186,6 +192,7 @@
|
||||
<p>Quark Analysis</p>
|
||||
</a>
|
||||
</li>
|
||||
{% endif %}
|
||||
{% if virus_total %}
|
||||
<li class="nav-item">
|
||||
<a href="#virus_total" class="nav-link">
|
||||
@@ -235,26 +242,29 @@
|
||||
<p>Emails</p>
|
||||
</a>
|
||||
</li>
|
||||
{% if app_type not in 'so' %}
|
||||
<li class="nav-item">
|
||||
<a href="#trackers" class="nav-link">
|
||||
<i class="fas fa-user-secret nav-icon"></i>
|
||||
<p>Trackers</p>
|
||||
</a>
|
||||
</li>
|
||||
{% endif %}
|
||||
<li class="nav-item">
|
||||
<a href="#secrets" class="nav-link">
|
||||
<i class="fas fa-key nav-icon"></i>
|
||||
<p>Hardcoded Secrets</p>
|
||||
</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a href="#strings" class="nav-link">
|
||||
<i class="fas fa-font nav-icon"></i>
|
||||
<p>Strings</p>
|
||||
</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a href="#secrets" class="nav-link">
|
||||
<i class="fas fa-key nav-icon"></i>
|
||||
<p>Hardcoded Secrets</p>
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</li>
|
||||
{% if app_type not in 'so' %}
|
||||
<li class="nav-item has-treeview">
|
||||
<a href="#" class="nav-link">
|
||||
<i class="nav-icon fas fa-th-large"></i>
|
||||
@@ -302,6 +312,7 @@
|
||||
</li>
|
||||
</ul>
|
||||
</li>
|
||||
{% endif %}
|
||||
<li class="nav-item">
|
||||
<a href="../pdf/?md5={{ md5 }}" class="nav-link">
|
||||
<i class="nav-icon fas fa-file-pdf"></i>
|
||||
@@ -328,7 +339,7 @@
|
||||
</a>
|
||||
</li>
|
||||
{% else %}
|
||||
{% if app_type not in 'jar,aar' %}
|
||||
{% if app_type not in 'jar,aar,so' %}
|
||||
<li class="nav-item">
|
||||
<a onclick="dynamic_loader()" href="../android_dynamic/{{md5}}" class="nav-link">
|
||||
<i class="nav-icon fa fa-play-circle"></i>
|
||||
@@ -372,6 +383,7 @@
|
||||
<div class="card-body">
|
||||
<h5 class="card-title"></h5>
|
||||
<div class="row">
|
||||
{% if app_type not in 'so' %}
|
||||
<div class="col-2">
|
||||
<p><strong><i class="fas fa-check-double"></i> APP SCORES</strong></p>
|
||||
<img id="app_icon" src="{% if icon_found %}/download/{{ md5 }}-icon.png{% else %}{% static 'img/no_icon.png' %}{% endif %}"/>
|
||||
@@ -399,6 +411,7 @@
|
||||
<p> <a href="../appsec_dashboard/{{ md5 }}/" class="btn btn-outline-primary btn-sm" role="button"><i class="fas fa-user-shield"></i> MobSF Scorecard</a></p>
|
||||
|
||||
</div>
|
||||
{% endif %}
|
||||
<div class="col-6">
|
||||
<p><strong><i class="fas fa-box-open"></i> FILE INFORMATION </strong></p>
|
||||
<span class="badge bg-primary">File Name</span>
|
||||
@@ -412,6 +425,7 @@
|
||||
<span class="badge bg-primary">SHA256</span>
|
||||
{{ sha256 }}<br/>
|
||||
</div>
|
||||
{% if app_type not in 'jar,aar,so' %}
|
||||
<div class="col-4">
|
||||
<p><strong><i class="fas fa-info"></i> APP INFORMATION </strong></p>
|
||||
<span class="badge bg-primary">App Name</span>
|
||||
@@ -431,6 +445,7 @@
|
||||
<span class="badge bg-primary">Android Version Code</span>
|
||||
{{ version_code }}
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
</div>
|
||||
@@ -486,7 +501,7 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% if app_type not in 'jar,aar' %}
|
||||
{% if app_type not in 'jar,aar,so' %}
|
||||
<!-- Boxes -->
|
||||
<div class="container-fluid">
|
||||
<div class="row">
|
||||
@@ -636,9 +651,14 @@
|
||||
<strong><i class="fas fa-cog"></i> SCAN OPTIONS</strong>
|
||||
</p>
|
||||
<p> <a href="../static_analyzer/?checksum={{ md5 }}&name={{ file_name }}&type={{ app_type }}&rescan=1" class="btn btn-info" role="button"><i class="fa fa-sync"></i> Rescan</a>
|
||||
{% if app_type in 'so' %}
|
||||
<a href="../generate_downloads/?hash={{ md5 }}&file_type={{ app_type }}" class="btn btn-warning"> <i class="fa fa-download"></i> Download {{ app_type | upper}}</a>
|
||||
{% endif %}
|
||||
{% if app_type not in 'so' %}
|
||||
<a id="supbtn" onclick="list_suppressions()" role="button" class="btn btn-primary" data-target="#sup_list" data-toggle="modal" href="#"><i class="fa fa-list"></i> Manage Suppressions</a>
|
||||
{% endif %}
|
||||
</p>
|
||||
{% if app_type not in 'jar,aar' %}
|
||||
{% if app_type not in 'jar,aar,so' %}
|
||||
<p>
|
||||
<a onclick="dynamic_loader()" href="../android_dynamic/{{md5}}" class="btn btn-success" role="button"><i class="fa fa-play-circle"></i> Start Dynamic Analysis</a>
|
||||
</p>
|
||||
@@ -646,6 +666,7 @@
|
||||
</div>
|
||||
</div><!-- /.card -->
|
||||
</div>
|
||||
{% if app_type not in 'so' %}
|
||||
<div class="col-lg-8">
|
||||
<div class="card">
|
||||
<div class="card-body">
|
||||
@@ -671,12 +692,14 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
<!-- end row -->
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
<!-- ===========================end scan options================================== -->
|
||||
{% if app_type not in 'so' %}
|
||||
<a id="certificate" class="anchor"></a>
|
||||
<section class="content">
|
||||
<div class="container-fluid">
|
||||
@@ -1304,6 +1327,7 @@
|
||||
</div>
|
||||
</section>
|
||||
<!-- ===========================end code analysis ================================== -->
|
||||
{% endif %}
|
||||
<a id="binary_analysis" class="anchor"></a>
|
||||
<section class="content">
|
||||
<div class="container-fluid">
|
||||
@@ -1318,8 +1342,10 @@
|
||||
<table id="table_binary" class="table table-bordered table-hover table-striped">
|
||||
<thead>
|
||||
<tr>
|
||||
{% if app_type not in 'so' %}
|
||||
<th>NO</th>
|
||||
<th>SHARED OBJECT</th>
|
||||
{% endif %}
|
||||
<th>NX</th>
|
||||
<th>STACK CANARY</th>
|
||||
<th>RPATH</th>
|
||||
@@ -1334,8 +1360,10 @@
|
||||
{% endif %}
|
||||
{% for so in binary_analysis %}
|
||||
<tr>
|
||||
{% if app_type not in 'so' %}
|
||||
<td>{{ forloop.counter }}</td>
|
||||
<td>{{so.name}}</td>
|
||||
{% endif %}
|
||||
<td><b>{{so.nx.is_nx}}</b>
|
||||
<br/>
|
||||
<span class="badge bg-{% if so.nx.severity == 'high' %}danger{% elif so.nx.severity == 'warning' %}warning{% else %}info{% endif %}">{{so.nx.severity}}</span>
|
||||
@@ -1374,6 +1402,7 @@
|
||||
</div>
|
||||
</section>
|
||||
<!-- ===========================end binary analysis ================================== -->
|
||||
{% if app_type not in 'so' %}
|
||||
<a id="niap_analysis" class="anchor"></a>
|
||||
<section class="content">
|
||||
<div class="container-fluid">
|
||||
@@ -1598,6 +1627,7 @@
|
||||
</div>
|
||||
</section>
|
||||
<!-- ===========================end quark analysis ================================== -->
|
||||
{% endif %}
|
||||
{% if virus_total %}
|
||||
<a id="virus_total" class="anchor"></a>
|
||||
<section class="content">
|
||||
@@ -1905,6 +1935,7 @@
|
||||
</div>
|
||||
</section>
|
||||
<!-- ===========================end emails ================================== -->
|
||||
{% if app_type not in 'so' %}
|
||||
<a id="trackers" class="anchor"></a>
|
||||
<section class="content">
|
||||
<div class="container-fluid">
|
||||
@@ -1951,6 +1982,32 @@
|
||||
</div>
|
||||
</section>
|
||||
<!-- ===========================end trackers ================================== -->
|
||||
{% endif %}
|
||||
<a id="secrets" class="anchor"></a>
|
||||
<section class="content">
|
||||
<div class="container-fluid">
|
||||
<div class="row">
|
||||
<div class="col-lg-12">
|
||||
<div class="card">
|
||||
<div class="card-body">
|
||||
<p>
|
||||
<strong><i class="fas fa-key"></i> POSSIBLE HARDCODED SECRETS</strong>
|
||||
</p>
|
||||
<div class="list-group">
|
||||
<p>
|
||||
{% for val in secrets %}
|
||||
{{ val }}<br/>
|
||||
{% endfor %}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div><!-- /.card -->
|
||||
</div>
|
||||
<!-- end row -->
|
||||
</div>
|
||||
</section>
|
||||
<!-- ===========================end secrets ================================== -->
|
||||
<a id="strings" class="anchor"></a>
|
||||
<section class="content">
|
||||
<div class="container-fluid">
|
||||
@@ -1962,9 +2019,41 @@
|
||||
<strong><i class="fas fa-font"></i> STRINGS</strong>
|
||||
</p>
|
||||
<div class="list-group">
|
||||
{% if app_type not in 'so' %}
|
||||
<p><strong>From APK Resource</strong></p>
|
||||
<p>
|
||||
{% for val in strings %}
|
||||
{{ val }}<br/>
|
||||
{% for key, val in strings.items %}
|
||||
{% if key == 'strings_apk_res' %}
|
||||
{% for v in val %}
|
||||
{{ v }}<br/>
|
||||
{% endfor %}
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
</p>
|
||||
<p><strong>From Code</strong></p>
|
||||
<p>
|
||||
{% for key, val in strings.items %}
|
||||
{% if key == 'strings_code' %}
|
||||
{% for v in val %}
|
||||
{{ v }}<br/>
|
||||
{% endfor %}
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
</p>
|
||||
{% endif %}
|
||||
<p><strong>From Shared Objects</strong></p>
|
||||
<p>
|
||||
{% for key, val in strings.items %}
|
||||
{% if key == 'strings_so' %}
|
||||
{% for ls in val %}
|
||||
{% for k,v in ls.items %}
|
||||
<p></br><b><i>{{ k }}</i></b></p>
|
||||
{% for i in v %}
|
||||
{{ i }}<br/>
|
||||
{% endfor %}
|
||||
{% endfor %}
|
||||
{% endfor %}
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
</p>
|
||||
</div>
|
||||
@@ -1976,32 +2065,7 @@
|
||||
</div>
|
||||
</section>
|
||||
<!-- ===========================end strings ================================== -->
|
||||
<a id="secrets" class="anchor"></a>
|
||||
<section class="content">
|
||||
<div class="container-fluid">
|
||||
<div class="row">
|
||||
<div class="col-lg-12">
|
||||
<div class="card">
|
||||
<div class="card-body">
|
||||
<p>
|
||||
<strong><i class="fas fa-key"></i> POSSIBLE HARDCODED SECRETS</strong>
|
||||
</p>
|
||||
<div class="list-group">
|
||||
<p>
|
||||
{% for val in secrets %}
|
||||
{{ val }}<br/>
|
||||
{% endfor %}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div><!-- /.card -->
|
||||
</div>
|
||||
<!-- end row -->
|
||||
</div>
|
||||
</section>
|
||||
<!-- ===========================end secrets ================================== -->
|
||||
|
||||
{% if app_type not in 'so' %}
|
||||
<a id="activities" class="anchor"></a>
|
||||
<section class="content">
|
||||
<div class="container-fluid">
|
||||
@@ -2153,6 +2217,7 @@
|
||||
</div>
|
||||
</section>
|
||||
<!-- ===========================end files ================================== -->
|
||||
{% endif %}
|
||||
<!-- end of contents -->
|
||||
</div>
|
||||
|
||||
|
||||
@@ -177,6 +177,12 @@
|
||||
<p>Trackers</p>
|
||||
</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a href="#secrets" class="nav-link">
|
||||
<i class="fas fa-key nav-icon"></i>
|
||||
<p>Hardcoded Secrets</p>
|
||||
</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a href="#strings" class="nav-link">
|
||||
<i class="fas fa-font nav-icon"></i>
|
||||
@@ -1465,31 +1471,61 @@
|
||||
</div>
|
||||
</section>
|
||||
<!-- ===========================end trackers ================================== -->
|
||||
<a id="strings" class="anchor"></a>
|
||||
<section class="content">
|
||||
<div class="container-fluid">
|
||||
<div class="row">
|
||||
<div class="col-lg-12">
|
||||
<div class="card">
|
||||
<div class="card-body">
|
||||
<p>
|
||||
<strong><i class="fas fa-font"></i> STRINGS</strong>
|
||||
</p>
|
||||
<div class="list-group">
|
||||
<p>
|
||||
{% for val in strings %}
|
||||
{{ val }}<br/>
|
||||
{% endfor %}
|
||||
</p>
|
||||
<a id="secrets" class="anchor"></a>
|
||||
<section class="content">
|
||||
<div class="container-fluid">
|
||||
<div class="row">
|
||||
<div class="col-lg-12">
|
||||
<div class="card">
|
||||
<div class="card-body">
|
||||
<p>
|
||||
<strong><i class="fas fa-key"></i> POSSIBLE HARDCODED SECRETS</strong>
|
||||
</p>
|
||||
<div class="list-group">
|
||||
<p>
|
||||
{% for val in secrets %}
|
||||
{{ val }}<br/>
|
||||
{% endfor %}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div><!-- /.card -->
|
||||
</div>
|
||||
</div><!-- /.card -->
|
||||
</div>
|
||||
<!-- end row -->
|
||||
</div>
|
||||
</section>
|
||||
<!-- ===========================end strings ================================== -->
|
||||
<!-- end row -->
|
||||
</div>
|
||||
</section>
|
||||
<!-- ===========================end secrets ================================== -->
|
||||
<a id="strings" class="anchor"></a>
|
||||
<section class="content">
|
||||
<div class="container-fluid">
|
||||
<div class="row">
|
||||
<div class="col-lg-12">
|
||||
<div class="card">
|
||||
<div class="card-body">
|
||||
<p>
|
||||
<strong><i class="fas fa-font"></i> STRINGS</strong>
|
||||
</p>
|
||||
<div class="list-group">
|
||||
<p><strong>From Code</strong></p>
|
||||
<p>
|
||||
{% for key, val in strings.items %}
|
||||
{% if key == 'strings_code' %}
|
||||
{% for v in val %}
|
||||
{{ v }}<br/>
|
||||
{% endfor %}
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div><!-- /.card -->
|
||||
</div>
|
||||
<!-- end row -->
|
||||
</div>
|
||||
</section>
|
||||
<!-- ===========================end strings ================================== -->
|
||||
<a id="activities" class="anchor"></a>
|
||||
<section class="content">
|
||||
<div class="container-fluid">
|
||||
|
||||
@@ -178,6 +178,12 @@
|
||||
<p>Hardcoded Secrets</p>
|
||||
</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a href="#strings" class="nav-link">
|
||||
<i class="fas fa-font nav-icon"></i>
|
||||
<p>Strings</p>
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
@@ -1063,7 +1069,7 @@
|
||||
{% endfor %}
|
||||
</td>
|
||||
<td>
|
||||
<a href="../?file={{email_dict|key:"path"}}&md5={{ md5}}&type=ios">{{email_dict|key:"path"}}</a>
|
||||
<a href="../view_file_ios/?file={{email_dict|key:"path"}}&md5={{ md5}}&type=ios">{{email_dict|key:"path"}}</a>
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
@@ -1150,6 +1156,31 @@
|
||||
</div>
|
||||
</section>
|
||||
<!-- ===========================end secrets ================================== -->
|
||||
<a id="strings" class="anchor"></a>
|
||||
<section class="content">
|
||||
<div class="container-fluid">
|
||||
<div class="row">
|
||||
<div class="col-lg-12">
|
||||
<div class="card">
|
||||
<div class="card-body">
|
||||
<p>
|
||||
<strong><i class="fas fa-font"></i> STRINGS</strong>
|
||||
</p>
|
||||
<div class="list-group">
|
||||
<p>
|
||||
{% for val in strings %}
|
||||
{{ val }}<br/>
|
||||
{% endfor %}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div><!-- /.card -->
|
||||
</div>
|
||||
<!-- end row -->
|
||||
</div>
|
||||
</section>
|
||||
<!-- ===========================end strings ================================== -->
|
||||
<a id="files" class="anchor"></a>
|
||||
<section class="content">
|
||||
<div class="container-fluid">
|
||||
|
||||
Reference in New Issue
Block a user