Skip to content

Commit

Permalink
HOTFIX: Dynamic Analysis Improvements Android & iOS (#2295)
Browse files Browse the repository at this point in the history
iOS Screencast, better swipe
Android Screencast to support touch, swipe and text input events
Android Frida Logs update
Android Improved Screencast
Android Frida spawn, inject and attach support
Added new Android Frida scripts
Replaced Clipdump with Frida script for clipboard monitoring
  • Loading branch information
ajinabraham authored Dec 4, 2023
1 parent 88904e8 commit 2e6c6c5
Show file tree
Hide file tree
Showing 47 changed files with 843 additions and 412 deletions.
27 changes: 0 additions & 27 deletions .pyup.yml

This file was deleted.

1 change: 0 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@ Made with ![Love](https://cloud.githubusercontent.com/assets/4301109/16754758/82
[![Docker Pulls](https://img.shields.io/docker/pulls/opensecurity/mobile-security-framework-mobsf?style=social)](https://hub.docker.com/r/opensecurity/mobile-security-framework-mobsf/)

[![MobSF tests](https://github.com/MobSF/Mobile-Security-Framework-MobSF/workflows/MobSF%20tests/badge.svg?branch=master)](https://github.com/MobSF/Mobile-Security-Framework-MobSF/actions)
[![Requirements Status](https://pyup.io/repos/github/MobSF/Mobile-Security-Framework-MobSF/shield.svg)](https://pyup.io/repos/github/MobSF/Mobile-Security-Framework-MobSF/)
[![Quality Gate Status](https://sonarcloud.io/api/project_badges/measure?project=MobSF_Mobile-Security-Framework-MobSF&metric=alert_status)](https://sonarcloud.io/dashboard?id=MobSF_Mobile-Security-Framework-MobSF)
![GitHub closed issues](https://img.shields.io/github/issues-closed/MobSF/Mobile-Security-Framework-MobSF)
[![CII Best Practices](https://bestpractices.coreinfrastructure.org/projects/6392/badge)](https://bestpractices.coreinfrastructure.org/projects/6392)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
// Based on https://github.com/sensepost/objection/blob/f8e78d8a29574c6dadd2b953a63207b45a19b1cf/objection/hooks/android/clipboard/monitor.js
var ActivityThread = Java.use('android.app.ActivityThread');
var ClipboardManager = Java.use('android.content.ClipboardManager');
var CLIPBOARD_SERVICE = 'clipboard';

var currentApplication = ActivityThread.currentApplication();
var context = currentApplication.getApplicationContext();

var clipboard_handle = context.getApplicationContext().getSystemService(CLIPBOARD_SERVICE);
var clipboard = Java.cast(clipboard_handle, ClipboardManager);

// Variable used for the current string data
var string_data;

function check_clipboard_data() {

Java.perform(function () {

var primary_clip = clipboard.getPrimaryClip();

// If we have managed to get the primary clipboard and there are
// items stored in it, process an update.
if (primary_clip != null && primary_clip.getItemCount() > 0) {

var data = primary_clip.getItemAt(0).coerceToText(context).toString();

// If the data is the same, just stop.
if (string_data == data) {
return;
}

// Update the data with the new string and report back.
string_data = data;
send('mobsf-android-clipboard:' + data);
}
});
}

// Poll every 5 seconds
setInterval(check_clipboard_data, 1000 * 5);
Original file line number Diff line number Diff line change
Expand Up @@ -130,12 +130,12 @@ Java.performNow(function () {
}
}
if (shouldModifyCommand) {
send("[RootDetection Bypass] ProcessBuilder " + cmd);
send("[RootDetection Bypass] ProcessBuilder " + JSON.stringify(cmd));
this.command.call(this, ["grep"]);
return this.start.call(this);
}
if (cmd.indexOf("su") != -1) {
send("[RootDetection Bypass] ProcessBuilder " + cmd);
send("[RootDetection Bypass] ProcessBuilder " + JSON.stringify(cmd));
this.command.call(this, ["justafakecommandthatcannotexistsusingthisshouldthowanexceptionwheneversuiscalled"]);
return this.start.call(this);
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
// Based on https://github.com/sensepost/objection/blob/f8e78d8a29574c6dadd2b953a63207b45a19b1cf/objection/hooks/android/filesystem/environment.js
var ActivityThread = Java.use('android.app.ActivityThread');

var currentApplication = ActivityThread.currentApplication();
var context = currentApplication.getApplicationContext();

var data = {

filesDirectory: context.getFilesDir().getAbsolutePath().toString(),
cacheDirectory: context.getCacheDir().getAbsolutePath().toString(),
externalCacheDirectory: context.getExternalCacheDir().getAbsolutePath().toString(),
codeCacheDirectory: 'getCodeCacheDir' in context ? context.getCodeCacheDir().getAbsolutePath().toString() : 'n/a',
obbDir: context.getObbDir().getAbsolutePath().toString(),
packageCodePath: context.getPackageCodePath().toString()
};


send(JSON.stringify(data, null, 2));
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
// https://github.com/sensepost/objection/blob/f8e78d8a29574c6dadd2b953a63207b45a19b1cf/objection/hooks/android/keystore/list.js
// Dump entries in the Android Keystore, together with a flag
// indicating if its a key or a certificate.
//
// Ref: https://developer.android.com/reference/java/security/KeyStore.html

var KeyStore = Java.use('java.security.KeyStore');
var entries = [];

// Prepare the AndroidKeyStore keystore provider and load it.
// Maybe at a later stage we should support adding other stores
// like from file or JKS.
var ks = KeyStore.getInstance('AndroidKeyStore');
ks.load(null, null);

// Get the aliases and loop through them. The aliases() method
// return an Enumeration<String> type.
var aliases = ks.aliases();

while (aliases.hasMoreElements()) {

var alias = aliases.nextElement();

entries.push({
'alias': alias.toString(),
'is_key': ks.isKeyEntry(alias),
'is_certificate': ks.isCertificateEntry(alias)
})
}


send(JSON.stringify(entries, null, 2));

// - Sample Java
//
// KeyStore ks = KeyStore.getInstance("AndroidKeyStore");
// ks.load(null);
// Enumeration<String> aliases = ks.aliases();
//
// while(aliases.hasMoreElements()) {
// Log.e("E", "Aliases = " + aliases.nextElement());
// }
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
var Build = Java.use('android.os.Build');

var ActivityThread = Java.use('android.app.ActivityThread');

var currentApplication = ActivityThread.currentApplication();
var context = currentApplication.getApplicationContext();

var data = {
application_name: context.getPackageName(),
model: Build.MODEL.value.toString(),
board: Build.BOARD.value.toString(),
brand: Build.BRAND.value.toString(),
device: Build.DEVICE.value.toString(),
host: Build.HOST.value.toString(),
id: Build.ID.value.toString(),
product: Build.PRODUCT.value.toString(),
user: Build.USER.value.toString(),
version: Java.androidVersion
}
send(JSON.stringify(data, null, 2));
24 changes: 15 additions & 9 deletions mobsf/DynamicAnalyzer/views/android/analysis.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,15 +28,21 @@ def run_analysis(apk_dir, md5_hash, package):
clipboard = []
# Collect Log data
data = get_log_data(apk_dir, package)
clip_tag = 'I/CLIPDUMP-INFO-LOG'
clip_tag2 = 'I CLIPDUMP-INFO-LOG'
# Collect Clipboard
for log_line in data['logcat']:
if clip_tag in log_line:
clipboard.append(log_line.replace(clip_tag, 'Process ID '))
if clip_tag2 in log_line:
log_line = log_line.split(clip_tag2)[1]
clipboard.append(log_line)
clip = Path(apk_dir) / 'mobsf_app_clipboard.txt'
if clip.exists():
clipboard = clip.read_text('utf-8', 'ignore').split('\n')
else:
# For Xposed
clip_tag = 'I/CLIPDUMP-INFO-LOG'
clip_tag2 = 'I CLIPDUMP-INFO-LOG'
# Collect Clipboard
for log_line in data['logcat']:
if clip_tag in log_line:
clipboard.append(
log_line.replace(clip_tag, 'Process ID '))
if clip_tag2 in log_line:
log_line = log_line.split(clip_tag2)[1]
clipboard.append(log_line)
urls, domains, emails = extract_urls_domains_emails(
data['traffic'].lower())
# Tar dump and fetch files
Expand Down
10 changes: 3 additions & 7 deletions mobsf/DynamicAnalyzer/views/android/dynamic_analyzer.py
Original file line number Diff line number Diff line change
Expand Up @@ -165,6 +165,8 @@ def dynamic_analyzer(request, checksum, api=False):
'Failed to MobSFy the instance',
api)
if version < 5:
# Start Clipboard monitor
env.start_clipmon()
xposed_first_run = True
if xposed_first_run:
msg = ('Have you MobSFyed the instance before'
Expand All @@ -182,10 +184,6 @@ def dynamic_analyzer(request, checksum, api=False):
env.enable_adb_reverse_tcp(version)
# Apply Global Proxy to device
env.set_global_proxy(version)
# Start Clipboard monitor
env.start_clipmon()
# Get Screen Resolution
screen_width, screen_height = env.get_screen_res()
if install == '1':
# Install APK
apk_path = Path(settings.UPLD_DIR) / checksum / f'{checksum}.apk'
Expand All @@ -203,9 +201,7 @@ def dynamic_analyzer(request, checksum, api=False):
msg,
api)
logger.info('Testing Environment is Ready!')
context = {'screen_width': screen_width,
'screen_height': screen_height,
'package': package,
context = {'package': package,
'hash': checksum,
'android_version': version,
'version': settings.MOBSF_VER,
Expand Down
70 changes: 25 additions & 45 deletions mobsf/DynamicAnalyzer/views/android/environment.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,12 @@
"""Dynamic Analyzer Helpers."""
import logging
import os
import re
import shutil
import subprocess
import tempfile
import threading
import time
from base64 import b64encode
from hashlib import md5

from django.conf import settings
Expand Down 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 @@ -305,47 +305,25 @@ def start_clipmon(self):
'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)
'/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)
self.adb_command(['pull',
'/data/local/stream.png',
'{}screen.png'.format(settings.SCREEN_DIR)])
True, True)
out = self.adb_command(['cat', '/data/local/stream.png'], True, True)
if out:
return b64encode(out).decode('utf-8')
return ''

def android_component(self, bin_hash, comp):
"""Get APK Components."""
Expand All @@ -371,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 All @@ -403,7 +383,8 @@ def get_environment(self):
def get_android_version(self):
"""Get Android version."""
out = self.adb_command(['getprop',
'ro.build.version.release'], True)
'ro.build.version.release'],
True, False)
and_version = out.decode('utf-8').rstrip()
if and_version.count('.') > 1:
and_version = and_version.rsplit('.', 1)[0]
Expand Down Expand Up @@ -581,13 +562,6 @@ def mobsf_agents_setup(self, agent):
create_ca()
# 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'
agent_str = self.frida_str
Expand All @@ -604,6 +578,12 @@ def xposed_setup(self, android_version):
"""Setup Xposed."""
xposed_dir = 'onDevice/xposed/'
xposed_modules = xposed_dir + 'modules/'
# Install MobSF Agents for Xposed
clip_dump_apk = os.path.join(self.tools_dir,
xposed_dir,
'ClipDump.apk')
logger.info('Installing MobSF Clipboard Dumper')
self.adb_command(['install', '-r', clip_dump_apk])
if android_version < 5:
logger.info('Installing Xposed for Kitkat and below')
xposed_apk = os.path.join(self.tools_dir,
Expand Down
Loading

0 comments on commit 2e6c6c5

Please sign in to comment.