flake8 linting and code QA

This commit is contained in:
Ajin Abraham
2019-07-18 16:48:59 -04:00
parent e6fea2abb2
commit 41347d4e5b
78 changed files with 5738 additions and 5605 deletions
+2 -3
View File
@@ -52,9 +52,8 @@ addons:
- sqlite3
install:
- export PYTHONIOENCODING=UTF8
- pip install coveralls
before_script:
- pip install flake8 -r requirements.txt
- pip install tox -r requirements.txt
script:
- flake8 . --count --select=E9,F63,F72,F82 --exclude .git,__pycache__,venv --show-source --statistics
- tox -e lint
- python manage.py test
-3
View File
@@ -1,3 +0,0 @@
from django.contrib import admin
# Register your models here.
-3
View File
@@ -1,3 +0,0 @@
from django.db import models
# Create your models here.
-3
View File
@@ -1,3 +0,0 @@
from django.test import TestCase
# Create your tests here.
+148 -122
View File
@@ -1,34 +1,28 @@
"""
Perform Analysis on Dynamic Analysis Data
"""
import os
import io
import re
import json
"""Perform Analysis on Dynamic Analysis Data."""
import base64
import tarfile
import shutil
import io
import json
import logging
import os
import re
import shutil
import tarfile
from pathlib import Path
from django.utils.html import escape
from MalwareAnalyzer.views.domain_check import malware_check
from MobSF.utils import (
python_list,
isBase64,
PrintException
)
from MobSF.utils import is_base64, log_exception, python_list
logger = logging.getLogger(__name__)
def api_analysis(package, location):
"""API Analysis"""
"""API Analysis."""
api_analysis_result = {}
logger.info("Dynamic API Analysis")
dat = ""
logger.info('Dynamic API Analysis')
dat = ''
api_base64 = []
api_fileio = []
api_reflect = []
@@ -43,99 +37,119 @@ def api_analysis(package, location):
api_cmd = []
api_sms = []
try:
with open(location, "r", encoding="utf-8") as flip:
with open(location, 'r', encoding='utf-8') as flip:
dat = flip.readlines()
res_id = "Droidmon-apimonitor-" + package + ":"
res_id = 'Droidmon-apimonitor-' + package + ':'
for line in dat:
if res_id in line:
# print "LINE: " + line
_, value = line.split(res_id, 1)
# print "PARAM is :" + param
# print "Value is :"+ value
try:
apis = json.loads(value, strict=False)
ret = ''
args = ''
mtd = str(apis["method"])
clss = str(apis["class"])
# print "Called Class: " + CLS
# print "Called Method: " + MTD
mtd = str(apis['method'])
clss = str(apis['class'])
if apis.get('return'):
ret = str(apis["return"])
# print "Return Data: " + RET
ret = str(apis['return'])
else:
# print "No Return Data"
ret = "No Return Data"
ret = 'No Return Data'
if apis.get('args'):
args = str(apis["args"])
# print "Passed Arguments" + ARGS
args = str(apis['args'])
else:
# print "No Arguments Passed"
args = "No Arguments Passed"
args = 'No Arguments Passed'
# XSS Safe
call_data = "</br>METHOD: " + \
escape(mtd) + "</br>ARGUMENTS: " + escape(args) + \
"</br>RETURN DATA: " + escape(ret)
call_data = ('</br>METHOD: {}'
'</br>ARGUMENTS: {}'
'</br>RETURN DATA: {}').format(
escape(mtd),
escape(args),
escape(ret))
if re.findall("android.util.Base64", clss):
if re.findall('android.util.Base64', clss):
# Base64 Decode
if "decode" in mtd:
if 'decode' in mtd:
args_list = python_list(args)
if isBase64(args_list[0]):
call_data += '</br><span class="label label-info">' +\
'Decoded String:</span> ' + \
escape(base64.b64decode(args_list[0]))
if is_base64(args_list[0]):
call_data += ('</br><span class='
'"label label - info">'
'Decoded String'
':</span> {}').format(
escape(base64.b64decode(
args_list[0])))
api_base64.append(call_data)
if re.findall('libcore.io|android.app.SharedPreferencesImpl\$EditorImpl', clss):
if re.findall('libcore.io|'
'android.app.SharedP'
'referencesImpl\\$EditorImpl', clss):
api_fileio.append(call_data)
if re.findall('java.lang.reflect', clss):
api_reflect.append(call_data)
if re.findall('android.content.ContentResolver|android.location.Location|android.media.AudioRecord|android.media.MediaRecorder|android.os.SystemProperties', clss):
if re.findall('android.content.ContentResolver|'
'android.location.Location|'
'android.media.AudioRecord|'
'android.media.MediaRecorder|'
'android.os.SystemProperties', clss):
api_sysprop.append(call_data)
if re.findall('android.app.Activity|android.app.ContextImpl|android.app.ActivityThread', clss):
if re.findall('android.app.Activity|'
'android.app.ContextImpl|'
'android.app.ActivityThread', clss):
api_binder.append(call_data)
if re.findall('javax.crypto.spec.SecretKeySpec|javax.crypto.Cipher|javax.crypto.Mac', clss):
if re.findall('javax.crypto.spec.SecretKeySpec|'
'javax.crypto.Cipher|'
'javax.crypto.Mac', clss):
api_crypto.append(call_data)
if re.findall('android.accounts.AccountManager|android.app.ApplicationPackageManager|android.app.NotificationManager|android.net.ConnectivityManager|android.content.BroadcastReceiver', clss):
if re.findall('android.accounts.AccountManager|'
'android.app.ApplicationPackageManager|'
'android.app.NotificationManager|'
'android.net.ConnectivityManager|'
'android.content.BroadcastReceiver', clss):
api_acntmnger.append(call_data)
if re.findall('android.telephony.TelephonyManager|android.net.wifi.WifiInfo|android.os.Debug', clss):
if re.findall('android.telephony.TelephonyManager|'
'android.net.wifi.WifiInfo|'
'android.os.Debug', clss):
api_deviceinfo.append(call_data)
if re.findall('dalvik.system.BaseDexClassLoader|dalvik.system.DexFile|dalvik.system.DexClassLoader|dalvik.system.PathClassLoader', clss):
if re.findall('dalvik.system.BaseDexClassLoader|'
'dalvik.system.DexFile|'
'dalvik.system.DexClassLoader|'
'dalvik.system.PathClassLoader', clss):
api_dexloader.append(call_data)
if re.findall('java.lang.Runtime|java.lang.ProcessBuilder|java.io.FileOutputStream|java.io.FileInputStream|android.os.Process', clss):
if re.findall('java.lang.Runtime|java.lang.ProcessBuilder|'
'java.io.FileOutputStream|'
'java.io.FileInputStream|'
'android.os.Process', clss):
api_cmd.append(call_data)
if re.findall('android.content.ContentValues', clss):
api_cntvl.append(call_data)
if re.findall('android.telephony.SmsManager', clss):
api_sms.append(call_data)
if re.findall('java.net.URL|org.apache.http.impl.client.AbstractHttpClient', clss):
if re.findall('java.net.URL|org.apache.http.'
'impl.client.AbstractHttpClient', clss):
api_net.append(call_data)
except:
PrintException("Parsing JSON Failed for: " + value)
except:
PrintException("Dynamic API Analysis")
api_analysis_result["api_net"] = list(set(api_net))
api_analysis_result["api_base64"] = list(set(api_base64))
api_analysis_result["api_fileio"] = list(set(api_fileio))
api_analysis_result["api_binder"] = list(set(api_binder))
api_analysis_result["api_crypto"] = list(set(api_crypto))
api_analysis_result["api_deviceinfo"] = list(set(api_deviceinfo))
api_analysis_result["api_cntvl"] = list(set(api_cntvl))
api_analysis_result["api_sms"] = list(set(api_sms))
api_analysis_result["api_sysprop"] = list(set(api_sysprop))
api_analysis_result["api_dexloader"] = list(set(api_dexloader))
api_analysis_result["api_reflect"] = list(set(api_reflect))
api_analysis_result["api_acntmnger"] = list(set(api_acntmnger))
api_analysis_result["api_cmd"] = list(set(api_cmd))
except Exception:
log_exception('Parsing JSON Failed for: ' + value)
except Exception:
log_exception('Dynamic API Analysis')
api_analysis_result['api_net'] = list(set(api_net))
api_analysis_result['api_base64'] = list(set(api_base64))
api_analysis_result['api_fileio'] = list(set(api_fileio))
api_analysis_result['api_binder'] = list(set(api_binder))
api_analysis_result['api_crypto'] = list(set(api_crypto))
api_analysis_result['api_deviceinfo'] = list(set(api_deviceinfo))
api_analysis_result['api_cntvl'] = list(set(api_cntvl))
api_analysis_result['api_sms'] = list(set(api_sms))
api_analysis_result['api_sysprop'] = list(set(api_sysprop))
api_analysis_result['api_dexloader'] = list(set(api_dexloader))
api_analysis_result['api_reflect'] = list(set(api_reflect))
api_analysis_result['api_acntmnger'] = list(set(api_acntmnger))
api_analysis_result['api_cmd'] = list(set(api_cmd))
return api_analysis_result
def run_analysis(apk_dir, md5_hash, package):
"""Run Dynamic File Analysis"""
"""Run Dynamic File Analysis."""
analysis_result = {}
logger.info("Dynamic File Analysis")
capfuzz_home = os.path.join(str(Path.home()), ".capfuzz")
web = os.path.join(capfuzz_home, 'flows', package + ".flows.txt")
logger.info('Dynamic File Analysis')
capfuzz_home = os.path.join(str(Path.home()), '.capfuzz')
web = os.path.join(capfuzz_home, 'flows', package + '.flows.txt')
logcat = os.path.join(apk_dir, 'logcat.txt')
xlogcat = os.path.join(apk_dir, 'x_logcat.txt')
traffic = ''
@@ -144,28 +158,30 @@ def run_analysis(apk_dir, md5_hash, package):
domains = {}
logcat_data = []
clipboard = []
clip_tag = "I/CLIPDUMP-INFO-LOG"
clip_tag = 'I/CLIPDUMP-INFO-LOG'
try:
with io.open(web, mode='r', encoding="utf8", errors="ignore") as flip:
with io.open(web, mode='r', encoding='utf8', errors='ignore') as flip:
web_data = flip.read()
except:
except Exception:
pass
with io.open(logcat, mode='r', encoding="utf8", errors="ignore") as flip:
with io.open(logcat, mode='r', encoding='utf8', errors='ignore') as flip:
logcat_data = flip.readlines()
traffic = ''.join(logcat_data)
with io.open(xlogcat, mode='r', encoding="utf8", errors="ignore") as flip:
with io.open(xlogcat, mode='r', encoding='utf8', errors='ignore') as flip:
xlg = flip.read()
traffic = web_data + traffic + xlg
for log_line in logcat_data:
if log_line.startswith(clip_tag):
clipboard.append(log_line.replace(clip_tag, "Process ID "))
clipboard.append(log_line.replace(clip_tag, 'Process ID '))
urls = []
# URLs My Custom regex
url_pattern = re.compile(
r'((?:https?://|s?ftps?://|file://|javascript:|data:|www\d{0,3}[.])[\w().=/;,#:@?&~*+!$%\'{}-]+)', re.UNICODE)
r'((?:https?://|s?ftps?://|file://|'
r'javascript:|data:|www\d{0,3}'
r'[.])[\w().=/;,#:@?&~*+!$%\'{}-]+)', re.UNICODE)
urllist = re.findall(url_pattern, traffic.lower())
# Domain Extraction and Malware Check
logger.info("Performing Malware Check on extracted Domains")
logger.info('Performing Malware Check on extracted Domains')
domains = malware_check(urllist)
for url in urllist:
if url not in urls:
@@ -173,10 +189,10 @@ def run_analysis(apk_dir, md5_hash, package):
# Email Etraction Regex
emails = []
regex = re.compile(r"[\w.-]+@[\w-]+\.[\w.]+")
regex = re.compile(r'[\w.-]+@[\w-]+\.[\w.]+')
for email in regex.findall(traffic.lower()):
if (email not in emails) and (not email.startswith('//')):
if email == "[email protected]":
if email == '[email protected]':
pass
else:
emails.append(email)
@@ -189,10 +205,10 @@ def run_analysis(apk_dir, md5_hash, package):
with tarfile.open(tar_loc) as tar:
try:
tar.extractall(untar_dir)
except:
except Exception:
pass
except:
PrintException("TAR EXTRACTION FAILED")
except Exception:
log_exception('TAR EXTRACTION FAILED')
# Do Static Analysis on Data from Device
xmlfiles = ''
sqlite_db = ''
@@ -205,57 +221,67 @@ def run_analysis(apk_dir, md5_hash, package):
for dir_name, _, files in os.walk(untar_dir):
for jfile in files:
file_path = os.path.join(untar_dir, dir_name, jfile)
if "+" in file_path:
shutil.move(file_path, file_path.replace("+", "x"))
file_path = file_path.replace("+", "x")
if '+' in file_path:
shutil.move(file_path, file_path.replace('+', 'x'))
file_path = file_path.replace('+', 'x')
fileparam = file_path.replace(untar_dir, '')
if jfile == 'lib':
pass
else:
if jfile.endswith('.xml'):
typ = 'xml'
xmlfiles += "<tr><td><a href='../View/?file=" + \
escape(fileparam) + "&md5=" + md5_hash + "&type=" + \
typ + "'>" + escape(fileparam) + "</a></td><tr>"
xmlfiles += ('<tr><td><a href="../View/?'
'file={}&md5={}&type={}">{}'
'</a></td><tr>').format(
escape(fileparam),
md5_hash,
typ,
escape(fileparam))
else:
with io.open(file_path, mode='r', encoding="utf8", errors="ignore") as flip:
with open(file_path, 'r') as flip:
file_cnt_sig = flip.read(6)
if file_cnt_sig == "SQLite":
if file_cnt_sig == 'SQLite':
typ = 'db'
sqlite_db += "<tr><td><a href='../View/?file=" + \
escape(fileparam) + "&md5=" + md5_hash + "&type=" + \
typ + "'>" + \
escape(fileparam) + "</a></td><tr>"
sqlite_db += ('<tr><td><a href="../View/?'
'file={}&md5={}&type={}">{}'
'</a></td><tr>').format(
escape(fileparam),
md5_hash,
typ,
escape(fileparam))
elif not jfile.endswith('.DS_Store'):
typ = 'others'
other_files += "<tr><td><a href='../View/?file=" + \
escape(fileparam) + "&md5=" + md5_hash + "&type=" + \
typ + "'>" + \
escape(fileparam) + "</a></td><tr>"
except:
PrintException("Dynamic File Analysis")
analysis_result["urls"] = urls
analysis_result["domains"] = domains
analysis_result["emails"] = emails
analysis_result["clipboard"] = clipboard
analysis_result["web_data"] = web_data
analysis_result["xmlfiles"] = xmlfiles
analysis_result["sqlite_db"] = sqlite_db
analysis_result["other_files"] = other_files
other_files += ('<tr><td><a href="../View/?'
'file={}&md5={}&type={}">{}'
'</a></td><tr>').format(
escape(fileparam),
md5_hash,
typ,
escape(fileparam))
except Exception:
log_exception('Dynamic File Analysis')
analysis_result['urls'] = urls
analysis_result['domains'] = domains
analysis_result['emails'] = emails
analysis_result['clipboard'] = clipboard
analysis_result['web_data'] = web_data
analysis_result['xmlfiles'] = xmlfiles
analysis_result['sqlite_db'] = sqlite_db
analysis_result['other_files'] = other_files
return analysis_result
def download(md5_hash, download_dir, apk_dir, package):
"""Generating Downloads"""
logger.info("Generating Downloads")
"""Generating Downloads."""
logger.info('Generating Downloads')
try:
capfuzz_home = os.path.join(str(Path.home()), ".capfuzz")
capfuzz_home = os.path.join(str(Path.home()), '.capfuzz')
logcat = os.path.join(apk_dir, 'logcat.txt')
xlogcat = os.path.join(apk_dir, 'x_logcat.txt')
dumpsys = os.path.join(apk_dir, 'dump.txt')
sshot = os.path.join(apk_dir, 'screenshots-apk/')
web = os.path.join(capfuzz_home, 'flows', package + ".flows.txt")
web = os.path.join(capfuzz_home, 'flows', package + '.flows.txt')
star = os.path.join(apk_dir, package + '.tar')
dlogcat = os.path.join(download_dir, md5_hash + '-logcat.txt')
@@ -278,15 +304,15 @@ def download(md5_hash, download_dir, apk_dir, package):
shutil.copyfile(dumpsys, ddumpsys)
try:
shutil.copytree(sshot, dsshot)
except:
except Exception:
pass
try:
shutil.copyfile(web, dweb)
except:
except Exception:
pass
try:
shutil.copyfile(star, dstar)
except:
except Exception:
pass
except:
PrintException("Generating Downloads")
except Exception:
log_exception('Generating Downloads')
-98
View File
@@ -1,98 +0,0 @@
"""
Android Dynamic Analyzer for Android AVD (ARM) VM
"""
import os
import time
import platform
import subprocess
import logging
from DynamicAnalyzer.views.android.shared import adb_command
from MobSF.utils import PrintException
from django.conf import settings
from scripts.start_avd import main as start_avd_cold
logger = logging.getLogger(__name__)
def stop_avd():
"""Stop AVD"""
logger.info("Stopping MobSF Emulator")
try:
adb_command(['emu', 'kill'], silent=True)
except:
PrintException("Stopping MobSF Emulator")
def start_avd_from_snapshot():
"""Start AVD"""
logger.info("Starting MobSF Emulator")
try:
if platform.system() == 'Darwin':
# There is a strage error in mac with the dyld one in a while..
# this should fix it..
if 'DYLD_FALLBACK_LIBRARY_PATH' in list(os.environ.keys()):
del os.environ['DYLD_FALLBACK_LIBRARY_PATH']
args = [
settings.AVD_EMULATOR,
'-avd',
settings.AVD_NAME,
"-writable-system",
"-snapshot",
settings.AVD_SNAPSHOT,
"-netspeed",
"full",
"-netdelay",
"none",
"-port",
str(settings.AVD_ADB_PORT),
]
subprocess.Popen(args, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
# Give a few seconds and check if the snapshot load succeed
time.sleep(5)
result = adb_command(["getprop", "init.svc.bootanim"], True)
if result:
if result.strip() == b"stopped":
return True
# Snapshot failed, stop the avd and return an error
adb_command(["emu", "kill"])
return False
except:
PrintException("Starting MobSF Emulator")
return False
def refresh_avd():
"""Refresh AVD"""
# Before we load the AVD, check paths
for path in [settings.AVD_EMULATOR,
settings.ADB_BINARY]:
if not path:
logger.error("AVD binaries not configured, please refer to the official documentation")
return False
logger.info("Refreshing MobSF Emulator")
try:
# Stop existing emulator
stop_avd()
# Check if configuration specifies cold or warm boot
if settings.AVD_COLD_BOOT:
if start_avd_cold():
logger.info("AVD has been started successfully")
return True
else:
if not settings.AVD_SNAPSHOT:
logger.error("AVD not configured properly - AVD_SNAPSHOT is missing")
return False
if start_avd_from_snapshot():
logger.info("AVD has been loaded from snapshot successfully")
return True
return False
except:
PrintException("Refreshing MobSF VM")
return False
File diff suppressed because it is too large Load Diff
+64 -66
View File
@@ -1,66 +1,65 @@
"""Shared Functions for Android Dynamic Analyzer"""
# -*- coding: utf_8 -*-
"""Shared Functions for Android Dynamic Analyzer."""
import logging
import subprocess
import time
import traceback
import logging
from django.conf import settings
from MobSF.utils import PrintException, getADB
from MobSF.utils import get_adb, log_exception
logger = logging.getLogger(__name__)
def wait(sec):
"""Wait in Seconds"""
logger.info("Waiting for " + str(sec) + " seconds...")
"""Wait in Seconds."""
logger.info('Waiting for %s seconds...', str(sec))
time.sleep(sec)
def get_res():
"""Get Screen Resolution or Device or VM"""
logger.info("Getting Screen Resolution")
"""Get Screen Resolution or Device or VM."""
logger.info('Getting Screen Resolution')
try:
adb = getADB()
adb = get_adb()
resp = subprocess.check_output(
[adb, "-s", get_identifier(), "shell", "dumpsys", "window"])
resp = resp.decode("utf-8").split("\n")
res = ""
[adb, '-s', get_identifier(), 'shell', 'dumpsys', 'window'])
resp = resp.decode('utf-8').split('\n')
res = ''
for line in resp:
if "mUnrestrictedScreen" in line:
if 'mUnrestrictedScreen' in line:
res = line
break
res = res.split("(0,0)")[1]
res = res.split('(0,0)')[1]
res = res.strip()
res = res.split("x")
res = res.split('x')
if len(res) == 2:
return res[0], res[1]
# width, height
return "", ""
except:
PrintException("Getting Screen Resolution")
return "", ""
return '', ''
except Exception:
log_exception('Getting Screen Resolution')
return '', ''
def get_identifier():
"""Get Device Type"""
"""Get Device Type."""
try:
if settings.ANDROID_DYNAMIC_ANALYZER == "MobSF_REAL_DEVICE":
return settings.DEVICE_IP + ":" + str(settings.DEVICE_ADB_PORT)
elif settings.ANDROID_DYNAMIC_ANALYZER == "MobSF_AVD":
return 'emulator-' + str(settings.AVD_ADB_PORT)
if settings.ANDROID_DYNAMIC_ANALYZER == 'MobSF_REAL_DEVICE':
return settings.DEVICE_IP + ':' + str(settings.DEVICE_ADB_PORT)
else:
return settings.VM_IP + ":" + str(settings.VM_ADB_PORT)
except:
PrintException(
"Getting ADB Connection Identifier for Device/VM")
return settings.VM_IP + ':' + str(settings.VM_ADB_PORT)
except Exception:
log_exception(
'Getting ADB Connection Identifier for Device/VM')
def adb_command(cmd_list, shell=False, silent=False):
emulator = get_identifier()
adb = getADB()
adb = get_adb()
args = [adb,
"-s",
'-s',
emulator]
if shell:
args += ['shell']
@@ -69,55 +68,54 @@ def adb_command(cmd_list, shell=False, silent=False):
try:
result = subprocess.check_output(args)
return result
except:
except Exception:
if not silent:
PrintException("Running ADB Command")
log_exception('Running ADB Command')
return None
def connect():
"""Connect to VM/Device"""
logger.info("Connecting to VM/Device")
adb = getADB()
subprocess.call([adb, "kill-server"])
subprocess.call([adb, "start-server"])
logger.info("ADB Started")
"""Connect to VM/Device."""
logger.info('Connecting to VM/Device')
adb = get_adb()
subprocess.call([adb, 'kill-server'])
subprocess.call([adb, 'start-server'])
logger.info('ADB Started')
wait(5)
logger.info("Connecting to VM/Device")
out = subprocess.check_output([adb, "connect", get_identifier()])
if b"unable to connect" in out:
raise ValueError("ERROR Connecting to VM/Device. ",
out.decode("utf-8").replace("\n", ""))
logger.info('Connecting to VM/Device')
out = subprocess.check_output([adb, 'connect', get_identifier()])
if b'unable to connect' in out:
raise ValueError('ERROR Connecting to VM/Device. ',
out.decode('utf-8').replace('\n', ''))
try:
subprocess.call([adb, "-s", get_identifier(), "wait-for-device"])
logger.info("Mounting")
if settings.ANDROID_DYNAMIC_ANALYZER == "MobSF_REAL_DEVICE":
adb_command(["su", "-c", "mount", "-o",
"rw,remount,rw", "/system"], True)
subprocess.call([adb, '-s', get_identifier(), 'wait-for-device'])
logger.info('Mounting')
if settings.ANDROID_DYNAMIC_ANALYZER == 'MobSF_REAL_DEVICE':
adb_command(['su', '-c', 'mount', '-o',
'rw,remount,rw', '/system'], True)
else:
adb_command(["su", "-c", "mount", "-o",
"rw,remount,rw", "/system"], True)
adb_command(['su', '-c', 'mount', '-o',
'rw,remount,rw', '/system'], True)
# This may not work for VMs other than the default MobSF VM
adb_command(["mount", "-o", "rw,remount", "-t", "rfs",
"/dev/block/sda6", "/system"], True)
except:
PrintException("Connecting to VM/Device")
adb_command(['mount', '-o', 'rw,remount', '-t', 'rfs',
'/dev/block/sda6', '/system'], True)
except Exception:
log_exception('Connecting to VM/Device')
def install_and_run(apk_path, package, launcher, is_activity):
"""Install APK and Run it"""
logger.info("Starting App for Dynamic Analysis")
"""Install APK and Run it."""
logger.info('Starting App for Dynamic Analysis')
try:
adb = getADB()
logger.info("Installing APK")
adb_command(["install", "-r", apk_path])
logger.info('Installing APK')
adb_command(['install', '-r', apk_path])
if is_activity:
run_app = package + "/" + launcher
logger.info("Launching APK Main Activity")
adb_command(["am", "start", "-n", run_app], True)
run_app = package + '/' + launcher
logger.info('Launching APK Main Activity')
adb_command(['am', 'start', '-n', run_app], True)
else:
logger.info("App Doesn't have a Main Activity")
logger.info('App Doesn\'t have a Main Activity')
# Handle Service or Give Choice to Select in Future.
logger.info("Testing Environment is Ready!")
except:
PrintException("Starting App for Dynamic Analysis")
logger.info('Testing Environment is Ready!')
except Exception:
log_exception('Starting App for Dynamic Analysis')
+16 -11
View File
@@ -1,31 +1,36 @@
import time
import subprocess
import logging
from MobSF.utils import PrintException
import subprocess
import time
from django.conf import settings
from MobSF.utils import log_exception
logger = logging.getLogger(__name__)
def refresh_vm(uuid, snapshot_uuid, vbox_exe):
"""Refresh VirtualBox based VMs"""
logger.info("Refreshing MobSF VM")
"""Refresh VirtualBox based VMs."""
logger.info('Refreshing MobSF VM')
try:
if not vbox_exe:
logger.error("VirtualBox not found, Manually set VBOXMANAGE_BINARY in settings.py")
logger.error(
'VirtualBox not found, Manually'
' set VBOXMANAGE_BINARY in settings.py')
# Close VM
args = [vbox_exe, 'controlvm', uuid, 'poweroff']
subprocess.call(args)
logger.info("VM Closed")
logger.info('VM Closed')
time.sleep(3)
# Restore Snapshot
args = [vbox_exe, 'snapshot', uuid, 'restore', snapshot_uuid]
subprocess.call(args)
logger.info("VM Restore Snapshot")
logger.info('VM Restore Snapshot')
# Start Fresh VM
args = [vbox_exe, 'startvm', uuid]
if settings.VBOX_HEADLESS:
args += ['--type', 'headless']
subprocess.call(args)
logger.info("VM Starting")
except:
PrintException("Refreshing MobSF VM")
logger.info('VM Starting')
except Exception:
log_exception('Refreshing MobSF VM')
-3
View File
@@ -1,3 +0,0 @@
from django.contrib import admin
# Register your models here.
-3
View File
@@ -1,3 +0,0 @@
from django.db import models
# Create your models here.
-3
View File
@@ -1,3 +0,0 @@
from django.test import TestCase
# Create your tests here.
File diff suppressed because one or more lines are too long
+54 -44
View File
@@ -1,34 +1,31 @@
import os
import re
import io
import subprocess
import glob
import io
import itertools
import json
import logging
import tempfile
import requests
import os
import re
import shutil
import subprocess
import tempfile
from collections import namedtuple
import requests
from django.conf import settings
from MobSF.utils import (
sha256,
upstream_proxy,
isFileExists,
PrintException,
)
from MobSF.utils import is_file_exists, log_exception, sha256, upstream_proxy
logger = logging.getLogger(__name__)
class Trackers:
def __init__(self, apk_dir, tools_dir):
TRACKERS_DB_DIR = os.path.join(settings.BASE_DIR, 'MalwareAnalyzer/trackersdb/')
trackers_db_dir = os.path.join(
settings.BASE_DIR, 'MalwareAnalyzer/trackersdb/')
self.apk = None
self.apk_dir = apk_dir
self.tracker_db = os.path.join(TRACKERS_DB_DIR, 'exodus_trackers')
self.tracker_db = os.path.join(trackers_db_dir, 'exodus_trackers')
self.signatures = None
self.nb_trackers_signature = 0
self.compiled_tracker_signature = None
@@ -37,15 +34,15 @@ class Trackers:
self._update_tracker_db()
def _update_tracker_db(self):
"""Update Trackers DB"""
"""Update Trackers DB."""
try:
proxies, verify = upstream_proxy('http')
except:
PrintException("[ERROR] Setting upstream proxy")
except Exception:
log_exception('[ERROR] Setting upstream proxy')
try:
exodus_url = settings.TRACKERS_DB_URL
res = requests.get(exodus_url, timeout=3, proxies=proxies,
allow_redirects=True, verify=verify)
allow_redirects=True, verify=verify)
data = res.content
tmp_dwd = tempfile.NamedTemporaryFile()
tmp_dwd.write(data)
@@ -54,28 +51,32 @@ class Trackers:
if sha256(tmp_dwd.name) != sha256(self.tracker_db):
# DB needs update
# Check2: DB Syntax Changed
data = json.loads(tmp_dwd.read().decode("utf-8", "ignore"))
data = json.loads(tmp_dwd.read().decode('utf-8', 'ignore'))
is_db_format_good = False
if "trackers" in data:
if "1" in data["trackers"]:
if "code_signature" in data["trackers"]["1"]:
if 'trackers' in data:
if '1' in data['trackers']:
if 'code_signature' in data['trackers']['1']:
is_db_format_good = True
if is_db_format_good:
# DB Format is not changed. Let's update DB
logger.info("Updating Tracker Database....")
logger.info('Updating Tracker Database....')
shutil.copyfile(tmp_dwd.name, self.tracker_db)
else:
logger.info("Tracker Database format from reports.exodus-privacy.eu.org has changed. Database is not updated. "
"Please report to: https://github.com/MobSF/Mobile-Security-Framework-MobSF/issues")
logger.info('Tracker Database format from '
'reports.exodus-privacy.eu.org has changed.'
' Database is not updated. '
'Please report to: https://github.com/MobSF/'
'Mobile-Security-Framework-MobSF/issues')
else:
logger.info("Tracker Database is up-to-date.")
logger.info('Tracker Database is up-to-date.')
tmp_dwd.close()
except:
PrintException("[ERROR] Tracker DB Update")
except Exception:
log_exception('[ERROR] Tracker DB Update')
def _compile_signatures(self):
"""
Compile Signatures.
Compiles the regex associated to each signature, in order to speed up
the trackers detection.
:return: A compiled list of signatures.
@@ -85,36 +86,46 @@ class Trackers:
self.compiled_tracker_signature = [re.compile(track.code_signature)
for track in self.signatures]
except TypeError:
logger.error("compiling tracker signature failed")
logger.error('compiling tracker signature failed')
def load_trackers_signatures(self):
"""
Load trackers signatures from the official Exodus database.
:return: a dictionary of signatures.
"""
self.signatures = []
with io.open(self.tracker_db, mode='r', encoding="utf8", errors="ignore") as flip:
with io.open(self.tracker_db,
mode='r',
encoding='utf8',
errors='ignore') as flip:
data = json.loads(flip.read())
for elm in data['trackers']:
self.signatures.append(namedtuple('tracker', data['trackers'][elm].keys())(
*data['trackers'][elm].values()))
self.signatures.append(
namedtuple('tracker',
data['trackers'][elm].keys())(
*data['trackers'][elm].values()))
self._compile_signatures()
self.nb_trackers_signature = len(self.signatures)
def get_embedded_classes(self):
"""
Get the list of Java classes from all DEX files.
:return: list of Java classes
"""
if self.classes is not None:
return self.classes
for dex_file in glob.iglob(os.path.join(self.apk_dir, '*.dex')):
if len(settings.BACKSMALI_BINARY) > 0 and isFileExists(settings.BACKSMALI_BINARY):
if (len(settings.BACKSMALI_BINARY) > 0
and is_file_exists(settings.BACKSMALI_BINARY)):
bs_path = settings.BACKSMALI_BINARY
else:
bs_path = os.path.join(self.tools_dir, 'baksmali-2.2.7.jar')
args = [settings.JAVA_PATH + 'java', '-jar', bs_path, 'list', 'classes', dex_file]
classes = subprocess.check_output(args, universal_newlines=True).splitlines()
args = [settings.JAVA_PATH + 'java', '-jar',
bs_path, 'list', 'classes', dex_file]
classes = subprocess.check_output(
args, universal_newlines=True).splitlines()
if self.classes is not None:
self.classes = self.classes + classes
else:
@@ -124,6 +135,7 @@ class Trackers:
def detect_trackers_in_list(self, class_list):
"""
Detect embedded trackers in the provided classes list.
:return: list of embedded trackers
"""
if self.signatures is None:
@@ -151,6 +163,7 @@ class Trackers:
def detect_trackers(self):
"""
Detect embedded trackers.
:return: list of embedded trackers
"""
if self.signatures is None:
@@ -158,15 +171,12 @@ class Trackers:
return self.detect_trackers_in_list(self.get_embedded_classes())
def get_trackers(self):
"""
Get Trackers
"""
"""Get Trackers."""
logger.info('Detecting Trackers')
trackers = self.detect_trackers()
tracker_dict = {"detected_trackers": len(trackers),
"total_trackers": self.nb_trackers_signature,
"trackers": []}
tracker_dict = {'detected_trackers': len(trackers),
'total_trackers': self.nb_trackers_signature,
'trackers': []}
for trk in trackers:
tracker_dict["trackers"].append({trk.name: trk.website})
tracker_dict['trackers'].append({trk.name: trk.website})
return tracker_dict
+61 -38
View File
@@ -1,7 +1,11 @@
import requests
import logging
from MobSF.utils import PrintException, upstream_proxy
import requests
from django.conf import settings
from MobSF.utils import log_exception, upstream_proxy
logger = logging.getLogger(__name__)
@@ -11,6 +15,8 @@ class VirusTotal:
def get_report(self, file_hash):
"""
Get Report from VT.
:param file_hash: md5/sha1/sha256
:return: json response / None
"""
@@ -18,90 +24,107 @@ class VirusTotal:
url = self.base_url + 'report'
params = {
'apikey': settings.VT_API_KEY,
'resource': file_hash
}
headers = {"Accept-Encoding": "gzip, deflate"}
'resource': file_hash}
headers = {'Accept-Encoding': 'gzip, deflate'}
try:
proxies, verify = upstream_proxy('https')
except:
PrintException("Setting upstream proxy")
except Exception:
log_exception('Setting upstream proxy')
try:
response = requests.get(
url, params=params, headers=headers, proxies=proxies, verify=verify)
url,
params=params,
headers=headers,
proxies=proxies,
verify=verify)
if response.status_code == 403:
logger.error("VirusTotal Permission denied, wrong api key?")
logger.error(
'VirusTotal Permission denied, wrong api key?')
return None
except:
logger.error("VirusTotal ConnectionError, check internet connectivity")
except Exception:
logger.error(
'VirusTotal ConnectionError, check internet connectivity')
return None
try:
json_response = response.json()
return json_response
except ValueError:
return None
except:
PrintException("VirusTotal get_report")
except Exception:
log_exception('VirusTotal get_report')
return None
def upload_file(self, file_path):
"""
Upload File to VT.
:param file_path: file path to upload
:return: json response / None
"""
try:
url = self.base_url + "scan"
files = {
'file': open(file_path, 'rb')
}
headers = {
"apikey": settings.VT_API_KEY
}
url = self.base_url + 'scan'
files = {'file': open(file_path, 'rb')}
headers = {'apikey': settings.VT_API_KEY}
try:
proxies, verify = upstream_proxy('https')
except:
PrintException("Setting upstream proxy")
except Exception:
log_exception('Setting upstream proxy')
try:
response = requests.post(
url, files=files, data=headers, proxies=proxies, verify=verify)
url,
files=files,
data=headers,
proxies=proxies,
verify=verify)
if response.status_code == 403:
logger.error("VirusTotal Permission denied, wrong api key?")
logger.error(
'VirusTotal Permission denied, wrong api key?')
return None
except:
logger.error("VirusTotal Connection Error, check internet connectivity")
except Exception:
logger.error(
'VirusTotal Connection Error, check internet connectivity')
return None
json_response = response.json()
return json_response
except:
PrintException("VirusTotal upload_file")
except Exception:
log_exception('VirusTotal upload_file')
return None
def get_result(self, file_path, file_hash):
"""
Uoloading a file and getting the approval msg from VT or fetching existing report
Get Results from VT.
Uploading a file and getting the approval msg from VT
or fetching existing report
:param file_path: file's path
:param file_hash: file's hash - md5/sha1/sha256
:return: VirusTotal result json / None upon error
"""
try:
logger.info("VirusTotal: Check for existing report")
logger.info('VirusTotal: Check for existing report')
report = self.get_report(file_hash)
# Check for existing report
if report:
if report['response_code'] == 1:
logger.info("VirusTotal: " + report['verbose_msg'])
logger.info('VirusTotal: %s', report['verbose_msg'])
return report
if settings.VT_UPLOAD:
logger.info("VirusTotal: file upload")
logger.info('VirusTotal: file upload')
upload_response = self.upload_file(file_path)
if upload_response:
logger.info("VirusTotal: {}".format(upload_response['verbose_msg']))
logger.info('VirusTotal: %s',
upload_response['verbose_msg'])
return upload_response
else:
logger.info("MobSF: VirusTotal Scan not performed as file upload is disabled in settings.py. "
"To enable file upload, set VT_UPLOAD to True.")
logger.info('MobSF: VirusTotal Scan not performed as file'
' upload is disabled in settings.py. '
'To enable file upload, set VT_UPLOAD to True.')
report = {
"verbose_msg": "Scan Not performed, VirusTotal file upload disabled in settings.py", "positives": 0, "total": 0}
'verbose_msg': ('Scan Not performed, VirusTotal file'
' upload disabled in settings.py'),
'positives': 0,
'total': 0}
return report
except:
PrintException("VirusTotal get_result")
except Exception:
log_exception('VirusTotal get_result')
+8 -7
View File
@@ -1,22 +1,23 @@
# -*- coding: utf_8 -*-
import os
import logging
import os
from django.conf import settings
logger = logging.getLogger(__name__)
def apkid_analysis(app_dir, apk_file, apk_name):
"""APKiD Analysis of DEX files"""
"""APKID Analysis of DEX files."""
if not settings.APKID_ENABLED:
return {}
try:
import apkid
except ImportError:
logger.error("APKiD - Could not import APKiD")
logger.error('APKiD - Could not import APKiD')
return {}
if not os.path.exists(apk_file):
logger.error("APKiD - APK not found")
logger.error('APKiD - APK not found')
return {}
apkid_ver = apkid.__version__
@@ -24,17 +25,17 @@ def apkid_analysis(app_dir, apk_file, apk_name):
from apkid.output import OutputFormatter
from apkid.rules import RulesManager
logger.info("Running APKiD %s", apkid_ver)
logger.info('Running APKiD %s', apkid_ver)
options = Options(
timeout=30,
verbose=False,
entry_max_scan_size=100 * 1024 * 1024,
recursive=True
recursive=True,
)
output = OutputFormatter(
json_output=True,
output_dir=None,
rules_manager=RulesManager()
rules_manager=RulesManager(),
)
rules = options.rules_manager.load()
scanner = Scanner(rules, options)
+67 -60
View File
@@ -1,34 +1,33 @@
# -*- coding: utf_8 -*-
# Module for Malware Analysis
from urllib.parse import urlparse
import logging
import shutil
import io
import logging
import os
import re
import shutil
import tempfile
from urllib.parse import urlparse
import requests
from django.conf import settings
from MobSF.utils import (
PrintException,
isInternetAvailable,
upstream_proxy,
sha256
)
from MobSF.utils import (is_internet_available, log_exception, sha256,
upstream_proxy)
logger = logging.getLogger(__name__)
# PATH
MALWARE_DB_DIR = TOOLS_DIR = os.path.join(
settings.BASE_DIR, 'MalwareAnalyzer/malwaredb/')
def update_malware_db():
"""Check for update in malware DB"""
"""Check for update in malware DB."""
try:
proxies, verify = upstream_proxy('http')
except:
PrintException("[ERROR] Setting upstream proxy")
except Exception:
log_exception('[ERROR] Setting upstream proxy')
try:
url = settings.MALWARE_DB_URL
response = requests.get(url, timeout=3, proxies=proxies, verify=verify)
@@ -41,81 +40,89 @@ def update_malware_db():
if sha256(tmp_dwd.name) != sha256(mal_db):
# DB needs update
# Check2: DB Syntax Changed
line = tmp_dwd.readline().decode("utf-8", "ignore")
line = tmp_dwd.readline().decode('utf-8', 'ignore')
lst = line.split('",')
if len(lst) == 10:
# DB Format is not changed. Let's update DB
logger.info("Updating Malware Database....")
logger.info('Updating Malware Database....')
shutil.copyfile(tmp_dwd.name, mal_db)
else:
logger.info("Malware Database format from malwaredomainlist.com has changed. Database is not updated. "
"Please report to: https://github.com/MobSF/Mobile-Security-Framework-MobSF/issues")
logger.info('Malware Database format from '
'malwaredomainlist.com has changed.'
' Database is not updated. '
'Please report to: https://github.com/'
'MobSF/Mobile-Security-Framework-MobSF/issues')
else:
logger.info("Malware Database is up-to-date.")
logger.info('Malware Database is up-to-date.')
tmp_dwd.close()
except:
PrintException("[ERROR] Malware DB Update")
except Exception:
log_exception('[ERROR] Malware DB Update')
def malware_check(urllist):
result = {}
try:
if settings.DOMAIN_MALWARE_SCAN == False:
logger.info("Domain Malware Check disabled in settings")
if not settings.DOMAIN_MALWARE_SCAN:
logger.info('Domain Malware Check disabled in settings')
return result
domainlist = get_domains(urllist)
if domainlist:
if isInternetAvailable():
update_malware_db()
else:
logger.warning(
"No Internet Connection. Skipping Malware Database Update.")
mal_db = os.path.join(MALWARE_DB_DIR, 'malwaredomainlist')
with io.open(mal_db, mode='r', encoding="utf8", errors="ignore") as flip:
entry_list = flip.readlines()
for entry in entry_list:
enlist = entry.split('","')
if len(enlist) > 5:
details_dict = dict()
details_dict["domain_or_url"] = enlist[1]
details_dict["ip"] = enlist[2]
details_dict["desc"] = enlist[4]
details_dict["bad"] = "yes"
for domain in domainlist:
if (domain in details_dict["domain_or_url"] or
(details_dict["domain_or_url"] in domain and
(len(details_dict["domain_or_url"]) > 1)) or
details_dict["ip"].startswith(domain)):
result[domain] = details_dict
# Good Domains
for domain in domainlist:
if domain not in result:
tmp_d = dict()
tmp_d["bad"] = "no"
result[domain] = tmp_d
except:
PrintException("[ERROR] Performing Malware Check")
if not domainlist:
return result
if is_internet_available():
update_malware_db()
else:
logger.warning(
'No Internet Connection. Skipping Malware Database Update.')
mal_db = os.path.join(MALWARE_DB_DIR, 'malwaredomainlist')
with io.open(mal_db,
mode='r',
encoding='utf8',
errors='ignore') as flip:
entry_list = flip.readlines()
for entry in entry_list:
enlist = entry.split('', '')
if len(enlist) > 5:
details_dict = {}
details_dict['domain_or_url'] = enlist[1]
details_dict['ip'] = enlist[2]
details_dict['desc'] = enlist[4]
details_dict['bad'] = 'yes'
for domain in domainlist:
if (domain in details_dict['domain_or_url']
or (details_dict['domain_or_url'] in domain
and (len(details_dict['domain_or_url']) > 1))
or details_dict['ip'].startswith(domain)):
result[domain] = details_dict
# Good Domains
for domain in domainlist:
if domain not in result:
tmp_d = {}
tmp_d['bad'] = 'no'
result[domain] = tmp_d
except Exception:
log_exception('[ERROR] Performing Malware Check')
return result
# Helper Functions
def get_domains(urls):
"""Get Domains"""
"""Get Domains."""
try:
domains = []
for url in urls:
parsed_uri = urlparse(url)
if not urlparse(url).scheme:
url = "//"+url
url = '//' + url
parsed_uri = urlparse(url)
domain = '{uri.netloc}'.format(uri=parsed_uri)
if ((domain not in domains) and
(len(domain) > 2) and
("." in domain) and
(domain.endswith(".") is False and re.search('[a-zA-Z0-9]', domain))):
if (domain not in domains
and len(domain) > 2
and '.' in domain
and (domain.endswith('.') is False
and re.search('[a-zA-Z0-9]', domain))):
domains.append(domain)
return domains
except:
PrintException("[ERROR] Extracting Domain form URL")
except Exception:
log_exception('[ERROR] Extracting Domain form URL')
+3 -2
View File
@@ -12,7 +12,8 @@ class FormUtil(object):
@staticmethod
def errors_message(form):
"""
"""Form Errors.
:param form forms.Form
form.errors.get_json_data() django 2.0 or higher
@@ -20,7 +21,7 @@ class FormUtil(object):
example
{
"error": {
"file": "This field is required.",
"file": "This field is required.",
"test": "This field is required."
}
}
-1
View File
@@ -1 +0,0 @@
from django.db import models
+148 -158
View File
@@ -1,3 +1,4 @@
# noqa: E800
"""
Django settings for MobSF project.
@@ -5,43 +6,39 @@ MobSF and Django settings
"""
import imp
import os
import logging
from MobSF.utils import (
first_run,
FindJava,
FindVbox,
getMobSFHome,
PrintException,
)
import os
from MobSF.utils import (find_java_binary, find_vboxmange_binary, first_run,
get_mobsf_home, log_exception)
logger = logging.getLogger(__name__)
#!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
# !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
# MOBSF CONFIGURATIONS
#!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
# !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
MOBSF_VER = "v1.1.2 Beta"
MOBSF_VER = 'v1.1.3 Beta'
BANNER = """
__ __ _ ____ _____ _ _
__ __ _ ____ _____ _ _
| \/ | ___ | |__/ ___|| ___| __ __ / | / |
| |\/| |/ _ \| '_ \___ \| |_ \ \ / / | | | |
| | | | (_) | |_) |__) | _| \ V / | |_| |
|_| |_|\___/|_.__/____/|_| \_/ |_(_)_|
"""
""" # noqa: W291
# ASCII Standard
#==============================================
# ==============================================
BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
#==========MobSF Home Directory=================
# ==========MobSF Home Directory=================
USE_HOME = False
# True : All Uploads/Downloads will be stored in user's home directory
# False : All Uploads/Downloads will be stored in MobSF root directory
# If you need multiple users to share the scan results set this to False
#===============================================
# ===============================================
MobSF_HOME = getMobSFHome(USE_HOME)
MobSF_HOME = get_mobsf_home(USE_HOME)
# Logs Directory
LOG_DIR = os.path.join(MobSF_HOME, 'logs/')
# Download Directory
@@ -55,7 +52,7 @@ DB_DIR = os.path.join(MobSF_HOME, 'db.sqlite3')
# Tools Directory
TOOLS_DIR = os.path.join(BASE_DIR, 'DynamicAnalyzer/tools/')
# Secret File
SECRET_FILE = os.path.join(MobSF_HOME, "secret")
SECRET_FILE = os.path.join(MobSF_HOME, 'secret')
# Database
# https://docs.djangoproject.com/en/dev/ref/settings/#databases
@@ -65,12 +62,12 @@ DATABASES = {
'default': {
'ENGINE': 'django.db.backends.sqlite3',
'NAME': DB_DIR,
}
},
}
# End Sqlite3 support
# Postgres DB - Install psycopg2
'''
"""
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.postgresql_psycopg2',
@@ -82,40 +79,41 @@ DATABASES = {
}
}
# End Postgres support
'''
#===============================================
"""
# ===============================================
#==========LOAD CONFIG FROM MobSF HOME==========
# ==========LOAD CONFIG FROM MobSF HOME==========
try:
# Update Config from MobSF Home Directory
if USE_HOME:
USER_CONFIG = os.path.join(MobSF_HOME, 'config.py')
sett = imp.load_source('user_settings', USER_CONFIG)
locals().update(
{k: v for k, v in list(sett.__dict__.items()) if not k.startswith("__")})
{k: v for k, v in list(sett.__dict__.items())
if not k.startswith('__')})
CONFIG_HOME = True
else:
CONFIG_HOME = False
except:
PrintException("Reading Config")
except Exception:
log_exception('Reading Config')
CONFIG_HOME = False
#===============================================
# ===============================================
#===MOBSF SECRET GENERATION AND DB MIGRATION====
# ===MOBSF SECRET GENERATION AND DB MIGRATION====
SECRET_KEY = first_run(SECRET_FILE, BASE_DIR, MobSF_HOME)
#=============================================
# =============================================
#=============ALLOWED EXTENSIONS================
# =============ALLOWED EXTENSIONS================
ALLOWED_EXTENSIONS = {
".txt": "text/plain",
".png": "image/png",
".zip": "application/zip",
".tar": "application/x-tar"
'.txt': 'text/plain',
'.png': 'image/png',
'.zip': 'application/zip',
'.tar': 'application/x-tar',
}
#===============================================
# ===============================================
#=============ALLOWED MIMETYPES=================
# =============ALLOWED MIMETYPES=================
APK_MIME = [
'application/octet-stream',
@@ -139,19 +137,18 @@ ZIP_MIME = [
APPX_MIME = [
'application/octet-stream',
'application/vns.ms-appx',
'application/x-zip-compressed'
'application/x-zip-compressed',
]
#===============================================
#============DJANGO SETTINGS =================
# ===============================================
# ============DJANGO SETTINGS =================
DEBUG = True
DJANGO_LOG_LEVEL = DEBUG
ALLOWED_HOSTS = ['127.0.0.1', 'mobsf', '*']
# Application definition
INSTALLED_APPS = (
#'django.contrib.admin',
# 'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
@@ -190,44 +187,46 @@ TEMPLATES = [
'APP_DIRS': True,
'DIRS':
[
os.path.join(BASE_DIR, 'templates')
os.path.join(BASE_DIR, 'templates'),
],
'OPTIONS':
{
'debug': True,
}
},
},
]
MEDIA_ROOT = os.path.join(BASE_DIR, 'uploads')
MEDIA_URL = '/uploads/'
STATICFILES_DIRS = [os.path.join(BASE_DIR, "static")]
STATICFILES_DIRS = [os.path.join(BASE_DIR, 'static')]
STATIC_URL = '/static/'
STATICFILES_STORAGE = 'whitenoise.storage.CompressedManifestStaticFilesStorage'
# 256MB
DATA_UPLOAD_MAX_MEMORY_SIZE = 268435456
#===================
# ===================
# USER CONFIGURATION
#===================
# ===================
if CONFIG_HOME:
logger.info("Loading User config from: {}".format(USER_CONFIG))
logger.info('Loading User config from: %s', USER_CONFIG)
else:
'''
"""
IMPORTANT
If 'USE_HOME' is set to True, then below user configuration settings are not considered.
The user configuration will be loaded from config.py in MobSF Home directory.
'''
#^CONFIG-START^: Do not edit this line
#!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
If 'USE_HOME' is set to True,
then below user configuration settings are not considered.
The user configuration will be loaded from
config.py in MobSF Home directory.
"""
# ^CONFIG-START^: Do not edit this line
# !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
# MOBSF USER CONFIGURATIONS
#!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
# !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
#-------------------------
# -------------------------
# STATIC ANALYZER SETTINGS
#-------------------------
# -------------------------
#==========ANDROID SKIP CLASSES==========================
# ==========ANDROID SKIP CLASSES==========================
# Common third party classes that will be skipped during static analysis
SKIP_CLASSES = [
r'com[\\\/]{1}google[\\\/]{1}',
@@ -244,117 +243,106 @@ else:
r'org[\\\/]{1}apache[\\\/]{1}',
r'oauth[\\\/]{1}signpost[\\\/]{1}',
r'org[\\\/]{1}chromium[\\\/]{1}',
r'com[\\\/]{1}facebook[\\\/]{1}'
r'com[\\\/]{1}facebook[\\\/]{1}',
]
#==========DECOMPILER SETTINGS=================
# ==========DECOMPILER SETTINGS=================
DECOMPILER = "cfr"
DECOMPILER = 'cfr'
# Three Decompilers are available
# 1. jd-core
# 2. cfr
# 3. procyon
#==============================================
# ==============================================
#==========Dex to Jar Converter================
JAR_CONVERTER = "d2j"
# ==========Dex to Jar Converter================
JAR_CONVERTER = 'd2j'
# Two Dex to Jar converters are available
# 1. d2j
# 2. enjarify
#==============================================
# ==============================================
#======WINDOWS STATIC ANALYSIS SETTINGS ===========
# ======WINDOWS STATIC ANALYSIS SETTINGS ===========
# Private key
WINDOWS_VM_SECRET = 'MobSF/windows_vm_priv_key.asc'
# IP and Port of the MobSF Windows VM
# eg: WINDOWS_VM_IP = '127.0.0.1'
# example: WINDOWS_VM_IP = '127.0.0.1'
WINDOWS_VM_IP = None
WINDOWS_VM_PORT = '8000'
#==================================================
# ==================================================
#==============3rd Party Tools=================
'''
# ==============3rd Party Tools=================
"""
If you want to use a different version of 3rd party tools used by MobSF.
You can do that by specifying the path here. If specified, MobSF will run
the tool from this location.
'''
"""
# Android 3P Tools
DEX2JAR_BINARY = ""
BACKSMALI_BINARY = ""
CFR_DECOMPILER_BINARY = ""
JD_CORE_DECOMPILER_BINARY = ""
PROCYON_DECOMPILER_BINARY = ""
APKTOOL_BINARY = ""
ADB_BINARY = ""
ENJARIFY_DIRECTORY = ""
DEX2JAR_BINARY = ''
BACKSMALI_BINARY = ''
CFR_DECOMPILER_BINARY = ''
JD_CORE_DECOMPILER_BINARY = ''
PROCYON_DECOMPILER_BINARY = ''
APKTOOL_BINARY = ''
ADB_BINARY = ''
ENJARIFY_DIRECTORY = ''
# iOS 3P Tools
OTOOL_BINARY = ""
JTOOL_BINARY = ""
CLASSDUMPZ_BINARY = ""
CLASSDUMP_SWIFT_BINARY = ""
OTOOL_BINARY = ''
JTOOL_BINARY = ''
CLASSDUMPZ_BINARY = ''
CLASSDUMP_SWIFT_BINARY = ''
# COMMON
JAVA_DIRECTORY = ""
VBOXMANAGE_BINARY = ""
PYTHON3_PATH = ""
JAVA_DIRECTORY = ''
VBOXMANAGE_BINARY = ''
PYTHON3_PATH = ''
'''
"""
Examples:
JAVA_DIRECTORY = "C:/Program Files/Java/jdk1.7.0_17/bin/"
JAVA_DIRECTORY = "/usr/bin/"
DEX2JAR_BINARY = "/Users/ajin/dex2jar/d2j-dex2jar.sh"
ENJARIFY_DIRECTORY = "D:/enjarify/"
VBOXMANAGE_BINARY = "/usr/bin/VBoxManage"
CFR_DECOMPILER_BINARY = "/home/ajin/tools/cfr.jar"
PYTHON3_PATH = "C:/Users/Ajin/AppData/Local/Programs/Python/Python35-32/"
'''
#===============================================
JAVA_DIRECTORY = 'C:/Program Files/Java/jdk1.7.0_17/bin/'
JAVA_DIRECTORY = '/usr/bin/'
DEX2JAR_BINARY = '/Users/ajin/dex2jar/d2j-dex2jar.sh'
ENJARIFY_DIRECTORY = 'D:/enjarify/'
VBOXMANAGE_BINARY = '/usr/bin/VBoxManage'
CFR_DECOMPILER_BINARY = '/home/ajin/tools/cfr.jar'
PYTHON3_PATH = 'C:/Users/Ajin/AppData/Local/Programs/Python/Python35-32/'
"""
# ===============================================
#-------------------------
# -------------------------
# DYNAMIC ANALYZER SETTINGS
#-------------------------
# -------------------------
#========ANDROID DYNAMIC ANALYSIS SETTINGS================================
# ========ANDROID DYNAMIC ANALYSIS SETTINGS================================
ANDROID_DYNAMIC_ANALYZER = "MobSF_VM"
ANDROID_DYNAMIC_ANALYZER = 'MobSF_VM'
# You can choose any of the below
# 1. MobSF_VM
# 2. MobSF_AVD
# 3. MobSF_REAL_DEVICE
# 2. MobSF_REAL_DEVICE
'''
MobSF_VM - x86 Android 4.4.2 running on VirtualBox (Fast, not all Apps work)
MobSF_AVD - ARM Android 6 running on Android Emulator (Slow, Most Apps work)
MobSF_REAL_DEVICE - Rooted Android 4.03 - 4.4 Device (Very Fast, All Apps work)
"""
MobSF_VM x86 Android 4.4.2 running on VirtualBox(Fast, not all Apps work)
MobSF_REAL_DEVICE - Rooted Android 4.03 4.4 Device
(Very Fast, All Apps work)
Supports Android 5+ for real device. Not tested!
'''
"""
#=========================================================================
# =========================================================================
#=======ANDROID REAL DEVICE SETTINGS===========
# =======ANDROID REAL DEVICE SETTINGS===========
DEVICE_IP = '192.168.1.18'
DEVICE_ADB_PORT = 5555
DEVICE_TIMEOUT = 300
#==============================================
# ==============================================
#===========ANDROID EMULATOR SETTINGS ===========
# Android-Studio 'emulator' binary path
# Not Supported any more
AVD_EMULATOR = "/Users/[USERNAME]/Library/Android/sdk/tools/emulator"
AVD_NAME = "MobSFAPI23armV1"
AVD_ADB_PORT = 5554
AVD_SNAPSHOT = ""
AVD_COLD_BOOT = True
#================================================
#====ANDROID MOBSF VIRTUALBOX VM SETTINGS =====
# ====ANDROID MOBSF VIRTUALBOX VM SETTINGS =====
# VM UUID
UUID = '408e1874-759f-4417-9453-53ef21dc2ade'
# Snapshot UUID
@@ -364,70 +352,71 @@ else:
VM_ADB_PORT = 5555
VM_TIMEOUT = 100
VBOX_HEADLESS = False
#==============================================
# ==============================================
#--------------------------
# --------------------------
# MobSF MITM PROXY SETTINGS
#--------------------------
# --------------------------
#================HOST/PROXY SETTINGS ===============
# ================HOST/PROXY SETTINGS ===============
PROXY_IP = '192.168.56.1' # Host/Server/Proxy IP
PORT = 1337 # Proxy Port
ROOT_CA = '0026aabb.0'
SCREEN_IP = PROXY_IP # ScreenCast IP
SCREEN_PORT = 9339 # ScreenCast Port(Do not Change)
#===================================================
# ===================================================
#========UPSTREAM PROXY SETTINGS ==============
# ========UPSTREAM PROXY SETTINGS ==============
# If you are behind a Proxy
UPSTREAM_PROXY_ENABLED = False
UPSTREAM_PROXY_SSL_VERIFY = True
UPSTREAM_PROXY_TYPE = "http"
UPSTREAM_PROXY_IP = "127.0.0.1"
UPSTREAM_PROXY_TYPE = 'http'
UPSTREAM_PROXY_IP = '127.0.0.1'
UPSTREAM_PROXY_PORT = 3128
UPSTREAM_PROXY_USERNAME = ""
UPSTREAM_PROXY_PASSWORD = ""
#==============================================
UPSTREAM_PROXY_USERNAME = ''
UPSTREAM_PROXY_PASSWORD = ''
# ==============================================
#--------------------------
# --------------------------
# MALWARE ANALYZER SETTINGS
#--------------------------
# --------------------------
DOMAIN_MALWARE_SCAN = True
#----------APKiD-------------------------------
# ----------APKiD-------------------------------
APKID_ENABLED = True
#==============================================
# ==============================================
#========DISABLED COMPONENTS===================
# ========DISABLED COMPONENTS===================
#----------VirusTotal--------------------------
# ----------VirusTotal--------------------------
VT_ENABLED = False
VT_API_KEY = 'XXXXXXXXXXXXXX'
VT_UPLOAD = False
# Before setting VT_ENABLED to True,
# Make sure VT_API_KEY is set to your VirusTotal API key
# register at: https://www.virustotal.com/#/join-us
# You can get your API KEY from https://www.virustotal.com/en/user/<username>/apikey/
# BE AWARE - if you enable VT, in case the file wasn't already uploaded to VirusTotal,
# It will be uploaded if you set VT_UPLOAD to True!
#==============================================
# You can get your API KEY from:
# https://www.virustotal.com/en/user/<username>/apikey/
# Files will be uploaded to VirusTotal
# if VT_UPLOAD is set to True.
# ==============================================
#-----External URLS--------------------------
# -----External URLS--------------------------
MALWARE_DB_URL = 'http://www.malwaredomainlist.com/mdlcsv.php'
VIRUS_TOTAL_BASE_URL = 'https://www.virustotal.com/vtapi/v2/file/'
TRACKERS_DB_URL = 'https://reports.exodus-privacy.eu.org/api/trackers'
#^CONFIG-END^: Do not edit this line
# ^CONFIG-END^: Do not edit this line
# The below code should be loaded last.
#============JAVA SETTINGS======================
JAVA_PATH = FindJava(False)
#===============================================
# ============JAVA SETTINGS======================
JAVA_PATH = find_java_binary(False)
# ===============================================
#================VirtualBox Settings============
VBOX = FindVbox(False)
#===============================================
# ================VirtualBox Settings============
VBOX = find_vboxmange_binary(False)
# ===============================================
# Better logging
LOGGING = {
@@ -435,18 +424,19 @@ LOGGING = {
'disable_existing_loggers': True,
'formatters': {
'standard': {
'format': "[%(levelname)s] %(asctime)-15s - %(message)s",
'datefmt': "%d/%b/%Y %H:%M:%S"
'format': '[%(levelname)s] %(asctime)-15s - %(message)s',
'datefmt': '%d/%b/%Y %H:%M:%S',
},
'color': {
'()': 'colorlog.ColoredFormatter',
'format': '%(log_color)s[%(levelname)s] %(asctime)-15s - %(message)s',
'datefmt': "%d/%b/%Y %H:%M:%S",
'format':
'%(log_color)s[%(levelname)s] %(asctime)-15s - %(message)s',
'datefmt': '%d/%b/%Y %H:%M:%S',
'log_colors': {
'DEBUG': 'cyan',
'INFO': 'green',
'WARNING': 'yellow',
'ERROR': 'red',
'DEBUG': 'cyan',
'INFO': 'green',
'WARNING': 'yellow',
'ERROR': 'red',
'CRITICAL': 'red,bg_white',
},
},
@@ -496,5 +486,5 @@ LOGGING = {
'level': 'DEBUG',
'propagate': False,
},
}
},
}
+21 -34
View File
@@ -1,44 +1,30 @@
from django.conf.urls import url
from DynamicAnalyzer.views.android import (
dynamic
)
from DynamicAnalyzer.views.android import dynamic
from MobSF import utils
from MobSF.views import (
home,
)
from MobSF.views.api import (
rest_api
)
from MobSF.views import home
from MobSF.views.api import rest_api
from StaticAnalyzer import tests
from StaticAnalyzer.views import (
shared_func,
windows
)
from StaticAnalyzer.views.android import (
static_analyzer as android_sa,
view_source,
smali,
java,
find,
manifest_view
)
from StaticAnalyzer.views.ios import (
static_analyzer as ios_sa,
view_source as io_view_source
)
from StaticAnalyzer.views import shared_func, windows
from StaticAnalyzer.views.android import find, java, manifest_view, smali
from StaticAnalyzer.views.android import static_analyzer as android_sa
from StaticAnalyzer.views.android import view_source
from StaticAnalyzer.views.ios import static_analyzer as ios_sa
from StaticAnalyzer.views.ios import view_source as io_view_source
urlpatterns = [
# Examples:
url(r'^$', home.index, name="home"),
url(r'^$', home.index, name='home'),
url(r'^upload/$', home.Upload.as_view),
url(r'^download/', home.download),
url(r'^about$', home.about, name="about"),
url(r'^api_docs$', home.api_docs, name="api_docs"),
url(r'^recent_scans/$', home.recent_scans, name="recent"),
url(r'^about$', home.about, name='about'),
url(r'^api_docs$', home.api_docs, name='api_docs'),
url(r'^recent_scans/$', home.recent_scans, name='recent'),
url(r'^delete_scan/$', home.delete_scan),
url(r'^search$', home.search),
url(r'^error/$', home.error, name="error"),
url(r'^error/$', home.error, name='error'),
url(r'^not_found/$', home.not_found),
url(r'^zip_format/$', home.zip_format),
url(r'^mac_only/$', home.mac_only),
@@ -54,12 +40,13 @@ urlpatterns = [
# IOS
url(r'^StaticAnalyzer_iOS/$', ios_sa.static_analyzer_ios),
url(r'^ViewFile/$', io_view_source.run),
#Windows
# Windows
url(r'^StaticAnalyzer_Windows/$', windows.staticanalyzer_windows),
#Shared
# Shared
url(r'^PDF/$', shared_func.pdf),
# We validate the hash sanity in the URL already
url(r'^compare/(?P<first_hash>[0-9a-f]{32})/(?P<second_hash>[0-9a-f]{32})/$', shared_func.compare_apps, ),
url(r'^compare/(?P<hash1>[0-9a-f]{32})/(?P<hash2>[0-9a-f]{32})/$',
shared_func.compare_apps),
# Android Dynamic Analysis
url(r'^DynamicAnalyzer/$', dynamic.android_dynamic_analyzer),
@@ -93,4 +80,4 @@ urlpatterns = [
]
utils.printMobSFverison()
utils.print_version()
+275 -274
View File
@@ -1,27 +1,27 @@
"""
Common Utils
"""
import os
import re
import io
"""Common Utils."""
import ast
import sys
import signal
import hashlib
import io
import logging
import ntpath
import os
import platform
import random
import subprocess
import linecache
import ntpath
import hashlib
import unicodedata
import re
import shutil
import signal
import subprocess
import sys
import unicodedata
import requests
import logging
from django.shortcuts import render
from install.windows.setup import windows_config_local
from django.shortcuts import render
from . import settings
logger = logging.getLogger(__name__)
@@ -34,18 +34,23 @@ class Color(object):
def upstream_proxy(flaw_type):
"""Set upstream Proxy if needed"""
"""Set upstream Proxy if needed."""
if settings.UPSTREAM_PROXY_ENABLED:
if not settings.UPSTREAM_PROXY_USERNAME:
proxy_port = str(settings.UPSTREAM_PROXY_PORT)
proxy_host = settings.UPSTREAM_PROXY_TYPE + '://' + \
settings.UPSTREAM_PROXY_IP + ':' + proxy_port
proxy_host = '{}://{}:{}'.format(
settings.UPSTREAM_PROXY_TYPE,
settings.UPSTREAM_PROXY_IP,
proxy_port)
proxies = {flaw_type: proxy_host}
else:
proxy_port = str(settings.UPSTREAM_PROXY_PORT)
proxy_host = settings.UPSTREAM_PROXY_TYPE + '://' + settings.UPSTREAM_PROXY_USERNAME + \
':' + settings.UPSTREAM_PROXY_PASSWORD + "@" + \
settings.UPSTREAM_PROXY_IP + ':' + proxy_port
proxy_host = '{}://{}:{}@{}:{}'.format(
settings.UPSTREAM_PROXY_TYPE,
settings.UPSTREAM_PROXY_USERNAME,
settings.UPSTREAM_PROXY_PASSWORD,
settings.UPSTREAM_PROXY_IP,
proxy_port)
proxies = {flaw_type: proxy_host}
else:
proxies = {flaw_type: None}
@@ -54,37 +59,36 @@ def upstream_proxy(flaw_type):
def api_key():
"""Print REST API Key"""
"""Print REST API Key."""
if os.environ.get('MOBSF_API_KEY'):
logger.info("\nAPI Key read from environment variable")
logger.info('\nAPI Key read from environment variable')
return os.environ['MOBSF_API_KEY']
secret_file = os.path.join(settings.MobSF_HOME, "secret")
if isFileExists(secret_file):
secret_file = os.path.join(settings.MobSF_HOME, 'secret')
if is_file_exists(secret_file):
try:
_api_key = open(secret_file).read().strip()
return gen_sha256_hash(_api_key)
except Exception:
PrintException("Cannot Read API Key")
log_exception('Cannot Read API Key')
def printMobSFverison():
"""Print MobSF Version"""
def print_version():
"""Print MobSF Version."""
logger.info(settings.BANNER)
if platform.system() == "Windows":
logger.info('Mobile Security Framework ' + settings.MOBSF_VER)
print("REST API Key: " + api_key())
ver = settings.MOBSF_VER
if platform.system() == 'Windows':
logger.info('Mobile Security Framework %s', ver)
print('REST API Key: ' + api_key())
else:
logger.info('\033[1m\033[34mMobile Security Framework ' +
settings.MOBSF_VER + '\033[0m')
print("REST API Key: " + Color.BOLD + api_key() + Color.END)
logger.info("OS: " + platform.system())
logger.info("Platform: " + platform.platform())
logger.info('\033[1m\033[34mMobile Security Framework %s\033[0m', ver)
print('REST API Key: ' + Color.BOLD + api_key() + Color.END)
logger.info('OS: %s', platform.system())
logger.info('Platform: %s', platform.platform())
if platform.dist()[0]:
logger.info("Dist: " + str(platform.dist()))
FindJava(True)
FindVbox(True)
logger.info('Dist: %s', str(platform.dist()))
find_java_binary(True)
find_vboxmange_binary(True)
check_basic_env()
adb_binary_or32bit_support()
check_update()
@@ -92,163 +96,161 @@ def printMobSFverison():
def check_update():
try:
logger.info("Checking for Update.")
github_url = "https://raw.githubusercontent.com/MobSF/Mobile-Security-Framework-MobSF/master/MobSF/settings.py"
logger.info('Checking for Update.')
github_url = ('https://raw.githubusercontent.com/'
'MobSF/Mobile-Security-Framework-MobSF/'
'master/MobSF/settings.py')
try:
proxies, verify = upstream_proxy('https')
except Exception:
PrintException("Setting upstream proxy")
log_exception('Setting upstream proxy')
response = requests.get(github_url, timeout=5,
proxies=proxies, verify=verify)
html = str(response.text).split("\n")
html = str(response.text).split('\n')
for line in html:
if line.startswith("MOBSF_VER"):
line = line.replace("MOBSF_VER", "").replace('"', '')
line = line.replace("=", "").strip()
if line.startswith('MOBSF_VER'):
line = line.replace('MOBSF_VER', '').replace('"', '')
line = line.replace('=', '').strip()
if line != settings.MOBSF_VER:
logger.warning("A new version of MobSF is available, Please update from master branch or check "
"for new releases.")
logger.warning('A new version of MobSF is available, '
'Please update from master branch or check '
'for new releases.')
else:
logger.info("No updates available.")
except requests.exceptions.HTTPError as err:
logger.warning(
"\nCannot check for updates.. No Internet Connection Found.")
logger.info('No updates available.')
except requests.exceptions.HTTPError:
logger.warning('\nCannot check for updates..'
' No Internet Connection Found.')
return
except:
PrintException("Cannot Check for updates.")
except Exception:
log_exception('Cannot Check for updates.')
def createUserConfig(MobSF_HOME):
def create_user_conf(mobsf_home):
try:
CONFIG_PATH = os.path.join(MobSF_HOME, 'config.py')
if isFileExists(CONFIG_PATH) == False:
SAMPLE_CONF = os.path.join(settings.BASE_DIR, "MobSF/settings.py")
with io.open(SAMPLE_CONF, mode='r', encoding="utf8", errors="ignore") as f:
config_path = os.path.join(mobsf_home, 'config.py')
if not is_file_exists(config_path):
sample_conf = os.path.join(settings.BASE_DIR, 'MobSF/settings.py')
with open(sample_conf, 'r') as f:
dat = f.readlines()
CONFIG = list()
config = []
add = False
for line in dat:
if "^CONFIG-START^" in line:
if '^CONFIG-START^' in line:
add = True
if "^CONFIG-END^" in line:
if '^CONFIG-END^' in line:
break
if add:
CONFIG.append(line.lstrip())
CONFIG.pop(0)
COMFIG_STR = ''.join(CONFIG)
with io.open(CONFIG_PATH, mode='w', encoding="utf8", errors="ignore") as f:
f.write(COMFIG_STR)
except:
PrintException("Cannot create config file")
config.append(line.lstrip())
config.pop(0)
conf_str = ''.join(config)
with open(config_path, 'w') as f:
f.write(conf_str)
except Exception:
log_exception('Cannot create config file')
def getMobSFHome(useHOME):
def get_mobsf_home(use_home):
try:
MobSF_HOME = ""
if useHOME:
MobSF_HOME = os.path.join(os.path.expanduser('~'), ".MobSF")
mobsf_home = ''
if use_home:
mobsf_home = os.path.join(os.path.expanduser('~'), '.MobSF')
# MobSF Home Directory
if not os.path.exists(MobSF_HOME):
os.makedirs(MobSF_HOME)
createUserConfig(MobSF_HOME)
if not os.path.exists(mobsf_home):
os.makedirs(mobsf_home)
create_user_conf(mobsf_home)
else:
MobSF_HOME = settings.BASE_DIR
mobsf_home = settings.BASE_DIR
# Logs Directory
LOG_DIR = os.path.join(MobSF_HOME, 'logs/')
if not os.path.exists(LOG_DIR):
os.makedirs(LOG_DIR)
log_dir = os.path.join(mobsf_home, 'logs/')
if not os.path.exists(log_dir):
os.makedirs(log_dir)
# Certs Directory
CERT_DIR = os.path.join(LOG_DIR, 'certs/')
if not os.path.exists(CERT_DIR):
os.makedirs(CERT_DIR)
cert_dir = os.path.join(log_dir, 'certs/')
if not os.path.exists(cert_dir):
os.makedirs(cert_dir)
# Download Directory
DWD_DIR = os.path.join(MobSF_HOME, 'downloads/')
if not os.path.exists(DWD_DIR):
os.makedirs(DWD_DIR)
dwd_dir = os.path.join(mobsf_home, 'downloads/')
if not os.path.exists(dwd_dir):
os.makedirs(dwd_dir)
# Screenshot Directory
SCREEN_DIR = os.path.join(DWD_DIR, 'screen/')
if not os.path.exists(SCREEN_DIR):
os.makedirs(SCREEN_DIR)
screen_dir = os.path.join(dwd_dir, 'screen/')
if not os.path.exists(screen_dir):
os.makedirs(screen_dir)
# Upload Directory
UPLD_DIR = os.path.join(MobSF_HOME, 'uploads/')
if not os.path.exists(UPLD_DIR):
os.makedirs(UPLD_DIR)
return MobSF_HOME
except:
PrintException("Creating MobSF Home Directory")
def get_python():
"""Get Python Executable"""
return sys.executable
upload_dir = os.path.join(mobsf_home, 'uploads/')
if not os.path.exists(upload_dir):
os.makedirs(upload_dir)
return mobsf_home
except Exception:
log_exception('Creating MobSF Home Directory')
def make_migrations(base_dir):
"""Create Database Migrations"""
"""Create Database Migrations."""
try:
python = get_python()
manage = os.path.join(base_dir, "manage.py")
args = [python, manage, "makemigrations"]
manage = os.path.join(base_dir, 'manage.py')
args = [sys.executable, manage, 'makemigrations']
subprocess.call(args)
args = [python, manage, "makemigrations", "StaticAnalyzer"]
args = [sys.executable, manage, 'makemigrations', 'StaticAnalyzer']
subprocess.call(args)
except:
PrintException("Cannot Make Migrations")
except Exception:
log_exception('Cannot Make Migrations')
def migrate(BASE_DIR):
"""Migrate Database"""
def migrate(base_dir):
"""Migrate Database."""
try:
python = get_python()
manage = os.path.join(BASE_DIR, "manage.py")
args = [python, manage, "migrate"]
manage = os.path.join(base_dir, 'manage.py')
args = [sys.executable, manage, 'migrate']
subprocess.call(args)
except:
PrintException("Cannot Migrate")
args = [sys.executable, manage, 'migrate', '--run-syncdb']
subprocess.call(args)
except Exception:
log_exception('Cannot Migrate')
def kali_fix(BASE_DIR):
def kali_fix(base_dir):
try:
if platform.system() == "Linux" and platform.dist()[0] == "Kali":
fix_path = os.path.join(BASE_DIR, "scripts/kali_fix.sh")
subprocess.call(["chmod", "a+x", fix_path])
if platform.system() == 'Linux' and platform.dist()[0] == 'Kali':
fix_path = os.path.join(base_dir, 'scripts/kali_fix.sh')
subprocess.call(['chmod', 'a+x', fix_path])
subprocess.call([fix_path], shell=True)
except:
PrintException("Cannot run Kali Fix")
except Exception:
log_exception('Cannot run Kali Fix')
def FindVbox(debug=False):
def find_vboxmange_binary(debug=False):
try:
if settings.ANDROID_DYNAMIC_ANALYZER == "MobSF_VM":
if len(settings.VBOXMANAGE_BINARY) > 0 and isFileExists(settings.VBOXMANAGE_BINARY):
vpt = ['C:\\Program Files\\Oracle\\VirtualBox\\VBoxManage.exe',
'C:\\Program Files (x86)\\Oracle\\VirtualBox\\VBoxManage.exe']
if settings.ANDROID_DYNAMIC_ANALYZER == 'MobSF_VM':
if (len(settings.VBOXMANAGE_BINARY) > 0
and is_file_exists(settings.VBOXMANAGE_BINARY)):
return settings.VBOXMANAGE_BINARY
if platform.system() == "Windows":
# Path to VBoxManage.exe
vbox_path = ["C:\Program Files\Oracle\VirtualBox\VBoxManage.exe",
"C:\Program Files (x86)\Oracle\VirtualBox\VBoxManage.exe"]
for path in vbox_path:
if platform.system() == 'Windows':
for path in vpt:
if os.path.isfile(path):
return path
else:
# Path to VBoxManage in Linux/Mac
vbox_path = ["/usr/bin/VBoxManage",
"/usr/local/bin/VBoxManage"]
for path in vbox_path:
vpt = ['/usr/bin/VBoxManage',
'/usr/local/bin/VBoxManage']
for path in vpt:
if os.path.isfile(path):
return path
if debug:
logger.warning("Could not find VirtualBox path.")
except:
logger.warning('Could not find VirtualBox path')
except Exception:
if debug:
PrintException("Cannot find VirtualBox path.")
log_exception('Cannot find VirtualBox path.')
# Maintain JDK Version
JAVA_VER = '1.7|1.8|1.9|2.0|2.1|2.2|2.3'
def FindJava(debug=False):
""" Find Java """
def find_java_binary(debug=False):
"""Find Java."""
# Maintain JDK Version
java_versions = '1.7|1.8|1.9|2.0|2.1|2.2|2.3|8|9|10|11'
"""
@@ -256,114 +258,114 @@ def FindJava(debug=False):
of setting java path :-(
"""
win_java_paths = [
"C:/Program Files/Java/",
"C:/Program Files (x86)/Java/",
"D:/Program Files/Java/",
"D:/Program Files (x86)/Java/",
"E:/Program Files/Java/",
"E:/Program Files (x86)/Java/",
"F:/Program Files/Java/",
"F:/Program Files (x86)/Java/",
"G:/Program Files/Java/",
"G:/Program Files (x86)/Java/",
"H:/Program Files/Java/",
"H:/Program Files (x86)/Java/",
"I:/Program Files/Java/",
"I:/Program Files (x86)/Java/",
'C:/Program Files/Java/',
'C:/Program Files (x86)/Java/',
'D:/Program Files/Java/',
'D:/Program Files (x86)/Java/',
'E:/Program Files/Java/',
'E:/Program Files (x86)/Java/',
'F:/Program Files/Java/',
'F:/Program Files (x86)/Java/',
'G:/Program Files/Java/',
'G:/Program Files (x86)/Java/',
'H:/Program Files/Java/',
'H:/Program Files (x86)/Java/',
'I:/Program Files/Java/',
'I:/Program Files (x86)/Java/',
]
try:
err_msg1 = "Oracle JDK 1.7 or above is not found!"
if isDirExists(settings.JAVA_DIRECTORY):
if settings.JAVA_DIRECTORY.endswith("/"):
err_msg1 = 'Oracle JDK 1.7 or above is not found!'
if is_dir_exists(settings.JAVA_DIRECTORY):
if settings.JAVA_DIRECTORY.endswith('/'):
return settings.JAVA_DIRECTORY
elif settings.JAVA_DIRECTORY.endswith("\\"):
elif settings.JAVA_DIRECTORY.endswith('\\'):
return settings.JAVA_DIRECTORY
else:
return settings.JAVA_DIRECTORY + "/"
elif platform.system() == "Windows":
return settings.JAVA_DIRECTORY + '/'
elif platform.system() == 'Windows':
if debug:
logger.info("Finding JDK Location in Windows....")
logger.info('Finding JDK Location in Windows....')
# JDK 7 jdk1.7.0_17/bin/
for java_path in win_java_paths:
if os.path.isdir(java_path):
for dirname in os.listdir(java_path):
if "jdk" in dirname:
win_java_path = java_path + dirname + "/bin/"
args = [win_java_path + "java", "-version"]
dat = RunProcess(args)
if "java" in dat:
if 'jdk' in dirname:
win_java_path = java_path + dirname + '/bin/'
args = [win_java_path + 'java', '-version']
dat = run_process(args)
if 'java' in dat:
if debug:
logger.info(
"Oracle Java JDK is installed!")
'Oracle Java JDK is installed!')
return win_java_path
for env in ["JDK_HOME", "JAVA_HOME"]:
for env in ['JDK_HOME', 'JAVA_HOME']:
java_home = os.environ.get(env)
if java_home and os.path.isdir(java_home):
win_java_path = java_home + "/bin/"
args = [win_java_path + "java", "-version"]
dat = RunProcess(args)
if "java" in dat:
win_java_path = java_home + '/bin/'
args = [win_java_path + 'java', '-version']
dat = run_process(args)
if 'java' in dat:
if debug:
logger.info("Oracle Java is installed!")
logger.info('Oracle Java is installed!')
return win_java_path
if debug:
logger.info(err_msg1)
return "java"
return 'java'
else:
if debug:
logger.info("Finding JDK Location in Linux/MAC....")
logger.info('Finding JDK Location in Linux/MAC....')
# Check in Environment Variables
for env in ["JDK_HOME", "JAVA_HOME"]:
for env in ['JDK_HOME', 'JAVA_HOME']:
java_home = os.environ.get(env)
if java_home and os.path.isdir(java_home):
lm_java_path = java_home + "/bin/"
args = [lm_java_path + "java", "-version"]
dat = RunProcess(args)
if "oracle" in dat:
lm_java_path = java_home + '/bin/'
args = [lm_java_path + 'java', '-version']
dat = run_process(args)
if 'oracle' in dat:
if debug:
logger.info("Oracle Java is installed!")
logger.info('Oracle Java is installed!')
return lm_java_path
mac_linux_java_dir = "/usr/bin/"
args = [mac_linux_java_dir + "java"]
dat = RunProcess(args)
if "oracle" in dat:
args = [mac_linux_java_dir + "java", '-version']
dat = RunProcess(args)
f_line = dat.split("\n")[0]
mac_linux_java_dir = '/usr/bin/'
args = [mac_linux_java_dir + 'java']
dat = run_process(args)
if 'oracle' in dat:
args = [mac_linux_java_dir + 'java', '-version']
dat = run_process(args)
f_line = dat.split('\n')[0]
if re.findall(java_versions, f_line):
if debug:
logger.info("JDK 1.7 or above is available")
logger.info('JDK 1.7 or above is available')
return mac_linux_java_dir
else:
err_msg = "Please install Oracle JDK 1.7 or above"
err_msg = 'Please install Oracle JDK 1.7 or above'
if debug:
logger.error(err_msg)
return "java"
return 'java'
else:
args = [mac_linux_java_dir + "java", '-version']
dat = RunProcess(args)
f_line = dat.split("\n")[0]
args = [mac_linux_java_dir + 'java', '-version']
dat = run_process(args)
f_line = dat.split('\n')[0]
if re.findall(java_versions, f_line):
if debug:
logger.info("JDK 1.7 or above is available")
logger.info('JDK 1.7 or above is available')
return mac_linux_java_dir
else:
err_msg = "Please install Oracle JDK 1.7 or above"
err_msg = 'Please install Oracle JDK 1.7 or above'
if debug:
logger.error(err_msg)
return "java"
return 'java'
except:
except Exception:
if debug:
PrintException("Oracle Java (JDK >=1.7) is not found!")
return "java"
log_exception('Oracle Java (JDK >=1.7) is not found!')
return 'java'
def RunProcess(args):
def run_process(args):
try:
proc = subprocess.Popen(
args, stdout=subprocess.PIPE, stderr=subprocess.STDOUT,)
args, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
dat = ''
while True:
line = proc.stdout.readline()
@@ -371,41 +373,35 @@ def RunProcess(args):
break
dat += str(line)
return dat
except:
PrintException("Finding Java path - Cannot Run Process")
return ""
except Exception:
log_exception('Finding Java path - Cannot Run Process')
return ''
def PrintException(msg, web=False):
"""Print Exception verbose"""
exc_type, exc_obj, tb = sys.exc_info()
f = tb.tb_frame
lineno = tb.tb_lineno
filename = f.f_code.co_filename
linecache.checkcache(filename)
line = linecache.getline(filename, lineno, f.f_globals)
dat = '\n' + msg + \
' ({0}, LINE {1} "{2}"): {3}'.format(
filename, lineno, line.strip(), exc_obj)
def log_exception(msg, web=False):
"""Log Exception verbose."""
if web:
logger.warning(dat)
logger.warning(msg)
else:
logger.error(dat)
logger.error(msg)
def print_n_send_error_response(request, msg, api=False, exp='Error Description'):
"""Print and log errors"""
def print_n_send_error_response(request,
msg,
api=False,
exp='Error Description'):
"""Print and log errors."""
logger.error(msg)
if api:
api_response = {"error": msg}
api_response = {'error': msg}
return api_response
else:
context = {
'title': 'Error',
'exp': exp,
'doc': msg
'doc': msg,
}
template = "general/error.html"
template = 'general/error.html'
return render(request, template, context, status=500)
@@ -414,17 +410,17 @@ def filename_from_path(path):
return tail or ntpath.basename(head)
def getMD5(data):
def get_md5(data):
return hashlib.md5(data).hexdigest()
def findBetween(s, first, last):
def find_between(s, first, last):
try:
start = s.index(first) + len(first)
end = s.index(last, start)
return s[start:end]
except ValueError:
return ""
return ''
def is_number(s):
@@ -457,47 +453,47 @@ def python_dict(value):
return ast.literal_eval(value)
def isBase64(str):
return re.match('^[A-Za-z0-9+/]+[=]{0,2}$', str)
def is_base64(b_str):
return re.match('^[A-Za-z0-9+/]+[=]{0,2}$', b_str)
def isInternetAvailable():
def is_internet_available():
try:
proxies, verify = upstream_proxy('https')
except:
PrintException("Setting upstream proxy")
except Exception:
log_exception('Setting upstream proxy')
try:
requests.get('https://www.google.com', timeout=5,
proxies=proxies, verify=verify)
return True
except requests.exceptions.HTTPError as err:
except requests.exceptions.HTTPError:
try:
requests.get('https://www.baidu.com/', timeout=5,
proxies=proxies, verify=verify)
return True
except requests.exceptions.HTTPError as err1:
except requests.exceptions.HTTPError:
return False
return False
def sha256(file_path):
BLOCKSIZE = 65536
blocksize = 65536
hasher = hashlib.sha256()
with io.open(file_path, mode='rb') as afile:
buf = afile.read(BLOCKSIZE)
buf = afile.read(blocksize)
while buf:
hasher.update(buf)
buf = afile.read(BLOCKSIZE)
buf = afile.read(blocksize)
return (hasher.hexdigest())
def gen_sha256_hash(msg):
"""Generate SHA 256 Hash of the message"""
"""Generate SHA 256 Hash of the message."""
hash_object = hashlib.sha256(msg.encode('utf-8'))
return hash_object.hexdigest()
def isFileExists(file_path):
def is_file_exists(file_path):
if os.path.isfile(file_path):
return True
# This fix situation where a user just typed "adb" or another executable
@@ -508,101 +504,106 @@ def isFileExists(file_path):
return False
def isDirExists(dir_path):
def is_dir_exists(dir_path):
if os.path.isdir(dir_path):
return True
else:
return False
def genRandom():
return ''.join([random.SystemRandom().choice('abcdefghijklmnopqrstuvwxyz0123456789!@#$%^&*(-_=+)') for i in range(50)])
def get_random():
choice = 'abcdefghijklmnopqrstuvwxyz0123456789!@#$%^&*(-_=+)'
return ''.join([random.SystemRandom().choice(choice) for i in range(50)])
def zipdir(path, zip_file):
"""Zip a directory."""
try:
logger.info("Zipping")
logger.info('Zipping')
# pylint: disable=unused-variable
# Needed by os.walk
for root, _sub_dir, files in os.walk(path):
for file_name in files:
zip_file.write(os.path.join(root, file_name))
except:
PrintException("Zipping")
except Exception:
log_exception('Zipping')
def getADB():
"""Get ADB binary path"""
def get_adb():
"""Get ADB binary path."""
try:
if len(settings.ADB_BINARY) > 0 and isFileExists(settings.ADB_BINARY):
if (len(settings.ADB_BINARY) > 0
and is_file_exists(settings.ADB_BINARY)):
return settings.ADB_BINARY
else:
adb = 'adb'
if platform.system() == "Darwin":
if platform.system() == 'Darwin':
adb_dir = os.path.join(settings.TOOLS_DIR, 'adb/mac/')
subprocess.call(["chmod", "777", adb_dir])
subprocess.call(['chmod', '777', adb_dir])
adb = os.path.join(settings.TOOLS_DIR, 'adb/mac/adb')
elif platform.system() == "Linux":
elif platform.system() == 'Linux':
adb_dir = os.path.join(settings.TOOLS_DIR, 'adb/linux/')
subprocess.call(["chmod", "777", adb_dir])
subprocess.call(['chmod', '777', adb_dir])
adb = os.path.join(settings.TOOLS_DIR, 'adb/linux/adb')
elif platform.system() == "Windows":
elif platform.system() == 'Windows':
adb = os.path.join(settings.TOOLS_DIR, 'adb/windows/adb.exe')
return adb
except:
PrintException("Getting ADB Location")
return "adb"
except Exception:
log_exception('Getting ADB Location')
return 'adb'
def adb_binary_or32bit_support():
"""Check if 32bit is supported. Also if the binary works"""
adb_path = getADB()
"""Check if 32bit is supported. Also if the binary works."""
adb_path = get_adb()
try:
fnull = open(os.devnull, 'w')
subprocess.call([adb_path], stdout=fnull, stderr=fnull)
except:
msg = "\nYou don't have 32 bit execution support enabled or MobSF shipped" \
" ADB binary is not compatible with your OS."\
"\nPlease set the 'ADB_BINARY' path in settings.py"
except Exception:
msg = ('\nYou don\'t have 32 bit execution support enabled'
' or MobSF shipped ADB binary is not compatible with your OS.'
'\nPlease set the ADB_BINARY path in MobSF/settings.py')
logger.warning(msg)
def check_basic_env():
"""Check if we have basic env for MobSF to run"""
logger.info("MobSF Basic Environment Check")
"""Check if we have basic env for MobSF to run."""
logger.info('MobSF Basic Environment Check')
try:
import capfuzz
import capfuzz # noqa F401
except ImportError:
PrintException("CapFuzz not installed!")
log_exception('CapFuzz not installed!')
os.kill(os.getpid(), signal.SIGTERM)
try:
import lxml
import lxml # noqa F401
except ImportError:
PrintException("lxml is not installed!")
log_exception('lxml is not installed!')
os.kill(os.getpid(), signal.SIGTERM)
if platform.system() == "Windows":
if platform.system() == 'Windows':
java = settings.JAVA_PATH + 'java.exe'
else:
java = settings.JAVA_PATH + 'java'
if not isFileExists(java):
if not is_file_exists(java):
logger.error(
"Oracle Java is not available or `JAVA_DIRECTORY` in settings.py is configured incorrectly!")
logger.info("JAVA_DIRECTORY=%s" % settings.JAVA_DIRECTORY)
logger.info('''Example Configuration:
JAVA_DIRECTORY = "C:/Program Files/Java/jdk1.7.0_17/bin/"
JAVA_DIRECTORY = "/usr/bin/"
''')
'Oracle Java is not available '
'or JåAVA_DIRECTORY in MobSF/settings.py '
'is configured incorrectly!')
logger.info('JAVA_DIRECTORY=%s"', settings.JAVA_DIRECTORY)
logger.info('Example Confxiguration:'
'\nJAVA_DIRECTORY = "C:/Program Files/'
'Java/jdk1.7.0_17/bin/"'
'\nJAVA_DIRECTORY = "/usr/bin/"')
os.kill(os.getpid(), signal.SIGTERM)
def first_run(secret_file, base_dir, mobsf_home):
# Based on https://gist.github.com/ndarville/3452907#file-secret-key-gen-py
try:
secret_key = open(secret_file).read().strip()
except IOError:
try:
secret_key = genRandom()
secret_key = get_random()
secret = open(secret_file, 'w')
secret.write(secret_key)
secret.close()
+56 -82
View File
@@ -1,50 +1,26 @@
"""
MobSF REST API V 1
"""
# -*- coding: utf_8 -*-
"""MobSF REST API V 1."""
from django.http import (
HttpResponse,
JsonResponse
)
from django.http import HttpResponse, JsonResponse
from django.views.decorators.csrf import csrf_exempt
from MobSF.views.home import (
Upload,
delete_scan,
RecentScans
)
from MobSF.utils import (
api_key,
)
from MobSF.views.helpers import (
request_method,
)
from StaticAnalyzer.views.shared_func import (
pdf
)
from StaticAnalyzer.views.android.static_analyzer import (
static_analyzer
)
from StaticAnalyzer.views.ios.static_analyzer import (
static_analyzer_ios,
)
from StaticAnalyzer.views.android import (
view_source
)
from StaticAnalyzer.views.ios import (
view_source as ios_view_source
)
from StaticAnalyzer.views.windows import (
staticanalyzer_windows
)
from MobSF.utils import api_key
from MobSF.views.helpers import request_method
from MobSF.views.home import RecentScans, Upload, delete_scan
from StaticAnalyzer.views.android import view_source
from StaticAnalyzer.views.android.static_analyzer import static_analyzer
from StaticAnalyzer.views.ios import view_source as ios_view_source
from StaticAnalyzer.views.ios.static_analyzer import static_analyzer_ios
from StaticAnalyzer.views.shared_func import pdf
from StaticAnalyzer.views.windows import staticanalyzer_windows
BAD_REQUEST = 400
OK = 200
def make_api_response(data, status=OK):
"""Make API Response"""
"""Make API Response."""
resp = JsonResponse(data=data, status=status)
resp['Access-Control-Allow-Origin'] = '*'
resp['Access-Control-Allow-Methods'] = 'POST'
@@ -54,16 +30,16 @@ def make_api_response(data, status=OK):
def api_auth(meta):
"""Check if API Key Matches"""
if "HTTP_AUTHORIZATION" in meta:
return bool(api_key() == meta["HTTP_AUTHORIZATION"])
"""Check if API Key Matches."""
if 'HTTP_AUTHORIZATION' in meta:
return bool(api_key() == meta['HTTP_AUTHORIZATION'])
return False
@request_method(['POST'])
@csrf_exempt
def api_upload(request):
"""POST - Upload API"""
"""POST - Upload API."""
upload = Upload(request)
resp, code = upload.upload_api()
return make_api_response(resp, code)
@@ -72,10 +48,10 @@ def api_upload(request):
@request_method(['GET'])
@csrf_exempt
def api_recent_scans(request):
"""GET - get recent scans"""
"""GET - get recent scans."""
scans = RecentScans(request)
resp = scans.recent_scans()
if "error" in resp:
if 'error' in resp:
return make_api_response(resp, 500)
else:
return make_api_response(resp, 200)
@@ -84,130 +60,128 @@ def api_recent_scans(request):
@request_method(['POST'])
@csrf_exempt
def api_scan(request):
"""POST - Scan API"""
"""POST - Scan API."""
params = ['scan_type', 'hash', 'file_name']
if set(request.POST) >= set(params):
scan_type = request.POST['scan_type']
# APK, Android ZIP and iOS ZIP
if scan_type in ["apk", "zip"]:
if scan_type in ['apk', 'zip']:
resp = static_analyzer(request, True)
if "type" in resp:
if 'type' in resp:
# For now it's only ios_zip
request.POST._mutable = True
request.POST['scan_type'] = "ios"
request.POST['scan_type'] = 'ios'
resp = static_analyzer_ios(request, True)
if "error" in resp:
if 'error' in resp:
response = make_api_response(resp, 500)
else:
response = make_api_response(resp, 200)
# IPA
elif scan_type == "ipa":
elif scan_type == 'ipa':
resp = static_analyzer_ios(request, True)
if "error" in resp:
if 'error' in resp:
response = make_api_response(resp, 500)
else:
response = make_api_response(resp, 200)
# APPX
elif scan_type == "appx":
elif scan_type == 'appx':
resp = staticanalyzer_windows(request, True)
if "error" in resp:
if 'error' in resp:
response = make_api_response(resp, 500)
else:
response = make_api_response(resp, 200)
else:
response = make_api_response(
{"error": "Missing Parameters"}, 422)
{'error': 'Missing Parameters'}, 422)
return response
@request_method(['POST'])
@csrf_exempt
def api_delete_scan(request):
"""POST - Delete a Scan"""
if "hash" in request.POST:
"""POST - Delete a Scan."""
if 'hash' in request.POST:
resp = delete_scan(request, True)
if "error" in resp:
if 'error' in resp:
response = make_api_response(resp, 500)
else:
response = make_api_response(resp, 200)
else:
response = make_api_response(
{"error": "Missing Parameters"}, 422)
{'error': 'Missing Parameters'}, 422)
return response
@request_method(['POST'])
@csrf_exempt
def api_pdf_report(request):
"""Generate and Download PDF"""
"""Generate and Download PDF."""
params = ['scan_type', 'hash']
if set(request.POST) == set(params):
resp = pdf(request, api=True)
if "error" in resp:
if resp.get("error") == "Invalid scan hash":
if 'error' in resp:
if resp.get('error') == 'Invalid scan hash':
response = make_api_response(resp, 400)
else:
response = make_api_response(resp, 500)
elif "pdf_dat" in resp:
elif 'pdf_dat' in resp:
response = HttpResponse(
resp["pdf_dat"], content_type='application/pdf')
response["Access-Control-Allow-Origin"] = "*"
elif resp.get("report") == "Report not Found":
resp['pdf_dat'], content_type='application/pdf')
response['Access-Control-Allow-Origin'] = '*'
elif resp.get('report') == 'Report not Found':
response = make_api_response(resp, 404)
elif resp.get("scan_type") == "Type is not Allowed":
elif resp.get('scan_type') == 'Type is not Allowed':
response = make_api_response(resp, 400)
else:
response = make_api_response(
{"error": "PDF Generation Error"}, 500)
{'error': 'PDF Generation Error'}, 500)
else:
response = make_api_response(
{"error": "Missing Parameters"}, 422)
{'error': 'Missing Parameters'}, 422)
return response
@request_method(['POST'])
@csrf_exempt
def api_json_report(request):
"""Generate JSON Report"""
"""Generate JSON Report."""
params = ['scan_type', 'hash']
if set(request.POST) == set(params):
resp = pdf(request, api=True, jsonres=True)
if "error" in resp:
if resp.get("error") == "Invalid scan hash":
if 'error' in resp:
if resp.get('error') == 'Invalid scan hash':
response = make_api_response(resp, 400)
else:
response = make_api_response(resp, 500)
elif "report_dat" in resp:
response = make_api_response(resp["report_dat"], 200)
elif resp.get("report") == "Report not Found":
elif 'report_dat' in resp:
response = make_api_response(resp['report_dat'], 200)
elif resp.get('report') == 'Report not Found':
response = make_api_response(resp, 404)
elif resp.get("scan_type") == "Type is not Allowed":
elif resp.get('scan_type') == 'Type is not Allowed':
response = make_api_response(resp, 400)
else:
response = make_api_response(
{"error": "JSON Generation Error"}, 500)
{'error': 'JSON Generation Error'}, 500)
else:
response = make_api_response(
{"error": "Missing Parameters"}, 422)
{'error': 'Missing Parameters'}, 422)
return response
@request_method(['POST'])
@csrf_exempt
def api_view_source(request):
"""
View Source for android & ios source file
"""
"""View Source for android & ios source file."""
params = ['file', 'type', 'hash']
if set(request.POST) >= set(params):
if request.POST["type"] in ["eclipse", "studio", "apk"]:
if request.POST['type'] in ['eclipse', 'studio', 'apk']:
resp = view_source.run(request, api=True)
else:
resp = ios_view_source.run(request, api=True)
if "error" in resp:
if 'error' in resp:
response = make_api_response(resp, 500)
else:
response = make_api_response(resp, 200)
else:
response = make_api_response({"error": "Missing Parameters"}, 422)
response = make_api_response({'error': 'Missing Parameters'}, 422)
return response
+11 -14
View File
@@ -1,26 +1,23 @@
"""
REST API Middleware
"""
# -*- coding: utf_8 -*-
"""REST API Middleware."""
from django.utils.deprecation import MiddlewareMixin
from MobSF.views.api.rest_api import (
api_auth,
make_api_response
)
from MobSF.views.api.rest_api import api_auth, make_api_response
class RestApiAuthMiddleware(MiddlewareMixin):
"""
Middleware
Middleware.
Middleware for REST API.
"""
def process_request(self, request):
"""
Middleware to handle API Auth
"""
if not request.path.startswith("/api/"):
"""Middleware to handle API Auth."""
if not request.path.startswith('/api/'):
return
if request.method == "OPTIONS":
if request.method == 'OPTIONS':
return make_api_response({}, 200)
if not api_auth(request.META):
return make_api_response(
{"error": "You are unauthorized to make this request."}, 401)
{'error': 'You are unauthorized to make this request.'}, 401)
+18 -14
View File
@@ -1,16 +1,11 @@
"""
Helpers
"""
"""Helpers."""
import functools
from django.conf import settings
from django.http import (
HttpRequest,
HttpResponseNotAllowed,
)
from django.conf import settings
from django.http import HttpRequest, HttpResponseNotAllowed
ALLOW_METHODS = ['GET', 'POST', 'PUT', 'DELETE',
'PATCH', 'OPTIONS', 'HEAD', 'TRACE', ]
'PATCH', 'OPTIONS', 'HEAD', 'TRACE']
class FileType(object):
@@ -21,6 +16,8 @@ class FileType(object):
def is_allow_file(self):
"""
Is File Allowed.
return bool
"""
if self.is_apk() or self.is_zip() or self.is_ipa() or self.is_appx():
@@ -28,20 +25,26 @@ class FileType(object):
return False
def is_apk(self):
return (self.file_type in settings.APK_MIME) and self.file_name_lower.endswith('.apk')
return (self.file_type in settings.APK_MIME
and self.file_name_lower.endswith('.apk'))
def is_zip(self):
return (self.file_type in settings.ZIP_MIME) and self.file_name_lower.endswith('.zip')
return (self.file_type in settings.ZIP_MIME
and self.file_name_lower.endswith('.zip'))
def is_ipa(self):
return (self.file_type in settings.IPA_MIME) and self.file_name_lower.endswith('.ipa')
return (self.file_type in settings.IPA_MIME
and self.file_name_lower.endswith('.ipa'))
def is_appx(self):
return (self.file_type in settings.APPX_MIME) and self.file_name_lower.endswith('.appx')
return (self.file_type in settings.APPX_MIME
and self.file_name_lower.endswith('.appx'))
def request_method(methods):
"""
Request Method Checks.
:param methods http method
need django HttpRequest
"""
@@ -49,7 +52,8 @@ def request_method(methods):
@functools.wraps(func)
def wrapper(*args, **kwargs):
if not isinstance(methods, list) and not isinstance(methods, tuple):
if (not isinstance(methods, list)
and not isinstance(methods, tuple)):
raise ValueError(
'the parameter methods is not a list or tuple')
+64 -108
View File
@@ -1,65 +1,42 @@
# -*- coding: utf_8 -*-
"""
MobSF File Upload and Home Routes
"""
"""MobSF File Upload and Home Routes."""
import json
import logging
import os
import platform
import re
import shutil
from wsgiref.util import FileWrapper
import logging
from django.conf import settings
from django.core.paginator import Paginator
from django.http import (
HttpResponse,
HttpResponseRedirect,
JsonResponse
)
from django.http import HttpResponse, HttpResponseRedirect
from django.shortcuts import render
from MobSF.forms import (
FormUtil,
UploadFileForm
)
from MobSF.views.scanning import (
Scanning,
)
from MobSF.views.helpers import (
FileType,
)
from MobSF.utils import (
PrintException,
api_key,
isDirExists,
isFileExists,
print_n_send_error_response
)
from StaticAnalyzer.models import (
RecentScansDB,
StaticAnalyzerAndroid,
StaticAnalyzerIOSZIP,
StaticAnalyzerIPA,
StaticAnalyzerWindows
)
LINUX_PLATFORM = ["Darwin", "Linux"]
from MobSF.forms import FormUtil, UploadFileForm
from MobSF.utils import (api_key, is_dir_exists, is_file_exists,
print_n_send_error_response)
from MobSF.views.helpers import FileType
from MobSF.views.scanning import Scanning
from StaticAnalyzer.models import (RecentScansDB, StaticAnalyzerAndroid,
StaticAnalyzerIOSZIP, StaticAnalyzerIPA,
StaticAnalyzerWindows)
LINUX_PLATFORM = ['Darwin', 'Linux']
HTTP_BAD_REQUEST = 400
logger = logging.getLogger(__name__)
def index(request):
"""
Index Route
"""
"""Index Route."""
context = {'version': settings.MOBSF_VER}
template = "general/index.html"
template = 'general/index.html'
return render(request, template, context)
class Upload(object):
"""
Handle File Upload based on App type
"""
"""Handle File Upload based on App type."""
def __init__(self, request):
self.request = request
@@ -75,7 +52,7 @@ class Upload(object):
def resp_json(self, data):
resp = HttpResponse(json.dumps(data),
content_type="application/json; charset=utf-8")
content_type='application/json; charset=utf-8')
resp['Access-Control-Allow-Origin'] = '*'
return resp
@@ -84,17 +61,16 @@ class Upload(object):
response_data = {
'url': '',
'description': '',
'status': ''
'status': '',
}
if request.method != 'POST':
logger.error("Method not Supported!")
form = UploadFileForm()
logger.error('Method not Supported!')
response_data['description'] = 'Method not Supported!'
response_data['status'] = HTTP_BAD_REQUEST
return self.resp_json(response_data)
if not self.form.is_valid():
logger.error("Invalid Form Data!")
logger.error('Invalid Form Data!')
response_data['description'] = 'Invalid Form Data!'
response_data['status'] = HTTP_BAD_REQUEST
return self.resp_json(response_data)
@@ -103,17 +79,17 @@ class Upload(object):
self.file_name_lower = request.FILES['file'].name.lower()
self.file_type = FileType(self.file_content_type, self.file_name_lower)
if not self.file_type.is_allow_file():
logger.error("File format not Supported!")
logger.error('File format not Supported!')
response_data['description'] = 'File format not Supported!'
response_data['status'] = HTTP_BAD_REQUEST
return self.resp_json(response_data)
if self.file_type.is_ipa():
if platform.system() not in LINUX_PLATFORM:
logger.error(
"Static Analysis of iOS IPA requires Mac or Linux")
msg = 'Static Analysis of iOS IPA requires Mac or Linux'
logger.error(msg)
response_data[
'description'] = 'Static Analysis of iOS IPA requires Mac or Linux'
'description'] = msg
response_data['status'] = 'success'
response_data['url'] = 'mac_only/'
return self.resp_json(response_data)
@@ -122,9 +98,7 @@ class Upload(object):
return self.resp_json(response_data)
def upload_api(self):
"""
API File Upload
"""
"""API File Upload."""
api_response = {}
request = self.request
if not self.form.is_valid():
@@ -134,13 +108,13 @@ class Upload(object):
self.file_name_lower = request.FILES['file'].name.lower()
self.file_type = FileType(self.file_content_type, self.file_name_lower)
if not self.file_type.is_allow_file():
api_response["error"] = "File format not Supported!"
api_response['error'] = 'File format not Supported!'
return api_response, HTTP_BAD_REQUEST
data = self.upload()
api_response = {
'scan_type': data['scan_type'],
'hash': data['hash'],
'file_name': data['file_name']
'file_name': data['file_name'],
}
return api_response, 200
@@ -150,8 +124,7 @@ class Upload(object):
file_type = self.file_content_type
file_name_lower = self.file_name_lower
logger.info("MIME Type: {} FILE: {}".format(
file_type, file_name_lower))
logger.info('MIME Type: %s FILE: %s', file_type, file_name_lower)
if self.file_type.is_apk():
return scanning.scan_apk()
elif self.file_type.is_zip():
@@ -164,73 +137,57 @@ class Upload(object):
def api_docs(request):
"""
API Docs Route
"""
"""Api Docs Route."""
context = {'title': 'REST API Docs', 'api_key': api_key()}
template = "general/apidocs.html"
template = 'general/apidocs.html'
return render(request, template, context)
def about(request):
"""
About Route
"""
"""About Route."""
context = {'title': 'About'}
template = "general/about.html"
template = 'general/about.html'
return render(request, template, context)
def error(request):
"""
Error Route
"""
"""Error Route."""
context = {'title': 'Error'}
template = "general/error.html"
template = 'general/error.html'
return render(request, template, context)
def zip_format(request):
"""
Zip Format Message Route
"""
"""Zip Format Message Route."""
context = {'title': 'Zipped Source Instruction'}
template = "general/zip.html"
template = 'general/zip.html'
return render(request, template, context)
def mac_only(request):
"""
Mac Ony Message Route
"""
"""Mac Ony Message Route."""
context = {'title': 'Supports OSX Only'}
template = "general/ios.html"
template = 'general/ios.html'
return render(request, template, context)
def not_found(request):
"""
Not Found Route
"""
"""Not Found Route."""
context = {'title': 'Not Found'}
template = "general/not_found.html"
template = 'general/not_found.html'
return render(request, template, context)
def recent_scans(request):
"""
Show Recent Scans Route
"""
"""Show Recent Scans Route."""
db_obj = RecentScansDB.objects.all().order_by('-TS')
context = {'title': 'Recent Scans', 'entries': db_obj}
template = "general/recent.html"
template = 'general/recent.html'
return render(request, template, context)
def search(request):
"""
Search Scan by MD5 Route
"""
"""Search Scan by MD5 Route."""
md5 = request.GET['md5']
if re.match('[0-9a-f]{32}', md5):
db_obj = RecentScansDB.objects.filter(MD5=md5)
@@ -238,25 +195,24 @@ def search(request):
return HttpResponseRedirect('/' + db_obj[0].URL)
else:
return HttpResponseRedirect('/not_found')
return print_n_send_error_response(request, "Invalid Scan Hash")
return print_n_send_error_response(request, 'Invalid Scan Hash')
def download(request):
"""
Download from MobSF Route
"""
msg = "Error Downloading File "
"""Download from MobSF Route."""
msg = 'Error Downloading File '
if request.method == 'GET':
allowed_exts = settings.ALLOWED_EXTENSIONS
filename = request.path.replace("/download/", "", 1)
filename = request.path.replace('/download/', '', 1)
# Security Checks
if "../" in filename:
return print_n_send_error_response(request, "Path Traversal Attack detected")
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"))
wrapper = FileWrapper(open(dwd_file, 'rb'))
response = HttpResponse(
wrapper, content_type=allowed_exts[ext])
response['Content-Length'] = os.path.getsize(dwd_file)
@@ -266,9 +222,7 @@ def download(request):
def delete_scan(request, api=False):
"""
Delete Scan from DB and remove the scan related files
"""
"""Delete Scan from DB and remove the scan related files."""
try:
if request.method == 'POST':
if api:
@@ -287,23 +241,25 @@ def delete_scan(request, api=False):
StaticAnalyzerWindows.objects.filter(MD5=md5_hash).delete()
# Delete Upload Dir Contents
app_upload_dir = os.path.join(settings.UPLD_DIR, md5_hash)
if isDirExists(app_upload_dir):
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 isFileExists(item_path) and item.startswith(md5_hash + "-"):
if is_file_exists(item_path) and valid_item:
os.remove(item_path)
# Delete related directories
if isDirExists(item_path) and item.startswith(md5_hash + "-"):
if is_dir_exists(item_path) and valid_item:
shutil.rmtree(item_path)
data = {'deleted': 'yes'}
if api:
return data
else:
return HttpResponse(json.dumps(data), content_type='application/json; charset=utf-8')
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__
@@ -326,10 +282,10 @@ class RecentScans(object):
paginator = Paginator(result, page_size)
content = paginator.page(page)
data = {
"content": list(content),
"count": paginator.count,
"num_pages": paginator.num_pages
'content': list(content),
'count': paginator.count,
'num_pages': paginator.num_pages,
}
except Exception as exp:
data = {"error": str(exp)}
data = {'error': str(exp)}
return data
+37 -40
View File
@@ -1,34 +1,32 @@
# from MobSF.views import handle_uploaded_file, add_to_recent_scan
# -*- coding: utf_8 -*-
import hashlib
import os
import logging
import os
from django.conf import settings
from django.utils import timezone
from MobSF.utils import PrintException
from MobSF.utils import log_exception
from StaticAnalyzer.models import RecentScansDB
logger = logging.getLogger(__name__)
def add_to_recent_scan(name, md5, url):
"""
Add Entry to Database under Recent Scan
"""
"""Add Entry to Database under Recent Scan."""
try:
db_obj = RecentScansDB.objects.filter(MD5=md5)
if not db_obj.exists():
new_db_obj = RecentScansDB(
NAME=name, MD5=md5, URL=url, TS=timezone.now())
new_db_obj.save()
except:
PrintException("Adding Scan URL to Database")
except Exception:
log_exception('Adding Scan URL to Database')
def handle_uploaded_file(filecnt, typ):
"""
Write Uploaded File
"""
"""Write Uploaded File."""
md5 = hashlib.md5() # modify if crash for large
for chunk in filecnt.chunks():
md5.update(chunk)
@@ -42,79 +40,78 @@ def handle_uploaded_file(filecnt, typ):
return md5sum
class Scanning(object):
def __init__(self, request):
self.request = request
self.file = request.FILES['file']
self.file_name = request.FILES['file'].name
def scan_apk(self):
"""
"""
# APK
"""Android APK."""
md5 = handle_uploaded_file(self.file, '.apk')
url = 'StaticAnalyzer/?name={}&type=apk&checksum={}'.format(
self.file_name, md5)
data = {
'url': 'StaticAnalyzer/?name={}&type=apk&checksum={}'.format(self.file_name, md5),
'url': url,
'status': 'success',
'hash': md5,
'scan_type': 'apk',
'file_name': self.file_name
'file_name': self.file_name,
}
add_to_recent_scan(self.file_name, md5, data['url'])
logger.info("Performing Static Analysis of Android APK")
logger.info('Performing Static Analysis of Android APK')
return data
def scan_zip(self):
"""
"""
# Android /iOS Zipped Source
"""Android /iOS Zipped Source."""
md5 = handle_uploaded_file(self.file, '.zip')
url = 'StaticAnalyzer/?name={}&type=zip&checksum={}'.format(
self.file_name, md5)
data = {
'url': 'StaticAnalyzer/?name={}&type=zip&checksum={}'.format(self.file_name, md5),
'url': url,
'status': 'success',
'hash': md5,
'scan_type': 'zip',
'file_name': self.file_name
'file_name': self.file_name,
}
add_to_recent_scan(self.file_name, md5, data['url'])
logger.info("Performing Static Analysis of Android/iOS Source Code")
logger.info('Performing Static Analysis of Android/iOS Source Code')
return data
def scan_ipa(self):
"""
iOS Binary
"""
"""IOS Binary."""
md5 = handle_uploaded_file(self.file, '.ipa')
url = 'StaticAnalyzer_iOS/?name={}&type=ipa&checksum={}'.format(
self.file_name, md5)
data = {
'hash': md5,
'scan_type': 'ipa',
'file_name': self.file_name,
'url': 'StaticAnalyzer_iOS/?name={}&type=ipa&checksum={}'.format(self.file_name, md5),
'status': 'success'
'url': url,
'status': 'success',
}
add_to_recent_scan(self.file_name, md5, data['url'])
logger.info("Performing Static Analysis of iOS IPA")
logger.info('Performing Static Analysis of iOS IPA')
return data
def scan_appx(self):
"""
"""
"""Windows appx."""
md5 = handle_uploaded_file(self.file, '.appx')
url = 'StaticAnalyzer_Windows/?name={}&type=appx&checksum={}'.format(
self.file_name, md5)
data = {
'hash': md5,
'scan_type': 'appx',
'file_name': self.file_name,
'url': 'StaticAnalyzer_Windows/?name={}&type=appx&checksum={}'.format(self.file_name, md5),
'status': 'success'
'url': url,
'status': 'success',
}
add_to_recent_scan(self.file_name, md5, data['url'])
logger.info("Performing Static Analysis of Windows APP")
logger.info('Performing Static Analysis of Windows APP')
return data
+5 -4
View File
@@ -7,11 +7,12 @@ For more information on this file, see
https://docs.djangoproject.com/en/dev/howto/deployment/wsgi/
"""
import os
from whitenoise import WhiteNoise
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "MobSF.settings")
from django.core.wsgi import get_wsgi_application
from whitenoise import WhiteNoise
os.environ.setdefault('DJANGO_SETTINGS_MODUL', 'MobSF.settings')
application = WhiteNoise(get_wsgi_application(),
root='static', prefix='static/')
root='static', prefix='static/')
-3
View File
@@ -1,3 +0,0 @@
from django.contrib import admin
# Register your models here.
+19 -33
View File
@@ -7,49 +7,41 @@ class AttackDetect(forms.Form):
file = forms.CharField()
def clean_file(self):
"""
Check ../ from path
"""
"""Check ../ from path."""
file = self.cleaned_data['file']
if (("../" in file) or ("%2e%2e" in file) or
(".." in file) or ("%252e" in file)):
raise forms.ValidationError("Attack Detected")
if (('../' in file) or ('%2e%2e' in file)
or ('..' in file) or ('%252e' in file)):
raise forms.ValidationError('Attack Detected')
return file
class IOSChecks(forms.Form):
file = forms.CharField()
type = forms.ChoiceField(
type = forms.ChoiceField( # noqa A003
choices=(
('ipa', 'ipa'),
('ios', 'ios')
)
)
('ios', 'ios')))
def clean_ext(self):
"""
Safe Extension
"""
"""Safe Extension."""
file = self.cleaned_data['file']
ext = file.split('.')[-1]
ext_type = re.search("plist|db|sqlitedb|sqlite|txt|m", ext)
ext_type = re.search('plist|db|sqlitedb|sqlite|txt|m', ext)
if not ext_type:
raise forms.ValidationError("File Extension not supported")
raise forms.ValidationError('File Extension not supported')
return file
class APIChecks(forms.Form):
hash = forms.CharField(min_length=32, max_length=32)
hash = forms.CharField(min_length=32, max_length=32) # noqa A003
def clean_hash(self):
"""
Hash is valid
"""
"""Hash is valid."""
md5 = self.cleaned_data['hash']
md5_match = re.match('^[0-9a-f]{32}$', md5)
if not md5_match:
raise forms.ValidationError("Invalid Hash")
raise forms.ValidationError('Invalid Hash')
return md5
@@ -57,34 +49,28 @@ class WebChecks(forms.Form):
md5 = forms.CharField(min_length=32, max_length=32)
def clean_hash(self):
"""
Hash is valid
"""
"""Hash is valid."""
md5 = self.cleaned_data['md5']
md5_match = re.match('^[0-9a-f]{32}$', md5)
if not md5_match:
raise forms.ValidationError("Invalid Hash")
raise forms.ValidationError('Invalid Hash')
return md5
class AndroidChecks(forms.Form):
type = forms.ChoiceField(
type = forms.ChoiceField( # noqa A003
choices=(
('eclipse', 'eclipse'),
('studio', 'studio'),
('apk', 'apk')
)
)
('apk', 'apk')))
def clean_ext(self):
"""
Safe Extension
"""
"""Safe Extension."""
file = self.cleaned_data['file']
ext = file.split('.')[-1]
ext_type = re.search("java|smali", ext)
ext_type = re.search('java|smali', ext)
if not ext_type:
raise forms.ValidationError("File Extension not supported")
raise forms.ValidationError('File Extension not supported')
return file
+1 -1
View File
@@ -90,7 +90,7 @@ class StaticAnalyzerIPA(models.Model):
BUNDLE_URL_TYPES = models.TextField()
BUNDLE_SUPPORTED_PLATFORMS = models.CharField(max_length=50)
BUNDLE_LOCALIZATIONS = models.TextField()
BINTYPE = models.CharField(max_length=20, default="")
BINTYPE = models.CharField(max_length=20, default='')
APPSTORE_DETAILS = models.TextField(default={})
+163 -144
View File
@@ -1,18 +1,15 @@
#!/usr/bin/env python
import json
import logging
import os
import platform
import json
from django.conf import settings
from django.test import Client
from django.http import HttpResponse
from django.test import Client, TestCase
from MobSF.utils import api_key, log_exception
from MobSF.utils import (
PrintException,
api_key,
)
import logging
from django.test import TestCase
logger = logging.getLogger(__name__)
RESCAN = False
@@ -20,284 +17,306 @@ RESCAN = False
def static_analysis_test():
"""Test Static Analyzer"""
logger.info("Running Static Analyzer Unit test")
err_msg = '%s'
if platform.system() != "Windows":
err_msg = '\033[91m \033[1m %s \033[0m'
"""Test Static Analyzer."""
logger.info('Running Static Analyzer Unit test')
try:
uploaded = []
logger.info("Running Upload Test")
logger.info('Running Upload Test')
http_client = Client()
apk_dir = os.path.join(settings.BASE_DIR, "StaticAnalyzer/test_files/")
apk_dir = os.path.join(settings.BASE_DIR, 'StaticAnalyzer/test_files/')
for filename in os.listdir(apk_dir):
fpath = os.path.join(apk_dir, filename)
with open(fpath, 'rb') as filp:
response = http_client.post('/upload/', {'file': filp})
obj = json.loads(response.content.decode("utf-8"))
if response.status_code == 200 and obj["status"] == "success":
logger.info("[OK] Upload OK: " + filename)
uploaded.append(obj["url"])
obj = json.loads(response.content.decode('utf-8'))
if response.status_code == 200 and obj['status'] == 'success':
logger.info('[OK] Upload OK: %s', filename)
uploaded.append(obj['url'])
else:
logger.error(err_msg % " Performing Upload: " + filename)
logger.error('Performing Upload: %s', filename)
return True
break
logger.info("[OK] Completed Upload test")
logger.info("Running Static Analysis Test")
logger.info('[OK] Completed Upload test')
logger.info('Running Static Analysis Test')
for upl in uploaded:
if RESCAN:
upl = "/" + upl + "&rescan=1"
upl = '/' + upl + '&rescan=1'
else:
upl = "/" + upl
upl = '/' + upl
resp = http_client.get(upl, follow=True)
if resp.status_code == 200:
logger.info("[OK] Static Analysis Complete: " + upl)
logger.info('[OK] Static Analysis Complete: %s', upl)
else:
logger.error(err_msg % " Performing Static Analysis: " + upl)
logger.error('Performing Static Analysis: %s', upl)
return True
break
logger.info("[OK] Static Analysis test completed")
logger.info("Running PDF Generation Test")
logger.info('[OK] Static Analysis test completed')
logger.info('Running PDF Generation Test')
if platform.system() in ['Darwin', 'Linux']:
pdfs = [
"/PDF/?md5=3a552566097a8de588b8184b059b0158&type=APK",
"/PDF/?md5=6c23c2970551be15f32bbab0b5db0c71&type=IPA",
"/PDF/?md5=52c50ae824e329ba8b5b7a0f523efffe&type=ANDZIP",
"/PDF/?md5=57bb5be0ea44a755ada4a93885c3825e&type=IOSZIP",
"/PDF/?md5=8179b557433835827a70510584f3143e&type=APPX",
'/PDF/?md5=3a552566097a8de588b8184b059b0158&type=APK',
'/PDF/?md5=6c23c2970551be15f32bbab0b5db0c71&type=IPA',
'/PDF/?md5=52c50ae824e329ba8b5b7a0f523efffe&type=ANDZIP',
'/PDF/?md5=57bb5be0ea44a755ada4a93885c3825e&type=IOSZIP',
'/PDF/?md5=8179b557433835827a70510584f3143e&type=APPX',
]
else:
pdfs = [
"/PDF/?md5=3a552566097a8de588b8184b059b0158&type=APK",
"/PDF/?md5=52c50ae824e329ba8b5b7a0f523efffe&type=ANDZIP",
"/PDF/?md5=57bb5be0ea44a755ada4a93885c3825e&type=IOSZIP",
"/PDF/?md5=8179b557433835827a70510584f3143e&type=APPX",
'/PDF/?md5=3a552566097a8de588b8184b059b0158&type=APK',
'/PDF/?md5=52c50ae824e329ba8b5b7a0f523efffe&type=ANDZIP',
'/PDF/?md5=57bb5be0ea44a755ada4a93885c3825e&type=IOSZIP',
'/PDF/?md5=8179b557433835827a70510584f3143e&type=APPX',
]
for pdf in pdfs:
resp = http_client.get(pdf)
if (resp.status_code == 200 and
resp._headers['content-type'][1] == "application/pdf"
):
logger.info("[OK] PDF Report Generated: " + pdf)
if (resp.status_code == 200
and resp._headers['content-type'][1] == 'application/pdf'):
logger.info('[OK] PDF Report Generated: %s', pdf)
else:
logger.error(err_msg % " Generating PDF: " + pdf)
logger.error('Generating PDF: %s', pdf)
logger.info(resp.content)
return True
break
logger.info("[OK] PDF Generation test completed")
logger.info('[OK] PDF Generation test completed')
# Compare apps test
logger.info("Running App Compare tests")
logger.info('Running App Compare tests')
first_app = '3a552566097a8de588b8184b059b0158'
second_app = '52c50ae824e329ba8b5b7a0f523efffe'
url = '/compare/{}/{}/'.format(first_app, second_app)
resp = http_client.get(url, follow=True)
assert (resp.status_code == 200)
if resp.status_code == 200:
logger.info("[OK] App compare tests passed successfully")
logger.info('[OK] App compare tests passed successfully')
else:
logger.error(err_msg % " App compare tests failed")
logger.error('App compare tests failed')
logger.info(resp.content)
return True
logger.info("Running Delete Scan Results test")
logger.info('Running Delete Scan Results test')
# Deleting Scan Results
if platform.system() in ['Darwin', 'Linux']:
scan_md5s = ["3a552566097a8de588b8184b059b0158", "6c23c2970551be15f32bbab0b5db0c71",
"52c50ae824e329ba8b5b7a0f523efffe", "57bb5be0ea44a755ada4a93885c3825e",
"8179b557433835827a70510584f3143e"]
scan_md5s = ['3a552566097a8de588b8184b059b0158',
'6c23c2970551be15f32bbab0b5db0c71',
'52c50ae824e329ba8b5b7a0f523efffe',
'57bb5be0ea44a755ada4a93885c3825e',
'8179b557433835827a70510584f3143e']
else:
scan_md5s = ["3a552566097a8de588b8184b059b0158", "52c50ae824e329ba8b5b7a0f523efffe",
"57bb5be0ea44a755ada4a93885c3825e", "8179b557433835827a70510584f3143e"]
scan_md5s = ['3a552566097a8de588b8184b059b0158',
'52c50ae824e329ba8b5b7a0f523efffe',
'57bb5be0ea44a755ada4a93885c3825e',
'8179b557433835827a70510584f3143e']
for md5 in scan_md5s:
resp = http_client.post('/delete_scan/', {'md5': md5})
if resp.status_code == 200:
dat = json.loads(resp.content.decode("utf-8"))
if dat["deleted"] == "yes":
logger.info("[OK] Deleted Scan: " + md5)
dat = json.loads(resp.content.decode('utf-8'))
if dat['deleted'] == 'yes':
logger.info('[OK] Deleted Scan: %s', md5)
else:
logger.error(err_msg % " Deleting Scan: " + md5)
logger.error('Deleting Scan: %s', md5)
return True
else:
logger.error(err_msg % " Deleting Scan: " + md5)
logger.error('Deleting Scan: %s', md5)
return True
logger.info("Delete Scan Results test completed")
except:
PrintException("Completing Static Analyzer Test")
logger.info('Delete Scan Results test completed')
except Exception:
log_exception('Completing Static Analyzer Test')
return False
def api_test():
"""View for Handling REST API Test"""
logger.info("\nRunning REST API Unit test")
"""View for Handling REST API Test."""
logger.info('\nRunning REST API Unit test')
auth = api_key()
err_msg = '%s'
if platform.system() != "Windows":
err_msg = '\033[91m \033[1m %s \033[0m'
try:
uploaded = []
logger.info("Running Test on Upload API")
logger.info('Running Test on Upload API')
http_client = Client()
apk_dir = os.path.join(settings.BASE_DIR, "StaticAnalyzer/test_files/")
apk_dir = os.path.join(settings.BASE_DIR, 'StaticAnalyzer/test_files/')
for filename in os.listdir(apk_dir):
fpath = os.path.join(apk_dir, filename)
if (platform.system() not in ['Darwin', 'Linux'] and
fpath.endswith(".ipa")):
if (platform.system() not in ['Darwin', 'Linux']
and fpath.endswith('.ipa')):
continue
with open(fpath, "rb") as filp:
with open(fpath, 'rb') as filp:
response = http_client.post(
'/api/v1/upload', {'file': filp}, HTTP_AUTHORIZATION=auth)
obj = json.loads(response.content.decode("utf-8"))
if response.status_code == 200 and "hash" in obj:
logger.info("[OK] Upload OK: " + filename)
obj = json.loads(response.content.decode('utf-8'))
if response.status_code == 200 and 'hash' in obj:
logger.info('[OK] Upload OK: %s', filename)
uploaded.append(obj)
else:
logger.error(err_msg % " Performing Upload" + filename)
logger.error('Performing Upload %s', filename)
return True
logger.info("[OK] Completed Upload API test")
logger.info("Running Static Analysis API Test")
logger.info('[OK] Completed Upload API test')
logger.info('Running Static Analysis API Test')
for upl in uploaded:
resp = http_client.post(
'/api/v1/scan', upl, HTTP_AUTHORIZATION=auth)
if resp.status_code == 200:
logger.info("[OK] Static Analysis Complete: " + upl["file_name"])
logger.info('[OK] Static Analysis Complete: %s',
upl['file_name'])
else:
logger.error(err_msg % " Performing Static Analysis: " + upl["file_name"])
logger.error('Performing Static Analysis: %s',
upl['file_name'])
return True
logger.info("[OK] Static Analysis API test completed")
logger.info('[OK] Static Analysis API test completed')
# Scan List API test
logger.info("Running Scan List API tests")
logger.info('Running Scan List API tests')
resp = http_client.get('/api/v1/scans', HTTP_AUTHORIZATION=auth)
if resp.status_code == 200:
logger.info("Scan List API Test 1 success")
logger.info('Scan List API Test 1 success')
else:
logger.error(err_msg % " Scan List API Test 1")
logger.error('Scan List API Test 1')
return True
resp = http_client.get('/api/v1/scans?page=1&page_size=10', HTTP_AUTHORIZATION=auth)
resp = http_client.get(
'/api/v1/scans?page=1&page_size=10', HTTP_AUTHORIZATION=auth)
if resp.status_code == 200:
logger.info("Scan List API Test 2 success")
logger.info('Scan List API Test 2 success')
else:
logger.error(err_msg % " Scan List API Test 2")
logger.error('Scan List API Test 2')
return True
logger.info("[OK] Scan List API tests completed")
logger.info('[OK] Scan List API tests completed')
# PDF Tests
logger.info("Running PDF Generation API Test")
logger.info('Running PDF Generation API Test')
if platform.system() in ['Darwin', 'Linux']:
pdfs = [
{"hash": "3a552566097a8de588b8184b059b0158", "scan_type": "apk"},
{"hash": "6c23c2970551be15f32bbab0b5db0c71", "scan_type": "ipa"},
{"hash": "52c50ae824e329ba8b5b7a0f523efffe", "scan_type": "andzip"},
{"hash": "57bb5be0ea44a755ada4a93885c3825e", "scan_type": "ioszip"},
{"hash": "8179b557433835827a70510584f3143e", "scan_type": "appx"},
{'hash': '3a552566097a8de588b8184b059b0158',
'scan_type': 'apk'},
{'hash': '6c23c2970551be15f32bbab0b5db0c71',
'scan_type': 'ipa'},
{'hash': '52c50ae824e329ba8b5b7a0f523efffe',
'scan_type': 'andzip'},
{'hash': '57bb5be0ea44a755ada4a93885c3825e',
'scan_type': 'ioszip'},
{'hash': '8179b557433835827a70510584f3143e',
'scan_type': 'appx'},
]
else:
pdfs = [
{"hash": "3a552566097a8de588b8184b059b0158", "scan_type": "apk"},
{"hash": "52c50ae824e329ba8b5b7a0f523efffe", "scan_type": "andzip"},
{"hash": "57bb5be0ea44a755ada4a93885c3825e", "scan_type": "ioszip"},
{"hash": "8179b557433835827a70510584f3143e", "scan_type": "appx"},
{'hash': '3a552566097a8de588b8184b059b0158',
'scan_type': 'apk'},
{'hash': '52c50ae824e329ba8b5b7a0f523efffe',
'scan_type': 'andzip'},
{'hash': '57bb5be0ea44a755ada4a93885c3825e',
'scan_type': 'ioszip'},
{'hash': '8179b557433835827a70510584f3143e',
'scan_type': 'appx'},
]
for pdf in pdfs:
resp = http_client.post(
'/api/v1/download_pdf', pdf, HTTP_AUTHORIZATION=auth)
if (resp.status_code == 200 and
resp._headers['content-type'][1] == "application/pdf"
):
logger.info("[OK] PDF Report Generated: " + pdf["hash"])
if (resp.status_code == 200
and resp._headers['content-type'][1] == 'application/pdf'):
logger.info('[OK] PDF Report Generated: %s', pdf['hash'])
else:
logger.error(err_msg % " Generating PDF: " + pdf["hash"])
logger.error('Generating PDF: %s', pdf['hash'])
logger.info(resp.content)
return True
logger.info("[OK] PDF Generation API test completed")
logger.info("Running JSON Report API test")
logger.info('[OK] PDF Generation API test completed')
logger.info('Running JSON Report API test')
# JSON Report
ctype = 'application/json; charset=utf-8'
for jsn in pdfs:
resp = http_client.post(
'/api/v1/report_json', jsn, HTTP_AUTHORIZATION=auth)
if (resp.status_code == 200) and (resp._headers['content-type'][1] == "application/json; charset=utf-8"):
logger.info("[OK] JSON Report Generated: " + jsn["hash"])
if (resp.status_code == 200
and resp._headers['content-type'][1] == ctype):
logger.info('[OK] JSON Report Generated: %s', jsn['hash'])
else:
logger.error("{} Generating JSON Response: {}".format(err_msg, jsn["hash"]))
logger.error('Generating JSON Response: %s', jsn['hash'])
return True
logger.info("[OK] JSON Report API test completed")
logger.info("Running View Source API test")
logger.info('[OK] JSON Report API test completed')
logger.info('Running View Source API test')
# View Source tests
files = [{"file": "opensecurity/helloworld/MainActivity.java", "type": "apk", "hash": "3a552566097a8de588b8184b059b0158"},
{"file": "helloworld.app/Info.plist", "type": "ipa", "hash": "6c23c2970551be15f32bbab0b5db0c71"},
{"file": "opensecurity/webviewignoressl/MainActivity.java", "type": "studio", "hash": "52c50ae824e329ba8b5b7a0f523efffe"},
{"file": "DamnVulnerableIOSApp/AppDelegate.m", "type": "ios", "hash": "57bb5be0ea44a755ada4a93885c3825e"}]
files = [{'file': 'opensecurity/helloworld/MainActivity.java',
'type': 'apk',
'hash': '3a552566097a8de588b8184b059b0158'},
{'file': 'helloworld.app/Info.plist',
'type': 'ipa',
'hash': '6c23c2970551be15f32bbab0b5db0c71'},
{'file': 'opensecurity/webviewignoressl/MainActivity.java',
'type': 'studio',
'hash': '52c50ae824e329ba8b5b7a0f523efffe'},
{'file': 'DamnVulnerableIOSApp/AppDelegate.m',
'type': 'ios',
'hash': '57bb5be0ea44a755ada4a93885c3825e'}]
for sfile in files:
resp = http_client.post(
'/api/v1/view_source', sfile, HTTP_AUTHORIZATION=auth)
if resp.status_code == 200:
dat = json.loads(resp.content.decode("utf-8"))
if dat["title"]:
logger.info("[OK] Reading - " + sfile["file"])
dat = json.loads(resp.content.decode('utf-8'))
if dat['title']:
logger.info('[OK] Reading - %s', sfile['file'])
else:
logger.error(err_msg % " Reading - " + sfile["file"])
logger.error('Reading - %s', sfile['file'])
return True
else:
logger.error(err_msg % " Reading - " + sfile["file"])
logger.error('Reading - %s', sfile['file'])
return True
logger.info("[OK] View Source API test completed")
logger.info("Running Delete Scan API Results test")
logger.info('[OK] View Source API test completed')
logger.info('Running Delete Scan API Results test')
# Deleting Scan Results
if platform.system() in ['Darwin', 'Linux']:
scan_md5s = ["3a552566097a8de588b8184b059b0158", "6c23c2970551be15f32bbab0b5db0c71",
"52c50ae824e329ba8b5b7a0f523efffe", "57bb5be0ea44a755ada4a93885c3825e",
"8179b557433835827a70510584f3143e"
scan_md5s = ['3a552566097a8de588b8184b059b0158',
'6c23c2970551be15f32bbab0b5db0c71',
'52c50ae824e329ba8b5b7a0f523efffe',
'57bb5be0ea44a755ada4a93885c3825e',
'8179b557433835827a70510584f3143e',
]
else:
scan_md5s = ["3a552566097a8de588b8184b059b0158", "52c50ae824e329ba8b5b7a0f523efffe",
"57bb5be0ea44a755ada4a93885c3825e", "8179b557433835827a70510584f3143e"
scan_md5s = ['3a552566097a8de588b8184b059b0158',
'52c50ae824e329ba8b5b7a0f523efffe',
'57bb5be0ea44a755ada4a93885c3825e',
'8179b557433835827a70510584f3143e',
]
for md5 in scan_md5s:
resp = http_client.post(
'/api/v1/delete_scan', {'hash': md5}, HTTP_AUTHORIZATION=auth)
if resp.status_code == 200:
dat = json.loads(resp.content.decode("utf-8"))
if dat["deleted"] == "yes":
logger.info("[OK] Deleted Scan: " + md5)
dat = json.loads(resp.content.decode('utf-8'))
if dat['deleted'] == 'yes':
logger.info('[OK] Deleted Scan: %s', md5)
else:
logger.error(err_msg % " Deleting Scan: " + md5)
logger.error('Deleting Scan: %s', md5)
return True
else:
logger.error(err_msg % " Deleting Scan: " + md5)
logger.error('Deleting Scan: %s', md5)
return True
logger.info("Delete Scan Results API test completed")
except:
PrintException("Completing REST API Unit Test")
logger.info('Delete Scan Results API test completed')
except Exception:
log_exception('Completing REST API Unit Test')
return False
def start_test(request):
""" Static Analyzer Unit test"""
"""Static Analyzer Unit test."""
item = request.GET.get('module', 'static')
if item == "static":
comp = "static_analyzer"
if item == 'static':
comp = 'static_analyzer'
failed_stat = static_analysis_test()
else:
comp = "static_analyzer_api"
comp = 'static_analyzer_api'
failed_stat = api_test()
try:
if failed_stat:
message = "some tests failed"
message = 'some tests failed'
resp_code = 403
else:
message = "all tests completed"
message = 'all tests completed'
resp_code = 200
except:
except Exception:
resp_code = 403
message = "error"
logger.info("\n\nALL TESTS COMPLETED!")
logger.info("Test Status: " + message)
message = 'error'
logger.info('\n\nALL TESTS COMPLETED!')
logger.info('Test Status: %s', message)
return HttpResponse(json.dumps({comp: message}),
content_type="application/json; charset=utf-8",
content_type='application/json; charset=utf-8',
status=resp_code)
class StaticAnalyzerAndAPI(TestCase):
"""Unit Tests"""
"""Unit Tests."""
def setUp(self):
self.http_client = Client()
+16 -9
View File
@@ -1,17 +1,24 @@
import io
"""
Python Strings.
Strings-module to be undependend from operating systems
Taken from: http://stackoverflow.com/a/17197027
"""
def strings_util(filename, min=6):
"""Print out all connected series of readable chars longer than min."""
with io.open(filename, mode="rb") as f:
result = ""
for c in f.read().decode("utf-8", "ignore"):
if c in '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ!"#$%&\'()*+,-./:;<=>?@[\\]^_`{|}~ ':
def strings_util(filename, minimum=6):
"""Print out all connected series of readable chars longer than minimum."""
with io.open(filename, mode='rb') as f:
result = ''
for c in f.read().decode('utf-8', 'ignore'):
if c in ('0123456789abcdefghijklmnopqrs'
'tuvwxyzABCDEFGHIJKLMNOPQRSTUV'
'WXYZ!"#$%&\'()*+,-./:;<=>?@[\\]^_`{|}~ '):
result += c
continue
if len(result) >= min and result[0].isalnum():
yield "'" + result + "'"
result = ""
if len(result) >= minimum and result[0].isalnum():
yield '\'' + result + '\''
result = ''
+72 -59
View File
@@ -1,5 +1,5 @@
"""
Rule Format
Rule Format.
1. desc - Description of the findings
@@ -11,14 +11,19 @@ Rule Format
a. single_regex - if re.findall(regex1, input)
b .regex_and - if re.findall(regex1, input) and re.findall(regex2, input)
c. regex_or - if re.findall(regex1, input) or re.findall(regex2, input)
d. regex_and_perm - if re.findall(regex, input) and (permission in permission_list_from_manifest)
d. regex_and_perm - if re.findall(regex, input) and (permission in
permission_list_from_manifest)
e. single_string - if string1 in input
f. string_and - if (string1 in input) and (string2 in input)
g. string_or - if (string1 in input) or (string2 in input)
h. string_and_or - if (string1 in input) and ((string2 in input) or (string3 in input))
i. string_or_and - if (string1 in input) or ((string2 in input) and (string3 in input))
j. string_and_perm - if (string1 in input) and (permission in permission_list_from_manifest)
k. string_or_and_perm - if ((string1 in input) or (string2 in input)) and (permission in permission_list_from_manifest)
h. string_and_or - if (string1 in input) and ((string2 in input)
or (string3 in input))
i. string_or_and - if (string1 in input) or ((string2 in input)
and (string3 in input))
j. string_and_perm - if (string1 in input) and
(permission in permission_list_from_manifest)
k. string_or_and_perm - if ((string1 in input) or (string2 in input))
and (permission in permission_list_from_manifest)
4. input_case
a. upper
@@ -37,77 +42,85 @@ APIS = [
'type': 'regex',
'match': 'single_regex',
'regex1': r'System\.loadLibrary\(|System\.load\(',
'input_case': 'exact'
'input_case': 'exact',
},
{
'desc': 'Get System Service',
'type': 'regex',
'match': 'single_regex',
'regex1': r'getSystemService',
'input_case': 'exact'
'input_case': 'exact',
},
{
'desc': 'Dynamic Class and Dexloading',
'type': 'regex',
'match': 'single_regex',
'regex1': r'dalvik\.system\.DexClassLoader|java\.security\.ClassLoader|java\.net\.URLClassLoader|java\.security\.SecureClassLoader',
'input_case': 'exact'
'regex1': (r'dalvik\.system\.DexClassLoader|'
r'java\.security\.ClassLoader|'
r'java\.net\.URLClassLoader|'
r'java\.security\.SecureClassLoader'),
'input_case': 'exact',
},
{
'desc': 'Java Reflection',
'type': 'regex',
'match': 'single_regex',
'regex1': r'java\.lang\.reflect\.Method|java\.lang\.reflect\.Field|Class\.forName',
'input_case': 'exact'
'regex1': (r'java\.lang\.reflect\.Method|'
r'java\.lang\.reflect\.Field|Class\.forName'),
'input_case': 'exact',
},
{
'desc': 'Crypto',
'type': 'regex',
'match': 'single_regex',
'regex1': r'javax\.crypto|kalium\.crypto|bouncycastle\.crypto',
'input_case': 'exact'
'input_case': 'exact',
},
{
'desc': 'Starting Activity',
'type': 'regex',
'match': 'single_regex',
'regex1': r'startActivity\(|startActivityForResult\(',
'input_case': 'exact'
'input_case': 'exact',
},
{
'desc': 'Starting Service',
'type': 'regex',
'match': 'single_regex',
'regex1': r'startService\(|bindService\(',
'input_case': 'exact'
'input_case': 'exact',
},
{
'desc': 'Sending Broadcast',
'type': 'regex',
'match': 'single_regex',
'regex1': r'sendBroadcast\(|sendOrderedBroadcast\(|sendStickyBroadcast\(',
'input_case': 'exact'
'regex1': (r'sendBroadcast\(|'
r'sendOrderedBroadcast\(|sendStickyBroadcast\('),
'input_case': 'exact',
},
{
'desc': 'Local File I/O Operations',
'type': 'regex',
'match': 'single_regex',
'regex1': r'OpenFileOutput|getSharedPreferences|SharedPreferences\.Editor|getCacheDir|getExternalStorageState|openOrCreateDatabase',
'input_case': 'exact'
'regex1': (r'OpenFileOutput|getSharedPreferences|'
r'SharedPreferences\.Editor|getCacheDir|'
r'getExternalStorageState|openOrCreateDatabase'),
'input_case': 'exact',
},
{
'desc': 'Inter Process Communication',
'type': 'regex',
'match': 'single_regex',
'regex1': r'IRemoteService|IRemoteService\.Stub|IBinder|Intent',
'input_case': 'exact'
'input_case': 'exact',
},
{
'desc': 'HTTP Requests, Connections and Sessions',
'type': 'regex',
'match': 'single_regex',
'regex1': r'http\.client\.HttpClient|net\.http\.AndroidHttpClient|http\.impl\.client\.AbstractHttpClient',
'input_case': 'exact'
'regex1': (r'http\.client\.HttpClient|net\.http\.AndroidHttpClient|'
r'http\.impl\.client\.AbstractHttpClient'),
'input_case': 'exact',
},
{
'desc': 'HTTP Connection',
@@ -115,22 +128,23 @@ APIS = [
'match': 'regex_and',
'regex1': r'HttpURLConnection|org\.apache\.http',
'regex2': r'openConnection|connect|HttpRequest',
'input_case': 'exact'
'input_case': 'exact',
},
{
'desc': 'Load and Manipulate Dex Files',
'type': 'regex',
'match': 'regex_and',
'regex1': r'dalvik\.system\.PathClassLoader|dalvik\.system\.DexFile|dalvik\.system\.DexPathList',
'regex1': (r'dalvik\.system\.PathClassLoader|'
r'dalvik\.system\.DexFile|dalvik\.system\.DexPathList'),
'regex2': r'loadDex|loadClass|DexClassLoader|loadDexFile',
'input_case': 'exact'
'input_case': 'exact',
},
{
'desc': 'Content Provider',
'type': 'string',
'match': 'single_string',
'string1': 'android.content.ContentProvider',
'input_case': 'exact'
'input_case': 'exact',
},
{
'desc': 'Obfuscation',
@@ -138,7 +152,7 @@ APIS = [
'match': 'string_and',
'string1': 'utils.AESObfuscator',
'string2': 'getObfuscator',
'input_case': 'exact'
'input_case': 'exact',
},
{
'desc': 'Execute OS Command',
@@ -146,7 +160,7 @@ APIS = [
'match': 'string_and',
'string1': 'getRuntime().exec(',
'string2': 'getRuntime(',
'input_case': 'exact'
'input_case': 'exact',
},
{
'desc': 'Android Keystore',
@@ -154,16 +168,15 @@ APIS = [
'match': 'string_and',
'string1': 'security.KeyStore',
'string2': 'Keystore.getInstance(',
'input_case': 'exact'
'input_case': 'exact',
},
{
'desc': 'TCP Server Socket',
'type': 'string',
'match': 'string_and',
'string1': 'ServerSocket',
'string2': 'net.ServerSocket',
'input_case': 'exact'
'input_case': 'exact',
},
{
'desc': 'TCP Socket',
@@ -171,7 +184,7 @@ APIS = [
'match': 'string_and',
'string1': 'Socket',
'string2': 'net.Socket',
'input_case': 'exact'
'input_case': 'exact',
},
{
'desc': 'UDP Datagram Packet',
@@ -179,7 +192,7 @@ APIS = [
'match': 'string_and',
'string1': 'DatagramPacket',
'string2': 'net.DatagramPacket',
'input_case': 'exact'
'input_case': 'exact',
},
{
'desc': 'UDP Datagram Socket',
@@ -187,7 +200,7 @@ APIS = [
'match': 'string_and',
'string1': 'DatagramSocket',
'string2': 'net.DatagramSocket',
'input_case': 'exact'
'input_case': 'exact',
},
{
'desc': 'WebView JavaScript Interface',
@@ -196,7 +209,7 @@ APIS = [
'string1': 'addJavascriptInterface',
'string2': 'WebView',
'string3': 'android.webkit',
'input_case': 'exact'
'input_case': 'exact',
},
{
'desc': 'WebView GET Request',
@@ -205,7 +218,7 @@ APIS = [
'string1': 'WebView',
'string2': 'loadData',
'string3': 'android.webkit',
'input_case': 'exact'
'input_case': 'exact',
},
{
'desc': 'WebView POST Request',
@@ -214,7 +227,7 @@ APIS = [
'string1': 'WebView',
'string2': 'postUrl',
'string3': 'android.webkit',
'input_case': 'exact'
'input_case': 'exact',
},
{
'desc': 'Android Notifications',
@@ -222,7 +235,7 @@ APIS = [
'match': 'string_and',
'string1': 'app.NotificationManager',
'string2': 'notify',
'input_case': 'exact'
'input_case': 'exact',
},
{
'desc': 'Get Cell Information',
@@ -230,7 +243,7 @@ APIS = [
'match': 'string_and',
'string1': 'telephony.TelephonyManager',
'string2': 'getAllCellInfo',
'input_case': 'exact'
'input_case': 'exact',
},
{
'desc': 'Get Cell Location',
@@ -238,7 +251,7 @@ APIS = [
'match': 'string_and',
'string1': 'telephony.TelephonyManager',
'string2': 'getCellLocation',
'input_case': 'exact'
'input_case': 'exact',
},
{
'desc': 'Get Subscriber ID',
@@ -246,7 +259,7 @@ APIS = [
'match': 'string_and',
'string1': 'telephony.TelephonyManager',
'string2': 'getSubscriberId',
'input_case': 'exact'
'input_case': 'exact',
},
{
'desc': 'Get Device ID, IMEI,MEID/ESN etc.',
@@ -254,7 +267,7 @@ APIS = [
'match': 'string_and',
'string1': 'telephony.TelephonyManager',
'string2': 'getDeviceId',
'input_case': 'exact'
'input_case': 'exact',
},
{
'desc': 'Get Software Version, IMEI/SV etc.',
@@ -262,7 +275,7 @@ APIS = [
'match': 'string_and',
'string1': 'telephony.TelephonyManager',
'string2': 'getDeviceSoftwareVersion',
'input_case': 'exact'
'input_case': 'exact',
},
{
'desc': 'Get SIM Serial Number',
@@ -270,7 +283,7 @@ APIS = [
'match': 'string_and',
'string1': 'telephony.TelephonyManager',
'string2': 'getSimSerialNumber',
'input_case': 'exact'
'input_case': 'exact',
},
{
'desc': 'Get SIM Provider Details',
@@ -278,7 +291,7 @@ APIS = [
'match': 'string_and',
'string1': 'telephony.TelephonyManager',
'string2': 'getSimOperator',
'input_case': 'exact'
'input_case': 'exact',
},
{
'desc': 'Get SIM Operator Name',
@@ -286,7 +299,7 @@ APIS = [
'match': 'string_and',
'string1': 'telephony.TelephonyManager',
'string2': 'getSimOperatorName',
'input_case': 'exact'
'input_case': 'exact',
},
{
'desc': 'Query Database of SMS, Contacts etc.',
@@ -294,7 +307,7 @@ APIS = [
'match': 'string_and',
'string1': 'content.ContentResolver',
'string2': 'query',
'input_case': 'exact'
'input_case': 'exact',
},
{
'desc': 'Query Database of SMS, Contacts etc.',
@@ -302,7 +315,7 @@ APIS = [
'match': 'string_and',
'string1': 'content.ContentResolver',
'string2': 'query',
'input_case': 'exact'
'input_case': 'exact',
},
{
'desc': 'Base64 Decode',
@@ -310,7 +323,7 @@ APIS = [
'match': 'string_and',
'string1': 'android.util.Base64',
'string2': '.decode',
'input_case': 'exact'
'input_case': 'exact',
},
{
'desc': 'Send SMS',
@@ -320,7 +333,7 @@ APIS = [
'string_or1': 'sendMultipartTextMessage',
'string_or2': 'sendTextMessage',
'string_or3': 'vnd.android-dir/mms-sms',
'input_case': 'exact'
'input_case': 'exact',
},
{
'desc': 'URL Connection to file/http/https/ftp/jar',
@@ -330,7 +343,7 @@ APIS = [
'string_or1': 'openConnection',
'string_or2': 'connect',
'string_or3': 'openStream',
'input_case': 'exact'
'input_case': 'exact',
},
{
'desc': 'JAR URL Connection',
@@ -339,7 +352,7 @@ APIS = [
'string1': 'net.JarURLConnection',
'string_or1': 'JarURLConnection',
'string_or2': 'jar:',
'input_case': 'exact'
'input_case': 'exact',
},
{
'desc': 'HTTPS Connection',
@@ -348,7 +361,7 @@ APIS = [
'string1': 'javax.net.ssl.HttpsURLConnection',
'string_or1': 'HttpsURLConnection',
'string_or2': 'connect',
'input_case': 'exact'
'input_case': 'exact',
},
{
'desc': 'URL Connection supports file,http,https,ftp and jar',
@@ -357,7 +370,7 @@ APIS = [
'string1': 'net.URL',
'string_or1': 'openConnection',
'string_or2': 'openStream',
'input_case': 'exact'
'input_case': 'exact',
},
{
'desc': 'Set or Read Clipboard data',
@@ -366,7 +379,7 @@ APIS = [
'string1': 'content.ClipboardManager',
'string_or1': 'CLIPBOARD_SERVICE',
'string_or2': 'ClipboardManager',
'input_case': 'exact'
'input_case': 'exact',
},
{
'desc': 'Base64 Encode',
@@ -375,7 +388,7 @@ APIS = [
'string1': 'android.util.Base64',
'string_or1': '.encodeToString',
'string_or2': '.encode',
'input_case': 'exact'
'input_case': 'exact',
},
{
'desc': 'Base64 Encode',
@@ -384,7 +397,7 @@ APIS = [
'string1': 'android.util.Base64',
'string_or1': '.encodeToString',
'string_or2': '.encode',
'input_case': 'exact'
'input_case': 'exact',
},
{
'desc': 'Message Digest',
@@ -393,7 +406,7 @@ APIS = [
'string1': 'java.security.MessageDigest',
'string_or1': 'MessageDigestSpi',
'string_or2': 'MessageDigest',
'input_case': 'exact'
'input_case': 'exact',
},
{
'desc': 'GPS Location',
@@ -404,6 +417,6 @@ APIS = [
'string_or2': 'requestLocationUpdates(',
'string_or3': 'getLatitude(',
'string_or4': 'getLongitude(',
'input_case': 'exact'
'input_case': 'exact',
},
]
@@ -1,272 +1,705 @@
MANIFEST_DESC = {
'a_debuggable': {
'title': 'Debug Enabled For App <br>[android:debuggable=true]',
'level': 'high',
'description': '''Debugging was enabled on the app which makes it easier for reverse engineers to hook a debugger to it. This allows dumping a stack trace and accessing debugging helper classes.''',
'name': 'Debug Enabled For App [android:debuggable=true]'
},
'a_allowbackup': {
'title': 'Application Data can be Backed up<br>[android:allowBackup=true]',
'level': 'medium',
'description': '''This flag allows anyone to backup your application data via adb. It allows users who have enabled USB debugging to copy application data off of the device.''',
'name': 'Application Data can be Backed up [android:allowBackup=true]'
},
'a_allowbackup_miss': {
'title': 'Application Data can be Backed up<br>[android:allowBackup] flag is missing.',
'level': 'medium',
'description': 'The flag [android:allowBackup] should be set to false. By default it is set to true and allows anyone to backup your application data via adb. It allows users who have enabled USB debugging to copy application data off of the device.''',
'name': 'Application Data can be Backed up [android:allowBackup] flag is missing.'
},
'a_testonly': {
'title': 'Application is in Test Mode <br>[android:testOnly=true]',
'level': 'high',
'description': ' It may expose functionality or data outside of itself that would cause a security hole.',
'name': 'Application is in Test Mode [android:testOnly=true]',
},
'a_taskaffinity': {
'title': 'TaskAffinity is set for Activity </br>(%s)',
'level': 'high',
'description': 'If taskAffinity is set, then other application could read the Intents sent to Activities belonging to another task. Always use the default setting keeping the affinity as the package name in order to prevent sensitive information inside sent or received Intents from being read by another application.',
'name': 'TaskAffinity is set for Activity'
},
'a_launchmode': {
'title': 'Launch Mode of Activity (%s) is not standard.',
'level': 'high',
'description': 'An Activity should not be having the launch mode attribute set to "singleTask/singleInstance" as it becomes root Activity and it is possible for other applications to read the contents of the calling Intent. So it is required to use the "standard" launch mode attribute when sensitive information is included in an Intent.',
'name': 'Launch Mode of Activity is not standard.'
},
'a_prot_sign': {
'title': '<strong>%s</strong> (%s) is Protected by a permission.</br>%s<br>[android:exported=true]',
'level': 'info',
'description': 'A%s %s is found to be exported, but is protected by permission.',
'name': 'is Protected by a permission.[android:exported=true]'
},
'a_prot_normal': {
'title': '<strong>%s</strong> (%s) is Protected by a permission, but the protection level of the permission should be checked.</br>%s <br>[android:exported=true]',
'level': 'high',
'description': 'A%s %s is found to be shared with other apps on the device therefore leaving it accessible to any other application on the device. It is protected by a permission. However, the protection level of the permission is set to normal. This means that a malicious application can request and obtain the permission and interact with the component. If it was set to signature, only applications signed with the same certificate could obtain the permission.',
'name': 'is Protected by a permission, but the protection level of the permission should be checked.[android:exported=true]'
},
'a_prot_danger': {
'title': '<strong>%s</strong> (%s) is Protected by a permission, but the protection level of the permission should be checked.</br>%s <br>[android:exported=true]',
'level': 'high',
'description': 'A%s %s is found to be shared with other apps on the device therefore leaving it accessible to any other application on the device. It is protected by a permission. However, the protection level of the permission is set to dangerous. This means that a malicious application can request and obtain the permission and interact with the component. If it was set to signature, only applications signed with the same certificate could obtain the permission.',
'name': 'is Protected by a permission, but the protection level of the permission should be checked.[android:exported=true]'
},
'a_prot_sign_sys': {
'title': '<strong>%s</strong> (%s) is Protected by a permission, but the protection level of the permission should be checked.</br>%s <br>[android:exported=true]',
'level': 'info',
'description': 'A%s %s is found to be exported, but is protected by a permission. However, the protection level of the permission is set to signatureOrSystem. It is recommended that signature level is used instead. Signature level should suffice for most purposes, and does not depend on where the applications are installed on the device.',
'name': 'is Protected by a permission, but the protection level of the permission should be checked.[android:exported=true]'
},
'a_prot_unknown': {
'title': '<strong>%s</strong> (%s) is Protected by a permission, but the protection level of the permission should be checked.</br>%s <br>[android:exported=true]',
'level': 'high',
'description': 'A%s %s is found to be shared with other apps on the device therefore leaving it accessible to any other application on the device. It is protected by a permission which is not defined in the analysed application. As a result, the protection level of the permission should be checked where it is defined. If it is set to normal or dangerous, a malicious application can request and obtain the permission and interact with the component. If it is set to signature, only applications signed with the same certificate can obtain the permission.',
'name': 'is Protected by a permission, but the protection level of the permission should be checked.[android:exported=true]'
},
'a_prot_normal_appl': {
'title': '<strong>%s</strong> (%s) is Protected by a permission at the application level, but the protection level of the permission should be checked.</br>%s <br>[android:exported=true]',
'level': 'high',
'description': 'A%s %s is found to be shared with other apps on the device therefore leaving it accessible to any other application on the device. It is protected by a permission at the application level. However, the protection level of the permission is set to normal. This means that a malicious application can request and obtain the permission and interact with the component. If it was set to signature, only applications signed with the same certificate could obtain the permission.',
'name': 'is Protected by a permission at the application level, but the protection level of the permission should be checked.[android:exported=true]'
},
'a_prot_danger_appl': {
'title': '<strong>%s</strong> (%s) is Protected by a permission at the application level, but the protection level of the permission should be checked.</br>%s <br>[android:exported=true]',
'level': 'high',
'description': 'A%s %s is found to be shared with other apps on the device therefore leaving it accessible to any other application on the device. It is protected by a permission at the application level. However, the protection level of the permission is set to dangerous. This means that a malicious application can request and obtain the permission and interact with the component. If it was set to signature, only applications signed with the same certificate could obtain the permission.',
'name': 'is Protected by a permission at the application level, but the protection level of the permission should be checked.[android:exported=true]'
},
'a_prot_sign_appl': {
'title': '<strong>%s</strong> (%s) Protected by a permission at the application level.</br>%s<br>[android:exported=true]',
'level': 'info',
'description': 'A%s %s is found to be exported, but is protected by a permission at the application level.',
'name': ' Protected by a permission at the application level.[android:exported=true]'
},
'a_prot_sign_sys_appl': {
'title': '<strong>%s</strong> (%s) is Protected by a permission at the application level, but the protection level of the permission should be checked.</br>%s <br>[android:exported=true]',
'level': 'info',
'description': 'A%s %s is found to be exported, but is protected by a permission at the application level. However, the protection level of the permission is set to signatureOrSystem. It is recommended that signature level is used instead. Signature level should suffice for most purposes, and does not depend on where the applications are installed on the device.',
'name': 'is Protected by a permission at the application level, but the protection level of the permission should be checked.[android:exported=true]'
},
'a_prot_unknown_appl': {
'title': '<strong>%s</strong> (%s) is Protected by a permission at the application, but the protection level of the permission should be checked.</br>%s <br>[android:exported=true]',
'level': 'high',
'description': 'A%s %s is found to be shared with other apps on the device therefore leaving it accessible to any other application on the device. It is protected by a permission at the application level which is not defined in the analysed application. As a result, the protection level of the permission should be checked where it is defined. If it is set to normal or dangerous, a malicious application can request and obtain the permission and interact with the component. If it is set to signature, only applications signed with the same certificate can obtain the permission.',
'name': 'is Protected by a permission at the application, but the protection level of the permission should be checked.[android:exported=true]'
},
'a_not_protected': {
'title': '<strong>%s</strong> (%s) is not Protected. <br>[android:exported=true]',
'level': 'high',
'description': 'A%s %s is found to be shared with other apps on the device therefore leaving it accessible to any other application on the device.',
'name': 'is not Protected. [android:exported=true]'
},
'a_not_protected_filter': {
'title': '<strong>%s</strong> (%s) is not Protected.<br>An intent-filter exists.',
'level': 'high',
'description': 'A%s %s is found to be shared with other apps on the device therefore leaving it accessible to any other application on the device. The presence of intent-filter indicates that the %s is explicitly exported.',
'name': 'is not Protected.An intent-filter exists.'
},
'c_not_protected': {
'title': '<strong>%s</strong> (%s) is not Protected. <br>[[Content Provider, targetSdkVersion < 17]',
'level': 'high',
'description': 'A%s %s is found to be shared with other apps on the device therefore leaving it accessible to any other application on the device. It is a Content Provider that targets an API level under 17, which makes it exported by default, regardless of the API level of the system that the application runs on.',
'name': 'is not Protected.[[Content Provider, targetSdkVersion < 17]'
},
'c_not_protected2': {
'title': '<strong>%s</strong> (%s) would not be Protected if the application ran on a device where the the API level was less than 17. <br>[Content Provider, targetSdkVersion >= 17]',
'level': 'high',
'description': 'The Content Provider(%s %s) would be exported if the application ran on a device where the the API level was less than 17. In that situation, it would be shared with other apps on the device therefore leaving it accessible to any other application on the device.',
'name': 'would not be Protected if the application ran on a device where the the API level was less than 17.[Content Provider, targetSdkVersion >= 17]'
},
'c_prot_normal': {
'title': '<strong>%s</strong> (%s) is Protected by a permission, but the protection level of the permission should be checked.</br>%s <br>[Content Provider, targetSdkVersion < 17]',
'level': 'high',
'description': 'A%s %s is found to be shared with other apps on the device therefore leaving it accessible to any other application on the device. It is protected by a permission. However, the protection level of the permission is set to normal. This means that a malicious application can request and obtain the permission and interact with the component. If it was set to signature, only applications signed with the same certificate could obtain the permission.',
'name': 'is Protected by a permission, but the protection level of the permission should be checked.[Content Provider, targetSdkVersion < 17]'
},
'c_prot_danger': {
'title': '<strong>%s</strong> (%s) is Protected by a permission, but the protection level of the permission should be checked.</br>%s <br>[Content Provider, targetSdkVersion < 17]',
'level': 'high',
'description': 'A%s %s is found to be shared with other apps on the device therefore leaving it accessible to any other application on the device. It is protected by a permission. However, the protection level of the permission is set to dangerous. This means that a malicious application can request and obtain the permission and interact with the component. If it was set to signature, only applications signed with the same certificate could obtain the permission.',
'name': 'is Protected by a permission, but the protection level of the permission should be checked.[Content Provider, targetSdkVersion < 17]'
},
'c_prot_sign': {
'title': '<strong>%s</strong> (%s) is Protected by a permission.</br>%s <br>[Content Provider, targetSdkVersion < 17]',
'level': 'info',
'description': 'A%s %s is found to be shared with other apps on the device therefore leaving it accessible to any other application on the device. It is protected by permission.',
'name': 'is Protected by a permission.[Content Provider, targetSdkVersion < 17]'
},
'c_prot_sign_sys': {
'title': '<strong>%s</strong> (%s) is Protected by a permission, but the protection level of the permission should be checked.</br>%s <br>[Content Provider, targetSdkVersion < 17]',
'level': 'info',
'description': 'A%s %s is found to be exported, but is protected by a permission. However, the protection level of the permission is set to signatureOrSystem. It is recommended that signature level is used instead. Signature level should suffice for most purposes, and does not depend on where the applications are installed on the device.',
'name': 'is Protected by a permission, but the protection level of the permission should be checked.[Content Provider, targetSdkVersion < 17]'
},
'c_prot_unknown': {
'title': '<strong>%s</strong> (%s) is Protected by a permission, but the protection level of the permission should be checked.</br>%s <br>[Content Provider, targetSdkVersion < 17]',
'level': 'high',
'description': 'A%s %s is found to be shared with other apps on the device therefore leaving it accessible to any other application on the device. It is protected by a permission which is not defined in the analysed application. As a result, the protection level of the permission should be checked where it is defined. If it is set to normal or dangerous, a malicious application can request and obtain the permission and interact with the component. If it is set to signature, only applications signed with the same certificate can obtain the permission.',
'name': 'is Protected by a permission, but the protection level of the permission should be checked.[Content Provider, targetSdkVersion < 17]'
},
'c_prot_normal_appl': {
'title': '<strong>%s</strong> (%s) is Protected by a permission at the application level, but the protection level of the permission should be checked.</br>%s <br>[Content Provider, targetSdkVersion < 17]',
'level': 'high',
'description': 'A%s %s is found to be shared with other apps on the device therefore leaving it accessible to any other application on the device. It is protected by a permission at the application level. However, the protection level of the permission is set to normal. This means that a malicious application can request and obtain the permission and interact with the component. If it was set to signature, only applications signed with the same certificate could obtain the permission.',
'name': 'is Protected by a permission at the application level, but the protection level of the permission should be checked.[Content Provider, targetSdkVersion < 17]'
},
'c_prot_danger_appl': {
'title': '<strong>%s</strong> (%s) is Protected by a permission at the application level, but the protection level of the permission should be checked.</br>%s <br>[Content Provider, targetSdkVersion < 17]',
'level': 'high',
'description': 'A%s %s is found to be shared with other apps on the device therefore leaving it accessible to any other application on the device. It is protected by a permission at the application level. However, the protection level of the permission is set to dangerous. This means that a malicious application can request and obtain the permission and interact with the component. If it was set to signature, only applications signed with the same certificate could obtain the permission.',
'name': 'is Protected by a permission at the application level, but the protection level of the permission should be checked.[Content Provider, targetSdkVersion < 17]'
},
'c_prot_sign_appl': {
'title': '<strong>%s</strong> (%s) is Protected by a permission at the application level.</br>%s <br>[Content Provider, targetSdkVersion < 17]',
'level': 'info',
'description': 'A%s %s is found to be shared with other apps on the device therefore leaving it accessible to any other application on the device. It is protected by permission at the application level.',
'name': 'is Protected by a permission at the application level.[Content Provider, targetSdkVersion < 17]'
},
'c_prot_sign_sys_appl': {
'title': '<strong>%s</strong> (%s) is Protected by a permission at the application level, but the protection level of the permission should be checked.</br>%s <br>[Content Provider, targetSdkVersion < 17]',
'level': 'info',
'description': 'A%s %s is found to be exported, but is protected by a permission at the application level. However, the protection level of the permission is set to signatureOrSystem. It is recommended that signature level is used instead. Signature level should suffice for most purposes, and does not depend on where the applications are installed on the device.',
'name': 'is Protected by a permission at the application level, but the protection level of the permission should be checked.[Content Provider, targetSdkVersion < 17]'
},
'c_prot_unknown_appl': {
'title': '<strong>%s</strong> (%s) is Protected by a permission at application level, but the protection level of the permission should be checked.</br>%s <br>[Content Provider, targetSdkVersion < 17]',
'level': 'high',
'description': 'A%s %s is found to be shared with other apps on the device therefore leaving it accessible to any other application on the device. It is protected by a permission at application level which is not defined in the analysed application. As a result, the protection level of the permission should be checked where it is defined. If it is set to normal or dangerous, a malicious application can request and obtain the permission and interact with the component. If it is set to signature, only applications signed with the same certificate can obtain the permission.',
'name': 'is Protected by a permission at application level, but the protection level of the permission should be checked.[Content Provider, targetSdkVersion < 17]'
},
'c_prot_normal_new': {
'title': '<strong>%s</strong> (%s) is Protected by a permission, but the protection level of the permission should be checked if the application runs on a device where the the API level is less than 17</br>%s <br>[Content Provider, targetSdkVersion >= 17]',
'level': 'high',
'description': 'The Content Provider (%s) would be exported if the application ran on a device where the the API level was less than 17. In that situation, it would still be protected by a permission. However, the protection level of the permission is set to normal. This means that a malicious application could request and obtain the permission and interact with the component. If it was set to signature, only applications signed with the same certificate could obtain the permission.',
'name': 'is Protected by a permission, but the protection level of the permission should be checked if the application runs on a device where the the API level is less than 17 [Content Provider, targetSdkVersion >= 17]'
},
'c_prot_danger_new': {
'title': '<strong>%s</strong> (%s) is Protected by a permission, but the protection level of the permission should be checked if the application runs on a device where the the API level is less than 17.</br>%s <br>[Content Provider, targetSdkVersion >= 17]',
'level': 'high',
'description': 'The Content Provider(%s) would be exported if the application ran on a device where the the API level was less than 17. In that situation, it would still be protected by a permission. However, the protection level of the permission is set to dangerous. This means that a malicious application could request and obtain the permission and interact with the component. If it was set to signature, only applications signed with the same certificate could obtain the permission.',
'name': 'is Protected by a permission, but the protection level of the permission should be checked if the application runs on a device where the the API level is less than 17.[Content Provider, targetSdkVersion >= 17]'
},
'c_prot_sign_new': {
'title': '<strong>%s</strong> (%s) is Protected by a permission.</br>%s <br>[Content Provider, targetSdkVersion >= 17]',
'level': 'info',
'description': 'The Content Provider(%s) would be exported if the application ran on a device where the the API level was less than 17. Nevertheless, it is protected by a permission.',
'name': 'is Protected by a permission.[Content Provider, targetSdkVersion >= 17]'
},
'c_prot_sign_sys_new': {
'title': '<strong>%s</strong> (%s) is Protected by a permission, but the protection level of the permission should be checked.</br>%s <br>[Content Provider, targetSdkVersion >= 17]',
'level': 'info',
'description': 'The Content Provider(%s) would be exported if the application ran on a device where the API level was less than 17. In that situation, it would still be protected by a permission. However, the protection level of the permission is set to signatureOrSystem. It is recommended that signature level is used instead. Signature level should suffice for most purposes, and does not depend on where the applications are installed on the device.',
'name': 'is Protected by a permission, but the protection level of the permission should be checked.[Content Provider, targetSdkVersion >= 17]'
},
'c_prot_unknown_new': {
'title': '<strong>%s</strong> (%s) is Protected by a permission, but the protection level of the permission should be checked if the application runs on a device where the the API level is less than 17.</br>%s <br>[Content Provider, targetSdkVersion >= 17]',
'level': 'high',
'description': 'The Content Provider(%s) would be exported if the application ran on a device where the the API level was less than 17. In that situation, it would still be protected by a permission which is not defined in the analysed application. As a result, the protection level of the permission should be checked where it is defined. If it is set to normal or dangerous, a malicious application can request and obtain the permission and interact with the component. If it is set to signature, only applications signed with the same certificate can obtain the permission.',
'name': 'is Protected by a permission, but the protection level of the permission should be checked if the application runs on a device where the the API level is less than 17.[Content Provider, targetSdkVersion >= 17]'
},
'c_prot_normal_new_appl': {
'title': '<strong>%s</strong> (%s) is Protected by a permission at the application level, but the protection level of the permission should be checked if the application runs on a device where the the API level is less than 17</br>%s <br>[Content Provider, targetSdkVersion >= 17]',
'level': 'high',
'description': 'The Content Provider (%s) would be exported if the application ran on a device where the the API level was less than 17. In that situation, it would still be protected by a permission. However, the protection level of the permission is set to normal. This means that a malicious application could request and obtain the permission and interact with the component. If it was set to signature, only applications signed with the same certificate could obtain the permission.',
'name': 'is Protected by a permission at the application level, but the protection level of the permission should be checked if the application runs on a device where the the API level is less than 17.[Content Provider, targetSdkVersion >= 17]'
},
'c_prot_danger_new_appl': {
'title': '<strong>%s</strong> (%s) is Protected by a permission at the application level, but the protection level of the permission should be checked if the application runs on a device where the the API level is less than 17.</br>%s <br>[Content Provider, targetSdkVersion >= 17]',
'level': 'high',
'description': 'The Content Provider(%s) would be exported if the application ran on a device where the the API level was less than 17. In that situation, it would still be protected by a permission. However, the protection level of the permission is set to dangerous. This means that a malicious application could request and obtain the permission and interact with the component. If it was set to signature, only applications signed with the same certificate could obtain the permission.',
'name': 'is Protected by a permission at the application level, but the protection level of the permission should be checked if the application runs on a device where the the API level is less than 17.[Content Provider, targetSdkVersion >= 17]'
},
'c_prot_sign_new_appl': {
'title': '<strong>%s</strong> (%s) is Protected by a permission at the application level.</br>%s<br>[Content Provider, targetSdkVersion >= 17]',
'level': 'info',
'description': 'The Content Provider(%s) would be exported if the application ran on a device where the the API level was less than 17. Nevertheless, it is protected by a permission.',
'name': 'is Protected by a permission at the application level.[Content Provider, targetSdkVersion >= 17]'
},
'c_prot_sign_sys_new_appl': {
'title': '<strong>%s</strong> (%s) is Protected by a permission at the application level, but the protection level of the permission should be checked.</br>%s <br>[Content Provider, targetSdkVersion >= 17]',
'level': 'info',
'description': 'The Content Provider(%s) would be exported if the application ran on a device where the API level was less than 17. In that situation, it would still be protected by a permission. However, the protection level of the permission is set to signatureOrSystem. It is recommended that signature level is used instead. Signature level should suffice for most purposes, and does not depend on where the applications are installed on the device.',
'name': 'is Protected by a permission at the application level, but the protection level of the permission should be checked.[Content Provider, targetSdkVersion >= 17]'
},
'c_prot_unknown_new_appl': {
'title': '<strong>%s</strong> (%s) is Protected by a permission at the application level, but the protection level of the permission should be checked if the application runs on a device where the the API level is less than 17.</br>%s <br>[Content Provider, targetSdkVersion >= 17]',
'level': 'high',
'description': 'The Content Provider(%s) would be exported if the application ran on a device where the the API level was less than 17. In that situation, it would still be protected by a permission which is not defined in the analysed application. As a result, the protection level of the permission should be checked where it is defined. If it is set to normal or dangerous, a malicious application can request and obtain the permission and interact with the component. If it is set to signature, only applications signed with the same certificate can obtain the permission.',
'name': 'is Protected by a permission at the application level, but the protection level of the permission should be checked if the application runs on a device where the the API level is less than 17.[Content Provider, targetSdkVersion >= 17]'
},
'a_improper_provider': {
'title': 'Improper Content Provider Permissions<br>[%s]',
'level': 'high',
'description': 'A content provider permission was set to allows access from any other app on the device. Content providers may contain sensitive information about an app and therefore should not be shared.',
'name': 'Improper Content Provider Permissions'
},
'a_dailer_code': {
'title': 'Dailer Code: %s Found <br>[android:scheme="android_secret_code"]',
'level': 'high',
'description': 'A secret code was found in the manifest. These codes, when entered into the dialer grant access to hidden content that may contain sensitive information.',
'name': 'Dailer Code: Found <br>[android:scheme="android_secret_code"]'
},
'a_sms_receiver_port': {
'title': 'Data SMS Receiver Set on Port: %s Found<br>[android:port]',
'level': 'high',
'description': 'A binary SMS recevier is configured to listen on a port. Binary SMS messages sent to a device are processed by the application in whichever way the developer choses. The data in this SMS should be properly validated by the application. Furthermore, the application should assume that the SMS being received is from an untrusted source."',
'name': 'Data SMS Receiver Set on Port: Found<br>[android:port]'
},
'a_high_intent_priority': {
'title': 'High Intent Priority (%s)<br>[android:priority]',
'level': 'medium',
'description': 'By setting an intent priority higher than another intent, the app effectively overrides other requests.',
'name': 'High Intent Priority [android:priority]'
},
'a_high_action_priority': {
'title': 'High Action Priority (%s)<br>[android:priority] ',
'level': 'medium',
'description': 'By setting an action priority higher than another action, the app effectively overrides other requests.',
'name': 'High Action Priority [android:priority] '
},
}
'a_debuggable': {
'title': 'Debug Enabled For App <br>[android:debuggable=true]',
'level': 'high',
'description': ('Debugging was enabled on the app which makes it '
'easier for reverse engineers to hook a debugger to'
' it. This allows dumping a stack trace and accessing'
' debugging helper classes.'),
'name': 'Debug Enabled For App [android:debuggable=true]',
},
'a_allowbackup': {
'title': ('Application Data can be Backed up'
'<br>[android:allowBackup=true]'),
'level': 'medium',
'description': ('This flag allows anyone to backup your application'
' data via adb. It allows users who have enabled USB'
' debugging to copy application data off of the'
' device.'),
'name': 'Application Data can be Backed up [android:allowBackup=true]',
},
'a_allowbackup_miss': {
'title': ('Application Data can be Backed up<br>[android:allowBackup]'
' flag is missing.'),
'level': 'medium',
'description': ('The flag [android:allowBackup] should be set to false'
'. By default it is set to true and allows anyone to '
'backup your application data via adb. It allows users'
' who have enabled USB debugging to copy application '
'data off of the device.'),
'name': ('Application Data can be Backed up [android:allowBackup] flag'
' is missing.'),
},
'a_testonly': {
'title': 'Application is in Test Mode <br>[android:testOnly=true]',
'level': 'high',
'description': ('It may expose functionality or data outside of itself'
' that would cause a security hole.'),
'name': 'Application is in Test Mode [android:testOnly=true]',
},
'a_taskaffinity': {
'title': 'TaskAffinity is set for Activity </br>(%s)',
'level': 'high',
'description': ('If taskAffinity is set, then other application'
' could read the Intents sent to Activities '
'belonging to another task. Always use the default'
' setting keeping the affinity as the package name'
' in order to prevent sensitive information inside'
' sent or received Intents from being read by '
'another application.'),
'name': 'TaskAffinity is set for Activity',
},
'a_launchmode': {
'title': 'Launch Mode of Activity (%s) is not standard.',
'level': 'high',
'description': ('An Activity should not be having the launch mode'
' attribute set to "singleTask/singleInstance" as '
'it becomes root Activity and it is possible for'
' other applications to read the contents of the'
' calling Intent. So it is required to use the'
' "standard" launch mode attribute when sensitive'
' information is included in an Intent.'),
'name': 'Launch Mode of Activity is not standard.',
},
'a_prot_sign': {
'title': ('<strong>%s</strong> (%s) is Protected by a permission.'
'</br>%s<br>[android:exported=true]'),
'level': 'info',
'description': ('A%s %s is found to be exported, but'
' is protected by permission.'),
'name': 'is Protected by a permission.[android:exported=true]',
},
'a_prot_normal': {
'title': ('<strong>%s</strong> (%s) is Protected by a permission, but'
' the protection level of the permission should be checked.'
'</br>%s <br>[android:exported=true]'),
'level': 'high',
'description': ('A%s %s is found to be shared with other apps on the'
' device therefore leaving it accessible to any other'
' application on the device. It is protected by a '
'permission. However, the protection level of the'
' permission is set to normal. This means that a '
'malicious application can request and obtain'
' the permission and interact with the component.'
' If it was set to signature, only applications '
'signed with the same certificate could obtain '
'the permission.'),
'name': ('is Protected by a permission, but the protection level of '
'the permission should be checked.[android:exported=true]'),
},
'a_prot_danger': {
'title': ('<strong>%s</strong> (%s) is Protected by a permission, but'
' the protection level of the permission should be checked.'
'</br>%s <br>[android:exported=true]'),
'level': 'high',
'description': ('A%s %s is found to be shared with other apps on the'
' device therefore leaving it accessible to any other'
' application on the device. It is protected by a'
' permission. However, the protection level of the'
' permission is set to dangerous. This means that a'
' malicious application can request and obtain the'
' permission and interact with the component. If it'
' was set to signature, only applications signed with'
' the same certificate could obtain the permission.'),
'name': ('is Protected by a permission, but the protection level of'
' the permission should be checked.[android:exported=true]'),
},
'a_prot_sign_sys': {
'title': ('<strong>%s</strong> (%s) is Protected by a permission, but'
' the protection level of the permission should be checked.'
'</br>%s <br>[android:exported=true]'),
'level': 'info',
'description': ('A%s %s is found to be exported, but is protected by'
' a permission. However, the protection level of the'
' permission is set to signatureOrSystem. It is '
'recommended that signature level is used instead. '
'Signature level should suffice for most purposes, '
'and does not depend on where the applications are '
'installed on the device.'),
'name': ('is Protected by a permission, but the protection level of'
' the permission should be checked.[android:exported=true]'),
},
'a_prot_unknown': {
'title': ('<strong>%s</strong> (%s) is Protected by a permission, but'
' the protection level of the permission should be checked.'
'</br>%s <br>[android:exported=true]'),
'level': 'high',
'description': ('A%s %s is found to be shared with other apps on the'
' device therefore leaving it accessible to any other'
' application on the device. It is protected by a '
'permission which is not defined in the analysed '
'application. As a result, the protection level of the'
' permission should be checked where it is defined. If'
' it is set to normal or dangerous, a malicious '
'application can request and obtain the permission and'
' interact with the component. If it is set to '
'signature, only applications signed with the same '
'certificate can obtain the permission.'),
'name': ('is Protected by a permission, but the protection level '
'of the permission should be '
'checked.[android:exported=true]'),
},
'a_prot_normal_appl': {
'title': ('<strong>%s</strong> (%s) is Protected by a permission at'
' the application level, but the protection level of the '
'permission should be checked.</br>%s <br>'
'[android:exported=true]'),
'level': 'high',
'description': ('A%s %s is found to be shared with other apps on the'
' device therefore leaving it accessible to any other'
' application on the device. It is protected by a '
'permission at the application level. However, the'
' protection level of the permission is set to normal.'
' This means that a malicious application can request '
'and obtain the permission and interact with the '
'component. If it was set to signature, only '
'applications signed with the same certificate '
'could obtain the permission.'),
'name': ('is Protected by a permission at the application level,'
' but the protection level of the permission should be '
'checked.[android:exported=true]'),
},
'a_prot_danger_appl': {
'title': ('<strong>%s</strong> (%s) is Protected by a permission at'
' the application level, but the protection level of the '
'permission should be checked.'
'</br>%s <br>[android:exported=true]'),
'level': 'high',
'description': ('A%s %s is found to be shared with other apps on the'
' device therefore leaving it accessible to any other'
' application on the device. It is protected by a '
'permission at the application level. However, the '
'protection level of the permission is set to '
'dangerous. This means that a malicious application '
'can request and obtain the permission and interact '
'with the component. If it was set to signature, '
'only applications signed with the same certificate'
' could obtain the permission.'),
'name': ('is Protected by a permission at the application level, but'
' the protection level of the permission should be '
'checked.[android:exported=true]'),
},
'a_prot_sign_appl': {
'title': ('<strong>%s</strong> (%s) Protected by a permission at'
' the application level.</br>%s<br>[android:exported=true]'),
'level': 'info',
'description': ('A%s %s is found to be exported, but is protected by'
' a permission at the application level.'),
'name': ('Protected by a permission at the application level.'
'[android:exported=true]'),
},
'a_prot_sign_sys_appl': {
'title': ('<strong>%s</strong> (%s) is Protected by a permission at '
'the application level, but the protection level of the '
'permission should be checked.'
'</br>%s <br>[android:exported=true]'),
'level': 'info',
'description': ('A%s %s is found to be exported, but is protected '
'by a permission at the application level. However,'
' the protection level of the permission is set to'
' signatureOrSystem. It is recommended that '
'signature level is used instead. Signature level'
' should suffice for most purposes, and does not '
'depend on where the applications are installed'
' on the device.'),
'name': ('is Protected by a permission at the application level, but'
' the protection level of the permission should be checked.'
'[android:exported=true]'),
},
'a_prot_unknown_appl': {
'title': ('<strong>%s</strong> (%s) is Protected by a permission'
' at the application, but the protection level of the '
'permission should be checked.'
'</br>%s <br>[android:exported=true]'),
'level': 'high',
'description': ('A%s %s is found to be shared with other apps on '
'the device therefore leaving it accessible to any'
' other application on the device. It is protected'
' by a permission at the application level which is'
' not defined in the analysed application. As a'
' result, the protection level of the permission'
' should be checked where it is defined. If it is'
' set to normal or dangerous, a malicious application'
' can request and obtain the permission and interact'
' with the component. If it is set to signature, only'
' applications signed with the same certificate can'
' obtain the permission.'),
'name': ('is Protected by a permission at the application, but the'
' protection level of the permission should be checked.'
'[android:exported=true]'),
},
'a_not_protected': {
'title': ('<strong>%s</strong> (%s) is not Protected.'
' <br>[android:exported=true]'),
'level': 'high',
'description': ('A%s %s is found to be shared with other apps on the'
' device therefore leaving it accessible to any other'
' application on the device.'),
'name': 'is not Protected. [android:exported=true]',
},
'a_not_protected_filter': {
'title': ('<strong>%s</strong> (%s) is not Protected.<br>'
'An intent-filter exists.'),
'level': 'high',
'description': ('A%s %s is found to be shared with other apps on the'
' device therefore leaving it accessible to any other '
'application on the device. The presence of '
'intent-filter indicates that the %s'
' is explicitly exported.'),
'name': 'is not Protected.An intent-filter exists.',
},
'c_not_protected': {
'title': ('<strong>%s</strong> (%s) is not Protected. <br>'
'[[Content Provider, targetSdkVersion < 17]'),
'level': 'high',
'description': ('A%s %s is found to be shared with other apps'
' on the device therefore leaving it accessible '
'to any other application on the device. It is '
'a Content Provider that targets an API level '
'under 17, which makes it exported by default,'
' regardless of the API level of the system '
'that the application runs on.'),
'name': 'is not Protected.[[Content Provider, targetSdkVersion < 17]',
},
'c_not_protected2': {
'title': ('<strong>%s</strong> (%s) would not be Protected if the'
' application ran on a device where the the API level was'
' less than 17. <br>[Content Provider, '
'targetSdkVersion >= 17]'),
'level': 'high',
'description': ('The Content Provider(%s %s) would be exported if the'
' application ran on a device where the the API level '
'was less than 17. In that situation, it would be '
'shared with other apps on the device therefore '
'leaving it accessible to any other application '
'on the device.'),
'name': ('would not be Protected if the application ran on a device'
' where the the API level was less than 17.[Content Provider,'
' targetSdkVersion >= 17]'),
},
'c_prot_normal': {
'title': ('<strong>%s</strong> (%s) is Protected by a permission, but'
' the protection level of the permission should be checked.'
'</br>%s <br>[Content Provider, targetSdkVersion < 17]'),
'level': 'high',
'description': ('A%s %s is found to be shared with other apps on the'
' device therefore leaving it accessible to any other'
' application on the device. It is protected by a '
'permission. However, the protection level of the'
' permission is set to normal. This means that a '
'malicious application can request and obtain '
'the permission and interact with the component. '
'If it was set to signature, only applications signed '
'with the same certificate could obtain '
'the permission.'),
'name': ('is Protected by a permission, but the protection level'
' of the permission should be checked.[Content Provider,'
' targetSdkVersion < 17]'),
},
'c_prot_danger': {
'title': ('<strong>%s</strong> (%s) is Protected by a permission, '
'but the protection level of the permission should be '
'checked.</br>%s <br>[Content Provider, '
'targetSdkVersion < 17]'),
'level': 'high',
'description': ('A%s %s is found to be shared with other apps on the'
' device therefore leaving it accessible to any other'
' application on the device. It is protected by a '
'permission. However, the protection level of the '
'permission is set to dangerous. This means that a '
'malicious application can request and obtain the '
'permission and interact with the component. If it'
' was set to signature, only applications signed with'
' the same certificate could obtain '
'the permission.'),
'name': ('is Protected by a permission, but the protection level of '
'the permission should be checked.[Content Provider, '
'targetSdkVersion < 17]'),
},
'c_prot_sign': {
'title': ('<strong>%s</strong> (%s) is Protected by a permission.'
'</br>%s <br>[Content Provider, targetSdkVersion < 17]'),
'level': 'info',
'description': ('A%s %s is found to be shared with other apps on the'
' device therefore leaving it accessible to any other'
' application on the device. It is '
'protected by permission.'),
'name': ('is Protected by a permission.[Content Provider, '
'targetSdkVersion < 17]'),
},
'c_prot_sign_sys': {
'title': ('<strong>%s</strong> (%s) is Protected by a permission,'
' but the protection level of the permission should be '
'checked.</br>%s <br>[Content Provider, '
'targetSdkVersion < 17]'),
'level': 'info',
'description': ('A%s %s is found to be exported, but is protected by'
' a permission. However, the protection level of the'
' permission is set to signatureOrSystem. It is'
' recommended that signature level is used instead.'
' Signature level should suffice for most purposes,'
' and does not depend on where the applications are'
' installed on the device.'),
'name': ('is Protected by a permission, but the protection level of '
'the permission should be checked.[Content Provider, '
'targetSdkVersion < 17]'),
},
'c_prot_unknown': {
'title': ('<strong>%s</strong> (%s) is Protected by a permission, but'
' the protection level of the permission should be checked.'
'</br>%s <br>[Content Provider, targetSdkVersion < 17]'),
'level': 'high',
'description': ('A%s %s is found to be shared with other apps on the '
'device therefore leaving it accessible to any other '
'application on the device. It is protected by a '
'permission which is not defined in the analysed '
'application. As a result, the protection level of the'
' permission should be checked where it is defined. If'
' it is set to normal or dangerous, a malicious '
'application can request and obtain the permission and'
' interact with the component. If it is set to '
'signature, only applications signed with the same '
'certificate can obtain the permission.'),
'name': ('is Protected by a permission, but the protection level of'
' the permission should be checked.[Content Provider,'
' targetSdkVersion < 17]'),
},
'c_prot_normal_appl': {
'title': ('<strong>%s</strong> (%s) is Protected by a permission at'
' the application level, but the protection level of the'
' permission should be checked.'
'</br>%s <br>[Content Provider, targetSdkVersion < 17]'),
'level': 'high',
'description': ('A%s %s is found to be shared with other apps on the'
' device therefore leaving it accessible to any other'
' application on the device. It is protected by a'
' permission at the application level. However, the'
' protection level of the permission is set to normal.'
' This means that a malicious application can request'
' and obtain the permission and interact with the'
' component. If it was set to signature, only '
'applications signed with the same certificate could'
' obtain the permission.'),
'name': ('is Protected by a permission at the application level, but'
' the protection level of the permission should be checked.'
'[Content Provider, targetSdkVersion < 17]'),
},
'c_prot_danger_appl': {
'title': ('<strong>%s</strong> (%s) is Protected by a permission at '
'the application level, but the protection level of the '
'permission should be checked.'
'</br>%s <br>[Content Provider, targetSdkVersion < 17]'),
'level': 'high',
'description': ('A%s %s is found to be shared with other apps on the'
' device therefore leaving it accessible to any other'
' application on the device. It is protected by a '
'permission at the application level. However, the '
'protection level of the permission is set to '
'dangerous. This means that a malicious application'
' can request and obtain the permission and interact'
' with the component. If it was set to signature, '
'only applications signed with the same certificate'
' could obtain the permission.'),
'name': ('is Protected by a permission at the application level, but'
' the protection level of the permission should be checked.'
'[Content Provider, targetSdkVersion < 17]'),
},
'c_prot_sign_appl': {
'title': ('<strong>%s</strong> (%s) is Protected by a permission at'
' the application level.</br>%s <br>[Content Provider,'
' targetSdkVersion < 17]'),
'level': 'info',
'description': ('A%s %s is found to be shared with other apps on'
' the device therefore leaving it accessible to any'
' other application on the device. It is protected '
'by permission at the application level.'),
'name': ('is Protected by a permission at the application level.'
'[Content Provider, targetSdkVersion < 17]'),
},
'c_prot_sign_sys_appl': {
'title': ('<strong>%s</strong> (%s) is Protected by a permission at'
' the application level, but the protection level of the'
' permission should be checked.'
'</br>%s <br>[Content Provider, targetSdkVersion < 17]'),
'level': 'info',
'description': ('A%s %s is found to be exported, but is protected by'
' a permission at the application level. However, the'
' protection level of the permission is set to '
'signatureOrSystem. It is recommended that signature '
'level is used instead. Signature level should suffice'
' for most purposes, and does not depend on where the'
' applications are installed on the device.'),
'name': ('is Protected by a permission at the application level, '
'but the protection level of the permission should be '
'checked.[Content Provider, targetSdkVersion < 17]'),
},
'c_prot_unknown_appl': {
'title': ('<strong>%s</strong> (%s) is Protected by a permission at'
' application level, but the protection level of the '
'permission should be checked.</br>%s '
'<br>[Content Provider, targetSdkVersion < 17]'),
'level': 'high',
'description': ('A%s %s is found to be shared with other apps on the'
' device therefore leaving it accessible to any other'
' application on the device. It is protected by a '
'permission at application level which is not defined'
' in the analysed application. As a result, the '
'protection level of the permission should be checked'
' where it is defined. If it is set to normal or '
'dangerous, a malicious application can request and'
' obtain the permission and interact with the '
'component. If it is set to signature, only '
'applications signed with the same certificate '
'can obtain the permission.'),
'name': ('is Protected by a permission at application level, but'
' the protection level of the permission should be checked.'
'[Content Provider, targetSdkVersion < 17]'),
},
'c_prot_normal_new': {
'title': ('<strong>%s</strong> (%s) is Protected by a permission, '
'but the protection level of the permission should be '
'checked if the application runs on a device where the '
'the API level is less than 17'
'</br>%s <br>[Content Provider, targetSdkVersion >= 17]'),
'level': 'high',
'description': ('The Content Provider (%s) would be exported if the'
' application ran on a device where the the API level'
' was less than 17. In that situation, it would still'
' be protected by a permission. However, the '
'protection level of the permission is set to normal. '
'This means that a malicious application could request'
' and obtain the permission and interact with the'
' component. If it was set to signature, only'
' applications signed with the same certificate '
'could obtain the permission.'),
'name': ('is Protected by a permission, but the protection level of'
' the permission should be checked if the application runs '
'on a device where the the API level is less than 17 '
'[Content Provider, targetSdkVersion >= 17]'),
},
'c_prot_danger_new': {
'title': ('<strong>%s</strong> (%s) is Protected by a permission,'
' but the protection level of the permission should be '
'checked if the application runs on a device where '
'the API level is less than 17.</br>%s <br>'
'[Content Provider, targetSdkVersion >= 17]'),
'level': 'high',
'description': ('The Content Provider(%s) would be exported if the'
' application ran on a device where the the API level'
' was less than 17. In that situation, it would still'
' be protected by a permission. However, the '
'protection level of the permission is set to'
' dangerous. This means that a malicious application'
' could request and obtain the permission and interact'
' with the component. If it was set to signature, only'
' applications signed with the same certificate could'
' obtain the permission.'),
'name': ('is Protected by a permission, but the protection level of'
' the permission should be checked if the application runs on'
' a device where the the API level is less than 17.'
'[Content Provider, targetSdkVersion >= 17]'),
},
'c_prot_sign_new': {
'title': ('<strong>%s</strong> (%s) is Protected by a permission.'
'</br>%s <br>[Content Provider, targetSdkVersion >= 17]'),
'level': 'info',
'description': ('The Content Provider(%s) would be exported if the'
' application ran on a device where the the API level'
' was less than 17. Nevertheless, it is protected '
'by a permission.'),
'name': ('is Protected by a permission.'
'[Content Provider, targetSdkVersion >= 17]'),
},
'c_prot_sign_sys_new': {
'title': ('<strong>%s</strong> (%s) is Protected by a permission, but'
' the protection level of the permission should be checked.'
'</br>%s <br>[Content Provider, targetSdkVersion >= 17]'),
'level': 'info',
'description': ('The Content Provider(%s) would be exported if the '
'application ran on a device where the API level was'
' less than 17. In that situation, it would still '
'be protected by a permission. However, the protection'
' level of the permission is set to signatureOrSystem.'
' It is recommended that signature level is used '
'instead. Signature level should suffice for most'
' purposes, and does not depend on where the'
' applications are installed on the device.'),
'name': ('is Protected by a permission, but the protection level of'
' the permission should be checked.'
'[Content Provider, targetSdkVersion >= 17]'),
},
'c_prot_unknown_new': {
'title': ('<strong>%s</strong> (%s) is Protected by a permission, but'
' the protection level of the permission should be checked'
' if the application runs on a device where the the API '
'level is less than 17.</br>%s <br>'
'[Content Provider, targetSdkVersion >= 17]'),
'level': 'high',
'description': ('The Content Provider(%s) would be exported if the'
' application ran on a device where the the API level'
' was less than 17. In that situation, it would still'
' be protected by a permission which is not defined in'
' the analysed application. As a result, the '
'protection level of the permission should be '
'checked where it is defined. If it is set to normal'
' or dangerous, a malicious application can request'
' and obtain the permission and interact with the '
'component. If it is set to signature, only '
'applications signed with the same certificate'
' can obtain the permission.'),
'name': ('is Protected by a permission, but the protection level of'
' the permission should be checked if the application runs'
' on a device where the the API level is less than 17.'
'[Content Provider, targetSdkVersion >= 17]'),
},
'c_prot_normal_new_appl': {
'title': ('<strong>%s</strong> (%s) is Protected by a permission at'
' the application level, but the protection level of the'
' permission should be checked if the application runs on'
' a device where the the API level is less than 17'
'</br>%s <br>[Content Provider, targetSdkVersion >= 17]'),
'level': 'high',
'description': ('The Content Provider (%s) would be exported if the'
' application ran on a device where the the API '
'level was less than 17. In that situation, it'
' would still be protected by a permission. '
'However, the protection level of the permission'
' is set to normal. This means that a malicious'
' application could request and obtain the '
'permission and interact with the component. '
'If it was set to signature, only applications '
'signed with the same certificate could obtain'
' the permission.'),
'name': ('is Protected by a permission at the application level '
'should be checked, but the protection level of the '
'permission if the application runs on a device where'
' the the API level is less than 17.'
'[Content Provider, targetSdkVersion >= 17]'),
},
'c_prot_danger_new_appl': {
'title': ('<strong>%s</strong> (%s) is Protected by a permission at'
' the application level, but the protection level of the'
' permission should be checked if the application runs on'
' a device where the the API level is less than 17.'
'</br>%s <br>[Content Provider, targetSdkVersion >= 17]'),
'level': 'high',
'description': ('The Content Provider(%s) would be exported if the'
' application ran on a device where the the API '
'level was less than 17. In that situation, it'
' would still be protected by a permission. However,'
' the protection level of the permission is set to'
' dangerous. This means that a malicious application'
' could request and obtain the permission and interact'
' with the component. If it was set to signature, only'
' applications signed with the same certificate could'
' obtain the permission.'),
'name': ('is Protected by a permission at the application level, but'
' the protection level of the permission should be checked '
'if the application runs on a device where the the API level '
'is less than 17.[Content Provider, targetSdkVersion >= 17]'),
},
'c_prot_sign_new_appl': {
'title': ('<strong>%s</strong> (%s) is Protected by a permission at'
' the application level.</br>%s<br>'
'[Content Provider, targetSdkVersion >= 17]'),
'level': 'info',
'description': ('The Content Provider(%s) would be exported if the'
' application ran on a device where the the API '
'level was less than 17. Nevertheless, it is '
'protected by a permission.'),
'name': ('is Protected by a permission at the application level.'
'[Content Provider, targetSdkVersion >= 17]'),
},
'c_prot_sign_sys_new_appl': {
'title': ('<strong>%s</strong> (%s) is Protected by a permission at'
' the application level, but the protection level of the'
' permission should be checked.'
'</br>%s <br>[Content Provider, targetSdkVersion >= 17]'),
'level': 'info',
'description': ('The Content Provider(%s) would be exported if the'
' application ran on a device where the API level was'
' less than 17. In that situation, it would still be'
' protected by a permission. However, the protection'
' level of the permission is set to signatureOrSystem.'
' It is recommended that signature level is used'
' instead. Signature level should suffice for most'
' purposes, and does not depend on where the '
'applications are installed on the device.'),
'name': ('is Protected by a permission at the application level, but'
' the protection level of the permission should be checked.'
'[Content Provider, targetSdkVersion >= 17]'),
},
'c_prot_unknown_new_appl': {
'title': ('<strong>%s</strong> (%s) is Protected by a permission at '
'the application level, but the protection level of the '
'permission should be checked if the application runs on'
' a device where the the API level is less than 17.'
'</br>%s <br>[Content Provider, targetSdkVersion >= 17]'),
'level': 'high',
'description': ('The Content Provider(%s) would be exported if the'
' application ran on a device where the the API level'
' was less than 17. In that situation, it would still'
' be protected by a permission which is not defined '
'in the analysed application. As a result, the'
' protection level of the permission should be checked'
' where it is defined. If it is set to normal or'
' dangerous, a malicious application can request'
' and obtain the permission and interact with the'
' component. If it is set to signature, only '
'applications signed with the same certificate'
' can obtain the permission.'),
'name': ('is Protected by a permission at the application level,'
' but the protection level of the permission should be'
' checked if the application runs on a device where the'
' the API level is less than 17.'
'[Content Provider, targetSdkVersion >= 17]'),
},
'a_improper_provider': {
'title': 'Improper Content Provider Permissions<br>[%s]',
'level': 'high',
'description': ('A content provider permission was set to allows'
' access from any other app on the device. '
'Content providers may contain sensitive '
'information about an app and therefore '
'should not be shared.'),
'name': 'Improper Content Provider Permissions',
},
'a_dailer_code': {
'title': ('Dailer Code: %s Found'
' <br>[android:scheme="android_secret_code"]'),
'level': 'high',
'description': ('A secret code was found in the manifest. These codes,'
' when entered into the dialer grant access to hidden'
' content that may contain sensitive information.'),
'name': ('Dailer Code: Found '
'<br>[android:scheme="android_secret_code"]'),
},
'a_sms_receiver_port': {
'title': 'Data SMS Receiver Set on Port: %s Found<br>[android:port]',
'level': 'high',
'description': ('A binary SMS recevier is configured to listen on a'
' port. Binary SMS messages sent to a device are '
'processed by the application in whichever way the'
' developer choses. The data in this SMS should be'
' properly validated by the application. Furthermore,'
' the application should assume that the SMS being'
' received is from an untrusted source.'),
'name': 'Data SMS Receiver Set on Port: Found<br>[android:port]',
},
'a_high_intent_priority': {
'title': 'High Intent Priority (%s)<br>[android:priority]',
'level': 'medium',
'description': ('By setting an intent priority higher than another'
' intent, the app effectively overrides '
'other requests.'),
'name': 'High Intent Priority [android:priority]',
},
'a_high_action_priority': {
'title': 'High Action Priority (%s)<br>[android:priority] ',
'level': 'medium',
'description': ('By setting an action priority higher than'
' another action, the app effectively '
'overrides other requests.'),
'name': 'High Action Priority [android:priority]',
},
}
+147 -85
View File
@@ -1,5 +1,5 @@
"""
Rule Format
Rule Format.
1. desc - Description of the findings
@@ -11,14 +11,19 @@ Rule Format
a. single_regex - if re.findall(regex1, input)
b .regex_and - if re.findall(regex1, input) and re.findall(regex2, input)
c. regex_or - if re.findall(regex1, input) or re.findall(regex2, input)
d. regex_and_perm - if re.findall(regex, input) and (permission in permission_list_from_manifest)
d. regex_and_perm - if re.findall(regex, input) and
(permission in permission_list_from_manifest)
e. single_string - if string1 in input
f. string_and - if (string1 in input) and (string2 in input)
g. string_or - if (string1 in input) or (string2 in input)
h. string_and_or - if (string1 in input) and ((string_or1 in input) or (string_or2 in input))
i. string_or_and - if (string1 in input) or ((string_and1 in input) and (string_and2 in input))
j. string_and_perm - if (string1 in input) and (permission in permission_list_from_manifest)
k. string_or_and_perm - if ((string1 in input) or (string2 in input)) and (permission in permission_list_from_manifest)
h. string_and_or - if (string1 in input) and ((string_or1 in input)
or (string_or2 in input))
i. string_or_and - if (string1 in input) or ((string_and1 in input)
and (string_and2 in input))
j. string_and_perm - if (string1 in input)
and (permission in permission_list_from_manifest)
k. string_or_and_perm - if ((string1 in input) or (string2 in input))
and (permission in permission_list_from_manifest)
4. level
a. high
@@ -39,14 +44,19 @@ Rule Format
"""
RULES = [
{
'desc': 'Files may contain hardcoded sensitive informations like usernames, passwords, keys etc.',
'desc': ('Files may contain hardcoded sensitive '
'informations like usernames, passwords, keys etc.'),
'type': 'regex',
'regex1': r'''(password\s*=\s*['|"].+['|"]\s{0,5})|(pass\s*=\s*['|"].+['|"]\s{0,5})|(username\s*=\s*['|"].+['|"]\s{0,5})|(secret\s*=\s*['|"].+['|"]\s{0,5})|(key\s*=\s*['|"].+['|"]\s{0,5})''',
'regex1': (r'(password\s*=\s*[\'|\"].+[\'|\"]\s{0,5})|'
r'(pass\s*=\s*[\'|\"].+[\'|\"]\s{0,5})|'
r'(username\s*=\s*[\'|\"].+[\'|\"]\s{0,5})|'
r'(secret\s*=\s*[\'|\"].+[\'|\"]\s{0,5})|'
r'(key\s*=\s*[\'|\"].+[\'|\"]\s{0,5})'),
'level': 'high',
'match': 'single_regex',
'input_case': 'lower',
'cvss': 7.4,
'cwe': 'CWE-312'
'cwe': 'CWE-312',
},
{
'desc': 'IP Address disclosure',
@@ -56,51 +66,67 @@ RULES = [
'match': 'single_regex',
'input_case': 'exact',
'cvss': 4.3,
'cwe': 'CWE-200'
'cwe': 'CWE-200',
},
{
'desc': 'Hidden elements in view can be used to hide data from user. But this data can be leaked',
'desc': ('Hidden elements in view can be used to hide'
' data from user. But this data can be leaked'),
'type': 'regex',
'regex1': r'setVisibility\(View\.GONE\)|setVisibility\(View\.INVISIBLE\)',
'regex1': (r'setVisibility\(View\.GONE\)|'
r'setVisibility\(View\.INVISIBLE\)'),
'level': 'high',
'match': 'single_regex',
'input_case': 'exact',
'cvss': 4.3,
'cwe': 'CWE-919'
'cwe': 'CWE-919',
},
{
'desc': 'The App uses ECB mode in Cryptographic encryption algorithm. ECB mode is known to be weak as it results in the same ciphertext for identical blocks of plaintext.',
'desc': ('The App uses ECB mode in Cryptographic encryption algorithm.'
' ECB mode is known to be weak as it results in the same'
' ciphertext for identical blocks of plaintext.'),
'type': 'regex',
'regex1': r'Cipher\.getInstance\(\s*"\s*AES\/ECB',
'level': 'high',
'match': 'single_regex',
'input_case': 'exact',
'cvss': 5.9,
'cwe': 'CWE-327'
'cwe': 'CWE-327',
},
{
'desc': 'This App uses RSA Crypto without OAEP padding. The purpose of the padding scheme is to prevent a number of attacks on RSA that only work when the encryption is performed without padding.',
'desc': ('This App uses RSA Crypto without OAEP padding. The purpose'
' of the padding scheme is to prevent a number of attacks on'
' RSA that only work when the encryption is performed'
' without padding.'),
'type': 'regex',
'regex1': r'cipher\.getinstance\(\s*"rsa/.+/nopadding',
'level': 'high',
'match': 'single_regex',
'input_case': 'lower',
'cvss': 5.9,
'cwe': 'CWE-780'
'cwe': 'CWE-780',
},
{
'desc': 'Insecure Implementation of SSL. Trusting all the certificates or accepting self signed certificates is a critical Security Hole. This application is vulnerable to MITM attacks',
'desc': ('Insecure Implementation of SSL. Trusting all the '
'certificates or accepting self signed certificates'
' is a critical Security Hole. This application is'
' vulnerable to MITM attacks'),
'type': 'regex',
'regex1': r'javax\.net\.ssl',
'regex2': r'TrustAllSSLSocket-Factory|AllTrustSSLSocketFactory|NonValidatingSSLSocketFactory|net\.SSLCertificateSocketFactory|ALLOW_ALL_HOSTNAME_VERIFIER|\.setDefaultHostnameVerifier\(|NullHostnameVerifier\(',
'regex2': (r'TrustAllSSLSocket-Factory|AllTrustSSLSocketFactory|'
r'NonValidatingSSLSocketFactory|'
r'net\.SSLCertificateSocketFactory|'
r'ALLOW_ALL_HOSTNAME_VERIFIER|'
r'\.setDefaultHostnameVerifier\(|'
r'NullHostnameVerifier\('),
'level': 'high',
'match': 'regex_and',
'input_case': 'exact',
'cvss': 7.4,
'cwe': 'CWE-295'
'cwe': 'CWE-295',
},
{
'desc': 'WebView load files from external storage. Files in external storage can be modified by any application.',
'desc': ('WebView load files from external storage. Files in external'
' storage can be modified by any application.'),
'type': 'regex',
'regex1': r'\.loadUrl\(.*getExternalStorageDirectory\(',
'regex2': r'webkit\.WebView',
@@ -108,7 +134,7 @@ RULES = [
'match': 'regex_and',
'input_case': 'exact',
'cvss': 5.0,
'cwe': 'CWE-919'
'cwe': 'CWE-919',
},
{
'desc': 'The file is World Readable. Any App can read from the file',
@@ -119,7 +145,7 @@ RULES = [
'match': 'regex_or',
'input_case': 'exact',
'cvss': 4.0,
'cwe': 'CWE-276'
'cwe': 'CWE-276',
},
{
@@ -131,57 +157,65 @@ RULES = [
'match': 'regex_or',
'input_case': 'exact',
'cvss': 6.0,
'cwe': 'CWE-276'
'cwe': 'CWE-276',
},
{
'desc': 'The file is World Readable and Writable. Any App can read/write to the file',
'desc': ('The file is World Readable and Writable. '
'Any App can read/write to the file'),
'type': 'regex',
'regex1': r'openFileOutput\(\s*".+"\s*,\s*3\s*\)',
'level': 'high',
'match': 'single_regex',
'input_case': 'exact',
'cvss': 6.0,
'cwe': 'CWE-276'
'cwe': 'CWE-276',
},
{
'desc': 'Weak Hash algorithm used',
'type': 'regex',
'regex1': r'getInstance(\"md4\")|getInstance(\"rc2\")|getInstance(\"rc4\")|getInstance(\"RC4\")|getInstance(\"RC2\")|getInstance(\"MD4\")',
'regex1': (r'getInstance(\"md4\")|getInstance(\"rc2\")|'
r'getInstance(\"rc4\")|getInstance(\"RC4\")|'
r'getInstance(\"RC2\")|getInstance(\"MD4\")'),
'level': 'high',
'match': 'single_regex',
'input_case': 'exact',
'cvss': 7.4,
'cwe': 'CWE-327'
'cwe': 'CWE-327',
},
{
'desc': 'MD5 is a weak hash known to have hash collisions.',
'type': 'regex',
'regex1': r'MessageDigest\.getInstance\(\"*MD5\"*\)|MessageDigest\.getInstance\(\"*md5\"*\)|DigestUtils\.md5\(',
'regex1': (r'MessageDigest\.getInstance\(\"*MD5\"*\)|'
r'MessageDigest\.getInstance\(\"*md5\"*\)|'
r'DigestUtils\.md5\('),
'level': 'high',
'match': 'single_regex',
'input_case': 'exact',
'cvss': 7.4,
'cwe': 'CWE-327'
'cwe': 'CWE-327',
},
{
'desc': 'SHA-1 is a weak hash known to have hash collisions.',
'type': 'regex',
'regex1': r'MessageDigest\.getInstance\(\"*SHA-1\"*\)|MessageDigest\.getInstance\(\"*sha-1\"*\)|DigestUtils\.sha\(',
'regex1': (r'MessageDigest\.getInstance\(\"*SHA-1\"*\)|'
r'MessageDigest\.getInstance\(\"*sha-1\"*\)|'
r'DigestUtils\.sha\('),
'level': 'high',
'match': 'single_regex',
'input_case': 'exact',
'cvss': 5.9,
'cwe': 'CWE-327'
'cwe': 'CWE-327',
},
{
'desc': 'App can write to App Directory. Sensitive Information should be encrypted.',
'desc': ('App can write to App Directory. '
'Sensitive Information should be encrypted.'),
'type': 'regex',
'regex1': r'MODE_PRIVATE|Context\.MODE_PRIVATE',
'level': 'info',
'match': 'single_regex',
'input_case': 'exact',
'cvss': 3.9,
'cwe': 'CWE-276'
'cwe': 'CWE-276',
},
{
'desc': 'The App uses an insecure Random Number Generator.',
@@ -191,37 +225,41 @@ RULES = [
'match': 'single_regex',
'input_case': 'exact',
'cvss': 7.5,
'cwe': 'CWE-330'
'cwe': 'CWE-330',
},
{
'desc': 'The App logs information. Sensitive information should never be logged.',
'desc': ('The App logs information. '
'Sensitive information should never be logged.'),
'type': 'regex',
'regex1': r'Log\.(v|d|i|w|e|f|s)|System\.out\.print|System\.err\.print',
'regex1': (r'Log\.(v|d|i|w|e|f|s)|'
r'System\.out\.print|System\.err\.print'),
'level': 'info',
'match': 'single_regex',
'input_case': 'exact',
'cvss': 7.5,
'cwe': 'CWE-532'
'cwe': 'CWE-532',
},
{
'desc': 'This App uses Java Hash Code. It\'s a weak hash function and should never be used in Secure Crypto Implementation.',
'desc': ('This App uses Java Hash Code. It\'s a weak hash function and'
' should never be used in Secure Crypto Implementation.'),
'type': 'string',
'string1': '.hashCode()',
'level': 'high',
'match': 'single_string',
'input_case': 'exact',
'cvss': 4.3,
'cwe': 'CWE-327'
'cwe': 'CWE-327',
},
{
'desc': 'These activities prevent screenshot when they go to background.',
'desc': ('These activities prevent '
'screenshot when they go to background.'),
'type': 'string',
'string1': 'LayoutParams.FLAG_SECURE',
'level': 'good',
'match': 'single_string',
'input_case': 'exact',
'cvss': 0,
'cwe': ''
'cwe': '',
},
{
'desc': 'This App uses SQL Cipher. But the secret may be hardcoded.',
@@ -231,7 +269,7 @@ RULES = [
'match': 'single_string',
'input_case': 'exact',
'cvss': 0,
'cwe': ''
'cwe': '',
},
{
'desc': 'This app has capabilities to prevent tapjacking attacks.',
@@ -241,10 +279,11 @@ RULES = [
'match': 'single_string',
'input_case': 'exact',
'cvss': 0,
'cwe': ''
'cwe': '',
},
{
'desc': 'App can read/write to External Storage. Any App can read data written to External Storage.',
'desc': ('App can read/write to External Storage. '
'Any App can read data written to External Storage.'),
'perm': 'android.permission.WRITE_EXTERNAL_STORAGE',
'type': 'string',
'string1': '.getExternalStorage',
@@ -253,10 +292,11 @@ RULES = [
'match': 'string_or_and_perm',
'input_case': 'exact',
'cvss': 5.5,
'cwe': 'CWE-276'
'cwe': 'CWE-276',
},
{
'desc': 'App creates temp file. Sensitive information should never be written into a temp file.',
'desc': ('App creates temp file. Sensitive '
'information should never be written into a temp file.'),
'perm': 'android.permission.WRITE_EXTERNAL_STORAGE',
'type': 'string',
'string1': '.createTempFile(',
@@ -264,10 +304,11 @@ RULES = [
'match': 'string_and_perm',
'input_case': 'exact',
'cvss': 5.5,
'cwe': 'CWE-276'
'cwe': 'CWE-276',
},
{
'desc': 'Insecure WebView Implementation. Execution of user controlled code in WebView is a critical Security Hole.',
'desc': ('Insecure WebView Implementation. Execution of user'
' controlled code in WebView is a critical Security Hole.'),
'type': 'string',
'string1': 'setJavaScriptEnabled(true)',
'string2': '.addJavascriptInterface(',
@@ -275,10 +316,11 @@ RULES = [
'match': 'string_and',
'input_case': 'exact',
'cvss': 8.8,
'cwe': 'CWE-749'
'cwe': 'CWE-749',
},
{
'desc': 'This App uses SQL Cipher. SQLCipher provides 256-bit AES encryption to sqlite database files.',
'desc': ('This App uses SQL Cipher. SQLCipher '
'provides 256-bit AES encryption to sqlite database files.'),
'type': 'string',
'string1': 'SQLiteDatabase.loadLibs(',
'string2': 'net.sqlcipher.',
@@ -286,7 +328,7 @@ RULES = [
'match': 'string_and',
'input_case': 'exact',
'cvss': 0,
'cwe': ''
'cwe': '',
},
{
'desc': 'This App download files using Android Download Manager',
@@ -297,7 +339,7 @@ RULES = [
'match': 'string_and',
'input_case': 'exact',
'cvss': 0,
'cwe': ''
'cwe': '',
},
{
'desc': 'This App use Realm Database with encryption.',
@@ -308,10 +350,15 @@ RULES = [
'match': 'string_and',
'input_case': 'exact',
'cvss': 0,
'cwe': ''
'cwe': '',
},
{
'desc': 'The App may use weak IVs like "0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00" or "0x01,0x02,0x03,0x04,0x05,0x06,0x07". Not using a random IV makes the resulting ciphertext much more predictable and susceptible to a dictionary attack.',
'desc': ('The App may use weak IVs like '
'"0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00" or '
'"0x01,0x02,0x03,0x04,0x05,0x06,0x07". '
'Not using a random IV makes the resulting '
'ciphertext much more predictable and '
'susceptible to a dictionary attack.'),
'type': 'string',
'string1': '0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00',
'string2': '0x01,0x02,0x03,0x04,0x05,0x06,0x07',
@@ -319,7 +366,7 @@ RULES = [
'match': 'string_or',
'input_case': 'exact',
'cvss': 9.8,
'cwe': 'CWE-329'
'cwe': 'CWE-329',
},
{
'desc': 'Remote WebView debugging is enabled.',
@@ -330,10 +377,11 @@ RULES = [
'match': 'string_and',
'input_case': 'exact',
'cvss': 5.4,
'cwe': 'CWE-919'
'cwe': 'CWE-919',
},
{
'desc': 'This app listens to Clipboard changes. Some malwares also listen to Clipboard changes.',
'desc': ('This app listens to Clipboard changes.'
' Some malwares also listen to Clipboard changes.'),
'type': 'string',
'string1': 'content.ClipboardManager',
'string2': 'OnPrimaryClipChangedListener',
@@ -341,10 +389,12 @@ RULES = [
'match': 'string_and',
'input_case': 'exact',
'cvss': 0,
'cwe': ''
'cwe': '',
},
{
'desc': 'This App copies data to clipboard. Sensitive data should not be copied to clipboard as other applications can access it.',
'desc': ('This App copies data to clipboard. Sensitive data should'
' not be copied to clipboard as other'
' applications can access it.'),
'type': 'string',
'string1': 'content.ClipboardManager',
'string2': 'setPrimaryClip(',
@@ -352,10 +402,12 @@ RULES = [
'match': 'string_and',
'input_case': 'exact',
'cvss': 0,
'cwe': ''
'cwe': '',
},
{
'desc': 'Insecure WebView Implementation. WebView ignores SSL Certificate errors and accept any SSL Certificate. This application is vulnerable to MITM attacks',
'desc': ('Insecure WebView Implementation. WebView ignores SSL'
' Certificate errors and accept any SSL Certificate.'
' This application is vulnerable to MITM attacks'),
'type': 'string',
'string1': 'onReceivedSslError(WebView',
'string2': '.proceed();',
@@ -363,10 +415,13 @@ RULES = [
'match': 'string_and',
'input_case': 'exact',
'cvss': 7.4,
'cwe': 'CWE-295'
'cwe': 'CWE-295',
},
{
'desc': 'App uses SQLite Database and execute raw SQL query. Untrusted user input in raw SQL queries can cause SQL Injection. Also sensitive information should be encrypted and written to the database.',
'desc': ('App uses SQLite Database and execute raw SQL query. '
'Untrusted user input in raw SQL queries can cause'
' SQL Injection. Also sensitive information should'
' be encrypted and written to the database.'),
'type': 'string',
'string1': 'android.database.sqlite',
'string_or1': 'rawQuery(',
@@ -375,7 +430,7 @@ RULES = [
'match': 'string_and_or',
'input_case': 'exact',
'cvss': 5.9,
'cwe': 'CWE-89'
'cwe': 'CWE-89',
},
{
'desc': 'This App detects frida server.',
@@ -388,10 +443,13 @@ RULES = [
'match': 'string_and_or',
'input_case': 'exact',
'cvss': 0,
'cwe': ''
'cwe': '',
},
{
'desc': 'This App uses an SSL Pinning Library (org.thoughtcrime.ssl.pinning) to prevent MITM attacks in secure communication channel.',
'desc': ('This App uses an SSL Pinning Library '
'(org.thoughtcrime.ssl.pinning) to '
'prevent MITM attacks in secure'
' communication channel.'),
'type': 'string',
'string1': 'org.thoughtcrime.ssl.pinning',
'string_or1': 'PinningHelper.getPinnedHttpsURLConnection',
@@ -401,10 +459,11 @@ RULES = [
'match': 'string_and_or',
'input_case': 'exact',
'cvss': 0,
'cwe': ''
'cwe': '',
},
{
'desc': 'This App has capabilities to prevent against Screenshots from Recent Task History/ Now On Tap etc.',
'desc': ('This App has capabilities to prevent against'
' Screenshots from Recent Task History/ Now On Tap etc.'),
'type': 'string',
'string1': '.FLAG_SECURE',
'string_or1': 'getWindow().setFlags(',
@@ -413,10 +472,11 @@ RULES = [
'match': 'string_and_or',
'input_case': 'exact',
'cvss': 0,
'cwe': ''
'cwe': '',
},
{
'desc': 'DexGuard Debug Detection code to detect wheather an App is debuggable or not is identified.',
'desc': ('DexGuard Debug Detection code to detect'
' wheather an App is debuggable or not is identified.'),
'type': 'string',
'string1': 'import dexguard.util',
'string2': 'DebugDetector.isDebuggable',
@@ -424,7 +484,7 @@ RULES = [
'match': 'string_and',
'input_case': 'exact',
'cvss': 0,
'cwe': ''
'cwe': '',
},
{
'desc': 'DexGuard Debugger Detection code is identified.',
@@ -435,7 +495,7 @@ RULES = [
'match': 'string_and',
'input_case': 'exact',
'cvss': 0,
'cwe': ''
'cwe': '',
},
{
'desc': 'DexGuard Emulator Detection code is identified.',
@@ -446,10 +506,11 @@ RULES = [
'match': 'string_and',
'input_case': 'exact',
'cvss': 0,
'cwe': ''
'cwe': '',
},
{
'desc': 'DecGuard code to detect wheather the App is signed with a debug key or not is identified.',
'desc': ('DexGuard code to detect wheather the App'
' is signed with a debug key or not is identified.'),
'type': 'string',
'string1': 'import dexguard.util',
'string2': 'DebugDetector.isSignedWithDebugKey',
@@ -457,7 +518,7 @@ RULES = [
'match': 'string_and',
'input_case': 'exact',
'cvss': 0,
'cwe': ''
'cwe': '',
},
{
'desc': 'DexGuard Root Detection code is identified.',
@@ -468,7 +529,7 @@ RULES = [
'match': 'string_and',
'input_case': 'exact',
'cvss': 0,
'cwe': ''
'cwe': '',
},
{
'desc': 'DexGuard App Tamper Detection code is identified.',
@@ -479,10 +540,11 @@ RULES = [
'match': 'string_and',
'input_case': 'exact',
'cvss': 0,
'cwe': ''
'cwe': '',
},
{
'desc': 'DexGuard Signer Certificate Tamper Detection code is identified.',
'desc': ('DexGuard Signer Certificate'
' Tamper Detection code is identified.'),
'type': 'string',
'string1': 'import dexguard.util',
'string2': 'TCertificateChecker.checkCertificate',
@@ -490,7 +552,7 @@ RULES = [
'match': 'string_and',
'input_case': 'exact',
'cvss': 0,
'cwe': ''
'cwe': '',
},
{
'desc': 'The App may use package signature for tamper detection.',
@@ -501,7 +563,7 @@ RULES = [
'match': 'string_and',
'input_case': 'exact',
'cvss': 0,
'cwe': ''
'cwe': '',
},
{
'desc': 'This App uses SafetyNet API.',
@@ -511,7 +573,7 @@ RULES = [
'match': 'single_string',
'input_case': 'exact',
'cvss': 0,
'cwe': ''
'cwe': '',
},
{
'desc': 'This App may request root (Super User) privileges.',
@@ -525,7 +587,7 @@ RULES = [
'match': 'string_or',
'input_case': 'exact',
'cvss': 0,
'cwe': 'CWE-250'
'cwe': 'CWE-250',
},
{
'desc': 'This App may have root detection capabilities.',
@@ -536,10 +598,10 @@ RULES = [
'string4': '/system/bin/failsafe/su',
'string5': '/system/sd/xbin/su',
'string6': '"/system/xbin/which", "su"',
"string7": 'RootTools.isAccessGiven()',
'string7': 'RootTools.isAccessGiven()',
'level': 'good',
'match': 'string_or',
'input_case': 'exact',
'cvss': 0,
'cwe': ''
'cwe': '',
}]
+88 -72
View File
@@ -1,19 +1,17 @@
# !/usr/bin/python
# coding=utf-8
import os
import io
import struct
import logging
from MobSF.utils import (
PrintException
)
import os
import struct
from MobSF.utils import log_exception
logger = logging.getLogger(__name__)
class tinyELFFile(object):
'''
from pyelftools
'''
class TinyELFFile(object):
"""from pyelftools."""
def __init__(self, stream):
self.stream = stream
@@ -33,7 +31,7 @@ class tinyELFFile(object):
self.little_endian = False
else:
raise Exception('Invalid EI_DATA %s' % repr(ei_data))
self.unpack_endian = "<" if self.little_endian else ">"
self.unpack_endian = '<' if self.little_endian else '>'
self.stream.seek(0)
self.header = {
'e_ident': {
@@ -60,7 +58,9 @@ class tinyELFFile(object):
'e_shstrndx': self.unpack_half(),
}
xxx = self.decode_shdr(
self.header['e_shoff'] + self.header['e_shstrndx'] * self.header['e_shentsize'])
self.header['e_shoff']
+ self.header['e_shstrndx']
* self.header['e_shentsize'])
self._file_stringtable_section = xxx['sh_offset']
def decode_shdr(self, off):
@@ -92,7 +92,6 @@ class tinyELFFile(object):
st_info_bind = st_info_struct >> 4 & 0x0F
st_info_type = st_info_struct & 0x0F
st_other_struct = self.unpack_byte()
st_other_padding = st_info_struct >> 3 & 63
st_other_visibility = st_other_struct & 0x07
elf_sym['st_info'] = {
'bind': st_info_bind,
@@ -150,73 +149,86 @@ class tinyELFFile(object):
return strn.decode('ascii')
def unpack_byte(self):
return struct.unpack(self.unpack_endian + "B", self.stream.read(1))[0]
return struct.unpack(self.unpack_endian + 'B', self.stream.read(1))[0]
def unpack_half(self):
return struct.unpack(self.unpack_endian + "H", self.stream.read(2))[0]
return struct.unpack(self.unpack_endian + 'H', self.stream.read(2))[0]
def unpack_word(self):
return struct.unpack(self.unpack_endian + "L", self.stream.read(4))[0]
return struct.unpack(self.unpack_endian + 'L', self.stream.read(4))[0]
def unpack_word64(self):
return struct.unpack(self.unpack_endian + "Q", self.stream.read(8))[0]
return struct.unpack(self.unpack_endian + 'Q', self.stream.read(8))[0]
def unpack_addr(self):
if self.elfclass == 32:
return struct.unpack(self.unpack_endian + "L", self.stream.read(4))[0]
return (struct.unpack(self.unpack_endian
+ 'L', self.stream.read(4))[0])
else:
return struct.unpack(self.unpack_endian + "Q", self.stream.read(8))[0]
return (struct.unpack(self.unpack_endian
+ 'Q', self.stream.read(8))[0])
def unpack_offset(self):
return self.unpack_addr()
def unpack_sword(self):
return struct.unpack(self.unpack_endian + "l", self.stream.read(4))[0]
return struct.unpack(self.unpack_endian + 'l', self.stream.read(4))[0]
def unpack_xword(self):
if self.elfclass == 32:
return struct.unpack(self.unpack_endian + "L", self.stream.read(4))[0]
return (struct.unpack(self.unpack_endian
+ 'L', self.stream.read(4))[0])
else:
return struct.unpack(self.unpack_endian + "Q", self.stream.read(8))[0]
return (struct.unpack(self.unpack_endian
+ 'Q', self.stream.read(8))[0])
def unpack_sxword(self):
if self.elfclass == 32:
return struct.unpack(self.unpack_endian + "l", self.stream.read(4))[0]
return (struct.unpack(self.unpack_endian
+ 'l', self.stream.read(4))[0])
else:
return struct.unpack(self.unpack_endian + "q", self.stream.read(8))[0]
return (struct.unpack(self.unpack_endian
+ 'q', self.stream.read(8))[0])
def check_elf_built(f):
has_pi = False
has_sp = False
has_pi_flag = {
3: [8, ], # EM_386=3, R_386_RELATIVE=8,
62: [8, ], # EM_X86_64=62, R_X86_64_RELATIVE=8,
has_pi_fg = {
3: [8], # EM_386=3, R_386_RELATIVE=8,
62: [8], # EM_X86_64=62, R_X86_64_RELATIVE=8,
40: [23, 3], # EM_ARM=40, R_ARM_RELATIVE=23,R_ARM_REL32=3,
183: [1027, 3], # EM_AARCH64=183, R_AARCH64_RELATIVE=1027,R_ARM_REL32=3,
8: [3, ], # EM_MIPS=8, R_MIPS_REL32=3,
183: [1027, 3], # EM_AARCH64=183,
# R_AARCH64_RELATIVE=1027,R_ARM_REL32=3,
8: [3], # EM_MIPS=8, R_MIPS_REL32=3,
}
elffile = tinyELFFile(f)
elffile = TinyELFFile(f)
for i in range(elffile.header['e_shnum']):
section_header = elffile.decode_shdr(
elffile.header['e_shoff'] + i * elffile.header['e_shentsize'])
name = elffile.decode_string(
elffile._file_stringtable_section + section_header['sh_name'])
sectype = section_header['sh_type']
if sectype in (4, 9): # SHT_RELA=4,SHT_REL=9,
if section_header['sh_entsize'] > 0:
for i in range(section_header['sh_size'] // section_header['sh_entsize']):
siz = section_header['sh_size'] // section_header['sh_entsize']
for i in range(siz):
elffile.stream.seek(
section_header['sh_offset'] + i * section_header['sh_entsize'])
section_header['sh_offset']
+ i
* section_header['sh_entsize'])
if section_header['sh_type'] == 9:
entry = elffile.decode_rel(
section_header['sh_offset'] + i * section_header['sh_entsize'])
section_header['sh_offset']
+ i
* section_header['sh_entsize'])
elif section_header['sh_type'] == 4:
entry = elffile.decode_rela(
section_header['sh_offset'] + i * section_header['sh_entsize'])
section_header['sh_offset']
+ i
* section_header['sh_entsize'])
else:
continue
if entry['r_info_type'] in has_pi_flag.get(elffile.header['e_machine'], []):
if (entry['r_info_type']
in has_pi_fg.get(elffile.header['e_machine'], [])):
if entry['r_info_sym'] == 0:
has_pi = True
break
@@ -226,35 +238,35 @@ def check_elf_built(f):
def res_analysis(app_dir):
"""Perform the elf analysis."""
try:
logger.info("Static Android Resource Analysis Started")
logger.info('Static Android Resource Analysis Started')
elf_desc = {
'html_infected':
(
'Found html files infected by malware.',
'high',
'The built environment was probably infected by malware, The html file '
'used in this APK is infected.'
),
}
'The built environment was probably'
' infected by malware, The html file '
'used in this APK is infected.')}
html_an_dic = {}
for k in list(elf_desc.keys()):
html_an_dic[k] = []
resraw = os.path.join(app_dir, "res", "raw")
assets = os.path.join(app_dir, "assets")
resraw = os.path.join(app_dir, 'res', 'raw')
assets = os.path.join(app_dir, 'assets')
for resdir in (resraw, assets):
if os.path.exists(resdir) and os.path.isdir(resdir):
for pdir, dirl, filel in os.walk(resdir):
for pdir, _dirl, filel in os.walk(resdir):
for filename in filel:
if filename.endswith(".htm") or filename.endswith(".html"):
if (filename.endswith('.htm')
or filename.endswith('.html')):
try:
filepath = os.path.join(pdir, filename)
buf = ""
buf = ''
with io.open(filepath, mode='rb') as filp:
buf = filp.read()
if "svchost.exe" in buf:
if 'svchost.exe' in buf:
html_an_dic['html_infected'].append(
filepath.replace(app_dir, ""))
except Exception as e:
filepath.replace(app_dir, ''))
except Exception:
pass
res = []
for k, filelist in list(html_an_dic.items()):
@@ -263,48 +275,52 @@ def res_analysis(app_dir):
res.append({'title': descs[0],
'stat': descs[1],
'desc': descs[2],
'file': " ".join(filelist),
'file': ' '.join(filelist),
})
return res
except:
PrintException("Performing Resourse Analysis")
except Exception:
log_exception('Performing Resourse Analysis')
def elf_analysis(app_dir: str) -> list:
"""Perform the elf analysis."""
try:
logger.info("Static Android Binary Analysis Started")
logger.info('Static Android Binary Analysis Started')
fgs = ['nopie', 'nonpie', 'no-pie']
elf_desc = {
'elf_no_pi':
(
'Found elf built without Position Independent Executable (PIE) flag',
'Found elf built without Position Independent Executable'
' (PIE) flag',
'high',
'In order to prevent an attacker from reliably jumping to, for example, a particular'
' exploited function in memory, Address space layout randomization (ASLR) randomly '
'arranges the address space positions of key data areas of a process, including the '
'base of the executable and the positions of the stack, heap and libraries. Built with'
' option <strong>-pie</strong>.'
)
}
'In order to prevent an attacker from reliably jumping'
' to, for example, a particular'
' exploited function in memory, Address space layout'
' randomization (ASLR) randomly '
'arranges the address space positions of key data areas'
' of a process, including the '
'base of the executable and the positions of the stack,'
' heap and libraries. Built with'
' option <strong>-pie</strong>.')}
elf_an_dic = {}
for k in list(elf_desc.keys()):
elf_an_dic[k] = []
libdir = os.path.join(app_dir, "lib")
libdir = os.path.join(app_dir, 'lib')
if os.path.exists(libdir):
for pdir, dirl, filel in os.walk(libdir):
for filename in filel:
if filename.endswith(".so"):
for pdir, _dirl, filel in os.walk(libdir):
for fname in filel:
if fname.endswith('.so'):
try:
filepath = os.path.join(pdir, filename)
filepath = os.path.join(pdir, fname)
f = io.open(filepath, mode='rb')
has_pie, has_sg = check_elf_built(f)
f.close()
if not has_pie:
if not any(pie_str in ["nopie", "nonpie", "no-pie"] for pie_str in filename):
if not any(pie_st in fgs for pie_st in fname):
elf_an_dic['elf_no_pi'].append(
filepath.replace(libdir, "lib"))
except Exception as e:
filepath.replace(libdir, 'lib'))
except Exception:
pass
res = []
for k, filelist in list(elf_an_dic.items()):
@@ -313,9 +329,9 @@ def elf_analysis(app_dir: str) -> list:
res.append({'title': descs[0],
'stat': descs[1],
'desc': descs[2],
'file': " ".join(filelist),
'file': ' '.join(filelist),
})
return res
except:
PrintException("Performing Binary Analysis")
except Exception:
log_exception('Performing Binary Analysis')
+75 -61
View File
@@ -1,56 +1,61 @@
# -*- coding: utf_8 -*-
"""Module holding the functions for code analysis."""
import subprocess
import re
import binascii
import hashlib
import logging
import os
import re
import subprocess
from androguard.core.bytecodes.apk import APK
from androguard.util import get_certificate_name_string
from asn1crypto import keys, x509
from django.conf import settings
from django.utils.html import escape
from MobSF.utils import (
PrintException
)
from androguard.core.bytecodes.apk import APK
from androguard.util import get_certificate_name_string
import hashlib
import binascii
from asn1crypto import x509, keys
from MobSF.utils import log_exception
import logging
logger = logging.getLogger(__name__)
def get_hardcoded_cert_keystore(files):
"""Returns the hardcoded certificate keystore."""
try:
logger.info("Getting Hardcoded Certificates/Keystores")
logger.info('Getting Hardcoded Certificates/Keystores')
dat = ''
certz = ''
key_store = ''
for file_name in files:
ext = file_name.split('.')[-1]
if re.search("cer|pem|cert|crt|pub|key|pfx|p12", ext):
certz += escape(file_name) + "</br>"
if re.search("jks|bks", ext):
key_store += escape(file_name) + "</br>"
if re.search('cer|pem|cert|crt|pub|key|pfx|p12', ext):
certz += escape(file_name) + '</br>'
if re.search('jks|bks', ext):
key_store += escape(file_name) + '</br>'
if len(certz) > 1:
dat += (
"<tr><td>Certificate/Key Files Hardcoded inside the App.</td><td>" +
certz +
"</td><tr>"
'<tr><td>Certificate/Key Files Hardcoded'
+ ' inside the App.</td><td>'
+ certz
+ '</td><tr>'
)
if len(key_store) > 1:
dat += "<tr><td>Hardcoded Keystore Found.</td><td>" + key_store + "</td><tr>"
dat += (
'<tr><td>Hardcoded Keystore Found.</td><td>'
+ key_store
+ '</td><tr>'
)
return dat
except:
PrintException("Getting Hardcoded Certificates/Keystores")
except Exception:
log_exception('Getting Hardcoded Certificates/Keystores')
def cert_info(app_dir, app_file, tools_dir):
"""Return certificate information."""
try:
logger.info("Reading Code Signing Certificate")
logger.info('Reading Code Signing Certificate')
cert = os.path.join(app_dir, 'META-INF/')
cp_path = tools_dir + 'CertPrint.jar'
files = [f for f in os.listdir(
@@ -59,84 +64,93 @@ def cert_info(app_dir, app_file, tools_dir):
dat = ''
manidat = ''
manifestfile = None
if "CERT.RSA" in files:
certfile = os.path.join(cert, "CERT.RSA")
if 'CERT.RSA' in files:
certfile = os.path.join(cert, 'CERT.RSA')
else:
for file_name in files:
if file_name.lower().endswith(".rsa"):
if file_name.lower().endswith('.rsa'):
certfile = os.path.join(cert, file_name)
elif file_name.lower().endswith(".dsa"):
elif file_name.lower().endswith('.dsa'):
certfile = os.path.join(cert, file_name)
if certfile:
args = [settings.JAVA_PATH + 'java', '-jar', cp_path, certfile]
issued = 'good'
try:
dat = subprocess.check_output(args)
unicode_output = str(dat, encoding="utf-8", errors="replace")
unicode_output = str(dat, encoding='utf-8', errors='replace')
dat = escape(unicode_output).replace('\n', '</br>')
except:
except Exception:
dat = androguard_certinfo(app_dir, app_file)
else:
dat = 'No Code Signing Certificate Found!'
issued = 'missing'
if re.findall(r"CN=Android Debug", dat):
if re.findall(r'CN=Android Debug', dat):
issued = 'bad'
if re.findall(r"\[SHA1withRSA\]", dat):
if re.findall(r'\[SHA1withRSA\]', dat):
issued = 'bad hash'
if "MANIFEST.MF" in files:
manifestfile = os.path.join(cert, "MANIFEST.MF")
if 'MANIFEST.MF' in files:
manifestfile = os.path.join(cert, 'MANIFEST.MF')
if manifestfile:
with open(manifestfile, 'r', encoding='utf-8') as manifile:
manidat = manifile.read()
sha256Digest = bool(re.findall(r"SHA-256-Digest", manidat))
sha256_digest = bool(re.findall(r'SHA-256-Digest', manidat))
cert_dic = {
'cert_info': dat,
'issued': issued,
'sha256Digest': sha256Digest
'sha256Digest': sha256_digest,
}
return cert_dic
except:
PrintException("Reading Code Signing Certificate")
except Exception:
log_exception('Reading Code Signing Certificate')
def androguard_certinfo(app_dir, app_file):
"""Return certificate information."""
certlist = []
apk_file = os.path.join(app_dir, app_file)
hashfunctions = dict(md5=hashlib.md5,
sha1=hashlib.sha1,
sha256=hashlib.sha256,
sha512=hashlib.sha512,
)
hashfunctions = {
'md5': hashlib.md5,
'sha1': hashlib.sha1,
'sha256': hashlib.sha256,
'sha512': hashlib.sha512,
}
a = APK(apk_file)
certlist.append("v1: {}".format(a.is_signed_v1()))
certlist.append("v2: {}".format(a.is_signed_v2()))
certlist.append("v3: {}".format(a.is_signed_v3()))
certlist.append('v1: {}'.format(a.is_signed_v1()))
certlist.append('v2: {}'.format(a.is_signed_v2()))
certlist.append('v3: {}'.format(a.is_signed_v3()))
certs = set(a.get_certificates_der_v3() + a.get_certificates_der_v2() + [a.get_certificate_der(x) for x in a.get_signature_names()])
certs = set(a.get_certificates_der_v3() + a.get_certificates_der_v2()
+ [a.get_certificate_der(x) for x in a.get_signature_names()])
pkeys = set(a.get_public_keys_der_v3() + a.get_public_keys_der_v2())
for cert in certs:
x509_cert = x509.Certificate.load(cert)
certlist.append("Subject: {}".format(get_certificate_name_string(x509_cert.subject, short=True)))
certlist.append("Signature Algorithm: {}".format(x509_cert.signature_algo))
certlist.append("Valid From: {}".format(x509_cert['tbs_certificate']['validity']['not_before'].native))
certlist.append("Valid To: {}".format(x509_cert['tbs_certificate']['validity']['not_after'].native))
certlist.append("Issuer: {}".format(get_certificate_name_string(x509_cert.issuer, short=True)))
certlist.append("Serial Number: {}".format(hex(x509_cert.serial_number)))
certlist.append("Hash Algorithm: {}".format(x509_cert.hash_algo))
certlist.append('Subject: {}'.format(
get_certificate_name_string(x509_cert.subject, short=True)))
certlist.append('Signature Algorithm: {}'.format(
x509_cert.signature_algo))
certlist.append('Valid From: {}'.format(
x509_cert['tbs_certificate']['validity']['not_before'].native))
certlist.append('Valid To: {}'.format(
x509_cert['tbs_certificate']['validity']['not_after'].native))
certlist.append('Issuer: {}'.format(
get_certificate_name_string(x509_cert.issuer, short=True)))
certlist.append('Serial Number: {}'.format(
hex(x509_cert.serial_number)))
certlist.append('Hash Algorithm: {}'.format(x509_cert.hash_algo))
for k, v in hashfunctions.items():
certlist.append("{} {}".format(k, v(cert).hexdigest()))
certlist.append('{} {}'.format(k, v(cert).hexdigest()))
for public_key in pkeys:
x509_public_key = keys.PublicKeyInfo.load(public_key)
certlist.append("PublicKey Algorithm: {}".format(x509_public_key.algorithm))
certlist.append("Bit Size: {}".format(x509_public_key.bit_size))
certlist.append("Fingerprint: {}".format(binascii.hexlify(x509_public_key.fingerprint).decode("utf-8")))
certlist.append('PublicKey Algorithm: {}'.format(
x509_public_key.algorithm))
certlist.append('Bit Size: {}'.format(x509_public_key.bit_size))
certlist.append('Fingerprint: {}'.format(
binascii.hexlify(x509_public_key.fingerprint).decode('utf-8')))
try:
certlist.append("Hash Algorithm: {}".format(x509_public_key.hash_algo))
except ValueError as ve:
# RSA pkey does not have an hash algorithm
certlist.append('Hash Algorithm: {}'.format(
x509_public_key.hash_algo))
except ValueError:
pass
return '\n'.join(certlist)
+33 -38
View File
@@ -2,36 +2,27 @@
"""Module holding the functions for code analysis."""
import io
import shutil
import re
import logging
import os
import re
import shutil
from django.conf import settings
from django.utils.html import escape
from MobSF.utils import (
PrintException,
filename_from_path
)
from MobSF.utils import filename_from_path, log_exception
from StaticAnalyzer.views.shared_func import (
url_n_email_extract,
api_rule_matcher,
code_rule_matcher,
)
from StaticAnalyzer.views.android import android_apis, android_rules
from StaticAnalyzer.views.shared_func import (api_rule_matcher,
code_rule_matcher,
url_n_email_extract)
from StaticAnalyzer.views.android import (
android_rules,
android_apis
)
import logging
logger = logging.getLogger(__name__)
def code_analysis(app_dir, perms, typ):
"""Perform the code analysis."""
try:
logger.info("Static Android Code Analysis Started")
logger.info('Static Android Code Analysis Started')
api_rules = android_apis.APIS
code_rules = android_rules.RULES
code_findings = {}
@@ -39,54 +30,58 @@ def code_analysis(app_dir, perms, typ):
email_n_file = []
url_n_file = []
url_list = []
domains = {}
if typ == "apk":
if typ == 'apk':
java_src = os.path.join(app_dir, 'java_source/')
elif typ == "studio":
elif typ == 'studio':
java_src = os.path.join(app_dir, 'app/src/main/java/')
elif typ == "eclipse":
elif typ == 'eclipse':
java_src = os.path.join(app_dir, 'src/')
logger.info("Code Analysis Started on - " + filename_from_path(java_src))
logger.info('Code Analysis Started on - %s',
filename_from_path(java_src))
# pylint: disable=unused-variable
# Needed by os.walk
for dir_name, sub_dir, files in os.walk(java_src):
for dir_name, _sub_dir, files in os.walk(java_src):
for jfile in files:
jfile_path = os.path.join(java_src, dir_name, jfile)
if "+" in jfile:
if '+' in jfile:
p_2 = os.path.join(java_src, dir_name,
jfile.replace("+", "x"))
jfile.replace('+', 'x'))
shutil.move(jfile_path, p_2)
jfile_path = p_2
repath = dir_name.replace(java_src, '')
if (
jfile.endswith('.java') and
any(re.search(cls, repath)
for cls in settings.SKIP_CLASSES) is False
jfile.endswith('.java')
and any(re.search(cls, repath)
for cls in settings.SKIP_CLASSES) is False
):
dat = ''
with io.open(
jfile_path,
mode='r',
encoding="utf8",
errors="ignore"
encoding='utf8',
errors='ignore',
) as file_pointer:
dat = file_pointer.read()
# Code Analysis
# print "[INFO] Doing Code Analysis on - " + jfile_path
relative_java_path = jfile_path.replace(java_src, '')
code_rule_matcher(
code_findings, list(perms.keys()), dat, relative_java_path, code_rules)
code_findings,
list(perms.keys()),
dat,
relative_java_path,
code_rules)
# API Check
api_rule_matcher(api_findings, list(perms.keys()),
dat, relative_java_path, api_rules)
# Extract URLs and Emails
urls, urls_nf, emails_nf = url_n_email_extract(dat, relative_java_path)
# Extract URLs and Emails
urls, urls_nf, emails_nf = url_n_email_extract(
dat, relative_java_path)
url_list.extend(urls)
url_n_file.extend(urls_nf)
email_n_file.extend(emails_nf)
logger.info("Finished Code Analysis, Email and URL Extraction")
logger.info('Finished Code Analysis, Email and URL Extraction')
code_an_dic = {
'api': api_findings,
'findings': code_findings,
@@ -95,5 +90,5 @@ def code_analysis(app_dir, perms, typ):
'emails': email_n_file,
}
return code_an_dic
except:
PrintException("Performing Code Analysis")
except Exception:
log_exception('Performing Code Analysis')
+88 -74
View File
@@ -2,165 +2,179 @@
"""Module holding the functions for converting."""
import glob
import logging
import os
import platform
import subprocess
from django.conf import settings
from MobSF.utils import (
PrintException,
isFileExists,
isDirExists,
get_python,
filename_from_path
)
from StaticAnalyzer.views.android.win_fixes import (
win_fix_python3,
win_fix_java
)
import logging
from MobSF.utils import (filename_from_path, get_python, is_dir_exists,
is_file_exists, log_exception)
from StaticAnalyzer.views.android.win_fixes import (win_fix_java,
win_fix_python3)
logger = logging.getLogger(__name__)
def get_dex_files(app_dir):
"""Get all Dex Files for analysis"""
glob_pattern = app_dir + "*.dex"
"""Get all Dex Files for analysis."""
glob_pattern = app_dir + '*.dex'
return glob.glob(glob_pattern)
def get_jar_files(app_dir):
"""Get all Dex Files for analysis"""
glob_pattern = app_dir + "*.jar"
"""Get all Dex Files for analysis."""
glob_pattern = app_dir + '*.jar'
return glob.glob(glob_pattern)
def dex_2_jar(app_path, app_dir, tools_dir):
"""Run dex2jar."""
try:
logger.info("DEX -> JAR")
logger.info('DEX -> JAR')
working_dir = None
args = []
if settings.JAR_CONVERTER == "d2j":
logger.info("Using JAR converter - dex2jar")
if settings.JAR_CONVERTER == 'd2j':
logger.info('Using JAR converter - dex2jar')
dexes = get_dex_files(app_dir)
for idx, dex in enumerate(dexes):
logger.info("Converting " +
filename_from_path(dex) + " to JAR")
if len(settings.DEX2JAR_BINARY) > 0 and isFileExists(settings.DEX2JAR_BINARY):
logger.info('Converting %s to JAR',
filename_from_path(dex))
if (len(settings.DEX2JAR_BINARY) > 0
and is_file_exists(settings.DEX2JAR_BINARY)):
d2j = settings.DEX2JAR_BINARY
else:
if platform.system() == "Windows":
if platform.system() == 'Windows':
win_fix_java(tools_dir)
d2j = os.path.join(tools_dir, 'd2j2/d2j-dex2jar.bat')
else:
inv = os.path.join(tools_dir, 'd2j2/d2j_invoke.sh')
d2j = os.path.join(tools_dir, 'd2j2/d2j-dex2jar.sh')
subprocess.call(["chmod", "777", d2j])
subprocess.call(["chmod", "777", inv])
subprocess.call(['chmod', '777', d2j])
subprocess.call(['chmod', '777', inv])
args = [
d2j,
dex,
'-f',
'-o',
app_dir + 'classes' + str(idx) + '.jar'
app_dir + 'classes' + str(idx) + '.jar',
]
subprocess.call(args)
elif settings.JAR_CONVERTER == "enjarify":
logger.info("Using JAR converter - Google enjarify")
if len(settings.ENJARIFY_DIRECTORY) > 0 and isDirExists(settings.ENJARIFY_DIRECTORY):
elif settings.JAR_CONVERTER == 'enjarify':
logger.info('Using JAR converter - Google enjarify')
if (len(settings.ENJARIFY_DIRECTORY) > 0
and is_dir_exists(settings.ENJARIFY_DIRECTORY)):
working_dir = settings.ENJARIFY_DIRECTORY
else:
working_dir = os.path.join(tools_dir, 'enjarify/')
if platform.system() == "Windows":
if platform.system() == 'Windows':
win_fix_python3(tools_dir)
enjarify = os.path.join(working_dir, 'enjarify.bat')
args = [enjarify, app_path, "-f",
"-o", app_dir + 'classes.jar']
args = [enjarify, app_path, '-f',
'-o', app_dir + 'classes.jar']
else:
if len(settings.PYTHON3_PATH) > 2:
python3 = os.path.join(settings.PYTHON3_PATH, "python3")
python3 = os.path.join(settings.PYTHON3_PATH, 'python3')
else:
python3 = get_python()
args = [
python3,
"-O",
"-m",
"enjarify.main",
'-O',
'-m',
'enjarify.main',
app_path,
"-f",
"-o",
app_dir + 'classes.jar'
'-f',
'-o',
app_dir + 'classes.jar',
]
subprocess.call(args, cwd=working_dir)
except:
PrintException("Converting Dex to JAR")
except Exception:
log_exception('Converting Dex to JAR')
def dex_2_smali(app_dir, tools_dir):
"""Run dex2smali"""
"""Run dex2smali."""
try:
logger.info("DEX -> SMALI")
logger.info('DEX -> SMALI')
dexes = get_dex_files(app_dir)
for dex_path in dexes:
logger.info("Converting " +
filename_from_path(dex_path) + " to Smali Code")
if len(settings.BACKSMALI_BINARY) > 0 and isFileExists(settings.BACKSMALI_BINARY):
logger.info('Converting %s to Smali Code',
filename_from_path(dex_path))
if (len(settings.BACKSMALI_BINARY) > 0
and is_file_exists(settings.BACKSMALI_BINARY)):
bs_path = settings.BACKSMALI_BINARY
else:
bs_path = os.path.join(tools_dir, 'baksmali-2.2.7.jar')
output = os.path.join(app_dir, 'smali_source/')
args = [
settings.JAVA_PATH + 'java',
'-jar', bs_path, 'd', dex_path, '-o', output
'-jar',
bs_path,
'd',
dex_path,
'-o',
output,
]
subprocess.call(args)
except:
PrintException("Converting DEX to SMALI")
except Exception:
log_exception('Converting DEX to SMALI')
def jar_2_java(app_dir, tools_dir):
"""Conver jar to java."""
try:
logger.info("JAR -> JAVA")
logger.info('JAR -> JAVA')
jar_files = get_jar_files(app_dir)
output = os.path.join(app_dir, 'java_source/')
for jar_path in jar_files:
logger.info("Decompiling {} to Java Code".format(
filename_from_path(jar_path)))
logger.info(
'Decompiling %s to Java Code',
filename_from_path(jar_path))
if settings.DECOMPILER == 'jd-core':
if (
len(settings.JD_CORE_DECOMPILER_BINARY) > 0 and
isFileExists(settings.JD_CORE_DECOMPILER_BINARY)
):
jd_path = settings.JD_CORE_DECOMPILER_BINARY
ext_jdcore = settings.JD_CORE_DECOMPILER_BINARY
if (len(ext_jdcore) > 0
and is_file_exists(ext_jdcore)):
jd_path = ext_jdcore
else:
jd_path = os.path.join(tools_dir, 'jd-core.jar')
args = [settings.JAVA_PATH + 'java',
'-jar', jd_path, jar_path, output]
'-jar',
jd_path,
jar_path,
output]
elif settings.DECOMPILER == 'cfr':
if (
len(settings.CFR_DECOMPILER_BINARY) > 0 and
isFileExists(settings.CFR_DECOMPILER_BINARY)
):
jd_path = settings.CFR_DECOMPILER_BINARY
ext_cfr = settings.CFR_DECOMPILER_BINARY
if (len(ext_cfr) > 0
and is_file_exists(ext_cfr)):
jd_path = ext_cfr
else:
jd_path = os.path.join(tools_dir, 'cfr-0.144.jar')
args = [settings.JAVA_PATH + 'java', '-jar',
jd_path, jar_path, '--outputdir', output, '--silent', 'true']
elif settings.DECOMPILER == "procyon":
if (
len(settings.PROCYON_DECOMPILER_BINARY) > 0 and
isFileExists(settings.PROCYON_DECOMPILER_BINARY)
):
pd_path = settings.PROCYON_DECOMPILER_BINARY
args = [settings.JAVA_PATH + 'java',
'-jar',
jd_path,
jar_path,
'--outputdir',
output,
'--silent',
'true']
elif settings.DECOMPILER == 'procyon':
ext_proc = settings.PROCYON_DECOMPILER_BINARY
if (len(ext_proc) > 0
and is_file_exists(ext_proc)):
pd_path = ext_proc
else:
pd_path = os.path.join(
tools_dir, 'procyon-decompiler-0.5.34.jar')
args = [settings.JAVA_PATH + 'java',
'-jar', pd_path, jar_path, '-o', output]
'-jar',
pd_path,
jar_path,
'-o',
output]
subprocess.call(args)
except:
PrintException("Converting JAR to JAVA")
except Exception:
log_exception('Converting JAR to JAVA')
+57 -36
View File
@@ -1,22 +1,22 @@
# -*- coding: utf_8 -*-
from django.db.models import QuerySet
"""Module holding the functions for the db."""
import logging
from MobSF.utils import (
PrintException,
python_list,
python_dict
)
from django.db.models import QuerySet
from MobSF.utils import log_exception, python_dict, python_list
from StaticAnalyzer.models import StaticAnalyzerAndroid
import logging
"""Module holding the functions for the db."""
logger = logging.getLogger(__name__)
def get_context_from_db_entry(db_entry: QuerySet) -> dict:
"""Return the context for APK/ZIP from DB"""
"""Return the context for APK/ZIP from DB."""
try:
logger.info("Analysis is already Done. Fetching data from the DB...")
logger.info('Analysis is already Done. Fetching data from the DB...')
context = {
'title': db_entry[0].TITLE,
@@ -70,12 +70,19 @@ def get_context_from_db_entry(db_entry: QuerySet) -> dict:
'trackers': python_dict(db_entry[0].TRACKERS),
}
return context
except:
PrintException("Fetching from DB")
except Exception:
log_exception('Fetching from DB')
def get_context_from_analysis(app_dic, man_data_dic, man_an_dic, code_an_dic, cert_dic, bin_anal, apk_id, trackers) -> dict:
"""Get the context for APK/ZIP from analysis results"""
def get_context_from_analysis(app_dic,
man_data_dic,
man_an_dic,
code_an_dic,
cert_dic,
bin_anal,
apk_id,
trackers) -> dict:
"""Get the context for APK/ZIP from analysis results."""
try:
context = {
'title': 'Static Analysis',
@@ -119,22 +126,29 @@ def get_context_from_analysis(app_dic, man_data_dic, man_an_dic, code_an_dic, ce
'strings': app_dic['strings'],
'zipped': app_dic['zipped'],
'mani': app_dic['mani'],
'e_act': man_an_dic['exported_cnt']["act"],
'e_ser': man_an_dic['exported_cnt']["ser"],
'e_bro': man_an_dic['exported_cnt']["bro"],
'e_cnt': man_an_dic['exported_cnt']["cnt"],
'e_act': man_an_dic['exported_cnt']['act'],
'e_ser': man_an_dic['exported_cnt']['ser'],
'e_bro': man_an_dic['exported_cnt']['bro'],
'e_cnt': man_an_dic['exported_cnt']['cnt'],
'apkid': apk_id,
'play_details': app_dic['playstore'],
'firebase': code_an_dic['firebase'],
'trackers': trackers,
}
return context
except:
PrintException("Rendering to Template")
except Exception:
log_exception('Rendering to Template')
def update_db_entry(app_dic, man_data_dic, man_an_dic, code_an_dic, cert_dic, bin_anal, apk_id, trackers) -> None:
"""Update an APK/ZIP DB entry"""
def update_db_entry(app_dic,
man_data_dic,
man_an_dic,
code_an_dic,
cert_dic,
bin_anal,
apk_id,
trackers) -> None:
"""Update an APK/ZIP DB entry."""
try:
# pylint: disable=E1101
StaticAnalyzerAndroid.objects.filter(MD5=app_dic['md5']).update(
@@ -180,21 +194,28 @@ def update_db_entry(app_dic, man_data_dic, man_an_dic, code_an_dic, cert_dic, bi
ZIPPED=app_dic['zipped'],
MANI=app_dic['mani'],
EXPORTED_ACT=man_an_dic['exported_act'],
E_ACT=man_an_dic['exported_cnt']["act"],
E_SER=man_an_dic['exported_cnt']["ser"],
E_BRO=man_an_dic['exported_cnt']["bro"],
E_CNT=man_an_dic['exported_cnt']["cnt"],
E_ACT=man_an_dic['exported_cnt']['act'],
E_SER=man_an_dic['exported_cnt']['ser'],
E_BRO=man_an_dic['exported_cnt']['bro'],
E_CNT=man_an_dic['exported_cnt']['cnt'],
APK_ID=apk_id,
PLAY_DETAILS=app_dic['playstore'],
FIREBASE=code_an_dic['firebase'],
TRACKERS=trackers,
)
except:
PrintException("Updating DB")
except Exception:
log_exception('Updating DB')
def create_db_entry(app_dic, man_data_dic, man_an_dic, code_an_dic, cert_dic, bin_anal, apk_id, trackers) -> None:
"""Create a new DB-Entry for APK/ZIP"""
def create_db_entry(app_dic,
man_data_dic,
man_an_dic,
code_an_dic,
cert_dic,
bin_anal,
apk_id,
trackers) -> None:
"""Create a new DB-Entry for APK/ZIP."""
try:
static_db = StaticAnalyzerAndroid(
TITLE='Static Analysis',
@@ -239,15 +260,15 @@ def create_db_entry(app_dic, man_data_dic, man_an_dic, code_an_dic, cert_dic, bi
ZIPPED=app_dic['zipped'],
MANI=app_dic['mani'],
EXPORTED_ACT=man_an_dic['exported_act'],
E_ACT=man_an_dic['exported_cnt']["act"],
E_SER=man_an_dic['exported_cnt']["ser"],
E_BRO=man_an_dic['exported_cnt']["bro"],
E_CNT=man_an_dic['exported_cnt']["cnt"],
E_ACT=man_an_dic['exported_cnt']['act'],
E_SER=man_an_dic['exported_cnt']['ser'],
E_BRO=man_an_dic['exported_cnt']['bro'],
E_CNT=man_an_dic['exported_cnt']['cnt'],
APK_ID=apk_id,
PLAY_DETAILS=app_dic['playstore'],
FIREBASE=code_an_dic['firebase'],
TRACKERS=trackers,
)
static_db.save()
except:
PrintException("Saving to DB")
except Exception:
log_exception('Saving to DB')
+213 -215
View File
@@ -13,226 +13,224 @@
# along with Androguard. If not, see <http://www.gnu.org/licenses/>.
# frameworks/base/core/res/AndroidManifest.xml
########################################## PERMISSIONS ###################
# ######################################### PERMISSIONS ###################
# flake8: noqa
DVM_PERMISSIONS = {
"MANIFEST_PERMISSION": {
"SEND_SMS": ["dangerous", "send SMS messages", "Allows application to send SMS messages. Malicious applications may cost you money by sending messages without your confirmation."],
"SEND_SMS_NO_CONFIRMATION": ["dangerous", "send SMS messages", "send SMS messages via the Messaging app with no user input or confirmation"],
"CALL_PHONE": ["dangerous", "directly call phone numbers", "Allows the application to call phone numbers without your intervention. Malicious applications may cause unexpected calls on your phone bill. Note that this does not allow the application to call emergency numbers."],
"RECEIVE_SMS": ["dangerous", "receive SMS", "Allows application to receive and process SMS messages. Malicious applications may monitor your messages or delete them without showing them to you."],
"RECEIVE_MMS": ["dangerous", "receive MMS", "Allows application to receive and process MMS messages. Malicious applications may monitor your messages or delete them without showing them to you."],
"READ_SMS": ["dangerous", "read SMS or MMS", "Allows application to read SMS messages stored on your phone or SIM card. Malicious applications may read your confidential messages."],
"WRITE_SMS": ["dangerous", "edit SMS or MMS", "Allows application to write to SMS messages stored on your phone or SIM card. Malicious applications may delete your messages."],
"RECEIVE_WAP_PUSH": ["dangerous", "receive WAP", "Allows application to receive and process WAP messages. Malicious applications may monitor your messages or delete them without showing them to you."],
"READ_CONTACTS": ["dangerous", "read contact data", "Allows an application to read all of the contact (address) data stored on your phone. Malicious applications can use this to send your data to other people."],
"WRITE_CONTACTS": ["dangerous", "write contact data", "Allows an application to modify the contact (address) data stored on your phone. Malicious applications can use this to erase or modify your contact data."],
"READ_PROFILE": ["dangerous", "read the user's personal profile data", "Allows an application to read the user's personal profile data."],
"WRITE_PROFILE": ["dangerous", "write the user's personal profile data", "Allows an application to write (but not read) the user's personal profile data."],
"READ_SOCIAL_STREAM": ["dangerous", "read from the user's social stream", "Allows an application to read from the user's social stream."],
"WRITE_SOCIAL_STREAM": ["dangerous", "write the user's social stream", "Allows an application to write (but not read) the user's social stream data."],
"READ_CALENDAR": ["dangerous", "read calendar events", "Allows an application to read all of the calendar events stored on your phone. Malicious applications can use this to send your calendar events to other people."],
"WRITE_CALENDAR": ["dangerous", "add or modify calendar events and send emails to guests", "Allows an application to add or change the events on your calendar, which may send emails to guests. Malicious applications can use this to erase or modify your calendar events or to send emails to guests."],
"READ_USER_DICTIONARY": ["dangerous", "read user-defined dictionary", "Allows an application to read any private words, names and phrases that the user may have stored in the user dictionary."],
"WRITE_USER_DICTIONARY": ["normal", "write to user-defined dictionary", "Allows an application to write new words into the user dictionary."],
"READ_HISTORY_BOOKMARKS": ["dangerous", "read Browser\'s history and bookmarks", "Allows the application to read all the URLs that the browser has visited and all of the browser\'s bookmarks."],
"WRITE_HISTORY_BOOKMARKS": ["dangerous", "write Browser\'s history and bookmarks", "Allows an application to modify the browser\'s history or bookmarks stored on your phone. Malicious applications can use this to erase or modify your browser\'s data."],
"SET_ALARM": ["normal", "set alarm in alarm clock", "Allows the application to set an alarm in an installed alarm clock application. Some alarm clock applications may not implement this feature."],
"ACCESS_FINE_LOCATION": ["dangerous", "fine (GPS) location", "Access fine location sources, such as the Global Positioning System on the phone, where available. Malicious applications can use this to determine where you are and may consume additional battery power."],
"ACCESS_COARSE_LOCATION": ["dangerous", "coarse (network-based) location", "Access coarse location sources, such as the mobile network database, to determine an approximate phone location, where available. Malicious applications can use this to determine approximately where you are."],
"ACCESS_MOCK_LOCATION": ["dangerous", "mock location sources for testing", "Create mock location sources for testing. Malicious applications can use this to override the location and/or status returned by real-location sources such as GPS or Network providers."],
"ACCESS_LOCATION_EXTRA_COMMANDS": ["normal", "access extra location provider commands", "Access extra location provider commands. Malicious applications could use this to interfere with the operation of the GPS or other location sources."],
"INSTALL_LOCATION_PROVIDER": ["signatureOrSystem", "permission to install a location provider", "Create mock location sources for testing. Malicious applications can use this to override the location and/or status returned by real-location sources such as GPS or Network providers, or monitor and report your location to an external source."],
"INTERNET": ["dangerous", "full Internet access", "Allows an application to create network sockets."],
"ACCESS_NETWORK_STATE": ["normal", "view network status", "Allows an application to view the status of all networks."],
"ACCESS_WIFI_STATE": ["normal", "view Wi-Fi status", "Allows an application to view the information about the status of Wi-Fi."],
"BLUETOOTH": ["dangerous", "create Bluetooth connections", "Allows an application to view configuration of the local Bluetooth phone and to make and accept connections with paired devices."],
"NFC": ["dangerous", "control Near-Field Communication", "Allows an application to communicate with Near-Field Communication (NFC) tags, cards and readers."],
"USE_SIP": ["dangerous", "make/receive Internet calls", "Allows an application to use the SIP service to make/receive Internet calls."],
"ACCOUNT_MANAGER": ["signature", "act as the Account Manager Service", "Allows an application to make calls to Account Authenticators"],
"GET_ACCOUNTS": ["normal", "discover known accounts", "Allows an application to access the list of accounts known by the phone."],
"AUTHENTICATE_ACCOUNTS": ["dangerous", "act as an account authenticator", "Allows an application to use the account authenticator capabilities of the Account Manager, including creating accounts as well as obtaining and setting their passwords."],
"USE_CREDENTIALS": ["dangerous", "use the authentication credentials of an account", "Allows an application to request authentication tokens."],
"MANAGE_ACCOUNTS": ["dangerous", "manage the accounts list", "Allows an application to perform operations like adding and removing accounts and deleting their password."],
"MODIFY_AUDIO_SETTINGS": ["dangerous", "change your audio settings", "Allows application to modify global audio settings, such as volume and routing."],
"RECORD_AUDIO": ["dangerous", "record audio", "Allows application to access the audio record path."],
"CAMERA": ["dangerous", "take pictures and videos", "Allows application to take pictures and videos with the camera. This allows the application to collect images that the camera is seeing at any time."],
"VIBRATE": ["normal", "control vibrator", "Allows the application to control the vibrator."],
"FLASHLIGHT": ["normal", "control flashlight", "Allows the application to control the flashlight."],
"ACCESS_USB": ["signatureOrSystem", "access USB devices", "Allows the application to access USB devices."],
"HARDWARE_TEST": ["signature", "test hardware", "Allows the application to control various peripherals for the purpose of hardware testing."],
"PROCESS_OUTGOING_CALLS": ["dangerous", "intercept outgoing calls", "Allows application to process outgoing calls and change the number to be dialled. Malicious applications may monitor, redirect or prevent outgoing calls."],
"MODIFY_PHONE_STATE": ["signatureOrSystem", "modify phone status", "Allows the application to control the phone features of the device. An application with this permission can switch networks, turn the phone radio on and off and the like, without ever notifying you."],
"READ_PHONE_STATE": ["dangerous", "read phone state and identity", "Allows the application to access the phone features of the device. An application with this permission can determine the phone number and serial number of this phone, whether a call is active, the number that call is connected to and so on."],
"WRITE_EXTERNAL_STORAGE": ["dangerous", "read/modify/delete SD card contents", "Allows an application to write to the SD card."],
"READ_EXTERNAL_STORAGE": ["dangerous", "read SD card contents", "Allows an application to read from SD Card."],
"WRITE_SETTINGS": ["dangerous", "modify global system settings", "Allows an application to modify the system\'s settings data. Malicious applications can corrupt your system\'s configuration."],
"WRITE_SECURE_SETTINGS": ["signatureOrSystem", "modify secure system settings", "Allows an application to modify the system\'s secure settings data. Not for use by common applications."],
"WRITE_GSERVICES": ["signatureOrSystem", "modify the Google services map", "Allows an application to modify the Google services map. Not for use by common applications."],
"EXPAND_STATUS_BAR": ["normal", "expand/collapse status bar", "Allows application to expand or collapse the status bar."],
"GET_TASKS": ["dangerous", "retrieve running applications", "Allows application to retrieve information about currently and recently running tasks. May allow malicious applications to discover private information about other applications."],
"REORDER_TASKS": ["dangerous", "reorder applications running", "Allows an application to move tasks to the foreground and background. Malicious applications can force themselves to the front without your control."],
"CHANGE_CONFIGURATION": ["dangerous", "change your UI settings", "Allows an application to change the current configuration, such as the locale or overall font size."],
"RESTART_PACKAGES": ["normal", "kill background processes", "Allows an application to kill background processes of other applications, even if memory is not low."],
"KILL_BACKGROUND_PROCESSES": ["normal", "kill background processes", "Allows an application to kill background processes of other applications, even if memory is not low."],
"FORCE_STOP_PACKAGES": ["signature", "force-stop other applications", "Allows an application to stop other applications forcibly."],
"DUMP": ["signatureOrSystem", "retrieve system internal status", "Allows application to retrieve internal status of the system. Malicious applications may retrieve a wide variety of private and secure information that they should never commonly need."],
"SYSTEM_ALERT_WINDOW": ["dangerous", "display system-level alerts", "Allows an application to show system-alert windows. Malicious applications can take over the entire screen of the phone."],
"SET_ANIMATION_SCALE": ["dangerous", "modify global animation speed", "Allows an application to change the global animation speed (faster or slower animations) at any time."],
"PERSISTENT_ACTIVITY": ["dangerous", "make application always run", "Allows an application to make parts of itself persistent, so that the system can\'t use it for other applications."],
"GET_PACKAGE_SIZE": ["normal", "measure application storage space", "Allows an application to retrieve its code, data and cache sizes"],
"SET_PREFERRED_APPLICATIONS": ["signature", "set preferred applications", "Allows an application to modify your preferred applications. This can allow malicious applications to silently change the applications that are run, spoofing your existing applications to collect private data from you."],
"RECEIVE_BOOT_COMPLETED": ["normal", "automatically start at boot", "Allows an application to start itself as soon as the system has finished booting. This can make it take longer to start the phone and allow the application to slow down the overall phone by always running."],
"BROADCAST_STICKY": ["normal", "send sticky broadcast", "Allows an application to send sticky broadcasts, which remain after the broadcast ends. Malicious applications can make the phone slow or unstable by causing it to use too much memory."],
"WAKE_LOCK": ["dangerous", "prevent phone from sleeping", "Allows an application to prevent the phone from going to sleep."],
"SET_WALLPAPER": ["normal", "set wallpaper", "Allows the application to set the system wallpaper."],
"SET_WALLPAPER_HINTS": ["normal", "set wallpaper size hints", "Allows the application to set the system wallpaper size hints."],
"SET_TIME": ["signatureOrSystem", "set time", "Allows an application to change the phone\'s clock time."],
"SET_TIME_ZONE": ["dangerous", "set time zone", "Allows an application to change the phone\'s time zone."],
"MOUNT_UNMOUNT_FILESYSTEMS": ["dangerous", "mount and unmount file systems", "Allows the application to mount and unmount file systems for removable storage."],
"MOUNT_FORMAT_FILESYSTEMS": ["dangerous", "format external storage", "Allows the application to format removable storage."],
"ASEC_ACCESS": ["signature", "get information on internal storage", "Allows the application to get information on internal storage."],
"ASEC_CREATE": ["signature", "create internal storage", "Allows the application to create internal storage."],
"ASEC_DESTROY": ["signature", "destroy internal storage", "Allows the application to destroy internal storage."],
"ASEC_MOUNT_UNMOUNT": ["signature", "mount/unmount internal storage", "Allows the application to mount/unmount internal storage."],
"ASEC_RENAME": ["signature", "rename internal storage", "Allows the application to rename internal storage."],
"DISABLE_KEYGUARD": ["dangerous", "disable key lock", "Allows an application to disable the key lock and any associated password security. A legitimate example of this is the phone disabling the key lock when receiving an incoming phone call, then re-enabling the key lock when the call is finished."],
"READ_SYNC_SETTINGS": ["normal", "read sync settings", "Allows an application to read the sync settings, such as whether sync is enabled for Contacts."],
"WRITE_SYNC_SETTINGS": ["dangerous", "write sync settings", "Allows an application to modify the sync settings, such as whether sync is enabled for Contacts."],
"READ_SYNC_STATS": ["normal", "read sync statistics", "Allows an application to read the sync stats; e.g. the history of syncs that have occurred."],
"WRITE_APN_SETTINGS": ["dangerous", "write Access Point Name settings", "Allows an application to modify the APN settings, such as Proxy and Port of any APN."],
"SUBSCRIBED_FEEDS_READ": ["normal", "read subscribed feeds", "Allows an application to receive details about the currently synced feeds."],
"SUBSCRIBED_FEEDS_WRITE": ["dangerous", "write subscribed feeds", "Allows an application to modify your currently synced feeds. This could allow a malicious application to change your synced feeds."],
"CHANGE_NETWORK_STATE": ["dangerous", "change network connectivity", "Allows an application to change the state of network connectivity."],
"CHANGE_WIFI_STATE": ["dangerous", "change Wi-Fi status", "Allows an application to connect to and disconnect from Wi-Fi access points and to make changes to configured Wi-Fi networks."],
"CHANGE_WIFI_MULTICAST_STATE": ["dangerous", "allow Wi-Fi Multicast reception", "Allows an application to receive packets not directly addressed to your device. This can be useful when discovering services offered nearby. It uses more power than the non-multicast mode."],
"BLUETOOTH_ADMIN": ["dangerous", "bluetooth administration", "Allows an application to configure the local Bluetooth phone and to discover and pair with remote devices."],
"CLEAR_APP_CACHE": ["dangerous", "delete all application cache data", "Allows an application to free phone storage by deleting files in application cache directory. Access is usually very restricted to system process."],
"READ_LOGS": ["dangerous", "read sensitive log data", "Allows an application to read from the system\'s various log files. This allows it to discover general information about what you are doing with the phone, potentially including personal or private information."],
"SET_DEBUG_APP": ["dangerous", "enable application debugging", "Allows an application to turn on debugging for another application. Malicious applications can use this to kill other applications."],
"SET_PROCESS_LIMIT": ["dangerous", "limit number of running processes", "Allows an application to control the maximum number of processes that will run. Never needed for common applications."],
"SET_ALWAYS_FINISH": ["dangerous", "make all background applications close", "Allows an application to control whether activities are always finished as soon as they go to the background. Never needed for common applications."],
"SIGNAL_PERSISTENT_PROCESSES": ["dangerous", "send Linux signals to applications", "Allows application to request that the supplied signal be sent to all persistent processes."],
"DIAGNOSTIC": ["signature", "read/write to resources owned by diag", "Allows an application to read and write to any resource owned by the diag group; for example, files in /dev. This could potentially affect system stability and security. This should ONLY be used for hardware-specific diagnostics by the manufacturer or operator."],
"STATUS_BAR": ["signatureOrSystem", "disable or modify status bar", "Allows application to disable the status bar or add and remove system icons."],
"STATUS_BAR_SERVICE": ["signature", "status bar", "Allows the application to be the status bar."],
"FORCE_BACK": ["signature", "force application to close", "Allows an application to force any activity that is in the foreground to close and go back. Should never be needed for common applications."],
"UPDATE_DEVICE_STATS": ["signatureOrSystem", "modify battery statistics", "Allows the modification of collected battery statistics. Not for use by common applications."],
"INTERNAL_SYSTEM_WINDOW": ["signature", "display unauthorised windows", "Allows the creation of windows that are intended to be used by the internal system user interface. Not for use by common applications."],
"MANAGE_APP_TOKENS": ["signature", "manage application tokens", "Allows applications to create and manage their own tokens, bypassing their common Z-ordering. Should never be needed for common applications."],
"INJECT_EVENTS": ["signature", "press keys and control buttons", "Allows an application to deliver its own input events (key presses, etc.) to other applications. Malicious applications can use this to take over the phone."],
"SET_ACTIVITY_WATCHER": ["signature", "monitor and control all application launching", "Allows an application to monitor and control how the system launches activities. Malicious applications may compromise the system completely. This permission is needed only for development, never for common phone usage."],
"SHUTDOWN": ["signature", "partial shutdown", "Puts the activity manager into a shut-down state. Does not perform a complete shut down."],
"STOP_APP_SWITCHES": ["signature", "prevent app switches", "Prevents the user from switching to another application."],
"READ_INPUT_STATE": ["signature", "record what you type and actions that you take", "Allows applications to watch the keys that you press even when interacting with another application (such as entering a password). Should never be needed for common applications."],
"BIND_INPUT_METHOD": ["signature", "bind to an input method", "Allows the holder to bind to the top-level interface of an input method. Should never be needed for common applications."],
"BIND_WALLPAPER": ["signatureOrSystem", "bind to wallpaper", "Allows the holder to bind to the top-level interface of wallpaper. Should never be needed for common applications."],
"BIND_DEVICE_ADMIN": ["signature", "interact with device admin", "Allows the holder to send intents to a device administrator. Should never be needed for common applications."],
"SET_ORIENTATION": ["signature", "change screen orientation", "Allows an application to change the rotation of the screen at any time. Should never be needed for common applications."],
"INSTALL_PACKAGES": ["signatureOrSystem", "directly install applications", "Allows an application to install new or updated Android packages. Malicious applications can use this to add new applications with arbitrarily powerful permissions."],
"REQUEST_INSTALL_PACKAGES": ["dangerous", "Allows an application to request installing packages.", "Malicious applications can use this to try and trick users into installing additional malicious packages."],
"CLEAR_APP_USER_DATA": ["signature", "delete other applications\' data", "Allows an application to clear user data."],
"DELETE_CACHE_FILES": ["signatureOrSystem", "delete other applications\' caches", "Allows an application to delete cache files."],
"DELETE_PACKAGES": ["signatureOrSystem", "delete applications", "Allows an application to delete Android packages. Malicious applications can use this to delete important applications."],
"MOVE_PACKAGE": ["signatureOrSystem", "Move application resources", "Allows an application to move application resources from internal to external media and vice versa."],
"CHANGE_COMPONENT_ENABLED_STATE": ["signatureOrSystem", "enable or disable application components", "Allows an application to change whether or not a component of another application is enabled. Malicious applications can use this to disable important phone capabilities. It is important to be careful with permission, as it is possible to bring application components into an unusable, inconsistent or unstable state."],
"ACCESS_SURFACE_FLINGER": ["signature", "access SurfaceFlinger", "Allows application to use SurfaceFlinger low-level features."],
"READ_FRAME_BUFFER": ["signature", "read frame buffer", "Allows application to read the content of the frame buffer."],
"BRICK": ["signature", "permanently disable phone", "Allows the application to disable the entire phone permanently. This is very dangerous."],
"REBOOT": ["signatureOrSystem", "force phone reboot", "Allows the application to force the phone to reboot."],
"DEVICE_POWER": ["signature", "turn phone on or off", "Allows the application to turn the phone on or off."],
"FACTORY_TEST": ["signature", "run in factory test mode", "Run as a low-level manufacturer test, allowing complete access to the phone hardware. Only available when a phone is running in manufacturer test mode."],
"BROADCAST_PACKAGE_REMOVED": ["signature", "send package removed broadcast", "Allows an application to broadcast a notification that an application package has been removed. Malicious applications may use this to kill any other application running."],
"BROADCAST_SMS": ["signature", "send SMS-received broadcast", "Allows an application to broadcast a notification that an SMS message has been received. Malicious applications may use this to forge incoming SMS messages."],
"BROADCAST_WAP_PUSH": ["signature", "send WAP-PUSH-received broadcast", "Allows an application to broadcast a notification that a WAP-PUSH message has been received. Malicious applications may use this to forge MMS message receipt or to replace the content of any web page silently with malicious variants."],
"MASTER_CLEAR": ["signatureOrSystem", "reset system to factory defaults", "Allows an application to completely reset the system to its factory settings, erasing all data, configuration and installed applications."],
"CALL_PRIVILEGED": ["signatureOrSystem", "directly call any phone numbers", "Allows the application to call any phone number, including emergency numbers, without your intervention. Malicious applications may place unnecessary and illegal calls to emergency services."],
"PERFORM_CDMA_PROVISIONING": ["signatureOrSystem", "directly start CDMA phone setup", "Allows the application to start CDMA provisioning. Malicious applications may start CDMA provisioning unnecessarily"],
"CONTROL_LOCATION_UPDATES": ["signatureOrSystem", "control location update notifications", "Allows enabling/disabling location update notifications from the radio. Not for use by common applications."],
"ACCESS_CHECKIN_PROPERTIES": ["signatureOrSystem", "access check-in properties", "Allows read/write access to properties uploaded by the check-in service. Not for use by common applications."],
"PACKAGE_USAGE_STATS": ["signature", "update component usage statistics", "Allows the modification of collected component usage statistics. Not for use by common applications."],
"BATTERY_STATS": ["normal", "modify battery statistics", "Allows the modification of collected battery statistics. Not for use by common applications."],
"BACKUP": ["signatureOrSystem", "control system back up and restore", "Allows the application to control the system\'s back-up and restore mechanism. Not for use by common applications."],
"BIND_APPWIDGET": ["signatureOrSystem", "choose widgets", "Allows the application to tell the system which widgets can be used by which application. With this permission, applications can give access to personal data to other applications. Not for use by common applications."],
"CHANGE_BACKGROUND_DATA_SETTING": ["signature", "change background data usage setting", "Allows an application to change the background data usage setting."],
"GLOBAL_SEARCH": ["signatureOrSystem", "", ""],
"GLOBAL_SEARCH_CONTROL": ["signature", "", ""],
"SET_WALLPAPER_COMPONENT": ["signatureOrSystem", "", ""],
"ACCESS_CACHE_FILESYSTEM": ["signatureOrSystem", "access the cache file system", "Allows an application to read and write the cache file system."],
"COPY_PROTECTED_DATA": ["signature", "Allows to invoke default container service to copy content. Not for use by common applications.", "Allows to invoke default container service to copy content. Not for use by common applications."],
"C2D_MESSAGE": ["signature", "Allows cloud to device messaging", "Allows the application to receive push notifications."],
"ADD_VOICEMAIL": ["dangerous", "add voicemails into the system", "Allows an application to add voicemails into the system."],
"ACCEPT_HANDOVER": ["dangerous", "", "Allows a calling app to continue a call which was started in another app. An example is a video calling app that wants to continue a voice call on the user's mobile network."],
"ACCESS_NOTIFICATION_POLICY": ["normal", "", "Marker permission for applications that wish to access notification policy."],
"ANSWER_PHONE_CALLS": ["dangerous", "", "Allows the app to answer an incoming phone call."],
"BIND_ACCESSIBILITY_SERVICE": ["signature", "", "Must be required by an AccessibilityService, to ensure that only the system can bind to it."],
"BIND_AUTOFILL_SERVICE": ["signature", "", "Must be required by a AutofillService, to ensure that only the system can bind to it."],
"BIND_CARRIER_MESSAGING_SERVICE": ["normal", "", "The system process that is allowed to bind to services in carrier apps will have this permission."],
"BIND_CARRIER_SERVICES": ["signature", "", "The system process that is allowed to bind to services in carrier apps will have this permission. Carrier apps should use this permission to protect their services that only the system is allowed to bind to."],
"BIND_CHOOSER_TARGET_SERVICE": ["signature", "", "Must be required by a ChooserTargetService, to ensure that only the system can bind to it"],
"BIND_CONDITION_PROVIDER_SERVICE": ["signature", "", "Must be required by a ConditionProviderService, to ensure that only the system can bind to it"],
"BIND_DREAM_SERVICE": ["signature", "", "Must be required by an DreamService, to ensure that only the system can bind to it."],
"BIND_INCALL_SERVICE": ["signature", "", "Must be required by a InCallService, to ensure that only the system can bind to it."],
"BIND_MIDI_DEVICE_SERVICE": ["signature", "", "Must be required by an MidiDeviceService, to ensure that only the system can bind to it."],
"BIND_NFC_SERVICE": ["signature", "", "Must be required by a HostApduService or OffHostApduService to ensure that only the system can bind to it."],
"BIND_NOTIFICATION_LISTENER_SERVICE": ["signature", "", "Must be required by an NotificationListenerService, to ensure that only the system can bind to it."],
"BIND_PRINT_SERVICE": ["signature", "", "Must be required by a PrintService, to ensure that only the system can bind to it."],
"BIND_QUICK_SETTINGS_TILE": ["normal", "", "Allows an application to bind to third party quick settings tiles."],
"BIND_REMOTEVIEWS": ["normal", "", "Must be required by a RemoteViewsService, to ensure that only the system can bind to it."],
"BIND_SCREENING_SERVICE": ["signature", "", "Must be required by a CallScreeningService, to ensure that only the system can bind to it."],
"BIND_TELECOM_CONNECTION_SERVICE": ["signature", "", "Must be required by a ConnectionService, to ensure that only the system can bind to it."],
"BIND_TEXT_SERVICE": ["signature", "", "Must be required by a TextService (e.g. SpellCheckerService) to ensure that only the system can bind to it."],
"BIND_TV_INPUT": ["signature", "", "Must be required by a TvInputService to ensure that only the system can bind to it."],
"BIND_VISUAL_VOICEMAIL_SERVICE": ["signature", "", "Must be required by a link"],
"BIND_VOICE_INTERACTION": ["signature", "", "Must be required by a VoiceInteractionService, to ensure that only the system can bind to it."],
"BIND_VPN_SERVICE": ["signature", "", "Must be required by a VpnService, to ensure that only the system can bind to it."],
"BIND_VR_LISTENER_SERVICE": ["signature", "", "Must be required by an VrListenerService, to ensure that only the system can bind to it."],
"BLUETOOTH_PRIVILEGED": ["normal", "", "Allows applications to pair bluetooth devices without user interaction, and to allow or disallow phonebook access or message access. This is not available to third party applications."],
"BODY_SENSORS": ["dangerous", "", "Allows an application to access data from sensors that the user uses to measure what is happening inside his/her body, such as heart rate."],
"CAPTURE_AUDIO_OUTPUT": ["normal", "", "Allows an application to capture audio output."],
"CAPTURE_SECURE_VIDEO_OUTPUT": ["normal", "", "Allows an application to capture secure video output."],
"CAPTURE_VIDEO_OUTPUT": ["normal", "", "Allows an application to capture video output."],
"FOREGROUND_SERVICE": ["normal", "", "Allows a regular application to use"],
"GET_ACCOUNTS_PRIVILEGED": ["normal", "", "Allows access to the list of accounts in the Accounts Service."],
"INSTALL_SHORTCUT": ["normal", "", "Allows an application to install a shortcut in Launcher."],
"INSTANT_APP_FOREGROUND_SERVICE": ["normal", "", "Allows an instant app to create foreground services."],
"LOCATION_HARDWARE": ["normal", "", "Allows an application to use location features in hardware, such as the geofencing api."],
"MANAGE_DOCUMENTS": ["signature", "", "Allows an application to manage access to documents, usually as part of a document picker."],
"MANAGE_OWN_CALLS": ["normal", "", "Allows a calling application which manages it own calls through the self-managed"],
"MEDIA_CONTENT_CONTROL": ["normal", "", "Allows an application to know what content is playing and control its playback."],
"NFC_TRANSACTION_EVENT": ["normal", "", "Allows applications to receive NFC transaction events."],
"READ_CALL_LOG": ["dangerous", "", "Allows an application to read the user's call log."],
"READ_PHONE_NUMBERS": ["dangerous", "", "Allows read access to the device's phone number(s). This is a subset of the capabilities granted by"],
"READ_VOICEMAIL": ["signature", "", "Allows an application to read voicemails in the system."],
"REQUEST_COMPANION_RUN_IN_BACKGROUND": ["normal", "", "Allows a companion app to run in the background."],
"REQUEST_COMPANION_USE_DATA_IN_BACKGROUND": ["normal", "", "Allows a companion app to use data in the background."],
"REQUEST_DELETE_PACKAGES": ["normal", "", "Allows an application to request deleting packages. Apps targeting APIs"],
"REQUEST_IGNORE_BATTERY_OPTIMIZATIONS": ["normal", "", "Permission an application must hold in order to use"],
"SEND_RESPOND_VIA_MESSAGE": ["normal", "", "Allows an application (Phone) to send a request to other applications to handle the respond-via-message action during incoming calls."],
"TRANSMIT_IR": ["normal", "", "Allows using the device's IR transmitter, if available."],
"UNINSTALL_SHORTCUT": ["normal", "", "Don\'t use this permission in your app. This permission is no longer supported."],
"USE_BIOMETRIC": ["normal", "", "Allows an app to use device supported biometric modalities."],
"USE_FINGERPRINT": ["normal", "", "This constant was deprecated in API level 28. Applications should request USE_BIOMETRIC instead"],
"WRITE_CALL_LOG": ["dangerous", "", "Allows an application to write (but not read) the user's call log data."],
"WRITE_VOICEMAIL": ["signature", "", "Allows an application to modify and remove existing voicemails in the system."],
'MANIFEST_PERMISSION': {
'SEND_SMS': ['dangerous', 'send SMS messages', 'Allows application to send SMS messages. Malicious applications may cost you money by sending messages without your confirmation.'],
'SEND_SMS_NO_CONFIRMATION': ['dangerous', 'send SMS messages', 'send SMS messages via the Messaging app with no user input or confirmation'],
'CALL_PHONE': ['dangerous', 'directly call phone numbers', 'Allows the application to call phone numbers without your intervention. Malicious applications may cause unexpected calls on your phone bill. Note that this does not allow the application to call emergency numbers.'],
'RECEIVE_SMS': ['dangerous', 'receive SMS', 'Allows application to receive and process SMS messages. Malicious applications may monitor your messages or delete them without showing them to you.'],
'RECEIVE_MMS': ['dangerous', 'receive MMS', 'Allows application to receive and process MMS messages. Malicious applications may monitor your messages or delete them without showing them to you.'],
'READ_SMS': ['dangerous', 'read SMS or MMS', 'Allows application to read SMS messages stored on your phone or SIM card. Malicious applications may read your confidential messages.'],
'WRITE_SMS': ['dangerous', 'edit SMS or MMS', 'Allows application to write to SMS messages stored on your phone or SIM card. Malicious applications may delete your messages.'],
'RECEIVE_WAP_PUSH': ['dangerous', 'receive WAP', 'Allows application to receive and process WAP messages. Malicious applications may monitor your messages or delete them without showing them to you.'],
'READ_CONTACTS': ['dangerous', 'read contact data', 'Allows an application to read all of the contact (address) data stored on your phone. Malicious applications can use this to send your data to other people.'],
'WRITE_CONTACTS': ['dangerous', 'write contact data', 'Allows an application to modify the contact (address) data stored on your phone. Malicious applications can use this to erase or modify your contact data.'],
'READ_PROFILE': ['dangerous', 'read the user\'s personal profile data', 'Allows an application to read the user\'s personal profile data.'],
'WRITE_PROFILE': ['dangerous', 'write the user\'s personal profile data', 'Allows an application to write (but not read) the user\'s personal profile data.'],
'READ_SOCIAL_STREAM': ['dangerous', 'read from the user\'s social stream', 'Allows an application to read from the user\'s social stream.'],
'WRITE_SOCIAL_STREAM': ['dangerous', 'write the user\'s social stream', 'Allows an application to write (but not read) the user\'s social stream data.'],
'READ_CALENDAR': ['dangerous', 'read calendar events', 'Allows an application to read all of the calendar events stored on your phone. Malicious applications can use this to send your calendar events to other people.'],
'WRITE_CALENDAR': ['dangerous', 'add or modify calendar events and send emails to guests', 'Allows an application to add or change the events on your calendar, which may send emails to guests. Malicious applications can use this to erase or modify your calendar events or to send emails to guests.'],
'READ_USER_DICTIONARY': ['dangerous', 'read user-defined dictionary', 'Allows an application to read any private words, names and phrases that the user may have stored in the user dictionary.'],
'WRITE_USER_DICTIONARY': ['normal', 'write to user-defined dictionary', 'Allows an application to write new words into the user dictionary.'],
'READ_HISTORY_BOOKMARKS': ['dangerous', 'read Browser\'s history and bookmarks', 'Allows the application to read all the URLs that the browser has visited and all of the browser\'s bookmarks.'],
'WRITE_HISTORY_BOOKMARKS': ['dangerous', 'write Browser\'s history and bookmarks', 'Allows an application to modify the browser\'s history or bookmarks stored on your phone. Malicious applications can use this to erase or modify your browser\'s data.'],
'SET_ALARM': ['normal', 'set alarm in alarm clock', 'Allows the application to set an alarm in an installed alarm clock application. Some alarm clock applications may not implement this feature.'],
'ACCESS_FINE_LOCATION': ['dangerous', 'fine (GPS) location', 'Access fine location sources, such as the Global Positioning System on the phone, where available. Malicious applications can use this to determine where you are and may consume additional battery power.'],
'ACCESS_COARSE_LOCATION': ['dangerous', 'coarse (network-based) location', 'Access coarse location sources, such as the mobile network database, to determine an approximate phone location, where available. Malicious applications can use this to determine approximately where you are.'],
'ACCESS_MOCK_LOCATION': ['dangerous', 'mock location sources for testing', 'Create mock location sources for testing. Malicious applications can use this to override the location and/or status returned by real-location sources such as GPS or Network providers.'],
'ACCESS_LOCATION_EXTRA_COMMANDS': ['normal', 'access extra location provider commands', 'Access extra location provider commands. Malicious applications could use this to interfere with the operation of the GPS or other location sources.'],
'INSTALL_LOCATION_PROVIDER': ['signatureOrSystem', 'permission to install a location provider', 'Create mock location sources for testing. Malicious applications can use this to override the location and/or status returned by real-location sources such as GPS or Network providers, or monitor and report your location to an external source.'],
'INTERNET': ['dangerous', 'full Internet access', 'Allows an application to create network sockets.'],
'ACCESS_NETWORK_STATE': ['normal', 'view network status', 'Allows an application to view the status of all networks.'],
'ACCESS_WIFI_STATE': ['normal', 'view Wi-Fi status', 'Allows an application to view the information about the status of Wi-Fi.'],
'BLUETOOTH': ['dangerous', 'create Bluetooth connections', 'Allows an application to view configuration of the local Bluetooth phone and to make and accept connections with paired devices.'],
'NFC': ['dangerous', 'control Near-Field Communication', 'Allows an application to communicate with Near-Field Communication (NFC) tags, cards and readers.'],
'USE_SIP': ['dangerous', 'make/receive Internet calls', 'Allows an application to use the SIP service to make/receive Internet calls.'],
'ACCOUNT_MANAGER': ['signature', 'act as the Account Manager Service', 'Allows an application to make calls to Account Authenticators'],
'GET_ACCOUNTS': ['normal', 'discover known accounts', 'Allows an application to access the list of accounts known by the phone.'],
'AUTHENTICATE_ACCOUNTS': ['dangerous', 'act as an account authenticator', 'Allows an application to use the account authenticator capabilities of the Account Manager, including creating accounts as well as obtaining and setting their passwords.'],
'USE_CREDENTIALS': ['dangerous', 'use the authentication credentials of an account', 'Allows an application to request authentication tokens.'],
'MANAGE_ACCOUNTS': ['dangerous', 'manage the accounts list', 'Allows an application to perform operations like adding and removing accounts and deleting their password.'],
'MODIFY_AUDIO_SETTINGS': ['dangerous', 'change your audio settings', 'Allows application to modify global audio settings, such as volume and routing.'],
'RECORD_AUDIO': ['dangerous', 'record audio', 'Allows application to access the audio record path.'],
'CAMERA': ['dangerous', 'take pictures and videos', 'Allows application to take pictures and videos with the camera. This allows the application to collect images that the camera is seeing at any time.'],
'VIBRATE': ['normal', 'control vibrator', 'Allows the application to control the vibrator.'],
'FLASHLIGHT': ['normal', 'control flashlight', 'Allows the application to control the flashlight.'],
'ACCESS_USB': ['signatureOrSystem', 'access USB devices', 'Allows the application to access USB devices.'],
'HARDWARE_TEST': ['signature', 'test hardware', 'Allows the application to control various peripherals for the purpose of hardware testing.'],
'PROCESS_OUTGOING_CALLS': ['dangerous', 'intercept outgoing calls', 'Allows application to process outgoing calls and change the number to be dialled. Malicious applications may monitor, redirect or prevent outgoing calls.'],
'MODIFY_PHONE_STATE': ['signatureOrSystem', 'modify phone status', 'Allows the application to control the phone features of the device. An application with this permission can switch networks, turn the phone radio on and off and the like, without ever notifying you.'],
'READ_PHONE_STATE': ['dangerous', 'read phone state and identity', 'Allows the application to access the phone features of the device. An application with this permission can determine the phone number and serial number of this phone, whether a call is active, the number that call is connected to and so on.'],
'WRITE_EXTERNAL_STORAGE': ['dangerous', 'read/modify/delete SD card contents', 'Allows an application to write to the SD card.'],
'READ_EXTERNAL_STORAGE': ['dangerous', 'read SD card contents', 'Allows an application to read from SD Card.'],
'WRITE_SETTINGS': ['dangerous', 'modify global system settings', 'Allows an application to modify the system\'s settings data. Malicious applications can corrupt your system\'s configuration.'],
'WRITE_SECURE_SETTINGS': ['signatureOrSystem', 'modify secure system settings', 'Allows an application to modify the system\'s secure settings data. Not for use by common applications.'],
'WRITE_GSERVICES': ['signatureOrSystem', 'modify the Google services map', 'Allows an application to modify the Google services map. Not for use by common applications.'],
'EXPAND_STATUS_BAR': ['normal', 'expand/collapse status bar', 'Allows application to expand or collapse the status bar.'],
'GET_TASKS': ['dangerous', 'retrieve running applications', 'Allows application to retrieve information about currently and recently running tasks. May allow malicious applications to discover private information about other applications.'],
'REORDER_TASKS': ['dangerous', 'reorder applications running', 'Allows an application to move tasks to the foreground and background. Malicious applications can force themselves to the front without your control.'],
'CHANGE_CONFIGURATION': ['dangerous', 'change your UI settings', 'Allows an application to change the current configuration, such as the locale or overall font size.'],
'RESTART_PACKAGES': ['normal', 'kill background processes', 'Allows an application to kill background processes of other applications, even if memory is not low.'],
'KILL_BACKGROUND_PROCESSES': ['normal', 'kill background processes', 'Allows an application to kill background processes of other applications, even if memory is not low.'],
'FORCE_STOP_PACKAGES': ['signature', 'force-stop other applications', 'Allows an application to stop other applications forcibly.'],
'DUMP': ['signatureOrSystem', 'retrieve system internal status', 'Allows application to retrieve internal status of the system. Malicious applications may retrieve a wide variety of private and secure information that they should never commonly need.'],
'SYSTEM_ALERT_WINDOW': ['dangerous', 'display system-level alerts', 'Allows an application to show system-alert windows. Malicious applications can take over the entire screen of the phone.'],
'SET_ANIMATION_SCALE': ['dangerous', 'modify global animation speed', 'Allows an application to change the global animation speed (faster or slower animations) at any time.'],
'PERSISTENT_ACTIVITY': ['dangerous', 'make application always run', 'Allows an application to make parts of itself persistent, so that the system can\'t use it for other applications.'],
'GET_PACKAGE_SIZE': ['normal', 'measure application storage space', 'Allows an application to retrieve its code, data and cache sizes'],
'SET_PREFERRED_APPLICATIONS': ['signature', 'set preferred applications', 'Allows an application to modify your preferred applications. This can allow malicious applications to silently change the applications that are run, spoofing your existing applications to collect private data from you.'],
'RECEIVE_BOOT_COMPLETED': ['normal', 'automatically start at boot', 'Allows an application to start itself as soon as the system has finished booting. This can make it take longer to start the phone and allow the application to slow down the overall phone by always running.'],
'BROADCAST_STICKY': ['normal', 'send sticky broadcast', 'Allows an application to send sticky broadcasts, which remain after the broadcast ends. Malicious applications can make the phone slow or unstable by causing it to use too much memory.'],
'WAKE_LOCK': ['dangerous', 'prevent phone from sleeping', 'Allows an application to prevent the phone from going to sleep.'],
'SET_WALLPAPER': ['normal', 'set wallpaper', 'Allows the application to set the system wallpaper.'],
'SET_WALLPAPER_HINTS': ['normal', 'set wallpaper size hints', 'Allows the application to set the system wallpaper size hints.'],
'SET_TIME': ['signatureOrSystem', 'set time', 'Allows an application to change the phone\'s clock time.'],
'SET_TIME_ZONE': ['dangerous', 'set time zone', 'Allows an application to change the phone\'s time zone.'],
'MOUNT_UNMOUNT_FILESYSTEMS': ['dangerous', 'mount and unmount file systems', 'Allows the application to mount and unmount file systems for removable storage.'],
'MOUNT_FORMAT_FILESYSTEMS': ['dangerous', 'format external storage', 'Allows the application to format removable storage.'],
'ASEC_ACCESS': ['signature', 'get information on internal storage', 'Allows the application to get information on internal storage.'],
'ASEC_CREATE': ['signature', 'create internal storage', 'Allows the application to create internal storage.'],
'ASEC_DESTROY': ['signature', 'destroy internal storage', 'Allows the application to destroy internal storage.'],
'ASEC_MOUNT_UNMOUNT': ['signature', 'mount/unmount internal storage', 'Allows the application to mount/unmount internal storage.'],
'ASEC_RENAME': ['signature', 'rename internal storage', 'Allows the application to rename internal storage.'],
'DISABLE_KEYGUARD': ['dangerous', 'disable key lock', 'Allows an application to disable the key lock and any associated password security. A legitimate example of this is the phone disabling the key lock when receiving an incoming phone call, then re-enabling the key lock when the call is finished.'],
'READ_SYNC_SETTINGS': ['normal', 'read sync settings', 'Allows an application to read the sync settings, such as whether sync is enabled for Contacts.'],
'WRITE_SYNC_SETTINGS': ['dangerous', 'write sync settings', 'Allows an application to modify the sync settings, such as whether sync is enabled for Contacts.'],
'READ_SYNC_STATS': ['normal', 'read sync statistics', 'Allows an application to read the sync stats; e.g. the history of syncs that have occurred.'],
'WRITE_APN_SETTINGS': ['dangerous', 'write Access Point Name settings', 'Allows an application to modify the APN settings, such as Proxy and Port of any APN.'],
'SUBSCRIBED_FEEDS_READ': ['normal', 'read subscribed feeds', 'Allows an application to receive details about the currently synced feeds.'],
'SUBSCRIBED_FEEDS_WRITE': ['dangerous', 'write subscribed feeds', 'Allows an application to modify your currently synced feeds. This could allow a malicious application to change your synced feeds.'],
'CHANGE_NETWORK_STATE': ['dangerous', 'change network connectivity', 'Allows an application to change the state of network connectivity.'],
'CHANGE_WIFI_STATE': ['dangerous', 'change Wi-Fi status', 'Allows an application to connect to and disconnect from Wi-Fi access points and to make changes to configured Wi-Fi networks.'],
'CHANGE_WIFI_MULTICAST_STATE': ['dangerous', 'allow Wi-Fi Multicast reception', 'Allows an application to receive packets not directly addressed to your device. This can be useful when discovering services offered nearby. It uses more power than the non-multicast mode.'],
'BLUETOOTH_ADMIN': ['dangerous', 'bluetooth administration', 'Allows an application to configure the local Bluetooth phone and to discover and pair with remote devices.'],
'CLEAR_APP_CACHE': ['dangerous', 'delete all application cache data', 'Allows an application to free phone storage by deleting files in application cache directory. Access is usually very restricted to system process.'],
'READ_LOGS': ['dangerous', 'read sensitive log data', 'Allows an application to read from the system\'s various log files. This allows it to discover general information about what you are doing with the phone, potentially including personal or private information.'],
'SET_DEBUG_APP': ['dangerous', 'enable application debugging', 'Allows an application to turn on debugging for another application. Malicious applications can use this to kill other applications.'],
'SET_PROCESS_LIMIT': ['dangerous', 'limit number of running processes', 'Allows an application to control the maximum number of processes that will run. Never needed for common applications.'],
'SET_ALWAYS_FINISH': ['dangerous', 'make all background applications close', 'Allows an application to control whether activities are always finished as soon as they go to the background. Never needed for common applications.'],
'SIGNAL_PERSISTENT_PROCESSES': ['dangerous', 'send Linux signals to applications', 'Allows application to request that the supplied signal be sent to all persistent processes.'],
'DIAGNOSTIC': ['signature', 'read/write to resources owned by diag', 'Allows an application to read and write to any resource owned by the diag group; for example, files in /dev. This could potentially affect system stability and security. This should ONLY be used for hardware-specific diagnostics by the manufacturer or operator.'],
'STATUS_BAR': ['signatureOrSystem', 'disable or modify status bar', 'Allows application to disable the status bar or add and remove system icons.'],
'STATUS_BAR_SERVICE': ['signature', 'status bar', 'Allows the application to be the status bar.'],
'FORCE_BACK': ['signature', 'force application to close', 'Allows an application to force any activity that is in the foreground to close and go back. Should never be needed for common applications.'],
'UPDATE_DEVICE_STATS': ['signatureOrSystem', 'modify battery statistics', 'Allows the modification of collected battery statistics. Not for use by common applications.'],
'INTERNAL_SYSTEM_WINDOW': ['signature', 'display unauthorised windows', 'Allows the creation of windows that are intended to be used by the internal system user interface. Not for use by common applications.'],
'MANAGE_APP_TOKENS': ['signature', 'manage application tokens', 'Allows applications to create and manage their own tokens, bypassing their common Z-ordering. Should never be needed for common applications.'],
'INJECT_EVENTS': ['signature', 'press keys and control buttons', 'Allows an application to deliver its own input events (key presses, etc.) to other applications. Malicious applications can use this to take over the phone.'],
'SET_ACTIVITY_WATCHER': ['signature', 'monitor and control all application launching', 'Allows an application to monitor and control how the system launches activities. Malicious applications may compromise the system completely. This permission is needed only for development, never for common phone usage.'],
'SHUTDOWN': ['signature', 'partial shutdown', 'Puts the activity manager into a shut-down state. Does not perform a complete shut down.'],
'STOP_APP_SWITCHES': ['signature', 'prevent app switches', 'Prevents the user from switching to another application.'],
'READ_INPUT_STATE': ['signature', 'record what you type and actions that you take', 'Allows applications to watch the keys that you press even when interacting with another application (such as entering a password). Should never be needed for common applications.'],
'BIND_INPUT_METHOD': ['signature', 'bind to an input method', 'Allows the holder to bind to the top-level interface of an input method. Should never be needed for common applications.'],
'BIND_WALLPAPER': ['signatureOrSystem', 'bind to wallpaper', 'Allows the holder to bind to the top-level interface of wallpaper. Should never be needed for common applications.'],
'BIND_DEVICE_ADMIN': ['signature', 'interact with device admin', 'Allows the holder to send intents to a device administrator. Should never be needed for common applications.'],
'SET_ORIENTATION': ['signature', 'change screen orientation', 'Allows an application to change the rotation of the screen at any time. Should never be needed for common applications.'],
'INSTALL_PACKAGES': ['signatureOrSystem', 'directly install applications', 'Allows an application to install new or updated Android packages. Malicious applications can use this to add new applications with arbitrarily powerful permissions.'],
'REQUEST_INSTALL_PACKAGES': ['dangerous', 'Allows an application to request installing packages.', 'Malicious applications can use this to try and trick users into installing additional malicious packages.'],
'CLEAR_APP_USER_DATA': ['signature', 'delete other applications\' data', 'Allows an application to clear user data.'],
'DELETE_CACHE_FILES': ['signatureOrSystem', 'delete other applications\' caches', 'Allows an application to delete cache files.'],
'DELETE_PACKAGES': ['signatureOrSystem', 'delete applications', 'Allows an application to delete Android packages. Malicious applications can use this to delete important applications.'],
'MOVE_PACKAGE': ['signatureOrSystem', 'Move application resources', 'Allows an application to move application resources from internal to external media and vice versa.'],
'CHANGE_COMPONENT_ENABLED_STATE': ['signatureOrSystem', 'enable or disable application components', 'Allows an application to change whether or not a component of another application is enabled. Malicious applications can use this to disable important phone capabilities. It is important to be careful with permission, as it is possible to bring application components into an unusable, inconsistent or unstable state.'],
'ACCESS_SURFACE_FLINGER': ['signature', 'access SurfaceFlinger', 'Allows application to use SurfaceFlinger low-level features.'],
'READ_FRAME_BUFFER': ['signature', 'read frame buffer', 'Allows application to read the content of the frame buffer.'],
'BRICK': ['signature', 'permanently disable phone', 'Allows the application to disable the entire phone permanently. This is very dangerous.'],
'REBOOT': ['signatureOrSystem', 'force phone reboot', 'Allows the application to force the phone to reboot.'],
'DEVICE_POWER': ['signature', 'turn phone on or off', 'Allows the application to turn the phone on or off.'],
'FACTORY_TEST': ['signature', 'run in factory test mode', 'Run as a low-level manufacturer test, allowing complete access to the phone hardware. Only available when a phone is running in manufacturer test mode.'],
'BROADCAST_PACKAGE_REMOVED': ['signature', 'send package removed broadcast', 'Allows an application to broadcast a notification that an application package has been removed. Malicious applications may use this to kill any other application running.'],
'BROADCAST_SMS': ['signature', 'send SMS-received broadcast', 'Allows an application to broadcast a notification that an SMS message has been received. Malicious applications may use this to forge incoming SMS messages.'],
'BROADCAST_WAP_PUSH': ['signature', 'send WAP-PUSH-received broadcast', 'Allows an application to broadcast a notification that a WAP-PUSH message has been received. Malicious applications may use this to forge MMS message receipt or to replace the content of any web page silently with malicious variants.'],
'MASTER_CLEAR': ['signatureOrSystem', 'reset system to factory defaults', 'Allows an application to completely reset the system to its factory settings, erasing all data, configuration and installed applications.'],
'CALL_PRIVILEGED': ['signatureOrSystem', 'directly call any phone numbers', 'Allows the application to call any phone number, including emergency numbers, without your intervention. Malicious applications may place unnecessary and illegal calls to emergency services.'],
'PERFORM_CDMA_PROVISIONING': ['signatureOrSystem', 'directly start CDMA phone setup', 'Allows the application to start CDMA provisioning. Malicious applications may start CDMA provisioning unnecessarily'],
'CONTROL_LOCATION_UPDATES': ['signatureOrSystem', 'control location update notifications', 'Allows enabling/disabling location update notifications from the radio. Not for use by common applications.'],
'ACCESS_CHECKIN_PROPERTIES': ['signatureOrSystem', 'access check-in properties', 'Allows read/write access to properties uploaded by the check-in service. Not for use by common applications.'],
'PACKAGE_USAGE_STATS': ['signature', 'update component usage statistics', 'Allows the modification of collected component usage statistics. Not for use by common applications.'],
'BATTERY_STATS': ['normal', 'modify battery statistics', 'Allows the modification of collected battery statistics. Not for use by common applications.'],
'BACKUP': ['signatureOrSystem', 'control system back up and restore', 'Allows the application to control the system\'s back-up and restore mechanism. Not for use by common applications.'],
'BIND_APPWIDGET': ['signatureOrSystem', 'choose widgets', 'Allows the application to tell the system which widgets can be used by which application. With this permission, applications can give access to personal data to other applications. Not for use by common applications.'],
'CHANGE_BACKGROUND_DATA_SETTING': ['signature', 'change background data usage setting', 'Allows an application to change the background data usage setting.'],
'GLOBAL_SEARCH': ['signatureOrSystem', '', ''],
'GLOBAL_SEARCH_CONTROL': ['signature', '', ''],
'SET_WALLPAPER_COMPONENT': ['signatureOrSystem', '', ''],
'ACCESS_CACHE_FILESYSTEM': ['signatureOrSystem', 'access the cache file system', 'Allows an application to read and write the cache file system.'],
'COPY_PROTECTED_DATA': ['signature', 'Allows to invoke default container service to copy content. Not for use by common applications.', 'Allows to invoke default container service to copy content. Not for use by common applications.'],
'C2D_MESSAGE': ['signature', 'Allows cloud to device messaging', 'Allows the application to receive push notifications.'],
'ADD_VOICEMAIL': ['dangerous', 'add voicemails into the system', 'Allows an application to add voicemails into the system.'],
'ACCEPT_HANDOVER': ['dangerous', '', 'Allows a calling app to continue a call which was started in another app. An example is a video calling app that wants to continue a voice call on the user\'s mobile network.'],
'ACCESS_NOTIFICATION_POLICY': ['normal', '', 'Marker permission for applications that wish to access notification policy.'],
'ANSWER_PHONE_CALLS': ['dangerous', '', 'Allows the app to answer an incoming phone call.'],
'BIND_ACCESSIBILITY_SERVICE': ['signature', '', 'Must be required by an AccessibilityService, to ensure that only the system can bind to it.'],
'BIND_AUTOFILL_SERVICE': ['signature', '', 'Must be required by a AutofillService, to ensure that only the system can bind to it.'],
'BIND_CARRIER_MESSAGING_SERVICE': ['normal', '', 'The system process that is allowed to bind to services in carrier apps will have this permission.'],
'BIND_CARRIER_SERVICES': ['signature', '', 'The system process that is allowed to bind to services in carrier apps will have this permission. Carrier apps should use this permission to protect their services that only the system is allowed to bind to.'],
'BIND_CHOOSER_TARGET_SERVICE': ['signature', '', 'Must be required by a ChooserTargetService, to ensure that only the system can bind to it'],
'BIND_CONDITION_PROVIDER_SERVICE': ['signature', '', 'Must be required by a ConditionProviderService, to ensure that only the system can bind to it'],
'BIND_DREAM_SERVICE': ['signature', '', 'Must be required by an DreamService, to ensure that only the system can bind to it.'],
'BIND_INCALL_SERVICE': ['signature', '', 'Must be required by a InCallService, to ensure that only the system can bind to it.'],
'BIND_MIDI_DEVICE_SERVICE': ['signature', '', 'Must be required by an MidiDeviceService, to ensure that only the system can bind to it.'],
'BIND_NFC_SERVICE': ['signature', '', 'Must be required by a HostApduService or OffHostApduService to ensure that only the system can bind to it.'],
'BIND_NOTIFICATION_LISTENER_SERVICE': ['signature', '', 'Must be required by an NotificationListenerService, to ensure that only the system can bind to it.'],
'BIND_PRINT_SERVICE': ['signature', '', 'Must be required by a PrintService, to ensure that only the system can bind to it.'],
'BIND_QUICK_SETTINGS_TILE': ['normal', '', 'Allows an application to bind to third party quick settings tiles.'],
'BIND_REMOTEVIEWS': ['normal', '', 'Must be required by a RemoteViewsService, to ensure that only the system can bind to it.'],
'BIND_SCREENING_SERVICE': ['signature', '', 'Must be required by a CallScreeningService, to ensure that only the system can bind to it.'],
'BIND_TELECOM_CONNECTION_SERVICE': ['signature', '', 'Must be required by a ConnectionService, to ensure that only the system can bind to it.'],
'BIND_TEXT_SERVICE': ['signature', '', 'Must be required by a TextService (e.g. SpellCheckerService) to ensure that only the system can bind to it.'],
'BIND_TV_INPUT': ['signature', '', 'Must be required by a TvInputService to ensure that only the system can bind to it.'],
'BIND_VISUAL_VOICEMAIL_SERVICE': ['signature', '', 'Must be required by a link'],
'BIND_VOICE_INTERACTION': ['signature', '', 'Must be required by a VoiceInteractionService, to ensure that only the system can bind to it.'],
'BIND_VPN_SERVICE': ['signature', '', 'Must be required by a VpnService, to ensure that only the system can bind to it.'],
'BIND_VR_LISTENER_SERVICE': ['signature', '', 'Must be required by an VrListenerService, to ensure that only the system can bind to it.'],
'BLUETOOTH_PRIVILEGED': ['normal', '', 'Allows applications to pair bluetooth devices without user interaction, and to allow or disallow phonebook access or message access. This is not available to third party applications.'],
'BODY_SENSORS': ['dangerous', '', 'Allows an application to access data from sensors that the user uses to measure what is happening inside his/her body, such as heart rate.'],
'CAPTURE_AUDIO_OUTPUT': ['normal', '', 'Allows an application to capture audio output.'],
'CAPTURE_SECURE_VIDEO_OUTPUT': ['normal', '', 'Allows an application to capture secure video output.'],
'CAPTURE_VIDEO_OUTPUT': ['normal', '', 'Allows an application to capture video output.'],
'FOREGROUND_SERVICE': ['normal', '', 'Allows a regular application to use'],
'GET_ACCOUNTS_PRIVILEGED': ['normal', '', 'Allows access to the list of accounts in the Accounts Service.'],
'INSTALL_SHORTCUT': ['normal', '', 'Allows an application to install a shortcut in Launcher.'],
'INSTANT_APP_FOREGROUND_SERVICE': ['normal', '', 'Allows an instant app to create foreground services.'],
'LOCATION_HARDWARE': ['normal', '', 'Allows an application to use location features in hardware, such as the geofencing api.'],
'MANAGE_DOCUMENTS': ['signature', '', 'Allows an application to manage access to documents, usually as part of a document picker.'],
'MANAGE_OWN_CALLS': ['normal', '', 'Allows a calling application which manages it own calls through the self-managed'],
'MEDIA_CONTENT_CONTROL': ['normal', '', 'Allows an application to know what content is playing and control its playback.'],
'NFC_TRANSACTION_EVENT': ['normal', '', 'Allows applications to receive NFC transaction events.'],
'READ_CALL_LOG': ['dangerous', '', 'Allows an application to read the user\'s call log.'],
'READ_PHONE_NUMBERS': ['dangerous', '', 'Allows read access to the device\'s phone number(s). This is a subset of the capabilities granted by'],
'READ_VOICEMAIL': ['signature', '', 'Allows an application to read voicemails in the system.'],
'REQUEST_COMPANION_RUN_IN_BACKGROUND': ['normal', '', 'Allows a companion app to run in the background.'],
'REQUEST_COMPANION_USE_DATA_IN_BACKGROUND': ['normal', '', 'Allows a companion app to use data in the background.'],
'REQUEST_DELETE_PACKAGES': ['normal', '', 'Allows an application to request deleting packages. Apps targeting APIs'],
'REQUEST_IGNORE_BATTERY_OPTIMIZATIONS': ['normal', '', 'Permission an application must hold in order to use'],
'SEND_RESPOND_VIA_MESSAGE': ['normal', '', 'Allows an application (Phone) to send a request to other applications to handle the respond-via-message action during incoming calls.'],
'TRANSMIT_IR': ['normal', '', 'Allows using the device\'s IR transmitter, if available.'],
'UNINSTALL_SHORTCUT': ['normal', '', 'Don\'t use this permission in your app. This permission is no longer supported.'],
'USE_BIOMETRIC': ['normal', '', 'Allows an app to use device supported biometric modalities.'],
'USE_FINGERPRINT': ['normal', '', 'This constant was deprecated in API level 28. Applications should request USE_BIOMETRIC instead'],
'WRITE_CALL_LOG': ['dangerous', '', 'Allows an application to write (but not read) the user\'s call log data.'],
'WRITE_VOICEMAIL': ['signature', '', 'Allows an application to modify and remove existing voicemails in the system.'],
},
"MANIFEST_PERMISSION_GROUP":
'MANIFEST_PERMISSION_GROUP':
{
"ACCOUNTS": "Permissions for direct access to the accounts managed by the Account Manager.",
"COST_MONEY": "Used for permissions that can be used to make the user spend money without their direct involvement.",
"DEVELOPMENT_TOOLS": "Group of permissions that are related to development features.",
"HARDWARE_CONTROLS": "Used for permissions that provide direct access to the hardware on the device.",
"LOCATION": "Used for permissions that allow access to the user's current location.",
"MESSAGES": "Used for permissions that allow an application to send messages on behalf of the user or intercept messages being received by the user.",
"NETWORK": "Used for permissions that provide access to networking services.",
"PERSONAL_INFO": "Used for permissions that provide access to the user's private data, such as contacts, calendar events, e-mail messages, etc.",
"PHONE_CALLS": "Used for permissions that are associated with accessing and modifyign telephony state: intercepting outgoing calls, reading and modifying the phone state.",
"STORAGE": "Group of permissions that are related to SD card access.",
"SYSTEM_TOOLS": "Group of permissions that are related to system APIs.",
'ACCOUNTS': 'Permissions for direct access to the accounts managed by the Account Manager.',
'COST_MONEY': 'Used for permissions that can be used to make the user spend money without their direct involvement.',
'DEVELOPMENT_TOOLS': 'Group of permissions that are related to development features.',
'HARDWARE_CONTROLS': 'Used for permissions that provide direct access to the hardware on the device.',
'LOCATION': 'Used for permissions that allow access to the user\'s current location.',
'MESSAGES': 'Used for permissions that allow an application to send messages on behalf of the user or intercept messages being received by the user.',
'NETWORK': 'Used for permissions that provide access to networking services.',
'PERSONAL_INFO': 'Used for permissions that provide access to the user\'s private data, such as contacts, calendar events, e-mail messages, etc.',
'PHONE_CALLS': 'Used for permissions that are associated with accessing and modifyign telephony state: intercepting outgoing calls, reading and modifying the phone state.',
'STORAGE': 'Group of permissions that are related to SD card access.',
'SYSTEM_TOOLS': 'Group of permissions that are related to system APIs.',
},
}
+30 -26
View File
@@ -1,20 +1,18 @@
# -*- coding: utf_8 -*-
"""Find in java or smali files."""
import io
import logging
import os
import re
import shutil
import io
import os
import logging
from django.shortcuts import render
from django.http import HttpResponseRedirect
from django.conf import settings
from django.shortcuts import render
from django.utils.html import escape
from MobSF.utils import (
PrintException,
print_n_send_error_response
)
from MobSF.utils import log_exception, print_n_send_error_response
logger = logging.getLogger(__name__)
@@ -28,46 +26,52 @@ def run(request):
code = request.POST['code']
matches = []
if code == 'java':
src = os.path.join(settings.UPLD_DIR, md5+'/java_source/')
src = os.path.join(settings.UPLD_DIR, md5 + '/java_source/')
ext = '.java'
elif code == 'smali':
src = os.path.join(settings.UPLD_DIR, md5+'/smali_source/')
src = os.path.join(settings.UPLD_DIR, md5 + '/smali_source/')
ext = '.smali'
else:
return print_n_send_error_response(request, "Only Java/Smali files are allowed")
err = 'Only Java/Smali files are allowed'
return print_n_send_error_response(request,
err)
# pylint: disable=unused-variable
# Needed by os.walk
for dir_name, sub_dir, files in os.walk(src):
for dir_name, _sub_dir, files in os.walk(src):
for jfile in files:
if jfile.endswith(ext):
file_path = os.path.join(src, dir_name, jfile)
if "+" in jfile:
fp2 = os.path.join(src, dir_name, jfile.replace("+", "x"))
if '+' in jfile:
fp2 = os.path.join(
src, dir_name, jfile.replace('+', 'x'))
shutil.move(file_path, fp2)
file_path = fp2
fileparam = file_path.replace(src, '')
with io.open(
file_path,
mode='r',
encoding="utf8",
errors="ignore"
encoding='utf8',
errors='ignore',
) as file_pointer:
dat = file_pointer.read()
if query in dat:
matches.append(
"<a href='../ViewSource/?file=" + escape(fileparam) +
"&md5=" + md5 +
"&type=apk'>" + escape(fileparam) + "</a>"
matches.append((
'<a href="../ViewSource/?file={}'
'&md5={}'
'&type=apk">{}</a>').format(
escape(fileparam),
md5,
escape(fileparam)),
)
flz = len(matches)
context = {
'title': 'Search Results',
'matches': matches,
'term': query,
'found' : str(flz)
'found': str(flz),
}
template = "general/search.html"
template = 'general/search.html'
return render(request, template, context)
except:
PrintException("Searching Failed")
return print_n_send_error_response(request, "Searching Failed")
except Exception:
log_exception('Searching Failed')
return print_n_send_error_response(request, 'Searching Failed')
+29 -26
View File
@@ -1,15 +1,14 @@
# -*- coding: utf_8 -*-
"""Module holding the functions for icon analysis."""
import subprocess
import re
import os
import platform
import fnmatch
import string
import logging
from django.conf import settings
import os
from androguard.core.bytecodes import apk
from MobSF.utils import log_exception
logger = logging.getLogger(__name__)
@@ -21,7 +20,7 @@ KNOWN_PATHS = [
'drawable-xhdpi',
'mipmap-mdpi',
'drawable-mdpi',
'mipmap-hdpi-v4'
'mipmap-hdpi-v4',
]
KNOWN_MIPMAP_SIZES = [
@@ -30,13 +29,9 @@ KNOWN_MIPMAP_SIZES = [
'-xhdpi',
'-xhdpi-v4',
'-mdpi',
'-mdpi-v4'
'-mdpi-v4',
]
from MobSF.utils import (
PrintException
)
def search_folder(src, file_pattern):
matches = []
@@ -50,7 +45,7 @@ def guess_icon_path(res_dir):
icon_folders = [
'mipmap-hdpi',
'mipmap-hdpi-v4',
'drawable'
'drawable',
]
for icon_path in icon_folders:
guessed_icon_path = os.path.join(res_dir, icon_path, 'ic_launcher.png')
@@ -67,11 +62,16 @@ def guess_icon_path(res_dir):
def find_icon_path_zip(res_dir, icon_paths_from_manifest):
"""Tries to find an icon, based on paths fetched from the manifest and by global search
returns an empty string on fail or a full path"""
"""
Find icon.
Tries to find an icon, based on paths
fetched from the manifest and by global search
returns an empty string on fail or a full path
"""
global KNOWN_MIPMAP_SIZES
try:
logger.info("Guessing icon path")
logger.info('Guessing icon path')
for icon_path in icon_paths_from_manifest:
if icon_path.startswith('@'):
path_array = icon_path.strip('@').split(os.sep)
@@ -103,15 +103,18 @@ def find_icon_path_zip(res_dir, icon_paths_from_manifest):
# If didn't find, try the default name.. returns empty if not find
return guess_icon_path(res_dir)
except:
PrintException("Guessing icon path")
except Exception:
log_exception('Guessing icon path')
def get_icon(apk_path, res_dir):
"""Returns a dict with isHidden boolean and a relative path
path is a full path (not relative to resource folder) """
"""
Returns a dict with isHidden boolean and a relative path.
path is a full path (not relative to resource folder)
"""
try:
logger.info("Fetching icon path")
logger.info('Fetching icon path')
a = apk.APK(apk_path)
icon_resolution = 0xFFFE - 1
icon_name = a.get_app_icon(max_dpi=icon_resolution)
@@ -119,16 +122,16 @@ def get_icon(apk_path, res_dir):
if '.xml' in icon_name:
return {
'path': guess_icon_path(res_dir),
'hidden': False
'hidden': False,
}
else:
return {
'path': os.path.join(os.path.dirname(apk_path), icon_name),
'hidden': False
'hidden': False,
}
return {
'path': guess_icon_path(res_dir),
'hidden': True
'hidden': True,
}
except:
PrintException("Fetching icon function")
except Exception:
log_exception('Fetching icon function')
+36 -33
View File
@@ -1,67 +1,70 @@
# -*- coding: utf_8 -*-
"""List all java files."""
import logging
import os
import re
import shutil
import os
from django.shortcuts import render
from django.http import HttpResponseRedirect
from django.conf import settings
from django.shortcuts import render
from django.utils.html import escape
from MobSF.utils import (
PrintException,
print_n_send_error_response
)
import logging
from MobSF.utils import log_exception, print_n_send_error_response
logger = logging.getLogger(__name__)
def run(request):
"""Show the java code."""
try:
logger.debug("showing java code for request : {}".format(request))
logger.debug('showing java code for request : %s', request)
match = re.match('^[0-9a-f]{32}$', request.GET['md5'])
typ = request.GET['type']
if match:
html = ''
md5 = request.GET['md5']
if typ == 'eclipse':
src = os.path.join(settings.UPLD_DIR, md5 + '/src/')
elif typ == 'studio':
src = os.path.join(settings.UPLD_DIR, md5 + '/app/src/main/java/')
src = os.path.join(settings.UPLD_DIR, md5
+ '/app/src/main/java/')
elif typ == 'apk':
src = os.path.join(settings.UPLD_DIR, md5 + '/java_source/')
else:
return print_n_send_error_response(request, "Invalid Directory Structure")
html = ''
return print_n_send_error_response(
request,
'Invalid Directory Structure')
# pylint: disable=unused-variable
# Needed by os.walk
for dir_name, sub_dir, files in os.walk(src):
for dir_name, _sub_dir, files in os.walk(src):
for jfile in files:
if jfile.endswith(".java"):
if jfile.endswith('.java'):
file_path = os.path.join(src, dir_name, jfile)
if "+" in jfile:
fp2 = os.path.join(src, dir_name, jfile.replace("+", "x"))
if '+' in jfile:
fp2 = os.path.join(
src, dir_name, jfile.replace('+', 'x'))
shutil.move(file_path, fp2)
file_path = fp2
fileparam = file_path.replace(src, '')
if any(re.search(cls, fileparam) for cls in settings.SKIP_CLASSES) is False:
html += (
"<tr><td><a href='../ViewSource/?file=" + escape(fileparam) +
"&md5=" + md5 +
"&type=" + typ + "'>" +
escape(fileparam) + "</a></td></tr>"
)
context = {
'title': 'Java Source',
'files': html,
'md5': md5,
'type': typ,
}
if (any(re.search(cls, fileparam)
for cls in settings.SKIP_CLASSES) is False):
html += ('<tr><td><a href="../ViewSource/'
'?file={}&md5={}&type={}">{}'
'</a></td></tr>').format(
escape(fileparam),
md5,
typ,
escape(fileparam))
context = {
'title': 'Java Source',
'files': html,
'md5': md5,
'type': typ,
}
template = "static_analysis/java.html"
template = 'static_analysis/java.html'
return render(request, template, context)
except:
PrintException("Getting Java Files")
return print_n_send_error_response(request, "Error Getting Java Files")
except Exception:
log_exception('Getting Java Files')
return print_n_send_error_response(request, 'Error Getting Java Files')
File diff suppressed because it is too large Load Diff
+19 -17
View File
@@ -1,20 +1,17 @@
# -*- coding: utf_8 -*-
"""Module for manifest_view."""
import re
import logging
import os
import re
from django.shortcuts import render
from django.http import HttpResponseRedirect
from django.conf import settings
from django.shortcuts import render
from MobSF.utils import (
PrintException,
print_n_send_error_response
)
from MobSF.utils import log_exception, print_n_send_error_response
from StaticAnalyzer.views.android.manifest_analysis import read_manifest
import logging
logger = logging.getLogger(__name__)
@@ -26,23 +23,28 @@ def run(request):
typ = request.GET['type'] # APK or SOURCE
binary = request.GET['bin']
match = re.match('^[0-9a-f]{32}$', md5)
if match and (typ in ['eclipse', 'studio', 'apk']) and (binary in ['1', '0']):
if (match
and (typ in ['eclipse', 'studio', 'apk'])
and (binary in ['1', '0'])):
app_dir = os.path.join(
settings.UPLD_DIR, md5 + '/') # APP DIRECTORY
tools_dir = os.path.join(directory, 'StaticAnalyzer/tools/') # TOOLS DIR
tools_dir = os.path.join(
directory, 'StaticAnalyzer/tools/') # TOOLS DIR
if binary == '1':
is_binary = True
elif binary == '0':
is_binary = False
app_path = os.path.join(app_dir, md5 + ".apk")
manifest = read_manifest(app_dir, app_path, tools_dir, typ, is_binary)
app_path = os.path.join(app_dir, md5 + '.apk')
manifest = read_manifest(
app_dir, app_path, tools_dir, typ, is_binary)
context = {
'title': 'AndroidManifest.xml',
'file': 'AndroidManifest.xml',
'dat': manifest
'dat': manifest,
}
template = "static_analysis/view_mani.html"
template = 'static_analysis/view_mani.html'
return render(request, template, context)
except:
PrintException("Viewing AndroidManifest.xml")
return print_n_send_error_response(request, "Error Viewing AndroidManifest.xml")
except Exception:
log_exception('Viewing AndroidManifest.xml')
return print_n_send_error_response(request,
'Error Viewing AndroidManifest.xml')
+7 -6
View File
@@ -2,17 +2,18 @@
import play_scraper
import logging
logger = logging.getLogger(__name__)
def get_app_details(package_id):
'''Get App Details form PlayStore'''
"""Get App Details form PlayStore."""
try:
logger.info("Fetching Details from Play Store: %s", package_id)
logger.info('Fetching Details from Play Store: %s', package_id)
det = play_scraper.details(package_id)
det.pop('description_html', None)
det["error"] = False
except Exception as exp:
logger.warning("Unable to get app details. %s", exp)
det = {"error": True}
det['error'] = False
except Exception:
logger.warning('Unable to get app details.')
det = {'error': True}
return det
+25 -23
View File
@@ -1,19 +1,17 @@
# -*- coding: utf_8 -*-
"""List all smali files.."""
"""List all smali files."""
import logging
import os
import re
import shutil
import os
import logging
from django.shortcuts import render
from django.http import HttpResponseRedirect
from django.conf import settings
from django.shortcuts import render
from django.utils.html import escape
from MobSF.utils import (
PrintException,
print_n_send_error_response
)
from MobSF.utils import log_exception, print_n_send_error_response
logger = logging.getLogger(__name__)
@@ -23,32 +21,36 @@ def run(request):
match = re.match('^[0-9a-f]{32}$', request.GET['md5'])
if match:
md5 = request.GET['md5']
src = os.path.join(settings.UPLD_DIR, md5+'/smali_source/')
src = os.path.join(settings.UPLD_DIR, md5 + '/smali_source/')
html = ''
# pylint: disable=unused-variable
# Needed by os.walk
for dir_name, sub_dir, files in os.walk(src):
for dir_name, _sub_dir, files in os.walk(src):
for jfile in files:
if jfile.endswith(".smali"):
if jfile.endswith('.smali'):
file_path = os.path.join(src, dir_name, jfile)
if "+" in jfile:
fp2 = os.path.join(src, dir_name, jfile.replace("+", "x"))
if '+' in jfile:
fp2 = os.path.join(
src, dir_name, jfile.replace('+', 'x'))
shutil.move(file_path, fp2)
file_path = fp2
fileparam = file_path.replace(src, '')
html += (
"<tr><td><a href='../ViewSource/?file=" + escape(fileparam) +
"&type=apk&md5=" + md5 + "'>" +
escape(fileparam) + "</a></td></tr>"
)
html += ('<tr><td><a href="../ViewSource/?'
'file={}&type=apk&md5={}">{}</a>'
'</td></tr>').format(
escape(fileparam),
md5,
escape(fileparam))
context = {
'title': 'Smali Source',
'files': html,
'md5': md5,
}
template = "static_analysis/smali.html"
template = 'static_analysis/smali.html'
return render(request, template, context)
except:
PrintException("Getting Smali Files")
return print_n_send_error_response(request, "Error Getting Smali Files")
except Exception:
log_exception('Getting Smali Files')
return print_n_send_error_response(
request,
'Error Getting Smali Files')
+142 -157
View File
@@ -1,79 +1,53 @@
# -*- coding: utf_8 -*-
"""
Android Static Code Analysis
"""
"""Android Static Code Analysis."""
import re
import os
import zipfile
import shutil
import logging
try:
import io
StringIO = io.StringIO
except ImportError:
from io import StringIO
import os
import re
import shutil
import zipfile
from django.shortcuts import render
from django.http import HttpResponseRedirect
from django.conf import settings
from django.template.defaulttags import register
from MobSF.utils import (
print_n_send_error_response,
PrintException,
zipdir
)
from StaticAnalyzer.models import StaticAnalyzerAndroid
from StaticAnalyzer.views.shared_func import (
file_size,
firebase_analysis,
hash_gen,
unzip,
score,
update_scan_timestamp,
)
from StaticAnalyzer.views.android.db_interaction import (
get_context_from_db_entry,
get_context_from_analysis,
update_db_entry,
create_db_entry,
)
from StaticAnalyzer.views.android.code_analysis import code_analysis
from StaticAnalyzer.views.android.strings import strings_jar
from StaticAnalyzer.views.android.converter import (
dex_2_jar,
dex_2_smali,
jar_2_java
)
from StaticAnalyzer.views.android.cert_analysis import (
get_hardcoded_cert_keystore,
cert_info,
)
from StaticAnalyzer.views.android.manifest_analysis import (
manifest_data,
manifest_analysis,
get_manifest
)
from StaticAnalyzer.views.android.binary_analysis import (
elf_analysis,
res_analysis,
)
from StaticAnalyzer.views.android.icon_analysis import (
get_icon,
find_icon_path_zip,
)
from StaticAnalyzer.views.android.playstore import (
get_app_details,
)
from MalwareAnalyzer.views.domain_check import malware_check
from MalwareAnalyzer.views.apkid import apkid_analysis
import MalwareAnalyzer.views.Trackers as Trackers
import MalwareAnalyzer.views.VirusTotal as VirusTotal
from MalwareAnalyzer.views.apkid import apkid_analysis
from MalwareAnalyzer.views.domain_check import malware_check
from django.conf import settings
from django.http import HttpResponseRedirect
from django.shortcuts import render
from django.template.defaulttags import register
from MobSF.utils import log_exception, print_n_send_error_response, zipdir
from StaticAnalyzer.models import StaticAnalyzerAndroid
from StaticAnalyzer.views.android.binary_analysis import (elf_analysis,
res_analysis)
from StaticAnalyzer.views.android.cert_analysis import (
cert_info, get_hardcoded_cert_keystore)
from StaticAnalyzer.views.android.code_analysis import code_analysis
from StaticAnalyzer.views.android.converter import (dex_2_jar, dex_2_smali,
jar_2_java)
from StaticAnalyzer.views.android.db_interaction import (
create_db_entry, get_context_from_analysis, get_context_from_db_entry,
update_db_entry)
from StaticAnalyzer.views.android.icon_analysis import (find_icon_path_zip,
get_icon)
from StaticAnalyzer.views.android.manifest_analysis import (get_manifest,
manifest_analysis,
manifest_data)
from StaticAnalyzer.views.android.playstore import get_app_details
from StaticAnalyzer.views.android.strings import strings_jar
from StaticAnalyzer.views.shared_func import (file_size, firebase_analysis,
hash_gen, score, unzip,
update_scan_timestamp)
try:
import io
StringIO = io.StringIO # noqa F401
except ImportError:
from io import StringIO # noqa F401
logger = logging.getLogger(__name__)
@@ -103,8 +77,8 @@ def static_analyzer(request, api=False):
(
match
) and (
filename.lower().endswith('.apk') or
filename.lower().endswith('.zip')
filename.lower().endswith('.apk')
or filename.lower().endswith('.zip')
) and (
typ in ['zip', 'apk']
)
@@ -116,8 +90,7 @@ def static_analyzer(request, api=False):
'md5'] + '/') # APP DIRECTORY
app_dic['tools_dir'] = os.path.join(
app_dic['dir'], 'StaticAnalyzer/tools/') # TOOLS DIR
# DWD_DIR = settings.DWD_DIR # not needed? Var is never used.
logger.info("Starting Analysis on : " + app_dic['app_name'])
logger.info('Starting Analysis on : %s', app_dic['app_name'])
if typ == 'apk':
# Check if in DB
@@ -129,8 +102,8 @@ def static_analyzer(request, api=False):
else:
app_dic['app_file'] = app_dic[
'md5'] + '.apk' # NEW FILENAME
app_dic['app_path'] = app_dic['app_dir'] + \
app_dic['app_file'] # APP PATH
app_dic['app_path'] = (app_dic['app_dir']
+ app_dic['app_file']) # APP PATH
# ANALYSIS BEGINS
app_dic['size'] = str(
@@ -142,15 +115,21 @@ def static_analyzer(request, api=False):
app_dic['app_path'], app_dic['app_dir'])
if not app_dic['files']:
# Can't Analyze APK, bail out.
msg = "APK file is invalid or corrupt"
msg = 'APK file is invalid or corrupt'
if api:
return print_n_send_error_response(request, msg, True)
return print_n_send_error_response(
request,
msg,
True)
else:
return print_n_send_error_response(request, msg, False)
return print_n_send_error_response(
request,
msg,
False)
app_dic['certz'] = get_hardcoded_cert_keystore(app_dic[
'files'])
logger.info("APK Extracted")
logger.info('APK Extracted')
# Manifest XML
app_dic['parsed_xml'] = get_manifest(
@@ -158,7 +137,7 @@ def static_analyzer(request, api=False):
app_dic['app_dir'],
app_dic['tools_dir'],
'',
True
True,
)
# Get icon
@@ -179,20 +158,22 @@ def static_analyzer(request, api=False):
app_dic['icon_path'] = icon_dic['path']
# Set Manifest link
app_dic['mani'] = '../ManifestView/?md5=' + \
app_dic['md5'] + '&type=apk&bin=1'
app_dic['mani'] = ('../ManifestView/?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
)
man_data_dic)
bin_an_buff = []
bin_an_buff += elf_analysis(app_dic['app_dir'])
bin_an_buff += res_analysis(app_dic['app_dir'])
cert_dic = cert_info(
app_dic['app_dir'], app_dic['app_file'], app_dic['tools_dir'])
app_dic['app_dir'],
app_dic['app_file'],
app_dic['tools_dir'])
apkid_results = apkid_analysis(app_dic[
'app_dir'], app_dic['app_path'], app_dic['app_name'])
tracker = Trackers.Trackers(
@@ -205,42 +186,40 @@ def static_analyzer(request, api=False):
code_an_dic = code_analysis(
app_dic['app_dir'],
man_an_dic['permissons'],
"apk"
)
'apk')
# Get the strings
string_res = strings_jar(
app_dic['app_file'],
app_dic['app_dir']
)
app_dic['app_dir'])
if string_res:
app_dic['strings'] = string_res['strings']
code_an_dic["urls_list"].extend(
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'])
code_an_dic['urls'].extend(string_res['url_nf'])
code_an_dic['emails'].extend(string_res['emails_nf'])
else:
app_dic['strings'] = []
# Firebase DB Check
code_an_dic['firebase'] = firebase_analysis(
list(set(code_an_dic["urls_list"])))
list(set(code_an_dic['urls_list'])))
# Domain Extraction and Malware Check
logger.info(
"Performing Malware Check on extracted Domains")
code_an_dic["domains"] = malware_check(
list(set(code_an_dic["urls_list"])))
'Performing Malware Check on extracted Domains')
code_an_dic['domains'] = malware_check(
list(set(code_an_dic['urls_list'])))
logger.info("Generating Java and Smali Downloads")
logger.info('Generating Java and Smali Downloads')
gen_downloads(app_dic['app_dir'], app_dic[
'md5'], app_dic['icon_path'])
app_dic['zipped'] = '&type=apk'
logger.info("Connecting to Database")
logger.info('Connecting to Database')
try:
# SAVE TO DB
if rescan == '1':
logger.info("Updating Database...")
logger.info('Updating Database...')
update_db_entry(
app_dic,
man_data_dic,
@@ -253,7 +232,7 @@ def static_analyzer(request, api=False):
)
update_scan_timestamp(app_dic['md5'])
elif rescan == '0':
logger.info("Saving to Database")
logger.info('Saving to Database')
create_db_entry(
app_dic,
man_data_dic,
@@ -264,8 +243,8 @@ def static_analyzer(request, api=False):
apkid_results,
tracker_res,
)
except:
PrintException("Saving to Database Failed")
except Exception:
log_exception('Saving to Database Failed')
context = get_context_from_analysis(
app_dic,
man_data_dic,
@@ -276,8 +255,8 @@ def static_analyzer(request, api=False):
apkid_results,
tracker_res,
)
context["average_cvss"], context[
"security_score"] = score(context["findings"])
context['average_cvss'], context[
'security_score'] = score(context['findings'])
context['dynamic_analysis_done'] = os.path.exists(
os.path.join(app_dic['app_dir'], 'logcat.txt'))
@@ -287,9 +266,8 @@ def static_analyzer(request, api=False):
context['VT_RESULT'] = vt.get_result(
os.path.join(app_dic['app_dir'],
app_dic['md5']) + '.apk',
app_dic['md5']
)
template = "static_analysis/android_binary_analysis.html"
app_dic['md5'])
template = 'static_analysis/android_binary_analysis.html'
if api:
return context
else:
@@ -312,26 +290,27 @@ def static_analyzer(request, api=False):
else:
app_dic['app_file'] = app_dic[
'md5'] + '.zip' # NEW FILENAME
app_dic['app_path'] = app_dic['app_dir'] + \
app_dic['app_file'] # APP PATH
logger.info("Extracting ZIP")
app_dic['app_path'] = (app_dic['app_dir']
+ app_dic['app_file']) # APP PATH
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_android_zip(app_dic['app_dir'])
if valid and pro_type == 'ios':
logger.info("Redirecting to iOS Source Code Analyzer")
logger.info('Redirecting to iOS Source Code Analyzer')
if api:
return {"type": "ios"}
return {'type': 'ios'}
else:
return HttpResponseRedirect(
'/StaticAnalyzer_iOS/?name=' + app_dic['app_name'] +
'&type=ios&checksum=' + app_dic['md5']
)
app_dic['certz'] = get_hardcoded_cert_keystore(app_dic[
'files'])
'/StaticAnalyzer_iOS/?name='
+ app_dic['app_name']
+ '&type=ios&checksum='
+ app_dic['md5'])
app_dic['certz'] = get_hardcoded_cert_keystore(
app_dic['files'])
app_dic['zipped'] = pro_type
logger.info("ZIP Type - " + pro_type)
logger.info('ZIP Type - %s', pro_type)
if valid and (pro_type in ['eclipse', 'studio']):
# ANALYSIS BEGINS
app_dic['size'] = str(
@@ -341,17 +320,18 @@ def static_analyzer(request, api=False):
# Manifest XML
app_dic['persed_xml'] = get_manifest(
"",
'',
app_dic['app_dir'],
app_dic['tools_dir'],
pro_type,
False
False,
)
# Set manifest view link
app_dic['mani'] = (
'../ManifestView/?md5=' +
app_dic['md5'] + '&type=' + pro_type + '&bin=0'
'../ManifestView/?md5='
+ app_dic['md5'] + '&type='
+ pro_type + '&bin=0'
)
man_data_dic = manifest_data(app_dic['persed_xml'])
@@ -359,7 +339,7 @@ def static_analyzer(request, api=False):
man_data_dic['packagename'])
man_an_dic = manifest_analysis(
app_dic['persed_xml'],
man_data_dic
man_data_dic,
)
# Get icon
eclipse_res_path = os.path.join(
@@ -384,27 +364,30 @@ def static_analyzer(request, api=False):
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'))
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'],
man_an_dic['permissons'],
pro_type
pro_type,
)
# Firebase DB Check
code_an_dic['firebase'] = firebase_analysis(
list(set(code_an_dic["urls_list"])))
list(set(code_an_dic['urls_list'])))
# Domain Extraction and Malware Check
logger.info(
"Performing Malware Check on extracted Domains")
code_an_dic["domains"] = malware_check(
list(set(code_an_dic["urls_list"])))
logger.info("Connecting to Database")
'Performing Malware Check on extracted Domains')
code_an_dic['domains'] = malware_check(
list(set(code_an_dic['urls_list'])))
logger.info('Connecting to Database')
try:
# SAVE TO DB
if rescan == '1':
logger.info("Updating Database...")
logger.info('Updating Database...')
update_db_entry(
app_dic,
man_data_dic,
@@ -417,7 +400,7 @@ def static_analyzer(request, api=False):
)
update_scan_timestamp(app_dic['md5'])
elif rescan == '0':
logger.info("Saving to Database")
logger.info('Saving to Database')
create_db_entry(
app_dic,
man_data_dic,
@@ -428,8 +411,8 @@ def static_analyzer(request, api=False):
{},
{},
)
except:
PrintException("Saving to Database Failed")
except Exception:
log_exception('Saving to Database Failed')
context = get_context_from_analysis(
app_dic,
man_data_dic,
@@ -441,24 +424,28 @@ def static_analyzer(request, api=False):
{},
)
else:
msg = "This ZIP Format is not supported"
msg = 'This ZIP Format is not supported'
if api:
return print_n_send_error_response(request, msg, True)
return print_n_send_error_response(
request,
msg,
True)
else:
print_n_send_error_response(request, msg, False)
return HttpResponseRedirect('/zip_format/')
context["average_cvss"], context[
"security_score"] = score(context["findings"])
template = "static_analysis/android_source_analysis.html"
context['average_cvss'], context[
'security_score'] = score(context['findings'])
template = 'static_analysis/android_source_analysis.html'
if api:
return context
else:
return render(request, template, context)
else:
logger.error(
"Only APK,IPA and Zipped Android/iOS Source code supported now!")
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"
msg = 'Hash match failed or Invalid file extension or file type'
if api:
return print_n_send_error_response(request, msg, True)
else:
@@ -476,34 +463,32 @@ def static_analyzer(request, api=False):
def valid_android_zip(app_dir):
"""Test if this is an valid android zip."""
try:
logger.info("Checking for ZIP Validity and Mode")
logger.info('Checking for ZIP Validity and Mode')
# Eclipse
man = os.path.isfile(os.path.join(app_dir, "AndroidManifest.xml"))
src = os.path.exists(os.path.join(app_dir, "src/"))
man = os.path.isfile(os.path.join(app_dir, 'AndroidManifest.xml'))
src = os.path.exists(os.path.join(app_dir, 'src/'))
if man and src:
return 'eclipse', True
# Studio
man = os.path.isfile(
os.path.join(
app_dir, "app/src/main/AndroidManifest.xml"
)
os.path.join(app_dir, 'app/src/main/AndroidManifest.xml'),
)
src = os.path.exists(os.path.join(app_dir, "app/src/main/java/"))
src = os.path.exists(os.path.join(app_dir, 'app/src/main/java/'))
if man and src:
return 'studio', True
# iOS Source
xcode = [f for f in os.listdir(app_dir) if f.endswith(".xcodeproj")]
xcode = [f for f in os.listdir(app_dir) if f.endswith('.xcodeproj')]
if xcode:
return 'ios', True
return '', False
except:
PrintException("Determining Upload type")
except Exception:
log_exception('Determining Upload type')
def gen_downloads(app_dir, md5, icon_path=''):
"""Generate downloads for java and smali."""
try:
logger.info("Generating Downloads")
logger.info('Generating Downloads')
# For Java
directory = os.path.join(app_dir, 'java_source/')
dwd_dir = os.path.join(settings.DWD_DIR, md5 + '-java.zip')
@@ -523,5 +508,5 @@ def gen_downloads(app_dir, md5, icon_path=''):
shutil.copy2(icon_path, os.path.join(
settings.DWD_DIR, md5 + '-icon.png'))
except:
PrintException("Generating Downloads")
except Exception:
log_exception('Generating Downloads')
+13 -20
View File
@@ -1,20 +1,13 @@
# -*- coding: utf_8 -*-
"""Module for strings-method for java."""
import io
import os
import logging
import subprocess
import os
from androguard.core.bytecodes import apk
from django.conf import settings
from MobSF.utils import log_exception
from MobSF.utils import (
PrintException
)
from StaticAnalyzer.views.shared_func import (
url_n_email_extract,
)
from StaticAnalyzer.views.shared_func import url_n_email_extract
logger = logging.getLogger(__name__)
@@ -22,7 +15,7 @@ logger = logging.getLogger(__name__)
def strings_jar(app_file, app_dir):
"""Extract the strings from an app."""
try:
logger.info("Extracting Strings from APK")
logger.info('Extracting Strings from APK')
dat = []
apk_file = os.path.join(app_dir, app_file)
and_a = apk.APK(apk_file)
@@ -34,14 +27,14 @@ def strings_jar(app_file, app_dir):
if res_string:
for duo in res_string:
dat.append('"' + duo[0] + '" : "' + duo[1] + '"')
data_string = "".join(dat)
data_string = ''.join(dat)
urls, urls_nf, emails_nf = url_n_email_extract(
data_string, "Android String Resource")
return {"strings": dat,
"urls_list": urls,
"url_nf": urls_nf,
"emails_nf": emails_nf,
data_string, 'Android String Resource')
return {'strings': dat,
'urls_list': urls,
'url_nf': urls_nf,
'emails_nf': emails_nf,
}
except:
PrintException("Extracting Strings from APK")
except Exception:
log_exception('Extracting Strings from APK')
return {}
+19 -25
View File
@@ -2,34 +2,27 @@
"""View Source of a file."""
import io
import ntpath
import re
import os
import logging
from django.shortcuts import render
from django.http import HttpResponseRedirect
import ntpath
import os
from django.conf import settings
from django.shortcuts import render
from django.utils.html import escape
from StaticAnalyzer.forms import (
ViewSourceAndroidApiForm,
ViewSourceAndroidForm
)
from MobSF.forms import FormUtil
from MobSF.utils import print_n_send_error_response
from MobSF.forms import (
FormUtil
)
from StaticAnalyzer.forms import (ViewSourceAndroidApiForm,
ViewSourceAndroidForm)
from MobSF.utils import (
print_n_send_error_response
)
logger = logging.getLogger(__name__)
def run(request, api=False):
"""View the source of a file."""
try:
logger.info("View Android Source File")
logger.info('View Android Source File')
if api:
fil = request.POST['file']
md5 = request.POST['hash']
@@ -47,9 +40,9 @@ def run(request, api=False):
context = {
'title': 'Error',
'exp': 'Error Description',
'doc': err
'doc': err,
}
template = "general/error.html"
template = 'general/error.html'
return render(request, template, context, status=400)
if fil.endswith('.java'):
if typ == 'eclipse':
@@ -64,28 +57,29 @@ def run(request, api=False):
src = os.path.join(settings.UPLD_DIR,
md5 + '/smali_source/')
else:
msg = "Not Found"
doc = "File not Found!"
msg = 'Not Found'
doc = 'File not Found!'
is_api = False
if api:
is_api = True
return print_n_send_error_response(request, msg, is_api, doc)
# Unset SRC for any other case. Otherwise it will cause Directory Traversal
# Unset SRC for any other case.
# Otherwise it will cause Directory Traversal
sfile = os.path.join(src, fil)
dat = ''
with io.open(
sfile,
mode='r',
encoding="utf8",
errors="ignore"
encoding='utf8',
errors='ignore',
) as file_pointer:
dat = file_pointer.read()
context = {
'title': escape(ntpath.basename(fil)),
'file': escape(ntpath.basename(fil)),
'dat': dat
'dat': dat,
}
template = "static_analysis/view_source.html"
template = 'static_analysis/view_source.html'
if api:
return context
return render(request, template, context)
+20 -19
View File
@@ -1,52 +1,53 @@
# -*- coding: utf_8 -*-
"""Holds the different winfixes."""
import os
import logging
import os
from django.conf import settings
from MobSF.utils import (
PrintException
)
from MobSF.utils import log_exception
logger = logging.getLogger(__name__)
def win_fix_java(tools_dir):
"""Runn JAVA path fix in Windows"""
"""Run JAVA path fix in Windows."""
try:
logger.info("Running JAVA path fix in Windows")
logger.info('Running JAVA path fix in Windows')
dmy = os.path.join(tools_dir, 'd2j2/d2j_invoke.tmp')
org = os.path.join(tools_dir, 'd2j2/d2j_invoke.bat')
dat = ''
with open(dmy, 'r') as file_pointer:
dat = file_pointer.read().replace("[xxx]", settings.JAVA_PATH + "java")
dat = file_pointer.read().replace(
'[xxx]', settings.JAVA_PATH + 'java')
with open(org, 'w') as file_pointer:
file_pointer.write(dat)
except:
PrintException("Running JAVA path fix in Windows")
except Exception:
log_exception('Running JAVA path fix in Windows')
def win_fix_python3(tools_dir):
"""Runn Python 3 path fix in Windows."""
"""Run Python 3 path fix in Windows."""
try:
logger.info("Running Python 3 path fix in Windows")
python3_path = ""
logger.info('Running Python 3 path fix in Windows')
python3_path = ''
if len(settings.PYTHON3_PATH) > 2:
python3_path = settings.python3_path
else:
pathenv = os.environ["path"]
pathenv = os.environ['path']
if pathenv:
paths = pathenv.split(";")
paths = pathenv.split(';')
for path in paths:
if "python3" in path.lower():
if 'python3' in path.lower():
python3_path = path
python3 = "\"" + os.path.join(python3_path, "python") + "\""
python3 = '"' + os.path.join(python3_path, 'python') + '"'
dmy = os.path.join(tools_dir, 'enjarify/enjarify.tmp')
org = os.path.join(tools_dir, 'enjarify/enjarify.bat')
dat = ''
with open(dmy, 'r') as file_pointer:
dat = file_pointer.read().replace("[xxx]", python3)
dat = file_pointer.read().replace('[xxx]', python3)
with open(org, 'w') as file_pointer:
file_pointer.write(dat)
except:
PrintException("Running Python 3 path fix in Windows")
except Exception:
log_exception('Running Python 3 path fix in Windows')
+86 -59
View File
@@ -1,22 +1,20 @@
# -*- coding: utf_8 -*-
"""
Module providing the shared functions for static analysis of iOS and Android
Compare Apps.
Module for comparing app results.
"""
import re
from copy import deepcopy
from collections import defaultdict
from copy import deepcopy
from django.shortcuts import render
from MobSF.utils import (
print_n_send_error_response
)
from MobSF.utils import print_n_send_error_response
from StaticAnalyzer.models import StaticAnalyzerAndroid
from StaticAnalyzer.views.android.db_interaction import (
get_context_from_db_entry
)
get_context_from_db_entry)
# The APKiD is pain because it really differs from the others
@@ -25,22 +23,25 @@ def diff_apkid(context: dict) -> None:
'packer', 'dropper', 'manipulator',
'anti_disassembly', 'anti_debug', 'abnormal']
context['apkid'] = dict()
context['apkid']['common'] = dict()
context['apkid']['only_first'] = dict()
context['apkid']['only_second'] = dict()
context['apkid'] = {}
context['apkid']['common'] = {}
context['apkid']['only_first'] = {}
context['apkid']['only_second'] = {}
first_apkid, second_apkid = context['first_app']['apkid'], context['second_app']['apkid']
first_error, second_error = first_apkid.get('error', False), second_apkid.get('error', False)
first_apkid, second_apkid = (context['first_app']['apkid'],
context['second_app']['apkid'])
first_error, second_error = first_apkid.get(
'error', False), second_apkid.get('error', False)
context['apkid_error'] = (first_error or second_error)
if context['apkid_error']:
return
tmp_flat_results = dict()
tmp_flat_results = {}
for curr_app_str in ['first_app', 'second_app']:
# Flatten the results per app (join all the dex files result into one)
flat_results = defaultdict(list)
for results in [dex_results for (dex_name, dex_results) in context[curr_app_str]['apkid'].items()]:
for results in [dex_results for (dex_name, dex_results) in
context[curr_app_str]['apkid'].items()]:
for apkid_key, key_results in results.items():
flat_results[apkid_key] += key_results
# Distinct list
@@ -49,31 +50,41 @@ def diff_apkid(context: dict) -> None:
for key in apkid_keys:
# The keys may be different between the two lists
context['apkid']['common'][key] = [x for x in tmp_flat_results['first_app'][key] if x in
tmp_flat_results['second_app'][key]]
context['apkid']['common'][key] = [
x for x in tmp_flat_results['first_app'][key] if x in
tmp_flat_results['second_app'][key]]
context['apkid']['only_first'][key] = [x for x in tmp_flat_results['first_app'][key] if x not in
tmp_flat_results['second_app'][key]]
context['apkid']['only_first'][key] = [
x for x in tmp_flat_results['first_app'][key] if x not in
tmp_flat_results['second_app'][key]]
context['apkid']['only_second'][key] = [x for x in tmp_flat_results['second_app'][key] if x not in
tmp_flat_results['first_app'][key]]
context['apkid']['only_second'][key] = [
x for x in tmp_flat_results['second_app'][key] if x not in
tmp_flat_results['first_app'][key]]
# suppose to get any 2 apps (android / ios / appx) and then figure out what to do with them
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
# suppose to get any 2 apps (android / ios / appx)
# and then figure out what to do with them
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',
'first_app': dict(),
'second_app': dict(),
'urls': dict(),
'api': dict(),
'permissions': dict(),
'apkid': dict()
'first_app': {},
'second_app': {},
'urls': {},
'api': {},
'permissions': {},
'apkid': {},
}
static_fields = ['md5', 'name', 'size', 'icon_found', 'icon_hidden', 'act_count', 'e_act', 'serv_count', 'e_ser',
'bro_count', 'e_bro', 'prov_count', 'e_cnt', 'apkid']
static_fields = ['md5', 'name', 'size', 'icon_found',
'icon_hidden', 'act_count', 'e_act', 'serv_count',
'e_ser', 'bro_count', 'e_bro', 'prov_count',
'e_cnt', 'apkid']
# For now - support only android
db_entry = StaticAnalyzerAndroid.objects.filter(MD5=first_hash)
@@ -81,21 +92,28 @@ def generic_compare(request, first_hash: str, second_hash: str, api: bool = Fals
if not (db_entry.exists() and db_entry2.exists()):
return print_n_send_error_response(request,
"One of the Hashes wasn't found in the Android-results DB, make sure "
"both of the apps finished analysis & they are both Android", False)
'One of the Hashes wasn\'t found '
'in the Android - results DB, make '
'sure both of the apps finished '
'analysis & they are both Android',
False)
# 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
# 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)]:
# 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['packagename'],
db_context['androvername'])
context[curr_app]['name_ver'] = '{0} - {1}'.format(
db_context['packagename'],
db_context['androvername'])
# Fill all the static information
for static_attr in static_fields:
@@ -107,14 +125,15 @@ def generic_compare(request, first_hash: str, second_hash: str, api: bool = Fals
if match:
context[curr_app]['cert_subject'] = match.group(1)
else:
context[curr_app]['cert_subject'] = "No subject"
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 = list()
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
# urls can mess up the table because they can be really long,
# so let's cut them
tmp_url = url[:70]
while len(url) > 70:
url = url[70:]
@@ -125,38 +144,46 @@ def generic_compare(request, first_hash: str, second_hash: str, api: bool = Fals
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...
# 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),
('api', True),
('urls', False)
('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()]
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()]
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()]
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]['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_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]]
context[section]['only_second'] = [
x for x in second_app[section] if x not in
first_app[section]]
template = "static_analysis/compare.html"
template = 'static_analysis/compare.html'
if api:
return context
else:
+13 -11
View File
@@ -1,23 +1,23 @@
# -*- coding: utf_8 -*-
import requests
import logging
from MobSF.utils import (
upstream_proxy,
)
import requests
from MobSF.utils import upstream_proxy
logger = logging.getLogger(__name__)
def app_search(app_id):
'''iOS Get App Details from App Store'''
logger.info("Fetching Details from App Store: %s", app_id)
"""IOS Get App Details from App Store."""
logger.info('Fetching Details from App Store: %s', app_id)
lookup_url = 'https://itunes.apple.com/lookup'
req_url = '{}?bundleId={}&country={}&entity=software'.format(
lookup_url, app_id, 'us')
headers = {
'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_10_1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/39.0.2171.95 Safari/537.36'}
'User-Agent': ('Mozilla/5.0 (Macintosh; Intel Mac OS X 10_10_1) '
'AppleWebKit/537.36 (KHTML, like Gecko) '
'Chrome/39.0.2171.95 Safari/537.36')}
try:
det = {}
proxies, verify = upstream_proxy('https')
@@ -28,7 +28,9 @@ def app_search(app_id):
det = resp['results'][0]
return {
'features': det['features'] or [],
'icon': det['artworkUrl512'] or det['artworkUrl100'] or det['artworkUrl60'] or '',
'icon': (det['artworkUrl512']
or det['artworkUrl100']
or det['artworkUrl60'] or ''),
'developer_id': det['artistId'],
'developer': det['artistName'],
'developer_url': det['artistViewUrl'],
@@ -45,6 +47,6 @@ def app_search(app_id):
}
logger.warning('Unable to get app details.')
return {'error': True}
except Exception as exp:
logger.warning('Unable to get app details. %s', exp)
except Exception:
logger.warning('Unable to get app details')
return {'error': True}
+230 -166
View File
@@ -1,22 +1,24 @@
# -*- coding: utf_8 -*-
'''Module for iOS IPA Binary Analysis.'''
"""Module for iOS IPA Binary Analysis."""
import re
import logging
import os
import platform
import re
import subprocess
from django.conf import settings
from django.utils.html import escape
from django.utils.encoding import smart_text
from django.utils.html import escape
from macholib.mach_o import (CPU_TYPE_NAMES, MH_CIGAM_64, MH_MAGIC_64,
get_cpu_subtype)
from macholib.MachO import MachO
from macholib.mach_o import get_cpu_subtype, CPU_TYPE_NAMES
from macholib.mach_o import MH_CIGAM_64, MH_MAGIC_64
from MobSF.utils import is_file_exists, log_exception
from StaticAnalyzer.tools.strings import strings_util
from MobSF.utils import PrintException, isFileExists
import logging
logger = logging.getLogger(__name__)
SECURE = 'Secure'
IN_SECURE = 'Insecure'
@@ -25,12 +27,14 @@ WARNING = 'Warning'
def get_otool_out(tools_dir, cmd_type, bin_path, bin_dir):
'''Get otool args by OS and type'''
if len(settings.OTOOL_BINARY) > 0 and isFileExists(settings.OTOOL_BINARY):
"""Get otool args by OS and type."""
if (len(settings.OTOOL_BINARY) > 0
and is_file_exists(settings.OTOOL_BINARY)):
otool_bin = settings.OTOOL_BINARY
else:
otool_bin = 'otool'
if len(settings.JTOOL_BINARY) > 0 and isFileExists(settings.JTOOL_BINARY):
if (len(settings.JTOOL_BINARY) > 0
and is_file_exists(settings.JTOOL_BINARY)):
jtool_bin = settings.JTOOL_BINARY
else:
jtool_bin = os.path.join(tools_dir, 'jtool.ELF64')
@@ -62,20 +66,21 @@ def get_otool_out(tools_dir, cmd_type, bin_path, bin_dir):
elif plat == 'Linux':
arg1 = [jtool_bin, '-arch', 'arm', '-bind', '-v', bin_path]
arg2 = [jtool_bin, '-arch', 'arm', '-lazy_bind', '-v', bin_path]
return subprocess.check_output(arg1) + subprocess.check_output(arg2)
return (subprocess.check_output(arg1)
+ subprocess.check_output(arg2))
else:
# Platform Not Supported
return None
def otool_analysis(tools_dir, bin_name, bin_path, bin_dir):
'''OTOOL Analysis of Binary'''
"""OTOOL Analysis of Binary."""
try:
otool_dict = {
'libs': [],
'anal': []
'anal': [],
}
logger.info('Running Object Analysis of Binary : ' + bin_name)
logger.info('Running Object Analysis of Binary : %s', bin_name)
otool_dict['libs'] = get_otool_out(
tools_dir, 'libs', bin_path, bin_dir)
# PIE
@@ -84,9 +89,11 @@ def otool_analysis(tools_dir, bin_name, bin_path, bin_dir):
pie_flag = {
'issue': 'fPIE -pie flag is Found',
'status': SECURE,
'description': '''App is compiled with Position Independent Executable (PIE) flag.
This enables Address Space Layout Randomization (ASLR), a memory protection
mechanism for exploit mitigation.''',
'description': ('App is compiled with Position Independent '
'Executable (PIE) flag. This enables Address'
' Space Layout Randomization (ASLR), a memory'
' protection mechanism for'
' exploit mitigation.'),
'cvss': 0,
'cwe': '',
}
@@ -94,218 +101,274 @@ def otool_analysis(tools_dir, bin_name, bin_path, bin_dir):
pie_flag = {
'issue': 'fPIE -pie flag is not Found',
'status': IN_SECURE,
'description': '''with Position Independent Executable (PIE) flag. So Address Space Layout
Randomization (ASLR) is missing. ASLR is a memory protection mechanism for exploit mitigation.''',
'description': ('with Position Independent Executable (PIE) '
'flag. So Address Space Layout Randomization '
'(ASLR) is missing. ASLR is a memory '
'protection mechanism for '
'exploit mitigation.'),
'cvss': 2,
'cwe': 'CWE-119',
}
# Stack Smashing Protection & ARC
dat = get_otool_out(tools_dir, 'symbols', bin_path, bin_dir)
if b'stack_chk_guard' in dat:
ssmash = {'issue': 'fstack-protector-all flag is Found',
'status': SECURE,
'description': '''App is compiled with Stack Smashing Protector (SSP) flag and is having protection against Stack
Overflows/Stack Smashing Attacks.''',
'cvss': 0,
'cwe': '',
}
ssmash = {
'issue': 'fstack-protector-all flag is Found',
'status': SECURE,
'description': ('App is compiled with Stack Smashing Protector'
' (SSP) flag and is having protection against'
' Stack Overflows/Stack Smashing Attacks.'),
'cvss': 0,
'cwe': ''}
else:
ssmash = {'issue': 'fstack-protector-all flag is not Found',
'status': IN_SECURE,
'description': '''App is not compiled with Stack Smashing Protector (SSP) flag. It is vulnerable to
Stack Overflows/Stack Smashing Attacks.''',
'cvss': 2,
'cwe': 'CWE-119',
}
ssmash = {
'issue': 'fstack-protector-all flag is not Found',
'status': IN_SECURE,
'description': ('App is not compiled with Stack Smashing '
'Protector (SSP) flag. It is vulnerable to'
'Stack Overflows/Stack Smashing Attacks.'),
'cvss': 2,
'cwe': 'CWE-119'}
# ARC
if b'_objc_release' in dat:
arc_flag = {'issue': 'fobjc-arc flag is Found',
'status': SECURE,
'description': '''App is compiled with Automatic Reference Counting (ARC) flag. ARC is a compiler feature
that provides automatic memory management of Objective-C objects and is an
exploit mitigation mechanism against memory corruption vulnerabilities.''',
'cvss': 0,
'cwe': '',
}
arc_flag = {
'issue': 'fobjc-arc flag is Found',
'status': SECURE,
'description': ('App is compiled with Automatic Reference '
'Counting (ARC) flag. ARC is a compiler '
'feature that provides automatic memory '
'management of Objective-C objects and is an '
'exploit mitigation mechanism against memory '
'corruption vulnerabilities.'),
'cvss': 0,
'cwe': ''}
else:
arc_flag = {'issue': 'fobjc-arc flag is not Found',
'status': IN_SECURE,
'description': '''App is not compiled with Automatic Reference Counting (ARC) flag. ARC is a compiler feature that
provides automatic memory management of Objective-C objects and protects from
memory corruption vulnerabilities.''',
'cvss': 2,
'cwe': 'CWE-119',
}
arc_flag = {
'issue': 'fobjc-arc flag is not Found',
'status': IN_SECURE,
'description': ('App is not compiled with Automatic Reference '
'Counting (ARC) flag. ARC is a compiler '
'feature that provides automatic memory '
'management of Objective-C objects and '
'protects from memory corruption '
'vulnerabilities.'),
'cvss': 2,
'cwe': 'CWE-119'}
banned_apis = {}
baned = re.findall(
b'_alloca|_gets|_memcpy|_printf|_scanf|_sprintf|_sscanf|_strcat|StrCat|_strcpy|' +
b'StrCpy|_strlen|StrLen|_strncat|StrNCat|_strncpy|StrNCpy|_strtok|_swprintf|_vsnprintf|' +
b'_vsprintf|_vswprintf|_wcscat|_wcscpy|_wcslen|_wcsncat|_wcsncpy|_wcstok|_wmemcpy|' +
b'_alloca|_gets|_memcpy|_printf|_scanf|_sprintf|_sscanf|_strcat|'
b'StrCat|_strcpy|StrCpy|_strlen|StrLen|_strncat|StrNCat|_strncpy|'
b'StrNCpy|_strtok|_swprintf|_vsnprintf|_vsprintf|_vswprintf|'
b'_wcscat|_wcscpy|_wcslen|_wcsncat|_wcsncpy|_wcstok|_wmemcpy|'
b'_fopen|_chmod|_chown|_stat|_mktemp', dat)
baned = list(set(baned))
baned_s = b', '.join(baned)
if len(baned_s) > 1:
banned_apis = {'issue': 'Binary make use of banned API(s)',
'status': IN_SECURE,
'description': 'The binary may contain the following banned API(s) ' + baned_s.decode('utf-8', 'ignore') + '.',
'cvss': 6,
'cwe': 'CWE-676',
}
banned_apis = {
'issue': 'Binary make use of banned API(s)',
'status': IN_SECURE,
'description': ('The binary may contain'
' the following banned API(s) {}.').format(
baned_s.decode('utf-8', 'ignore')),
'cvss': 6,
'cwe': 'CWE-676'}
weak_cryptos = {}
weak_algo = re.findall(
b'kCCAlgorithmDES|kCCAlgorithm3DES||kCCAlgorithmRC2|kCCAlgorithmRC4|' +
b'kCCOptionECBMode|kCCOptionCBCMode', dat)
b'kCCAlgorithmDES|kCCAlgorithm3DES||kCCAlgorithmRC2|'
b'kCCAlgorithmRC4|kCCOptionECBMode|kCCOptionCBCMode', dat)
weak_algo = list(set(weak_algo))
weak_algo_s = b', '.join(weak_algo)
if len(weak_algo_s) > 1:
weak_cryptos = {'issue': 'Binary make use of some Weak Crypto API(s)',
'status': IN_SECURE,
'description': 'The binary may use the following weak crypto API(s) ' + weak_algo_s.decode('utf-8', 'ignore') + '.',
'cvss': 3,
'cwe': 'CWE-327',
}
weak_cryptos = {
'issue': 'Binary make use of some Weak Crypto API(s)',
'status': IN_SECURE,
'description': ('The binary may use the'
' following weak crypto API(s) {}.').formnat(
weak_algo_s.decode('utf-8', 'ignore')),
'cvss': 3,
'cwe': 'CWE-327'}
crypto = {}
crypto_algo = re.findall(
b'CCKeyDerivationPBKDF|CCCryptorCreate|CCCryptorCreateFromData|' +
b'CCCryptorRelease|CCCryptorUpdate|CCCryptorFinal|CCCryptorGetOutputLength|' +
b'CCCryptorReset|CCCryptorRef|kCCEncrypt|kCCDecrypt|kCCAlgorithmAES128|' +
b'kCCKeySizeAES128|kCCKeySizeAES192|kCCKeySizeAES256|kCCAlgorithmCAST|' +
b'SecCertificateGetTypeID|SecIdentityGetTypeID|SecKeyGetTypeID|SecPolicyGetTypeID|' +
b'SecTrustGetTypeID|SecCertificateCreateWithData|SecCertificateCreateFromData|' +
b'SecCertificateCopyData|SecCertificateAddToKeychain|SecCertificateGetData|' +
b'SecCertificateCopySubjectSummary|SecIdentityCopyCertificate|' +
b'SecIdentityCopyPrivateKey|SecPKCS12Import|SecKeyGeneratePair|SecKeyEncrypt|' +
b'SecKeyDecrypt|SecKeyRawSign|SecKeyRawVerify|SecKeyGetBlockSize|' +
b'SecPolicyCopyProperties|SecPolicyCreateBasicX509|SecPolicyCreateSSL|' +
b'SecTrustCopyCustomAnchorCertificates|SecTrustCopyExceptions|' +
b'SecTrustCopyProperties|SecTrustCopyPolicies|SecTrustCopyPublicKey|' +
b'SecTrustCreateWithCertificates|SecTrustEvaluate|SecTrustEvaluateAsync|' +
b'SecTrustGetCertificateCount|SecTrustGetCertificateAtIndex|SecTrustGetTrustResult|' +
b'SecTrustGetVerifyTime|SecTrustSetAnchorCertificates|' +
b'SecTrustSetAnchorCertificatesOnly|SecTrustSetExceptions|SecTrustSetPolicies|' +
b'SecTrustSetVerifyDate|SecCertificateRef|' +
b'CCKeyDerivationPBKDF|CCCryptorCreate|CCCryptorCreateFromData|'
b'CCCryptorRelease|CCCryptorUpdate|CCCryptorFinal|'
b'CCCryptorGetOutputLength|CCCryptorReset|CCCryptorRef|kCCEncrypt|'
b'kCCDecrypt|kCCAlgorithmAES128|kCCKeySizeAES128|kCCKeySizeAES192|'
b'kCCKeySizeAES256|kCCAlgorithmCAST|SecCertificateGetTypeID|'
b'SecIdentityGetTypeID|SecKeyGetTypeID|SecPolicyGetTypeID|'
b'SecTrustGetTypeID|SecCertificateCreateWithData|'
b'SecCertificateCreateFromData|SecCertificateCopyData|'
b'SecCertificateAddToKeychain|SecCertificateGetData|'
b'SecCertificateCopySubjectSummary|SecIdentityCopyCertificate|'
b'SecIdentityCopyPrivateKey|SecPKCS12Import|SecKeyGeneratePair|'
b'SecKeyEncrypt|SecKeyDecrypt|SecKeyRawSign|SecKeyRawVerify|'
b'SecKeyGetBlockSize|SecPolicyCopyProperties|'
b'SecPolicyCreateBasicX509|SecPolicyCreateSSL|'
b'SecTrustCopyCustomAnchorCertificates|SecTrustCopyExceptions|'
b'SecTrustCopyProperties|SecTrustCopyPolicies|'
b'SecTrustCopyPublicKey|SecTrustCreateWithCertificates|'
b'SecTrustEvaluate|SecTrustEvaluateAsync|'
b'SecTrustGetCertificateCount|SecTrustGetCertificateAtIndex|'
b'SecTrustGetTrustResult|SecTrustGetVerifyTime|'
b'SecTrustSetAnchorCertificates|SecTrustSetAnchorCertificatesOnly|'
b'SecTrustSetExceptions|SecTrustSetPolicies|'
b'SecTrustSetVerifyDate|SecCertificateRef|'
b'SecIdentityRef|SecKeyRef|SecPolicyRef|SecTrustRef', dat)
crypto_algo = list(set(crypto_algo))
crypto_algo_s = b', '.join(crypto_algo)
if len(crypto_algo_s) > 1:
crypto = {'issue': 'Binary make use of the following Crypto API(s)',
'status': 'Info',
'description': 'The binary may use the following crypto API(s) ' + crypto_algo_s.decode('utf-8', 'ignore') + '.',
'cvss': 0,
'cwe': '',
}
crypto = {
'issue': 'Binary make use of the following Crypto API(s)',
'status': 'Info',
'description': ('The binary may use '
'the following crypto API(s) {}.').format(
crypto_algo_s.decode('utf-8', 'ignore')),
'cvss': 0,
'cwe': ''}
weak_hashes = {}
weak_hash_algo = re.findall(
b'CC_MD2_Init|CC_MD2_Update|CC_MD2_Final|CC_MD2|MD2_Init|' +
b'MD2_Update|MD2_Final|CC_MD4_Init|CC_MD4_Update|CC_MD4_Final|CC_MD4|MD4_Init|' +
b'MD4_Update|MD4_Final|CC_MD5_Init|CC_MD5_Update|CC_MD5_Final|CC_MD5|MD5_Init|' +
b'MD5_Update|MD5_Final|MD5Init|MD5Update|MD5Final|CC_SHA1_Init|CC_SHA1_Update|' +
b'CC_MD2_Init|CC_MD2_Update|CC_MD2_Final|CC_MD2|MD2_Init|'
b'MD2_Update|MD2_Final|CC_MD4_Init|CC_MD4_Update|CC_MD4_Final|'
b'CC_MD4|MD4_Init|MD4_Update|MD4_Final|CC_MD5_Init|CC_MD5_Update'
b'|CC_MD5_Final|CC_MD5|MD5_Init|MD5_Update|MD5_Final|MD5Init|'
b'MD5Update|MD5Final|CC_SHA1_Init|CC_SHA1_Update|'
b'CC_SHA1_Final|CC_SHA1|SHA1_Init|SHA1_Update|SHA1_Final', dat)
weak_hash_algo = list(set(weak_hash_algo))
weak_hash_algo_s = b', '.join(weak_hash_algo)
if len(weak_hash_algo_s) > 1:
weak_hashes = {'issue': 'Binary make use of the following Weak HASH API(s)',
'status': IN_SECURE,
'description': 'The binary may use the following weak hash API(s) ' + weak_hash_algo_s.decode('utf-8', 'ignore') + '.',
'cvss': 3,
'cwe': 'CWE-327',
}
weak_hashes = {
'issue': 'Binary make use of the following Weak HASH API(s)',
'status': IN_SECURE,
'description': (
'The binary may use the '
'following weak hash API(s) {}.').format(
weak_hash_algo_s.decode('utf-8', 'ignore')),
'cvss': 3,
'cwe': 'CWE-327'}
hashes = {}
hash_algo = re.findall(
b'CC_SHA224_Init|CC_SHA224_Update|CC_SHA224_Final|CC_SHA224|' +
b'SHA224_Init|SHA224_Update|SHA224_Final|CC_SHA256_Init|CC_SHA256_Update|' +
b'CC_SHA256_Final|CC_SHA256|SHA256_Init|SHA256_Update|SHA256_Final|' +
b'CC_SHA384_Init|CC_SHA384_Update|CC_SHA384_Final|CC_SHA384|SHA384_Init|' +
b'SHA384_Update|SHA384_Final|CC_SHA512_Init|CC_SHA512_Update|CC_SHA512_Final|' +
b'CC_SHA224_Init|CC_SHA224_Update|CC_SHA224_Final|CC_SHA224|'
b'SHA224_Init|SHA224_Update|SHA224_Final|CC_SHA256_Init|'
b'CC_SHA256_Update|CC_SHA256_Final|CC_SHA256|SHA256_Init|'
b'SHA256_Update|SHA256_Final|CC_SHA384_Init|CC_SHA384_Update|'
b'CC_SHA384_Final|CC_SHA384|SHA384_Init|SHA384_Update|'
b'SHA384_Final|CC_SHA512_Init|CC_SHA512_Update|CC_SHA512_Final|'
b'CC_SHA512|SHA512_Init|SHA512_Update|SHA512_Final', dat)
hash_algo = list(set(hash_algo))
hash_algo_s = b', '.join(hash_algo)
if len(hash_algo_s) > 1:
hashes = {'issue': 'Binary make use of the following HASH API(s)',
'status': INFO,
'description': 'The binary may use the following hash API(s) ' + hash_algo_s.decode('utf-8', 'ignore') + '.',
'cvss': 0,
'cwe': '',
}
hashes = {
'issue': 'Binary make use of the following HASH API(s)',
'status': INFO,
'description': ('The binary may use the'
' following hash API(s) {}.').format(
hash_algo_s.decode('utf-8', 'ignore')),
'cvss': 0,
'cwe': ''}
randoms = {}
rand_algo = re.findall(b'_srand|_random', dat)
rand_algo = list(set(rand_algo))
rand_algo_s = b', '.join(rand_algo)
if len(rand_algo_s) > 1:
randoms = {'issue': 'Binary make use of the insecure Random Function(s)',
'status': IN_SECURE,
'description': 'The binary may use the following insecure Random Function(s) ' + rand_algo_s.decode('utf-8', 'ignore') + '.',
'cvss': 3,
'cwe': 'CWE-338',
}
randoms = {
'issue': 'Binary make use of the insecure Random Function(s)',
'status': IN_SECURE,
'description': ('The binary may use the following '
'insecure Random Function(s) {}.').format(
rand_algo_s.decode('utf-8', 'ignore')),
'cvss': 3,
'cwe': 'CWE-338'}
logging = {}
log = re.findall(b'_NSLog', dat)
log = list(set(log))
log_s = b', '.join(log)
if len(log_s) > 1:
logging = {'issue': 'Binary make use of Logging Function',
'status': INFO,
'description': 'The binary may use NSLog function for logging.',
'cvss': 7.5,
'cwe': 'CWE-532',
}
malloc = {}
logging = {
'issue': 'Binary make use of Logging Function',
'status': INFO,
'description': ('The binary may use NSLog'
' function for logging.'),
'cvss': 7.5,
'cwe': 'CWE-532'}
malloc = {}
mal = re.findall(b'_malloc', dat)
mal = list(set(mal))
mal_s = b', '.join(mal)
if len(mal_s) > 1:
malloc = {'issue': 'Binary make use of malloc Function',
'status': IN_SECURE,
'description': 'The binary may use malloc function instead of calloc.',
'cvss': 2,
'cwe': 'CWE-789',
}
debug = {}
malloc = {
'issue': 'Binary make use of malloc Function',
'status': IN_SECURE,
'description': ('The binary may use malloc'
' function instead of calloc.'),
'cvss': 2,
'cwe': 'CWE-789'}
debug = {}
ptrace = re.findall(b'_ptrace', dat)
ptrace = list(set(ptrace))
ptrace_s = b', '.join(ptrace)
if len(ptrace_s) > 1:
debug = {'issue': 'Binary calls ptrace Function for anti-debugging.',
'status': WARNING,
'description': '''The binary may use ptrace function. It can be used to detect and prevent debuggers.
Ptrace is not a public API and Apps that use non-public APIs will be rejected from AppStore. ''',
'cvss': 0,
'cwe': '',
}
otool_dict['anal'] = [pie_flag, ssmash, arc_flag, banned_apis, weak_cryptos,
crypto, weak_hashes, hashes, randoms, logging, malloc, debug]
debug = {
'issue': 'Binary calls ptrace Function for anti-debugging.',
'status': WARNING,
'description': ('The binary may use ptrace function. It can be'
' used to detect and prevent debuggers.'
'Ptrace is not a public API and Apps that use'
' non-public APIs will be rejected'
' from AppStore.'),
'cvss': 0,
'cwe': ''}
otool_dict['anal'] = [pie_flag,
ssmash,
arc_flag,
banned_apis,
weak_cryptos,
crypto,
weak_hashes,
hashes,
randoms,
logging,
malloc,
debug]
return otool_dict
except:
PrintException('Performing Object Analysis of Binary')
except Exception:
log_exception('Performing Object Analysis of Binary')
def detect_bin_type(libs):
'''Detect IPA binary type'''
if any("libswiftCore.dylib" in itm for itm in libs):
return "Swift"
"""Detect IPA binary type."""
if any('libswiftCore.dylib' in itm for itm in libs):
return 'Swift'
else:
return "Objective C"
return 'Objective C'
def class_dump(tools_dir, bin_path, app_dir, bin_type):
'''Running Classdumpz on binary'''
"""Running Classdumpz on binary."""
try:
webview = {}
if platform.system() == 'Darwin':
logger.info('Dumping classes')
if bin_type == "Swift":
logger.info("Running class-dump-swift aganst binary")
if len(settings.CLASSDUMP_SWIFT_BINARY) > 0 and isFileExists(settings.CLASSDUMP_SWIFT_BINARY):
if bin_type == 'Swift':
logger.info('Running class-dump-swift aganst binary')
if (len(settings.CLASSDUMP_SWIFT_BINARY) > 0
and is_file_exists(settings.CLASSDUMP_SWIFT_BINARY)):
class_dump_bin = settings.CLASSDUMP_SWIFT_BINARY
else:
class_dump_bin = os.path.join(
tools_dir, 'class-dump-swift')
else:
logger.info("Running class-dump-z aganst binary")
if len(settings.CLASSDUMPZ_BINARY) > 0 and isFileExists(settings.CLASSDUMPZ_BINARY):
logger.info('Running class-dump-z aganst binary')
if (len(settings.CLASSDUMPZ_BINARY) > 0
and is_file_exists(settings.CLASSDUMPZ_BINARY)):
class_dump_bin = settings.CLASSDUMPZ_BINARY
else:
class_dump_bin = os.path.join(tools_dir, 'class-dump-z')
@@ -313,7 +376,8 @@ def class_dump(tools_dir, bin_path, app_dir, bin_type):
args = [class_dump_bin, bin_path]
elif platform.system() == 'Linux':
logger.info('Running jtool against the binary for dumping classes')
if len(settings.JTOOL_BINARY) > 0 and isFileExists(settings.JTOOL_BINARY):
if (len(settings.JTOOL_BINARY) > 0
and is_file_exists(settings.JTOOL_BINARY)):
jtool_bin = settings.JTOOL_BINARY
else:
jtool_bin = os.path.join(tools_dir, 'jtool.ELF64')
@@ -325,7 +389,7 @@ def class_dump(tools_dir, bin_path, app_dir, bin_type):
return {}
with open(os.devnull, 'w') as devnull:
classdump = subprocess.check_output(args, stderr=devnull)
if b"Source: (null)" in classdump and platform.system() == 'Darwin':
if b'Source: (null)' in classdump and platform.system() == 'Darwin':
logger.info('Running fail safe class-dump-swift')
class_dump_bin = os.path.join(
tools_dir, 'class-dump-swift')
@@ -337,17 +401,17 @@ def class_dump(tools_dir, bin_path, app_dir, bin_type):
if b'UIWebView' in classdump:
webview = {'issue': 'Binary uses WebView Component.',
'status': INFO,
'description': 'The binary may use WebView Component.',
'description': 'The binary may use WebView Component.',
'cvss': 0,
'cwe': '',
}
return webview
except:
except Exception:
logger.error('class-dump-z/class-dump-swift failed on this binary')
def strings_on_ipa(bin_path):
'''Extract Strings from IPA'''
"""Extract Strings from IPA."""
try:
logger.info('Running strings against the Binary')
unique_str = []
@@ -355,12 +419,12 @@ def strings_on_ipa(bin_path):
unique_str = [escape(ip_str)
for ip_str in unique_str] # Escape evil strings
return unique_str
except:
PrintException('Running strings against the Binary')
except Exception:
log_exception('Running strings against the Binary')
def get_bin_info(bin_file):
'''Get Binary Information'''
"""Get Binary Information."""
logger.info('Getting Binary Information')
m = MachO(bin_file)
for header in m.headers:
@@ -379,7 +443,7 @@ def get_bin_info(bin_file):
def binary_analysis(src, tools_dir, app_dir, executable_name):
'''Binary Analysis of IPA'''
"""Binary Analysis of IPA."""
try:
binary_analysis_dict = {}
logger.info('Starting Binary Analysis')
@@ -400,13 +464,13 @@ def binary_analysis(src, tools_dir, app_dir, executable_name):
binary_analysis_dict['libs'] = []
binary_analysis_dict['bin_res'] = []
binary_analysis_dict['strings'] = []
if not isFileExists(bin_path):
logger.warning('MobSF Cannot find binary in ' + bin_path)
if not is_file_exists(bin_path):
logger.warning('MobSF Cannot find binary in %s', bin_path)
logger.warning('Skipping Otool, Classdump and Strings')
else:
bin_info = get_bin_info(bin_path)
otool_dict = otool_analysis(tools_dir, bin_name, bin_path, bin_dir)
bin_type = detect_bin_type(otool_dict["libs"])
bin_type = detect_bin_type(otool_dict['libs'])
cls_dump = class_dump(tools_dir, bin_path, app_dir, bin_type)
if not cls_dump:
cls_dump = {}
@@ -420,5 +484,5 @@ def binary_analysis(src, tools_dir, app_dir, executable_name):
binary_analysis_dict['bin_type'] = bin_type
return binary_analysis_dict
except:
PrintException('iOS Binary Analysis')
except Exception:
log_exception('iOS Binary Analysis')
+31 -29
View File
@@ -1,32 +1,28 @@
import os
import io
import logging
import os
import shutil
from StaticAnalyzer.views.shared_func import (
url_n_email_extract,
api_rule_matcher,
code_rule_matcher,
)
from StaticAnalyzer.views.ios import (
ios_rules,
ios_apis
)
from MalwareAnalyzer.views.domain_check import malware_check
from MobSF.utils import (
PrintException,
)
import logging
from MobSF.utils import log_exception
from StaticAnalyzer.views.ios import ios_apis, ios_rules
from StaticAnalyzer.views.shared_func import (api_rule_matcher,
code_rule_matcher,
url_n_email_extract)
logger = logging.getLogger(__name__)
def ios_source_analysis(src):
"""iOS Objective-C Code Analysis"""
"""IOS Objective-C Code Analysis."""
try:
logger.info("Starting iOS Source Code and PLIST Analysis")
logger.info('Starting iOS Source Code and PLIST Analysis')
api_rules = ios_apis.CODE_APIS
code_rules = ios_rules.CODE_RULES
code_findings = {}
api_findings = {}
api_findings = {}
email_n_file = []
url_n_file = []
url_list = []
@@ -34,34 +30,40 @@ def ios_source_analysis(src):
for dirname, _, files in os.walk(src):
for jfile in files:
if jfile.endswith(".m"):
if jfile.endswith('.m'):
jfile_path = os.path.join(src, dirname, jfile)
if "+" in jfile:
if '+' in jfile:
new_path = os.path.join(
src, dirname, jfile.replace("+", "x"))
src, dirname, jfile.replace('+', 'x'))
shutil.move(jfile_path, new_path)
jfile_path = new_path
dat = ''
with io.open(jfile_path, mode='r', encoding="utf8", errors="ignore") as flip:
with io.open(jfile_path,
mode='r',
encoding='utf8',
errors='ignore') as flip:
dat = flip.read()
# Code Analysis
relative_src_path = jfile_path.replace(src, '')
code_rule_matcher(code_findings, [], dat, relative_src_path, code_rules)
code_rule_matcher(code_findings, [], dat,
relative_src_path, code_rules)
# API Analysis
api_rule_matcher(api_findings, [], dat, relative_src_path, api_rules)
api_rule_matcher(api_findings, [], dat,
relative_src_path, api_rules)
# Extract URLs and Emails
urls, urls_nf, emails_nf = url_n_email_extract(dat, relative_src_path)
urls, urls_nf, emails_nf = url_n_email_extract(
dat, relative_src_path)
url_list.extend(urls)
url_n_file.extend(urls_nf)
email_n_file.extend(emails_nf)
urls_list = list(set(url_list))
urls_list = list(set(url_list))
# Domain Extraction and Malware Check
logger.info("Performing Malware Check on extracted Domains")
logger.info('Performing Malware Check on extracted Domains')
domains = malware_check(urls_list)
logger.info("Finished Code Analysis, Email and URL Extraction")
logger.info('Finished Code Analysis, Email and URL Extraction')
code_analysis_dict = {
'api': api_findings,
'code_anal': code_findings,
@@ -72,5 +74,5 @@ def ios_source_analysis(src):
}
return code_analysis_dict
except:
PrintException("iOS Source Code Analysis")
except Exception:
log_exception('iOS Source Code Analysis')
+217 -195
View File
@@ -1,61 +1,62 @@
"""Module holding the functions for the db."""
from MobSF.utils import (
PrintException,
python_list,
python_dict
)
from StaticAnalyzer.models import (
StaticAnalyzerIPA,
StaticAnalyzerIOSZIP
)
import logging
from MobSF.utils import log_exception, python_dict, python_list
from StaticAnalyzer.models import StaticAnalyzerIOSZIP, StaticAnalyzerIPA
logger = logging.getLogger(__name__)
# IPA DB
def get_context_from_analysis_ipa(app_dict, info_dict, bin_dict, files, sfiles):
"""Get the context for IPA from analysis results"""
def get_context_from_analysis_ipa(app_dict,
info_dict,
bin_dict,
files,
sfiles):
"""Get the context for IPA from analysis results."""
try:
context = {
'title': 'Static Analysis',
'file_name': app_dict["file_name"],
'size': app_dict["size"],
'md5': app_dict["md5_hash"],
'sha1': app_dict["sha1"],
'sha256': app_dict["sha256"],
'plist': info_dict["plist_xml"],
'bin_name': info_dict["bin_name"],
'id': info_dict["id"],
'build': info_dict["build"],
'version': info_dict["bundle_version_name"],
'sdk': info_dict["sdk"],
'pltfm': info_dict["pltfm"],
'min': info_dict["min"],
'bin_anal': bin_dict["bin_res"],
'libs': bin_dict["libs"],
'file_name': app_dict['file_name'],
'size': app_dict['size'],
'md5': app_dict['md5_hash'],
'sha1': app_dict['sha1'],
'sha256': app_dict['sha256'],
'plist': info_dict['plist_xml'],
'bin_name': info_dict['bin_name'],
'id': info_dict['id'],
'build': info_dict['build'],
'version': info_dict['bundle_version_name'],
'sdk': info_dict['sdk'],
'pltfm': info_dict['pltfm'],
'min': info_dict['min'],
'bin_anal': bin_dict['bin_res'],
'libs': bin_dict['libs'],
'files': files,
'file_analysis': sfiles,
'strings': bin_dict["strings"],
'permissions': info_dict["permissions"],
'insecure_connections': info_dict["inseccon"],
'bundle_name': info_dict["bundle_name"],
'bundle_url_types': info_dict["bundle_url_types"],
'bundle_supported_platforms': info_dict["bundle_supported_platforms"],
'bundle_localizations': info_dict["bundle_localizations"],
'binary_info': bin_dict["macho"],
'bin_type': bin_dict["bin_type"],
'strings': bin_dict['strings'],
'permissions': info_dict['permissions'],
'insecure_connections': info_dict['inseccon'],
'bundle_name': info_dict['bundle_name'],
'bundle_url_types': info_dict['bundle_url_types'],
'bundle_supported_platforms':
info_dict['bundle_supported_platforms'],
'bundle_localizations': info_dict['bundle_localizations'],
'binary_info': bin_dict['macho'],
'bin_type': bin_dict['bin_type'],
'appstore_details': app_dict['appstore'],
}
return context
except:
PrintException("Rendering to Template")
except Exception:
log_exception('Rendering to Template')
def get_context_from_db_entry_ipa(db_entry):
"""Return the context for IPA from DB"""
"""Return the context for IPA from DB."""
try:
logger.info("Analysis is already Done. Fetching data from the DB...")
logger.info('Analysis is already Done. Fetching data from the DB...')
context = {
'title': db_entry[0].TITLE,
'file_name': db_entry[0].FILE_NAME,
@@ -80,140 +81,155 @@ def get_context_from_db_entry_ipa(db_entry):
'insecure_connections': python_list(db_entry[0].INSECCON),
'bundle_name': db_entry[0].BUNDLE_NAME,
'bundle_url_types': python_list(db_entry[0].BUNDLE_URL_TYPES),
'bundle_supported_platforms': python_list(db_entry[0].BUNDLE_SUPPORTED_PLATFORMS),
'bundle_localizations': python_list(db_entry[0].BUNDLE_LOCALIZATIONS),
'bundle_supported_platforms':
python_list(db_entry[0].BUNDLE_SUPPORTED_PLATFORMS),
'bundle_localizations':
python_list(db_entry[0].BUNDLE_LOCALIZATIONS),
'binary_info': python_dict(db_entry[0].MACHOINFO),
'bin_type': db_entry[0].BINTYPE,
'appstore_details': python_dict(db_entry[0].APPSTORE_DETAILS),
}
return context
except:
PrintException("Fetching from DB")
except Exception:
log_exception('Fetching from DB')
def update_db_entry_ipa(app_dict, info_dict, bin_dict, files, sfiles):
"""Update an IPA DB entry"""
def update_db_entry_ipa(app_dict,
info_dict,
bin_dict,
files,
sfiles):
"""Update an IPA DB entry."""
try:
# pylint: disable=E1101
StaticAnalyzerIPA.objects.filter(MD5=app_dict["md5_hash"]).update(
StaticAnalyzerIPA.objects.filter(MD5=app_dict['md5_hash']).update(
TITLE='Static Analysis',
FILE_NAME=app_dict["file_name"],
SIZE=app_dict["size"],
MD5=app_dict["md5_hash"],
SHA1=app_dict["sha1"],
SHA256=app_dict["sha256"],
INFOPLIST=info_dict["plist_xml"],
BINNAME=info_dict["bin_name"],
IDF=info_dict["id"],
BUILD=info_dict["build"],
VERSION=info_dict["bundle_version_name"],
SDK=info_dict["sdk"],
PLTFM=info_dict["pltfm"],
MINX=info_dict["min"],
BIN_ANAL=bin_dict["bin_res"],
LIBS=bin_dict["libs"],
FILE_NAME=app_dict['file_name'],
SIZE=app_dict['size'],
MD5=app_dict['md5_hash'],
SHA1=app_dict['sha1'],
SHA256=app_dict['sha256'],
INFOPLIST=info_dict['plist_xml'],
BINNAME=info_dict['bin_name'],
IDF=info_dict['id'],
BUILD=info_dict['build'],
VERSION=info_dict['bundle_version_name'],
SDK=info_dict['sdk'],
PLTFM=info_dict['pltfm'],
MINX=info_dict['min'],
BIN_ANAL=bin_dict['bin_res'],
LIBS=bin_dict['libs'],
FILES=files,
SFILESX=sfiles,
STRINGS=bin_dict["strings"],
PERMISSIONS=info_dict["permissions"],
INSECCON=info_dict["inseccon"],
BUNDLE_NAME=info_dict["bundle_name"],
BUNDLE_URL_TYPES=info_dict["bundle_url_types"],
BUNDLE_SUPPORTED_PLATFORMS=info_dict["bundle_supported_platforms"],
BUNDLE_LOCALIZATIONS=info_dict["bundle_localizations"],
MACHOINFO=bin_dict["macho"],
BINTYPE=bin_dict["bin_type"],
APPSTORE_DETAILS=app_dict["appstore"],
STRINGS=bin_dict['strings'],
PERMISSIONS=info_dict['permissions'],
INSECCON=info_dict['inseccon'],
BUNDLE_NAME=info_dict['bundle_name'],
BUNDLE_URL_TYPES=info_dict['bundle_url_types'],
BUNDLE_SUPPORTED_PLATFORMS=info_dict['bundle_supported_platforms'],
BUNDLE_LOCALIZATIONS=info_dict['bundle_localizations'],
MACHOINFO=bin_dict['macho'],
BINTYPE=bin_dict['bin_type'],
APPSTORE_DETAILS=app_dict['appstore'],
)
except:
PrintException("Updating DB")
except Exception:
log_exception('Updating DB')
def create_db_entry_ipa(app_dict, info_dict, bin_dict, files, sfiles):
"""Save an IOS IPA DB entry"""
def create_db_entry_ipa(app_dict,
info_dict,
bin_dict,
files,
sfiles):
"""Save an IOS IPA DB entry."""
try:
static_db = StaticAnalyzerIPA(
TITLE='Static Analysis',
FILE_NAME=app_dict["file_name"],
SIZE=app_dict["size"],
MD5=app_dict["md5_hash"],
SHA1=app_dict["sha1"],
SHA256=app_dict["sha256"],
INFOPLIST=info_dict["plist_xml"],
BINNAME=info_dict["bin_name"],
IDF=info_dict["id"],
BUILD=info_dict["build"],
FILE_NAME=app_dict['file_name'],
SIZE=app_dict['size'],
MD5=app_dict['md5_hash'],
SHA1=app_dict['sha1'],
SHA256=app_dict['sha256'],
INFOPLIST=info_dict['plist_xml'],
BINNAME=info_dict['bin_name'],
IDF=info_dict['id'],
BUILD=info_dict['build'],
VERSION=info_dict['bundle_version_name'],
SDK=info_dict["sdk"],
PLTFM=info_dict["pltfm"],
MINX=info_dict["min"],
BIN_ANAL=bin_dict["bin_res"],
LIBS=bin_dict["libs"],
SDK=info_dict['sdk'],
PLTFM=info_dict['pltfm'],
MINX=info_dict['min'],
BIN_ANAL=bin_dict['bin_res'],
LIBS=bin_dict['libs'],
FILES=files,
SFILESX=sfiles,
STRINGS=bin_dict["strings"],
PERMISSIONS=info_dict["permissions"],
INSECCON=info_dict["inseccon"],
BUNDLE_NAME=info_dict["bundle_name"],
BUNDLE_URL_TYPES=info_dict["bundle_url_types"],
BUNDLE_SUPPORTED_PLATFORMS=info_dict["bundle_supported_platforms"],
BUNDLE_LOCALIZATIONS=info_dict["bundle_localizations"],
MACHOINFO=bin_dict["macho"],
BINTYPE=bin_dict["bin_type"],
APPSTORE_DETAILS=app_dict["appstore"],
STRINGS=bin_dict['strings'],
PERMISSIONS=info_dict['permissions'],
INSECCON=info_dict['inseccon'],
BUNDLE_NAME=info_dict['bundle_name'],
BUNDLE_URL_TYPES=info_dict['bundle_url_types'],
BUNDLE_SUPPORTED_PLATFORMS=info_dict['bundle_supported_platforms'],
BUNDLE_LOCALIZATIONS=info_dict['bundle_localizations'],
MACHOINFO=bin_dict['macho'],
BINTYPE=bin_dict['bin_type'],
APPSTORE_DETAILS=app_dict['appstore'],
)
static_db.save()
except:
PrintException("Saving to DB")
except Exception:
log_exception('Saving to DB')
# IOS ZIP DB ENTRY
def get_context_from_analysis_ios(app_dict, info_dict,code_dict, files, sfiles):
"""Get the context for IOS ZIP from analysis results"""
def get_context_from_analysis_ios(app_dict,
info_dict,
code_dict,
files,
sfiles):
"""Get the context for IOS ZIP from analysis results."""
try:
context = {
'title': 'Static Analysis',
'file_name': app_dict["file_name"],
'size': app_dict["size"],
'md5': app_dict["md5_hash"],
'sha1': app_dict["sha1"],
'sha256': app_dict["sha256"],
'plist': info_dict["plist_xml"],
'bin_name': info_dict["bin_name"],
'id': info_dict["id"],
'build': info_dict["bundle_version_name"],
'file_name': app_dict['file_name'],
'size': app_dict['size'],
'md5': app_dict['md5_hash'],
'sha1': app_dict['sha1'],
'sha256': app_dict['sha256'],
'plist': info_dict['plist_xml'],
'bin_name': info_dict['bin_name'],
'id': info_dict['id'],
'build': info_dict['bundle_version_name'],
'version': info_dict['bundle_version_name'],
'sdk': info_dict["sdk"],
'pltfm': info_dict["pltfm"],
'min': info_dict["min"],
'sdk': info_dict['sdk'],
'pltfm': info_dict['pltfm'],
'min': info_dict['min'],
'files': files,
'file_analysis': sfiles,
'api': code_dict["api"],
'insecure': code_dict["code_anal"],
'urls': code_dict["urlnfile"],
'domains': code_dict["domains"],
'emails': code_dict["emailnfile"],
'permissions': info_dict["permissions"],
'insecure_connections': info_dict["inseccon"],
'bundle_name': info_dict["bundle_name"],
'bundle_url_types': info_dict["bundle_url_types"],
'bundle_supported_platforms': info_dict["bundle_supported_platforms"],
'bundle_localizations': info_dict["bundle_localizations"],
'api': code_dict['api'],
'insecure': code_dict['code_anal'],
'urls': code_dict['urlnfile'],
'domains': code_dict['domains'],
'emails': code_dict['emailnfile'],
'permissions': info_dict['permissions'],
'insecure_connections': info_dict['inseccon'],
'bundle_name': info_dict['bundle_name'],
'bundle_url_types': info_dict['bundle_url_types'],
'bundle_supported_platforms':
info_dict['bundle_supported_platforms'],
'bundle_localizations': info_dict['bundle_localizations'],
'appstore_details': app_dict['appstore'],
'firebase': code_dict["firebase"],
'firebase': code_dict['firebase'],
}
return context
except:
PrintException("Rendering to Template")
except Exception:
log_exception('Rendering to Template')
def get_context_from_db_entry_ios(db_entry):
"""Return the context for IOS ZIP from DB"""
"""Return the context for IOS ZIP from DB."""
try:
logger.info("Analysis is already Done. Fetching data from the DB...")
logger.info('Analysis is already Done. Fetching data from the DB...')
context = {
'title': db_entry[0].TITLE,
'file_name': db_entry[0].FILE_NAME,
@@ -240,91 +256,97 @@ def get_context_from_db_entry_ios(db_entry):
'insecure_connections': python_list(db_entry[0].INSECCON),
'bundle_name': db_entry[0].BUNDLE_NAME,
'bundle_url_types': python_list(db_entry[0].BUNDLE_URL_TYPES),
'bundle_supported_platforms': python_list(db_entry[0].BUNDLE_SUPPORTED_PLATFORMS),
'bundle_localizations': python_list(db_entry[0].BUNDLE_LOCALIZATIONS),
'bundle_supported_platforms':
python_list(db_entry[0].BUNDLE_SUPPORTED_PLATFORMS),
'bundle_localizations':
python_list(db_entry[0].BUNDLE_LOCALIZATIONS),
'appstore_details': python_dict(db_entry[0].APPSTORE_DETAILS),
'firebase': python_list(db_entry[0].FIREBASE),
}
return context
except:
PrintException("Fetching from DB")
except Exception:
log_exception('Fetching from DB')
def update_db_entry_ios(app_dict, info_dict, code_dict, files, sfiles):
"""Update an IOS ZIP DB entry"""
def update_db_entry_ios(app_dict,
info_dict,
code_dict,
files,
sfiles):
"""Update an IOS ZIP DB entry."""
try:
# pylint: disable=E1101
StaticAnalyzerIOSZIP.objects.filter(MD5=app_dict["md5_hash"]).update(
StaticAnalyzerIOSZIP.objects.filter(MD5=app_dict['md5_hash']).update(
TITLE='Static Analysis',
FILE_NAME=app_dict["file_name"],
SIZE=app_dict["size"],
MD5=app_dict["md5_hash"],
SHA1=app_dict["sha1"],
SHA256=app_dict["sha256"],
INFOPLIST=info_dict["plist_xml"],
BINNAME=info_dict["bin_name"],
IDF=info_dict["id"],
BUILD=info_dict["build"],
VERSION=info_dict["bundle_version_name"],
SDK=info_dict["sdk"],
PLTFM=info_dict["pltfm"],
MINX=info_dict["min"],
FILE_NAME=app_dict['file_name'],
SIZE=app_dict['size'],
MD5=app_dict['md5_hash'],
SHA1=app_dict['sha1'],
SHA256=app_dict['sha256'],
INFOPLIST=info_dict['plist_xml'],
BINNAME=info_dict['bin_name'],
IDF=info_dict['id'],
BUILD=info_dict['build'],
VERSION=info_dict['bundle_version_name'],
SDK=info_dict['sdk'],
PLTFM=info_dict['pltfm'],
MINX=info_dict['min'],
FILES=files,
SFILESX=sfiles,
API=code_dict["api"],
CODEANAL=code_dict["code_anal"],
URLnFile=code_dict["urlnfile"],
DOMAINS=code_dict["domains"],
EmailnFile=code_dict["emailnfile"],
PERMISSIONS=info_dict["permissions"],
INSECCON=info_dict["inseccon"],
BUNDLE_NAME=info_dict["bundle_name"],
BUNDLE_URL_TYPES=info_dict["bundle_url_types"],
BUNDLE_SUPPORTED_PLATFORMS=info_dict["bundle_supported_platforms"],
BUNDLE_LOCALIZATIONS=info_dict["bundle_localizations"],
APPSTORE_DETAILS=app_dict["appstore"],
FIREBASE=code_dict["firebase"],
API=code_dict['api'],
CODEANAL=code_dict['code_anal'],
URLnFile=code_dict['urlnfile'],
DOMAINS=code_dict['domains'],
EmailnFile=code_dict['emailnfile'],
PERMISSIONS=info_dict['permissions'],
INSECCON=info_dict['inseccon'],
BUNDLE_NAME=info_dict['bundle_name'],
BUNDLE_URL_TYPES=info_dict['bundle_url_types'],
BUNDLE_SUPPORTED_PLATFORMS=info_dict['bundle_supported_platforms'],
BUNDLE_LOCALIZATIONS=info_dict['bundle_localizations'],
APPSTORE_DETAILS=app_dict['appstore'],
FIREBASE=code_dict['firebase'],
)
except:
PrintException("Updating DB")
except Exception:
log_exception('Updating DB')
def create_db_entry_ios(app_dict, info_dict, code_dict, files, sfiles):
"""Save an IOS ZIP DB entry"""
"""Save an IOS ZIP DB entry."""
try:
# pylint: disable=E1101
static_db = StaticAnalyzerIOSZIP(
TITLE='Static Analysis',
FILE_NAME=app_dict["file_name"],
SIZE=app_dict["size"],
MD5=app_dict["md5_hash"],
SHA1=app_dict["sha1"],
SHA256=app_dict["sha256"],
INFOPLIST=info_dict["plist_xml"],
BINNAME=info_dict["bin_name"],
IDF=info_dict["id"],
BUILD=info_dict["build"],
VERSION=info_dict["bundle_version_name"],
SDK=info_dict["sdk"],
PLTFM=info_dict["pltfm"],
MINX=info_dict["min"],
FILE_NAME=app_dict['file_name'],
SIZE=app_dict['size'],
MD5=app_dict['md5_hash'],
SHA1=app_dict['sha1'],
SHA256=app_dict['sha256'],
INFOPLIST=info_dict['plist_xml'],
BINNAME=info_dict['bin_name'],
IDF=info_dict['id'],
BUILD=info_dict['build'],
VERSION=info_dict['bundle_version_name'],
SDK=info_dict['sdk'],
PLTFM=info_dict['pltfm'],
MINX=info_dict['min'],
FILES=files,
SFILESX=sfiles,
API=code_dict["api"],
CODEANAL=code_dict["code_anal"],
URLnFile=code_dict["urlnfile"],
DOMAINS=code_dict["domains"],
EmailnFile=code_dict["emailnfile"],
PERMISSIONS=info_dict["permissions"],
INSECCON=info_dict["inseccon"],
BUNDLE_NAME=info_dict["bundle_name"],
BUNDLE_URL_TYPES=info_dict["bundle_url_types"],
BUNDLE_SUPPORTED_PLATFORMS=info_dict["bundle_supported_platforms"],
BUNDLE_LOCALIZATIONS=info_dict["bundle_localizations"],
APPSTORE_DETAILS=app_dict["appstore"],
FIREBASE=code_dict["firebase"],
API=code_dict['api'],
CODEANAL=code_dict['code_anal'],
URLnFile=code_dict['urlnfile'],
DOMAINS=code_dict['domains'],
EmailnFile=code_dict['emailnfile'],
PERMISSIONS=info_dict['permissions'],
INSECCON=info_dict['inseccon'],
BUNDLE_NAME=info_dict['bundle_name'],
BUNDLE_URL_TYPES=info_dict['bundle_url_types'],
BUNDLE_SUPPORTED_PLATFORMS=info_dict['bundle_supported_platforms'],
BUNDLE_LOCALIZATIONS=info_dict['bundle_localizations'],
APPSTORE_DETAILS=app_dict['appstore'],
FIREBASE=code_dict['firebase'],
)
static_db.save()
except:
PrintException("Saving DB")
except Exception:
log_exception('Saving DB')
+17 -16
View File
@@ -1,5 +1,5 @@
"""
Rule Format
Rule Format.
1. desc - Description of the findings
@@ -14,8 +14,10 @@ Rule Format
d. single_string - if string1 in input
e. string_and - if (string1 in input) and (string2 in input)
f. string_or - if (string1 in input) or (string2 in input)
g. string_and_or - if (string1 in input) and ((string2 in input) or (string3 in input))
h. string_or_and - if (string1 in input) or ((string2 in input) and (string3 in input))
g. string_and_or - if (string1 in input) and ((string2 in input)
or (string3 in input))
h. string_or_and - if (string1 in input) or ((string2 in input)
and (string3 in input))
4. input_case
a. upper
@@ -33,37 +35,37 @@ CODE_APIS = [
'type': 'regex',
'match': 'single_regex',
'regex1': r'NSURL|CFStream|NSStream',
'input_case': 'exact'
'input_case': 'exact',
},
{
'desc': 'Local File I/O Operations.',
'type': 'regex',
'match': 'single_regex',
'regex1': r'Keychain|kSecAttrAccessibleWhenUnlocked|' +
r'kSecAttrAccessibleAfterFirstUnlock|SecItemAdd|' +
r'SecItemUpdate|NSDataWritingFileProtectionComplete',
'input_case': 'exact'
'regex1': (r'Keychain|kSecAttrAccessibleWhenUnlocked|'
r'kSecAttrAccessibleAfterFirstUnlock|SecItemAdd|'
r'SecItemUpdate|NSDataWritingFileProtectionComplete'),
'input_case': 'exact',
},
{
'desc': 'WebView Component',
'type': 'regex',
'match': 'single_regex',
'regex1': r'UIWebView',
'input_case': 'exact'
'input_case': 'exact',
},
{
'desc': 'Encryption API',
'type': 'regex',
'match': 'single_regex',
'regex1': r'RNEncryptor|RNDecryptor|AESCrypt',
'input_case': 'exact'
'input_case': 'exact',
},
{
'desc': 'Keychain Access',
'type': 'string',
'match': 'single_string',
'string1': 'PDKeychainBindings',
'input_case': 'exact'
'input_case': 'exact',
},
{
'desc': 'WebView Load Request',
@@ -71,7 +73,7 @@ CODE_APIS = [
'match': 'string_and',
'string1': 'loadRequest',
'string2': 'webView',
'input_case': 'exact'
'input_case': 'exact',
},
{
'desc': 'WebView Load HTML String',
@@ -79,7 +81,7 @@ CODE_APIS = [
'match': 'string_and',
'string1': 'loadHTMLString',
'string2': 'webView',
'input_case': 'exact'
'input_case': 'exact',
},
{
'desc': 'Cookie Storage',
@@ -87,7 +89,7 @@ CODE_APIS = [
'match': 'string_and',
'string1': 'NSHTTPCookieStorage',
'string2': 'sharedHTTPCookieStorage',
'input_case': 'exact'
'input_case': 'exact',
},
{
'desc': 'Set or Read Clipboard',
@@ -95,7 +97,6 @@ CODE_APIS = [
'match': 'string_and',
'string1': 'UIPasteboard',
'string2': 'generalPasteboard',
'input_case': 'exact'
'input_case': 'exact',
},
]
+72 -49
View File
@@ -1,5 +1,5 @@
"""
Rule Format
Rule Format.
1. desc - Description of the findings
@@ -14,8 +14,10 @@ Rule Format
d. single_string - if string1 in input
e. string_and - if (string1 in input) and (string2 in input)
f. string_or - if (string1 in input) or (string2 in input)
g. string_and_or - if (string1 in input) and ((string_or1 in input) or (string_or2 in input))
h. string_or_and - if (string1 in input) or ((string_and1 in input) and (string_and2 in input))
g. string_and_or - if (string1 in input) and ((string_or1 in input)
or (string_or2 in input))
h. string_or_and - if (string1 in input) or ((string_and1 in input)
and (string_and2 in input))
4. level
a. high
@@ -35,52 +37,61 @@ Rule Format
"""
CODE_RULES = [
{
'desc': 'The App may contain banned API(s). These API(s) are insecure and must not be used.',
'desc': ('The App may contain banned API(s). '
'These API(s) are insecure and must not be used.'),
'type': 'regex',
'regex1': r'strcpy|memcpy|strcat|strncat|strncpy|sprintf|vsprintf|gets',
'regex1': (r'strcpy|memcpy|strcat|strncat|'
r'strncpy|sprintf|vsprintf|gets'),
'level': 'high',
'match': 'single_regex',
'input_case': 'exact',
'cvss': 2.2,
'cwe': 'CWE-676'
'cwe': 'CWE-676',
},
{
'desc': 'App allows self signed or invalid SSL certificates. App is vulnerable to MITM attacks.',
'desc': ('App allows self signed or invalid '
'SSL certificates. App is vulnerable to MITM attacks.'),
'type': 'regex',
'regex1': r'canAuthenticateAgainstProtectionSpace|' +
r'continueWithoutCredentialForAuthenticationChallenge|' +
r'kCFStreamSSLAllowsExpiredCertificates|' +
r'kCFStreamSSLAllowsAnyRoot|' +
r'kCFStreamSSLAllowsExpiredRoots|' +
r'validatesSecureCertificate\s*=\s*(no|NO)|' +
r'allowInvalidCertificates\s*=\s*(YES|yes)',
'regex1': (r'canAuthenticateAgainstProtectionSpace|'
r'continueWithoutCredentialForAuthenticationChallenge|'
r'kCFStreamSSLAllowsExpiredCertificates|'
r'kCFStreamSSLAllowsAnyRoot|'
r'kCFStreamSSLAllowsExpiredRoots|'
r'validatesSecureCertificate\s*=\s*(no|NO)|'
r'allowInvalidCertificates\s*=\s*(YES|yes)'),
'level': 'high',
'match': 'single_regex',
'input_case': 'exact',
'cvss': 7.4,
'cwe': 'CWE-295'
'cwe': 'CWE-295',
},
{
'desc': 'UIWebView in App ignore SSL errors and accept any SSL Certificate. App is vulnerable to MITM attacks.',
'desc': ('UIWebView in App ignore SSL errors and accept'
' any SSL Certificate. App is vulnerable to MITM attacks.'),
'type': 'regex',
'regex1': r'setAllowsAnyHTTPSCertificate:\s*YES|' +
r'allowsAnyHTTPSCertificateForHost|' +
r'loadingUnvalidatedHTTPSPage\s*=\s*(YES|yes)',
'regex1': (r'setAllowsAnyHTTPSCertificate:\s*YES|'
r'allowsAnyHTTPSCertificateForHost|'
r'loadingUnvalidatedHTTPSPage\s*=\s*(YES|yes)'),
'level': 'high',
'match': 'single_regex',
'input_case': 'exact',
'cvss': 7.4,
'cwe': 'CWE-295'
'cwe': 'CWE-295',
},
{
'desc': 'Files may contain hardcoded sensitive informations like usernames, passwords, keys etc.',
'desc': ('Files may contain hardcoded sensitive'
' informations like usernames, passwords, keys etc.'),
'type': 'regex',
'regex1': r'''(password\s*=\s*@*\s*['|"].+['|"]\s{0,5})|(pass\s*=\s*@*\s*['|"].+['|"]\s{0,5})|(username\s*=\s*@*\s*['|"].+['|"]\s{0,5})|(secret\s*=\s*@*\s*['|"].+['|"]\s{0,5})|(key\s*=\s*@*\s*['|"].+['|"]\s{0,5})''',
'regex1': (r'(password\s*=\s*[\'|\"].+[\'|\"]\s{0,5})|'
r'(pass\s*=\s*[\'|\"].+[\'|\"]\s{0,5})|'
r'(username\s*=\s*[\'|\"].+[\'|\"]\s{0,5})|'
r'(secret\s*=\s*[\'|\"].+[\'|\"]\s{0,5})|'
r'(key\s*=\s*[\'|\"].+[\'|\"]\s{0,5})'),
'level': 'high',
'match': 'single_regex',
'input_case': 'lower',
'cvss': 7.4,
'cwe': 'CWE-312'
'cwe': 'CWE-312',
},
{
'desc': 'IP Address disclosure',
@@ -90,50 +101,56 @@ CODE_RULES = [
'match': 'single_regex',
'input_case': 'exact',
'cvss': 4.3,
'cwe': 'CWE-200'
'cwe': 'CWE-200',
},
{
'desc': 'The App logs information. Sensitive information should never be logged.',
'desc': ('The App logs information. '
'Sensitive information should never be logged.'),
'type': 'regex',
'regex1': r'NSLog|NSAssert|fprintf|fprintf|Logging',
'level': 'info',
'match': 'single_regex',
'input_case': 'exact',
'cvss': 7.5,
'cwe': 'CWE-532'
'cwe': 'CWE-532',
},
{
'desc': 'This app listens to Clipboard changes. Some malwares also listen to Clipboard changes.',
'desc': ('This app listens to Clipboard changes. '
'Some malwares also listen to Clipboard changes.'),
'type': 'regex',
'regex1': r'UIPasteboardChangedNotification|generalPasteboard\]\.string',
'regex1': (r'UIPasteboardChangedNotification|'
r'generalPasteboard\]\.string'),
'level': 'warning',
'match': 'single_regex',
'input_case': 'exact',
'cvss': 0,
'cwe': ''
'cwe': '',
},
{
'desc': 'App uses SQLite Database. Sensitive Information should be encrypted.',
'desc': ('App uses SQLite Database. '
'Sensitive Information should be encrypted.'),
'type': 'string',
'string1': 'sqlite3_exec',
'level': 'info',
'match': 'single_string',
'input_case': 'exact',
'cvss': 0,
'cwe': ''
'cwe': '',
},
{
'desc': 'Untrusted user input to "NSTemporaryDirectory()" will result in path traversal vulnerability.',
'desc': ('Untrusted user input to "NSTemporaryDirectory()"'
' will result in path traversal vulnerability.'),
'type': 'string',
'string1': 'NSTemporaryDirectory(),',
'level': 'warning',
'match': 'single_string',
'input_case': 'exact',
'cvss': 7.5,
'cwe': 'CWE-22'
'cwe': 'CWE-22',
},
{
'desc': 'User input in "loadHTMLString" will result in JavaScript Injection.',
'desc': ('User input in "loadHTMLString" '
'will result in JavaScript Injection.'),
'type': 'string',
'string1': 'loadHTMLString',
'string2': 'webView',
@@ -141,7 +158,7 @@ CODE_RULES = [
'match': 'string_and',
'input_case': 'exact',
'cvss': 8.8,
'cwe': 'CWE-95'
'cwe': 'CWE-95',
},
{
'desc': 'SFAntiPiracy Jailbreak checks found',
@@ -153,7 +170,7 @@ CODE_RULES = [
'match': 'string_and',
'input_case': 'exact',
'cvss': 0,
'cwe': ''
'cwe': '',
},
{
'desc': 'SFAntiPiracy Piracy checks found',
@@ -165,7 +182,7 @@ CODE_RULES = [
'match': 'string_and',
'input_case': 'exact',
'cvss': 0,
'cwe': ''
'cwe': '',
},
{
'desc': 'MD5 is a weak hash known to have hash collisions.',
@@ -176,7 +193,7 @@ CODE_RULES = [
'match': 'string_and',
'input_case': 'exact',
'cvss': 7.4,
'cwe': 'CWE-327'
'cwe': 'CWE-327',
},
{
'desc': 'SHA1 is a weak hash known to have hash collisions.',
@@ -187,10 +204,12 @@ CODE_RULES = [
'match': 'string_and',
'input_case': 'exact',
'cvss': 5.9,
'cwe': 'CWE-327'
'cwe': 'CWE-327',
},
{
'desc': 'The App uses ECB mode in Cryptographic encryption algorithm. ECB mode is known to be weak as it results in the same ciphertext for identical blocks of plaintext.',
'desc': ('The App uses ECB mode in Cryptographic encryption algorithm.'
' ECB mode is known to be weak as it results in the same'
' ciphertext for identical blocks of plaintext.'),
'type': 'string',
'string1': 'kCCOptionECBMode',
'string2': 'kCCAlgorithmAES',
@@ -198,7 +217,7 @@ CODE_RULES = [
'match': 'string_and',
'input_case': 'exact',
'cvss': 5.9,
'cwe': 'CWE-327'
'cwe': 'CWE-327',
},
{
'desc': 'The App has ant-debugger code using ptrace() ',
@@ -209,7 +228,7 @@ CODE_RULES = [
'match': 'string_and',
'input_case': 'exact',
'cvss': 0,
'cwe': ''
'cwe': '',
},
{
'desc': 'This App has anti-debugger code using Mach Exception Ports.',
@@ -221,10 +240,12 @@ CODE_RULES = [
'match': 'string_and_or',
'input_case': 'exact',
'cvss': 0,
'cwe': ''
'cwe': '',
},
{
'desc': 'This App copies data to clipboard. Sensitive data should not be copied to clipboard as other applications can access it.',
'desc': ('This App copies data to clipboard. Sensitive data should'
' not be copied to clipboard as other applications'
' can access it.'),
'type': 'string',
'string1': 'UITextField',
'string_or1': '@select(cut:)',
@@ -233,7 +254,7 @@ CODE_RULES = [
'match': 'string_and_or',
'input_case': 'exact',
'cvss': 0,
'cwe': ''
'cwe': '',
},
{
'desc': 'This App may have Jailbreak detection capabilities.',
@@ -248,9 +269,11 @@ CODE_RULES = [
'string8': '/Applications/Icy.app',
'string9': '/Applications/IntelliScreen.app',
'string10': '/Applications/SBSettings.app',
'string11': '/Library/MobileSubstrate/DynamicLibraries/LiveClock.plist',
'string11': ('/Library/MobileSubstrate/DynamicLibraries/'
'LiveClock.plist'),
'string12': '/System/Library/LaunchDaemons/com.ikey.bbot.plist',
'string13': '/System/Library/LaunchDaemons/com.saurik.Cydia.Startup.plist',
'string13': ('/System/Library/LaunchDaemons/'
'com.saurik.Cydia.Startup.plist'),
'string14': '/etc/ssh/sshd_config',
'string15': '/private/var/tmp/cydia.log',
'string16': '/usr/libexec/ssh-keysign',
@@ -276,6 +299,6 @@ CODE_RULES = [
'match': 'string_or',
'input_case': 'exact',
'cvss': 0,
'cwe': ''
}
'cwe': '',
},
]
+147 -145
View File
@@ -1,140 +1,140 @@
# -*- coding: utf_8 -*-
"""Module for iOS App Plist Analysis."""
import os
import logging
import os
import plistlib
import biplist
from MobSF.utils import (
PrintException,
isFileExists
)
from biplist import (
readPlist,
writePlistToString
)
from biplist import readPlist, writePlistToString
from MobSF.utils import is_file_exists, log_exception
logger = logging.getLogger(__name__)
def convert_bin_xml(bin_xml_file):
"""Convert Binary XML to Readable XML"""
"""Convert Binary XML to Readable XML."""
try:
plist_obj = readPlist(bin_xml_file)
data = writePlistToString(plist_obj)
return data
plist_obj = readPlist(bin_xml_file)
data = writePlistToString(plist_obj)
return data
except biplist.InvalidPlistException:
logger.warning("Failed to convert plist")
def __check_permissions(p_list):
logger.warning('Failed to convert plist')
def check_permissions(p_list):
"""Check the permissions the app requests."""
# List taken from
# https://developer.apple.com/library/content/documentation/General/Reference/InfoPlistKeyReference/Articles/CocoaKeys.html
logger.info("Checking Permissions")
# https://developer.apple.com/library/content/
# documentation/General/Reference/InfoPlistKeyReference/Articles/CocoaKeys.html
logger.info('Checking Permissions')
permissions = []
if "NSAppleMusicUsageDescription" in p_list:
if 'NSAppleMusicUsageDescription' in p_list:
permissions.append({
"name": "NSAppleMusicUsageDescription",
"description": "Access Apple Media Library.",
"reason": p_list["NSAppleMusicUsageDescription"]
'name': 'NSAppleMusicUsageDescription',
'description': 'Access Apple Media Library.',
'reason': p_list['NSAppleMusicUsageDescription'],
})
if "NSBluetoothPeripheralUsageDescription" in p_list:
if 'NSBluetoothPeripheralUsageDescription' in p_list:
permissions.append({
"name": "NSBluetoothPeripheralUsageDescription",
"description": "Access Bluetooth Interface.",
"reason": p_list["NSBluetoothPeripheralUsageDescription"]
'name': 'NSBluetoothPeripheralUsageDescription',
'description': 'Access Bluetooth Interface.',
'reason': p_list['NSBluetoothPeripheralUsageDescription'],
})
if "NSCalendarsUsageDescription" in p_list:
if 'NSCalendarsUsageDescription' in p_list:
permissions.append({
"name": "NSCalendarsUsageDescription",
"description": "Access Calendars.",
"reason": p_list["NSCalendarsUsageDescription"]
'name': 'NSCalendarsUsageDescription',
'description': 'Access Calendars.',
'reason': p_list['NSCalendarsUsageDescription'],
})
if "NSCameraUsageDescription" in p_list:
if 'NSCameraUsageDescription' in p_list:
permissions.append({
"name": "NSCameraUsageDescription",
"description": "Access the Camera.",
"reason": p_list["NSCameraUsageDescription"]
'name': 'NSCameraUsageDescription',
'description': 'Access the Camera.',
'reason': p_list['NSCameraUsageDescription'],
})
if "NSContactsUsageDescription" in p_list:
if 'NSContactsUsageDescription' in p_list:
permissions.append({
"name": "NSContactsUsageDescription",
"description": "Access Contacts.",
"reason": p_list["NSContactsUsageDescription"]
'name': 'NSContactsUsageDescription',
'description': 'Access Contacts.',
'reason': p_list['NSContactsUsageDescription'],
})
if "NSHealthShareUsageDescription" in p_list:
if 'NSHealthShareUsageDescription' in p_list:
permissions.append({
"name": "NSHealthShareUsageDescription",
"description": "Read Health Data.",
"reason": p_list["NSHealthShareUsageDescription"]
'name': 'NSHealthShareUsageDescription',
'description': 'Read Health Data.',
'reason': p_list['NSHealthShareUsageDescription'],
})
if "NSHealthUpdateUsageDescription" in p_list:
if 'NSHealthUpdateUsageDescription' in p_list:
permissions.append({
"name": "NSHealthUpdateUsageDescription",
"description": "Write Health Data.",
"reason": p_list["NSHealthUpdateUsageDescription"]
'name': 'NSHealthUpdateUsageDescription',
'description': 'Write Health Data.',
'reason': p_list['NSHealthUpdateUsageDescription'],
})
if "NSHomeKitUsageDescription" in p_list:
if 'NSHomeKitUsageDescription' in p_list:
permissions.append({
"name": "NSHomeKitUsageDescription",
"description": "Access HomeKit configuration data.",
"reason": p_list["NSHomeKitUsageDescription"]
'name': 'NSHomeKitUsageDescription',
'description': 'Access HomeKit configuration data.',
'reason': p_list['NSHomeKitUsageDescription'],
})
if "NSLocationAlwaysUsageDescription" in p_list:
if 'NSLocationAlwaysUsageDescription' in p_list:
permissions.append({
"name": "NSLocationAlwaysUsageDescription",
"description": "Access location information at all times.",
"reason": p_list["NSLocationAlwaysUsageDescription"]
'name': 'NSLocationAlwaysUsageDescription',
'description': 'Access location information at all times.',
'reason': p_list['NSLocationAlwaysUsageDescription'],
})
if "NSLocationUsageDescription" in p_list:
if 'NSLocationUsageDescription' in p_list:
permissions.append({
"name": "NSLocationUsageDescription",
"description": "Access location information at all times (< iOS 8).",
"reason": p_list["NSLocationUsageDescription"]
'name': 'NSLocationUsageDescription',
'description': ('Access location information'
' at all times (< iOS 8).'),
'reason': p_list['NSLocationUsageDescription'],
})
if "NSLocationWhenInUseUsageDescription" in p_list:
if 'NSLocationWhenInUseUsageDescription' in p_list:
permissions.append({
"name": "NSLocationWhenInUseUsageDescription",
"description": "Access location information when app is in the foreground.",
"reason": p_list["NSLocationWhenInUseUsageDescription"]
'name': 'NSLocationWhenInUseUsageDescription',
'description': ('Access location information when'
' app is in the foreground.'),
'reason': p_list['NSLocationWhenInUseUsageDescription'],
})
if "NSMicrophoneUsageDescription" in p_list:
if 'NSMicrophoneUsageDescription' in p_list:
permissions.append({
"name": "NSMicrophoneUsageDescription",
"description": "Access microphone.",
"reason": p_list["NSMicrophoneUsageDescription"]
'name': 'NSMicrophoneUsageDescription',
'description': 'Access microphone.',
'reason': p_list['NSMicrophoneUsageDescription'],
})
if "NSMotionUsageDescription" in p_list:
if 'NSMotionUsageDescription' in p_list:
permissions.append({
"name": "NSMotionUsageDescription",
"description": "Access the device’s accelerometer.",
"reason": p_list["NSMotionUsageDescription"]
'name': 'NSMotionUsageDescription',
'description': 'Access the device’s accelerometer.',
'reason': p_list['NSMotionUsageDescription'],
})
if "NSPhotoLibraryUsageDescription" in p_list:
if 'NSPhotoLibraryUsageDescription' in p_list:
permissions.append({
"name": "NSPhotoLibraryUsageDescription",
"description": "Access the user’s photo library.",
"reason": p_list["NSPhotoLibraryUsageDescription"]
'name': 'NSPhotoLibraryUsageDescription',
'description': 'Access the user’s photo library.',
'reason': p_list['NSPhotoLibraryUsageDescription'],
})
if "NSRemindersUsageDescription" in p_list:
if 'NSRemindersUsageDescription' in p_list:
permissions.append({
"name": "NSRemindersUsageDescription",
"description": "Access the user’s reminders.",
"reason": p_list["NSRemindersUsageDescription"]
'name': 'NSRemindersUsageDescription',
'description': 'Access the user’s reminders.',
'reason': p_list['NSRemindersUsageDescription'],
})
if "NSVideoSubscriberAccountUsageDescription" in p_list:
if 'NSVideoSubscriberAccountUsageDescription' in p_list:
permissions.append({
"name": "NSVideoSubscriberAccountUsageDescription",
"description": "Access the user’s TV provider account.",
"reason": p_list["NSVideoSubscriberAccountUsageDescription"]
'name': 'NSVideoSubscriberAccountUsageDescription',
'description': 'Access the user’s TV provider account.',
'reason': p_list['NSVideoSubscriberAccountUsageDescription'],
})
return permissions
def __check_insecure_connections(p_list):
def check_insecure_connections(p_list):
"""Check info.plist for insecure connection configurations."""
logger.info("Checking for Insecure Connections")
logger.info('Checking for Insecure Connections')
insecure_connections = []
if 'NSAppTransportSecurity' in p_list:
@@ -149,86 +149,88 @@ def __check_insecure_connections(p_list):
def plist_analysis(src, is_source):
"""Plist Analysis"""
"""Plist Analysis."""
try:
logger.info("iOS Info.plist Analysis Started")
logger.info('iOS Info.plist Analysis Started')
plist_info = {
"bin_name": "",
"bin": "",
"id": "",
"version": "",
"build": "",
"sdk": "",
"pltfm": "",
"min": "",
"plist_xml": "",
"permissions": [],
"inseccon": [],
"bundle_name": "",
"build_version_name": "",
"bundle_url_types": [],
"bundle_supported_platforms": [],
"bundle_localizations": []
'bin_name': '',
'bin': '',
'id': '',
'version': '',
'build': '',
'sdk': '',
'pltfm': '',
'min': '',
'plist_xml': '',
'permissions': [],
'inseccon': [],
'bundle_name': '',
'build_version_name': '',
'bundle_url_types': [],
'bundle_supported_platforms': [],
'bundle_localizations': [],
}
plist_file = None
if is_source:
logger.info("Finding Info.plist in iOS Source")
for ifile in os.listdir(src):
if ifile.endswith(".xcodeproj"):
app_name = ifile.replace(".xcodeproj", "")
break
app_plist_file = "Info.plist"
for dirpath, dirnames, files in os.walk(src):
logger.info('Finding Info.plist in iOS Source')
app_plist_file = 'Info.plist'
for dirpath, _dirnames, files in os.walk(src):
for name in files:
if "__MACOSX" not in dirpath and name == app_plist_file:
if '__MACOSX' not in dirpath and name == app_plist_file:
plist_file = os.path.join(dirpath, name)
break
else:
logger.info("Finding Info.plist in iOS Binary")
logger.info('Finding Info.plist in iOS Binary')
dirs = os.listdir(src)
dot_app_dir = ""
dot_app_dir = ''
for dir_ in dirs:
if dir_.endswith(".app"):
if dir_.endswith('.app'):
dot_app_dir = dir_
break
bin_dir = os.path.join(src, dot_app_dir) # Full Dir/Payload/x.app
plist_file = os.path.join(bin_dir, "Info.plist")
if not isFileExists(plist_file):
logger.warning("Cannot find Info.plist file. Skipping Plist Analysis.")
bin_dir = os.path.join(src, dot_app_dir) # Full Dir/Payload/x.app
plist_file = os.path.join(bin_dir, 'Info.plist')
if not is_file_exists(plist_file):
logger.warning(
'Cannot find Info.plist file. Skipping Plist Analysis.')
else:
#Generic Plist Analysis
# Generic Plist Analysis
plist_obj = plistlib.readPlist(plist_file)
plist_info["plist_xml"] = plistlib.writePlistToBytes(plist_obj).decode("utf-8", "ignore")
if "CFBundleDisplayName" in plist_obj:
plist_info["bin_name"] = plist_obj["CFBundleDisplayName"]
plist_info['plist_xml'] = plistlib.writePlistToBytes(
plist_obj).decode('utf-8', 'ignore')
if 'CFBundleDisplayName' in plist_obj:
plist_info['bin_name'] = plist_obj['CFBundleDisplayName']
else:
if not is_source:
#For iOS IPA
plist_info["bin_name"] = dot_app_dir.replace(".app", "")
if "CFBundleExecutable" in plist_obj:
plist_info["bin"] = plist_obj["CFBundleExecutable"]
if "CFBundleIdentifier" in plist_obj:
plist_info["id"] = plist_obj["CFBundleIdentifier"]
# For iOS IPA
plist_info['bin_name'] = dot_app_dir.replace('.app', '')
if 'CFBundleExecutable' in plist_obj:
plist_info['bin'] = plist_obj['CFBundleExecutable']
if 'CFBundleIdentifier' in plist_obj:
plist_info['id'] = plist_obj['CFBundleIdentifier']
# build
if "CFBundleVersion" in plist_obj:
plist_info["build"] = plist_obj["CFBundleVersion"]
if "DTSDKName" in plist_obj:
plist_info["sdk"] = plist_obj["DTSDKName"]
if "DTPlatformVersion" in plist_obj:
plist_info["pltfm"] = plist_obj["DTPlatformVersion"]
if "MinimumOSVersion" in plist_obj:
plist_info["min"] = plist_obj["MinimumOSVersion"]
if 'CFBundleVersion' in plist_obj:
plist_info['build'] = plist_obj['CFBundleVersion']
if 'DTSDKName' in plist_obj:
plist_info['sdk'] = plist_obj['DTSDKName']
if 'DTPlatformVersion' in plist_obj:
plist_info['pltfm'] = plist_obj['DTPlatformVersion']
if 'MinimumOSVersion' in plist_obj:
plist_info['min'] = plist_obj['MinimumOSVersion']
plist_info["bundle_name"] = plist_obj.get("CFBundleName", "")
plist_info["bundle_version_name"] = plist_obj.get("CFBundleShortVersionString", "")
plist_info["bundle_url_types"] = plist_obj.get("CFBundleURLTypes", [])
plist_info["bundle_supported_platforms"] = plist_obj.get("CFBundleSupportedPlatforms", [])
plist_info["bundle_localizations"] = plist_obj.get("CFBundleLocalizations", [])
plist_info['bundle_name'] = plist_obj.get('CFBundleName', '')
plist_info['bundle_version_name'] = plist_obj.get(
'CFBundleShortVersionString', '')
plist_info['bundle_url_types'] = plist_obj.get(
'CFBundleURLTypes', [])
plist_info['bundle_supported_platforms'] = plist_obj.get(
'CFBundleSupportedPlatforms', [])
plist_info['bundle_localizations'] = plist_obj.get(
'CFBundleLocalizations', [])
# Check possible app-permissions
plist_info["permissions"] = __check_permissions(plist_obj)
plist_info["inseccon"] = __check_insecure_connections(plist_obj)
plist_info['permissions'] = check_permissions(plist_obj)
plist_info['inseccon'] = check_insecure_connections(plist_obj)
return plist_info
except:
PrintException("Reading from Info.plist")
except Exception:
log_exception('Reading from Info.plist')
+142 -141
View File
@@ -1,60 +1,33 @@
# -*- coding: utf_8 -*-
"""
iOS Static Code Analysis
"""
import re
import os
import io
import shutil
"""iOS Static Code Analysis."""
import logging
from django.shortcuts import render
from django.http import HttpResponseRedirect
from django.conf import settings
from django.utils.html import escape
from StaticAnalyzer.views.ios.db_interaction import (
get_context_from_analysis_ipa,
get_context_from_db_entry_ipa,
update_db_entry_ipa,
create_db_entry_ipa,
get_context_from_analysis_ios,
get_context_from_db_entry_ios,
update_db_entry_ios,
create_db_entry_ios,
)
from StaticAnalyzer.views.ios.binary_analysis import (
binary_analysis,
)
from StaticAnalyzer.views.ios.code_analysis import (
ios_source_analysis,
)
from StaticAnalyzer.views.ios.plist_analysis import (
plist_analysis,
convert_bin_xml
)
from StaticAnalyzer.views.ios.appstore import(
app_search,
)
from StaticAnalyzer.views.shared_func import (
file_size,
firebase_analysis,
hash_gen,
unzip,
score,
update_scan_timestamp,
)
from StaticAnalyzer.models import StaticAnalyzerIPA, StaticAnalyzerIOSZIP
from MobSF.utils import (
print_n_send_error_response,
PrintException,
isFileExists
)
import os
import re
import shutil
import MalwareAnalyzer.views.VirusTotal as VirusTotal
from django.conf import settings
from django.shortcuts import render
from django.utils.html import escape
from MobSF.utils import (log_exception,
print_n_send_error_response)
from StaticAnalyzer.models import StaticAnalyzerIOSZIP, StaticAnalyzerIPA
from StaticAnalyzer.views.ios.appstore import app_search
from StaticAnalyzer.views.ios.binary_analysis import binary_analysis
from StaticAnalyzer.views.ios.code_analysis import ios_source_analysis
from StaticAnalyzer.views.ios.db_interaction import (
create_db_entry_ios, create_db_entry_ipa, get_context_from_analysis_ios,
get_context_from_analysis_ipa, get_context_from_db_entry_ios,
get_context_from_db_entry_ipa, update_db_entry_ios, update_db_entry_ipa)
from StaticAnalyzer.views.ios.plist_analysis import (convert_bin_xml,
plist_analysis)
from StaticAnalyzer.views.shared_func import (file_size, firebase_analysis,
hash_gen, score, unzip,
update_scan_timestamp)
logger = logging.getLogger(__name__)
##############################################################
@@ -63,9 +36,9 @@ logger = logging.getLogger(__name__)
def ios_list_files(src, md5_hash, binary_form, mode):
"""List iOS files"""
"""List iOS files."""
try:
logger.info("Get Files, BIN Plist -> XML, and Normalize")
logger.info('Get Files, BIN Plist -> XML, and Normalize')
# Multi function, Get Files, BIN Plist -> XML, normalize + to x
filez = []
certz = []
@@ -74,55 +47,56 @@ def ios_list_files(src, md5_hash, binary_form, mode):
plist = []
for dirname, _, files in os.walk(src):
for jfile in files:
if not jfile.endswith(".DS_Store"):
if not jfile.endswith('.DS_Store'):
file_path = os.path.join(src, dirname, jfile)
if "+" in jfile:
if '+' in jfile:
plus2x = os.path.join(
src, dirname, jfile.replace("+", "x"))
src, dirname, jfile.replace('+', 'x'))
shutil.move(file_path, plus2x)
file_path = plus2x
fileparam = file_path.replace(src, '')
filez.append(fileparam)
ext = jfile.split('.')[-1]
if re.search("cer|pem|cert|crt|pub|key|pfx|p12", ext):
if re.search('cer|pem|cert|crt|pub|key|pfx|p12', ext):
certz.append({
'file_path': escape(file_path.replace(src, '')),
'type': None,
'hash': None
'hash': None,
})
if re.search("db|sqlitedb|sqlite", ext):
if re.search('db|sqlitedb|sqlite', ext):
database.append({
'file_path': escape(fileparam),
'type': mode,
'hash': md5_hash
'hash': md5_hash,
})
if jfile.endswith(".plist"):
if jfile.endswith('.plist'):
if binary_form:
convert_bin_xml(file_path)
plist.append({
'file_path': escape(fileparam),
'type': mode,
'hash': md5_hash
'hash': md5_hash,
})
if len(database) > 0:
sfiles.append({"issue": "SQLite Files", "files": database})
sfiles.append({'issue': 'SQLite Files', 'files': database})
if len(plist) > 0:
sfiles.append({"issue": "Plist Files", "files": plist})
sfiles.append({'issue': 'Plist Files', 'files': plist})
if len(certz) > 0:
sfiles.append(
{"issue": "Certificate/Key Files Hardcoded inside the App.", "files": certz})
{'issue': 'Certificate/Key Files Hardcoded inside the App.',
'files': certz})
return filez, sfiles
except:
PrintException("iOS List Files")
except Exception:
log_exception('iOS List Files')
def static_analyzer_ios(request, api=False):
"""Module that performs iOS IPA/ZIP Static Analysis"""
"""Module that performs iOS IPA/ZIP Static Analysis."""
try:
logger.info("iOS Static Analysis Started")
logger.info('iOS Static Analysis Started')
if api:
file_type = request.POST['scan_type']
checksum = request.POST['hash']
@@ -135,62 +109,75 @@ def static_analyzer_ios(request, api=False):
filename = request.GET['name']
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'])
):
if ((md5_match)
and (filename.lower().endswith('.ipa')
or filename.lower().endswith('.zip'))
and (file_type in ['ipa', 'ios'])):
app_dict = {}
app_dict["directory"] = settings.BASE_DIR # BASE DIR
app_dict["file_name"] = filename # APP ORGINAL NAME
app_dict["md5_hash"] = checksum # MD5
app_dict["app_dir"] = os.path.join(
settings.UPLD_DIR, app_dict["md5_hash"] + '/') # APP DIRECTORY
app_dict['directory'] = settings.BASE_DIR # BASE DIR
app_dict['file_name'] = filename # APP ORGINAL NAME
app_dict['md5_hash'] = checksum # MD5
app_dict['app_dir'] = os.path.join(
settings.UPLD_DIR, app_dict['md5_hash'] + '/') # APP DIRECTORY
tools_dir = os.path.join(
app_dict["directory"], 'StaticAnalyzer/tools/mac/') # TOOLS DIR
app_dict['directory'], 'StaticAnalyzer/tools/mac/')
if file_type == 'ipa':
# DB
ipa_db = StaticAnalyzerIPA.objects.filter(
MD5=app_dict["md5_hash"])
MD5=app_dict['md5_hash'])
if ipa_db.exists() and rescan == '0':
context = get_context_from_db_entry_ipa(ipa_db)
else:
logger.info("iOS Binary (IPA) Analysis Started")
app_dict["app_file"] = app_dict[
"md5_hash"] + '.ipa' # NEW FILENAME
app_dict["app_path"] = app_dict["app_dir"] + \
app_dict["app_file"] # APP PATH
app_dict["bin_dir"] = os.path.join(
app_dict["app_dir"], "Payload/")
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")
logger.info('iOS Binary (IPA) Analysis Started')
app_dict['app_file'] = app_dict[
'md5_hash'] + '.ipa' # NEW FILENAME
app_dict['app_path'] = (app_dict['app_dir']
+ app_dict['app_file'])
app_dict['bin_dir'] = os.path.join(
app_dict['app_dir'], 'Payload/')
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"])
unzip(app_dict['app_path'], app_dict['app_dir'])
# Get Files, normalize + to x,
# and convert binary plist -> xml
files, sfiles = 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['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'))
bin_analysis_dict = binary_analysis(
app_dict["bin_dir"], tools_dir, app_dict["app_dir"], infoplist_dict.get("bin"))
app_dict['bin_dir'],
tools_dir,
app_dict['app_dir'],
infoplist_dict.get('bin'))
# Saving to DB
logger.info("Connecting to DB")
logger.info('Connecting to DB')
if rescan == '1':
logger.info("Updating Database...")
logger.info('Updating Database...')
update_db_entry_ipa(
app_dict, infoplist_dict, bin_analysis_dict, files, sfiles)
update_scan_timestamp(app_dict["md5_hash"])
app_dict,
infoplist_dict,
bin_analysis_dict,
files,
sfiles)
update_scan_timestamp(app_dict['md5_hash'])
elif rescan == '0':
logger.info("Saving to Database")
logger.info('Saving to Database')
create_db_entry_ipa(
app_dict, infoplist_dict, bin_analysis_dict, files, sfiles)
app_dict,
infoplist_dict,
bin_analysis_dict,
files,
sfiles)
context = get_context_from_analysis_ipa(
app_dict, infoplist_dict, bin_analysis_dict, files, sfiles)
app_dict,
infoplist_dict,
bin_analysis_dict,
files,
sfiles)
context['VT_RESULT'] = None
if settings.VT_ENABLED:
@@ -198,69 +185,83 @@ def static_analyzer_ios(request, api=False):
context['VT_RESULT'] = vt.get_result(
os.path.join(app_dict['app_dir'], app_dict[
'md5_hash']) + '.ipa',
app_dict['md5_hash']
)
context["average_cvss"], context[
"security_score"] = score(context["bin_anal"])
template = "static_analysis/ios_binary_analysis.html"
app_dict['md5_hash'])
context['average_cvss'], context[
'security_score'] = score(context['bin_anal'])
template = 'static_analysis/ios_binary_analysis.html'
if api:
return context
else:
return render(request, template, context)
elif file_type == 'ios':
ios_zip_db = StaticAnalyzerIOSZIP.objects.filter(
MD5=app_dict["md5_hash"])
MD5=app_dict['md5_hash'])
if ios_zip_db.exists() and rescan == '0':
context = get_context_from_db_entry_ios(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_dict["app_dir"] + \
app_dict["app_file"] # APP PATH
logger.info('iOS Source Code Analysis Started')
app_dict['app_file'] = app_dict[
'md5_hash'] + '.zip' # NEW FILENAME
app_dict['app_path'] = (app_dict['app_dir']
+ app_dict['app_file'])
# ANALYSIS BEGINS - Already Unzipped
logger.info("ZIP Already Extracted")
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('ZIP Already Extracted')
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
files, sfiles = 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['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'))
code_analysis_dic = ios_source_analysis(
app_dict["app_dir"])
app_dict['app_dir'])
# Firebase DB Check
code_analysis_dic['firebase'] = firebase_analysis(
list(set(code_analysis_dic["urls_list"])))
list(set(code_analysis_dic['urls_list'])))
# Saving to DB
logger.info("Connecting to DB")
logger.info('Connecting to DB')
if rescan == '1':
logger.info("Updating Database...")
logger.info('Updating Database...')
update_db_entry_ios(
app_dict, infoplist_dict, code_analysis_dic, files, sfiles)
update_scan_timestamp(app_dict["md5_hash"])
app_dict,
infoplist_dict,
code_analysis_dic,
files,
sfiles)
update_scan_timestamp(app_dict['md5_hash'])
elif rescan == '0':
logger.info("Saving to Database")
logger.info('Saving to Database')
create_db_entry_ios(
app_dict, infoplist_dict, code_analysis_dic, files, sfiles)
app_dict,
infoplist_dict,
code_analysis_dic,
files,
sfiles)
context = get_context_from_analysis_ios(
app_dict, infoplist_dict, code_analysis_dic, files, sfiles)
context["average_cvss"], context[
"security_score"] = score(context["insecure"])
template = "static_analysis/ios_source_analysis.html"
app_dict,
infoplist_dict,
code_analysis_dic,
files,
sfiles)
context['average_cvss'], context[
'security_score'] = score(context['insecure'])
template = 'static_analysis/ios_source_analysis.html'
if api:
return context
else:
return render(request, template, context)
else:
msg = "File Type not supported!"
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"
msg = 'Hash match failed or Invalid file extension or file type'
if api:
return print_n_send_error_response(request, msg, True)
else:
+62 -66
View File
@@ -1,58 +1,48 @@
# -*- coding: utf_8 -*-
"""
iOS View Source
"""
import re
import os
"""iOS View Source."""
import io
import json
import ntpath
import sqlite3
import logging
from django.shortcuts import render
from django.http import HttpResponseRedirect
from django.utils.html import escape
import ntpath
import os
import sqlite3
import biplist
from django.conf import settings
from django.http import HttpResponseRedirect
from django.shortcuts import render
from django.utils.html import escape
from MobSF.forms import FormUtil
from MobSF.utils import (is_file_exists, log_exception,
print_n_send_error_response)
from StaticAnalyzer.forms import ViewSourceIOSApiForm, ViewSourceIOSForm
logger = logging.getLogger(__name__)
import biplist
from MobSF.forms import (
FormUtil
)
from StaticAnalyzer.forms import (
ViewSourceIOSApiForm,
ViewSourceIOSForm
)
from MobSF.utils import (
print_n_send_error_response,
PrintException,
isFileExists
)
def set_ext_api(file_path):
"""
Smart Function to set Extenstion
"""
"""Smart Function to set Extenstion."""
ext = file_path.split('.')[-1]
if ext == "plist":
return "plist"
if ext == 'plist':
return 'plist'
elif ext == 'xml':
return 'xml'
elif ext in ["sqlitedb", "db", "sqlite"]:
return "db"
elif ext == "m":
return "m"
elif ext in ['sqlitedb', 'db', 'sqlite']:
return 'db'
elif ext == 'm':
return 'm'
else:
return "txt"
return 'txt'
def run(request, api=False):
"""View iOS Files"""
"""View iOS Files."""
try:
logger.info("View iOS Source File")
file_format = "cpp"
logger.info('View iOS Source File')
file_format = 'cpp'
if api:
fil = request.POST['file']
md5_hash = request.POST['hash']
@@ -71,9 +61,9 @@ def run(request, api=False):
context = {
'title': 'Error',
'exp': 'Error Description',
'doc': err
'doc': err,
}
template = "general/error.html"
template = 'general/error.html'
return render(request, template, context, status=400)
if mode == 'ipa':
src = os.path.join(settings.UPLD_DIR,
@@ -84,44 +74,49 @@ def run(request, api=False):
dat = ''
if typ == 'm':
file_format = 'cpp'
with io.open(sfile, mode='r', encoding="utf8", errors="ignore") as flip:
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:
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:
except Exception:
pass
elif typ == 'db':
file_format = 'asciidoc'
dat = read_sqlite(sfile)
elif typ == 'txt' and fil == "classdump.txt":
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 isFileExists(cls_dump_file):
cls_dump_file = os.path.join(app_dir, 'classdump.txt')
if is_file_exists(cls_dump_file):
with io.open(cls_dump_file,
mode='r',
encoding="utf8",
errors="ignore"
) as flip:
encoding='utf8',
errors='ignore') as flip:
dat = flip.read()
else:
dat = "Class Dump result not Found"
dat = 'Class Dump result not Found'
else:
if api:
return {"error": "Invalid Parameters"}
return {'error': 'Invalid Parameters'}
return HttpResponseRedirect('/error/')
context = {'title': escape(ntpath.basename(fil)),
'file': escape(ntpath.basename(fil)),
'type': file_format,
'dat': dat}
template = "general/view.html"
template = 'general/view.html'
if api:
return context
return render(request, template, context)
@@ -135,31 +130,32 @@ def run(request, api=False):
def read_sqlite(sqlite_file):
"""Read SQlite File"""
"""Read SQlite File."""
try:
logger.info("Dumping SQLITE Database")
logger.info('Dumping SQLITE Database')
data = ''
con = sqlite3.connect(sqlite_file)
cur = con.cursor()
cur.execute("SELECT name FROM sqlite_master WHERE type='table';")
cur.execute('SELECT name FROM sqlite_master WHERE type=\'table\';')
tables = cur.fetchall()
for table in tables:
data += "\nTABLE: " + str(table[0]).decode('utf8', 'ignore') + \
" \n=====================================================\n"
cur.execute("PRAGMA table_info('%s')" % table)
data += ('\nTABLE: ' + str(table[0]).decode('utf8', 'ignore')
+ ' \n================================'
+ '=====================\n')
cur.execute('PRAGMA table_info(\'%s\')' % table)
rows = cur.fetchall()
head = ''
for row in rows:
head += str(row[1]).decode('utf8', 'ignore') + " | "
data += head + " \n========================================" +\
"=============================\n"
cur.execute("SELECT * FROM '%s'" % table)
head += str(row[1]).decode('utf8', 'ignore') + ' | '
data += head + (' \n========================================'
'=============================\n')
cur.execute('SELECT * FROM \'%s\'' % table)
rows = cur.fetchall()
for row in rows:
dat = ''
for item in row:
dat += str(item).decode('utf8', 'ignore') + " | "
data += dat + "\n"
dat += str(item).decode('utf8', 'ignore') + ' | '
data += dat + '\n'
return data
except:
PrintException("Dumping SQLITE Database")
except Exception:
log_exception('Dumping SQLITE Database')
+246 -226
View File
@@ -1,56 +1,49 @@
# -*- coding: utf_8 -*-
"""
Shared Functions.
Module providing the shared functions for static analysis of iOS and Android
"""
import hashlib
import io
import json
import logging
import os
import platform
import re
import json
import subprocess
import zipfile
import logging
import requests
from urllib.parse import urlparse
from django.http import HttpResponse
from django.template.loader import get_template
from django.utils.html import escape
from django.utils import timezone
from MobSF.utils import (
print_n_send_error_response,
PrintException,
python_list,
upstream_proxy,
)
from MobSF import settings
from StaticAnalyzer.models import(
RecentScansDB,
StaticAnalyzerAndroid,
StaticAnalyzerIOSZIP,
StaticAnalyzerIPA,
StaticAnalyzerWindows,
)
from StaticAnalyzer.views.android.db_interaction import (
get_context_from_db_entry
)
from StaticAnalyzer.views.ios.db_interaction import (
get_context_from_db_entry_ipa,
get_context_from_db_entry_ios
)
import requests
import MalwareAnalyzer.views.VirusTotal as VirusTotal
from django.http import HttpResponse
from django.template.loader import get_template
from django.utils import timezone
from django.utils.html import escape
from MobSF import settings
from MobSF.utils import (log_exception, print_n_send_error_response,
python_list, upstream_proxy)
from StaticAnalyzer.models import (RecentScansDB, StaticAnalyzerAndroid,
StaticAnalyzerIOSZIP, StaticAnalyzerIPA,
StaticAnalyzerWindows)
from StaticAnalyzer.views.android.db_interaction import (
get_context_from_db_entry)
from StaticAnalyzer.views.comparer import generic_compare
from StaticAnalyzer.views.ios.db_interaction import (
get_context_from_db_entry_ios, get_context_from_db_entry_ipa)
logger = logging.getLogger(__name__)
try:
import pdfkit
except ImportError:
logger.warning(
"wkhtmltopdf is not installed/configured properly. PDF Report Generation is disabled")
'wkhtmltopdf is not installed/configured properly.'
' PDF Report Generation is disabled')
logger = logging.getLogger(__name__)
@@ -60,9 +53,9 @@ def file_size(app_path):
def hash_gen(app_path) -> tuple:
"""Generate and return sha1 and sha256 as a tupel."""
"""Generate and return sha1 and sha256 as a tuple."""
try:
logger.info("Generating Hashes")
logger.info('Generating Hashes')
sha1 = hashlib.sha1()
sha256 = hashlib.sha256()
block_size = 65536
@@ -75,29 +68,29 @@ def hash_gen(app_path) -> tuple:
sha1val = sha1.hexdigest()
sha256val = sha256.hexdigest()
return sha1val, sha256val
except:
PrintException("Generating Hashes")
except Exception:
log_exception('Generating Hashes')
def unzip(app_path, ext_path):
logger.info("Unzipping")
logger.info('Unzipping')
try:
files = []
with zipfile.ZipFile(app_path, "r") as zipptr:
with zipfile.ZipFile(app_path, 'r') as zipptr:
for fileinfo in zipptr.infolist():
filename = fileinfo.filename
if not isinstance(filename, str):
filename = str(
filename, encoding="utf-8", errors="replace")
filename, encoding='utf-8', errors='replace')
files.append(filename)
zipptr.extract(filename, ext_path)
return files
except:
PrintException("Unzipping Error")
if platform.system() == "Windows":
logger.info("Not yet Implemented.")
except Exception:
log_exception('Unzipping Error')
if platform.system() == 'Windows':
logger.info('Not yet Implemented.')
else:
logger.info("Using the Default OS Unzip Utility.")
logger.info('Using the Default OS Unzip Utility.')
try:
subprocess.call(
['unzip', '-o', '-q', app_path, '-d', ext_path])
@@ -106,8 +99,8 @@ def unzip(app_path, ext_path):
files_det = ['Length Date Time Name']
files_det = files_det + dat
return files_det
except:
PrintException("Unzipping Error")
except Exception:
log_exception('Unzipping Error')
def pdf(request, api=False, jsonres=False):
@@ -124,64 +117,74 @@ def pdf(request, api=False, jsonres=False):
static_db = StaticAnalyzerAndroid.objects.filter(MD5=checksum)
if static_db.exists():
logger.info(
"Fetching data from DB for PDF Report Generation (Android)")
'Fetching data from DB for '
'PDF Report Generation (Android)')
context = get_context_from_db_entry(static_db)
context["average_cvss"], context[
"security_score"] = score(context["findings"])
context['average_cvss'], context[
'security_score'] = score(context['findings'])
if scan_type.lower() == 'apk':
template = get_template(
"pdf/android_binary_analysis.pdf.html")
'pdf/android_binary_analysis.pdf.html')
else:
template = get_template(
"pdf/android_source_analysis_pdf.html")
'pdf/android_source_analysis_pdf.html')
else:
if api:
return {"report": "Report not Found"}
return {'report': 'Report not Found'}
else:
return HttpResponse(json.dumps({"report": "Report not Found"}),
content_type="application/json; charset=utf-8", status=500)
return HttpResponse(
json.dumps({'report': 'Report not Found'}),
content_type='application/json; charset=utf-8',
status=500)
elif scan_type.lower() in ['ipa', 'ioszip']:
if scan_type.lower() == 'ipa':
static_db = StaticAnalyzerIPA.objects.filter(MD5=checksum)
if static_db.exists():
logger.info(
"Fetching data from DB for PDF Report Generation (IOS IPA)")
'Fetching data from DB for '
'PDF Report Generation (IOS IPA)')
context = get_context_from_db_entry_ipa(static_db)
context["average_cvss"], context[
"security_score"] = score(context["bin_anal"])
context['average_cvss'], context[
'security_score'] = score(context['bin_anal'])
template = get_template(
"pdf/ios_binary_analysis_pdf.html")
'pdf/ios_binary_analysis_pdf.html')
else:
if api:
return {"report": "Report not Found"}
return {'report': 'Report not Found'}
else:
return HttpResponse(json.dumps({"report": "Report not Found"}),
content_type="application/json; charset=utf-8", status=500)
return HttpResponse(
json.dumps({'report': 'Report not Found'}),
content_type='application/json; charset=utf-8',
status=500)
elif scan_type.lower() == 'ioszip':
static_db = StaticAnalyzerIOSZIP.objects.filter(
MD5=checksum)
if static_db.exists():
logger.info(
"Fetching data from DB for PDF Report Generation (IOS ZIP)")
'Fetching data from DB for '
'PDF Report Generation (IOS ZIP)')
context = get_context_from_db_entry_ios(static_db)
context["average_cvss"], context[
"security_score"] = score(context["insecure"])
context['average_cvss'], context[
'security_score'] = score(context['insecure'])
template = get_template(
"pdf/ios_source_analysis_pdf.html")
'pdf/ios_source_analysis_pdf.html')
else:
if api:
return {"report": "Report not Found"}
return {'report': 'Report not Found'}
else:
return HttpResponse(json.dumps({"report": "Report not Found"}),
content_type="application/json; charset=utf-8", status=500)
return HttpResponse(
json.dumps({'report': 'Report not Found'}),
content_type='application/json; charset=utf-8',
status=500)
elif 'appx' == scan_type.lower():
if scan_type.lower() == 'appx':
db_entry = StaticAnalyzerWindows.objects.filter( # pylint: disable-msg=E1101
MD5=checksum
db_entry = StaticAnalyzerWindows.objects.filter(
MD5=checksum,
)
if db_entry.exists():
logger.info(
"Fetching data from DB for PDF Report Generation (APPX)")
'Fetching data from DB for '
'PDF Report Generation (APPX)')
context = {
'title': db_entry[0].TITLE,
@@ -192,45 +195,50 @@ def pdf(request, api=False, jsonres=False):
'sha1': db_entry[0].SHA1,
'sha256': db_entry[0].SHA256,
'bin_name': db_entry[0].BINNAME,
'version': db_entry[0].VERSION,
'arch': db_entry[0].ARCH,
'compiler_version': db_entry[0].COMPILER_VERSION,
'visual_studio_version': db_entry[0].VISUAL_STUDIO_VERSION,
'visual_studio_edition': db_entry[0].VISUAL_STUDIO_EDITION,
'target_os': db_entry[0].TARGET_OS,
'appx_dll_version': db_entry[0].APPX_DLL_VERSION,
'proj_guid': db_entry[0].PROJ_GUID,
'opti_tool': db_entry[0].OPTI_TOOL,
'target_run': db_entry[0].TARGET_RUN,
'files': python_list(db_entry[0].FILES),
'version': db_entry[0].VERSION,
'arch': db_entry[0].ARCH,
'compiler_version': db_entry[0].COMPILER_VERSION,
'visual_studio_version':
db_entry[0].VISUAL_STUDIO_VERSION,
'visual_studio_edition':
db_entry[0].VISUAL_STUDIO_EDITION,
'target_os': db_entry[0].TARGET_OS,
'appx_dll_version': db_entry[0].APPX_DLL_VERSION,
'proj_guid': db_entry[0].PROJ_GUID,
'opti_tool': db_entry[0].OPTI_TOOL,
'target_run': db_entry[0].TARGET_RUN,
'files': python_list(db_entry[0].FILES),
'strings': python_list(db_entry[0].STRINGS),
'bin_an_results': python_list(db_entry[0].BIN_AN_RESULTS),
'bin_an_warnings': python_list(db_entry[0].BIN_AN_WARNINGS)
'bin_an_results':
python_list(db_entry[0].BIN_AN_RESULTS),
'bin_an_warnings':
python_list(db_entry[0].BIN_AN_WARNINGS),
}
template = get_template(
"pdf/windows_binary_analysis_pdf.html")
'pdf/windows_binary_analysis_pdf.html')
else:
if api:
return {"scan_type": "Type is not Allowed"}
return {'scan_type': 'Type is not Allowed'}
else:
return HttpResponse(json.dumps({"type": "Type is not Allowed"}),
content_type="application/json; charset=utf-8", status=500)
return HttpResponse(
json.dumps({'type': 'Type is not Allowed'}),
content_type='application/json; charset=utf-8',
status=500)
context['VT_RESULT'] = None
if settings.VT_ENABLED:
app_dir = os.path.join(settings.UPLD_DIR, checksum + '/')
vt = VirusTotal.VirusTotal()
if "zip" in scan_type.lower():
if 'zip' in scan_type.lower():
context['VT_RESULT'] = None
else:
context['VT_RESULT'] = vt.get_result(
os.path.join(app_dir, checksum) +
'.' + scan_type.lower(),
checksum
)
os.path.join(app_dir, checksum)
+ '.' + scan_type.lower(),
checksum)
try:
if api and jsonres:
return {"report_dat": context}
return {'report_dat': context}
else:
options = {
'page-size': 'A4',
@@ -240,32 +248,38 @@ def pdf(request, api=False, jsonres=False):
'margin-right': '0.50in',
'margin-bottom': '0.50in',
'margin-left': '0.50in',
'encoding': "UTF-8",
'encoding': 'UTF-8',
'custom-header': [
('Accept-Encoding', 'gzip')
('Accept-Encoding', 'gzip'),
],
'no-outline': None
'no-outline': None,
}
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')
return {'pdf_dat': pdf_dat}
return HttpResponse(pdf_dat,
content_type='application/pdf')
except Exception as exp:
logger.error(exp)
logger.error('Error Generating PDF Report')
if api:
return {"error": "Cannot Generate PDF/JSON", "err_details": str(exp)}
return {
'error': 'Cannot Generate PDF/JSON',
'err_details': str(exp)}
else:
return HttpResponse(json.dumps({"pdf_error": "Cannot Generate PDF",
"err_details": str(exp)}),
content_type="application/json; charset=utf-8", status=500)
return HttpResponse(
json.dumps({'pdf_error': 'Cannot Generate PDF',
'err_details': str(exp)}),
content_type='application/json; charset=utf-8',
status=500)
else:
if api:
return {"error": "Invalid scan hash"}
return {'error': 'Invalid scan hash'}
else:
return HttpResponse(json.dumps({"md5": "Invalid MD5"}),
content_type="application/json; charset=utf-8", status=500)
return HttpResponse(
json.dumps({'md5': 'Invalid MD5'}),
content_type='application/json; charset=utf-8', status=500)
except Exception as exp:
msg = str(exp)
exp = exp.__doc__
@@ -276,56 +290,56 @@ def pdf(request, api=False, jsonres=False):
def get_list_match_items(ruleset):
"""Get List of Match item"""
"""Get List of Match item."""
match_list = []
i = 1
identifier = ruleset["type"]
if ruleset["match"] == 'string_and_or':
identifier = ruleset['type']
if ruleset['match'] == 'string_and_or':
identifier = 'string_or'
elif ruleset["match"] == 'string_or_and':
elif ruleset['match'] == 'string_or_and':
identifier = 'string_and'
while identifier + str(i) in ruleset:
match_list.append(ruleset[identifier + str(i)])
i = i + 1
if identifier + str(i) in ruleset == False:
if not (identifier + str(i)) in ruleset:
break
return match_list
def add_findings(findings, desc, file_path, rule):
"""Add Code Analysis Findings"""
"""Add Code Analysis Findings."""
if desc in findings:
tmp_list = findings[desc]["path"]
tmp_list = findings[desc]['path']
if escape(file_path) not in tmp_list:
tmp_list.append(escape(file_path))
findings[desc]["path"] = tmp_list
findings[desc]['path'] = tmp_list
else:
findings[desc] = {"path": [escape(file_path)],
"level": rule["level"],
"cvss": rule["cvss"],
"cwe": rule["cwe"]}
findings[desc] = {'path': [escape(file_path)],
'level': rule['level'],
'cvss': rule['cvss'],
'cwe': rule['cwe']}
def code_rule_matcher(findings, perms, data, file_path, code_rules):
"""Android Static Analysis Rule Matcher"""
"""Android Static Analysis Rule Matcher."""
try:
for rule in code_rules:
# CASE CHECK
if rule["input_case"] == "lower":
if rule['input_case'] == 'lower':
tmp_data = data.lower()
elif rule["input_case"] == "upper":
elif rule['input_case'] == 'upper':
tmp_data = data.upper()
elif rule["input_case"] == "exact":
elif rule['input_case'] == 'exact':
tmp_data = data
# MATCH TYPE
if rule["type"] == "regex":
if rule["match"] == 'single_regex':
if re.findall(rule["regex1"], tmp_data):
if rule['type'] == 'regex':
if rule['match'] == 'single_regex':
if re.findall(rule['regex1'], tmp_data):
add_findings(findings, rule[
"desc"], file_path, rule)
elif rule["match"] == 'regex_and':
'desc'], file_path, rule)
elif rule['match'] == 'regex_and':
and_match_rgx = True
match_list = get_list_match_items(rule)
for match in match_list:
@@ -334,27 +348,28 @@ def code_rule_matcher(findings, perms, data, file_path, code_rules):
break
if and_match_rgx:
add_findings(findings, rule[
"desc"], file_path, rule)
elif rule["match"] == 'regex_or':
'desc'], file_path, rule)
elif rule['match'] == 'regex_or':
match_list = get_list_match_items(rule)
for match in match_list:
if re.findall(match, tmp_data):
add_findings(findings, rule[
"desc"], file_path, rule)
'desc'], file_path, rule)
break
elif rule["match"] == 'regex_and_perm':
if (rule["perm"] in perms) and (re.findall(rule["regex1"], tmp_data)):
elif rule['match'] == 'regex_and_perm':
if (rule['perm'] in perms
and re.findall(rule['regex1'], tmp_data)):
add_findings(findings, rule[
"desc"], file_path, rule)
'desc'], file_path, rule)
else:
logger.error("Code Regex Rule Match Error\n" + rule)
logger.error('Code Regex Rule Match Error\n %s', rule)
elif rule["type"] == "string":
if rule["match"] == 'single_string':
if rule["string1"] in tmp_data:
elif rule['type'] == 'string':
if rule['match'] == 'single_string':
if rule['string1'] in tmp_data:
add_findings(findings, rule[
"desc"], file_path, rule)
elif rule["match"] == 'string_and':
'desc'], file_path, rule)
elif rule['match'] == 'string_and':
and_match_str = True
match_list = get_list_match_items(rule)
for match in match_list:
@@ -363,86 +378,87 @@ def code_rule_matcher(findings, perms, data, file_path, code_rules):
break
if and_match_str:
add_findings(findings, rule[
"desc"], file_path, rule)
elif rule["match"] == 'string_or':
'desc'], file_path, rule)
elif rule['match'] == 'string_or':
match_list = get_list_match_items(rule)
for match in match_list:
if match in tmp_data:
add_findings(findings, rule[
"desc"], file_path, rule)
'desc'], file_path, rule)
break
elif rule["match"] == 'string_and_or':
elif rule['match'] == 'string_and_or':
match_list = get_list_match_items(rule)
string_or_stat = False
for match in match_list:
if match in tmp_data:
string_or_stat = True
break
if string_or_stat and (rule["string1"] in tmp_data):
if string_or_stat and (rule['string1'] in tmp_data):
add_findings(findings, rule[
"desc"], file_path, rule)
elif rule["match"] == 'string_or_and':
'desc'], file_path, rule)
elif rule['match'] == 'string_or_and':
match_list = get_list_match_items(rule)
string_and_stat = True
for match in match_list:
if match in tmp_data is False:
string_and_stat = False
break
if string_and_stat or (rule["string1"] in tmp_data):
if string_and_stat or (rule['string1'] in tmp_data):
add_findings(findings, rule[
"desc"], file_path, rule)
elif rule["match"] == 'string_and_perm':
if (rule["perm"] in perms) and (rule["string1"] in tmp_data):
'desc'], file_path, rule)
elif rule['match'] == 'string_and_perm':
if (rule['perm'] in perms
and rule['string1'] in tmp_data):
add_findings(findings, rule[
"desc"], file_path, rule)
elif rule["match"] == 'string_or_and_perm':
'desc'], file_path, rule)
elif rule['match'] == 'string_or_and_perm':
match_list = get_list_match_items(rule)
string_or_ps = False
for match in match_list:
if match in tmp_data:
string_or_ps = True
break
if (rule["perm"] in perms) and string_or_ps:
if (rule['perm'] in perms) and string_or_ps:
add_findings(findings, rule[
"desc"], file_path, rule)
'desc'], file_path, rule)
else:
logger.error("Code String Rule Match Error\n%s", rule)
logger.error('Code String Rule Match Error\n%s', rule)
else:
logger.error("Code Rule Error\n%s", rule)
except:
PrintException("Error in Code Rule Processing")
logger.error('Code Rule Error\n%s', rule)
except Exception:
log_exception('Error in Code Rule Processing')
def add_apis(api_findings, desc, file_path):
"""Add API Findings"""
"""Add API Findings."""
if desc in api_findings:
tmp_list = api_findings[desc]["path"]
tmp_list = api_findings[desc]['path']
if escape(file_path) not in tmp_list:
tmp_list.append(escape(file_path))
api_findings[desc]["path"] = tmp_list
api_findings[desc]['path'] = tmp_list
else:
api_findings[desc] = {"path": [escape(file_path)]}
api_findings[desc] = {'path': [escape(file_path)]}
def api_rule_matcher(api_findings, perms, data, file_path, api_rules):
"""Android API Analysis Rule Matcher"""
"""Android API Analysis Rule Matcher."""
try:
for api in api_rules:
# CASE CHECK
if api["input_case"] == "lower":
if api['input_case'] == 'lower':
tmp_data = data.lower()
elif api["input_case"] == "upper":
elif api['input_case'] == 'upper':
tmp_data = data.upper()
elif api["input_case"] == "exact":
elif api['input_case'] == 'exact':
tmp_data = data
# MATCH TYPE
if api["type"] == "regex":
if api["match"] == 'single_regex':
if re.findall(api["regex1"], tmp_data):
add_apis(api_findings, api["desc"], file_path)
elif api["match"] == 'regex_and':
if api['type'] == 'regex':
if api['match'] == 'single_regex':
if re.findall(api['regex1'], tmp_data):
add_apis(api_findings, api['desc'], file_path)
elif api['match'] == 'regex_and':
and_match_rgx = True
match_list = get_list_match_items(api)
for match in match_list:
@@ -450,24 +466,25 @@ def api_rule_matcher(api_findings, perms, data, file_path, api_rules):
and_match_rgx = False
break
if and_match_rgx:
add_apis(api_findings, api["desc"], file_path)
elif api["match"] == 'regex_or':
add_apis(api_findings, api['desc'], file_path)
elif api['match'] == 'regex_or':
match_list = get_list_match_items(api)
for match in match_list:
if re.findall(match, tmp_data):
add_apis(api_findings, api["desc"], file_path)
add_apis(api_findings, api['desc'], file_path)
break
elif api["match"] == 'regex_and_perm':
if (api["perm"] in perms) and (re.findall(api["regex1"], tmp_data)):
add_apis(api_findings, api["desc"], file_path)
elif api['match'] == 'regex_and_perm':
if (api['perm'] in perms
and re.findall(api['regex1'], tmp_data)):
add_apis(api_findings, api['desc'], file_path)
else:
logger.error("API Regex Rule Match Error\n" + api)
logger.error('API Regex Rule Match Error\n %s', api)
elif api["type"] == "string":
if api["match"] == 'single_string':
if api["string1"] in tmp_data:
add_apis(api_findings, api["desc"], file_path)
elif api["match"] == 'string_and':
elif api['type'] == 'string':
if api['match'] == 'single_string':
if api['string1'] in tmp_data:
add_apis(api_findings, api['desc'], file_path)
elif api['match'] == 'string_and':
and_match_str = True
match_list = get_list_match_items(api)
for match in match_list:
@@ -475,53 +492,53 @@ def api_rule_matcher(api_findings, perms, data, file_path, api_rules):
and_match_str = False
break
if and_match_str:
add_apis(api_findings, api["desc"], file_path)
elif api["match"] == 'string_or':
add_apis(api_findings, api['desc'], file_path)
elif api['match'] == 'string_or':
match_list = get_list_match_items(api)
for match in match_list:
if match in tmp_data:
add_apis(api_findings, api["desc"], file_path)
add_apis(api_findings, api['desc'], file_path)
break
elif api["match"] == 'string_and_or':
elif api['match'] == 'string_and_or':
match_list = get_list_match_items(api)
string_or_stat = False
for match in match_list:
if match in tmp_data:
string_or_stat = True
break
if string_or_stat and (api["string1"] in tmp_data):
add_apis(api_findings, api["desc"], file_path)
elif api["match"] == 'string_or_and':
if string_or_stat and (api['string1'] in tmp_data):
add_apis(api_findings, api['desc'], file_path)
elif api['match'] == 'string_or_and':
match_list = get_list_match_items(api)
string_and_stat = True
for match in match_list:
if match in tmp_data is False:
string_and_stat = False
break
if string_and_stat or (api["string1"] in tmp_data):
add_apis(api_findings, api["desc"], file_path)
elif api["match"] == 'string_and_perm':
if (api["perm"] in perms) and (api["string1"] in tmp_data):
add_apis(api_findings, api["desc"], file_path)
elif api["match"] == 'string_or_and_perm':
if string_and_stat or (api['string1'] in tmp_data):
add_apis(api_findings, api['desc'], file_path)
elif api['match'] == 'string_and_perm':
if (api['perm'] in perms) and (api['string1'] in tmp_data):
add_apis(api_findings, api['desc'], file_path)
elif api['match'] == 'string_or_and_perm':
match_list = get_list_match_items(api)
string_or_ps = False
for match in match_list:
if match in tmp_data:
string_or_ps = True
break
if (api["perm"] in perms) and string_or_ps:
add_apis(api_findings, api["desc"], file_path)
if (api['perm'] in perms) and string_or_ps:
add_apis(api_findings, api['desc'], file_path)
else:
logger.error("API String Rule Match Error\n%s", api)
logger.error('API String Rule Match Error\n%s', api)
else:
logger.error("API Rule Error\n%s", api)
except:
PrintException("Error in API Rule Processing")
logger.error('API Rule Error\n%s', api)
except Exception:
log_exception('Error in API Rule Processing')
def url_n_email_extract(dat, relative_path):
"""Extract URLs and Emails from Source Code"""
"""Extract URLs and Emails from Source Code."""
urls = []
emails = []
urllist = []
@@ -530,11 +547,11 @@ def url_n_email_extract(dat, relative_path):
# URLs Extraction My Custom regex
pattern = re.compile(
(
r'((?:https?://|s?ftps?://|file://|javascript:|data:|www\d{0,3}[.])'
r'((?:https?://|s?ftps?://|'
r'file://|javascript:|data:|www\d{0,3}[.])'
r'[\w().=/;,#:@?&~*+!$%\'{}-]+)'
),
re.UNICODE
)
re.UNICODE)
urllist = re.findall(pattern, dat.lower())
uflag = 0
for url in urllist:
@@ -543,7 +560,7 @@ def url_n_email_extract(dat, relative_path):
uflag = 1
if uflag == 1:
url_n_file.append(
{"urls": urls, "path": escape(relative_path)})
{'urls': urls, 'path': escape(relative_path)})
# Email Extraction Regex
regex = re.compile(r'[\w.-]+@[\w-]+\.[\w.]+')
@@ -554,18 +571,18 @@ def url_n_email_extract(dat, relative_path):
eflag = 1
if eflag == 1:
email_n_file.append(
{"emails": emails, "path": escape(relative_path)})
{'emails': emails, 'path': escape(relative_path)})
return urllist, url_n_file, email_n_file
# This is just the first sanity check that triggers generic_compare
def compare_apps(request, first_hash: str, second_hash: str):
if first_hash == second_hash:
error_msg = "Results with same hash cannot be compared"
def compare_apps(request, hash1: str, hash2: str):
if hash1 == hash1:
error_msg = 'Results with same hash cannot be compared'
return print_n_send_error_response(request, error_msg, False)
logger.info(
"Starting App compare for - {} and {}".format(first_hash, second_hash))
return generic_compare(request, first_hash, second_hash)
'Starting App compare for %s and %s', hash1, hash2)
return generic_compare(request, hash1, hash2)
def score(findings):
@@ -575,22 +592,23 @@ def score(findings):
app_score = 100
if isinstance(findings, list):
for finding in findings:
if "cvss" in finding:
if finding["cvss"] != 0:
cvss_scores.append(finding["cvss"])
if 'cvss' in finding:
if finding['cvss'] != 0:
cvss_scores.append(finding['cvss'])
else:
for _, finding in findings.items():
if "cvss" in finding:
if finding["cvss"] != 0:
cvss_scores.append(finding["cvss"])
if 'cvss' in finding:
if finding['cvss'] != 0:
cvss_scores.append(finding['cvss'])
if cvss_scores:
avg_cvss = round(sum(cvss_scores) / len(cvss_scores), 1)
app_score = int((10 - avg_cvss) * 10)
return avg_cvss, app_score
def update_scan_timestamp(scan_hash, tms=timezone.now()):
def update_scan_timestamp(scan_hash):
# Update the last scan time.
tms = timezone.now()
RecentScansDB.objects.filter(MD5=scan_hash).update(TS=tms)
@@ -598,27 +616,29 @@ def open_firebase(url):
# Detect Open Firebase Database
try:
purl = urlparse(url)
base_url = "{}://{}/.json".format(purl.scheme, purl.netloc)
base_url = '{}://{}/.json'.format(purl.scheme, purl.netloc)
proxies, verify = upstream_proxy('https')
headers = {
'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_10_1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/39.0.2171.95 Safari/537.36'}
'User-Agent': ('Mozilla/5.0 (Macintosh; Intel Mac OS X 10_10_1)'
' AppleWebKit/537.36 (KHTML, like Gecko) '
'Chrome/39.0.2171.95 Safari/537.36')}
resp = requests.get(base_url, headers=headers,
proxies=proxies, verify=verify)
if resp.status_code == 200:
return base_url, True
except Exception as exp:
logger.warning('Open Firebase DB detection failed. %s', exp)
except Exception:
logger.warning('Open Firebase DB detection failed.')
return url, False
def firebase_analysis(urls):
# Detect Firebase URL
firebase_db = []
logger.info("Detecting Firebase URL(s)")
logger.info('Detecting Firebase URL(s)')
for url in urls:
if 'firebaseio.com' in url:
returl, is_open = open_firebase(url)
fbdic = {"url": returl, "open": is_open}
fbdic = {'url': returl, 'open': is_open}
if fbdic not in firebase_db:
firebase_db.append(fbdic)
return firebase_db
+173 -159
View File
@@ -1,45 +1,33 @@
# -*- coding: utf_8 -*-
"""Windows Analysis Module."""
import re
import os
import io
from os.path import expanduser
import platform
import logging
# Binskim/Binscope analysis
import xmlrpc.client
import json
import base64
import rsa
# Local analysis
import configparser
import json
import logging
import os
import platform
import re
import subprocess
# Binskim/Binscope analysis
import xmlrpc.client
from os.path import expanduser
import rsa
# XML-Manifest
from lxml import etree
from django.shortcuts import render
from django.http import HttpResponseRedirect
from django.conf import settings
from django.shortcuts import render
from django.utils.html import escape
from StaticAnalyzer.views.shared_func import (
file_size,
hash_gen,
unzip,
update_scan_timestamp,
)
from MobSF.utils import log_exception, print_n_send_error_response, python_list
from StaticAnalyzer.models import StaticAnalyzerWindows
from StaticAnalyzer.tools.strings import strings_util
from StaticAnalyzer.views.shared_func import (file_size, hash_gen, unzip,
update_scan_timestamp)
from MobSF.utils import (
print_n_send_error_response,
python_list,
PrintException,
)
logger = logging.getLogger(__name__)
# Only used when xmlrpc is used
@@ -57,7 +45,7 @@ def staticanalyzer_windows(request, api=False):
"""Analyse a windows app."""
try:
# Input validation
logger.info("Windows Static Analysis Started")
logger.info('Windows Static Analysis Started')
app_dic = {} # Dict to store the binary attributes
if api:
typ = request.POST['scan_type']
@@ -79,11 +67,13 @@ def staticanalyzer_windows(request, api=False):
settings.BASE_DIR, 'StaticAnalyzer/tools/windows/')
if typ == 'appx':
# DB
db_entry = StaticAnalyzerWindows.objects.filter( # pylint: disable-msg=E1101
MD5=app_dic['md5']
db_entry = StaticAnalyzerWindows.objects.filter(
MD5=app_dic['md5'],
)
if db_entry.exists() and rescan == '0':
logger.info("Analysis is already Done. Fetching data from the DB...")
logger.info(
'Analysis is already Done.'
' Fetching data from the DB...')
context = {
'title': db_entry[0].TITLE,
'name': db_entry[0].APP_NAME,
@@ -93,42 +83,47 @@ def staticanalyzer_windows(request, api=False):
'sha1': db_entry[0].SHA1,
'sha256': db_entry[0].SHA256,
'bin_name': db_entry[0].BINNAME,
'version': db_entry[0].VERSION,
'arch': db_entry[0].ARCH,
'compiler_version': db_entry[0].COMPILER_VERSION,
'visual_studio_version': db_entry[0].VISUAL_STUDIO_VERSION,
'visual_studio_edition': db_entry[0].VISUAL_STUDIO_EDITION,
'target_os': db_entry[0].TARGET_OS,
'appx_dll_version': db_entry[0].APPX_DLL_VERSION,
'proj_guid': db_entry[0].PROJ_GUID,
'opti_tool': db_entry[0].OPTI_TOOL,
'target_run': db_entry[0].TARGET_RUN,
'version': db_entry[0].VERSION,
'arch': db_entry[0].ARCH,
'compiler_version': db_entry[0].COMPILER_VERSION,
'visual_studio_version':
db_entry[0].VISUAL_STUDIO_VERSION,
'visual_studio_edition':
db_entry[0].VISUAL_STUDIO_EDITION,
'target_os': db_entry[0].TARGET_OS,
'appx_dll_version': db_entry[0].APPX_DLL_VERSION,
'proj_guid': db_entry[0].PROJ_GUID,
'opti_tool': db_entry[0].OPTI_TOOL,
'target_run': db_entry[0].TARGET_RUN,
'files': python_list(db_entry[0].FILES),
'strings': python_list(db_entry[0].STRINGS),
'bin_an_results': python_list(db_entry[0].BIN_AN_RESULTS),
'bin_an_warnings': python_list(db_entry[0].BIN_AN_WARNINGS)
'bin_an_results':
python_list(db_entry[0].BIN_AN_RESULTS),
'bin_an_warnings':
python_list(db_entry[0].BIN_AN_WARNINGS),
}
else:
logger.info("Windows Binary Analysis Started")
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'
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")
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")
logger.info('Connecting to DB')
if rescan == '1':
logger.info("Updating Database...")
StaticAnalyzerWindows.objects.filter( # pylint: disable-msg=E1101
MD5=app_dic['md5']
logger.info('Updating Database...')
StaticAnalyzerWindows.objects.filter(
MD5=app_dic['md5'],
).update(
TITLE='Static Analysis',
APP_NAME=app_dic['app_name'],
@@ -157,7 +152,7 @@ def staticanalyzer_windows(request, api=False):
)
update_scan_timestamp(app_dic['md5'])
elif rescan == '0':
logger.info("Saving to Database")
logger.info('Saving to Database')
db_item = StaticAnalyzerWindows(
TITLE='Static Analysis',
APP_NAME=app_dic['app_name'],
@@ -197,8 +192,10 @@ def staticanalyzer_windows(request, api=False):
'version': xml_dic['version'],
'arch': xml_dic['arch'],
'compiler_version': xml_dic['compiler_version'],
'visual_studio_version': xml_dic['visual_studio_version'],
'visual_studio_edition': xml_dic['visual_studio_edition'],
'visual_studio_version':
xml_dic['visual_studio_version'],
'visual_studio_edition':
xml_dic['visual_studio_edition'],
'target_os': xml_dic['target_os'],
'appx_dll_version': xml_dic['appx_dll_version'],
'proj_guid': xml_dic['proj_guid'],
@@ -209,19 +206,19 @@ def staticanalyzer_windows(request, api=False):
'bin_an_results': bin_an_dic['results'],
'bin_an_warnings': bin_an_dic['warnings'],
}
template = "static_analysis/windows_binary_analysis.html"
template = 'static_analysis/windows_binary_analysis.html'
if api:
return context
else:
return render(request, template, context)
else:
msg = "File type not supported"
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"
msg = 'Hash match failed or Invalid file extension'
if api:
return print_n_send_error_response(request, msg, True)
else:
@@ -234,6 +231,7 @@ def staticanalyzer_windows(request, api=False):
else:
return print_n_send_error_response(request, msg, False, exp_doc)
def _get_token():
"""Get the authentication token for windows vm xmlrpc client."""
challenge = proxy.get_challenge()
@@ -246,7 +244,7 @@ def _get_token():
def _binary_analysis(app_dic):
"""Start binary analsis."""
logger.info("Starting Binary Analysis")
logger.info('Starting Binary Analysis')
bin_an_dic = {}
# Init optional sections to prevent None-Pointer-Errors
@@ -255,78 +253,88 @@ def _binary_analysis(app_dic):
# Search for exe
for file_name in app_dic['files']:
if file_name.endswith(".exe"):
if file_name.endswith('.exe'):
bin_an_dic['bin'] = file_name
bin_an_dic['bin_name'] = file_name.replace(".exe", "")
bin_an_dic['bin_name'] = file_name.replace('.exe', '')
break
if not bin_an_dic['bin_name']:
PrintException("No executeable in appx.")
log_exception('No executeable in appx.')
bin_path = os.path.join(app_dic['app_dir'], bin_an_dic['bin'])
# Execute strings command
bin_an_dic['strings'] = ""
str_list = list(set(strings_util(bin_path))) # Make unique # pylint: disable-msg=R0204
bin_an_dic['strings'] = ''
# Make unique # pylint: disable-msg=R0204
str_list = list(set(strings_util(bin_path)))
str_list = [escape(s) for s in str_list]
bin_an_dic['strings'] = str_list
# Search for unsave function
pattern = re.compile("(alloca|gets|memcpy|printf|scanf|sprintf|sscanf|strcat|StrCat|strcpy|StrCpy|strlen|StrLen|strncat|StrNCat|strncpy|StrNCpy|strtok|swprintf|vsnprintf|vsprintf|vswprintf|wcscat|wcscpy|wcslen|wcsncat|wcsncpy|wcstok|wmemcpy)")
pattern = re.compile('(alloca|gets|memcpy|printf|scanf|sprintf|sscanf|'
'strcat|StrCat|strcpy|StrCpy|strlen|StrLen|strncat|'
'StrNCat|strncpy|StrNCpy|strtok|swprintf|vsnprintf|'
'vsprintf|vswprintf|wcscat|wcscpy|wcslen|wcsncat|'
'wcsncpy|wcstok|wmemcpy)')
for elem in str_list:
if pattern.match(elem[5:-5]):
result = {
"rule_id": 'Possible Insecure Function',
"status": 'Insecure',
"desc": "Possible Insecure Function detected: {}".format(elem[5:-5])
}
'rule_id': 'Possible Insecure Function',
'status': 'Insecure',
'desc': ('Possible Insecure '
'Function detected: {}').format(elem[5:-5])}
bin_an_dic['results'].append(result)
# Execute binskim analysis if vm is available
if platform.system() != 'Windows':
if settings.WINDOWS_VM_IP:
logger.info("Windows VM configured.")
logger.info('Windows VM configured.')
global proxy
proxy = xmlrpc.client.ServerProxy( # pylint: disable-msg=C0103
"http://{}:{}".format(
'http://{}:{}'.format(
settings.WINDOWS_VM_IP,
settings.WINDOWS_VM_PORT
)
)
settings.WINDOWS_VM_PORT))
name = _upload_sample(bin_path)
bin_an_dic = __binskim(name, bin_an_dic)
bin_an_dic = __binscope(name, bin_an_dic)
bin_an_dic = binskim(name, bin_an_dic)
bin_an_dic = binscope(name, bin_an_dic)
else:
logger.warning("Windows VM not configured in settings.py. Skipping Binskim and Binscope.")
logger.warning(
'Windows VM not configured in settings.py.'
' Skipping Binskim and Binscope.')
warning = {
"rule_id": "VM",
"status": "Info",
"info": "",
"desc": "VM is not configured. Please read the readme.md in MobSF/install/windows."
'rule_id': 'VM',
'status': 'Info',
'info': '',
'desc': 'VM is not configured. Please read the readme.md'
' in MobSF/install/windows.',
}
bin_an_dic['results'].append(warning)
else:
logger.info("Running lokal analysis.")
logger.info('Running lokal analysis.')
global config
config = configparser.ConfigParser()
# Switch to settings definded path if available
config.read(expanduser("~") + "\\MobSF\\Config\\config.txt")
config.read(expanduser('~') + '\\MobSF\\Config\\config.txt')
# Run analysis functions
bin_an_dic = __binskim(bin_path, bin_an_dic,
run_local=True, app_dir=app_dic['app_dir'])
bin_an_dic = __binscope(bin_path, bin_an_dic,
run_local=True, app_dir=app_dic['app_dir'])
bin_an_dic = binskim(bin_path,
bin_an_dic,
run_local=True,
app_dir=app_dic['app_dir'])
bin_an_dic = binscope(bin_path,
bin_an_dic,
run_local=True,
app_dir=app_dic['app_dir'])
return bin_an_dic
def _upload_sample(bin_path):
"""Upload sample to windows vm."""
logger.info("Uploading sample.")
logger.info('Uploading sample.')
# Upload test
with open(bin_path,"rb") as handle:
with open(bin_path, 'rb') as handle:
binary_data = xmlrpc.client.Binary(handle.read())
# Name of the sample is return by the remote machine
name = proxy.upload_file(binary_data, _get_token())
@@ -334,9 +342,9 @@ def _upload_sample(bin_path):
return name
def __binskim(name, bin_an_dic, run_local=False, app_dir=None):
def binskim(name, bin_an_dic, run_local=False, app_dir=None):
"""Run the binskim analysis."""
logger.info("Running binskim.")
logger.info('Running binskim.')
if run_local:
bin_path = os.path.join(app_dir, bin_an_dic['bin'])
@@ -346,13 +354,13 @@ def __binskim(name, bin_an_dic, run_local=False, app_dir=None):
else:
binskim_path = config['binskim']['file_x86']
command = "analyze"
command = 'analyze'
path = bin_path
output_p = "-o"
output_d = bin_path + "_binskim"
verbose = "-v"
policy_p = "--config"
policy_d = "default" # TODO(Other policies?)
output_p = '-o'
output_d = bin_path + '_binskim'
verbose = '-v'
policy_p = '--config'
policy_d = 'default' # TODO(Other policies?)
# Assemble
params = [
@@ -361,7 +369,7 @@ def __binskim(name, bin_an_dic, run_local=False, app_dir=None):
path,
output_p, output_d,
verbose,
policy_p, policy_d
policy_p, policy_d,
]
# Execute process
@@ -378,50 +386,51 @@ def __binskim(name, bin_an_dic, run_local=False, app_dir=None):
# Load output as json
output = json.loads(response)
bin_an_dic = __parse_binskim(bin_an_dic, output)
bin_an_dic = parse_binskim(bin_an_dic, output)
return bin_an_dic
def __parse_binskim(bin_an_dic, output):
"""Parse output to results and warnings"""
def parse_binskim(bin_an_dic, output):
"""Parse output to results and warnings."""
current_run = output['runs'][0]
if 'results' in current_run:
rules = output['runs'][0]['rules']
for res in current_run['results']:
if res['level'] != "pass":
if res['level'] != 'pass':
result = {
"rule_id": res['ruleId'],
"status": "Insecure",
"desc": rules[res['ruleId']]['shortDescription']
'rule_id': res['ruleId'],
'status': 'Insecure',
'desc': rules[res['ruleId']]['shortDescription'],
}
if len(res['formattedRuleMessage']["arguments"])>2:
result["info"] = res['formattedRuleMessage']["arguments"][2]
if len(res['formattedRuleMessage']['arguments']) > 2:
result['info'] = res[
'formattedRuleMessage']['arguments'][2]
else:
result["info"] = ""
result['info'] = ''
else:
result = {
"rule_id": res['ruleId'],
"status": "Secure",
"desc": rules[res['ruleId']]['shortDescription']
'rule_id': res['ruleId'],
'status': 'Secure',
'desc': rules[res['ruleId']]['shortDescription'],
}
bin_an_dic['results'].append(result)
else:
logger.warning("binskim has no results.")
logger.warning('binskim has no results.')
# Create an warining for the gui
warning = {
"rule_id": "No Binskim-Results",
"status": "Info",
"desc": "No results from Binskim."
'rule_id': 'No Binskim-Results',
'status': 'Info',
'desc': 'No results from Binskim.',
}
bin_an_dic['warnings'].append(warning)
if 'configurationNotifications' in current_run:
for warn in current_run['configurationNotifications']:
warning = {
"rule_id": warn['ruleId'],
"status": "Info",
"desc": warn['message']
'rule_id': warn['ruleId'],
'status': 'Info',
'desc': warn['message'],
}
bin_an_dic['warnings'].append(warning)
@@ -429,9 +438,11 @@ def __parse_binskim(bin_an_dic, output):
return bin_an_dic
def __binscope(name, bin_an_dic, run_local=False, app_dir=None):
def binscope(name, bin_an_dic, run_local=False, app_dir=None):
"""Run the binskim analysis."""
logger.info("Running binscope. This might take a while, depending on the binary size.")
logger.info(
'Running binscope. This might take a'
' while, depending on the binary size.')
if run_local:
global config
bin_path = os.path.join(app_dir, bin_an_dic['bin'])
@@ -439,8 +450,8 @@ def __binscope(name, bin_an_dic, run_local=False, app_dir=None):
# Set params for execution of binskim
binscope_path = [config['binscope']['file']]
target = [bin_path]
out_type = ["/Red", "/v"]
output = ["/l", target[0] + "_binscope"]
out_type = ['/Red', '/v']
output = ['/l', target[0] + '_binscope']
checks = [
'/Checks', 'ATLVersionCheck',
'/Checks', 'ATLVulnCheck',
@@ -464,11 +475,11 @@ def __binscope(name, bin_an_dic, run_local=False, app_dir=None):
# Assemble
params = (
binscope_path +
target +
out_type +
output +
checks
binscope_path
+ target
+ out_type
+ output
+ checks
)
# Execute process
@@ -485,37 +496,38 @@ def __binscope(name, bin_an_dic, run_local=False, app_dir=None):
res = response[response.find('<'):]
config = etree.XMLParser( # pylint: disable-msg=E1101
remove_blank_text=True,
resolve_entities=False
resolve_entities=False,
)
xml_file = etree.XML(bytes(res, "utf-8", "ignore"), config) # pylint: disable-msg=E1101
xml_file = etree.XML(bytes(res, 'utf-8', 'ignore'),
config) # pylint: disable-msg=E1101
for item in xml_file.find('items').getchildren():
if item.find('issueType') is not None:
res = item.find('result').text
if res == 'PASS':
status = "Secure"
status = 'Secure'
try:
desc = item.find('Information').text
except AttributeError:
desc = "No description provided by analysing tool."
desc = 'No description provided by analysing tool.'
elif res == 'FAIL':
status = "Insecure"
status = 'Insecure'
if item.find('Failure1') is not None:
desc = item.find('Failure1').text
elif item.find('Information') is not None:
desc = item.find('Information').text
elif item.find('diagnostic') is not None:
status = "Info"
status = 'Info'
desc = item.find('diagnostic').text
else:
desc = "No description provided by analysing tool."
desc = 'No description provided by analysing tool.'
result = {
"rule_id": item.find('issueType').text,
"status": status,
"desc": desc
'rule_id': item.find('issueType').text,
'status': status,
'desc': desc,
}
bin_an_dic['results'].append(result)
@@ -524,8 +536,8 @@ def __binscope(name, bin_an_dic, run_local=False, app_dir=None):
def _parse_xml(app_dir):
"""Parse the AppxManifest file to get basic informations."""
logger.info("Starting Binary Analysis - XML")
xml_file = os.path.join(app_dir, "AppxManifest.xml")
logger.info('Starting Binary Analysis - XML')
xml_file = os.path.join(app_dir, 'AppxManifest.xml')
xml_dic = {
'version': '',
'arch': '',
@@ -538,53 +550,55 @@ def _parse_xml(app_dir):
'appx_dll_version': '',
'proj_guid': '',
'opti_tool': '',
'target_run': ''
'target_run': '',
}
try:
logger.info("Reading AppxManifest")
logger.info('Reading AppxManifest')
config = etree.XMLParser( # pylint: disable-msg=E1101
remove_blank_text=True,
resolve_entities=False
resolve_entities=False,
)
xml = etree.XML(open(xml_file, "rb").read(),
xml = etree.XML(open(xml_file, 'rb').read(),
config) # pylint: disable-msg=E1101
for child in xml.getchildren():
# } to prevent conflict with PhoneIdentity..
if isinstance(child.tag, str) and child.tag.endswith("}Identity"):
xml_dic['version'] = child.get("Version")
xml_dic['arch'] = child.get("ProcessorArchitecture")
elif isinstance(child.tag, str) and child.tag.endswith("Properties"):
if isinstance(child.tag, str) and child.tag.endswith('}Identity'):
xml_dic['version'] = child.get('Version')
xml_dic['arch'] = child.get('ProcessorArchitecture')
elif (isinstance(child.tag, str)
and child.tag.endswith('Properties')):
for sub_child in child.getchildren():
if sub_child.tag.endswith("}DisplayName"):
if sub_child.tag.endswith('}DisplayName'):
# TODO(Needed? Compare to existing app_name)
xml_dic['app_name'] = sub_child.text
elif sub_child.tag.endswith("}PublisherDisplayName"):
elif sub_child.tag.endswith('}PublisherDisplayName'):
xml_dic['pub_name'] = sub_child.text
elif isinstance(child.tag, str) and child.tag.endswith("}Metadata"):
xml_dic = __parse_xml_metadata(xml_dic, child)
except:
PrintException("Reading from AppxManifest.xml")
elif (isinstance(child.tag, str)
and child.tag.endswith('}Metadata')):
xml_dic = parse_xml_metadata(xml_dic, child)
except Exception:
log_exception('Reading from AppxManifest.xml')
return xml_dic
def __parse_xml_metadata(xml_dic, xml_node):
def parse_xml_metadata(xml_dic, xml_node):
"""Return the XML Metadata."""
for child in xml_node.getchildren():
if child.get('Name') == "cl.exe":
if child.get('Name') == 'cl.exe':
xml_dic['compiler_version'] = child.get('Version')
elif child.get('Name') == "VisualStudio":
elif child.get('Name') == 'VisualStudio':
xml_dic['visual_studio_version'] = child.get('Version')
elif child.get('Name') == "VisualStudioEdition":
elif child.get('Name') == 'VisualStudioEdition':
xml_dic['visual_studio_edition'] = child.get('Value')
elif child.get('Name') == "OperatingSystem":
elif child.get('Name') == 'OperatingSystem':
xml_dic['target_os'] = child.get('Version')
elif child.get('Name') == "Microsoft.Build.AppxPackage.dll":
elif child.get('Name') == 'Microsoft.Build.AppxPackage.dll':
xml_dic['appx_dll_version'] = child.get('Version')
elif child.get('Name') == "ProjectGUID":
elif child.get('Name') == 'ProjectGUID':
xml_dic['proj_guid'] = child.get('Value')
elif child.get('Name') == "OptimizingToolset":
elif child.get('Name') == 'OptimizingToolset':
xml_dic['opti_tool'] = child.get('Value')
elif child.get('Name') == "TargetRuntime":
elif child.get('Name') == 'TargetRuntime':
xml_dic['target_run'] = child.get('Value')
return xml_dic
+54 -56
View File
@@ -1,16 +1,15 @@
"""MobSF rpc_client for static windows app analysis."""
# pylint: disable=C0325,W0603,C0103
import os
from os.path import expanduser
import re
import subprocess
import configparser # pylint: disable-msg=E0401
import hashlib
import random
import string
import base64
from xmlrpc.server import SimpleXMLRPCServer # pylint: disable-msg=E0401
import configparser # pylint: disable-msg=E0401
import hashlib
import os
import random
# pylint: disable=C0325,W0603,C0103
import re
import string
import subprocess
from os.path import expanduser
from xmlrpc.server import SimpleXMLRPCServer # pylint: disable-msg=E0401
import rsa
@@ -22,49 +21,53 @@ pub_key = None
def _init_key():
global pub_key
pub_key = rsa.PublicKey.load_pkcs1(
open(config['MobSF']['pub_key']).read()
)
open(config['MobSF']['pub_key']).read())
def _check_challenge(signature):
signature = base64.b64decode(signature.data)
try:
rsa.verify(challenge.encode('utf-8'), signature, pub_key)
print("[*] Challenge successfully verified.")
print('[*] Challenge successfully verified.')
_revoke_challenge()
except rsa.pkcs1.VerificationError:
print("[!] Received wrong signature for challenge.")
raise Exception("Access Denied.")
print('[!] Received wrong signature for challenge.')
raise Exception('Access Denied.')
except (TypeError, AttributeError):
print("[!] Challenge already unset.")
raise Exception("Access Denied.")
print('[!] Challenge already unset.')
raise Exception('Access Denied.')
def _revoke_challenge():
"""Revoke the challenge (to prevent replay attacks)"""
"""Revoke the challenge (to prevent replay attacks)."""
global challenge
challenge = None
def get_challenge():
"""Return an ascii challenge to validate authentication in _check_challenge."""
"""Get challenge.
Return an ascii challenge to validate
authentication in _check_challenge.
"""
global challenge
# Not using os.urandom for Python 2/3 transfer errors
challenge = ''.join(
random.SystemRandom().choice(string.ascii_uppercase + string.digits) for _ in range(256)
random.SystemRandom().choice(
string.ascii_uppercase + string.digits) for _ in range(256)
)
return "{}".format(challenge)
return '{}'.format(challenge)
def test_challenge(signature):
"""Test function to check if rsa is working."""
_check_challenge(signature)
print("Check complete")
return "OK!"
print('Check complete')
return 'OK!'
def upload_file(sample_file, signature):
"""Upload a file."""
# Check challenge
_check_challenge(signature)
@@ -76,10 +79,7 @@ def upload_file(sample_file, signature):
with open(
os.path.join(
config['MobSF']['samples'],
md5.hexdigest()
),
"wb"
) as handle:
md5.hexdigest()), 'wb') as handle:
handle.write(sample_file.data)
# Return md5 as reference to the sample
@@ -87,24 +87,23 @@ def upload_file(sample_file, signature):
def binskim(sample, signature):
"""Perform an static analysis on the sample and return the json"""
"""Perform an static analysis on the sample and return the json."""
# Check challenge
_check_challenge(signature)
# Check if param is a md5 to prevent attacks (we only use lower-case)
if len(re.findall(r"([a-f\d]{32})", sample)) == 0:
return "Wrong Input!"
if len(re.findall(r'([a-f\d]{32})', sample)) == 0:
return 'Wrong Input!'
# Set params for execution of binskim
binskim_path = config['binskim']['file_x64']
command = "analyze"
command = 'analyze'
path = config['MobSF']['samples'] + sample
output_p = "-o"
output_d = config['MobSF']['samples'] + sample + "_binskim"
verbose = "-v"
policy_p = "--config"
policy_d = "default" # TODO(Other policies?)
output_p = '-o'
output_d = config['MobSF']['samples'] + sample + '_binskim'
verbose = '-v'
policy_p = '--config'
policy_d = 'default' # TODO(Other policies?)
# Assemble
params = [
@@ -113,7 +112,7 @@ def binskim(sample, signature):
path,
output_p, output_d,
verbose,
policy_p, policy_d
policy_p, policy_d,
]
# Execute process
@@ -127,7 +126,6 @@ def binskim(sample, signature):
def binscope(sample, signature):
"""Run binscope against an sample file."""
# Check challenge
_check_challenge(signature)
@@ -135,8 +133,8 @@ def binscope(sample, signature):
binscope_path = [config['binscope']['file']]
target = [config['MobSF']['samples'] + sample]
out_type = ["/Red", "/v"]
output = ["/l", target[0] + "_binscope"]
out_type = ['/Red', '/v']
output = ['/l', target[0] + '_binscope']
checks = [
'/Checks', 'ATLVersionCheck',
'/Checks', 'ATLVulnCheck',
@@ -159,11 +157,11 @@ def binscope(sample, signature):
]
# Assemble
params = (
binscope_path +
target +
out_type +
output +
checks
binscope_path
+ target
+ out_type
+ output
+ checks
)
# Execute process
@@ -179,15 +177,15 @@ if __name__ == '__main__':
# Init configparser
config = configparser.ConfigParser()
config.read(expanduser("~") + "\\MobSF\\Config\\config.txt")
config.read(expanduser('~') + '\\MobSF\\Config\\config.txt')
_init_key()
server = SimpleXMLRPCServer(("0.0.0.0", 8000))
print("Listening on port 8000...")
server.register_function(get_challenge, "get_challenge")
server.register_function(test_challenge, "test_challenge")
server.register_function(upload_file, "upload_file")
server.register_function(binskim, "binskim")
server.register_function(binscope, "binscope")
server = SimpleXMLRPCServer(('0.0.0.0', 8000))
print('Listening on port 8000...')
server.register_function(get_challenge, 'get_challenge')
server.register_function(test_challenge, 'test_challenge')
server.register_function(upload_file, 'upload_file')
server.register_function(binskim, 'binskim')
server.register_function(binscope, 'binscope')
server.serve_forever()
+80 -95
View File
@@ -1,12 +1,18 @@
"""Setup script for the Windows vm for usage with MobSF for static analysis of Windows apps."""
"""Static Analysis Setup Windows.
Setup script for the Windows vm for usage with MobSF for
static analysis of Windows apps.
"""
import configparser
import logging
import os
from os.path import expanduser
import platform
import re
import shutil
import subprocess
import sys
import logging
from os.path import expanduser
from six.moves import input
try:
@@ -14,7 +20,6 @@ try:
except ImportError:
import urllib as urlrequest
import configparser
logger = logging.getLogger(__name__)
@@ -22,38 +27,38 @@ logger = logging.getLogger(__name__)
# Only static URL, let's hope this never changes..
CONFIG_URL = (
"https://raw.githubusercontent.com/MobSF/"
"Mobile-Security-Framework-MobSF/master/install/windows/config.txt"
'https://raw.githubusercontent.com/MobSF/'
'Mobile-Security-Framework-MobSF/master/install/windows/config.txt'
)
# Static path to config file as a starting point
CONFIG_PATH = expanduser("~") + "\\MobSF\\Config\\"
CONFIG_FILE = "config.txt"
CONFIG_PATH = expanduser('~') + '\\MobSF\\Config\\'
CONFIG_FILE = 'config.txt'
# Static path to autostart
AUTOSTART = (
"C:\\Users\\{}\\AppData\\Roaming\\Microsoft\\"
"Windows\\Start Menu\\Programs\\Startup\\".format(os.getenv('username'))
'C:\\Users\\{}\\AppData\\Roaming\\Microsoft\\'
'Windows\\Start Menu\\Programs\\Startup\\'.format(os.getenv('username'))
)
# Global var so we don't have to pass it every time..
CONFIG = ""
CONFIG = ''
def windows_config_local(path):
"""Windows Configuration"""
"""Windows Configuration."""
# Configure here if you are on windows
# Path to lock-file (so setup is only run once)
path_to_lock_file = os.path.join(path, "setup_done.txt")
if (os.path.isfile(path_to_lock_file) is False) and platform.system() == 'Windows':
logger.info("Running first time setup for windows.")
path_to_lock_file = os.path.join(path, 'setup_done.txt')
if (os.path.isfile(path_to_lock_file) is False
and platform.system() == 'Windows'):
logger.info('Running first time setup for windows.')
# Setup is to-be-executed
install_locally(path)
def download_config():
"""Download initial config file."""
# Create config path
if not os.path.exists(CONFIG_PATH):
os.makedirs(CONFIG_PATH)
@@ -64,14 +69,14 @@ def download_config():
# TODO(Give user time to modify config, but mayber after rewrite?)
# Open File
conf_file_local = open(CONFIG_PATH + CONFIG_FILE, "wb")
conf_file_local = open(CONFIG_PATH + CONFIG_FILE, 'wb')
# Downloading File
print("[*] Downloading config file..")
print('[*] Downloading config file..')
conf_file = urlrequest.urlopen(CONFIG_URL) # pylint: disable-msg=E1101
# Save content
print(("[*] Saving to File {}".format(CONFIG_FILE)))
print(('[*] Saving to File {}'.format(CONFIG_FILE)))
# Write content to file
conf_file_local.write(bytes(conf_file.read()))
@@ -82,8 +87,7 @@ def download_config():
def read_config():
"""Read the config file and write it to the global var."""
print("[*] Reading config file..")
print('[*] Reading config file..')
global CONFIG
CONFIG = configparser.ConfigParser()
@@ -92,8 +96,7 @@ def read_config():
def create_folders():
"""Create MobSF dirs."""
print("[*] Creating other folders...")
print('[*] Creating other folders...')
if not os.path.exists(CONFIG['MobSF']['downloads']):
os.makedirs(CONFIG['MobSF']['downloads'])
@@ -110,23 +113,22 @@ def create_folders():
def check_dependencies():
"""Check dependencies and install if necessary."""
print("[*] Checking dependencies...")
print('[*] Checking dependencies...')
missing_deps = []
try:
import rsa
print("[+] rsa is installed.")
import rsa # noqa F401
print('[+] rsa is installed.')
except ImportError: # pylint: disable-msg=C0103
print("[!] rsa not installed!")
missing_deps.append("rsa")
print('[!] rsa not installed!')
missing_deps.append('rsa')
if len(missing_deps) > 0:
print("[!] Please install these missing dependencies:")
print('[!] Please install these missing dependencies:')
print(missing_deps)
sys.exit()
else:
print("[+] Everything good.")
print('[+] Everything good.')
def tools_nuget():
@@ -138,14 +140,14 @@ def tools_nuget():
# Open File
nuget_file_local = open(os.path.join(
mobsf_subdir_tools, nuget_file_path), "wb")
mobsf_subdir_tools, nuget_file_path), 'wb')
# Downloading File
print("[*] Downloading nuget..")
print('[*] Downloading nuget..')
nuget_file = urlrequest.urlopen(nuget_url) # pylint: disable-msg=E1101
# Save content
print(("[*] Saving to File {}".format(nuget_file_path)))
print(('[*] Saving to File {}'.format(nuget_file_path)))
# Write content to file
nuget_file_local.write(bytes(nuget_file.read()))
@@ -161,24 +163,21 @@ def tools_binskim():
mobsf_subdir_tools = CONFIG['MobSF']['tools']
nuget = mobsf_subdir_tools + CONFIG['nuget']['file']
print("[*] Downloading and installing Binskim...")
print('[*] Downloading and installing Binskim...')
# Execute nuget to get binkim
output = subprocess.check_output(
[
nuget,
"install", binskim_nuget, '-Pre',
'-o', mobsf_subdir_tools
]
)
'install', binskim_nuget, '-Pre',
'-o', mobsf_subdir_tools])
# Some code to determine the version on the fly so we don't have to fix the
# config file on every new release of binskim..
# Search for the version number
folder = re.search(
b"Microsoft\.CodeAnalysis\.BinSkim\..*('|\") ", output # pylint: disable-msg=W1401
)
b'Microsoft\\.CodeAnalysis\\.BinSkim\\..*(\'|\") ', output)
try:
# Substring-Foo for removing b'X's in python3
if sys.version_info.major == 3:
@@ -186,17 +185,17 @@ def tools_binskim():
else:
folder = folder.group(0)[:-2]
except AttributeError:
print("[!] Unable to parse folder from binskim nuget installation.")
print('[!] Unable to parse folder from binskim nuget installation.')
sys.exit()
# Search for the exes
binaries = _find_exe(mobsf_subdir_tools + folder, [])
if len(binaries) != 2:
print("[!] Found more than 2 exes for binskim, panic!")
print('[!] Found more than 2 exes for binskim, panic!')
sys.exit()
# Determinde which one is for which arch
if "x86" in binaries[0]:
if 'x86' in binaries[0]:
CONFIG['binskim']['file_x86'] = binaries[0]
CONFIG['binskim']['file_x64'] = binaries[1]
else:
@@ -212,10 +211,10 @@ def _find_exe(path, exe_list):
"""Return a list of all exes in path, recursive."""
for filename in os.listdir(path):
if os.path.isfile(os.path.join(path, filename)):
if ".exe" in filename:
exe_list.append(path + "\\" + filename)
if '.exe' in filename:
exe_list.append(path + '\\' + filename)
else:
exe_list = _find_exe(path + "\\" + filename, exe_list)
exe_list = _find_exe(path + '\\' + filename, exe_list)
return exe_list
@@ -226,14 +225,14 @@ def tools_rpcclient():
rpc_file_path = CONFIG['rpc']['file']
# Open File
rpc_local_file = open(mobsf_subdir_tools + rpc_file_path, "wb")
rpc_local_file = open(mobsf_subdir_tools + rpc_file_path, 'wb')
# Downloading File
print("[*] Downloading rpc_server..")
print('[*] Downloading rpc_server..')
rpc_file = urlrequest.urlopen(rpc_url)
# Save content
print(("[*] Saving to File {}".format(rpc_file_path)))
print(('[*] Saving to File {}'.format(rpc_file_path)))
# Write content to file
rpc_local_file.write(bytes(rpc_file.read()))
@@ -243,30 +242,29 @@ def tools_rpcclient():
def tools_binscope():
"""Download and install Binscope for MobSF"""
"""Download and install Binscope for MobSF."""
mobsf_subdir_tools = CONFIG['MobSF']['tools']
binscope_path = mobsf_subdir_tools + 'BinScope'
# Download the right version for os
if platform.machine().endswith('64'):
binscope_url = CONFIG['binscope']['url_x64']
binscope_installer_path = binscope_path + "\\BinScope_x64.msi"
binscope_installer_path = binscope_path + '\\BinScope_x64.msi'
else:
binscope_url = CONFIG['binscope']['url_x86']
binscope_installer_path = binscope_path + "\\BinScope_x86.msi"
binscope_installer_path = binscope_path + '\\BinScope_x86.msi'
if not os.path.exists(binscope_path):
os.makedirs(binscope_path)
binscope_installer_file = open(binscope_installer_path, "wb")
binscope_installer_file = open(binscope_installer_path, 'wb')
# Downloading File
print("[*] Downloading BinScope..")
print('[*] Downloading BinScope..')
binscope_installer = urlrequest.urlopen(binscope_url)
# Save content
print(("[*] Saving to File {}".format(binscope_installer_path)))
print(('[*] Saving to File {}'.format(binscope_installer_path)))
# Write content to file
binscope_installer_file.write(bytes(binscope_installer.read()))
@@ -275,15 +273,14 @@ def tools_binscope():
binscope_installer_file.close()
# Execute the installer
print(("[*] Installing BinScope to {}".format(binscope_path)))
print(('[*] Installing BinScope to {}'.format(binscope_path)))
os.system(
'msiexec' + ' '
'INSTALLLOCATION="' + binscope_path + '" ' +
'/i "' + binscope_installer_path + '" ' +
'/passive'
)
'INSTALLLOCATION='' + binscope_path + '' '
+ '/i '' + binscope_installer_path + '' '
+ '/passive')
CONFIG['binscope']['file'] = binscope_path + "\\Binscope.exe"
CONFIG['binscope']['file'] = binscope_path + '\\Binscope.exe'
# Write to config
with open(os.path.join(CONFIG_PATH, CONFIG_FILE), 'w') as configfile:
@@ -293,7 +290,7 @@ def tools_binscope():
def generate_secret():
"""Generate rsa keys for authentication."""
import rsa
print("[*] Generating secret, please hang on.")
print('[*] Generating secret, please hang on.')
# Generate keys, taken from
# https://stuvel.eu/python-rsa-doc/usage.html#generating-keys
(pubkey, privkey) = rsa.newkeys(2048)
@@ -307,41 +304,40 @@ def generate_secret():
pub_key_file.close()
print((
"[!] Please move the private key file\n"
"\t{}\n"
"\tto MobSF to the path specified in settings.py\n"
"\t(default: Mobile-Security-Framework-MobSF/MobSF/windows_vm_priv_key.asc)"
'[!] Please move the private key file\n'
'\t{}\n'
'\tto MobSF to the path specified in settings.py\n'
'\t(default: Mobile-Security-Framework-MobSF/'
'MobSF/windows_vm_priv_key.asc)'
.format(CONFIG['MobSF']['priv_key'])
))
input("Please press any key when done..")
input('Please press any key when done..')
def autostart():
"""Create the autostart binary and run it."""
mobsf_subdir_tools = CONFIG['MobSF']['tools']
rpc_file = CONFIG['rpc']['file']
autostart_file = CONFIG['autostart']['file']
batch_file = AUTOSTART + autostart_file
print("[*] Creating autostart binary...")
print('[*] Creating autostart binary...')
# Open file
autostart_file = open(batch_file, "wb")
autostart_file = open(batch_file, 'wb')
# Define bat-text
text = """
@echo off
python {} %*
pause""".format('"' + mobsf_subdir_tools + rpc_file + '"')
pause""".format(""" + mobsf_subdir_tools + rpc_file + """)
autostart_file.write(bytes(text, 'utf8'))
# Close handle
autostart_file.close()
print("[*] Done. Start the server.")
print('[*] Done. Start the server.')
# Execute. Beware the " " because of windows strange paths..
os.system('"' + batch_file + '"')
# Execute. Beware the ' ' because of windows strange paths..
os.system(""" + batch_file + """)
def _place_lockfile(mobsf_home):
@@ -352,24 +348,21 @@ def _place_lockfile(mobsf_home):
def local_config():
"""Move local config and save paths."""
# Set the CONFIG_PATH
#global CONFIG_PATH
#CONFIG_PATH = config_path
# Create path if it doesn't exist yet
if not os.path.exists(CONFIG_PATH):
os.makedirs(CONFIG_PATH)
# Copy predefined config to MobSF folder
shutil.copy(
os.getcwd() + "\\install\\windows\\config.txt",
os.path.join(CONFIG_PATH, CONFIG_FILE)
os.getcwd() + '\\install\\windows\\config.txt',
os.path.join(CONFIG_PATH, CONFIG_FILE),
)
def rewrite_local_config(mobsf_home):
"""For local installation some config-vars need to be rewritten."""
CONFIG['MobSF']['subdir_tools'] = mobsf_home + \
"\\StaticAnalyzer\\tools\\windows\\"
CONFIG['MobSF']['subdir_tools'] = (mobsf_home
+ '\\StaticAnalyzer\\tools\\windows\\')
CONFIG['MobSF']['dir'] = mobsf_home
# Write to config
@@ -379,9 +372,8 @@ def rewrite_local_config(mobsf_home):
def rewrite_config():
"""Rewrite the config to take the profile path as the base path."""
# Take user path as base path
CONFIG['MobSF']['dir'] = expanduser("~") + CONFIG['MobSF']['dir']
CONFIG['MobSF']['dir'] = expanduser('~') + CONFIG['MobSF']['dir']
# Rewrite config with new base path
CONFIG['MobSF']['downloads'] = CONFIG['MobSF'][
@@ -404,18 +396,10 @@ def rewrite_config():
def install_locally(mobsf_home):
"""Install the MobSF-Utils on the same system as MobSF."""
# if user_config:
# local_config(mobsf_home, user_config)
# else:
# user_config = os.path.join(mobsf_home + "\\MobSF\\")
local_config()
read_config()
# rewrite_local_config(mobsf_home)
rewrite_config()
create_folders()
# if not os.path.exists(CONFIG['MobSF']['subdir_tools']):
# os.makedirs(CONFIG['MobSF']['subdir_tools'])
tools_nuget()
tools_binskim()
tools_binscope()
@@ -436,6 +420,7 @@ def _install_remote():
generate_secret()
autostart()
if __name__ == "__main__":
if __name__ == '__main__':
# Gets directly run if setup.py is run on a remote machine
_install_remote()
+3 -2
View File
@@ -1,9 +1,10 @@
#!/usr/bin/env python3
"""Django Manage."""
import os
import sys
if __name__ == "__main__":
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "MobSF.settings")
if __name__ == '__main__':
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'MobSF.settings')
from django.core.management import execute_from_command_line
+13 -8
View File
@@ -3,34 +3,39 @@
import re
import requests
from bs4 import BeautifulSoup as Soup
ANDROID_PERMISSION_DOCS_URL = "https://developer.android.com/reference/android/Manifest.permission"
ANDROID_PERMISSION_DOCS_URL = ('https://developer.android.com/'
'reference/android/Manifest.permission')
response = requests.get(ANDROID_PERMISSION_DOCS_URL)
content = Soup(response.content, "html.parser")
content = Soup(response.content, 'html.parser')
online_permissions = {}
# grab all the permissions from the online docs
permission_divs = content.find_all(
"div", {"data-version-added": re.compile(r'\d*')})
'div', {'data-version-added': re.compile(r'\d*')})
for pd in permission_divs:
permission_name = pd.find("h3").contents[0]
permission_name = pd.find('h3').contents[0]
if permission_name in ['Constants', 'Manifest.permission']:
continue
try:
protection_level = re.search(
r'Protection level\: (\w+)', str(pd)).groups()[0]
except AttributeError:
protection_level = "normal"
description = str(pd.find("p").contents[0]).strip()
protection_level = 'normal'
description = str(pd.find('p').contents[0]).strip()
online_permissions[permission_name] = [protection_level,
"TODO - fill in short description", description]
'TODO - fill in short description',
description]
# check the permissions we currently have in dvm_permissions.py
DVM_PERMISSIONS = {}
eval(compile(open("StaticAnalyzer/views/android/dvm_permissions.py").read(), "<string>", "exec"))
eval(compile(open('StaticAnalyzer/views/android/dvm_permissions.py').read(),
'<string>',
'exec'))
MANIFEST_PERMISSIONS = DVM_PERMISSIONS['MANIFEST_PERMISSION']
for permission_name in online_permissions:
+1 -1
View File
@@ -34,7 +34,7 @@ then
find ./ | grep -E "(__pycache__|\.pyo$)" | xargs rm -rf
echo 'Deleting temp and log files'
rm -rf ./logs/*
rm -rf ./classes-error.zip
rm -rf ./classes*
echo 'Deleting DB'
rm -rf ./db.sqlite3
echo 'Deleting Secret File'
+42 -35
View File
@@ -1,55 +1,58 @@
#!/usr/bin/env python
import tornado.ioloop
import tornado.web
import sqlite3 as lite
import datetime
import re
import sqlite3 as lite
import tornado.ioloop
import tornado.web
def clean_db():
"""Clean Database"""
"""Clean Database."""
con = lite.connect('db.db')
with con:
cur = con.cursor()
tms = datetime.datetime.now() - datetime.timedelta(minutes=5)
cur.execute("DELETE FROM XXE WHERE ts <=?", (tms,))
cur.execute("DELETE FROM SSRF WHERE ts <=?", (tms,))
cur.execute('DELETE FROM XXE WHERE ts <=?', (tms,))
cur.execute('DELETE FROM SSRF WHERE ts <=?', (tms,))
con.commit()
def create_db():
"""Create Database"""
"""Create Database."""
con = lite.connect('db.db')
with con:
cur = con.cursor()
cur.execute(
"SELECT count(*) FROM sqlite_master WHERE type = 'table' AND name = 'XXE'")
'SELECT count(*) FROM sqlite_master'
' WHERE type = \'table\' AND name = \'XXE\'')
tbl_exists = cur.fetchone()[0]
if tbl_exists == 0:
cur.execute("CREATE TABLE XXE(payload TEXT, ts timestamp)")
cur.execute('CREATE TABLE XXE(payload TEXT, ts timestamp)')
con.commit()
cur.execute(
"SELECT count(*) FROM sqlite_master WHERE type = 'table' AND name = 'SSRF'")
'SELECT count(*) FROM sqlite_master'
' WHERE type = \'table\' AND name = \'SSRF\'')
tbl_exists = cur.fetchone()[0]
if tbl_exists == 0:
cur.execute("CREATE TABLE SSRF(ip TEXT, ts timestamp)")
cur.execute('CREATE TABLE SSRF(ip TEXT, ts timestamp)')
con.commit()
class DeleteByIPHandler(tornado.web.RequestHandler):
"""Delete SSRF Entry by IP"""
"""Delete SSRF Entry by IP."""
def get(self, pp):
ip_addr = pp if pp else ''
con = lite.connect('db.db')
with con:
cur = con.cursor()
cur.execute("DELETE FROM SSRF WHERE ip =?", (ip_addr,))
cur.execute('DELETE FROM SSRF WHERE ip =?', (ip_addr,))
con.commit()
class XXECheckHandler(tornado.web.RequestHandler):
"""XXE Check"""
"""XXE Check."""
def get(self, pp):
xxe = pp if pp else ''
@@ -58,7 +61,7 @@ class XXECheckHandler(tornado.web.RequestHandler):
con = lite.connect('db.db')
with con:
cur = con.cursor()
cur.execute("SELECT count(*) FROM XXE WHERE payload=?", (xxe,))
cur.execute('SELECT count(*) FROM XXE WHERE payload=?', (xxe,))
atk_exists = cur.fetchone()[0]
if atk_exists == 0:
self.write('{"status": "no"}')
@@ -67,7 +70,7 @@ class XXECheckHandler(tornado.web.RequestHandler):
class SSRFCheckHandler(tornado.web.RequestHandler):
"""SSRF Check"""
"""SSRF Check."""
def get(self, pp):
ip_addr = pp if pp else ''
@@ -77,55 +80,59 @@ class SSRFCheckHandler(tornado.web.RequestHandler):
if ip_addr == 'ts':
# based on timestamp
tms = datetime.datetime.now() - datetime.timedelta(seconds=30)
cur.execute("SELECT count(*) FROM SSRF WHERE ts >=?", (tms,))
cur.execute('SELECT count(*) FROM SSRF WHERE ts >=?', (tms,))
else:
# based on ip
cur.execute("SELECT count(*) FROM SSRF WHERE ip=?", (ip_addr,))
cur.execute('SELECT count(*) FROM SSRF WHERE ip=?', (ip_addr,))
atk_exists = cur.fetchone()[0]
if atk_exists == 0:
self.write('{"count": 0}')
self.write('{\'count\': 0}')
else:
self.write('{"count": ' + str(atk_exists) + '}')
self.write('{\'count\': \' + str(atk_exists) + \'}')
class XXE_SSRFHandler(tornado.web.RequestHandler):
"""XXE and SSRF Handler"""
class XXESSRFHandler(tornado.web.RequestHandler):
"""XXE and SSRF Handler."""
def get(self):
# SSRF and XXE count based
if self.request.uri == "/":
if self.request.uri == '/':
self.remote_ip = self.request.headers.get(
'X-Forwarded-For', self.request.headers.get('X-Real-Ip', self.request.remote_ip))
'X-Forwarded-For',
self.request.headers.get(
'X-Real-Ip',
self.request.remote_ip))
tms = datetime.datetime.now()
con0 = lite.connect('db.db')
with con0:
cur = con0.cursor()
cur.execute("INSERT INTO SSRF VALUES(?,?)",
cur.execute('INSERT INTO SSRF VALUES(?,?)',
(self.remote_ip, tms))
self.write("ok")
self.write('ok')
# XXE and SSRF hash based url
tms = datetime.datetime.now()
payload = (str(self.request.uri)[:33]).replace("/", "")
payload = (str(self.request.uri)[:33]).replace('/', '')
rmatch = re.match('[0-9a-f]{32}', payload)
if rmatch:
con = lite.connect('db.db')
with con:
cur = con.cursor()
cur.execute("INSERT INTO XXE VALUES(?,?)", (payload, tms))
cur.execute('INSERT INTO XXE VALUES(?,?)', (payload, tms))
self.write(payload)
if __name__ == "__main__":
if __name__ == '__main__':
create_db()
tornado.web.Application([
(r"/ip/(?P<pp>[^\/]+)", SSRFCheckHandler),
(r"/delete/(?P<pp>[^\/]+)", DeleteByIPHandler),
(r"/md5/(?P<pp>[^\/]+)", XXECheckHandler),
(r"/.*", XXE_SSRFHandler),
(r'/ip/(?P<pp>[^\/]+)', SSRFCheckHandler),
(r'/delete/(?P<pp>[^\/]+)', DeleteByIPHandler),
(r'/md5/(?P<pp>[^\/]+)', XXECheckHandler),
(r'/.*', XXESSRFHandler),
]).listen(8080)
tornado.ioloop.PeriodicCallback(
clean_db, 300000).start() # 15 mins in milliseconds
tornado.ioloop.IOLoop.instance().start()
'''
"""
URI
1. XXE and SSRF - http://127.0.0.1:8080/<md5>
Check - http://127.0.0.1:8080/md5/<md5>
@@ -135,4 +142,4 @@ if __name__ == "__main__":
http://127.0.0.1:8080/ip/ts by timestamp
3. Delete by IP
http://127.0.0.1:8080/delete/<ip>
'''
"""
+43 -32
View File
@@ -1,12 +1,14 @@
#!/usr/bin/env python
# Mass Static Analysis
import argparse
import logging
import os
import urllib.request
import urllib.error
import urllib.parse
import argparse
import urllib.request
import requests
import logging
logger = logging.getLogger(__name__)
@@ -20,63 +22,72 @@ def is_server_up(url):
def start_scan(directory, server_url, apikey, rescan='0'):
print("\nLooking for Android/iOS/Windows binaries or source code in : " + directory)
logger.info("Uploading to MobSF Server")
print('\nLooking for Android/iOS/'
'Windows binaries or source code in : ' + directory)
logger.info('Uploading to MobSF Server')
uploaded = []
MIME = {
".apk": 'application/octet-stream',
".ipa": 'application/octet-stream',
".appx": 'application/octet-stream',
".zip": 'application/zip'
mimes = {
'.apk': 'application/octet-stream',
'.ipa': 'application/octet-stream',
'.appx': 'application/octet-stream',
'.zip': 'application/zip',
}
for filename in os.listdir(directory):
fpath = os.path.join(directory, filename)
_, ext = os.path.splitext(fpath)
if ext in MIME:
if ext in mimes:
files = {'file': (filename, open(fpath, 'rb'),
MIME[ext], {'Expires': '0'})}
mimes[ext], {'Expires': '0'})}
response = requests.post(
server_url + '/api/v1/upload', files=files, headers={'AUTHORIZATION': apikey})
if response.status_code == 200 and "hash" in response.json():
logger.info("[OK] Upload OK: " + filename)
server_url + '/api/v1/upload',
files=files,
headers={'AUTHORIZATION': apikey})
if response.status_code == 200 and 'hash' in response.json():
logger.info('[OK] Upload OK: %s'. filename)
uploaded.append(response.json())
else:
logger.error("Performing Upload: " + filename)
logger.error('Performing Upload: %s', filename)
logger.info("Running Static Analysis")
logger.info('Running Static Analysis')
for upl in uploaded:
logger.info("Started Static Analysis on: %s", upl["file_name"])
logger.info('Started Static Analysis on: %s', upl['file_name'])
if rescan == '1':
upl["re_scan"] = 1
upl['re_scan'] = 1
response = requests.post(
server_url + "/api/v1/scan", data=upl, headers={'AUTHORIZATION': apikey})
server_url + '/api/v1/scan',
data=upl,
headers={'AUTHORIZATION': apikey})
if response.status_code == 200:
logger.info("[OK] Static Analysis Complete: " + upl["file_name"])
logger.info('[OK] Static Analysis Complete: %s', upl['file_name'])
else:
logger.error("Performing Static Analysis: " + upl["file_name"])
logger.error('Performing Static Analysis: %s', upl['file_name'])
if __name__ == "__main__":
if __name__ == '__main__':
parser = argparse.ArgumentParser()
parser.add_argument("-d", "--directory",
help="Path to the directory that contains mobile app binary/zipped source code")
parser.add_argument('-d', '--directory',
help='Path to the directory that contains '
'mobile app binary/zipped source code')
parser.add_argument(
"-s", "--ipport", help="IP address and Port number of a running MobSF Server. (ex: 127.0.0.1:8000)")
'-s', '--ipport', help='IP address and Port number '
'of a running MobSF Server. '
'(ex: 127.0.0.1:8000)')
parser.add_argument(
"-k", "--apikey", help="MobSF REST API Key")
'-k', '--apikey', help='MobSF REST API Key')
parser.add_argument(
"-r", "--rescan", help="Run a fresh scan. Value can be 1 or 0 (Default: 0)")
'-r', '--rescan', help='Run a fresh scan. '
'Value can be 1 or 0 (Default: 0)')
args = parser.parse_args()
if args.directory and args.ipport and args.apikey:
server = args.ipport
directory = args.directory
server_url = "http://" + server
server_url = 'http://' + server
apikey = args.apikey
rescan = args.rescan
if is_server_up(server_url) == False:
print("MobSF REST API Server is not running at " + server_url)
print("Exiting!")
if not is_server_up(server_url):
print('MobSF REST API Server is not running at ' + server_url)
print('Exiting!')
exit(0)
# MobSF is running, start scan
start_scan(directory, server_url, apikey, rescan)
+95 -100
View File
@@ -1,78 +1,73 @@
#!/usr/bin/env python
# -*- coding: utf_8 -*-
"""
Script to create MobSF Dynamic Analysis Environment
"""
"""Script to create MobSF Dynamic Analysis Environment."""
import argparse
import logging
import os
import platform
import subprocess
import sys
import argparse
import capfuzz as cp
import logging
logger = logging.getLogger(__name__)
BASE_DIR = os.path.dirname(os.path.realpath(__file__))
TOOLSDIR = os.path.join(BASE_DIR, '../DynamicAnalyzer/tools/') # TOOLS DIR
ROOTCA = os.path.join(os.path.dirname(cp.__file__), "ca", "mitmproxy-ca-cert.cer")
TYPE_VM = "1"
XPOSED_OLD = "1"
ROOTCA = os.path.join(os.path.dirname(cp.__file__),
'ca', 'mitmproxy-ca-cert.cer')
TYPE_VM = '1'
XPOSED_OLD = '1'
def execute_cmd(args, ret=False):
"""
Execute Commands
"""
"""Execute Commands."""
try:
logger.info("Executing Command - " + ' '.join(args))
logger.info('Executing Command - %s', ' '.join(args))
if ret:
return subprocess.check_output(args)
else:
subprocess.call(args)
except Exception as exp:
logger.error(("Executing Command - " + str(exp)))
except Exception:
logger.error('Error Executing Command')
def get_adb():
"""
Get ADB Location
"""
"""Get ADB Location."""
try:
adb = 'adb'
if platform.system() == "Darwin":
if platform.system() == 'Darwin':
adb_dir = os.path.join(TOOLSDIR, 'adb/mac/')
subprocess.call(["chmod", "777", adb_dir])
subprocess.call(['chmod', '777', adb_dir])
adb = os.path.join(TOOLSDIR, 'adb/mac/adb')
elif platform.system() == "Linux":
elif platform.system() == 'Linux':
adb_dir = os.path.join(TOOLSDIR, 'adb/linux/')
subprocess.call(["chmod", "777", adb_dir])
subprocess.call(['chmod', '777', adb_dir])
adb = os.path.join(TOOLSDIR, 'adb/linux/adb')
elif platform.system() == "Windows":
elif platform.system() == 'Windows':
adb = os.path.join(TOOLSDIR, 'adb/windows/adb.exe')
return adb
except Exception as exp:
logger.error(("Getting ADB Location - " + str(exp)))
return "adb"
logger.error(('Getting ADB Location - %s', str(exp)))
return 'adb'
def main():
"""
Main of Script
"""
print("\nMobSFy Script\n\nThis script allows you to configure any rooted "
"android Device or VM to perfrom MobSF dynamic analysis.\n"
"(Supports Android Version 4.03 to 4.4)")
"""Main of Script."""
print('\nMobSFy Script\n\nThis script allows you to configure any rooted '
'android Device or VM to perfrom MobSF dynamic analysis.\n'
'(Supports Android Version 4.03 to 4.4)')
parser = argparse.ArgumentParser()
parser.add_argument("-i", "--identifier",
help="IP:PORT or Serial no of Device/VM."
" (ex: 192.168.1.2:5555)")
parser.add_argument('-i', '--identifier',
help='IP:PORT or Serial no of Device/VM.'
' (ex: 192.168.1.2:5555)')
parser.add_argument(
"-t", "--type", help="Specify the type. Supported types are"
" 1. VM or 2.Device. (ex: 1)")
'-t', '--type', help='Specify the type. Supported types are'
' 1. VM or 2.Device. (ex: 1)')
parser.add_argument(
"-v", "--version", help="Specify the Android Version. "
"Available options are 1. <5 or 2. >=5. (ex: 1)")
'-v', '--version', help='Specify the Android Version. '
'Available options are 1. <5 or 2. >=5. (ex: 1)')
args = parser.parse_args()
try:
if args.identifier and args.type:
@@ -81,27 +76,27 @@ def main():
xposed_ver = args.version
else:
adbconnect = input(
"Enter the IP:PORT or Serial no of the Device/VM"
" (Ex: 192.168.1.2:5555) and press enter: ")
vm_or_ip = input("Choose\n 1. VM\n 2. Device\nEnter your choice: ")
'Enter the IP:PORT or Serial no of the Device/VM'
' (Ex: 192.168.1.2:5555) and press enter: ')
vm_or_ip = input('Choose\n 1. VM\n 2. Device\nEnter your choice: ')
xposed_ver = input(
"Android Version\n1. <5\n2. >=5\nEnter your choice: ")
execute_cmd([get_adb(), "kill-server"])
execute_cmd([get_adb(), "start-server"])
execute_cmd([get_adb(), "connect", adbconnect])
execute_cmd([get_adb(), "-s", adbconnect, "wait-for-device"])
'Android Version\n1. <5\n2. >=5\nEnter your choice: ')
execute_cmd([get_adb(), 'kill-server'])
execute_cmd([get_adb(), 'start-server'])
execute_cmd([get_adb(), 'connect', adbconnect])
execute_cmd([get_adb(), '-s', adbconnect, 'wait-for-device'])
# Install MITM RootCA
execute_cmd([get_adb(), "-s", adbconnect, "push",
ROOTCA, "/data/local/tmp/0025aabb.0"])
execute_cmd([get_adb(), "-s", adbconnect, "shell", "su", "-c",
"mount", "-o", "rw,remount,rw", "/system"])
execute_cmd([get_adb(), "-s", adbconnect, "shell", "su", "-c", "cp",
"/data/local/tmp/0025aabb.0",
"/system/etc/security/cacerts/0025aabb.0"])
execute_cmd([get_adb(), "-s", adbconnect, "shell", "su", "-c", "chmod",
"644", "/system/etc/security/cacerts/0025aabb.0"])
execute_cmd([get_adb(), "-s", adbconnect, "shell",
"rm", "/data/local/tmp/0025aabb.0"])
execute_cmd([get_adb(), '-s', adbconnect, 'push',
ROOTCA, '/data/local/tmp/0025aabb.0'])
execute_cmd([get_adb(), '-s', adbconnect, 'shell', 'su', '-c',
'mount', '-o', 'rw,remount,rw', '/system'])
execute_cmd([get_adb(), '-s', adbconnect, 'shell', 'su', '-c', 'cp',
'/data/local/tmp/0025aabb.0',
'/system/etc/security/cacerts/0025aabb.0'])
execute_cmd([get_adb(), '-s', adbconnect, 'shell', 'su', '-c', 'chmod',
'644', '/system/etc/security/cacerts/0025aabb.0'])
execute_cmd([get_adb(), '-s', adbconnect, 'shell',
'rm', '/data/local/tmp/0025aabb.0'])
# Install MobSF requirements
data_pusher = os.path.join(TOOLSDIR, 'onDevice/DataPusher.apk')
screen_cast = os.path.join(TOOLSDIR, 'onDevice/ScreenCast.apk')
@@ -123,56 +118,56 @@ def main():
fake_cpuinfo = os.path.join(TOOLSDIR, 'onDevice/antivm/fake-cpuinfo')
fake_drivers = os.path.join(TOOLSDIR, 'onDevice/antivm/fake-drivers')
logger.info("Installing MobSF DataPusher")
execute_cmd([get_adb(), "-s", adbconnect,
"install", "-r", data_pusher])
logger.info("Installing MobSF ScreenCast")
execute_cmd([get_adb(), "-s", adbconnect,
"install", "-r", screen_cast])
logger.info("Installing MobSF Clipboard Dumper")
execute_cmd([get_adb(), "-s", adbconnect, "install", "-r", clip_dump])
logger.info("Copying hooks.json")
execute_cmd([get_adb(), "-s", adbconnect,
"push", hooks, "/data/local/tmp/"])
logger.info("Installing Xposed Framework")
execute_cmd([get_adb(), "-s", adbconnect, "install", "-r", xposed])
logger.info("Installing Droidmon API Analyzer")
execute_cmd([get_adb(), "-s", adbconnect, "install", "-r", droidmon])
logger.info("Installing JustTrustMe")
execute_cmd([get_adb(), "-s", adbconnect, "install", "-r", justrustme])
logger.info("Installing RootCloak")
execute_cmd([get_adb(), "-s", adbconnect, "install", "-r", rootcloak])
logger.info('Installing MobSF DataPusher')
execute_cmd([get_adb(), '-s', adbconnect,
'install', '-r', data_pusher])
logger.info('Installing MobSF ScreenCast')
execute_cmd([get_adb(), '-s', adbconnect,
'install', '-r', screen_cast])
logger.info('Installing MobSF Clipboard Dumper')
execute_cmd([get_adb(), '-s', adbconnect, 'install', '-r', clip_dump])
logger.info('Copying hooks.json')
execute_cmd([get_adb(), '-s', adbconnect,
'push', hooks, '/data/local/tmp/'])
logger.info('Installing Xposed Framework')
execute_cmd([get_adb(), '-s', adbconnect, 'install', '-r', xposed])
logger.info('Installing Droidmon API Analyzer')
execute_cmd([get_adb(), '-s', adbconnect, 'install', '-r', droidmon])
logger.info('Installing JustTrustMe')
execute_cmd([get_adb(), '-s', adbconnect, 'install', '-r', justrustme])
logger.info('Installing RootCloak')
execute_cmd([get_adb(), '-s', adbconnect, 'install', '-r', rootcloak])
if vm_or_ip == TYPE_VM:
logger.info("Installing Android BluePill")
execute_cmd([get_adb(), "-s", adbconnect,
"install", "-r", bluepill])
logger.info("Copying fake-build.prop")
execute_cmd([get_adb(), "-s", adbconnect, "push",
fake_build, "/data/local/tmp/"])
logger.info("Copying fake-cpuinfo")
execute_cmd([get_adb(), "-s", adbconnect, "push",
fake_cpuinfo, "/data/local/tmp/"])
logger.info("Copying fake-drivers")
execute_cmd([get_adb(), "-s", adbconnect, "push",
fake_drivers, "/data/local/tmp/"])
logger.info("Launching Xposed Framework.")
xposed_installer = ("de.robv.android.xposed.installer/"
"de.robv.android.xposed.installer.WelcomeActivity")
execute_cmd([get_adb(), "-s", adbconnect, "shell", "am", "start", "-n",
logger.info('Installing Android BluePill')
execute_cmd([get_adb(), '-s', adbconnect,
'install', '-r', bluepill])
logger.info('Copying fake-build.prop')
execute_cmd([get_adb(), '-s', adbconnect, 'push',
fake_build, '/data/local/tmp/'])
logger.info('Copying fake-cpuinfo')
execute_cmd([get_adb(), '-s', adbconnect, 'push',
fake_cpuinfo, '/data/local/tmp/'])
logger.info('Copying fake-drivers')
execute_cmd([get_adb(), '-s', adbconnect, 'push',
fake_drivers, '/data/local/tmp/'])
logger.info('Launching Xposed Framework.')
xposed_installer = ('de.robv.android.xposed.installer/'
'de.robv.android.xposed.installer.WelcomeActivity')
execute_cmd([get_adb(), '-s', adbconnect, 'shell', 'am', 'start', '-n',
xposed_installer])
if vm_or_ip == TYPE_VM:
print("\n 1 .Install the Framework\n 2. Restart the device\n"
" 3. Enable Android BluePill, Droidmon, "
"JustTrustMe and RootCloak.")
print('\n 1 .Install the Framework\n 2. Restart the device\n'
' 3. Enable Android BluePill, Droidmon, '
'JustTrustMe and RootCloak.')
else:
print("\n 1 .Install the Framework\n 2. Restart the device\n "
"3. Enable Droidmon, JustTrustMe and RootCloak.")
logger.info("MobSFy Script Executed Successfully")
except Exception as exp:
logger.error("Error occurred - " + str(exp))
print('\n 1 .Install the Framework\n 2. Restart the device\n '
'3. Enable Droidmon, JustTrustMe and RootCloak.')
logger.info('MobSFy Script Executed Successfully')
except Exception:
logger.error('Error occurred')
sys.exit(0)
if __name__ == "__main__":
if __name__ == '__main__':
main()
-318
View File
@@ -1,318 +0,0 @@
#!/usr/bin/env python
# -*- coding: utf_8 -*-
import os
import platform
import subprocess
import sys
import getpass
import shutil
import io
import string
import re
import logging
logger = logging.getLogger(__name__)
BASE_DIR = os.path.join(os.path.dirname(os.path.realpath(__file__)), "../")
def get_windows_drive():
drive = os.getenv('WINDIR')[:1] + ':'
if not drive:
return 'C:'
return drive
def print_log(msg, log_type='INFO'):
print('\n[' + log_type + '] ' + msg + '\n')
def execute_cmd(args, ret=False):
try:
logger.info("Executing Command - " + ' '.join(args))
if ret:
return subprocess.check_output(args)
else:
subprocess.call(args)
except Exception as e:
logger.error("Executing Command - " + str(e))
def verify_path(help_msg):
path = input(help_msg + ': ')
path = path.strip()
while not os.path.exists(path):
print_log('Path specified does not exists \ no access', 'ERROR')
path = input(help_msg)
return path
def guess_android_avd_folder():
system = platform.system()
if system == 'Darwin':
username = getpass.getuser()
first_guess = os.path.join('/Users', username, '.android/avd')
if os.path.exists(first_guess):
return first_guess
elif system == "Linux":
for path in [os.path.expanduser('~/.android/avd/')]:
if os.path.exists(path):
return path
elif system == 'Windows':
username = getpass.getuser()
drive = get_windows_drive()
for path in [os.path.join(drive + '\\Documents and Settings', username, '.android\\avd'),
os.path.join(drive + '\\Users', username, '.android\\avd')]:
if os.path.exists(path):
return path
return False
def guess_android_sdk_folder():
system = platform.system()
username = getpass.getuser()
if system == 'Darwin':
for path in [os.path.join('/Users', username, 'Library/Android/Sdk/'),
os.path.join('/Users', username, 'Library/Android/sdk/')]:
if os.path.exists(path):
return path
elif system == "Linux":
for path in ['/usr/local/android-sdk',
'/usr/local/android',
'/usr/local/Android',
os.path.expanduser('~/Android/Sdk'),
os.path.expanduser('~/Android/sdk'),
os.path.expanduser('~/android/Sdk'),
os.path.expanduser('~/android/sdk')]:
if os.path.exists(path):
return path
elif system == 'Windows':
drive = get_windows_drive()
for path in [os.path.join(drive + '\\Users', username, 'AppData\\Local\\Android\\sdk'),
os.path.join(drive + '\\Users', username,
'AppData\\Local\\Android\\Sdk'),
os.path.join(drive + '\\Documents and Settings',
username, 'AppData\\Local\\Android\\sdk'),
os.path.join(drive + '\\Documents and Settings', username, 'AppData\\Local\\Android\\Sdk')]:
if os.path.exists(path):
return path
return False
def find_emulator_binary(sdk):
system = platform.system()
if system in ['Darwin', 'Linux']:
# Prefer emulator folder on tools folder
for path in [os.path.join(sdk, 'emulator', 'emulator'),
os.path.join(sdk, 'tools', 'emulator')]:
if os.path.exists(path):
return path
elif system == 'Windows':
for path in [os.path.join(sdk, 'emulator', 'emulator.exe'),
os.path.join(sdk, 'tools', 'emulator.exe')]:
if os.path.exists(path):
return path
return False
def find_adb_binary(sdk):
system = platform.system()
if system in ['Darwin', 'Linux']:
guess = os.path.join(sdk, 'platform-tools', 'adb')
if os.path.exists(guess):
return guess
elif system == 'Windows':
guess = os.path.join(sdk, 'platform-tools', 'adb.exe')
if os.path.exists(guess):
return guess
return False
def find_skin(sdk):
# Just a basic check
system = platform.system()
if system == 'Darwin':
guess = r'/Applications/Android Studio.app/Contents/plugins/android/lib/device-art-resources/nexus_5'
if os.path.exists(guess):
return guess
elif system in ['Windows', 'Linux']:
guess = os.path.join(sdk, 'skins', 'nexus_5')
if os.path.exists(guess):
return guess
return False
def is_file_exists(file_path):
"""Check if File Exists"""
return bool(os.path.isfile(file_path))
# returns an array of [str(tabs_string), str(rest_of_the_string)]
def split_tabs(inp_string):
rgx = re.compile(r"([\s]+)(.*)")
match = rgx.match(inp_string)
if match:
return [match.group(1), match.group(2)]
else:
return ['', inp_string]
# path to modify, replace dict = {'field_to_replace1':'value1',
# 'field_to_replace2': 'value2'}
def replace_values_by_fieldnames(path, replace_dict):
replaced_lines = []
with io.open(path, mode='r', encoding="utf8", errors="ignore") as fild:
for line in fild.readlines():
tmp_line = line
if path.endswith('.py'):
tabs_and_str = split_tabs(line)
for field_to_replace in list(replace_dict.keys()):
# Python files has annoying tabs that we should consider
if path.endswith('.py'):
if tabs_and_str[1].lower().startswith(field_to_replace.lower()):
tmp_line = tabs_and_str[0] + field_to_replace + " = r\"" + replace_dict[
field_to_replace].strip(" \"'").lstrip("r\"") + "\"\n"
else:
if line.startswith(field_to_replace + '='):
tmp_line = field_to_replace + '=' + \
replace_dict[field_to_replace].strip() + '\n'
replaced_lines.append(tmp_line)
with io.open(path, 'w') as fild:
# newlines are validated before
fild.write(string.join(replaced_lines, ''))
def main():
sdk_path = ''
avd_path = ''
adb_path = ''
emulator_binary = ''
mobsf_arm_folder = ''
settings_py = ''
print("\nMobSFy_AVD Script\n\n")
print_log('Starting MobSF - AVD interactive configuration script')
print_log('Make sure to run this script ONLY after you successfuly installed latest AndroidStudio & downloaded MobSF_ARM_Emulator.zip')
# First gather all the paths needed to make to copy opera
print_log('Please specify the path to MobSF_ARM_Emulator extracted folder')
mobsf_arm_folder = verify_path('MobSF_ARM_Emulator folder')
# Give the user the ability to change the sdk and avd folder, let me guess
# the other tools
print_log('This script will overwrite any previously generated files.')
guessd_sdk_path = guess_android_sdk_folder()
if guessd_sdk_path:
user_approve = input(
"Guessing Android sdk path: " + guessd_sdk_path + '\n Press Enter/alternative path')
if user_approve.strip() == '':
sdk_path = guessd_sdk_path
elif os.path.exists(user_approve):
sdk_path = user_approve
if not sdk_path:
sdk_path = verify_path('Android SDK path')
guessd_avd_path = guess_android_avd_folder()
if guessd_avd_path:
user_approve = input(
"Guessing Android AVD folder: " + guessd_avd_path + '\n Press Enter/alternative path')
if user_approve.strip() == '':
avd_path = guessd_avd_path
elif os.path.exists(user_approve):
avd_path = user_approve
if not avd_path:
avd_path = verify_path('Android AVD path')
emulator_binary = find_emulator_binary(sdk_path)
if not emulator_binary:
emulator_binary = verify_path('emulator binary')
adb_path = find_adb_binary(sdk_path)
if not adb_path:
adb_path = verify_path('adb binary')
settings_py = os.path.join(BASE_DIR, 'MobSF', 'settings.py')
if not os.path.exists(settings_py):
settings_py = verify_path('MobSF/settings.py file')
skin_path = find_skin(sdk_path)
if not skin_path:
skin_path = verify_path('nexus 5 skin path')
print_log('Finished finding all the paths needed')
################## Copy the downloaded emulator and system image #########
emulator_avd = os.path.join(mobsf_arm_folder, 'Nexus5API16.avd')
emulator_ini = os.path.join(mobsf_arm_folder, 'Nexus5API16.ini')
new_emulator_avd = os.path.join(avd_path, 'Nexus5API16.avd')
new_emulator_ini = os.path.join(avd_path, 'Nexus5API16.ini')
print_log('Copying emulator files to avd folder: ' + avd_path)
if is_file_exists(new_emulator_ini):
print_log("Replacing old Emulator INI")
os.remove(new_emulator_ini)
shutil.copyfile(emulator_ini, new_emulator_ini)
if os.path.isdir(new_emulator_avd):
print_log("Replacing old Emulator AVD")
shutil.rmtree(new_emulator_avd)
shutil.copytree(emulator_avd, new_emulator_avd)
system_images = os.path.join(sdk_path, 'system-images')
xposed_image_path = os.path.join(system_images, 'Xposed-android-16')
downloaded_xposed_image = os.path.join(
mobsf_arm_folder, 'Xposed-android-16')
if os.path.isdir(xposed_image_path):
print_log("Replacing old Xposed image")
shutil.rmtree(xposed_image_path)
shutil.copytree(downloaded_xposed_image, xposed_image_path)
################## Modify all the config files ###########################
print_log('Modifying config files')
# Nexus5API16.ini
replace_values_by_fieldnames(new_emulator_ini, {
'path': new_emulator_avd,
'skin.path': skin_path
})
# Nexus5API16.avd/config.ini
replace_values_by_fieldnames(os.path.join(new_emulator_avd, 'config.ini'), {
'skin.path': skin_path
})
# Nexus5API16.avd/hardware-qemu.ini
replace_values_by_fieldnames(os.path.join(new_emulator_avd, 'hardware-qemu.ini'), {
'hw.sdCard.path': os.path.join(new_emulator_avd, 'sdcard.img'),
'disk.cachePartition.path': os.path.join(new_emulator_avd, 'cache.img'),
'kernel.path': os.path.join(xposed_image_path, 'kernel-qemu'),
'disk.ramdisk.path': os.path.join(xposed_image_path, 'ramdisk.img'),
'disk.systemPartition.initPath': os.path.join(xposed_image_path, 'system.img'),
'disk.dataPartition.path': os.path.join(new_emulator_avd, 'userdata.img'),
})
replace_values_by_fieldnames(settings_py, {
'AVD_EMULATOR': emulator_binary,
'AVD_PATH': avd_path,
'ADB_BINARY': 'r"' + adb_path + '"'
})
print("\n\nAll Done! you can now use MobSF AVD Emulator :)\n\n")
if __name__ == '__main__':
sys.exit(main())
-87
View File
@@ -1,87 +0,0 @@
import os
import traceback
import struct
import logging
logger = logging.getLogger(__name__)
# This is a small wraper to help manipulate the qcow2 system image inside the avd
# For more info please refer to - https://people.gnome.org/~markmc/qcow-image-format.html
class Qcow2():
def __init__(self, path):
self.path = path
self.backing_file_str_offset = 0
self.backing_file_str_size = 0
self.BACKING_FILE_HEADER = 8
self.SIZE_HEADER = 16
self.tmp_backing_str = ''
def parse_header(self):
try:
# The backing file string offset & the size of the string are part of the qcow2 header
with open(self.path, 'rb') as read_fd:
# uint64_t
read_fd.seek(self.BACKING_FILE_HEADER)
raw_offset = read_fd.read(8)
self.backing_file_str_offset = struct.unpack('>q', raw_offset)[0]
# uint32_t
read_fd.seek(self.SIZE_HEADER)
raw_offset = read_fd.read(4)
self.backing_file_str_size = struct.unpack('>i', raw_offset)[0]
return True
except:
logger.error("Qcow2-parse_header: \r\n{}".format(traceback.format_exc()))
return None
def get_backing_file_path_str(self):
try:
if not (self.backing_file_str_offset and self.backing_file_str_size):
logger.error("Qcow2-get_backing_file_path_str: offset and size not initialized, run parse_header first")
return None
with open(self.path, 'rb') as read_fd:
read_fd.seek(self.backing_file_str_offset)
raw_str = read_fd.read(self.backing_file_str_size)
backing_str = str(raw_str.decode('ascii')).replace('\x00', '')
self.tmp_backing_str = backing_str
return backing_str
except:
logger.error("Qcow2-get_backing_file_path_str: \r\n{}".format(traceback.format_exc()))
return None
def write_new_system_path_inside_qcow(self, new_system_path):
try:
# Get the length of the path so we could update the header too
system_image_str_len = len(new_system_path)
# Open in override mode so we don't need to read the whole file just to replace a little string
with open(self.path, 'r+b') as write_fd:
# First let's update the str size in the header
write_fd.seek(self.SIZE_HEADER)
write_fd.write(struct.pack(">i", system_image_str_len))
# Write the new system.img path
# There is no termination (we just wrote the size header)
write_fd.seek(self.backing_file_str_offset)
write_fd.write(new_system_path.encode('ascii'))
# optional - Clean up the left overs from the previous string
# We don't really care about this check so we do it only when we already called get_backing_file_
# path_str
if self.tmp_backing_str:
if len(self.tmp_backing_str) > system_image_str_len:
zeros_to_append = len(self.tmp_backing_str) - system_image_str_len
write_fd.write(b'\x00' * zeros_to_append)
logger.info("New system path was written to qcow file")
return True
except:
logger.error("Qcow2-write_new_system_path_inside_qcow: \r\n{}".format(traceback.format_exc()))
return False
-199
View File
@@ -1,199 +0,0 @@
import sys
import os
import platform
import subprocess
import django
from os.path import dirname, abspath
import logging
from django.conf import settings
from DynamicAnalyzer.views.android.shared import adb_command
from DynamicAnalyzer.views.android.shared import get_identifier
from MobSF.utils import PrintException
logger = logging.getLogger(__name__)
MobSF_path = dirname(dirname(abspath(__file__)))
sys.path.append(MobSF_path)
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'MobSF.settings')
django.setup()
def check_config():
try:
for path in [settings.AVD_EMULATOR,
settings.ADB_BINARY]:
if not path:
logger.error("ADB binary not configured, please refer to the official documentation")
return False
if settings.ANDROID_DYNAMIC_ANALYZER != 'MobSF_AVD':
logger.error("Wrong configuration - ANDROID_DYNAMIC_ANALYZER, please refer to the official documentation")
return False
return True
except:
PrintException("check_config")
return False
# In some cases, the modifications to the AVD won't let it boot, this will to the trick
def help_boot_avd():
try:
emulator = get_identifier()
# Wait for the adb to answer
args = [settings.ADB_BINARY,
"-s",
emulator,
"wait-for-device"]
logger.info("help_boot_avd: wait-for-device")
subprocess.call(args)
# Make sure adb running as root
logger.info("help_boot_avd: root")
adb_command(['root'])
# Make sure adb running as root
logger.info("help_boot_avd: remount")
adb_command(['remount'])
# Make sure the system verity feature is disabled (Obviously, modified the system partition)
logger.info("help_boot_avd: disable-verity")
adb_command(['disable-verity'])
# Make SELinux permissive - in case SuperSu/Xposed didn't patch things right
logger.info("help_boot_avd: setenforce")
adb_command(['setenforce', '0'], shell=True)
logger.info("help_boot_avd: finished!")
return True
except:
PrintException("help_boot_avd")
return False
def start_avd():
try:
if platform.system() == 'Darwin':
# There is a strage error in mac with the dyld one in a while..
# this should fix it..
if 'DYLD_FALLBACK_LIBRARY_PATH' in list(os.environ.keys()):
del os.environ['DYLD_FALLBACK_LIBRARY_PATH']
args = [
settings.AVD_EMULATOR,
'-avd',
settings.AVD_NAME,
"-writable-system",
"-no-snapshot-load",
"-port",
str(settings.AVD_ADB_PORT),
]
logger.info("starting emulator: \r\n" + ' '.join(args))
subprocess.Popen(args, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
return True
except:
PrintException("start_avd")
return False
def guess_system_image(avd_emulator_path):
potential_paths = list()
if platform.system() == 'Darwin':
# Get only /Users/username first
user_home = '/'.join(avd_emulator_path.split(os.path.sep)[:3])
potential_paths.append(os.path.join(user_home, 'Library/Android/Sdk/system-images/android-23/google_apis/armeabi-v7a/system.img'))
elif platform.system() == 'Linux':
# TODO: TBD
pass
elif platform.system() == 'Windows':
# TODO: TBD
pass
for path in potential_paths:
if os.path.exists(path):
return path
return None
def get_qcow_image(avd_emulator_path):
potential_paths = list()
if platform.system() == 'Darwin':
# Get only /Users/username first
user_home = '/'.join(avd_emulator_path.split(os.path.sep)[:3])
potential_paths.append(os.path.join(user_home, '.android/avd/{}.avd/system.img.qcow2'.format(settings.AVD_NAME)))
elif platform.system() == 'Linux':
# TODO: TBD
pass
elif platform.system() == 'Windows':
# TODO: TBD
pass
for path in potential_paths:
if os.path.exists(path):
return path
return None
def check_system_file():
# ex. /Users/matandobr/Library/Android/sdk/tools/emulator
emulator_binary = settings.AVD_EMULATOR
# Check if the system image is installed currectly
local_system_image = guess_system_image(emulator_binary)
if not local_system_image:
logger.error("Cannot find system.img file, Please verify you've installed android 6 armv7 with google apis system image")
return False
# Check if the modified system is already been patched
qcow_image = get_qcow_image(emulator_binary)
if not qcow_image:
logger.error("system.img.qcow2 wasn't found on the system, make sure you copied the emulator to the proper folder")
return False
if not os.path.exists(qcow_image):
logger.error("system.img.qcow2 wasn't found on the system, make sure you copied the emulator to the proper folder")
return False
qcow = Qcow2(qcow_image)
if not qcow.parse_header():
return False
original_system_path = qcow.get_backing_file_path_str()
if not original_system_path:
return False
if os.path.exists(original_system_path):
logger.info("system.img.qcow path check passed successfully")
return True
logger.info("qcow path didn't match, replacing")
if not qcow.write_new_system_path_inside_qcow(local_system_image):
logger.error("Error fixing system file")
return False
# After we wrote a new system path, verify it
qcow.parse_header()
new_system_path = qcow.get_backing_file_path_str()
if os.path.exists(new_system_path):
logger.info("system.img.qcow path check passed successfully")
return True
else:
logger.error("system file verification failed")
return False
def main():
logger.info("MobSF - start_avd.py has started")
if not check_config():
return -1
if not check_system_file():
return -1
if not start_avd():
return -1
if not help_boot_avd():
return -1
logger.info("start_avd successfully finished")
logger.info("Please wait untill the emulator will load completely, only then take a snapshot")
return 1
if __name__ == '__main__':
from qcow2 import Qcow2
sys.exit(main())
else:
from scripts.qcow2 import Qcow2
+3 -8
View File
@@ -55,7 +55,7 @@ textarea:focus {
<div class="row">
<p id="but">
<a id="start" class="btn btn-primary" role="button">Create Environment</a>
<a id="screen" class="btn btn-primary" role="button" style="visibility: hidden" {% if AVD %}disabled{% endif %}>Show Screen</a>
<a id="screen" class="btn btn-primary" role="button" style="visibility: hidden" >Show Screen</a>
<a id="rootca" class="btn btn-primary" role="button" style="visibility: hidden" >Remove MobSF RootCA</a>
<a id="expactt" class="btn btn-primary" role="button" style="visibility: hidden" >Start Exported Activity Tester</a>
<a id="actt" class="btn btn-primary" role="button" style="visibility: hidden" >Start Activity Tester</a>
@@ -197,12 +197,7 @@ textarea:focus {
$('#start').text("Environment Created");
$('#stat').append("\nRunning HTTPS Proxy");
{% comment %}
AVD throws error if we overwrite files
{% endcomment %}
{% if AVD == True %}
$('#stat').append("\nMobSF RootCA Installed Successfully");
{% else %}
//Install MobSF RootCA
$.ajax({
url : "../MobSFCA/",
@@ -222,7 +217,7 @@ textarea:focus {
document.getElementById("er").srcdoc = xhr.responseText;
}
});
{% endif %}
$('#stat').append("\nConnecting to VM");
$('#stat').append("\nMounting");
+82
View File
@@ -0,0 +1,82 @@
[tox]
envlist = py36
skipsdist = True
[testenv]
basepython = python3.6
install_command = {basepython} {envdir}/bin/pip install {opts} {packages}
list_dependencies_command = {basepython} {envdir}/bin/pip freeze
deps =
-r{toxinidir}/requirements.txt
[testenv:lint]
skip_install = true
deps =
pydocstyle==3.0.0
flake8
flake8-broken-line
flake8-bugbear
flake8-builtins
flake8-colors
flake8-commas
flake8-comprehensions
flake8-docstrings
flake8-eradicate
flake8-import-order
flake8-logging-format
flake8-quotes
flake8-self
pep8-naming
radon
commands =
{basepython} {envdir}/bin/flake8 {posargs}
[testenv:clean]
deps =
skip_install = true
whitelist_externals =
bash
find
rm
commands =
find . -name "*.py[co]" -delete
bash -c 'find . -name "__pycache__" -exec rm -fr \{\} \; -prune'
bash -c 'find . -depth -name "*.egg-info" -exec rm -fr \{\} \;'
rm -rf .tox
bash -c './scripts/clean.sh y'
; Reference for error codes:
; http://www.pydocstyle.org/en/latest/error_codes.html
[flake8]
enable-extensions = G
exclude =
.tox
.git
__pycache__
uploads
downloads
static
templates
logs
venv
DynamicAnalyzer/tools/adb/
StaticAnalyzer/migrations/
StaticAnalyzer/tools/enjarify/
ignore =
D100
D101
D102
D103
D104
D105
D107
D401
W503
Q003 ; Allow only ' for strings
SF01 ; Allow private member access
I100 ; Use python sort imports
R701 ; TODO fix this later too complex
; remove # flake8: noqa from manifest_analysis.py, should be fixed with R701
radon_max_cc = 10