mirror of
https://github.com/MobSF/Mobile-Security-Framework-MobSF.git
synced 2026-10-11 22:50:09 +00:00
Dynamic Analysis with Genymotion. Supports Android 4.1 -9.0 Static Analyzer Threaded baksmali Improved Recent Scan Browser PDF Generation Dynamic Analyzer Restructuring Dynamic Analyzer Code QA and Modularization Automatic MobSFy with Xposed and Frida Full pass on all versions of android with basic features Streaming logcat Live API Monitor Better SQlite DB View In line frida scripting + loading from frida code share basic frida env, ssl unpin, root check, debugger detection bypass. Custom Frida Scripts and integration Frida Log Viewer Frida scripts loader Frida custom user script loader
405 lines
15 KiB
Python
405 lines
15 KiB
Python
# -*- coding: utf_8 -*-
|
|
"""Dynamic Analyzer Helpers."""
|
|
import logging
|
|
import os
|
|
import re
|
|
import shutil
|
|
import subprocess
|
|
import time
|
|
import threading
|
|
|
|
from django.conf import settings
|
|
|
|
from DynamicAnalyzer.tools.webproxy import (get_ca_dir,
|
|
start_proxy,
|
|
stop_httptools)
|
|
|
|
from StaticAnalyzer.models import StaticAnalyzerAndroid
|
|
|
|
from MobSF.utils import (get_adb,
|
|
get_device,
|
|
get_proxy_ip,
|
|
python_list)
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
|
|
class Environment:
|
|
|
|
def __init__(self, identifier=None):
|
|
if identifier:
|
|
self.identifier = identifier
|
|
else:
|
|
self.identifier = get_device()
|
|
self.tools_dir = settings.TOOLS_DIR
|
|
|
|
def wait(self, sec):
|
|
"""Wait in Seconds."""
|
|
logger.info('Waiting for %s seconds...', str(sec))
|
|
time.sleep(sec)
|
|
|
|
def connect_n_mount(self):
|
|
"""Test ADB Connection."""
|
|
self.adb_command(['kill-server'])
|
|
self.adb_command(['start-server'])
|
|
logger.info('ADB Restarted')
|
|
self.wait(2)
|
|
logger.info('Connecting to Android %s', self.identifier)
|
|
out = subprocess.check_output([get_adb(), 'connect', self.identifier])
|
|
if b'unable to connect' in out or b'failed to connect' in out:
|
|
logger.error('%s', out.decode('utf-8').replace('\n', ''))
|
|
return False
|
|
else:
|
|
logger.info('Remounting /system')
|
|
self.adb_command(['mount', '-o',
|
|
'rw,remount', '/system'], True)
|
|
return True
|
|
|
|
def adb_command(self, cmd_list, shell=False, silent=False):
|
|
"""ADB Command wrapper."""
|
|
args = [get_adb(),
|
|
'-s',
|
|
self.identifier]
|
|
if shell:
|
|
args += ['shell']
|
|
args += cmd_list
|
|
|
|
try:
|
|
result = subprocess.check_output(args)
|
|
return result
|
|
except Exception:
|
|
if not silent:
|
|
logger.exception('Error Running ADB Command')
|
|
return None
|
|
|
|
def dz_cleanup(self, bin_hash):
|
|
"""Clean up before Dynamic Analysis."""
|
|
# Delete ScreenStream Cache
|
|
screen_file = os.path.join(settings.SCREEN_DIR, 'screen.png')
|
|
if os.path.exists(screen_file):
|
|
os.remove(screen_file)
|
|
# Delete Contents of Screenshot Dir
|
|
screen_dir = os.path.join(
|
|
settings.UPLD_DIR, bin_hash + '/screenshots-apk/')
|
|
if os.path.isdir(screen_dir):
|
|
shutil.rmtree(screen_dir)
|
|
else:
|
|
os.makedirs(screen_dir)
|
|
|
|
def configure_proxy(self, project):
|
|
"""HTTPS Proxy."""
|
|
proxy_port = settings.PROXY_PORT
|
|
logger.info('Starting HTTPs Proxy on %s', proxy_port)
|
|
stop_httptools(proxy_port)
|
|
start_proxy(proxy_port, project)
|
|
|
|
def install_mobsf_ca(self, action):
|
|
"""Install or Remove MobSF Root CA."""
|
|
ca_file = os.path.join('/system/etc/security/cacerts/',
|
|
settings.ROOT_CA)
|
|
if action == 'install':
|
|
logger.info('Installing MobSF RootCA')
|
|
self.adb_command(['push', get_ca_dir(), ca_file])
|
|
self.adb_command(['chmod',
|
|
'644',
|
|
ca_file], True)
|
|
elif action == 'remove':
|
|
logger.info('Removing MobSF RootCA')
|
|
self.adb_command(['rm', ca_file], True)
|
|
|
|
def set_global_proxy(self, version):
|
|
"""Set Global Proxy on device."""
|
|
# Android 4.4+ supported
|
|
proxy_ip = None
|
|
proxy_port = settings.PROXY_PORT
|
|
if version < 5:
|
|
proxy_ip = get_proxy_ip(self.identifier)
|
|
else:
|
|
proxy_ip = '127.0.0.1'
|
|
if proxy_ip:
|
|
if version < 4.4:
|
|
logger.warning('Please set Android VM proxy as %s:%s',
|
|
proxy_ip, proxy_port)
|
|
return
|
|
logger.info('Setting Global Proxy for Android VM')
|
|
self.adb_command(
|
|
['settings',
|
|
'put',
|
|
'global',
|
|
'http_proxy',
|
|
'{}:{}'.format(proxy_ip, proxy_port)], True)
|
|
|
|
def enable_adb_reverse_tcp(self, version):
|
|
"""Enable ADB Reverse TCP for Proxy."""
|
|
# Androd 5+ supported
|
|
if not version >= 5:
|
|
return
|
|
proxy_port = settings.PROXY_PORT
|
|
logger.info('Enabling ADB Reverse TCP on %s', proxy_port)
|
|
tcp = 'tcp:{}'.format(proxy_port)
|
|
try:
|
|
proc = subprocess.Popen([get_adb(),
|
|
'-s', self.identifier,
|
|
'reverse', tcp, tcp],
|
|
stdout=subprocess.PIPE,
|
|
stderr=subprocess.PIPE)
|
|
_, stderr = proc.communicate()
|
|
if b'error: closed' in stderr:
|
|
logger.warning('ADB Reverse TCP works only on'
|
|
' Android 5.0 and above. Please '
|
|
'configure a reachable IP Address'
|
|
' in Android proxy settings.')
|
|
elif stderr:
|
|
logger.error(stderr.decode('utf-8').replace('\n', ''))
|
|
except Exception:
|
|
logger.exception('Enabling ADB Reverse TCP')
|
|
|
|
def start_clipmon(self):
|
|
"""Start Clipboard Monitoring."""
|
|
logger.info('Starting Clipboard Monitor')
|
|
args = ['am', 'startservice',
|
|
'opensecurity.clipdump/.ClipDumper']
|
|
self.adb_command(args, True)
|
|
|
|
def get_screen_res(self):
|
|
"""Get Screen Resolution of Android Instance."""
|
|
logger.info('Getting screen resolution')
|
|
try:
|
|
resp = self.adb_command(['dumpsys', 'window'], True)
|
|
scn_rgx = re.compile(r'mUnrestrictedScreen=\(0,0\) .*')
|
|
scn_rgx2 = re.compile(r'mUnrestricted=\[0,0\]\[.*\]')
|
|
match = scn_rgx.search(resp.decode('utf-8'))
|
|
if match:
|
|
screen_res = match.group().split(' ')[1]
|
|
width, height = screen_res.split('x', 1)
|
|
return width, height
|
|
match = scn_rgx2.search(resp.decode('utf-8'))
|
|
if match:
|
|
res = match.group().split('][')[1].replace(']', '')
|
|
width, height = res.split(',', 1)
|
|
return width, height
|
|
else:
|
|
logger.error('Error getting screen resolution')
|
|
except Exception:
|
|
logger.exception('Getting screen resolution')
|
|
return '1440', '2560'
|
|
|
|
def screen_shot(self, outfile):
|
|
"""Take Screenshot."""
|
|
self.adb_command(['screencap',
|
|
'-p',
|
|
'/data/local/screen.png'], True)
|
|
self.adb_command(['pull',
|
|
'/data/local/screen.png',
|
|
outfile])
|
|
|
|
def screen_stream(self):
|
|
"""Screen Stream."""
|
|
self.adb_command(['screencap',
|
|
'-p',
|
|
'/data/local/stream.png'],
|
|
True)
|
|
self.adb_command(['pull',
|
|
'/data/local/stream.png',
|
|
'{}screen.png'.format(settings.SCREEN_DIR)])
|
|
|
|
def android_component(self, bin_hash, comp):
|
|
"""Get APK Components."""
|
|
anddb = StaticAnalyzerAndroid.objects.filter(MD5=bin_hash)
|
|
resp = []
|
|
if comp == 'activities':
|
|
resp = python_list(anddb[0].ACTIVITIES)
|
|
elif comp == 'receivers':
|
|
resp = python_list(anddb[0].RECEIVERS)
|
|
elif comp == 'providers':
|
|
resp = python_list(anddb[0].PROVIDERS)
|
|
elif comp == 'services':
|
|
resp = python_list(anddb[0].SERVICES)
|
|
elif comp == 'libraries':
|
|
resp = python_list(anddb[0].LIBRARIES)
|
|
elif comp == 'exported_activities':
|
|
resp = python_list(anddb[0].EXPORTED_ACT)
|
|
return '\n'.join(resp)
|
|
|
|
def get_android_version(self):
|
|
"""Get Android version."""
|
|
out = self.adb_command(['getprop',
|
|
'ro.build.version.release'], True)
|
|
and_version = out.decode('utf-8').rstrip()
|
|
if and_version.count('.') > 1:
|
|
and_version = and_version.rsplit('.', 1)[0]
|
|
if and_version.count('.') > 1:
|
|
and_version = and_version.split('.', 1)[0]
|
|
return float(and_version)
|
|
|
|
def get_android_arch(self):
|
|
"""Get Android Architecture."""
|
|
out = self.adb_command(['getprop',
|
|
'ro.product.cpu.abi'], True)
|
|
return out.decode('utf-8').rstrip()
|
|
|
|
def launch_n_capture(self, package, activity, outfile):
|
|
"""Launch and Capture Activity."""
|
|
self.adb_command(['am',
|
|
'start',
|
|
'-n',
|
|
package + '/' + activity], True)
|
|
self.wait(3)
|
|
self.screen_shot(outfile)
|
|
logger.info('Activity screenshot captured')
|
|
logger.info('Stopping app')
|
|
self.adb_command(['am', 'force-stop', package], True)
|
|
|
|
def is_mobsfyied(self, android_version):
|
|
"""Check is Device is MobSFyed."""
|
|
logger.info('Environment MobSFyed Check')
|
|
if android_version < 5:
|
|
agent_file = '.mobsf-x'
|
|
agent_str = b'MobSF-Xposed'
|
|
else:
|
|
agent_file = '.mobsf-f'
|
|
agent_str = b'MobSF-Frida'
|
|
try:
|
|
out = subprocess.check_output(
|
|
[get_adb(),
|
|
'-s', self.identifier,
|
|
'shell',
|
|
'cat',
|
|
'/system/' + agent_file])
|
|
if agent_str not in out:
|
|
return False
|
|
except Exception:
|
|
return False
|
|
return True
|
|
|
|
def mobsfy_init(self):
|
|
"""Init MobSFy."""
|
|
version = self.get_android_version()
|
|
logger.info('Android Version identified as %s', version)
|
|
try:
|
|
if version < 5:
|
|
self.xposed_setup(version)
|
|
self.mobsf_agents_setup('xposed')
|
|
else:
|
|
self.frida_setup()
|
|
self.mobsf_agents_setup('frida')
|
|
logger.info('MobSFying Completed!')
|
|
return version
|
|
except Exception:
|
|
logger.exception('Failed to MobSFy Android Instance')
|
|
return False
|
|
|
|
def mobsf_agents_setup(self, agent):
|
|
"""Setup MobSF agents."""
|
|
# Install MITM RootCA
|
|
self.install_mobsf_ca('install')
|
|
# Install MobSF Agents
|
|
mobsf_agents = 'onDevice/mobsf_agents/'
|
|
clip_dump = os.path.join(self.tools_dir,
|
|
mobsf_agents,
|
|
'ClipDump.apk')
|
|
logger.info('Installing MobSF Clipboard Dumper')
|
|
self.adb_command(['install', '-r', clip_dump])
|
|
if agent == 'frida':
|
|
agent_file = '.mobsf-f'
|
|
else:
|
|
agent_file = '.mobsf-x'
|
|
mobsf_env = os.path.join(self.tools_dir,
|
|
mobsf_agents,
|
|
agent_file)
|
|
self.adb_command(['push', mobsf_env, '/system/' + agent_file])
|
|
|
|
def xposed_setup(self, android_version):
|
|
"""Setup Xposed."""
|
|
xposed_dir = 'onDevice/xposed/'
|
|
xposed_modules = xposed_dir + 'modules/'
|
|
if android_version < 5:
|
|
logger.info('Installing Xposed for Kitkat and below')
|
|
xposed_apk = os.path.join(self.tools_dir,
|
|
xposed_dir,
|
|
'Xposed.apk')
|
|
hooks = os.path.join(self.tools_dir,
|
|
xposed_modules,
|
|
'hooks.json')
|
|
droidmon = os.path.join(self.tools_dir,
|
|
xposed_modules,
|
|
'Droidmon.apk')
|
|
logger.info('Installing Droidmon API Analyzer')
|
|
self.adb_command(['install', '-r', droidmon])
|
|
logger.info('Copying Droidmon hooks config')
|
|
self.adb_command(['push', hooks, '/data/local/tmp/'])
|
|
else:
|
|
logger.info('Installing Xposed for Lollipop and above')
|
|
xposed_apk = os.path.join(self.tools_dir,
|
|
xposed_dir,
|
|
'XposedInstaller_3.1.5.apk')
|
|
self.adb_command(['install', '-r', xposed_apk])
|
|
# Xposed Modules and Support Files
|
|
justrustme = os.path.join(self.tools_dir,
|
|
xposed_modules,
|
|
'JustTrustMe.apk')
|
|
rootcloak = os.path.join(self.tools_dir,
|
|
xposed_modules,
|
|
'RootCloak.apk')
|
|
proxyon = os.path.join(self.tools_dir,
|
|
xposed_modules,
|
|
'mobi.acpm.proxyon_v1_419b04.apk')
|
|
sslunpin = os.path.join(self.tools_dir,
|
|
xposed_modules,
|
|
'mobi.acpm.sslunpinning_v2_37f44f.apk')
|
|
bluepill = os.path.join(self.tools_dir,
|
|
xposed_modules,
|
|
'AndroidBluePill.apk')
|
|
logger.info('Installing JustTrustMe')
|
|
self.adb_command(['install', '-r', justrustme])
|
|
logger.info('Installing SSLUnpinning')
|
|
self.adb_command(['install', '-r', sslunpin])
|
|
logger.info('Installing ProxyOn')
|
|
self.adb_command(['install', '-r', proxyon])
|
|
logger.info('Installing RootCloak')
|
|
self.adb_command(['install', '-r', rootcloak])
|
|
logger.info('Installing Android BluePill')
|
|
self.adb_command(['install', '-r', bluepill])
|
|
logger.info('Launching Xposed Framework.')
|
|
xposed_installer = ('de.robv.android.xposed.installer/'
|
|
'de.robv.android.xposed.installer.'
|
|
'WelcomeActivity')
|
|
self.adb_command(['am', 'start', '-n',
|
|
xposed_installer], True)
|
|
|
|
def frida_setup(self):
|
|
"""Setup Frida."""
|
|
frida_dir = 'onDevice/frida/'
|
|
frida_bin = os.path.join(self.tools_dir,
|
|
frida_dir,
|
|
'frida-server-12.7.4-android-x86')
|
|
arch = self.get_android_arch()
|
|
logger.info('Android instance architecture identified as %s', arch)
|
|
if 'x86' not in arch:
|
|
logger.error('Make sure a Genymotion Android x86'
|
|
'instance is running')
|
|
return
|
|
logger.info('Copying frida server')
|
|
self.adb_command(['push', frida_bin, '/system/fd_server'])
|
|
self.adb_command(['chmod', '755', '/system/fd_server'], True)
|
|
|
|
def run_frida_server(self):
|
|
"""Start Frida Server."""
|
|
check = self.adb_command(['ps'], True)
|
|
if b'/system/fd_server' in check:
|
|
logger.info('Frida Server is already running')
|
|
return
|
|
|
|
def start_frida():
|
|
fnull = open(os.devnull, 'w')
|
|
argz = [get_adb(),
|
|
'-s',
|
|
self.identifier,
|
|
'shell',
|
|
'/system/fd_server']
|
|
subprocess.call(argz, stdout=fnull, stderr=subprocess.STDOUT)
|
|
trd = threading.Thread(target=start_frida)
|
|
trd.daemon = True
|
|
trd.start()
|
|
logger.info('Frida Server is running')
|