Comprehensive Python toolkit for Android application reverse engineering and security analysis.
—
Quality
Pending
Does it follow best practices?
Impact
Pending
No eval scenarios have been run
Advanced static analysis capabilities including control flow analysis, call graph generation, method analysis, and cross-reference detection. The Analysis class provides comprehensive static analysis of Android applications.
The main analysis engine that provides comprehensive static analysis capabilities for Android applications.
class Analysis:
def __init__(self, *args):
"""
Initialize analysis engine.
Parameters:
- *args: DEX objects to analyze (can be multiple DEX files)
"""
def get_classes(self) -> list:
"""Return list of all ClassAnalysis objects."""
def get_methods(self) -> list:
"""Return list of all MethodAnalysis objects."""
def get_fields(self) -> list:
"""Return list of all FieldAnalysis objects."""
def get_strings(self) -> list:
"""Return list of all StringAnalysis objects."""
def find_classes(self, class_name: str = ".*", no_external: bool = False) -> list:
"""
Find classes matching pattern.
Parameters:
- class_name: Regular expression pattern for class names
- no_external: Exclude external classes if True
Returns:
List of matching ClassAnalysis objects
"""
def find_methods(self, class_name: str = ".*", method_name: str = ".*", descriptor: str = ".*", accessflags: str = ".*", no_external: bool = False) -> list:
"""
Find methods matching criteria.
Parameters:
- class_name: Class name pattern
- method_name: Method name pattern
- descriptor: Method descriptor pattern
- accessflags: Access flags pattern
- no_external: Exclude external methods if True
Returns:
List of matching MethodAnalysis objects
"""
def find_strings(self, string: str = ".*") -> list:
"""
Find strings matching pattern.
Parameters:
- string: Regular expression pattern
Returns:
List of matching StringAnalysis objects
"""
def find_fields(self, class_name: str = ".*", field_name: str = ".*", field_type: str = ".*", accessflags: str = ".*") -> list:
"""
Find fields matching criteria.
Parameters:
- class_name: Class name pattern
- field_name: Field name pattern
- field_type: Field type pattern
- accessflags: Access flags pattern
Returns:
List of matching FieldAnalysis objects
"""Generate and analyze call graphs showing method invocation relationships.
def get_call_graph(self, classname: str = ".*", methodname: str = ".*", descriptor: str = ".*", accessflags: str = ".*", no_isolated: bool = False, entry_points: list = None):
"""
Generate call graph for methods matching criteria.
Parameters:
- classname: Class name filter pattern
- methodname: Method name filter pattern
- descriptor: Method descriptor pattern
- accessflags: Access flags pattern
- no_isolated: Exclude isolated nodes
- entry_points: List of entry point methods
Returns:
NetworkX DiGraph object representing call graph
"""
def get_method(self, method) -> object:
"""
Get MethodAnalysis for EncodedMethod.
Parameters:
- method: EncodedMethod object
Returns:
MethodAnalysis object or None
"""
def get_method_by_idx(self, idx: int) -> object:
"""Get MethodAnalysis by index."""
def get_class_analysis(self, class_def) -> object:
"""
Get ClassAnalysis for ClassDefItem.
Parameters:
- class_def: ClassDefItem object
Returns:
ClassAnalysis object or None
"""
def get_field_analysis(self, field) -> object:
"""Get FieldAnalysis for EncodedField."""Detailed analysis of individual methods including control flow and cross-references.
class MethodAnalysis:
def get_method(self):
"""Return associated EncodedMethod object."""
def get_name(self) -> str:
"""Return method name."""
def get_descriptor(self) -> str:
"""Return method descriptor."""
def get_class_name(self) -> str:
"""Return containing class name."""
def get_access_flags_string(self) -> str:
"""Return access flags as readable string."""
def is_external(self) -> bool:
"""Return True if method is external/system method."""
def is_android_api(self) -> bool:
"""Return True if method is Android API."""
def get_length(self) -> int:
"""Return method code length."""
def get_basic_blocks(self) -> object:
"""
Get basic blocks for method.
Returns:
BasicBlocks object containing control flow blocks
"""
def get_xref_from(self) -> list:
"""
Get cross-references from this method.
Returns:
List of (ClassAnalysis, MethodAnalysis, offset) tuples
"""
def get_xref_to(self) -> list:
"""
Get cross-references to this method.
Returns:
List of (ClassAnalysis, MethodAnalysis, offset) tuples
"""
def get_xref_new_instance(self) -> list:
"""Get cross-references for new-instance instructions."""
def get_xref_const_class(self) -> list:
"""Get cross-references for const-class instructions."""Comprehensive analysis of classes and their relationships.
class ClassAnalysis:
def get_class(self):
"""Return associated ClassDefItem object."""
def get_name(self) -> str:
"""Return class name."""
def get_superclass_name(self) -> str:
"""Return superclass name."""
def get_interfaces(self) -> list[str]:
"""Return list of implemented interface names."""
def get_access_flags_string(self) -> str:
"""Return access flags as readable string."""
def is_external(self) -> bool:
"""Return True if class is external/system class."""
def is_android_api(self) -> bool:
"""Return True if class is Android API."""
def get_methods(self) -> list:
"""
Get all methods in class.
Returns:
List of MethodAnalysis objects
"""
def get_fields(self) -> list:
"""
Get all fields in class.
Returns:
List of FieldAnalysis objects
"""
def get_xref_from(self) -> list:
"""Get cross-references from this class."""
def get_xref_to(self) -> list:
"""Get cross-references to this class."""
def get_xref_new_instance(self) -> list:
"""Get new-instance references to this class."""
def get_xref_const_class(self) -> list:
"""Get const-class references to this class."""Analysis of field usage and cross-references.
class FieldAnalysis:
def get_field(self):
"""Return associated EncodedField object."""
def get_name(self) -> str:
"""Return field name."""
def get_descriptor(self) -> str:
"""Return field type descriptor."""
def get_class_name(self) -> str:
"""Return containing class name."""
def get_access_flags_string(self) -> str:
"""Return access flags as readable string."""
def is_external(self) -> bool:
"""Return True if field is external."""
def get_xref_read(self) -> list:
"""
Get cross-references for field reads.
Returns:
List of (ClassAnalysis, MethodAnalysis, offset) tuples
"""
def get_xref_write(self) -> list:
"""
Get cross-references for field writes.
Returns:
List of (ClassAnalysis, MethodAnalysis, offset) tuples
"""Analysis of string usage throughout the application.
class StringAnalysis:
def get_orig_value(self) -> str:
"""Return original string value."""
def get_value(self) -> str:
"""Return string value (may be modified)."""
def set_value(self, value: str) -> None:
"""Set new string value."""
def get_xref_from(self) -> list:
"""
Get cross-references from this string.
Returns:
List of (ClassAnalysis, MethodAnalysis, offset) tuples where string is used
"""Detailed control flow analysis with basic blocks and exception handling.
class BasicBlocks:
def get(self) -> list:
"""Return list of all basic blocks."""
def get_basic_block(self, idx: int):
"""Get basic block by index."""
def gets(self) -> list:
"""Return list of basic blocks with extra information."""
class DEXBasicBlock:
def get_name(self) -> str:
"""Return basic block name."""
def get_start(self) -> int:
"""Return start offset of block."""
def get_end(self) -> int:
"""Return end offset of block."""
def get_last_length(self) -> int:
"""Return length of last instruction."""
def get_nb_instructions(self) -> int:
"""Return number of instructions in block."""
def get_instructions(self) -> list:
"""Return list of instructions in basic block."""
def get_exception_analysis(self):
"""Return exception analysis for this block."""
def get_childs(self) -> list:
"""
Get child basic blocks.
Returns:
List of (next_block, condition) tuples
"""
def get_fathers(self) -> list:
"""
Get parent basic blocks.
Returns:
List of (prev_block, condition) tuples
"""Analysis of exception handling and flow.
class ExceptionAnalysis:
def get_exception(self):
"""Return exception object."""
def get_catches(self) -> list:
"""Return list of catch handlers."""
def show_source(self) -> str:
"""Return formatted source representation."""
class Exceptions:
def get_exception(self, addr_start: int, addr_end: int) -> list:
"""
Get exceptions for address range.
Parameters:
- addr_start: Start address
- addr_end: End address
Returns:
List of exception handlers
"""from androguard.core.dex import DEX
from androguard.core.analysis.analysis import Analysis
# Load and analyze DEX
dex = DEX(open("classes.dex", "rb").read())
dx = Analysis(dex)
# Get analysis statistics
print(f"Classes: {len(dx.get_classes())}")
print(f"Methods: {len(dx.get_methods())}")
print(f"Fields: {len(dx.get_fields())}")
print(f"Strings: {len(dx.get_strings())}")
# Find specific classes
activity_classes = dx.find_classes(r".*Activity$")
print(f"Activity classes: {len(activity_classes)}")
for cls in activity_classes:
print(f" {cls.get_name()}")# Find onCreate methods
oncreate_methods = dx.find_methods(method_name="onCreate")
print(f"Found {len(oncreate_methods)} onCreate methods")
for method in oncreate_methods:
print(f"\nMethod: {method.get_class_name()}.{method.get_name()}")
# Get methods called by this method
xrefs_from = method.get_xref_from()
if xrefs_from:
print(" Calls to:")
for ref_class, ref_method, offset in xrefs_from:
print(f" {ref_class.get_name()}.{ref_method.get_name()} at offset {offset}")
# Get methods that call this method
xrefs_to = method.get_xref_to()
if xrefs_to:
print(" Called by:")
for ref_class, ref_method, offset in xrefs_to:
print(f" {ref_class.get_name()}.{ref_method.get_name()} at offset {offset}")# Find strings containing sensitive keywords
sensitive_strings = dx.find_strings(r".*(password|secret|key|token).*")
print(f"Found {len(sensitive_strings)} sensitive strings")
for string_analysis in sensitive_strings:
string_value = string_analysis.get_value()
print(f"\nString: '{string_value}'")
# Find where this string is used
xrefs = string_analysis.get_xref_from()
if xrefs:
print(" Used in:")
for ref_class, ref_method, offset in xrefs:
print(f" {ref_class.get_name()}.{ref_method.get_name()} at offset {offset}")import networkx as nx
import matplotlib.pyplot as plt
# Generate call graph for specific class
call_graph = dx.get_call_graph(classname=r".*MainActivity.*")
print(f"Call graph nodes: {call_graph.number_of_nodes()}")
print(f"Call graph edges: {call_graph.number_of_edges()}")
# Find most called methods
in_degree = dict(call_graph.in_degree())
most_called = sorted(in_degree.items(), key=lambda x: x[1], reverse=True)[:10]
print("Most called methods:")
for method, count in most_called:
print(f" {method}: {count} calls")
# Find methods that call the most other methods
out_degree = dict(call_graph.out_degree())
most_calling = sorted(out_degree.items(), key=lambda x: x[1], reverse=True)[:10]
print("Methods with most outgoing calls:")
for method, count in most_calling:
print(f" {method}: {count} calls")# Analyze control flow for specific method
target_method = dx.find_methods(class_name=r".*MainActivity.*", method_name="onCreate")[0]
if target_method:
basic_blocks = target_method.get_basic_blocks()
print(f"Method: {target_method.get_name()}")
print(f"Basic blocks: {len(basic_blocks.get())}")
for i, bb in enumerate(basic_blocks.get()):
print(f"\nBasic Block {i}:")
print(f" Start: {bb.get_start():04x}")
print(f" End: {bb.get_end():04x}")
print(f" Instructions: {bb.get_nb_instructions()}")
# Get child blocks (control flow)
children = bb.get_childs()
if children:
print(" Children:")
for child, condition in children:
print(f" Block at {child.get_start():04x} (condition: {condition})")
# Check for exception handling
exception_analysis = bb.get_exception_analysis()
if exception_analysis:
print(" Exception handling present")# Find field access patterns
password_fields = dx.find_fields(field_name=r".*[Pp]assword.*")
print(f"Found {len(password_fields)} password-related fields")
for field in password_fields:
print(f"\nField: {field.get_class_name()}.{field.get_name()}")
print(f" Type: {field.get_descriptor()}")
# Get field read operations
reads = field.get_xref_read()
if reads:
print(" Read by:")
for ref_class, ref_method, offset in reads:
print(f" {ref_class.get_name()}.{ref_method.get_name()} at {offset}")
# Get field write operations
writes = field.get_xref_write()
if writes:
print(" Written by:")
for ref_class, ref_method, offset in writes:
print(f" {ref_class.get_name()}.{ref_method.get_name()} at {offset}")# Find crypto-related methods
crypto_methods = dx.find_methods(
class_name=r".*(Crypto|Cipher|Hash|Encrypt|Decrypt).*",
method_name=r".*(encrypt|decrypt|hash|sign|verify).*"
)
print(f"Found {len(crypto_methods)} crypto-related methods")
for method in crypto_methods:
print(f" {method.get_class_name()}.{method.get_name()}")
# Find network-related classes
network_classes = dx.find_classes(r".*(Http|Network|Socket|URL).*")
print(f"Found {len(network_classes)} network-related classes")
# Find potentially obfuscated methods (short names)
obfuscated_methods = dx.find_methods(method_name=r"^[a-c]$")
print(f"Potentially obfuscated methods: {len(obfuscated_methods)}")
# Find native methods
native_methods = dx.find_methods(accessflags=r".*native.*")
print(f"Native methods: {len(native_methods)}")def is_ascii_obfuscation(vm) -> bool:
"""
Detect ASCII-based obfuscation in DEX file.
Parameters:
- vm: DEX object to analyze
Returns:
True if ASCII obfuscation patterns detected
"""Install with Tessl CLI
npx tessl i tessl/pypi-androguard