Files
Mobile-Security-Framework-M…/mobsf/StaticAnalyzer/views/android/binary_analysis.py
T
Ajin Abraham 2854114b28 [HOTFIX][EFR06] Independent Shared Object (.so) Scan and Improved String search (#2228)
* String extraction from APK, Source, AAR, JAR, SO
* Strings sections to show source of strings extracted
* Strings Refactor
* Support for independent .SO scan
* Android SCA rules update
* Entropies scan support for strings
* URLs/Email extraction refactor
* Bug Fixes
  * iOS Source Report Fix
  * Frida APK Patcher (WIP)
  * Dynamic Analyzer identifier not available
  * Settings env var not working fix for enabled by default features
  * AppSec Score fix
  * Recent `scan not completed` fix for iOS zip
2023-08-14 11:47:55 -07:00

226 lines
7.7 KiB
Python
Executable File

# !/usr/bin/python
# coding=utf-8
import logging
from pathlib import Path
import lief
from mobsf.MobSF.utils import (
settings_enabled,
)
logger = logging.getLogger(__name__)
class Checksec:
def __init__(self, elf_file, so_rel):
self.elf_path = elf_file.as_posix()
self.elf_rel = so_rel
self.elf = lief.parse(self.elf_path)
def checksec(self):
elf_dict = {}
elf_dict['name'] = self.elf_rel
if not self.is_elf(self.elf_path):
return
is_nx = self.is_nx()
if is_nx:
severity = 'info'
desc = (
'The shared object has NX bit set. This marks a '
'memory page non-executable making attacker '
'injected shellcode non-executable.')
else:
severity = 'high'
desc = (
'The shared object does not have NX bit set. NX bit '
'offer protection against exploitation of memory corruption '
'vulnerabilities by marking memory page as non-executable. '
'Use option --noexecstack or -z noexecstack to mark stack as '
'non executable.')
elf_dict['nx'] = {
'is_nx': is_nx,
'severity': severity,
'description': desc,
}
has_canary = self.has_canary()
if has_canary:
severity = 'info'
desc = (
'This shared object has a stack canary value '
'added to the stack so that it will be overwritten by '
'a stack buffer that overflows the return address. '
'This allows detection of overflows by verifying the '
'integrity of the canary before function return.')
else:
severity = 'high'
desc = (
'This shared object does not have a stack '
'canary value added to the stack. Stack canaries '
'are used to detect and prevent exploits from '
'overwriting return address. Use the option '
'-fstack-protector-all to enable stack canaries.')
elf_dict['stack_canary'] = {
'has_canary': has_canary,
'severity': severity,
'description': desc,
}
rpath = self.rpath()
if rpath:
severity = 'high'
desc = (
'The shared object has RPATH set. In certain cases '
'an attacker can abuse this feature to run arbitrary '
'shared objects for code execution and privilege '
'escalation. The only time a shared library in '
'should set RPATH is if it is linked to private '
'shared libraries in the same package. Remove the '
'compiler option -rpath to remove RPATH.')
rpt = rpath.rpath
else:
severity = 'info'
desc = (
'The shared object does not have run-time search path '
'or RPATH set.')
rpt = rpath
elf_dict['rpath'] = {
'rpath': rpt,
'severity': severity,
'description': desc,
}
runpath = self.runpath()
if runpath:
severity = 'high'
desc = (
'The shared object has RUNPATH set. In certain cases '
'an attacker can abuse this feature and or modify '
'environment variables to run arbitrary '
'shared objects for code execution and privilege '
'escalation. The only time a shared library in should '
'set RUNPATH is if it is linked to private shared '
'libraries in the same package. Remove the compiler '
'option --enable-new-dtags,-rpath to remove RUNPATH.')
rnp = runpath.runpath
else:
severity = 'info'
desc = (
'The shared object does not have RUNPATH set.')
rnp = runpath
elf_dict['runpath'] = {
'runpath': rnp,
'severity': severity,
'description': desc,
}
fortified_functions = self.fortify()
if fortified_functions:
severity = 'info'
desc = ('The shared object has the '
f'following fortified functions: {fortified_functions}')
else:
severity = 'warning'
desc = ('The shared object does not have any '
'fortified functions. Fortified functions '
'provides buffer overflow checks against '
'glibc\'s commons insecure functions like '
'strcpy, gets etc. Use the compiler option '
'-D_FORTIFY_SOURCE=2 to fortify functions.')
elf_dict['fortify'] = {
'is_fortified': bool(fortified_functions),
'severity': severity,
'description': desc,
}
is_stripped = self.is_symbols_stripped()
if is_stripped:
severity = 'info'
desc = 'Symbols are stripped.'
else:
severity = 'warning'
desc = 'Symbols are available.'
elf_dict['symbol'] = {
'is_stripped': is_stripped,
'severity': severity,
'description': desc,
}
return elf_dict
def is_elf(self, elf_path):
return lief.is_elf(elf_path)
def is_nx(self):
return self.elf.has_nx
def has_canary(self):
for symbol in ('__stack_chk_fail',
'__intel_security_cookie'):
try:
if self.elf.get_symbol(symbol):
return True
except lief.not_found:
pass
return False
def rpath(self):
try:
rpath = lief.ELF.DYNAMIC_TAGS.RPATH
return self.elf.get(rpath)
except lief.not_found:
return False
def runpath(self):
try:
runpath = lief.ELF.DYNAMIC_TAGS.RUNPATH
return self.elf.get(runpath)
except lief.not_found:
return False
def is_symbols_stripped(self):
for i in self.elf.static_symbols:
if i:
return False
return True
def fortify(self):
fortified_funcs = []
for function in self.elf.symbols:
if function.name.endswith('_chk'):
fortified_funcs.append(function.name)
return fortified_funcs
def strings(self):
return self.elf.strings
def elf_analysis(app_dir: str) -> dict:
"""Perform elf analysis on shared object."""
elf = {'elf_analysis': [], 'elf_strings': []}
try:
if not settings_enabled('SO_ANALYSIS_ENABLED'):
return elf
logger.info('Binary Analysis Started')
sdir = Path(app_dir)
# Supports APK, SO, AAR and JAR
libs = [
sdir,
sdir / 'lib',
sdir / 'libs',
sdir / 'jni']
for lib_dir in libs:
if not lib_dir.is_dir():
continue
for sofile in lib_dir.rglob('*.so'):
so_rel = (
f'{sofile.parents[1].name}/'
f'{sofile.parents[0].name}/'
f'{sofile.name}')
logger.info('Analyzing %s', so_rel)
chk = Checksec(sofile, so_rel)
elf_find = chk.checksec()
if elf_find:
elf['elf_analysis'].append(
elf_find)
elf['elf_strings'].append(
{so_rel: chk.strings()})
except Exception:
logger.exception('Performing Binary Analysis')
return elf