Skip to content

Commit

Permalink
Merge pull request #1 from nbeguier/1.3.0
Browse files Browse the repository at this point in the history
1.3.0
  • Loading branch information
Nicolas Béguier authored Feb 16, 2023
2 parents c97c040 + bd221d3 commit 2a62804
Show file tree
Hide file tree
Showing 4 changed files with 332 additions and 91 deletions.
147 changes: 96 additions & 51 deletions Android_Activity_Swagger.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
"""
Android Activity Swagger
Copyright (C) 2020-2021 Nicolas Beguier
Copyright (C) 2020-2023 Nicolas Beguier
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published
Expand All @@ -23,19 +23,20 @@
# Standard library imports
from argparse import ArgumentParser
import json
import os
from pathlib import Path
import re
import xml.etree.ElementTree as ET

# Debug
# from pdb import set_trace as st

VERSION = '1.2.0'
VERSION = '1.3.0'

def update_class(current_class, line):
"""
Updates the current class if a new is found in line
"""
match = re.search("(public|private) [a-zA-Z\ ]+ ([a-zA-Z]+)\(", line)
match = re.search('(public|private) [a-zA-Z\\ ]+ ([a-zA-Z]+)\\(', line)
if match:
return match.group(2)
return current_class
Expand All @@ -44,19 +45,23 @@ def print_extras(context, key=None):
"""
Display found extra
"""
color_line = re.sub("(get|has)[a-zA-Z]*Extras?", lambda m: "\x1b[38;5;75m{}\x1b[0m".format(m.group()), context['line'][:-1])
color_line = re.sub(
'(get|has)[a-zA-Z]*Extras?', lambda m: f'\x1b[38;5;75m{m.group()}\x1b[0m',
context['line'][:-1])
if key is not None:
color_line = re.sub(key, lambda m: "\x1b[38;5;76m{}\x1b[0m".format(m.group()), color_line)
print("[{}:+{}] [{}] {}".format(context['activity_file_path'], context['line_n'], context['current_class'], color_line))
color_line = re.sub(key, lambda m: f'\x1b[38;5;76m{m.group()}\x1b[0m', color_line)
print(f"[{context['activity_file_path']}:+{context['line_n']}] [{context['current_class']}] {color_line}")
return True

def print_data(context):
"""
Display found data
"""
if re.search("\.getData[a-zA-Z]*\(", context['line']):
color_line = re.sub("getData[a-zA-Z]*", lambda m: "\x1b[38;5;76m{}\x1b[0m".format(m.group()), context['line'][:-1])
print("[{}:+{}] [{}] {}".format(context['activity_file_path'], context['line_n'], context['current_class'], color_line))
if re.search('\\.getData[a-zA-Z]*\\(', context['line']):
color_line = re.sub(
'getData[a-zA-Z]*', lambda m: f'\x1b[38;5;76m{m.group()}\x1b[0m',
context['line'][:-1])
print(f"[{context['activity_file_path']}:+{context['line_n']}] [{context['current_class']}] {color_line}")
return True
return False

Expand All @@ -66,8 +71,8 @@ def update_parent(value, line, activity_name):
"""
if value is not None:
return value
if "class {}".format(activity_name) in line and "extends" in line:
return line.split("extends")[1].split()[0]
if f'class {activity_name}' in line and 'extends' in line:
return line.split('extends')[1].split()[0]
return None

def update_swagger(context, swagger):
Expand All @@ -78,74 +83,105 @@ def update_swagger(context, swagger):

