mirror of
https://github.com/MobSF/Mobile-Security-Framework-MobSF.git
synced 2026-10-12 02:06:59 +00:00
flake8 linting and code QA
This commit is contained in:
+2
-3
@@ -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
|
||||
|
||||
@@ -1,3 +0,0 @@
|
||||
from django.contrib import admin
|
||||
|
||||
# Register your models here.
|
||||
|
||||
@@ -1,3 +0,0 @@
|
||||
from django.db import models
|
||||
|
||||
# Create your models here.
|
||||
|
||||
@@ -1,3 +0,0 @@
|
||||
from django.test import TestCase
|
||||
|
||||
# Create your tests here.
|
||||
|
||||
@@ -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')
|
||||
|
||||
@@ -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
@@ -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')
|
||||
|
||||
@@ -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')
|
||||
|
||||
@@ -1,3 +0,0 @@
|
||||
from django.contrib import admin
|
||||
|
||||
# Register your models here.
|
||||
|
||||
@@ -1,3 +0,0 @@
|
||||
from django.db import models
|
||||
|
||||
# Create your models here.
|
||||
|
||||
@@ -1,3 +0,0 @@
|
||||
from django.test import TestCase
|
||||
|
||||
# Create your tests here.
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -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
|
||||
|
||||
|
||||
@@ -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')
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
@@ -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 +0,0 @@
|
||||
from django.db import models
|
||||
+148
-158
@@ -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
@@ -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
@@ -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
@@ -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
|
||||
|
||||
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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/')
|
||||
|
||||
@@ -1,3 +0,0 @@
|
||||
from django.contrib import admin
|
||||
|
||||
# Register your models here.
|
||||
|
||||
+19
-33
@@ -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
|
||||
|
||||
|
||||
|
||||
@@ -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
@@ -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()
|
||||
|
||||
@@ -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 = ''
|
||||
|
||||
@@ -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]',
|
||||
},
|
||||
}
|
||||
|
||||
@@ -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': '',
|
||||
}]
|
||||
|
||||
@@ -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')
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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')
|
||||
|
||||
@@ -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')
|
||||
|
||||
@@ -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')
|
||||
|
||||
@@ -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.',
|
||||
},
|
||||
}
|
||||
|
||||
@@ -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')
|
||||
|
||||
@@ -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')
|
||||
|
||||
@@ -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
@@ -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')
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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')
|
||||
|
||||
@@ -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')
|
||||
|
||||
@@ -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 {}
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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')
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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}
|
||||
|
||||
@@ -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')
|
||||
|
||||
@@ -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')
|
||||
|
||||
@@ -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')
|
||||
|
||||
@@ -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',
|
||||
},
|
||||
|
||||
]
|
||||
|
||||
@@ -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': '',
|
||||
},
|
||||
]
|
||||
|
||||
@@ -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')
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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
@@ -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
@@ -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
|
||||
|
||||
@@ -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
@@ -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()
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
@@ -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'
|
||||
|
||||
@@ -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>
|
||||
'''
|
||||
"""
|
||||
|
||||
@@ -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
@@ -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()
|
||||
|
||||
@@ -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())
|
||||
@@ -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
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -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
|
||||
@@ -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");
|
||||
|
||||
@@ -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
|
||||
Reference in New Issue
Block a user