diff --git a/docs/source/app_decoding.rst b/docs/source/app_decoding.rst index a5261d2..39db2d2 100644 --- a/docs/source/app_decoding.rst +++ b/docs/source/app_decoding.rst @@ -100,7 +100,7 @@ To manually capture the logs follow these steps: 6. Perform the actions required to get the packets needed for analysis (add device, send commands, etc). .. note:: - This process is incredibly time sensitive, it may take multiple attempts to get it right. + This process is incredibly time sensitive, it may take multiple attempts (10+) to get it right. run.sh @@ -111,7 +111,7 @@ and in addition writes the logs to a file as well as the console. This is the recommend approach. .. note:: - The app must not be running when you start the script. + The app must **not** be running when you start the script. This script performs the following actions: diff --git a/scripts/frida.js b/scripts/frida.js index c8bac48..56387f2 100644 --- a/scripts/frida.js +++ b/scripts/frida.js @@ -2,33 +2,15 @@ * Script name : frida.js * Description : Frida script for preventing exit and extracting shared preferences * Author : Harvey Lelliott (@flip-dots) - * Date : 23/03/26 + * Date : 16/07/26 * * License : MIT - * Revision : 1.0.0 + * Revision : 1.1.0 * * This script is based off https://codeshare.frida.re/@ninjadiary/frinja---sharedpreferences/ * but with additional code to prevent the anti-tamper mechanism from killing the app. */ -// Prevent anti-tamper mechanism from fully killing app -setImmediate(function() { - Java.perform(function() { - var System = Java.use('java.lang.System'); - var Process = Java.use('android.os.Process'); - - // Stop System.exit() - System.exit.implementation = function(code) { - console.log("[!] Intercepted exit (" + code + ")"); - }; - - // Stop Process.killProcess() - Process.killProcess.implementation = function(pid) { - console.log("[!] Intercepted killProcess for PID: " + pid); - }; - }); -}); - // Log any reads/writes to shared preferences // From: https://codeshare.frida.re/@ninjadiary/frinja---sharedpreferences/ setImmediate(function() { @@ -181,3 +163,84 @@ Java.perform(function () { }; }); }); + + +/********************** + * * + * Anti-Tamper Bypass * + * * + **********************/ + + +// Prevent anti-tamper mechanism from killing the app in Java +setImmediate(function() { + Java.perform(function() { + console.log("Tampering with anti-tamper protection using Java API..."); + + var System = Java.use('java.lang.System'); + var Process = Java.use('android.os.Process'); + + // Stop System.exit() + System.exit.implementation = function(code) { + console.log("[!] Intercepted exit (" + code + ")"); + }; + + // Stop Process.killProcess() + Process.killProcess.implementation = function(pid) { + console.log("[!] Intercepted killProcess for PID: " + pid); + }; + }); +}); + + +// Prevent anti-tamper mechanism from killing the app in C +function blockNativeExit() { + console.log("Tampering with anti-tamper protection using C API..."); + + let usleepAddr = null; + try { + usleepAddr = Module.findGlobalExportByName("usleep") || Module.findExportByName("usleep"); + } catch (e) { + console.log("[-] Could not find usleep globally: " + e.message); + } + + let nativeUsleep = null; + if (usleepAddr) { + nativeUsleep = new NativeFunction(usleepAddr, 'int', ['uint32']); + } + + // Standard libc functions used by packers to terminate processes + const exitSymbols = ["exit", "_exit", "abort", "kill", "pthread_exit"]; + + exitSymbols.forEach(function (symbol) { + let address = null; + try { + // Find global exports using Frida 17 APIs + address = Module.findGlobalExportByName(symbol) || Module.findExportByName(symbol); + } catch (e) { + // Ignore search errors + } + + if (address) { + try { + Interceptor.attach(address, { + onEnter: function (args) { + console.log("[!] Native anti-debug triggered " + symbol + "(). Freezing thread to prevent crash..."); + + // Infinite loop using the native OS sleep to halt the calling thread + while (true) { + if (nativeUsleep) { + nativeUsleep(100000); // Sleep for 100ms per iteration safely + } + } + } + }); + console.log("[+] Hooked native function: " + symbol); + } catch (e) { + console.log("[-] Failed to hook: " + symbol + " (" + e.message + ")"); + } + } + }); +} + +blockNativeExit(); diff --git a/scripts/frida_2.js b/scripts/frida_2.js index ee27c25..7b7b47e 100644 --- a/scripts/frida_2.js +++ b/scripts/frida_2.js @@ -1,10 +1,10 @@ /* - * Script name : frida.js + * Script name : frida_2.js * Description : Frida script for preventing exit, extracting shared preferences, and tracing crypto/BLE with timestamps * Author : Harvey Lelliott (@flip-dots) / Enhanced - * Date : 23/03/26 + * Date : 16/07/26 * * License : MIT - * Revision : 1.1.1 + * Revision : 1.2.1 */ // --- UTILITY FUNCTIONS --- @@ -293,4 +293,56 @@ setImmediate(function() { log("[-] Error hooking Bluetooth: " + e); } }); -}); \ No newline at end of file +}); + +// Prevent anti-tamper mechanism from killing the app in C +function blockNativeExit() { + console.log("Tampering with anti-tamper protection using C API..."); + + let usleepAddr = null; + try { + usleepAddr = Module.findGlobalExportByName("usleep") || Module.findExportByName("usleep"); + } catch (e) { + console.log("[-] Could not find usleep globally: " + e.message); + } + + let nativeUsleep = null; + if (usleepAddr) { + nativeUsleep = new NativeFunction(usleepAddr, 'int', ['uint32']); + } + + // Standard libc functions used by packers to terminate processes + const exitSymbols = ["exit", "_exit", "abort", "kill", "pthread_exit"]; + + exitSymbols.forEach(function (symbol) { + let address = null; + try { + // Find global exports using Frida 17 APIs + address = Module.findGlobalExportByName(symbol) || Module.findExportByName(symbol); + } catch (e) { + // Ignore search errors + } + + if (address) { + try { + Interceptor.attach(address, { + onEnter: function (args) { + console.log("[!] Native anti-debug triggered " + symbol + "(). Freezing thread to prevent crash..."); + + // Infinite loop using the native OS sleep to halt the calling thread + while (true) { + if (nativeUsleep) { + nativeUsleep(100000); // Sleep for 100ms per iteration safely + } + } + } + }); + console.log("[+] Hooked native function: " + symbol); + } catch (e) { + console.log("[-] Failed to hook: " + symbol + " (" + e.message + ")"); + } + } + }); +} + +blockNativeExit(); diff --git a/scripts/patch.sh b/scripts/patch.sh index 9432d83..a34dabe 100755 --- a/scripts/patch.sh +++ b/scripts/patch.sh @@ -3,11 +3,11 @@ # Script name : patch.sh # Description : Script for injecting Frida gadget into Anker Android app # Author : Harvey Lelliott (@flip-dots) -# Date : 23/03/26 +# Date : 16/07/26 # Usage : ./patch.sh [ADB device (e.g 192.168.1.1:1234)] # # License : MIT -# Revision : 1.0.0 +# Revision : 1.1.0 # set -euxo pipefail @@ -16,7 +16,7 @@ set -euxo pipefail # Constants # ############# -FRIDA_VERSION="17.8.2" +FRIDA_VERSION="17.8.3" UBER_APK_SIGNER_VERSION="1.3.0" @@ -53,7 +53,7 @@ WORKING_FOLDER=$(pwd) # Folder to put all data in DATA_FOLDER="${WORKING_FOLDER}/data" -mkdir -p $DATA_FOLDER +rm -rf $DATA_FOLDER && mkdir -p $DATA_FOLDER # Folder to put source APK inside APK_SOURCE_FOLDER="${DATA_FOLDER}/source_apks" @@ -73,7 +73,7 @@ mkdir -p $APK_SIGNED_FOLDER # Folder to put tools in TOOLS_FOLDER="${WORKING_FOLDER}/tools" -mkdir -p $TOOLS_FOLDER +rm -rf $TOOLS_FOLDER && mkdir -p $TOOLS_FOLDER # Folder to put Frida gadgets in FRIDA_FOLDER="${TOOLS_FOLDER}/frida" @@ -124,10 +124,11 @@ cp "${WORKING_FOLDER}/frida_config.json" "${APK_DECOMPILED_FOLDER}/lib/armeabi-v cp "${WORKING_FOLDER}/frida_config.json" "${APK_DECOMPILED_FOLDER}/lib/arm64-v8a/libnative-utils.config.so" # Add Frida gadget to app startup -sed -i '' '34c \ -const-string v0, "native-utils"\ -invoke-static {v0}, Ljava/lang/System;->loadLibrary(Ljava/lang/String;)V\ -' "${APK_DECOMPILED_FOLDER}/smali/s/h/e/l/l/A.smali" +sed -i '' 's/\.locals 5/\.locals 6/g' "${APK_DECOMPILED_FOLDER}/smali/s/h/e/l/l/S.smali" +sed -i '' '2386c \ +const-string v5, "native-utils"\ +invoke-static {v5}, Ljava/lang/System;->loadLibrary(Ljava/lang/String;)V\ +' "${APK_DECOMPILED_FOLDER}/smali/s/h/e/l/l/S.smali" # Modify manifest to enable Frida loading sed -i '' '// s/android:allowBackup="false"/android:allowBackup="true"/' "${APK_DECOMPILED_FOLDER}/AndroidManifest.xml" diff --git a/scripts/patch_2.sh b/scripts/patch_2.sh new file mode 100755 index 0000000..b1d873e --- /dev/null +++ b/scripts/patch_2.sh @@ -0,0 +1,182 @@ +#!/bin/bash +# +# Script name : patch_2.sh +# Description : Script for injecting Frida gadget into Anker Android app +# Author : Harvey Lelliott (@flip-dots) +# Date : 16/07/26 +# Usage : ./patch_2.sh [ADB device (e.g 192.168.1.1:1234)] +# +# License : MIT +# Revision : 1.0.0 +# +set -euxo pipefail + + +############# +# Constants # +############# + +FRIDA_VERSION="17.8.3" + +UBER_APK_SIGNER_VERSION="1.3.0" + + +################################## +# Environment and arg validation # +################################## +echo "Checking environment/tools..." + +# Validate arguments +if [ $# -lt 1 ]; then + echo "Missing device argument (e.g 192.168.1.1:1234)!" + exit 2 +fi + +# The device for use with ADB +DEVICE=$1 + +# Check tools +command -v wget >/dev/null 2>&1 || { echo >&2 "wget is required!"; exit 1; } +command -v adb >/dev/null 2>&1 || { echo >&2 "adb is required!"; exit 1; } +command -v apktool >/dev/null 2>&1 || { echo >&2 "apktool is required!"; exit 1; } +command -v java >/dev/null 2>&1 || { echo >&2 "java is required!"; exit 1; } +command -v frida >/dev/null 2>&1 || { echo >&2 "frida is required!"; exit 1; } + + +################ +# Folder setup # +################ +echo "Setting up folders..." + +# The current folder +WORKING_FOLDER=$(pwd) + +# Folder to put all data in +DATA_FOLDER="${WORKING_FOLDER}/data" +mkdir -p $DATA_FOLDER + +# Folder to put source APK inside +APK_SOURCE_FOLDER="${DATA_FOLDER}/source_apks" +mkdir -p $APK_SOURCE_FOLDER + +# Folder to put the main decompiled APK inside +APK_MAIN_DECOMPILED_FOLDER="${DATA_FOLDER}/base_apk_decompiled" +mkdir -p $APK_MAIN_DECOMPILED_FOLDER + +# Folder to put the flutter decompiled APK inside +APK_FLUTTER_DECOMPILED_FOLDER="${DATA_FOLDER}/flutter_apk_decompiled" +mkdir -p $APK_FLUTTER_DECOMPILED_FOLDER + +# Folder to put patched APKs inside +APK_PATCHED_FOLDER="${DATA_FOLDER}/patched" +mkdir -p $APK_PATCHED_FOLDER + +# Folder to put signed APKs inside +APK_SIGNED_FOLDER="${DATA_FOLDER}/signed" +mkdir -p $APK_SIGNED_FOLDER + +# Folder to put tools in +TOOLS_FOLDER="${WORKING_FOLDER}/tools" +mkdir -p $TOOLS_FOLDER + +# Folder to put Frida gadgets in +FRIDA_FOLDER="${TOOLS_FOLDER}/frida" +mkdir -p $FRIDA_FOLDER + + + +####################### +# Download deps/tools # +####################### +echo "Downloading dependencies/tools" + +cd $FRIDA_FOLDER && wget "https://github.com/zer0def/undetected-frida/releases/download/${FRIDA_VERSION}/undetected-frida-gadget-${FRIDA_VERSION}-android-arm.so.xz" +cd $FRIDA_FOLDER && wget "https://github.com/zer0def/undetected-frida/releases/download/${FRIDA_VERSION}/undetected-frida-gadget-${FRIDA_VERSION}-android-arm64.so.xz" +cd $FRIDA_FOLDER && unxz -f "undetected-frida-gadget-${FRIDA_VERSION}-android-arm.so.xz" +cd $FRIDA_FOLDER && unxz -f "undetected-frida-gadget-${FRIDA_VERSION}-android-arm64.so.xz" + +cd $TOOLS_FOLDER && wget "https://github.com/patrickfav/uber-apk-signer/releases/download/v${UBER_APK_SIGNER_VERSION}/uber-apk-signer-${UBER_APK_SIGNER_VERSION}.jar" + + +######################## +# Pull and extract APK # +######################## + +# Pull Anker APKs from Phone +echo "Extracting original APKs from phone..." +adb -s $DEVICE shell pm path com.anker.charging | sed 's/^package://' | tr -d '\r' | xargs -I {} adb -s $DEVICE pull {} $APK_SOURCE_FOLDER + + +################ +# Inject Frida # +################ +echo "Injecting Frida gadget into flutter APK..." + +# Extract Flutter library from Flutter APK +mkdir -p "${APK_FLUTTER_DECOMPILED_FOLDER}/lib/arm64-v8a" +unzip -p "${APK_SOURCE_FOLDER}/split_config.arm64_v8a.apk" "lib/arm64-v8a/libflutter.so" > "${APK_FLUTTER_DECOMPILED_FOLDER}/lib/arm64-v8a/libflutter.so" + +# Use LIEF to inject the Frida gadget as a dependency of Flutter +python3 -c " +import lief +import sys + +target_path = '${APK_FLUTTER_DECOMPILED_FOLDER}/lib/arm64-v8a/libflutter.so' + +try: + # Parse the ELF binary + binary = lief.parse(target_path) + + # Inject the dependency + binary.add_library('libnative-utils.so') + binary.write(target_path) + print(f'Successfully injected Frida into Flutter') +except Exception as e: + print(f'Failed to inject Frida into Flutter: {e}') + sys.exit(1) +" + +# Copy Frida gadget binaries into Flutter APK +cp "${FRIDA_FOLDER}/undetected-frida-gadget-${FRIDA_VERSION}-android-arm64.so" "${APK_FLUTTER_DECOMPILED_FOLDER}/lib/arm64-v8a/libnative-utils.so" +cp "${WORKING_FOLDER}/frida_config.json" "${APK_FLUTTER_DECOMPILED_FOLDER}/lib/arm64-v8a/libnative-utils.config.so" + + +# Decompile the main APK +echo "Decompiling main APK..." +apktool d "${APK_SOURCE_FOLDER}/base.apk" -o $APK_MAIN_DECOMPILED_FOLDER -f + +# Modify manifest of main APK to enable Frida loading +sed -i '' '// s/android:allowBackup="false"/android:allowBackup="true"/' "${APK_MAIN_DECOMPILED_FOLDER}/AndroidManifest.xml" +sed -i '' '// s/android:extractNativeLibs="false"/android:extractNativeLibs="true" android:debuggable="true"/' "${APK_MAIN_DECOMPILED_FOLDER}/AndroidManifest.xml" + + + +########################## +# Re-package and re-sign # +########################## +echo "Re-packaging and re-signing APKs..." + +# Re-package APKs +apktool b -o "${APK_PATCHED_FOLDER}/base.apk" ${APK_MAIN_DECOMPILED_FOLDER} + +cp "${APK_SOURCE_FOLDER}/split_config.arm64_v8a.apk" "${APK_PATCHED_FOLDER}/split_config.arm64_v8a.apk" +cd "${APK_FLUTTER_DECOMPILED_FOLDER}" && zip -ur "${APK_PATCHED_FOLDER}/split_config.arm64_v8a.apk" lib/ + +# Re-sign APKs +java -jar "${TOOLS_FOLDER}/uber-apk-signer-${UBER_APK_SIGNER_VERSION}.jar" -o $APK_SIGNED_FOLDER --allowResign --apks \ + "${APK_PATCHED_FOLDER}/base.apk" \ + "${APK_PATCHED_FOLDER}/split_config.arm64_v8a.apk" \ + "${APK_SOURCE_FOLDER}/split_config.en.apk" \ + "${APK_SOURCE_FOLDER}/split_config.xxhdpi.apk" \ + "${APK_SOURCE_FOLDER}/split_flutter_assets_pack.apk" + +# Uninstall existing Anker app +adb -s $DEVICE uninstall com.anker.charging + +# Install patched APKs +adb -s $DEVICE install-multiple \ + "${APK_SIGNED_FOLDER}/base-aligned-debugSigned.apk" \ + "${APK_SIGNED_FOLDER}/split_config.arm64_v8a-aligned-debugSigned.apk" \ + "${APK_SIGNED_FOLDER}/split_config.en-aligned-debugSigned.apk" \ + "${APK_SIGNED_FOLDER}/split_config.xxhdpi-aligned-debugSigned.apk" \ + "${APK_SIGNED_FOLDER}/split_flutter_assets_pack-aligned-debugSigned.apk" diff --git a/scripts/run.sh b/scripts/run.sh index 3dcd6f5..90f86e0 100755 --- a/scripts/run.sh +++ b/scripts/run.sh @@ -3,11 +3,11 @@ # Script name : run.sh # Description : Script for executing patched Anker app and starting Frida. # Author : Harvey Lelliott (@flip-dots) -# Date : 23/03/26 +# Date : 16/07/26 # Usage : ./run.sh [ADB device (e.g 192.168.1.1:1234)] # # License : MIT -# Revision : 1.0.0 +# Revision : 1.1.0 # set -euxo pipefail @@ -55,12 +55,16 @@ echo "Starting execution..." # Port forward the port used by Frida gadget adb -s $DEVICE forward tcp:49152 tcp:49152 -# In 5 seconds open the app (non-blocking) -(sleep 5 && adb -s $DEVICE shell monkey -p com.anker.charging 1 && echo "Restarted app!") & +# Kill app +adb -s "$DEVICE" shell am force-stop com.anker.charging +sleep 1 -# Open the app and execute the Frida script -adb -s $DEVICE shell monkey -p com.anker.charging 1 \ - && frida -H 127.0.0.1:49152 -n Gadget -l frida.js \ +# Open app +adb -s $DEVICE shell monkey -p com.anker.charging 1 +sleep 1 + +# Execute Frida script +frida -H 127.0.0.1:49152 -n Gadget -l frida.js \ 2>&1 | tee -a "${LOG_FOLDER}/${TIMESTAMP}.log" echo "Done!"