Skip to content

Commit

Permalink
HTTPtools, SSH Remote Port Forward, Docker SSH KEy MAnagement
Browse files Browse the repository at this point in the history
  • Loading branch information
ajinabraham committed Dec 2, 2023
1 parent 56492e6 commit f6a484e
Show file tree
Hide file tree
Showing 15 changed files with 247 additions and 81 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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){
// }


5 changes: 2 additions & 3 deletions mobsf/DynamicAnalyzer/views/android/environment.py
Original file line number Diff line number Diff line change
Expand Up @@ -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):
Expand Down
3 changes: 1 addition & 2 deletions mobsf/DynamicAnalyzer/views/android/tests_common.py
Original file line number Diff line number Diff line change
Expand Up @@ -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',
Expand Down
10 changes: 7 additions & 3 deletions mobsf/DynamicAnalyzer/views/ios/analysis.py
Original file line number Diff line number Diff line change
Expand Up @@ -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))
Expand Down
15 changes: 14 additions & 1 deletion mobsf/DynamicAnalyzer/views/ios/corellium_apis.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
# -*- coding: utf_8 -*-
"""Corellium APIs."""
import logging
import os
from copy import deepcopy
from socket import gethostname

Expand Down Expand Up @@ -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(
Expand All @@ -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')
Expand Down
24 changes: 19 additions & 5 deletions mobsf/DynamicAnalyzer/views/ios/corellium_instance.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import logging
import re
import os
import shutil
import time
from base64 import b64encode
from pathlib import Path
Expand All @@ -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,
Expand Down Expand Up @@ -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()
Expand Down
78 changes: 73 additions & 5 deletions mobsf/DynamicAnalyzer/views/ios/corellium_ssh.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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)
Expand All @@ -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())

Check failure

Code scanning / CodeQL

Accepting unknown SSH host keys when using Paramiko High

Setting missing host key policy to WarningPolicy may be unsafe.
jumpbox.connect(
bastion_host,
username=bastion_user,
Expand All @@ -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())

Check failure

Code scanning / CodeQL

Accepting unknown SSH host keys when using Paramiko High

Setting missing host key policy to WarningPolicy may be unsafe.
target.connect(
private_ip,
username=user,
Expand All @@ -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):
Expand Down
Loading

0 comments on commit f6a484e

Please sign in to comment.