Skip to content

Commit

Permalink
Android Dynamic Analysis Spawn, Inject and Attach
Browse files Browse the repository at this point in the history
  • Loading branch information
ajinabraham committed Dec 4, 2023
1 parent 13f2d92 commit 1b4721f
Show file tree
Hide file tree
Showing 8 changed files with 226 additions and 72 deletions.
24 changes: 13 additions & 11 deletions mobsf/DynamicAnalyzer/views/android/environment.py
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,7 @@ def connect_n_mount(self):
if not self.identifier:
return False
self.adb_command(['kill-server'])
self.adb_command(['start-server'])
self.adb_command(['start-server'], False, True)
logger.info('ADB Restarted')
self.wait(2)
logger.info('Connecting to Android %s', self.identifier)
Expand Down Expand Up @@ -309,18 +309,18 @@ def screen_shot(self, outfile):
"""Take Screenshot."""
self.adb_command(['screencap',
'-p',
'/data/local/screen.png'], True)
'/data/local/screen.png'], True, True)
self.adb_command(['pull',
'/data/local/screen.png',
outfile])
outfile], False, True)

def screen_stream(self):
"""Screen Stream."""
self.adb_command(['screencap',
'-p',
'/data/local/stream.png'],
True)
out = self.adb_command(['cat', '/data/local/stream.png'], True)
True, True)
out = self.adb_command(['cat', '/data/local/stream.png'], True, True)
if out:
return b64encode(out).decode('utf-8')
return ''
Expand Down Expand Up @@ -349,18 +349,20 @@ def android_component(self, bin_hash, comp):
def get_environment(self):
"""Identify the environment."""
out = self.adb_command(['getprop',
'ro.boot.serialno'], True)
'ro.boot.serialno'], True, False)
out += self.adb_command(['getprop',
'ro.serialno'], True)
'ro.serialno'], True, False)
out += self.adb_command(['getprop',
'ro.build.user'], True)
'ro.build.user'], True, False)
out += self.adb_command(['getprop',
'ro.manufacturer.geny-def'], True)
'ro.manufacturer.geny-def'],
True, False)
out += self.adb_command(['getprop',
'ro.product.manufacturer.geny-def'], True)
'ro.product.manufacturer.geny-def'],
True, False)
ver = self.adb_command(['getprop',
'ro.genymotion.version'],
True).decode('utf-8', 'ignore')
True, False).decode('utf-8', 'ignore')
if b'EMULATOR' in out:
logger.info('Found Android Studio Emulator')
return 'emulator'
Expand Down
69 changes: 58 additions & 11 deletions mobsf/DynamicAnalyzer/views/android/frida_core.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
)

logger = logging.getLogger(__name__)
_FPID = None


class Frida:
Expand Down Expand Up @@ -118,22 +119,26 @@ def frida_response(self, message, data):
else:
logger.error('[Frida] %s', message)

def connect(self):
"""Connect to Frida Server."""
session = None
device = None
def spawn(self):
"""Frida Spawn."""
global _FPID
try:
env = Environment()
self.clean_up()
env.run_frida_server()
device = frida.get_device(get_device(), settings.FRIDA_TIMEOUT)
pid = device.spawn([self.package])
device = frida.get_device(
get_device(),
settings.FRIDA_TIMEOUT)
logger.info('Spawning %s', self.package)
session = device.attach(pid)
time.sleep(2)
_FPID = device.spawn([self.package])
device.resume(_FPID)
time.sleep(1)
except frida.NotSupportedError:
logger.exception('Not Supported Error')
return
except frida.ServerNotRunningError:
logger.warning('Frida server is not running')
self.connect()
self.spawn()
except frida.TimedOutError:
logger.error('Timed out while waiting for device to appear')
except (frida.ProcessNotFoundError,
Expand All @@ -142,22 +147,64 @@ def connect(self):
pass
except Exception:
logger.exception('Error Connecting to Frida')

def session(self, pid, package):
"""Use existing session to inject frida scripts."""
global _FPID
try:
if session:
try:
device = frida.get_device(
get_device(),
settings.FRIDA_TIMEOUT)
if pid and package:
_FPID = pid
self.package = package
session = device.attach(_FPID)
time.sleep(2)
except frida.NotSupportedError:
logger.exception('Not Supported Error')
return
except Exception:
logger.warning('Cannot attach to pid, spawning again')
self.spawn()
session = device.attach(_FPID)
time.sleep(2)
if session and device and _FPID:
script = session.create_script(self.get_script())
script.on('message', self.frida_response)
script.load()
device.resume(pid)
sys.stdin.read()
script.unload()
session.detach()
except frida.NotSupportedError:
logger.exception('Not Supported Error')
except (frida.ProcessNotFoundError,
frida.TransportError,
frida.InvalidOperationError):
pass
except Exception:
logger.exception('Error Connecting to Frida')

def ps(self):
"""Get running process pid."""
ps_dict = []
try:
device = frida.get_device(
get_device(),
settings.FRIDA_TIMEOUT)
processes = device.enumerate_applications(scope='minimal')
if device and processes:
for process in processes:
if process.pid != 0:
ps_dict.append({
'pid': process.pid,
'name': process.name,
'identifier': process.identifier,
})
except Exception:
logger.exception('Failed to enumerate running applications')
return ps_dict

def clean_up(self):
if is_file_exists(self.api_mon):
os.remove(self.api_mon)
Expand Down
44 changes: 32 additions & 12 deletions mobsf/DynamicAnalyzer/views/android/tests_frida.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
import re
import json
from pathlib import Path
import threading
from threading import Thread
import logging

from django.shortcuts import render
Expand All @@ -25,6 +25,7 @@
is_file_exists,
is_md5,
print_n_send_error_response,
strict_package_check,
)