if context['current_class'] in swagger['_parsing']:
for var in swagger['_parsing'][context['current_class']]:
type_match = re.search("{}\.get([a-zA-Z]+)\(".format(var), context['line'])
type_match = re.search(f'{var}\\.get([a-zA-Z]+)\\(', context['line'])
if type_match:
key_type = type_match.group(1)
key_name = '_unknown'
key_match = re.search("{}\.get[a-zA-Z]+\(\"?([a-zA-Z\_\.]+)\"?".format(var), context['line'])
key_match = re.search(
f'{var}\\.get[a-zA-Z]+\\(\"?([a-zA-Z_\\.]+)\"?',
context['line'])
if key_match:
key_name = key_match.group(1)
if key_type not in swagger['_result']:
swagger['_result'][key_type] = list()
swagger['_result'][key_type] = []
if key_name not in swagger['_result'][key_type]:
swagger['_result'][key_type].append(key_name)
print_extras(context, key=key_name)
added = True

# X = y.getExtras()
getextras_match = re.search("([a-zA-Z\.]+) = [a-zA-Z\.\(\)]+\.getExtras\(\)", context['line'])
getextras_match = re.search(
'([a-zA-Z\\.]+) = [a-zA-Z\\.\\(\\)]+\\.getExtras\\(\\)',
context['line'])
if getextras_match:
var = getextras_match.group(1)
if context['current_class'] not in swagger['_parsing']:
swagger['_parsing'][context['current_class']] = dict()
swagger['_parsing'][context['current_class']] = {}
if var not in swagger['_parsing'][context['current_class']]:
swagger['_parsing'][context['current_class']][var] = ''

# .getXExtra("Y"
getextra_match = re.search("\.get([a-zA-Z]+)Extra\(", context['line'])
getextra_match = re.search('\\.get([a-zA-Z]+)Extra\\(', context['line'])
if getextra_match:
key_type = getextra_match.group(1)
key_name = '_unknown'
key_match = re.search("\.get[a-zA-Z]+\(\"?([a-zA-Z\_\.]+)\"?", context['line'])
key_match = re.search('\\.get[a-zA-Z]+\\(\"?([a-zA-Z_\\.]+)\"?', context['line'])
if key_match:
key_name = key_match.group(1)
if key_type not in swagger['_result']:
swagger['_result'][key_type] = list()
swagger['_result'][key_type] = []
if key_name not in swagger['_result'][key_type]:
swagger['_result'][key_type].append(key_name)
print_extras(context, key=key_name)
added = True

if not added and re.search("\.(get|has)[a-zA-Z]*Extras?\(", context['line']):
if not added and re.search('\\.(get|has)[a-zA-Z]*Extras?\\(', context['line']):
print_extras(context)

return swagger

def get_activity_params(activity, swagger, is_recursive=False, verbosity=False):
def read_manifest(manifest_file, verbosity=False):
"""
Reads the AndroidManifest.xml and extract exported activities
"""
manifest_path = Path(manifest_file)
if not manifest_path.exists():
print(f'"{manifest_file}" cannot be found...')
return

tree = ET.parse(manifest_path)
root = tree.getroot()

print(f'Package: {root.get("package")}')
print('')

exported_activities = []

for activity in root.findall('.//activity'):
activity_name = activity.get('{http://schemas.android.com/apk/res/android}name')
is_exported = activity.get('{http://schemas.android.com/apk/res/android}exported')
if is_exported and is_exported.lower() == 'true':
exported_activities.append(activity_name)

for activity in exported_activities:
print(activity)

def get_activity_params(activity, swagger, base_directory, is_recursive=False, verbosity=False):
"""
Returns the Activity parameters
"""
activity_name = activity.split('.')[-1]
# Activity name can be override by a '$'
if '$' in activity_name:
activity_name = activity_name.split('$')[1]
activity_file_path = activity.replace(".", "/").split('$')[0] + ".java"
activity_file_path_str = base_directory + '/' + activity.replace('.', '/').split('$')[0] + '.java'

if not os.path.exists(activity_file_path):
if os.path.exists('sources/'+activity_file_path):
activity_file_path = 'sources/'+activity_file_path
if not Path(activity_file_path_str).exists():
if Path('sources/'+activity_file_path_str).exists():
activity_file_path_str = Path('sources/'+activity_file_path_str)
else:
if not is_recursive:
print("{} doesn't exist !".format(activity_file_path))
print(f"{activity_file_path_str} doesn't exist !")
return
activity_file_path = Path(activity_file_path_str)

