From 943783737e5a396b3ea65e0cecc2a52196edf9cd Mon Sep 17 00:00:00 2001 From: "Ajin.Abraham" Date: Sun, 10 Dec 2023 22:08:06 -0800 Subject: [PATCH 1/5] OS level debug symbols check for macho and elf --- mobsf/MobSF/init.py | 2 +- .../StaticAnalyzer/views/common/binary/elf.py | 24 +++++-- .../views/common/binary/macho.py | 68 ++++++++++++------- pyproject.toml | 2 +- 4 files changed, 64 insertions(+), 32 deletions(-) diff --git a/mobsf/MobSF/init.py b/mobsf/MobSF/init.py index 095944530e..b475711c68 100644 --- a/mobsf/MobSF/init.py +++ b/mobsf/MobSF/init.py @@ -10,7 +10,7 @@ logger = logging.getLogger(__name__) -VERSION = '3.8.6' +VERSION = '3.8.7' BANNER = """ __ __ _ ____ _____ _____ ___ | \/ | ___ | |__/ ___|| ___|_ _|___ / ( _ ) diff --git a/mobsf/StaticAnalyzer/views/common/binary/elf.py b/mobsf/StaticAnalyzer/views/common/binary/elf.py index c67bbc0aca..c1a2b824eb 100644 --- a/mobsf/StaticAnalyzer/views/common/binary/elf.py +++ b/mobsf/StaticAnalyzer/views/common/binary/elf.py @@ -1,5 +1,8 @@ # !/usr/bin/python # coding=utf-8 +import shutil +import subprocess + import lief from mobsf.StaticAnalyzer.views.common.binary.strings import ( @@ -7,6 +10,15 @@ ) +def nm_is_debug_symbol_stripped(elf_file): + """Check if debug symbols are stripped using OS utility.""" + # https://linux.die.net/man/1/nm + out = subprocess.check_output( + [shutil.which('nm'), '--debug-syms', elf_file], + stderr=subprocess.STDOUT) + return b'no debug symbols' in out + + class ELFChecksec: def __init__(self, elf_file, so_rel): self.elf_path = elf_file.as_posix() @@ -191,10 +203,14 @@ def runpath(self): return False def is_symbols_stripped(self): - for i in self.elf.static_symbols: - if i: - return False - return True + try: + return nm_is_debug_symbol_stripped( + self.elf_path) + except Exception: + for i in self.elf.static_symbols: + if i: + return False + return True def fortify(self): fortified_funcs = [] diff --git a/mobsf/StaticAnalyzer/views/common/binary/macho.py b/mobsf/StaticAnalyzer/views/common/binary/macho.py index a1a6ef85fb..f6bc945b44 100644 --- a/mobsf/StaticAnalyzer/views/common/binary/macho.py +++ b/mobsf/StaticAnalyzer/views/common/binary/macho.py @@ -1,5 +1,8 @@ # !/usr/bin/python # coding=utf-8 +import shutil +import subprocess + import lief from mobsf.StaticAnalyzer.views.common.binary.strings import ( @@ -7,6 +10,15 @@ ) +def objdump_is_debug_symbol_stripped(macho_file): + """Check if debug symbols are stripped using OS utility.""" + # https://www.unix.com/man-page/osx/1/objdump/ + out = subprocess.check_output( + [shutil.which('objdump'), '--syms', macho_file], + stderr=subprocess.STDOUT) + return b' d ' not in out + + class MachOChecksec: def __init__(self, macho, rel_path=None): self.macho_path = macho.as_posix() @@ -243,34 +255,38 @@ def is_encrypted(self): return False def is_symbols_stripped(self): - # Based on issues/1917#issuecomment-1238078359 - # and issues/2233#issue-1846914047 - stripped_sym = 'radr://5614542' - # radr://5614542 symbol is added back for - # debug symbols stripped binaries - for i in self.macho.symbols: - if i.name.lower().strip() in ('__mh_execute_header', stripped_sym): - # __mh_execute_header is present in both - # stripped and unstripped binaries - # also ignore radr://5614542 - continue - if (i.type & 0xe0) > 0 or i.type in (0x0e, 0x1e): - # N_STAB set or 14, 30 + try: + return objdump_is_debug_symbol_stripped(self.macho_path) + except Exception: + # Based on issues/1917#issuecomment-1238078359 + # and issues/2233#issue-1846914047 + stripped_sym = 'radr://5614542' + # radr://5614542 symbol is added back for + # debug symbols stripped binaries + for i in self.macho.symbols: + if i.name.lower().strip() in ( + '__mh_execute_header', stripped_sym): + # __mh_execute_header is present in both + # stripped and unstripped binaries + # also ignore radr://5614542 + continue + if (i.type & 0xe0) > 0 or i.type in (0x0e, 0x1e): + # N_STAB set or 14, 30 - # N_STAB 0xe0 /* if any of these bits set, - # a symbolic debugging entry */ -> 224 - # From https://opensource.apple.com/source/xnu/xnu-201/ - # EXTERNAL_HEADERS/mach-o/nlist.h - # Only symbolic debugging entries have - # some of the N_STAB bits set and if any - # of these bits are set then it is a - # symbolic debugging entry (a stab). + # N_STAB 0xe0 /* if any of these bits set, + # a symbolic debugging entry */ -> 224 + # https://opensource.apple.com/source/xnu/xnu-201/ + # EXTERNAL_HEADERS/mach-o/nlist.h + # Only symbolic debugging entries have + # some of the N_STAB bits set and if any + # of these bits are set then it is a + # symbolic debugging entry (a stab). - # Identified a debugging symbol - return False - if stripped_sym in self.get_symbols(): - return True - return False + # Identified a debugging symbol + return False + if stripped_sym in self.get_symbols(): + return True + return False def get_libraries(self): libs = [] diff --git a/pyproject.toml b/pyproject.toml index c3f4395c61..c826507a4d 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "mobsf" -version = "3.8.6" +version = "3.8.7" description = "Mobile Security Framework (MobSF) is an automated, all-in-one mobile application (Android/iOS/Windows) pen-testing, malware analysis and security assessment framework capable of performing static and dynamic analysis." keywords = ["mobsf", "mobile security framework", "mobile security", "security tool", "static analysis", "dynamic analysis", "malware analysis"] authors = ["Ajin Abraham "] From addf106caed3bd633537c219bfa4e030dfbf2cae Mon Sep 17 00:00:00 2001 From: "Ajin.Abraham" Date: Sun, 10 Dec 2023 23:02:03 -0800 Subject: [PATCH 2/5] Add RELRO checks back to shared obects --- .../StaticAnalyzer/views/common/binary/elf.py | 101 +++++++++++++++--- mobsf/templates/pdf/android_report.html | 5 + .../android_binary_analysis.html | 5 + 3 files changed, 98 insertions(+), 13 deletions(-) diff --git a/mobsf/StaticAnalyzer/views/common/binary/elf.py b/mobsf/StaticAnalyzer/views/common/binary/elf.py index c1a2b824eb..0bd5331d03 100644 --- a/mobsf/StaticAnalyzer/views/common/binary/elf.py +++ b/mobsf/StaticAnalyzer/views/common/binary/elf.py @@ -10,6 +10,15 @@ ) +NA = 'Not Applicable' +NO_RELRO = 'No RELRO' +PARTIAL_RELRO = 'Partial RELRO' +FULL_RELRO = 'Full RELRO' +INFO = 'info' +WARNING = 'warning' +HIGH = 'high' + + def nm_is_debug_symbol_stripped(elf_file): """Check if debug symbols are stripped using OS utility.""" # https://linux.die.net/man/1/nm @@ -32,13 +41,13 @@ def checksec(self): return is_nx = self.is_nx() if is_nx: - severity = 'info' + severity = INFO desc = ( 'The binary has NX bit set. This marks a ' 'memory page non-executable making attacker ' 'injected shellcode non-executable.') else: - severity = 'high' + severity = HIGH desc = ( 'The binary does not have NX bit set. NX bit ' 'offer protection against exploitation of memory corruption ' @@ -52,7 +61,7 @@ def checksec(self): } has_canary = self.has_canary() if has_canary: - severity = 'info' + severity = INFO desc = ( 'This binary has a stack canary value ' 'added to the stack so that it will be overwritten by ' @@ -60,7 +69,7 @@ def checksec(self): 'This allows detection of overflows by verifying the ' 'integrity of the canary before function return.') else: - severity = 'high' + severity = HIGH desc = ( 'This binary does not have a stack ' 'canary value added to the stack. Stack canaries ' @@ -74,9 +83,47 @@ def checksec(self): 'severity': severity, 'description': desc, } + relro = self.relro() + if relro == NA: + severity = INFO + desc = ('RELRO checks are not applicable for ' + 'Flutter/Dart binaries') + elif relro == FULL_RELRO: + severity = INFO + desc = ( + 'This shared object has full RELRO ' + 'enabled. RELRO ensures that the GOT cannot be ' + 'overwritten in vulnerable ELF binaries. ' + 'In Full RELRO, the entire GOT (.got and ' + '.got.plt both) is marked as read-only.') + elif relro == PARTIAL_RELRO: + severity = WARNING + desc = ( + 'This shared object has partial RELRO ' + 'enabled. RELRO ensures that the GOT cannot be ' + 'overwritten in vulnerable ELF binaries. ' + 'In partial RELRO, the non-PLT part of the GOT ' + 'section is read only but .got.plt is still ' + 'writeable. Use the option -z,relro,-z,now to ' + 'enable full RELRO.') + else: + severity = HIGH + desc = ( + 'This shared object does not have RELRO ' + 'enabled. The entire GOT (.got and ' + '.got.plt both) are writable. Without this compiler ' + 'flag, buffer overflows on a global variable can ' + 'overwrite GOT entries. Use the option ' + '-z,relro,-z,now to enable full RELRO and only ' + '-z,relro to enable partial RELRO.') + elf_dict['relocation_readonly'] = { + 'relro': relro, + 'severity': severity, + 'description': desc, + } rpath = self.rpath() if rpath: - severity = 'high' + severity = HIGH desc = ( 'The binary has RPATH set. In certain cases, ' 'an attacker can abuse this feature to run arbitrary ' @@ -87,7 +134,7 @@ def checksec(self): 'compiler option -rpath to remove RPATH.') rpt = rpath.rpath else: - severity = 'info' + severity = INFO desc = ( 'The binary does not have run-time search path ' 'or RPATH set.') @@ -99,7 +146,7 @@ def checksec(self): } runpath = self.runpath() if runpath: - severity = 'high' + severity = HIGH desc = ( 'The binary has RUNPATH set. In certain cases, ' 'an attacker can abuse this feature and or modify ' @@ -111,7 +158,7 @@ def checksec(self): 'option --enable-new-dtags,-rpath to remove RUNPATH.') rnp = runpath.runpath else: - severity = 'info' + severity = INFO desc = ( 'The binary does not have RUNPATH set.') rnp = runpath @@ -122,14 +169,14 @@ def checksec(self): } fortified_functions = self.fortify() if fortified_functions: - severity = 'info' + severity = INFO desc = ('The binary has the ' f'following fortified functions: {fortified_functions}') else: if self.is_dart(): - severity = 'info' + severity = INFO else: - severity = 'warning' + severity = WARNING desc = ('The binary does not have any ' 'fortified functions. Fortified functions ' 'provides buffer overflow checks against ' @@ -145,10 +192,10 @@ def checksec(self): } is_stripped = self.is_symbols_stripped() if is_stripped: - severity = 'info' + severity = INFO desc = 'Symbols are stripped.' else: - severity = 'warning' + severity = WARNING desc = 'Symbols are available.' elf_dict['symbol'] = { 'is_stripped': is_stripped, @@ -188,6 +235,34 @@ def has_canary(self): pass return False + def relro(self): + try: + gnu_relro = lief.ELF.SEGMENT_TYPES.GNU_RELRO + bind_now_flag = lief.ELF.DYNAMIC_FLAGS.BIND_NOW + flags_tag = lief.ELF.DYNAMIC_TAGS.FLAGS + flags1_tag = lief.ELF.DYNAMIC_TAGS.FLAGS_1 + now_flag = lief.ELF.DYNAMIC_FLAGS_1.NOW + + if self.is_dart(): + return NA + + if not self.elf.get(gnu_relro): + return NO_RELRO + + flags = self.elf.get(flags_tag) + bind_now = flags and bind_now_flag in flags + + flags1 = self.elf.get(flags1_tag) + now = flags1 and now_flag in flags1 + + if bind_now or now: + return FULL_RELRO + else: + return PARTIAL_RELRO + except lief.not_found: + pass + return NO_RELRO + def rpath(self): try: rpath = lief.ELF.DYNAMIC_TAGS.RPATH diff --git a/mobsf/templates/pdf/android_report.html b/mobsf/templates/pdf/android_report.html index bb76025b6a..1933bc58f8 100755 --- a/mobsf/templates/pdf/android_report.html +++ b/mobsf/templates/pdf/android_report.html @@ -635,6 +635,7 @@

SHARED LIBRARY BINARY ANALYSIS

SHARED OBJECT NX STACK CANARY + RELRO RPATH RUNPATH FORTIFY @@ -657,6 +658,10 @@

SHARED LIBRARY BINARY ANALYSIS


{{so.stack_canary.severity}}
{{so.stack_canary.description}} + {{so.relocation_readonly.relro}} +
+ {{so.relocation_readonly.severity}} +
{{so.relocation_readonly.description}} {{so.rpath.rpath}}
{{so.rpath.severity}} diff --git a/mobsf/templates/static_analysis/android_binary_analysis.html b/mobsf/templates/static_analysis/android_binary_analysis.html index d180e63344..5e25d3acfd 100755 --- a/mobsf/templates/static_analysis/android_binary_analysis.html +++ b/mobsf/templates/static_analysis/android_binary_analysis.html @@ -1357,6 +1357,7 @@
{{ code_analysis.summary.suppressed }}
{% endif %} NX STACK CANARY + RELRO RPATH RUNPATH FORTIFY @@ -1381,6 +1382,10 @@
{{ code_analysis.summary.suppressed }}

{{so.stack_canary.severity}}
{{so.stack_canary.description}} + {{so.relocation_readonly.relro}} +
+ {{so.relocation_readonly.severity}} +
{{so.relocation_readonly.description}} {{so.rpath.rpath}}
{{so.rpath.severity}} From f534e78c8284231b6ae66919eea58e4fb227e239 Mon Sep 17 00:00:00 2001 From: "Ajin.Abraham" Date: Sat, 16 Dec 2023 02:10:29 -0800 Subject: [PATCH 3/5] Support scanning dylib and so from APK/IPA binary analysis report --- mobsf/MobSF/urls.py | 5 +- mobsf/MobSF/utils.py | 17 ++++++ mobsf/StaticAnalyzer/views/android/jar_aar.py | 5 +- mobsf/StaticAnalyzer/views/android/so.py | 5 +- .../views/android/static_analyzer.py | 7 ++- mobsf/StaticAnalyzer/views/common/a.py | 5 +- .../views/common/binary/lib_analysis.py | 19 +++---- .../views/common/binary/macho.py | 1 + .../views/common/shared_func.py | 53 +++++++++++++++++++ mobsf/StaticAnalyzer/views/ios/dylib.py | 5 +- .../views/ios/static_analyzer.py | 9 +++- .../android_binary_analysis.html | 4 +- .../static_analysis/ios_binary_analysis.html | 4 +- 13 files changed, 119 insertions(+), 20 deletions(-) diff --git a/mobsf/MobSF/urls.py b/mobsf/MobSF/urls.py index 351565db1f..87faf1649e 100755 --- a/mobsf/MobSF/urls.py +++ b/mobsf/MobSF/urls.py @@ -149,7 +149,10 @@ # App Compare re_path(r'^compare/(?P[0-9a-f]{32})/(?P[0-9a-f]{32})/$', shared_func.compare_apps), - + # Relative Shared & Dynamic Library scan + re_path(r'^scan_library/(?P[0-9a-f]{32})$', + shared_func.scan_library, + name='scan_library'), # Dynamic Analysis re_path(r'^android/dynamic_analysis/$', dz.android_dynamic_analysis, diff --git a/mobsf/MobSF/utils.py b/mobsf/MobSF/utils.py index e5a639b8a3..dcc7bd96c9 100755 --- a/mobsf/MobSF/utils.py +++ b/mobsf/MobSF/utils.py @@ -19,6 +19,7 @@ import sqlite3 import unicodedata import threading +from pathlib import Path from distutils.version import StrictVersion import distro @@ -491,6 +492,8 @@ def update_local_db(db_name, url, local_file): else: logger.info('%s Database is up-to-date', db_name) return update + except requests.exceptions.ReadTimeout: + logger.warning('Failed to download %s DB.', db_name) except Exception: logger.exception('[ERROR] %s DB Update', db_name) return update @@ -758,6 +761,20 @@ def replace(value, arg): return value.replace(what, to) +def relative_path(value): + """Show relative path to two parents.""" + if '/' in value: + sep = '/' + elif '\\\\' in value: + sep = '\\\\' + elif '\\' in value: + sep = '\\' + if value.count(sep) < 2: + return value + path = Path(value) + return path.relative_to(path.parent.parent).as_posix() + + def pretty_json(value): """Pretty print JSON.""" try: diff --git a/mobsf/StaticAnalyzer/views/android/jar_aar.py b/mobsf/StaticAnalyzer/views/android/jar_aar.py index bc261ac973..1c86d9be99 100644 --- a/mobsf/StaticAnalyzer/views/android/jar_aar.py +++ b/mobsf/StaticAnalyzer/views/android/jar_aar.py @@ -142,7 +142,10 @@ def common_analysis(request, app_dic, rescan, api, analysis_type): 'certificate_summary': {}, } app_dic['real_name'] = '' - elf_dict = library_analysis(app_dic['app_dir'], 'elf') + elf_dict = library_analysis( + app_dic['app_dir'], + app_dic['md5'], + 'elf') tracker = Trackers.Trackers( app_dic['app_dir'], app_dic['tools_dir']) diff --git a/mobsf/StaticAnalyzer/views/android/so.py b/mobsf/StaticAnalyzer/views/android/so.py index 0235ee0ce5..7d1ec9b9fd 100644 --- a/mobsf/StaticAnalyzer/views/android/so.py +++ b/mobsf/StaticAnalyzer/views/android/so.py @@ -88,7 +88,10 @@ def so_analysis(request, app_dic, rescan, api): 'certificate_summary': {}, } app_dic['real_name'] = '' - elf_dict = library_analysis(app_dic['app_dir'], 'elf') + elf_dict = library_analysis( + app_dic['app_dir'], + app_dic['md5'], + 'elf') # File Analysis is used to store symbols from so app_dic['certz'] = get_symbols( elf_dict['elf_symbols']) diff --git a/mobsf/StaticAnalyzer/views/android/static_analyzer.py b/mobsf/StaticAnalyzer/views/android/static_analyzer.py index 2390d32ed8..8fd28ce131 100755 --- a/mobsf/StaticAnalyzer/views/android/static_analyzer.py +++ b/mobsf/StaticAnalyzer/views/android/static_analyzer.py @@ -25,6 +25,7 @@ is_md5, key, print_n_send_error_response, + relative_path, ) from mobsf.StaticAnalyzer.models import ( RecentScansDB, @@ -92,6 +93,7 @@ register.filter('key', key) register.filter('android_component', android_component) +register.filter('relative_path', relative_path) def static_analyzer(request, checksum, api=False): @@ -211,7 +213,10 @@ def static_analyzer(request, checksum, api=False): # apktool should run before this get_icon_apk(apk, app_dic) - elf_dict = library_analysis(app_dic['app_dir'], 'elf') + elf_dict = library_analysis( + app_dic['app_dir'], + app_dic['md5'], + 'elf') cert_dic = cert_info( apk, app_dic, diff --git a/mobsf/StaticAnalyzer/views/common/a.py b/mobsf/StaticAnalyzer/views/common/a.py index 0deaa38b51..26975fde70 100644 --- a/mobsf/StaticAnalyzer/views/common/a.py +++ b/mobsf/StaticAnalyzer/views/common/a.py @@ -116,7 +116,10 @@ def a_analysis(request, app_dict, rescan, api): 'framework_analysis': {}, } # Analyze static library - slib = library_analysis(app_dict['bin_dir'], 'ar') + slib = library_analysis( + app_dict['bin_dir'], + app_dict['md5_hash'], + 'ar') bin_dict['bin_info']['arch'] = slib['ar_a'] bin_dict['dylib_analysis'] = slib['ar_analysis'] # Store Symbols in File Analysis diff --git a/mobsf/StaticAnalyzer/views/common/binary/lib_analysis.py b/mobsf/StaticAnalyzer/views/common/binary/lib_analysis.py index 4f2d9b7c6d..868d4ff052 100644 --- a/mobsf/StaticAnalyzer/views/common/binary/lib_analysis.py +++ b/mobsf/StaticAnalyzer/views/common/binary/lib_analysis.py @@ -3,6 +3,8 @@ import lief +from django.conf import settings + from mobsf.MobSF.utils import ( settings_enabled, ) @@ -17,8 +19,9 @@ logger = logging.getLogger(__name__) -def library_analysis(src, arch): +def library_analysis(src, checksum, arch): """Perform library binary analysis.""" + base_dir = Path(settings.UPLD_DIR) / checksum res = { f'{arch}_analysis': [], f'{arch}_strings': [], @@ -42,10 +45,7 @@ def library_analysis(src, arch): # Supports Static Library, Shared objects, Dynamic Library, # from APK, SO, AAR, JAR, IPA, DYLIB, and A for libfile in Path(src).rglob(ext): - rel_path = ( - f'{libfile.parents[1].name}/' - f'{libfile.parents[0].name}/' - f'{libfile.name}') + rel_path = libfile.relative_to(base_dir).as_posix() logger.info('Analyzing %s', rel_path) if arch == 'ar': # Handle static library @@ -75,7 +75,7 @@ def library_analysis(src, arch): res['framework_analysis'] = [] res['framework_strings'] = [] res['framework_symbols'] = [] - frameworks_analysis(src, res) + frameworks_analysis(src, base_dir, res) if res['framework_strings']: res[f'{arch}_strings'].extend( res['framework_strings']) @@ -84,7 +84,7 @@ def library_analysis(src, arch): return res -def frameworks_analysis(src, res): +def frameworks_analysis(src, base_dir, res): """Binary Analysis on Frameworks.""" try: logger.info('Framework Binary Analysis Started') @@ -93,10 +93,7 @@ def frameworks_analysis(src, res): parent = ffile.parents[0].name if not parent.endswith('.framework'): continue - rel_path = ( - f'{ffile.parents[1].name}/' - f'{ffile.parents[0].name}/' - f'{ffile.name}') + rel_path = ffile.relative_to(base_dir).as_posix() if ffile.suffix != '' or ffile.name not in parent: continue # Frameworks/XXX.framework/XXX diff --git a/mobsf/StaticAnalyzer/views/common/binary/macho.py b/mobsf/StaticAnalyzer/views/common/binary/macho.py index f6bc945b44..9d4ca34f05 100644 --- a/mobsf/StaticAnalyzer/views/common/binary/macho.py +++ b/mobsf/StaticAnalyzer/views/common/binary/macho.py @@ -13,6 +13,7 @@ def objdump_is_debug_symbol_stripped(macho_file): """Check if debug symbols are stripped using OS utility.""" # https://www.unix.com/man-page/osx/1/objdump/ + # Works only on MacOS out = subprocess.check_output( [shutil.which('objdump'), '--syms', macho_file], stderr=subprocess.STDOUT) diff --git a/mobsf/StaticAnalyzer/views/common/shared_func.py b/mobsf/StaticAnalyzer/views/common/shared_func.py index 67bfe41bf3..fd86eac656 100755 --- a/mobsf/StaticAnalyzer/views/common/shared_func.py +++ b/mobsf/StaticAnalyzer/views/common/shared_func.py @@ -21,6 +21,7 @@ import arpy from django.utils.html import escape +from django.http import HttpResponseRedirect from mobsf.MobSF import settings from mobsf.MobSF.utils import ( @@ -28,9 +29,14 @@ STRINGS_REGEX, URL_REGEX, is_md5, + is_safe_path, print_n_send_error_response, upstream_proxy, ) +from mobsf.MobSF.views.scanning import ( + add_to_recent_scan, + handle_uploaded_file, +) from mobsf.StaticAnalyzer.views.comparer import ( generic_compare, ) @@ -359,3 +365,50 @@ def get_symbols(symbols): for _, val in i.items(): all_symbols.extend(val) return list(set(all_symbols)) + + +def scan_library(request, checksum): + """Scan a shared library or framework from path name.""" + try: + libchecksum = None + if not is_md5(checksum): + return print_n_send_error_response( + request, + 'Invalid MD5') + relative_path = request.GET['library'] + lib_dir = Path(settings.UPLD_DIR) / checksum + + sfile = lib_dir / relative_path + if not is_safe_path(lib_dir.as_posix(), sfile.as_posix()): + msg = 'Path Traversal Detected!' + return print_n_send_error_response(request, msg) + + ext = sfile.suffix + if not sfile.exists(): + msg = 'Library File not found' + return print_n_send_error_response(request, msg) + with open(sfile, 'rb') as f: + libchecksum = handle_uploaded_file(f, ext) + if ext in ('.ipa', '.dylib', '.a'): + static_analyzer = 'static_analyzer_ios' + elif ext == '.appx': + # Not applicable, but still set it + static_analyzer = 'windows_static_analyzer' + elif ext in ('.zip', '.so', '.jar', '.aar', '.apk', '.xapk'): + static_analyzer = 'static_analyzer' + else: + msg = 'Extension not supported' + return print_n_send_error_response(request, msg) + data = { + 'analyzer': static_analyzer, + 'status': 'success', + 'hash': libchecksum, + 'scan_type': ext.replace('.', ''), + 'file_name': sfile.name, + } + add_to_recent_scan(data) + return HttpResponseRedirect(f'/{static_analyzer}/{libchecksum}/') + except Exception: + msg = 'Failed to perform Static Analysis of library' + logger.exception(msg) + return print_n_send_error_response(request, msg) diff --git a/mobsf/StaticAnalyzer/views/ios/dylib.py b/mobsf/StaticAnalyzer/views/ios/dylib.py index 5a082cefb9..90861af70d 100644 --- a/mobsf/StaticAnalyzer/views/ios/dylib.py +++ b/mobsf/StaticAnalyzer/views/ios/dylib.py @@ -96,7 +96,10 @@ def dylib_analysis(request, app_dict, rescan, api): 'bin_type': 'Dylib', } # Analyze dylib - dy = library_analysis(app_dict['bin_dir'], 'macho') + dy = library_analysis( + app_dict['bin_dir'], + app_dict['md5_hash'], + 'macho') bin_dict['dylib_analysis'] = dy['macho_analysis'] bin_dict['framework_analysis'] = {} # Store Symbols in File Analysis diff --git a/mobsf/StaticAnalyzer/views/ios/static_analyzer.py b/mobsf/StaticAnalyzer/views/ios/static_analyzer.py index 5446ce5e4f..bf513c4997 100755 --- a/mobsf/StaticAnalyzer/views/ios/static_analyzer.py +++ b/mobsf/StaticAnalyzer/views/ios/static_analyzer.py @@ -8,11 +8,13 @@ from django.conf import settings from django.shortcuts import render +from django.template.defaulttags import register from mobsf.MobSF.utils import ( file_size, is_md5, print_n_send_error_response, + relative_path, ) from mobsf.StaticAnalyzer.models import ( RecentScansDB, @@ -62,6 +64,8 @@ logger = logging.getLogger(__name__) +register.filter('relative_path', relative_path) + ############################################################## # iOS Static Code Analysis IPA and Source Code ############################################################## @@ -156,7 +160,10 @@ def static_analyzer_ios(request, checksum, api=False): app_dict['app_dir'], infoplist_dict.get('bin')) # Analyze dylibs and frameworks - lb = library_analysis(app_dict['bin_dir'], 'macho') + lb = library_analysis( + app_dict['bin_dir'], + app_dict['md5_hash'], + 'macho') bin_dict['dylib_analysis'] = lb['macho_analysis'] bin_dict['framework_analysis'] = lb['framework_analysis'] # Get Icon diff --git a/mobsf/templates/static_analysis/android_binary_analysis.html b/mobsf/templates/static_analysis/android_binary_analysis.html index 5e25d3acfd..48b378291d 100755 --- a/mobsf/templates/static_analysis/android_binary_analysis.html +++ b/mobsf/templates/static_analysis/android_binary_analysis.html @@ -1372,7 +1372,9 @@
{{ code_analysis.summary.suppressed }}
{% if app_type not in 'so' %} {{ forloop.counter }} - {{so.name}} + {{so.name | relative_path}}
+ Analyze + {% endif %} {{so.nx.is_nx}}
diff --git a/mobsf/templates/static_analysis/ios_binary_analysis.html b/mobsf/templates/static_analysis/ios_binary_analysis.html index 07daad44f3..15299baed6 100755 --- a/mobsf/templates/static_analysis/ios_binary_analysis.html +++ b/mobsf/templates/static_analysis/ios_binary_analysis.html @@ -952,7 +952,9 @@
{{ binary_analysis.summary.suppressed }}
{% if app_type not in 'Dylib' %} {{ forloop.counter }} - {{dy.name}} + {{dy.name | relative_path}}
+ Analyze + {% endif %} {{dy.nx.has_nx}}
From 924a87889d55f30e9a6aecdde2b64fb4746814ff Mon Sep 17 00:00:00 2001 From: "Ajin.Abraham" Date: Sat, 16 Dec 2023 02:23:28 -0800 Subject: [PATCH 4/5] PDF report library relative path --- mobsf/templates/pdf/android_report.html | 2 +- mobsf/templates/pdf/ios_report.html | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/mobsf/templates/pdf/android_report.html b/mobsf/templates/pdf/android_report.html index 1933bc58f8..ea05b820a5 100755 --- a/mobsf/templates/pdf/android_report.html +++ b/mobsf/templates/pdf/android_report.html @@ -649,7 +649,7 @@

SHARED LIBRARY BINARY ANALYSIS

{% for so in binary_analysis %} {{ forloop.counter }} - {{so.name}} + {{so.name | relative_path}} {{so.nx.is_nx}}
{{so.nx.severity}} diff --git a/mobsf/templates/pdf/ios_report.html b/mobsf/templates/pdf/ios_report.html index 29f50d0010..c0aca19435 100644 --- a/mobsf/templates/pdf/ios_report.html +++ b/mobsf/templates/pdf/ios_report.html @@ -537,7 +537,7 @@

DYNAMIC LIBRARY BINARY ANALYSIS

{% for dy in dylib_analysis %} {{ forloop.counter }} - {{dy.name}} + {{dy.name | relative_path}} {{dy.nx.has_nx}}
{{dy.nx.severity}}
From d4dd27f21a29a23c0fafcfcc1e78101fb427e682 Mon Sep 17 00:00:00 2001 From: "Ajin.Abraham" Date: Sat, 16 Dec 2023 12:52:07 -0800 Subject: [PATCH 5/5] Merge iOS Framework and Dylib Analysis --- mobsf/MobSF/utils.py | 3 +- .../views/common/shared_func.py | 4 +- .../views/ios/static_analyzer.py | 3 + mobsf/templates/pdf/ios_report.html | 39 +++--------- .../static_analysis/ios_binary_analysis.html | 63 +++---------------- 5 files changed, 26 insertions(+), 86 deletions(-) diff --git a/mobsf/MobSF/utils.py b/mobsf/MobSF/utils.py index dcc7bd96c9..ba88e8a89a 100755 --- a/mobsf/MobSF/utils.py +++ b/mobsf/MobSF/utils.py @@ -763,13 +763,14 @@ def replace(value, arg): def relative_path(value): """Show relative path to two parents.""" + sep = None if '/' in value: sep = '/' elif '\\\\' in value: sep = '\\\\' elif '\\' in value: sep = '\\' - if value.count(sep) < 2: + if not sep or value.count(sep) < 2: return value path = Path(value) return path.relative_to(path.parent.parent).as_posix() diff --git a/mobsf/StaticAnalyzer/views/common/shared_func.py b/mobsf/StaticAnalyzer/views/common/shared_func.py index fd86eac656..fba6c7fe6b 100755 --- a/mobsf/StaticAnalyzer/views/common/shared_func.py +++ b/mobsf/StaticAnalyzer/views/common/shared_func.py @@ -382,8 +382,10 @@ def scan_library(request, checksum): if not is_safe_path(lib_dir.as_posix(), sfile.as_posix()): msg = 'Path Traversal Detected!' return print_n_send_error_response(request, msg) - ext = sfile.suffix + if not ext and 'Frameworks' in relative_path: + # Force Dylib on Frameworks + ext = '.dylib' if not sfile.exists(): msg = 'Library File not found' return print_n_send_error_response(request, msg) diff --git a/mobsf/StaticAnalyzer/views/ios/static_analyzer.py b/mobsf/StaticAnalyzer/views/ios/static_analyzer.py index bf513c4997..8cc547646c 100755 --- a/mobsf/StaticAnalyzer/views/ios/static_analyzer.py +++ b/mobsf/StaticAnalyzer/views/ios/static_analyzer.py @@ -95,6 +95,9 @@ def static_analyzer_ios(request, checksum, api=False): api) file_type = robj[0].SCAN_TYPE filename = robj[0].FILE_NAME + if file_type == 'dylib' and not Path(filename).suffix: + # Force dylib extension on Frameworks + filename = f'{filename}.dylib' allowed_exts = ('ios', '.ipa', '.zip', '.dylib', '.a') allowed_typ = [i.replace('.', '') for i in allowed_exts] if (not filename.lower().endswith(allowed_exts) diff --git a/mobsf/templates/pdf/ios_report.html b/mobsf/templates/pdf/ios_report.html index c0aca19435..ce8f4219da 100644 --- a/mobsf/templates/pdf/ios_report.html +++ b/mobsf/templates/pdf/ios_report.html @@ -516,14 +516,14 @@

IPA BINARY ANALYSIS

{% endif %} - - {% if dylib_analysis and app_type not in 'A' %} -

DYNAMIC LIBRARY BINARY ANALYSIS

+ {% if app_type not in 'A' %} + {% if dylib_analysis or framework_analysis %} +

DYNAMIC LIBRARY & FRAMEWORK BINARY ANALYSIS

- + @@ -568,28 +568,7 @@

DYNAMIC LIBRARY BINARY ANALYSIS


{{dy.symbol.description}} {% endfor %} - -
NODYLIBDYLIB/FRAMEWORK NX STACK CANARY ARC
- {% endif %} - - {% if framework_analysis and app_type not in 'A' %} -

FRAMEWORK BINARY ANALYSIS

- - - - - - - - - - - - - - - - {% for frm in framework_analysis %} + {% for frm in framework_analysis %} @@ -623,10 +602,10 @@

FRAMEWORK BINARY ANALYSIS


{{frm.symbol.description}} {% endfor %} - -
NOFRAMEWORKNXSTACK CANARYARCRPATHCODE SIGNATUREENCRYPTEDSYMBOLS STRIPPED
{{ forloop.counter }} {{frm.name}}
- {% endif %} - + + + {% endif %} + {% endif %} {% if app_type in 'A' %}

STATIC LIBRARY BINARY ANALYSIS

diff --git a/mobsf/templates/static_analysis/ios_binary_analysis.html b/mobsf/templates/static_analysis/ios_binary_analysis.html index 15299baed6..a4a8755c5c 100755 --- a/mobsf/templates/static_analysis/ios_binary_analysis.html +++ b/mobsf/templates/static_analysis/ios_binary_analysis.html @@ -119,13 +119,7 @@ - {% endif %} @@ -925,7 +919,7 @@
{{ binary_analysis.summary.suppressed }}

- DYNAMIC LIBRARY BINARY ANALYSIS + DYNAMIC LIBRARY & FRAMEWORK BINARY ANALYSIS

@@ -933,7 +927,7 @@
{{ binary_analysis.summary.suppressed }}
{% if app_type not in 'Dylib' %} - + {% endif %} @@ -945,8 +939,8 @@
{{ binary_analysis.summary.suppressed }}
- {% if not dylib_analysis %} - No Dylibs found. + {% if not dylib_analysis and not framework_analysis %} + No Dylibs/Frameworks found. {% endif %} {% for dy in dylib_analysis %} @@ -986,51 +980,12 @@
{{ binary_analysis.summary.suppressed }}

{{dy.symbol.description}} {% endfor %} - -
NODYLIBDYLIB/FRAMEWORKNX STACK CANARY
-
- -
-
- - - - - - - -
-
-
-
-
-
-

- FRAMEWORK BINARY ANALYSIS -

-
- - - - - - - - - - - - - - - - {% if not framework_analysis %} - No Frameworks found. - {% endif %} {% for frm in framework_analysis %} - +
NOFRAMEWORKNXSTACK CANARYARCRPATHCODE SIGNATUREENCRYPTEDSYMBOLS STRIPPED
{{ forloop.counter }}{{frm.name}}{{frm.name | relative_path}}
+ Analyze +
{{frm.nx.has_nx}}
{{frm.nx.severity}} @@ -1072,7 +1027,7 @@
{{ binary_analysis.summary.suppressed }}
- + {% endif %} {% if app_type in 'A' %}