Skip to content

Commit

Permalink
Add aapt/aapt2 support
Browse files Browse the repository at this point in the history
  • Loading branch information
ajinabraham committed Nov 28, 2024
1 parent 2433c27 commit 4b92911
Showing 1 changed file with 148 additions and 0 deletions.
148 changes: 148 additions & 0 deletions mobsf/StaticAnalyzer/views/android/aapt.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,148 @@
# -*- coding: utf_8 -*-
"""Use aapt2 to extract APK features."""
import re
import logging
import subprocess
from pathlib import Path

from django.conf import settings

from mobsf.MobSF.utils import (
find_aapt,
)

logger = logging.getLogger(__name__)


class AndroidAAPT:

def __init__(self, apk_path):
self.aapt2_path = None
self.aapt_path = None
self.apk_path = apk_path
self.data = {
'permissions': [],
'uses_features': {},
'package': None,
'application_label': None,
'application_icon': None,
'launchable_activity': None,
'min_sdk_version': None,
'target_sdk_version': None,
}

# Check for custom AAPT2 path in settings
if (getattr(settings, 'AAPT2_BINARY', '')
and len(settings.AAPT2_BINARY) > 0
and Path(settings.AAPT2_BINARY).exists()):
self.aapt2_path = settings.AAPT2_BINARY
else:
self.aapt2_path = find_aapt('aapt2')

# Check for custom AAPT path in settings
if (getattr(settings, 'AAPT_BINARY', '')
and len(settings.AAPT_BINARY) > 0
and Path(settings.AAPT_BINARY).exists()):
self.aapt_path = settings.AAPT_BINARY
else:
self.aapt_path = find_aapt('aapt')

# Ensure at least one tool is available
if not (self.aapt2_path and self.aapt_path):
raise FileNotFoundError('aapt and aapt2 found')

def _execute_command(self, args):
try:
out = subprocess.check_output(
args,

Check failure

Code scanning / CodeQL

Uncontrolled command line Critical

This command line depends on a
user-provided value
.
This command line depends on a
user-provided value
.
stderr=subprocess.STDOUT)
return out.decode('utf-8', errors='ignore')
except subprocess.CalledProcessError as e:
logger.warning(e.output)
return None

def _get_strings(self, output):
# Regex to match strings while ignoring paths (strings without slashes)
pattern = r'String #[\d]+ : ([^\/\n]+)'
matches = re.findall(pattern, output)
# Strip whitespace and return the extracted strings
return [match.strip() for match in matches]

def _parse_badging(self, output):
# Match the package information
package_match = re.search(r'package: name=\'([\w\.]+)\'', output)
if package_match:
self.data['package'] = package_match.group(1)

# Match permissions
permissions = re.findall(r'uses-permission: name=\'([\w\.]+)\'', output)
if permissions:
self.data['permissions'] = permissions

# Match minSdkVersion
min_sdk_match = re.search(r'minSdkVersion:\'(\d+)\'', output)
if min_sdk_match:
self.data['min_sdk_version'] = min_sdk_match.group(1)

# Match targetSdkVersion
target_sdk_match = re.search(r'targetSdkVersion:\'(\d+)\'', output)
if target_sdk_match:
self.data['target_sdk_version'] = target_sdk_match.group(1)

# Match application label
label_match = re.search(r'application-label(?:-[\w\-]+)?:\'([^\']+)\'', output)
if label_match:
self.data['application_label'] = label_match.group(1)

# Match application icon
icon_match = re.search(r'application:.*icon=\'([^\']+)\'', output)
if icon_match:
self.data['application_icon'] = icon_match.group(1)

# Match launchable activity
activity_match = re.search(r'launchable-activity: name=\'([\w\.]+)\'', output)
if activity_match:
self.data['launchable_activity'] = activity_match.group(1)

# Match used features
features = {}
feature_matches = re.findall(
(r'(uses-feature(?:-not-required)?|uses-implied-feature): '
r'name=\'([\w\.]+)\'(?: reason=\'([^\']+)\')?'),
output,
)
for feature_type, feature_name, reason in feature_matches:
features[feature_name] = {
'type': feature_type,
# e.g., 'uses-feature',
# 'uses-feature-not-required',
# 'uses-implied-feature'
'reason': reason if reason else 'No reason provided',
}
self.data['uses_features'] = features

return self.data

def get_apk_files(self):
"""List all files in the APK."""
output = self._execute_command(
[self.aapt_path, 'list', self.apk_path])
if output:
return output.splitlines()
return []

def get_apk_strings(self):
"""Extract strings from the APK."""
output = self._execute_command(
[self.aapt2_path, 'dump', 'strings', self.apk_path])
if output:
return self._get_strings(output)
return []

def get_apk_features(self):
"""Extract features from the APK."""
output = self._execute_command(
[self.aapt2_path, 'dump', 'badging', self.apk_path])
if output:
return self._parse_badging(output)
return self.data

0 comments on commit 4b92911

Please sign in to comment.