if verbosity:
print("Found activity: {}".format(activity))
print("Activity's file path: {}".format(activity_file_path))
print(f'Found activity: {activity}')
print(f'Activity\'s file path: {activity_file_path}')

parent_name = None

with open(activity_file_path, "r") as activity_file:
with activity_file_path.open('r', encoding='utf-8') as activity_file:
line_n = 1
current_class = None
for line in activity_file.readlines():
Expand All @@ -164,15 +200,15 @@ def get_activity_params(activity, swagger, is_recursive=False, verbosity=False):

if parent_name:
if verbosity:
print("Found parent: {}".format(parent_name))
print("")
print(f'Found parent: {parent_name}')
print('')
parent = None
with open(activity_file_path, "r") as activity_file:
with activity_file_path.open('r', encoding='utf-8') as activity_file:
for line in activity_file.readlines():
if ".{};".format(parent_name) in line and line.startswith("import"):
parent = line.split()[1].split(";")[0]
if f'.{parent_name};' in line and line.startswith('import'):
parent = line.split()[1].split(';')[0]
if parent:
get_activity_params(parent, swagger, is_recursive=True, verbosity=verbosity)
get_activity_params(parent, swagger, base_directory, is_recursive=True, verbosity=verbosity)

def print_adb_helper(swagger, activity, package):
"""
Expand Down Expand Up @@ -212,6 +248,7 @@ def print_adb_helper(swagger, activity, package):
'Long': '--el',
'Float': '--ef',
'data-uri': '-d',
'StringArray': '--esa',
}
adb_default_value_map = {
'String': '"some_string"',
Expand All @@ -220,10 +257,11 @@ def print_adb_helper(swagger, activity, package):
'Long': '0',
'Float': '0',
'data-uri': 'https://github.com/nbeguier/',
'StringArray': '"some_string","some_other_string"',
}
for extra_type in swagger:
# Ignore extra types
if extra_type in ['Parcelable']:
if extra_type in ['Parcelable', 'Bundle']:
continue
# Data-uri type
if extra_type == 'data-uri':
Expand All @@ -246,21 +284,28 @@ def main():
'activity',
action='store',
help='Activity')
parser.add_argument("--package", "-p", action="store",
help="Package.")
parser.add_argument("--adb", "-a", action="store_true",
default=False, help="ADB helper.")
parser.add_argument("--verbose", "-v", action="store_true",
default=False, help="Verbose output.")
parser.add_argument('--package', '-p', action='store',
help='Package.')
parser.add_argument('--adb', '-a', action='store_true',
default=False, help='ADB helper.')
parser.add_argument('--verbose', '-v', action='store_true',
default=False, help='Verbose output.')
parser.add_argument('--read-manifest', '-r', action='store_true',
default=False, help='Read AndroidManifest.xml and extract exported activities.')
parser.add_argument('--directory', '-d', action='store',
default='.', help='Base directory')
args = parser.parse_args()
swagger = {'_result': dict(), '_parsing': dict()}
get_activity_params(args.activity, swagger, verbosity=args.verbose)
print(json.dumps(swagger['_result'], sort_keys=True, indent=4, separators=(',', ': ')))
if args.adb:
if not args.package:
print('You should define the package to view ADB helper')
else:
print_adb_helper(swagger['_result'], args.activity, args.package)
if args.read_manifest:
read_manifest(args.activity, verbosity=args.verbose)
else:
swagger = {'_result': {}, '_parsing': {}}
get_activity_params(args.activity, swagger, args.directory, verbosity=args.verbose)
print(json.dumps(swagger['_result'], sort_keys=True, indent=4, separators=(',', ': ')))
if args.adb:
if not args.package:
print('You should define the package to view ADB helper')
else:
print_adb_helper(swagger['_result'], args.activity, args.package)

if __name__ == '__main__':
main()
Loading

0 comments on commit 2a62804

Please sign in to comment.