diff --git a/mobsf/DynamicAnalyzer/tools/frida_scripts_ios/others/app-environment.js b/mobsf/DynamicAnalyzer/tools/frida_scripts_ios/others/app-environment.js
index 79e59ed192..2d9be8d5e1 100644
--- a/mobsf/DynamicAnalyzer/tools/frida_scripts_ios/others/app-environment.js
+++ b/mobsf/DynamicAnalyzer/tools/frida_scripts_ios/others/app-environment.js
@@ -18,37 +18,51 @@ function app_env_info() {
send('App Document Path: ' + getPath(9));
send('App Library Path: ' + libPath);
send('App Cache Path: ' + getPath(13));
-
- // try {
- // //Credit: https://github.com/iddoeldor/frida-snippets#find-ios-application-uuid
- // var mainBundleContainerPathIdentifier = "";
- // var bundleIdentifier = String(ObjC.classes.NSBundle.mainBundle().objectForInfoDictionaryKey_('CFBundleIdentifier'));
- // var path_prefix = "/var/mobile/Containers/Data/Application/";
- // var plist_metadata = "/.com.apple.mobile_container_manager.metadata.plist";
- // var folders = ObjC.classes.NSFileManager.defaultManager().contentsOfDirectoryAtPath_error_(path_prefix, NULL);
- // if (!folders){
- // send('Unable to identify App Container Path.')
- // } else {
- // for (var i = 0, l = folders.count(); i < l; i++) {
- // var uuid = folders.objectAtIndex_(i);
- // var metadata = path_prefix + uuid + plist_metadata;
- // var dict = ObjC.classes.NSMutableDictionary.alloc().initWithContentsOfFile_(metadata);
- // var enumerator = dict.keyEnumerator();
- // var key;
- // while ((key = enumerator.nextObject()) !== null) {
- // if (key == 'MCMMetadataIdentifier') {
- // var appId = String(dict.objectForKey_(key));
- // if (appId.indexOf(bundleIdentifier) != -1) {
- // mainBundleContainerPathIdentifier = uuid;
- // break;
- // }
- // }
- // }
- // }
- // send("App Container Path: /var/mobile/Containers/Data/Application/" + mainBundleContainerPathIdentifier + "/");
- // }
- // } catch (e){
- // }
-
}
app_env_info()
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+// try {
+// //Credit: https://github.com/iddoeldor/frida-snippets#find-ios-application-uuid
+// var mainBundleContainerPathIdentifier = "";
+// var bundleIdentifier = String(ObjC.classes.NSBundle.mainBundle().objectForInfoDictionaryKey_('CFBundleIdentifier'));
+// var path_prefix = "/var/mobile/Containers/Data/Application/";
+// var plist_metadata = "/.com.apple.mobile_container_manager.metadata.plist";
+// var folders = ObjC.classes.NSFileManager.defaultManager().contentsOfDirectoryAtPath_error_(path_prefix, NULL);
+// if (!folders){
+// send('Unable to identify App Container Path.')
+// } else {
+// for (var i = 0, l = folders.count(); i < l; i++) {
+// var uuid = folders.objectAtIndex_(i);
+// var metadata = path_prefix + uuid + plist_metadata;
+// var dict = ObjC.classes.NSMutableDictionary.alloc().initWithContentsOfFile_(metadata);
+// var enumerator = dict.keyEnumerator();
+// var key;
+// while ((key = enumerator.nextObject()) !== null) {
+// if (key == 'MCMMetadataIdentifier') {
+// var appId = String(dict.objectForKey_(key));
+// if (appId.indexOf(bundleIdentifier) != -1) {
+// mainBundleContainerPathIdentifier = uuid;
+// break;
+// }
+// }
+// }
+// }
+// send("App Container Path: /var/mobile/Containers/Data/Application/" + mainBundleContainerPathIdentifier + "/");
+// }
+// } catch (e){
+// }
+
+
diff --git a/mobsf/DynamicAnalyzer/views/android/environment.py b/mobsf/DynamicAnalyzer/views/android/environment.py
index c48b85a1ea..1840a3ca13 100644
--- a/mobsf/DynamicAnalyzer/views/android/environment.py
+++ b/mobsf/DynamicAnalyzer/views/android/environment.py
@@ -191,9 +191,8 @@ def configure_proxy(self, project, request):
"""HTTPS Proxy."""
self.install_mobsf_ca('install')
proxy_port = settings.PROXY_PORT
- logger.info('Starting HTTPs Proxy on %s', proxy_port)
- httptools_url = get_http_tools_url(request)
- stop_httptools(httptools_url)
+ logger.info('Starting HTTPS Proxy on %s', proxy_port)
+ stop_httptools(get_http_tools_url(request))
start_proxy(proxy_port, project)
def install_mobsf_ca(self, action):
diff --git a/mobsf/DynamicAnalyzer/views/android/tests_common.py b/mobsf/DynamicAnalyzer/views/android/tests_common.py
index e1bf12327e..2925de356f 100644
--- a/mobsf/DynamicAnalyzer/views/android/tests_common.py
+++ b/mobsf/DynamicAnalyzer/views/android/tests_common.py
@@ -145,8 +145,7 @@ def download_data(request, api=False):
'message': 'App details not found in database'}
return send_response(data, api)
apk_dir = os.path.join(settings.UPLD_DIR, md5_hash + '/')
- httptools_url = get_http_tools_url(request)
- stop_httptools(httptools_url)
+ stop_httptools(get_http_tools_url(request))
files_loc = '/data/local/'
logger.info('Archiving files created by app')
env.adb_command(['tar', '-cvf', files_loc + package + '.tar',
diff --git a/mobsf/DynamicAnalyzer/views/ios/analysis.py b/mobsf/DynamicAnalyzer/views/ios/analysis.py
index e171e16a13..c916bf0ab0 100644
--- a/mobsf/DynamicAnalyzer/views/ios/analysis.py
+++ b/mobsf/DynamicAnalyzer/views/ios/analysis.py
@@ -30,25 +30,29 @@ def get_screenshots(checksum, download_dir):
return screenshots
-def get_logs_data(app_dir):
+def get_logs_data(app_dir, bundle_id):
"""Get Data for analysis."""
data = []
dump_file = Path(app_dir) / 'mobsf_dump_file.txt'
fd_log_file = Path(app_dir) / 'mobsf_frida_out.txt'
+ flows = Path.home() / '.httptools' / 'flows'
+ web_file = flows / f'{bundle_id}.flow.txt'
if dump_file.exists():
data.append(dump_file.read_text('utf-8', 'ignore'))
if fd_log_file.exists():
data.append(fd_log_file.read_text('utf-8', 'ignore'))
+ if web_file.exists():
+ data.append(web_file.read_text('utf-8', 'ignore'))
return '\n'.join(data)
-def run_analysis(app_dir, checksum):
+def run_analysis(app_dir, bundle_id, checksum):
"""Run Dynamic File Analysis."""
analysis_result = {}
logger.info('Dynamic File Analysis')
domains = {}
# Collect Log data
- data = get_logs_data(app_dir)
+ data = get_logs_data(app_dir, bundle_id)
urls = re.findall(URL_REGEX, data.lower())
if urls:
urls = list(set(urls))
diff --git a/mobsf/DynamicAnalyzer/views/ios/corellium_apis.py b/mobsf/DynamicAnalyzer/views/ios/corellium_apis.py
index 073f443c53..20f9159b91 100644
--- a/mobsf/DynamicAnalyzer/views/ios/corellium_apis.py
+++ b/mobsf/DynamicAnalyzer/views/ios/corellium_apis.py
@@ -1,6 +1,7 @@
# -*- coding: utf_8 -*-
"""Corellium APIs."""
import logging
+import os
from copy import deepcopy
from socket import gethostname
@@ -72,9 +73,12 @@ def get_authorized_keys(self):
def add_authorized_key(self, key):
"""Add SSH public key to the Project."""
logger.info('Adding SSH public key to Corellium project')
+ extras = ''
+ if os.getenv('MOBSF_PLATFORM') == 'docker':
+ extras = ' - (docker)'
data = {
'kind': 'ssh',
- 'label': f'MobSF SSH Key - {gethostname()}',
+ 'label': f'MobSF SSH Key - {gethostname()}{extras}',
'key': key,
}
r = requests.post(
@@ -85,6 +89,15 @@ def add_authorized_key(self, key):
return r.json()['identifier']
return False
+ def delete_authorized_key(self, key_id):
+ """Delete SSH public key from the Project."""
+ r = requests.delete(
+ f'{self.api}/projects/{self.project_id}/keys/{key_id}',
+ headers=self.headers)
+ if r.status_code in SUCCESS_RESP:
+ return OK
+ return False
+
def get_instances(self):
"""Get Instances."""
logger.info('Getting iOS instances')
diff --git a/mobsf/DynamicAnalyzer/views/ios/corellium_instance.py b/mobsf/DynamicAnalyzer/views/ios/corellium_instance.py
index 6a9c793807..1b8d11e60e 100644
--- a/mobsf/DynamicAnalyzer/views/ios/corellium_instance.py
+++ b/mobsf/DynamicAnalyzer/views/ios/corellium_instance.py
@@ -3,6 +3,7 @@
import logging
import re
import os
+import shutil
import time
from base64 import b64encode
from pathlib import Path
@@ -25,6 +26,10 @@
strict_package_check,
)
from mobsf.DynamicAnalyzer.forms import UploadFileForm
+from mobsf.DynamicAnalyzer.tools.webproxy import (
+ get_http_tools_url,
+ stop_httptools,
+)
from mobsf.DynamicAnalyzer.views.common.shared import (
invalid_params,
send_response,
@@ -707,26 +712,35 @@ def download_app_data(ci, checksum):
@require_http_methods(['POST'])
-def download_data(request, checksum, api=False):
+def download_data(request, bundle_id, api=False):
"""Download Application Data from Device."""
logger.info('Downloading application data')
data = {
'status': 'failed',
'message': 'Failed to Download application data'}
try:
- if not is_md5(checksum):
- # Additional Check for REST API
- data['message'] = 'Invalid Hash'
- return send_response(data, api)
instance_id = request.POST['instance_id']
failed = common_check(instance_id)
if failed:
return send_response(failed, api)
+ if not strict_package_check(bundle_id):
+ data['message'] = 'Invalid iOS Bundle id'
+ return send_response(data, api)
apikey = getattr(settings, 'CORELLIUM_API_KEY', '')
ci = CorelliumInstanceAPI(apikey, instance_id)
+ checksum = get_md5(bundle_id.encode('utf-8'))
# App Container download
logger.info('Downloading app container data')
download_app_data(ci, checksum)
+ # Stop HTTPS Proxy
+ stop_httptools(get_http_tools_url(request))
+ # Move HTTP raw logs to download directory
+ flows = Path.home() / '.httptools' / 'flows'
+ webf = flows / f'{bundle_id}.flow.txt'
+ dwd = Path(settings.DWD_DIR)
+ dweb = dwd / f'{checksum}-web_traffic.txt'
+ if webf.exists():
+ shutil.copyfile(webf, dweb)
# Pcap download
logger.info('Downloading network capture')
pcap = ci.download_network_capture()
diff --git a/mobsf/DynamicAnalyzer/views/ios/corellium_ssh.py b/mobsf/DynamicAnalyzer/views/ios/corellium_ssh.py
index d4c1467fb9..69df0cef5a 100644
--- a/mobsf/DynamicAnalyzer/views/ios/corellium_ssh.py
+++ b/mobsf/DynamicAnalyzer/views/ios/corellium_ssh.py
@@ -23,8 +23,10 @@
# Modified for MobSF.
import logging
-import socketserver
import select
+import socket
+import socketserver
+from threading import Thread
from pathlib import Path
import paramiko
@@ -78,6 +80,7 @@ def parse_ssh_string(ssh):
return ssh_dict
+# Local Port Forward
class ForwardServer(socketserver.ThreadingTCPServer):
daemon_threads = True
allow_reuse_address = True
@@ -148,6 +151,57 @@ class SubHander(Handler):
logger.info('Port Forwarding Already in place')
+# Remote Port Forward
+def handler(chan, host, port):
+ sock = socket.socket()
+ try:
+ sock.connect((host, port))
+ except ConnectionRefusedError:
+ # Proxy server is stopped
+ return
+ except Exception:
+ logger.info('Forwarding request to %s:%d failed', host, port)
+ return
+ try:
+ while True:
+ r, w, x = select.select([sock, chan], [], [])
+ if sock in r:
+ data = sock.recv(1024)
+ if len(data) == 0:
+ break
+ chan.send(data)
+ if chan in r:
+ data = chan.recv(1024)
+ if len(data) == 0:
+ break
+ sock.send(data)
+ except ConnectionResetError:
+ pass
+ finally:
+ chan.close()
+ sock.close()
+
+
+def reverse_forward_tunnel(server_port, remote_host, remote_port, transport):
+ try:
+ transport.request_port_forward('', server_port)
+ while True:
+ chan = transport.accept(1000)
+ if chan is None:
+ continue
+ Thread(
+ target=handler,
+ args=(chan, remote_host, remote_port),
+ daemon=True).start()
+ except paramiko.SSHException as exp:
+ if 'forwarding request denied' in str(exp):
+ # Handle TCP forwarding request denied
+ # Happens if already forwarding port
+ pass
+ else:
+ logger.exception('SSH Remote Port Forward Exception')
+
+
def ssh_jump_host(ssh_string):
"""Connect to SSH over a bastion."""
ssh_dict = parse_ssh_string(ssh_string)
@@ -160,7 +214,8 @@ def ssh_jump_host(ssh_string):
generate_keypair_if_not_exists(home)
keyf = home / 'ssh_key.private'
jumpbox = paramiko.SSHClient()
- jumpbox.set_missing_host_key_policy(paramiko.AutoAddPolicy())
+ jumpbox.load_system_host_keys()
+ jumpbox.set_missing_host_key_policy(paramiko.WarningPolicy())
jumpbox.connect(
bastion_host,
username=bastion_user,
@@ -173,7 +228,8 @@ def ssh_jump_host(ssh_string):
'direct-tcpip', dest_addr, src_addr)
target = paramiko.SSHClient()
- target.set_missing_host_key_policy(paramiko.AutoAddPolicy())
+ target.load_system_host_keys()
+ target.set_missing_host_key_policy(paramiko.WarningPolicy())
target.connect(
private_ip,
username=user,
@@ -194,8 +250,20 @@ def ssh_jumphost_port_forward(ssh_string):
forward_port,
target.get_transport(),
ssh_string)
- # target close()
- # jumpbox close()
+
+
+def ssh_jumphost_reverse_port_forward(ssh_string):
+ """SSH over Jump Host and Remote Port Forward."""
+ target, _jumpbox = ssh_jump_host(ssh_string)
+ # HTTPS proxy port
+ port = settings.PROXY_PORT
+ remote_host = '127.0.0.1'
+ reverse_forward_tunnel(
+ port,
+ remote_host,
+ port,
+ target.get_transport(),
+ )
def ssh_execute_cmd(target, cmd):
diff --git a/mobsf/DynamicAnalyzer/views/ios/dynamic_analyzer.py b/mobsf/DynamicAnalyzer/views/ios/dynamic_analyzer.py
index 9d4ec20bf3..326f5635f8 100644
--- a/mobsf/DynamicAnalyzer/views/ios/dynamic_analyzer.py
+++ b/mobsf/DynamicAnalyzer/views/ios/dynamic_analyzer.py
@@ -1,7 +1,9 @@
# -*- coding: utf_8 -*-
"""iOS Dynamic Analysis."""
import logging
+import os
from pathlib import Path
+from threading import Thread
from django.conf import settings
from django.shortcuts import render
@@ -18,8 +20,17 @@
from mobsf.DynamicAnalyzer.views.ios.corellium_ssh import (
generate_keypair_if_not_exists,
)
+from mobsf.DynamicAnalyzer.tools.webproxy import (
+ get_http_tools_url,
+ start_proxy,
+ stop_httptools,
+)
from mobsf.DynamicAnalyzer.views.ios.corellium_apis import (
CorelliumAPI,
+ CorelliumInstanceAPI,
+)
+from mobsf.DynamicAnalyzer.views.ios.corellium_ssh import (
+ ssh_jumphost_reverse_port_forward,
)
logger = logging.getLogger(__name__)
@@ -97,6 +108,9 @@ def dynamic_analyzer(request, api=False):
app_dir = Path(settings.UPLD_DIR) / bundle_hash
if not app_dir.exists():
app_dir.mkdir()
+ apikey = getattr(settings, 'CORELLIUM_API_KEY', '')
+ ci = CorelliumInstanceAPI(apikey, instance_id)
+ configure_proxy(request, bundleid, ci)
context = {
'hash': bundle_hash,
'instance_id': instance_id,
@@ -122,6 +136,7 @@ def setup_ssh_keys(c):
location = Path(settings.UPLD_DIR).parent
_prv, pub = generate_keypair_if_not_exists(location)
add_keys = False
+ is_docker = os.getenv('MOBSF_PLATFORM') == 'docker'
if not pkeys:
# No SSH Keys associated with the project
# let's add one
@@ -132,12 +147,17 @@ def setup_ssh_keys(c):
pub_key_exists = False
for pkey in pkeys:
if pkey['project'] == c.project_id:
- ckey = get_md5(pkey['key'].encode('utf-8'))
- lkey = get_md5(pub)
- if ckey == lkey:
+ rkey = get_md5(pkey['key'].encode('utf-8'))
+ if rkey == get_md5(pub):
pub_key_exists = True
break
- # Out key is not asscoiated with the project, let's add it
+ if is_docker and pkey['label'].endswith('(docker)'):
+ # Delete all docker generated keys
+ # This is done to avoid multiple stale keys being
+ # added on each run.
+ logger.info('Removing old stale SSH public key')
+ c.delete_authorized_key(pkey['identifier'])
+ # Our key is not asscoiated with the project, let's add it
if not pub_key_exists:
add_keys = True
if add_keys:
@@ -146,3 +166,16 @@ def setup_ssh_keys(c):
logger.error('Failed to add SSH Key to Corellium project')
return
logger.info('Added SSH Key to Corellium project')
+
+
+def configure_proxy(request, project, ci):
+ """Configure HTTPS Proxy."""
+ proxy_port = settings.PROXY_PORT
+ logger.info('Starting HTTPS Proxy on %s', proxy_port)
+ stop_httptools(get_http_tools_url(request))
+ start_proxy(proxy_port, project)
+ # Remote Port forward for HTTPS Proxy
+ logger.info('Starting Remote Port Forward over SSH')
+ Thread(target=ssh_jumphost_reverse_port_forward,
+ args=(ci.get_ssh_connection_string(),),
+ daemon=True).start()
diff --git a/mobsf/DynamicAnalyzer/views/ios/report.py b/mobsf/DynamicAnalyzer/views/ios/report.py
index c14791138c..1c22e8f307 100644
--- a/mobsf/DynamicAnalyzer/views/ios/report.py
+++ b/mobsf/DynamicAnalyzer/views/ios/report.py
@@ -16,11 +16,12 @@
from mobsf.MobSF.utils import (
base64_decode,
common_check,
- is_md5,
+ get_md5,
key,
pretty_json,
print_n_send_error_response,
replace,
+ strict_package_check,
)
@@ -31,22 +32,23 @@
register.filter('base64_decode', base64_decode)
-def ios_view_report(request, checksum, api=False):
+def ios_view_report(request, bundle_id, api=False):
"""Dynamic Analysis Report Generation."""
logger.info('iOS Dynamic Analysis Report Generation')
try:
- if not is_md5(checksum):
- # We need this check since checksum is not validated
- # in REST API
- return print_n_send_error_response(
- request,
- 'Invalid Hash',
- api)
instance_id = request.GET.get('instance_id')
if instance_id and not common_check(instance_id):
dev = instance_id
else:
dev = ''
+ if not strict_package_check(bundle_id):
+ # We need this check since bundleid
+ # is not validated in REST API
+ return print_n_send_error_response(
+ request,
+ 'Invalid iOS Bundle id',
+ api)
+ checksum = get_md5(bundle_id.encode('utf-8'))
app_dir = Path(settings.UPLD_DIR) / checksum
download_dir = settings.DWD_DIR
tools_dir = settings.TOOLS_DIR
@@ -56,8 +58,8 @@ def ios_view_report(request, checksum, api=False):
'for this app. Perform Dynamic Analysis '
'and generate the report.')
return print_n_send_error_response(request, msg, api)
- dynamic_dump = ios_api_analysis(app_dir)
- dump_analaysis = run_analysis(app_dir, checksum)
+ api_analysis = ios_api_analysis(app_dir)
+ dump_analaysis = run_analysis(app_dir, bundle_id, checksum)
trk = Trackers.Trackers(app_dir, tools_dir)
trackers = trk.get_trackers_domains_or_deps(
dump_analaysis['domains'], None)
@@ -67,11 +69,12 @@ def ios_view_report(request, checksum, api=False):
'version': settings.MOBSF_VER,
'title': 'iOS Dynamic Analysis Report',
'instance_id': dev,
+ 'bundleid': bundle_id,
'trackers': trackers,
'screenshots': screenshots,
'frida_logs': frida_log.exists(),
}
- context.update(dynamic_dump)
+ context.update(api_analysis)
context.update(dump_analaysis)
template = 'dynamic_analysis/ios/dynamic_report.html'
if api:
diff --git a/mobsf/MobSF/urls.py b/mobsf/MobSF/urls.py
index bfd4e37cda..8a921c865f 100755
--- a/mobsf/MobSF/urls.py
+++ b/mobsf/MobSF/urls.py
@@ -262,7 +262,7 @@
re_path(r'^ios/system_logs/$',
instance.system_logs,
name='ios_system_logs'),
- re_path(r'^ios/download_data/(?P
@@ -133,7 +140,7 @@
Start Dynamic Analysis
- View Report
+ View Report
Uninstall
Corellium Project ID: {{ project_id }}
VM
VERSION
STATE
- META
+ PROGRESS
ACTIONS
@@ -60,8 +60,15 @@ Corellium Project ID: {{ project_id }}
{{i.state}}
-
- {{ i.restoreStatus}}
+
+ {% if 'progress' in i.restoreStatus %}
+
{{ i.restoreStatus.stage}}
+ {% endif %}
@@ -337,6 +344,8 @@ Apps Available
Upload & Install
{% endif %}
Create a Corellium iOS VM
// List installed apps
function list_apps(){
$('#in_device tbody tr').remove();
+ $('#in_device thead').remove();
+ dynamic_loader();
$.ajax({
url: '{% url 'list_apps' %}',
type : 'POST',
@@ -377,13 +386,15 @@ Create a Corellium iOS VM
${escapeHtml(type)}
`);
}
+ stop_loader();
}
else {
+ stop_loader();
Swal.fire(
'Listing apps failed',
'Is VM ready?: ' + json.message,
@@ -392,6 +403,7 @@ Create a Corellium iOS VM
}
},
error : function(xhr,errmsg,err) {
+ stop_loader();
console.log(errmsg)
}
});
diff --git a/mobsf/templates/dynamic_analysis/ios/dynamic_analyzer.html b/mobsf/templates/dynamic_analysis/ios/dynamic_analyzer.html
index 95f758df79..a9731e1e1a 100644
--- a/mobsf/templates/dynamic_analysis/ios/dynamic_analyzer.html
+++ b/mobsf/templates/dynamic_analysis/ios/dynamic_analyzer.html
@@ -1138,11 +1138,11 @@ Attach to a Running Process
stop_network_capture();
// Download app data
print_status("Downloading application data.");
- action('{% url 'ios_download_data' checksum=hash %}', {instance_id: '{{ instance_id }}'}, function(json) {
+ action('{% url 'ios_download_data' bundle_id=bundle_id %}', {instance_id: '{{ instance_id }}'}, function(json) {
if (json.status==="ok"){
// Generate Report
print_status("Generating report");
- location.href = '{% url 'ios_view_report' checksum=hash %}?instance_id={{ instance_id }}';
+ location.href = '{% url 'ios_view_report' bundle_id=bundle_id %}?instance_id={{ instance_id }}';
} else {
print_status("Failed to download application data");
}
diff --git a/mobsf/templates/dynamic_analysis/ios/dynamic_report.html b/mobsf/templates/dynamic_analysis/ios/dynamic_report.html
index e6ece564fe..dda59045df 100644
--- a/mobsf/templates/dynamic_analysis/ios/dynamic_report.html
+++ b/mobsf/templates/dynamic_analysis/ios/dynamic_report.html
@@ -269,13 +269,16 @@
+ HTTP(S) Traffic Network Pcap
diff --git a/mobsf/templates/general/recent.html b/mobsf/templates/general/recent.html index c85900e6b8..9a91b445f5 100644 --- a/mobsf/templates/general/recent.html +++ b/mobsf/templates/general/recent.html @@ -71,7 +71,9 @@