mirror of
https://github.com/MobSF/Mobile-Security-Framework-MobSF.git
synced 2026-10-11 22:50:09 +00:00
Enables dynamic analysis on real jailbroken iOS devices connected via USB or WiFi SSH, complementing the existing Corellium-based iOS analysis.
- SSH connectivity — connects to jailbroken devices over USB (via iproxy port forwarding) or WiFi using Paramiko SSH
- Frida instrumentation — spawns/attaches Frida on-device, with full hook support matching the existing Corellium flow
- Environment setup — installs AppSync Unified and Frida server on first run; supports both arm and arm64 devices
- Dynamic analysis — app file extraction, system log streaming (oslog), screenshot capture, process listing, and report generation
- SSH terminal — execute shell commands on the device over SSH
---------
Co-authored-by: CylentSec <[email protected]>
Co-authored-by: Oz <[email protected]>
215 lines
6.8 KiB
Python
215 lines
6.8 KiB
Python
"""Initialize on first run."""
|
|
import logging
|
|
import os
|
|
import random
|
|
import subprocess
|
|
import sys
|
|
import shutil
|
|
import threading
|
|
from hashlib import sha256
|
|
from pathlib import Path
|
|
from importlib import (
|
|
machinery,
|
|
util,
|
|
)
|
|
|
|
from mobsf.MobSF.tools_download import install_jadx
|
|
from mobsf.install.windows.setup import windows_config_local
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
VERSION = '4.5.0'
|
|
BANNER = r"""
|
|
__ __ _ ____ _____ _ _ ____
|
|
| \/ | ___ | |__/ ___|| ___|_ _| || | | ___|
|
|
| |\/| |/ _ \| '_ \___ \| |_ \ \ / / || |_ |___ \
|
|
| | | | (_) | |_) |__) | _| \ V /|__ _| ___) |
|
|
|_| |_|\___/|_.__/____/|_| \_/ |_|(_)____/
|
|
""" # noqa: W291
|
|
# ASCII Font: Standard
|
|
|
|
|
|
def first_run(secret_file, base_dir, mobsf_home):
|
|
# Based on https://gist.github.com/ndarville/3452907#file-secret-key-gen-py
|
|
base_dir = Path(base_dir)
|
|
mobsf_home = Path(mobsf_home)
|
|
secret_file = Path(secret_file)
|
|
if os.getenv('MOBSF_SECRET_KEY'):
|
|
secret_key = os.environ['MOBSF_SECRET_KEY']
|
|
elif secret_file.exists() and secret_file.is_file():
|
|
secret_key = secret_file.read_text().strip()
|
|
else:
|
|
try:
|
|
secret_key = get_random()
|
|
secret_file.write_text(secret_key)
|
|
except IOError:
|
|
raise Exception('Secret file generation failed' % secret_file)
|
|
# Run Once
|
|
make_migrations(base_dir)
|
|
migrate(base_dir)
|
|
# Install JADX
|
|
thread = threading.Thread(
|
|
target=install_jadx,
|
|
name='install_jadx',
|
|
args=(mobsf_home.as_posix(),))
|
|
thread.start()
|
|
# Windows Setup
|
|
windows_config_local(mobsf_home.as_posix())
|
|
return secret_key
|
|
|
|
|
|
def create_user_conf(mobsf_home, base_dir):
|
|
try:
|
|
config_path = mobsf_home / 'config.py'
|
|
if not config_path.exists():
|
|
sample_conf = base_dir / 'MobSF' / 'settings.py'
|
|
dat = sample_conf.read_text().splitlines()
|
|
config = []
|
|
add = False
|
|
for line in dat:
|
|
if '^CONFIG-START^' in line:
|
|
add = True
|
|
if '^CONFIG-END^' in line:
|
|
break
|
|
if add:
|
|
config.append(line.lstrip())
|
|
config.pop(0)
|
|
conf_str = '\n'.join(config)
|
|
config_path.write_text(conf_str)
|
|
except Exception:
|
|
logger.exception('Cannot create config file')
|
|
|
|
|
|
def django_operation(cmds, base_dir):
|
|
"""Generic Function for Djano operations."""
|
|
manage = base_dir.parent / 'manage.py'
|
|
if manage.exists() and manage.is_file():
|
|
# Bail out for package
|
|
return
|
|
print(manage)
|
|
args = [sys.executable, manage.as_posix()]
|
|
args.extend(cmds)
|
|
subprocess.call(args)
|
|
|
|
|
|
def make_migrations(base_dir):
|
|
"""Create Database Migrations."""
|
|
try:
|
|
django_operation(['makemigrations'], base_dir)
|
|
django_operation(['makemigrations', 'StaticAnalyzer'], base_dir)
|
|
except Exception:
|
|
logger.exception('Cannot Make Migrations')
|
|
|
|
|
|
def migrate(base_dir):
|
|
"""Migrate Database."""
|
|
try:
|
|
django_operation(['migrate'], base_dir)
|
|
django_operation(['migrate', '--run-syncdb'], base_dir)
|
|
django_operation(['create_roles'], base_dir)
|
|
except Exception:
|
|
logger.exception('Cannot Migrate')
|
|
|
|
|
|
def get_random():
|
|
choice = 'abcdefghijklmnopqrstuvwxyz0123456789!@#$%^&*(-_=+)'
|
|
return ''.join([random.SystemRandom().choice(choice) for i in range(50)])
|
|
|
|
|
|
def get_mobsf_home(use_home, base_dir):
|
|
try:
|
|
base_dir = Path(base_dir)
|
|
mobsf_home = ''
|
|
if use_home:
|
|
mobsf_home = Path.home() / '.MobSF'
|
|
custom_home = os.getenv('MOBSF_HOME_DIR')
|
|
if custom_home:
|
|
p = Path(custom_home)
|
|
if p.exists() and p.is_absolute() and p.is_dir():
|
|
mobsf_home = p
|
|
# MobSF Home Directory
|
|
if not mobsf_home.exists():
|
|
mobsf_home.mkdir(parents=True, exist_ok=True)
|
|
create_user_conf(mobsf_home, base_dir)
|
|
else:
|
|
mobsf_home = base_dir
|
|
# Download Directory
|
|
dwd_dir = mobsf_home / 'downloads'
|
|
dwd_dir.mkdir(parents=True, exist_ok=True)
|
|
# Screenshot Directory
|
|
screen_dir = mobsf_home / 'screen'
|
|
screen_dir.mkdir(parents=True, exist_ok=True)
|
|
# Upload Directory
|
|
upload_dir = mobsf_home / 'uploads'
|
|
upload_dir.mkdir(parents=True, exist_ok=True)
|
|
# Downloaded tools
|
|
downloaded_tools_dir = mobsf_home / 'tools'
|
|
downloaded_tools_dir.mkdir(parents=True, exist_ok=True)
|
|
# Signatures Directory
|
|
sig_dir = mobsf_home / 'signatures'
|
|
sig_dir.mkdir(parents=True, exist_ok=True)
|
|
if use_home:
|
|
src = Path(base_dir) / 'signatures'
|
|
try:
|
|
shutil.copytree(src, sig_dir, dirs_exist_ok=True)
|
|
except Exception:
|
|
pass
|
|
return mobsf_home.as_posix()
|
|
except Exception:
|
|
logger.exception('Creating MobSF Home Directory')
|
|
|
|
|
|
def get_mobsf_version():
|
|
return BANNER, VERSION, f'v{VERSION}'
|
|
|
|
|
|
def load_source(modname, filename):
|
|
loader = machinery.SourceFileLoader(modname, filename)
|
|
spec = util.spec_from_file_location(modname, filename, loader=loader)
|
|
module = util.module_from_spec(spec)
|
|
loader.exec_module(module)
|
|
return module
|
|
|
|
|
|
def get_docker_secret_by_file(secret_key):
|
|
try:
|
|
secret_path = os.environ.get(secret_key)
|
|
path = Path(secret_path)
|
|
if path.exists() and path.is_file():
|
|
return path.read_text().strip()
|
|
except Exception:
|
|
logger.exception('Cannot read secret from %s', secret_path)
|
|
raise Exception('Cannot read secret from file')
|
|
|
|
|
|
def get_secret_from_file_or_env(env_secret_key):
|
|
docker_secret_key = f'{env_secret_key}_FILE'
|
|
if os.environ.get(docker_secret_key):
|
|
return get_docker_secret_by_file(docker_secret_key)
|
|
else:
|
|
return os.environ[env_secret_key]
|
|
|
|
|
|
def api_key(home_dir):
|
|
"""Print REST API Key."""
|
|
# Form Docker Secrets
|
|
if os.environ.get('MOBSF_API_KEY_FILE'):
|
|
logger.info('\nAPI Key read from docker secrets')
|
|
try:
|
|
return get_docker_secret_by_file('MOBSF_API_KEY_FILE')
|
|
except Exception:
|
|
logger.exception('Cannot read API Key from docker secrets')
|
|
# From Environment Variable
|
|
if os.environ.get('MOBSF_API_KEY'):
|
|
logger.info('\nAPI Key read from environment variable')
|
|
return os.environ['MOBSF_API_KEY']
|
|
home_dir = Path(home_dir)
|
|
secret_file = home_dir / 'secret'
|
|
if secret_file.exists() and secret_file.is_file():
|
|
try:
|
|
_api_key = secret_file.read_bytes().strip()
|
|
return sha256(_api_key).hexdigest()
|
|
except Exception:
|
|
logger.exception('Cannot Read API Key')
|
|
return None
|