diff --git a/CLAUDE.md b/CLAUDE.md index dada203..af98016 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -15,6 +15,9 @@ Three Bash scripts for making Android APKs debuggable and intercepting traffic: # Automated end-to-end (device → extract → patch → reinstall) ./apk-debuggable.sh [--device ] [--keep] [--trust-user-certs] [--proxy] +# Use a local APK file or split-APK directory +./apk-debuggable.sh --apk [--device ] [--keep] [--trust-user-certs] [--proxy] + # Single APK ./lib/make-debuggable.sh [output-apk] [--trust-user-certs] @@ -46,18 +49,22 @@ There are no build, test, or lint commands. Automation wrapper that orchestrates the full device-to-device workflow. Each function sets globals consumed by subsequent steps: -- **`parse_args()`** — Parses positional `APP_NAME` + optional `--device`, `--keep`, `--trust-user-certs`, `--proxy` flags (`--proxy` implies `--trust-user-certs`) +- **`parse_args()`** — Parses positional `APP_NAME` + optional `--apk`, `--device`, `--keep`, `--trust-user-certs`, `--proxy` flags (`--proxy` implies `--trust-user-certs`). Validates that exactly one of `APP_NAME` or `--apk` is provided. - **`find_adb()`** — Discovers `adb` from SDK locations or PATH (same pattern as `find_android_tools()`) +- **`find_aapt2()`** — Discovers `aapt2` from latest `build-tools/*/aapt2` in SDK locations, fallback to `command -v aapt2`. Used only in `--apk` mode for package name extraction. - **`select_device()`** — Parses `adb devices`, skips unauthorized; auto-selects if one device, interactive numbered menu if multiple. Fetches `ro.product.model` for display. +- **`prepare_local_apk()`** — (when `--apk`) For directory input, uses the path as-is as `PULL_DIR`. For single APK, copies to temp dir `apks_local_`. Extracts `PACKAGE_NAME` via `aapt2 dump badging` (soft failure if aapt2 unavailable). - **`select_package()`** — Runs `adb shell pm list packages | grep -i `; auto-selects if one match, interactive menu if multiple - **`pull_apks()`** — Gets paths via `adb shell pm path`, pulls each to `apks_/` directory - **`make_debuggable()`** — Delegates to `./lib/make-debuggable.sh ` (directory mode), forwarding `--trust-user-certs` if set. Output lands in `_debuggable/`. -- **`install_apks()`** — Uninstalls existing package (non-fatal), then `adb install` or `adb install-multiple` depending on APK count -- **`cleanup()`** — Removes temp directories unless `--keep` flag was set +- **`install_apks()`** — Uninstalls existing package if `PACKAGE_NAME` is known (non-fatal), then `adb install` or `adb install-multiple` depending on APK count +- **`cleanup()`** — Removes temp directories unless `--keep` flag was set. Never deletes a user-provided `--apk` directory. - **`start_proxy()`** — (when `--proxy`) Starts mitmproxy Docker container with `--set web_password`, pushes CA cert to device, waits for web UI. Always restarts the container fresh to avoid stale state. Proxy globals: `CONTAINER_NAME="mitmproxy-android"`, `PROXY_PORT=8080`, `WEB_PORT=8081`, `PROXY_PASSWORD="proxy"`, `MITMPROXY_DIR="$HOME/.mitmproxy"`. +Main flow: `find_adb → select_device → [prepare_local_apk | select_package + pull_apks] → make_debuggable → install_apks → cleanup` + Key conventions: all `adb` commands use `-s "$DEVICE_SERIAL"`, all `adb shell` output stripped of `\r` with `tr -d '\r'`, `grep` calls that may match zero use `|| true` to avoid `set -e` abort. ### `lib/proxy-setup.sh` Architecture diff --git a/README.md b/README.md index e77cdd5..fd569e9 100755 --- a/README.md +++ b/README.md @@ -39,6 +39,12 @@ Example: intercepting Wikipedia's API calls with mitmproxy after patching the ap # Intercept HTTPS traffic with mitmproxy (requires Docker) ./apk-debuggable.sh myapp --proxy + +# Use a local APK file (useful for emulators without Play Store) +./apk-debuggable.sh --apk ./some-app.apk --device emulator-5554 + +# Use a local split-APK directory +./apk-debuggable.sh --apk ./split-apks/ --proxy ``` The script will: @@ -52,6 +58,7 @@ The script will: | Flag | Description | |------|-------------| +| `--apk ` | Use a local APK file or split-APK directory instead of pulling from the device | | `--device ` | Use a specific device (from `adb devices`) | | `--keep` | Keep intermediate files (pulled APKs and patched APKs) | | `--trust-user-certs` | Trust user-installed CA certificates (for HTTPS interception) | @@ -85,6 +92,14 @@ Stop the proxy when done: docker stop mitmproxy-android ``` +## Limitations + +**Anti-tampering protection** — Some apps include native integrity-checking libraries (e.g. PairIP, DexGuard) that detect APK modifications and crash on launch. The script detects known anti-tampering libraries and warns you, but it cannot bypass them. + +**Workaround:** Use a **"Google APIs" emulator image** (not "Google Play") — these are `userdebug` builds where all apps are debuggable by default (`ro.debuggable=1`), no APK patching needed. Since these images don't include the Play Store, use `--apk` to install a local APK: `./apk-debuggable.sh --apk ./app.apk --device emulator-5554`. + +**Certificate pinning** — Apps that pin specific server certificates (common in banking apps) will reject connections even with the CA installed. Bypassing pinning requires tools like [Frida](https://frida.re/), which is out of scope. + ## Advanced / Standalone Usage The helper scripts in `lib/` can be used independently for more control over individual steps. See [lib/README.md](lib/README.md) for details on: diff --git a/apk-debuggable.sh b/apk-debuggable.sh index a614c77..c8c170a 100755 --- a/apk-debuggable.sh +++ b/apk-debuggable.sh @@ -12,14 +12,18 @@ NC='\033[0m' # No Color # Globals set by functions APP_NAME="" DEVICE_SERIAL="" +SOURCE_SERIAL="" KEEP_FILES=false TRUST_USER_CERTS=false PROXY_MODE=false ADB="" +AAPT2="" PACKAGE_NAME="" PULL_DIR="" DEBUGGABLE_DIR="" PROXY_HOST="" +LOCAL_APK_PATH="" +LOCAL_APK_IS_TEMP=false SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" # Proxy configuration @@ -46,7 +50,8 @@ print_info() { } usage() { - echo "Usage: $0 [--device ] [--keep] [--trust-user-certs] [--proxy]" + echo "Usage: $0 [--device ] [--source ] [--keep] [--trust-user-certs] [--proxy]" + echo " $0 --apk [--device ] [--keep] [--trust-user-certs] [--proxy]" echo "" echo "Automated end-to-end APK debugging: extracts APKs from a connected" echo "Android device, makes them debuggable, and reinstalls." @@ -55,7 +60,12 @@ usage() { echo " app-name Search term to find the package (e.g., 'myapp')" echo "" echo "Options:" - echo " --device Use a specific device (from 'adb devices')" + echo " --apk Use a local APK file or split-APK directory instead of" + echo " pulling from the device" + echo " --device Target device for installation (from 'adb devices')" + echo " --source Pull APK from this device instead of the target device." + echo " Useful for grabbing apps from a Play Store emulator and" + echo " installing on a non-Play-Store emulator." echo " --keep Keep intermediate files (pulled APKs and patched APKs)" echo " --trust-user-certs Trust user-installed CA certificates (for HTTPS interception)" echo " --proxy Start mitmproxy in Docker for HTTPS traffic interception" @@ -65,9 +75,12 @@ usage() { echo "Examples:" echo " $0 chrome" echo " $0 myapp --device emulator-5554" + echo " $0 myapp --source emulator-5554 --device emulator-5556" echo " $0 myapp --keep" echo " $0 myapp --trust-user-certs" echo " $0 myapp --proxy" + echo " $0 --apk ./some-app.apk --device emulator-5554" + echo " $0 --apk ./split-apks/ --proxy" exit 0 } @@ -81,6 +94,18 @@ parse_args() { --help|-h) usage ;; + --apk) + if [[ -z "$2" || "$2" == --* ]]; then + print_error "--apk requires a file or directory path" + exit 1 + fi + LOCAL_APK_PATH="$2" + if [[ ! -e "$LOCAL_APK_PATH" ]]; then + print_error "Path does not exist: $LOCAL_APK_PATH" + exit 1 + fi + shift 2 + ;; --device) if [[ -z "$2" || "$2" == --* ]]; then print_error "--device requires a serial number argument" @@ -89,6 +114,14 @@ parse_args() { DEVICE_SERIAL="$2" shift 2 ;; + --source) + if [[ -z "$2" || "$2" == --* ]]; then + print_error "--source requires a serial number argument" + exit 1 + fi + SOURCE_SERIAL="$2" + shift 2 + ;; --keep) KEEP_FILES=true shift @@ -118,8 +151,23 @@ parse_args() { esac done - if [[ -z "$APP_NAME" ]]; then - print_error "App name is required" + if [[ -z "$APP_NAME" && -z "$LOCAL_APK_PATH" ]]; then + print_error "App name or --apk is required" + exit 1 + fi + + if [[ -n "$APP_NAME" && -n "$LOCAL_APK_PATH" ]]; then + print_error "Cannot use both app name and --apk at the same time" + exit 1 + fi + + if [[ -n "$SOURCE_SERIAL" && -n "$LOCAL_APK_PATH" ]]; then + print_error "Cannot use both --source and --apk at the same time" + exit 1 + fi + + if [[ -n "$SOURCE_SERIAL" && -z "$APP_NAME" ]]; then + print_error "--source requires an app name to search for on the source device" exit 1 fi } @@ -151,6 +199,73 @@ find_adb() { fi print_step "Found adb: $ADB" + + # Ensure adb server is running and has discovered all devices + "$ADB" start-server 2>/dev/null +} + +find_aapt2() { + local sdk_locations=( + "$HOME/Library/Android/sdk" + "/Users/$USER/Library/Android/sdk" + "$ANDROID_HOME" + "$ANDROID_SDK_ROOT" + ) + + for loc in "${sdk_locations[@]}"; do + if [[ -n "$loc" && -d "$loc/build-tools" ]]; then + # Find latest build-tools version with aapt2 + local latest + latest=$(ls -1 "$loc/build-tools" 2>/dev/null | sort -V | tail -1) + if [[ -n "$latest" && -x "$loc/build-tools/$latest/aapt2" ]]; then + AAPT2="$loc/build-tools/$latest/aapt2" + break + fi + fi + done + + if [[ -z "$AAPT2" ]]; then + if command -v aapt2 &> /dev/null; then + AAPT2="$(command -v aapt2)" + fi + fi +} + +prepare_local_apk() { + print_step "Preparing local APK: $LOCAL_APK_PATH" + + if [[ -d "$LOCAL_APK_PATH" ]]; then + # Directory input — use as-is + PULL_DIR="$LOCAL_APK_PATH" + # Find base.apk for package name extraction + local base_apk="$PULL_DIR/base.apk" + if [[ ! -f "$base_apk" ]]; then + # Try to find any APK + base_apk=$(ls "$PULL_DIR"/*.apk 2>/dev/null | head -1) + fi + if [[ -n "$base_apk" && -n "$AAPT2" ]]; then + PACKAGE_NAME=$("$AAPT2" dump badging "$base_apk" 2>/dev/null | grep "^package:" | sed "s/.*name='//" | sed "s/'.*//" || true) + fi + else + # Single APK — copy to temp dir + local basename + basename=$(basename "$LOCAL_APK_PATH" .apk) + PULL_DIR="apks_local_${basename}" + LOCAL_APK_IS_TEMP=true + rm -rf "$PULL_DIR" + mkdir -p "$PULL_DIR" + cp "$LOCAL_APK_PATH" "$PULL_DIR/" + if [[ -n "$AAPT2" ]]; then + PACKAGE_NAME=$("$AAPT2" dump badging "$LOCAL_APK_PATH" 2>/dev/null | grep "^package:" | sed "s/.*name='//" | sed "s/'.*//" || true) + fi + fi + + if [[ -n "$PACKAGE_NAME" ]]; then + print_step "Detected package: $PACKAGE_NAME" + else + print_warning "Could not detect package name (aapt2 not found or extraction failed)" + print_info "The app will be installed but the previous version won't be uninstalled automatically" + fi } select_device() { @@ -174,7 +289,7 @@ select_device() { if [[ "$state" == "device" ]]; then serials+=("$serial") local model - model=$("$ADB" -s "$serial" shell getprop ro.product.model 2>/dev/null | tr -d '\r' || echo "unknown") + model=$("$ADB" -s "$serial" shell getprop ro.product.model < /dev/null 2>/dev/null | tr -d '\r' || echo "unknown") models+=("$model") elif [[ "$state" == "unauthorized" ]]; then print_warning "Device $serial is unauthorized — please accept the USB debugging prompt" @@ -186,7 +301,27 @@ select_device() { exit 1 fi - # If --device was specified, validate it + # Validate --source if specified + if [[ -n "$SOURCE_SERIAL" ]]; then + local source_found=false + for i in "${!serials[@]}"; do + if [[ "${serials[$i]}" == "$SOURCE_SERIAL" ]]; then + source_found=true + print_step "Source device: $SOURCE_SERIAL (${models[$i]})" + break + fi + done + if [[ "$source_found" == false ]]; then + print_error "Source device '$SOURCE_SERIAL' not found or not authorized." + echo "Available devices:" + for i in "${!serials[@]}"; do + echo " ${serials[$i]} (${models[$i]})" + done + exit 1 + fi + fi + + # Validate --device if specified if [[ -n "$DEVICE_SERIAL" ]]; then local found=false for s in "${serials[@]}"; do @@ -205,32 +340,102 @@ select_device() { fi local model model=$("$ADB" -s "$DEVICE_SERIAL" shell getprop ro.product.model 2>/dev/null | tr -d '\r' || echo "unknown") - print_step "Using specified device: $DEVICE_SERIAL ($model)" + if [[ -n "$SOURCE_SERIAL" ]]; then + print_step "Target device: $DEVICE_SERIAL ($model)" + else + print_step "Using device: $DEVICE_SERIAL ($model)" + fi return fi # Auto-select if only one device if [[ ${#serials[@]} -eq 1 ]]; then DEVICE_SERIAL="${serials[0]}" - print_step "Using device: $DEVICE_SERIAL (${models[0]})" + if [[ -n "$SOURCE_SERIAL" ]]; then + print_step "Target device: $DEVICE_SERIAL (${models[0]})" + else + print_step "Using device: $DEVICE_SERIAL (${models[0]})" + fi return fi - # Interactive menu for multiple devices - echo "" - echo "Multiple devices found:" + # Multiple devices available, no --device specified. + # If pulling from device (APP_NAME mode) and no --source, prompt for source first. + if [[ -n "$APP_NAME" && -z "$SOURCE_SERIAL" ]]; then + echo "" + echo "Multiple devices found. Select source device (pull APK from):" + for i in "${!serials[@]}"; do + echo " $((i + 1))) ${serials[$i]} (${models[$i]})" + done + echo "" + while true; do + read -rp "Source [1-${#serials[@]}]: " choice + if [[ "$choice" =~ ^[0-9]+$ ]] && [[ "$choice" -ge 1 ]] && [[ "$choice" -le ${#serials[@]} ]]; then + SOURCE_SERIAL="${serials[$((choice - 1))]}" + print_step "Source device: $SOURCE_SERIAL (${models[$((choice - 1))]})" + break + fi + echo "Invalid selection. Enter a number between 1 and ${#serials[@]}." + done + fi + + # Build target candidate list (exclude source in two-device mode) + local target_serials=() + local target_models=() for i in "${!serials[@]}"; do - echo " $((i + 1))) ${serials[$i]} (${models[$i]})" + if [[ -n "$SOURCE_SERIAL" && "${serials[$i]}" == "$SOURCE_SERIAL" ]]; then + continue + fi + target_serials+=("${serials[$i]}") + target_models+=("${models[$i]}") + done + + # If no candidates after filtering (only source device connected), error + if [[ -n "$SOURCE_SERIAL" && ${#target_serials[@]} -eq 0 ]]; then + print_error "No target device found. Connect a second device/emulator to install on." + exit 1 + fi + + # Fall back to full list if no source filtering happened + if [[ ${#target_serials[@]} -eq 0 ]]; then + target_serials=("${serials[@]}") + target_models=("${models[@]}") + fi + + # Auto-select if only one target candidate + if [[ ${#target_serials[@]} -eq 1 ]]; then + DEVICE_SERIAL="${target_serials[0]}" + if [[ -n "$SOURCE_SERIAL" ]]; then + print_step "Target device: $DEVICE_SERIAL (${target_models[0]})" + else + print_step "Using device: $DEVICE_SERIAL (${target_models[0]})" + fi + return + fi + + # Interactive menu for target device + echo "" + if [[ -n "$SOURCE_SERIAL" ]]; then + echo "Select target device (install on):" + else + echo "Multiple devices found:" + fi + for i in "${!target_serials[@]}"; do + echo " $((i + 1))) ${target_serials[$i]} (${target_models[$i]})" done echo "" while true; do - read -rp "Select device [1-${#serials[@]}]: " choice - if [[ "$choice" =~ ^[0-9]+$ ]] && [[ "$choice" -ge 1 ]] && [[ "$choice" -le ${#serials[@]} ]]; then - DEVICE_SERIAL="${serials[$((choice - 1))]}" - print_step "Using device: $DEVICE_SERIAL (${models[$((choice - 1))]})" + read -rp "Select device [1-${#target_serials[@]}]: " choice + if [[ "$choice" =~ ^[0-9]+$ ]] && [[ "$choice" -ge 1 ]] && [[ "$choice" -le ${#target_serials[@]} ]]; then + DEVICE_SERIAL="${target_serials[$((choice - 1))]}" + if [[ -n "$SOURCE_SERIAL" ]]; then + print_step "Target device: $DEVICE_SERIAL (${target_models[$((choice - 1))]})" + else + print_step "Using device: $DEVICE_SERIAL (${target_models[$((choice - 1))]})" + fi return fi - echo "Invalid selection. Enter a number between 1 and ${#serials[@]}." + echo "Invalid selection. Enter a number between 1 and ${#target_serials[@]}." done } @@ -238,7 +443,7 @@ select_package() { print_step "Searching for packages matching '$APP_NAME'..." local packages_raw - packages_raw=$("$ADB" -s "$DEVICE_SERIAL" shell pm list packages 2>&1 | tr -d '\r') + packages_raw=$("$ADB" -s "$SOURCE_SERIAL" shell pm list packages 2>&1 | tr -d '\r') local matches=() while IFS= read -r line; do @@ -248,7 +453,7 @@ select_package() { if [[ ${#matches[@]} -eq 0 ]]; then print_error "No packages found matching '$APP_NAME'" echo "Try a broader search term, or list all packages with:" - echo " adb -s $DEVICE_SERIAL shell pm list packages" + echo " adb -s $SOURCE_SERIAL shell pm list packages" exit 1 fi @@ -281,7 +486,7 @@ pull_apks() { print_step "Getting APK paths for $PACKAGE_NAME..." local paths_raw - paths_raw=$("$ADB" -s "$DEVICE_SERIAL" shell pm path "$PACKAGE_NAME" 2>&1 | tr -d '\r') + paths_raw=$("$ADB" -s "$SOURCE_SERIAL" shell pm path "$PACKAGE_NAME" 2>&1 | tr -d '\r') local apk_paths=() while IFS= read -r line; do @@ -305,7 +510,7 @@ pull_apks() { local apk_name apk_name=$(basename "$apk_path") print_info "Pulling $apk_name..." - "$ADB" -s "$DEVICE_SERIAL" pull "$apk_path" "$PULL_DIR/$apk_name" + "$ADB" -s "$SOURCE_SERIAL" pull "$apk_path" "$PULL_DIR/$apk_name" done print_step "APKs pulled to $PULL_DIR/" @@ -335,8 +540,10 @@ make_debuggable() { } install_apks() { - print_step "Uninstalling existing $PACKAGE_NAME..." - "$ADB" -s "$DEVICE_SERIAL" uninstall "$PACKAGE_NAME" || print_warning "Uninstall failed (app may not be installed) — continuing" + if [[ -n "$PACKAGE_NAME" ]]; then + print_step "Uninstalling existing $PACKAGE_NAME..." + "$ADB" -s "$DEVICE_SERIAL" uninstall "$PACKAGE_NAME" || print_warning "Uninstall failed (app may not be installed) — continuing" + fi local apk_files=("$DEBUGGABLE_DIR"/*.apk) local apk_count=${#apk_files[@]} @@ -366,7 +573,13 @@ cleanup() { fi print_step "Cleaning up temporary files..." - [[ -n "$PULL_DIR" && -d "$PULL_DIR" ]] && rm -rf "$PULL_DIR" + # Only delete PULL_DIR if we created it (not a user-provided directory) + if [[ -n "$LOCAL_APK_PATH" && -d "$LOCAL_APK_PATH" ]]; then + # User provided a directory — don't delete it + : + else + [[ -n "$PULL_DIR" && -d "$PULL_DIR" ]] && rm -rf "$PULL_DIR" + fi [[ -n "$DEBUGGABLE_DIR" && -d "$DEBUGGABLE_DIR" ]] && rm -rf "$DEBUGGABLE_DIR" } @@ -463,8 +676,20 @@ main() { find_adb select_device - select_package - pull_apks + + # Default source to target when not in two-device mode + if [[ -z "$SOURCE_SERIAL" ]]; then + SOURCE_SERIAL="$DEVICE_SERIAL" + fi + + if [[ -n "$LOCAL_APK_PATH" ]]; then + find_aapt2 + prepare_local_apk + else + select_package + pull_apks + fi + make_debuggable install_apks cleanup @@ -477,7 +702,15 @@ main() { echo "" echo -e "${GREEN}=== Done! ===${NC}" echo "" - echo -e " $PACKAGE_NAME is now debuggable on $DEVICE_SERIAL" + if [[ -n "$PACKAGE_NAME" ]]; then + echo -e " $PACKAGE_NAME is now debuggable on $DEVICE_SERIAL" + else + echo -e " App is now debuggable on $DEVICE_SERIAL" + fi + if [[ "$SOURCE_SERIAL" != "$DEVICE_SERIAL" ]]; then + echo -e " ${BLUE}Pulled from:${NC} $SOURCE_SERIAL" + echo -e " ${BLUE}Installed on:${NC} $DEVICE_SERIAL" + fi if [[ "$PROXY_MODE" == true ]]; then echo "" @@ -498,7 +731,7 @@ main() { else echo "" echo " To attach a debugger:" - echo " Android Studio → Run → Attach Debugger to Android Process → $PACKAGE_NAME" + echo " Android Studio → Run → Attach Debugger to Android Process${PACKAGE_NAME:+ → $PACKAGE_NAME}" fi } diff --git a/lib/make-debuggable.sh b/lib/make-debuggable.sh index 8031d47..179cb52 100755 --- a/lib/make-debuggable.sh +++ b/lib/make-debuggable.sh @@ -225,6 +225,47 @@ inject_network_security_config() { fi } +# Check for anti-tampering / integrity-protection libraries +check_anti_tampering() { + local found=() + + # Library filename:protection name pairs (Bash 3.2 compatible — no associative arrays) + local pairs=( + "libpairipcore.so:PairIP" + "libDexHelper.so:DexGuard" + "libDexHelper-x86.so:DexGuard" + "libjiagu.so:360 Jiagu" + "libjiagu_art.so:360 Jiagu" + "libsecexe.so:Bangcle" + "libsecmain.so:Bangcle" + "libtosprotection.so:Tencent Legu" + "libexec.so:Baidu" + "libexecmain.so:Baidu" + ) + + for pair in "${pairs[@]}"; do + local lib_name="${pair%%:*}" + local protection="${pair#*:}" + if find "$WORK_DIR/lib" -name "$lib_name" 2>/dev/null | grep -q .; then + found+=("$protection ($lib_name)") + fi + done + + if [[ ${#found[@]} -gt 0 ]]; then + echo "" + print_warning "Anti-tampering protection detected!" + echo -e " ${YELLOW}This app uses native integrity checks that will likely crash${NC}" + echo -e " ${YELLOW}after patching because the APK signature has changed.${NC}" + echo "" + for item in "${found[@]}"; do + echo -e " ${RED}•${NC} $item" + done + echo "" + echo -e " The patched APK will still be created, but the app may crash on launch." + echo "" + fi +} + # Process a single APK (make debuggable) process_single_apk() { local input_apk="$1" @@ -238,7 +279,18 @@ process_single_apk() { # Step 1: Disassemble print_step "Disassembling APK..." - $APKTOOL d -f -o "$WORK_DIR" "$input_apk" + if [[ "$TRUST_USER_CERTS" == true ]]; then + # Full resource decode needed so we can inject/overwrite network_security_config.xml + $APKTOOL d -f -o "$WORK_DIR" "$input_apk" + else + # --no-res: skip resource decoding — we only need AndroidManifest.xml. + # This avoids aapt2 "private resource" errors (e.g. android:style/TextAppearance.*) + # that occur when the app references private Android framework styles/resources. + $APKTOOL d -f --no-res -o "$WORK_DIR" "$input_apk" + fi + + # Step 1b: Check for anti-tampering protections + check_anti_tampering # Step 2: Make debuggable print_step "Making APK debuggable..."