logger = logging.getLogger(__name__)
Expand Down Expand Up @@ -58,9 +59,13 @@ def get_runtime_dependencies(request, api=False):
@require_http_methods(['POST'])
def instrument(request, api=False):
"""Instrument app with frida."""
data = {}
data = {
'status': 'failed',
'message': 'Failed to instrument app'}
try:
logger.info('Starting Instrumentation')
action = request.POST.get('frida_action', 'spawn')
pid = request.POST.get('pid')
new_pkg = request.POST.get('new_package')
md5_hash = request.POST['hash']
default_hooks = request.POST['default_hooks']
auxiliary_hooks = request.POST['auxiliary_hooks']
Expand All @@ -76,23 +81,39 @@ def instrument(request, api=False):
cls_trace = request.POST.get('class_trace')
if cls_trace:
extras['class_trace'] = cls_trace.strip()

if (is_attack_pattern(default_hooks)
or is_attack_pattern(auxiliary_hooks)
or not is_md5(md5_hash)):
or not is_md5(md5_hash)
or (new_pkg and not strict_package_check(new_pkg))):
return invalid_params(api)
package = get_package_name(md5_hash)
if not package:
if not package and not new_pkg:
return invalid_params(api)
frida_obj = Frida(md5_hash,
package,
default_hooks.split(','),
auxiliary_hooks.split(','),
extras,
code)
trd = threading.Thread(target=frida_obj.connect)
trd.daemon = True
trd.start()
data = {'status': 'ok'}
if action == 'spawn':
logger.info('Starting Instrumentation')
frida_obj.spawn()
elif action == 'ps':
logger.info('Enumerating running applications')
data['message'] = frida_obj.ps()
if action in ('spawn', 'session'):
if pid and pid.isdigit():
# Attach to a different pid/bundle id
args = (int(pid), new_pkg)
logger.info('Attaching to %s [PID: %s]', new_pkg, pid)
else:
# Injecting to existing session/spawn
if action == 'session':
logger.info('Injecting to existing frida session')
args = (None, None)
Thread(target=frida_obj.session, args=args, daemon=True).start()
data['status'] = 'ok'
except Exception as exp:
logger.exception('Instrumentation failed')
data = {'status': 'failed', 'message': str(exp)}
Expand Down Expand Up @@ -271,9 +292,8 @@ def get_dependencies(package, checksum):
location = Path(frd.deps)
if location.exists():
location.write_text('')
trd = threading.Thread(target=frd.connect)
trd.daemon = True
trd.start()
frd.spawn()
Thread(target=frd.session, args=(None, None), daemon=True).start()


def dependency_analysis(package, app_dir):
Expand Down
7 changes: 3 additions & 4 deletions mobsf/DynamicAnalyzer/views/android/tests_tls.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
"""Security tests on data in transit."""
import re
import logging
import threading
from threading import Thread
from json import dump
from pathlib import Path

Expand Down Expand Up @@ -90,9 +90,8 @@ def run_tls_tests(request, md5_hash, env, package, test_pkg, duration):
None,
None,
)
trd = threading.Thread(target=frd.connect)
trd.daemon = True
trd.start()
frd.spawn()
Thread(target=frd.session, args=(None, None), daemon=True).start()
env.wait(duration)
stop_httptools(get_http_tools_url(request))
traffic = get_traffic(test_pkg)
Expand Down
6 changes: 2 additions & 4 deletions mobsf/DynamicAnalyzer/views/ios/frida_core.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,8 +38,7 @@ def __init__(
dump,
auxiliary,
extras,
code,
action):
code):
self.ssh_connection_string = ssh_string
self.app_container = None
self.hash = app_hash
Expand All @@ -49,7 +48,6 @@ def __init__(
self.auxiliary = auxiliary
self.extras = extras
self.code = code
self.action = action
self.frida_dir = Path(settings.TOOLS_DIR) / 'frida_scripts' / 'ios'
self.ipa_dir = Path(settings.UPLD_DIR) / self.hash
self.frida_log = self.ipa_dir / 'mobsf_frida_out.txt'
Expand Down Expand Up @@ -157,8 +155,8 @@ def spawn(self):
"""Connect to Frida Server and spawn the app."""
global _PID
try:
self.clean_up()
try:
self.clean_up()
_PID = frida.get_remote_device().spawn([self.bundle_id])
except frida.NotSupportedError:
logger.exception('Not Supported Error')
Expand Down
5 changes: 2 additions & 3 deletions mobsf/DynamicAnalyzer/views/ios/tests_frida.py
Original file line number Diff line number Diff line change
Expand Up @@ -85,8 +85,7 @@ def ios_instrument(request, api=False):
dump_hooks.split(','),
auxiliary_hooks.split(','),
extras,
code,
action)
code)
if action == 'spawn':
logger.info('Starting Instrumentation')
frida_obj.spawn()
Expand All @@ -107,5 +106,5 @@ def ios_instrument(request, api=False):
data['status'] = OK
except Exception as exp:
logger.exception('Frida Instrumentation failed')
data = {'status': 'failed', 'message': str(exp)}
data['message'] = str(exp)
return send_response(data, api)
4 changes: 3 additions & 1 deletion mobsf/MobSF/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -187,7 +187,9 @@
re_path(r'^collect_logs/$', tests_common.collect_logs),
re_path(r'^tls_tests/$', tests_common.tls_tests),
# Frida
re_path(r'^frida_instrument/$', tests_frida.instrument),
re_path(r'^frida_instrument/$',
tests_frida.instrument,
name='android_instrument'),
re_path(r'^live_api/$',
tests_frida.live_api,
name='live_api'),
Expand Down
Loading

0 comments on commit 1b4721f

Please sign in to comment.