From 2071c14bfe50fdfa8ed8dbca55b902799fdd3bb0 Mon Sep 17 00:00:00 2001 From: hzfanchencan Date: Fri, 11 Sep 2026 19:17:54 +0800 Subject: [PATCH] fix(scripts): make shell scripts run on macOS system bash 3.2 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Two constructs in the scripts require bash 4, which macOS does not ship — /bin/bash is still 3.2.57 there, so both scripts died immediately: - fingerprint.sh:37 used ${INPUT,,}, the lowercase expansion (bash 4+). Failed with: line 37: ${INPUT,,}: bad substitution Replaced with a portable `tr` pipeline. - find-api-calls.sh:91 used `declare -A` for a 9-key counter map. Failed with: line 91: retrofit: unbound variable Replaced with plain counters. Separately, fingerprint.sh fed the DEX scan through `unzip -p -- "$apk" "$dex" | strings -n 8`. Apple's cctools strings skips the printable-run filter entirely when reading from stdin and emits raw bytes instead, so the scan was fed megabytes of binary noise rather than type descriptors. Handing strings a real file fixes it, and also cuts the run on a 346 MB / 53-dex APK from 35.6s to 12.8s. Verified on macOS against both interpreters: /bin/bash (3.2.57) -n -> both scripts pass bash 5.3.15 -n -> both scripts pass fingerprint.sh before: `bad substitution` after: exit 0, 273 native libs listed find-api-calls.sh before: `unbound variable` after: correct counts on a fixture Co-Authored-By: Claude Code --- .../scripts/find-api-calls.sh | 32 +++++++++---------- .../scripts/fingerprint.sh | 12 +++++-- 2 files changed, 25 insertions(+), 19 deletions(-) diff --git a/plugins/android-reverse-engineering/skills/android-reverse-engineering/scripts/find-api-calls.sh b/plugins/android-reverse-engineering/skills/android-reverse-engineering/scripts/find-api-calls.sh index acd69a6..b3ef7a2 100755 --- a/plugins/android-reverse-engineering/skills/android-reverse-engineering/scripts/find-api-calls.sh +++ b/plugins/android-reverse-engineering/skills/android-reverse-engineering/scripts/find-api-calls.sh @@ -88,47 +88,47 @@ run_grep() { # the tree, counts bucketed by tag — running 8 separate greps was too slow. if [[ "$SEARCH_ALL" == true ]]; then section "Summary (counted in a single pass)" - declare -A H=( - [retrofit]=0 [okhttp]=0 [ktor]=0 [apollo]=0 [volley]=0 - [hilt]=0 [koin]=0 [bearer]=0 [hmac]=0 - ) + # Plain counters rather than `declare -A`: associative arrays need bash 4, + # and macOS still ships bash 3.2 as /bin/bash. + n_retrofit=0; n_okhttp=0; n_ktor=0; n_apollo=0; n_volley=0 + n_hilt=0; n_koin=0; n_bearer=0; n_hmac=0 while IFS= read -r line; do case "$line" in - *"@GET("*|*"@POST("*|*"@PUT("*|*"@DELETE("*|*"@PATCH("*|*"@HTTP("*) H[retrofit]=$((H[retrofit]+1));; + *"@GET("*|*"@POST("*|*"@PUT("*|*"@DELETE("*|*"@PATCH("*|*"@HTTP("*) n_retrofit=$((n_retrofit+1));; esac case "$line" in - *"Request.Builder"*|*"HttpUrl"*|*".newCall("*) H[okhttp]=$((H[okhttp]+1));; + *"Request.Builder"*|*"HttpUrl"*|*".newCall("*) n_okhttp=$((n_okhttp+1));; esac case "$line" in - *"BearerTokens"*|*"defaultRequest {"*|*"client.get("*|*"client.post("*|*"httpClient.get("*|*"httpClient.post("*|*"HttpClient.get("*) H[ktor]=$((H[ktor]+1));; + *"BearerTokens"*|*"defaultRequest {"*|*"client.get("*|*"client.post("*|*"httpClient.get("*|*"httpClient.post("*|*"HttpClient.get("*) n_ktor=$((n_ktor+1));; esac case "$line" in - *"ApolloClient"*|*".serverUrl("*) H[apollo]=$((H[apollo]+1));; + *"ApolloClient"*|*".serverUrl("*) n_apollo=$((n_apollo+1));; esac case "$line" in - *"StringRequest"*|*"JsonObjectRequest"*|*"RequestQueue"*) H[volley]=$((H[volley]+1));; + *"StringRequest"*|*"JsonObjectRequest"*|*"RequestQueue"*) n_volley=$((n_volley+1));; esac case "$line" in - *"@HiltAndroidApp"*|*"@AndroidEntryPoint"*|*"@HiltViewModel"*|*"@Provides"*|*"@Binds"*) H[hilt]=$((H[hilt]+1));; + *"@HiltAndroidApp"*|*"@AndroidEntryPoint"*|*"@HiltViewModel"*|*"@Provides"*|*"@Binds"*) n_hilt=$((n_hilt+1));; esac case "$line" in - *"org.koin."*|*"module {"*|*"single<"*|*"factory<"*|*"singleOf("*|*"factoryOf("*) H[koin]=$((H[koin]+1));; + *"org.koin."*|*"module {"*|*"single<"*|*"factory<"*|*"singleOf("*|*"factoryOf("*) n_koin=$((n_koin+1));; esac case "$line" in - *'"Bearer '*|*'"bearer '*|*"BearerTokens"*) H[bearer]=$((H[bearer]+1));; + *'"Bearer '*|*'"bearer '*|*"BearerTokens"*) n_bearer=$((n_bearer+1));; esac case "$line" in - *"HmacSHA"*|*'Mac.getInstance("Hmac'*) H[hmac]=$((H[hmac]+1));; + *"HmacSHA"*|*'Mac.getInstance("Hmac'*) n_hmac=$((n_hmac+1));; esac done < <(grep -rEh --include='*.java' --include='*.kt' \ '@(GET|POST|PUT|DELETE|PATCH|HTTP)\(|Request\.Builder|HttpUrl|\.newCall\(|BearerTokens|defaultRequest \{|client\.(get|post)\(|httpClient\.(get|post)\(|ApolloClient|\.serverUrl\(|StringRequest|JsonObjectRequest|RequestQueue|@HiltAndroidApp|@AndroidEntryPoint|@HiltViewModel|@Provides|@Binds|org\.koin\.|module \{|single<|factory<|"[Bb]earer |HmacSHA|Mac\.getInstance' \ "$SOURCE_DIR" 2>/dev/null || true) printf ' HTTP framework: Retrofit=%-5s OkHttp=%-5s Ktor=%-5s Apollo=%-5s Volley=%-5s\n' \ - "${H[retrofit]}" "${H[okhttp]}" "${H[ktor]}" "${H[apollo]}" "${H[volley]}" + "${n_retrofit}" "${n_okhttp}" "${n_ktor}" "${n_apollo}" "${n_volley}" printf ' DI framework: Hilt/Dagger=%-5s Koin=%-5s\n' \ - "${H[hilt]}" "${H[koin]}" + "${n_hilt}" "${n_koin}" printf ' Auth signals: Bearer=%-5s HMAC/Sign=%-5s\n' \ - "${H[bearer]}" "${H[hmac]}" + "${n_bearer}" "${n_hmac}" echo echo " Run with one of --retrofit / --okhttp / --ktor / --apollo / --volley /" echo " --paths / --urls / --auth to inspect a single section." diff --git a/plugins/android-reverse-engineering/skills/android-reverse-engineering/scripts/fingerprint.sh b/plugins/android-reverse-engineering/skills/android-reverse-engineering/scripts/fingerprint.sh index c494358..4b7b0ed 100755 --- a/plugins/android-reverse-engineering/skills/android-reverse-engineering/scripts/fingerprint.sh +++ b/plugins/android-reverse-engineering/skills/android-reverse-engineering/scripts/fingerprint.sh @@ -33,8 +33,11 @@ TMP="$(mktemp -d -t apkfp.XXXXXX)" trap 'rm -rf "$TMP"' EXIT # Resolve to a list of APKs (handle XAPK = ZIP of APKs) +# Lowercase via tr rather than ${INPUT,,}: the ,, expansion needs bash 4, +# and macOS still ships bash 3.2 as /bin/bash. APKS=() -case "${INPUT,,}" in +INPUT_LC="$(printf '%s' "$INPUT" | tr '[:upper:]' '[:lower:]')" +case "$INPUT_LC" in *.xapk|*.apks|*.apkm) unzip -q -o "$INPUT" -d "$TMP/xapk" while IFS= read -r p; do APKS+=("$p"); done < <(find "$TMP/xapk" -maxdepth 2 -type f -name '*.apk') @@ -62,8 +65,11 @@ for apk in "${APKS[@]}"; do for dex in $(unzip -Z1 -- "$apk" 2>/dev/null | grep -E '^classes[0-9]*\.dex$' || true); do # DEX type descriptors look like "Lcom/foo/Bar;". Extract the inner # slash-separated FQN so callers can match e.g. 'io/ktor/' directly. - unzip -p -- "$apk" "$dex" 2>/dev/null \ - | strings -n 8 \ + # `strings` must be handed a real file. Apple's cctools strings skips the + # printable-run filter entirely when reading stdin and emits raw bytes + # instead, which turns the whole scan into noise. + unzip -p -- "$apk" "$dex" > "$TMP/dex.bin" 2>/dev/null || continue + strings -n 8 "$TMP/dex.bin" \ | grep -oE 'L[a-z][a-zA-Z0-9_]*(/[a-zA-Z0-9_$]+)+;' \ | sed -E 's/^L//; s/;$//' \ >> "$DEX_STRINGS" || true