|
# Android Reverse Engineering Automation |
|
# REasy justfile - Streamlined commands for Android app analysis |
|
|
|
set shell := ["bash", "-uc"] |
|
set export := true |
|
set dotenv-load := true |
|
|
|
# Variables |
|
android_sdk := env_var_or_default("ANDROID_SDK_ROOT", env_var_or_default("ANDROID_HOME", "/opt/android-sdk")) |
|
output_dir := "output" |
|
tools_dir := "tools" |
|
frida_scripts_dir := "templates/frida-scripts" |
|
mobsf_port := "8000" |
|
proxy_port := "8080" |
|
|
|
# Default recipe - show available commands |
|
default: |
|
@echo "π§ REasy Android Reverse Engineering Toolkit" |
|
@echo "=============================================" |
|
@echo "" |
|
@just --list |
|
|
|
# === SETUP & INSTALLATION === |
|
|
|
# Check if required tools are installed |
|
[group('setup')] |
|
check-tools: |
|
@echo "π Checking required tools..." |
|
@echo "ADB: $(which adb || echo 'β Not found')" |
|
@echo "Java: $(which java || echo 'β Not found')" |
|
@echo "Python: $(which python3 || echo 'β Not found')" |
|
@echo "apkeep: $(which apkeep || echo 'β Not found - install with: cargo install apkeep')" |
|
@echo "jadx: $(which jadx || echo 'β Not found')" |
|
@echo "apktool: $(ls {{tools_dir}}/apktool.jar 2>/dev/null || echo 'β Not found')" |
|
@echo "mitmproxy: $(which mitmproxy || echo 'β Not found - install with: pip install mitmproxy')" |
|
@echo "frida: $(which frida || echo 'β Not found - install with: pip install frida-tools')" |
|
|
|
# Download and setup analysis tools |
|
[group('setup')] |
|
setup-tools: |
|
mkdir -p {{tools_dir}} |
|
@echo "π₯ Downloading jadx..." |
|
curl -L "https://github.com/skylot/jadx/releases/latest/download/jadx.zip" -o {{tools_dir}}/jadx.zip |
|
unzip -q {{tools_dir}}/jadx.zip -d {{tools_dir}}/jadx |
|
chmod +x {{tools_dir}}/jadx/bin/jadx |
|
@echo "π₯ Downloading apktool..." |
|
curl -L "https://raw.githubusercontent.com/iBotPeaches/Apktool/master/scripts/linux/apktool.jar" -o {{tools_dir}}/apktool.jar |
|
@echo "β
Tools setup complete" |
|
|
|
# Install system dependencies |
|
[group('setup')] |
|
[confirm("Install system packages?")] |
|
install-deps: |
|
#!/usr/bin/env bash |
|
if command -v apt-get >/dev/null; then |
|
sudo apt-get update && sudo apt-get install -y android-tools-adb default-jre python3-pip unzip curl |
|
elif command -v brew >/dev/null; then |
|
brew install android-platform-tools openjdk unzip curl |
|
elif command -v dnf >/dev/null; then |
|
sudo dnf install -y android-tools java-latest-openjdk python3-pip unzip curl |
|
fi |
|
pip3 install frida-tools mitmproxy |
|
|
|
# === DEVICE MANAGEMENT === |
|
|
|
# List connected devices and emulators |
|
[group('device')] |
|
devices: |
|
@echo "π± Connected devices:" |
|
@adb devices -l |
|
|
|
# Check device details |
|
[group('device')] |
|
device-info: |
|
@echo "π Device Information:" |
|
@echo "Model: $(adb shell getprop ro.product.model)" |
|
@echo "Android: $(adb shell getprop ro.build.version.release)" |
|
@echo "API Level: $(adb shell getprop ro.build.version.sdk)" |
|
@echo "Architecture: $(adb shell getprop ro.product.cpu.abi)" |
|
@echo "Root: $(adb shell which su >/dev/null 2>&1 && echo 'β
Available' || echo 'β Not available')" |
|
|
|
# Start Android emulator |
|
[group('device')] |
|
start-emulator avd_name="REasy_AVD": |
|
@echo "π Starting emulator: {{avd_name}}" |
|
emulator -avd {{avd_name}} -no-snapshot -no-boot-anim -accel on -gpu host -memory 2048 -http-proxy http://127.0.0.1:{{proxy_port}} & |
|
@echo "β³ Waiting for emulator to boot..." |
|
adb wait-for-device |
|
@echo "β
Emulator ready" |
|
|
|
# Create new AVD for analysis |
|
[group('device')] |
|
create-avd avd_name="REasy_AVD" api_level="30": |
|
@echo "π± Creating AVD: {{avd_name}}" |
|
echo no | avdmanager create avd -n {{avd_name}} -k "system-images;android-{{api_level}};google_apis_playstore;x86_64" -d "pixel_4" --force |
|
@echo "β
AVD created. Start with: just start-emulator {{avd_name}}" |
|
|
|
# === APK ACQUISITION === |
|
|
|
# Download APK using apkeep |
|
[group('acquisition')] |
|
download package version="" source="apk-pure": |
|
#!/usr/bin/env bash |
|
mkdir -p {{output_dir}} |
|
timestamp=$(date +%Y-%m-%dT%H-%M-%S-%3NZ) |
|
output_dir="{{output_dir}}/$timestamp" |
|
mkdir -p "$output_dir" |
|
|
|
if [[ -n "{{version}}" ]]; then |
|
package_spec="{{package}}@{{version}}" |
|
else |
|
package_spec="{{package}}" |
|
fi |
|
|
|
echo "π₯ Downloading $package_spec from {{source}}..." |
|
apkeep -a "$package_spec" -d {{source}} "$output_dir" |
|
|
|
# Handle XAPK extraction if needed |
|
xapk_file=$(find "$output_dir" -name "*.xapk" | head -1) |
|
if [[ -n "$xapk_file" ]]; then |
|
echo "π¦ Extracting XAPK..." |
|
extract_dir="${xapk_file%.*}_extracted" |
|
mkdir -p "$extract_dir" |
|
unzip -q "$xapk_file" -d "$extract_dir" |
|
echo "β
APK ready: $extract_dir/*.apk" |
|
else |
|
echo "β
APK downloaded to: $output_dir" |
|
fi |
|
|
|
# Pull APK from connected device |
|
[group('acquisition')] |
|
pull-apk package: |
|
#!/usr/bin/env bash |
|
mkdir -p {{output_dir}}/device-pulls |
|
echo "π± Pulling {{package}} from device..." |
|
apk_path=$(adb shell pm path {{package}} | cut -d':' -f2 | tr -d '\r') |
|
if [[ -z "$apk_path" ]]; then |
|
echo "β Package {{package}} not found on device" |
|
exit 1 |
|
fi |
|
adb pull "$apk_path" "{{output_dir}}/device-pulls/{{package}}.apk" |
|
echo "β
Pulled to: {{output_dir}}/device-pulls/{{package}}.apk" |
|
|
|
# === STATIC ANALYSIS === |
|
|
|
# Decompile APK with jadx |
|
[group('analysis')] |
|
jadx apk_path output_name="": |
|
#!/usr/bin/env bash |
|
if [[ ! -f "{{apk_path}}" ]]; then |
|
echo "β APK file not found: {{apk_path}}" |
|
exit 1 |
|
fi |
|
|
|
if [[ -n "{{output_name}}" ]]; then |
|
output_dir="{{output_dir}}/{{output_name}}_jadx" |
|
else |
|
apk_name=$(basename "{{apk_path}}" .apk) |
|
output_dir="{{output_dir}}/${apk_name}_jadx" |
|
fi |
|
|
|
mkdir -p "$output_dir" |
|
echo "π Decompiling with jadx: {{apk_path}}" |
|
|
|
if [[ -f "{{tools_dir}}/jadx/bin/jadx" ]]; then |
|
{{tools_dir}}/jadx/bin/jadx "{{apk_path}}" -d "$output_dir" --show-bad-code |
|
else |
|
jadx "{{apk_path}}" -d "$output_dir" --show-bad-code |
|
fi |
|
|
|
echo "β
Jadx output: $output_dir" |
|
|
|
# Decode APK resources with apktool |
|
[group('analysis')] |
|
apktool apk_path output_name="": |
|
#!/usr/bin/env bash |
|
if [[ ! -f "{{apk_path}}" ]]; then |
|
echo "β APK file not found: {{apk_path}}" |
|
exit 1 |
|
fi |
|
|
|
if [[ -n "{{output_name}}" ]]; then |
|
output_dir="{{output_dir}}/{{output_name}}_apktool" |
|
else |
|
apk_name=$(basename "{{apk_path}}" .apk) |
|
output_dir="{{output_dir}}/${apk_name}_apktool" |
|
fi |
|
|
|
mkdir -p "$output_dir" |
|
echo "π§ Decoding with apktool: {{apk_path}}" |
|
|
|
if [[ -f "{{tools_dir}}/apktool.jar" ]]; then |
|
java -jar {{tools_dir}}/apktool.jar d "{{apk_path}}" -o "$output_dir" -f |
|
else |
|
apktool d "{{apk_path}}" -o "$output_dir" -f |
|
fi |
|
|
|
echo "β
Apktool output: $output_dir" |
|
|
|
# Complete static analysis |
|
[group('analysis')] |
|
static-analysis apk_path: (jadx apk_path) && (apktool apk_path) |
|
@echo "β
Static analysis complete for {{apk_path}}" |
|
|
|
# Extract strings and analyze APK info |
|
[group('analysis')] |
|
analyze-strings apk_path: |
|
#!/usr/bin/env bash |
|
echo "π€ Extracting strings from {{apk_path}}..." |
|
mkdir -p {{output_dir}}/strings |
|
strings_file="{{output_dir}}/strings/$(basename {{apk_path}} .apk)_strings.txt" |
|
|
|
strings "{{apk_path}}" > "$strings_file" |
|
|
|
echo "π URLs found:" |
|
grep -E 'https?://[^\s<>"{}|\\^`\[\]]+' "$strings_file" | head -10 |
|
|
|
echo "π Potential API keys:" |
|
grep -iE '(api[_-]?key|apikey|token|secret)' "$strings_file" | head -5 |
|
|
|
echo "β
Full strings saved to: $strings_file" |
|
|
|
# === FRIDA SETUP === |
|
|
|
# Check Frida installation status |
|
[group('frida')] |
|
frida-status: |
|
@echo "π Frida Status:" |
|
@echo "CLI: $(frida --version 2>/dev/null || echo 'β Not installed')" |
|
@echo "Server on device: $(adb shell ls /data/local/tmp/frida-server 2>/dev/null && echo 'β
Installed' || echo 'β Not installed')" |
|
@echo "Server running: $(adb shell ps | grep frida-server >/dev/null && echo 'β
Running' || echo 'β Not running')" |
|
|
|
# Install Frida server on device |
|
[group('frida')] |
|
install-frida-server: |
|
#!/usr/bin/env bash |
|
echo "π¦ Installing Frida server..." |
|
|
|
# Get device architecture |
|
arch=$(adb shell getprop ro.product.cpu.abi | tr -d '\r') |
|
echo "Device architecture: $arch" |
|
|
|
# Map to Frida architecture names |
|
case "$arch" in |
|
"arm64-v8a") frida_arch="arm64" ;; |
|
"armeabi-v7a") frida_arch="arm" ;; |
|
"x86") frida_arch="x86" ;; |
|
"x86_64") frida_arch="x86_64" ;; |
|
*) echo "β Unsupported architecture: $arch"; exit 1 ;; |
|
esac |
|
|
|
# Get Frida version |
|
version=$(frida --version 2>/dev/null || echo "16.1.4") |
|
|
|
# Download Frida server |
|
url="https://github.com/frida/frida/releases/download/$version/frida-server-$version-android-$frida_arch.xz" |
|
echo "π₯ Downloading: $url" |
|
|
|
mkdir -p /tmp/frida-install |
|
curl -L "$url" -o "/tmp/frida-install/frida-server.xz" |
|
xz -d "/tmp/frida-install/frida-server.xz" |
|
|
|
# Install on device |
|
adb push "/tmp/frida-install/frida-server" "/data/local/tmp/frida-server" |
|
adb shell "chmod 755 /data/local/tmp/frida-server" |
|
|
|
# Cleanup |
|
rm -rf /tmp/frida-install |
|
echo "β
Frida server installed" |
|
|
|
# Start Frida server on device |
|
[group('frida')] |
|
start-frida: |
|
@echo "π Starting Frida server..." |
|
@adb shell "nohup /data/local/tmp/frida-server &" || adb shell "su -c 'nohup /data/local/tmp/frida-server &'" |
|
@sleep 2 |
|
@echo "β
Frida server started" |
|
|
|
# Stop Frida server |
|
[group('frida')] |
|
stop-frida: |
|
@echo "π Stopping Frida server..." |
|
@adb shell "pkill frida-server" || adb shell "su -c 'pkill frida-server'" |
|
@echo "β
Frida server stopped" |
|
|
|
# Attach Frida with SSL unpinning |
|
[group('frida')] |
|
hook-ssl package: |
|
@echo "π Hooking {{package}} with SSL unpinning..." |
|
frida -U {{package}} -l "{{frida_scripts_dir}}/ssl_unpinning.js" |
|
|
|
# Run custom Frida script |
|
[group('frida')] |
|
frida-script package script_path: |
|
#!/usr/bin/env bash |
|
if [[ ! -f "{{script_path}}" ]]; then |
|
echo "β Script not found: {{script_path}}" |
|
exit 1 |
|
fi |
|
echo "π¬ Running Frida script on {{package}}" |
|
frida -U {{package}} -l "{{script_path}}" |
|
|
|
# === MITM PROXY === |
|
|
|
# Setup MITM proxy and certificates |
|
[group('mitm')] |
|
setup-mitm: |
|
@echo "π Setting up MITM proxy..." |
|
mkdir -p certs |
|
@echo "Generating certificates..." |
|
@timeout 5s mitmproxy --set confdir=./certs || true |
|
@echo "Installing certificate on device..." |
|
@if adb shell which su >/dev/null 2>&1; then \ |
|
echo "π Installing as system certificate (root available)..."; \ |
|
just install-system-cert; \ |
|
else \ |
|
echo "π± Installing as user certificate..."; \ |
|
just install-user-cert; \ |
|
fi |
|
|
|
# Install certificate as system cert (requires root) |
|
[group('mitm')] |
|
install-system-cert: |
|
#!/usr/bin/env bash |
|
cert_hash=$(openssl x509 -inform PEM -subject_hash_old -in certs/mitmproxy-ca-cert.pem | head -1) |
|
openssl x509 -inform PEM -in certs/mitmproxy-ca-cert.pem -out "certs/${cert_hash}.0" |
|
|
|
adb push "certs/${cert_hash}.0" "/data/local/tmp/" |
|
adb shell "su -c 'mount -o rw,remount /system'" |
|
adb shell "su -c 'cp /data/local/tmp/${cert_hash}.0 /system/etc/security/cacerts/'" |
|
adb shell "su -c 'chmod 644 /system/etc/security/cacerts/${cert_hash}.0'" |
|
adb shell "su -c 'mount -o ro,remount /system'" |
|
echo "β
System certificate installed" |
|
|
|
# Install certificate as user cert (no root required) |
|
[group('mitm')] |
|
install-user-cert: |
|
adb push certs/mitmproxy-ca-cert.cer /sdcard/mitmproxy-cert.cer |
|
adb shell "am start -a android.settings.SECURITY_SETTINGS" |
|
@echo "π Please manually install certificate from /sdcard/mitmproxy-cert.cer" |
|
|
|
# Start MITM proxy with traffic capture |
|
[group('mitm')] |
|
start-mitm session_name="": |
|
#!/usr/bin/env bash |
|
timestamp=$(date +%Y-%m-%dT%H-%M-%S) |
|
if [[ -n "{{session_name}}" ]]; then |
|
session_dir="{{output_dir}}/mitm_{{session_name}}_$timestamp" |
|
else |
|
session_dir="{{output_dir}}/mitm_$timestamp" |
|
fi |
|
mkdir -p "$session_dir" |
|
|
|
echo "π Starting MITM proxy..." |
|
echo "Traffic will be saved to: $session_dir" |
|
echo "Web interface: http://localhost:8081" |
|
|
|
# Configure device proxy |
|
host_ip=$(hostname -I | awk '{print $1}' || echo "10.0.2.2") |
|
adb shell "settings put global http_proxy $host_ip:{{proxy_port}}" |
|
|
|
# Start mitmproxy |
|
mitmproxy --listen-port {{proxy_port}} --web-port 8081 --set confdir=./certs -w "$session_dir/flows.mitm" |
|
|
|
# === DYNAMIC ANALYSIS === |
|
|
|
# Install APK on device |
|
[group('dynamic')] |
|
install-apk apk_path: |
|
#!/usr/bin/env bash |
|
if [[ ! -f "{{apk_path}}" ]]; then |
|
echo "β APK file not found: {{apk_path}}" |
|
exit 1 |
|
fi |
|
echo "π² Installing APK: {{apk_path}}" |
|
adb install -r -t -g "{{apk_path}}" |
|
echo "β
APK installed successfully" |
|
|
|
# Launch app with Frida instrumentation |
|
[group('dynamic')] |
|
launch-with-frida package script_path=(frida_scripts_dir + "/ssl_unpinning.js"): |
|
#!/usr/bin/env bash |
|
echo "π Launching {{package}} with Frida instrumentation..." |
|
|
|
# Start Frida server if not running |
|
if ! adb shell ps | grep -q frida-server; then |
|
just start-frida |
|
fi |
|
|
|
# Launch with script |
|
frida -U -f {{package}} -l "{{script_path}}" --no-pause |
|
|
|
# Full dynamic analysis setup |
|
[group('dynamic')] |
|
dynamic-setup apk_path: (install-apk apk_path) start-frida setup-mitm |
|
@echo "β
Dynamic analysis environment ready" |
|
|
|
# === MOBSF INTEGRATION === |
|
|
|
# Start MobSF Docker container |
|
[group('mobsf')] |
|
start-mobsf: |
|
@echo "π³ Starting MobSF container..." |
|
docker run -it --rm -p {{mobsf_port}}:8000 opensecurity/mobsf:latest |
|
@echo "π MobSF available at: http://localhost:{{mobsf_port}}" |
|
|
|
# Start MobSF in background |
|
[group('mobsf')] |
|
mobsf-daemon: |
|
@echo "π³ Starting MobSF daemon..." |
|
docker run -d --name mobsf -p {{mobsf_port}}:8000 opensecurity/mobsf:latest |
|
@echo "β
MobSF running at: http://localhost:{{mobsf_port}}" |
|
|
|
# Stop MobSF container |
|
[group('mobsf')] |
|
stop-mobsf: |
|
@echo "π Stopping MobSF..." |
|
@docker stop mobsf 2>/dev/null || echo "MobSF container not running" |
|
@docker rm mobsf 2>/dev/null || true |
|
|
|
# Upload APK to MobSF for analysis |
|
[group('mobsf')] |
|
mobsf-scan apk_path: |
|
#!/usr/bin/env bash |
|
if [[ ! -f "{{apk_path}}" ]]; then |
|
echo "β APK file not found: {{apk_path}}" |
|
exit 1 |
|
fi |
|
|
|
echo "π€ Uploading {{apk_path}} to MobSF..." |
|
|
|
# Check if MobSF is running |
|
if ! curl -s "http://localhost:{{mobsf_port}}" >/dev/null; then |
|
echo "β MobSF not running. Start with: just start-mobsf" |
|
exit 1 |
|
fi |
|
|
|
# Upload APK |
|
response=$(curl -s -X POST \ |
|
-F "file=@{{apk_path}}" \ |
|
"http://localhost:{{mobsf_port}}/api/v1/upload") |
|
|
|
hash=$(echo "$response" | grep -o '"hash":"[^"]*"' | cut -d'"' -f4) |
|
|
|
if [[ -n "$hash" ]]; then |
|
echo "β
Upload successful. Hash: $hash" |
|
echo "π View results: http://localhost:{{mobsf_port}}/static_analyzer/$hash" |
|
else |
|
echo "β Upload failed" |
|
fi |
|
|
|
# === FULL WORKFLOWS === |
|
|
|
# Complete analysis workflow |
|
[group('workflow')] |
|
analyze apk_path: (static-analysis apk_path) (dynamic-setup apk_path) |
|
@echo "π― Complete analysis workflow finished" |
|
@echo "π Check {{output_dir}} for results" |
|
|
|
# Quick analysis (static only) |
|
[group('workflow')] |
|
quick-scan apk_path: (jadx apk_path) (analyze-strings apk_path) |
|
@echo "β‘ Quick scan complete" |
|
|
|
# Security-focused analysis with MobSF |
|
[group('workflow')] |
|
security-audit apk_path: (static-analysis apk_path) (mobsf-scan apk_path) |
|
@echo "π Security audit complete" |
|
|
|
# Download and analyze workflow |
|
[group('workflow')] |
|
download-analyze package version="" source="apk-pure": |
|
#!/usr/bin/env bash |
|
echo "π Download and analyze workflow for {{package}}" |
|
just download {{package}} {{version}} {{source}} |
|
|
|
# Find the downloaded APK |
|
latest_dir=$(ls -1t {{output_dir}} | head -1) |
|
apk_file=$(find "{{output_dir}}/$latest_dir" -name "*.apk" | head -1) |
|
|
|
if [[ -z "$apk_file" ]]; then |
|
# Check for extracted XAPK |
|
apk_file=$(find "{{output_dir}}/$latest_dir" -path "*_extracted/*.apk" | head -1) |
|
fi |
|
|
|
if [[ -n "$apk_file" ]]; then |
|
echo "π Analyzing downloaded APK: $apk_file" |
|
just analyze "$apk_file" |
|
else |
|
echo "β No APK found in download output" |
|
exit 1 |
|
fi |
|
|
|
# === UTILITIES === |
|
|
|
# Clean up analysis outputs |
|
[group('utils')] |
|
[confirm("Delete all analysis output?")] |
|
clean: |
|
rm -rf {{output_dir}}/* |
|
@echo "π§Ή Cleanup complete" |
|
|
|
# Create analysis report |
|
[group('utils')] |
|
report apk_path: |
|
#!/usr/bin/env bash |
|
apk_name=$(basename "{{apk_path}}" .apk) |
|
report_file="{{output_dir}}/${apk_name}_report.md" |
|
|
|
echo "# Analysis Report: $apk_name" > "$report_file" |
|
echo "Generated: $(date)" >> "$report_file" |
|
echo "" >> "$report_file" |
|
|
|
# APK info |
|
echo "## APK Information" >> "$report_file" |
|
aapt dump badging "{{apk_path}}" | head -20 >> "$report_file" |
|
|
|
echo "β
Report generated: $report_file" |
|
|
|
# Archive analysis session |
|
[group('utils')] |
|
archive session_dir: |
|
#!/usr/bin/env bash |
|
if [[ ! -d "{{session_dir}}" ]]; then |
|
echo "β Session directory not found: {{session_dir}}" |
|
exit 1 |
|
fi |
|
|
|
archive_name="$(basename {{session_dir}}).tar.gz" |
|
tar -czf "$archive_name" -C "$(dirname {{session_dir}})" "$(basename {{session_dir}})" |
|
echo "β
Archive created: $archive_name" |
|
|
|
# Quick device screenshot |
|
[group('utils')] |
|
screenshot name="": |
|
#!/usr/bin/env bash |
|
if [[ -n "{{name}}" ]]; then |
|
filename="screenshot_{{name}}_$(date +%s).png" |
|
else |
|
filename="screenshot_$(date +%s).png" |
|
fi |
|
|
|
mkdir -p {{output_dir}}/screenshots |
|
adb shell screencap -p /sdcard/screenshot.png |
|
adb pull /sdcard/screenshot.png "{{output_dir}}/screenshots/$filename" |
|
adb shell rm /sdcard/screenshot.png |
|
echo "πΈ Screenshot saved: {{output_dir}}/screenshots/$filename" |
|
|
|
# Show help for specific groups |
|
[group('help')] |
|
help-setup: |
|
@echo "Setup commands:" |
|
@just --list | grep -A100 "setup:" |
|
|
|
[group('help')] |
|
help-analysis: |
|
@echo "Analysis commands:" |
|
@just --list | grep -A100 "analysis:" |
|
|
|
[group('help')] |
|
help-workflow: |
|
@echo "Workflow commands:" |
|
@just --list | grep -A100 "workflow:" |
|
|
|
# Node.js legacy commands (bridge to existing functionality) |
|
[group('legacy')] |
|
reasy-analyze package *args: |
|
node src/cli.js analyze {{package}} {{args}} |
|
|
|
[group('legacy')] |
|
reasy-download package *args: |
|
node src/cli.js download {{package}} {{args}} |
|
|
|
[group('legacy')] |
|
reasy-static apk *args: |
|
node src/cli.js static {{apk}} {{args}} |