mirror of
https://github.com/MobSF/Mobile-Security-Framework-MobSF.git
synced 2026-10-12 02:06:59 +00:00
* 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
226 lines
7.7 KiB
Python
Executable File
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
|