App decoding

In order to fix protocol incompatibilities and/or add support for controlling a device, it may be necessary to intercept the connection between the Anker app and the device. This allows for analysis of what commands sent by the app do what, and how different devices negotiate. How to perform this is covered in this section.

Requirements

Device requirements:

  • Android device with developer mode enabled.

  • Anker app installed on the device.

Software requirements:

  • bash

  • wget

  • adb

  • apktool

  • java

  • frida

Patching

In order to capture the messages sent between the phone and the device the app must be patched to include Frida. Frida is a dynamic instrumentation toolkit which allows for us to hook functions, this is necessary to dump shared preferences, get logs of all packets sent and received, as well as providing us a view of the encryption functions. Patching can be done manually, using tools by others or the patch.sh script inside the scripts folder of the project repository.

Note

If your device is rooted you can use frida-server instead of this approach.

patch.sh

This script is provided to automate the process of patching the Anker app. It takes one argument which is the ID of the Android device with the Anker app installed (e.g 192.168.1.1:1234, ABCDEF, etc), and then automatically copies the Anker app from the device, patches it, and installs the new patched version. It is designed to be called from the scripts directory.

Note

If you do not know the devices ID you can use adb devices -l to discover it.

This script performs the following actions:

  1. Downloads (some) tools and dependencies needed to patch the app.

  2. Downloads the installed Anker app from the connected Android device.

  3. De-compiles/unpacks the Anker app.

  4. Injects Frida gadget into the Anker apps files.

  5. Modifies AndroidManifest.xml to support using Frida gadget.

  6. Re-packages the APK.

  7. Re-signs the APK.

  8. Uninstalls the old Anker app.

  9. Installs the patched Anker app.

Example usage:

./patch.sh 192.168.0.1:1234

Note

The patched Anker app will not be usable for regular usage, it will only work when Frida is being run to actively patch the app.

Using modified app

The modified app can be used with the Frida script to capture all Bluetooth packets, the encryption keys, shared preferences, and plain/cipher texts.

The Anker app features a fairly robust anti-tamper mechanism which is non-trivial to evade. Instead the Frida script disables the anti-tamper mechanisms ability to kill the app, this means you must execute the Frida script within at most a second of opening the app.

To manually capture the logs follow these steps:

  1. Port forward the port used by Frida gadget: adb -s <your device> forward tcp:49152 tcp:49152.

  2. Open the Anker app on the device.

  3. Immediately execute: frida -H 127.0.0.1:49152 -n Gadget -l scripts/frida.js.

  4. The app will crash but Frida should remain open.

  5. Re-open the app on the device.

  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.

run.sh

This script is provided to automate the steps required to use the modified app 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.

This script performs the following actions:

  1. Starts the app.

  2. Executes the frida.js script.

  3. Waits 5 seconds and re-opens the app.

When the app has re-opened you should then perform the actions you wish to observe the result of. If you wish to observe a negotiation you should connect to a new device in the Anker app, or if you wish to observe what bytes are sent when you turn a switch on/off you should turn that switch on/off in the app and observe the log output. These log outputs can then be used to add equivalent functionality to this library, see new device control for more information.

Example usage:

./run.sh 192.168.0.1:1234

Note

The script is more reliable than executing the steps by hand but still fails, it may be necessary to try several times.

Example captures

Below are example outputs of the patched app with the Frida script.

Example log output including encryption, writing BLE data, and shared preferences.
 1[+] --- Cipher.init() ---
 2Mode: ENCRYPT
 3Algorithm: AES/GCM/NoPadding
 4Key (Hex): b8ff7422955d4eb6d554a2c470280559
 5
 6[+] --- Cipher.doFinal() ---
 7Input (Hex): a104f178c169
 8Output (Hex): 0a82ceaa27534ee0681742badc14e61ea9bd1c70776c
 9[*] Added a new boolean value to SharedPreferences with key: flutter.isBleConnected and value true
10
11
12[BLE WRITE] UUID: 8c850002-0302-41c5-b46e-cf057c562025
13Data (Hex): ff09200003000140010a82ceaa27534ee0681742badc14e61ea9bd1c70776c77
Example log output including receiving BLE data, decryption, and shared preferences.
 1[BLE NOTIFY] UUID: 8c850003-0302-41c5-b46e-cf057c562025
 2Data (Hex): ff0929000301114a0ab40833040922fad9c2ec9f4683c68fc6ce4f7faa3c62733f1b361f495bc5085f
 3[*] Getting string value from SharedPreferences with key: ecdh-keye8ad18f61bbd3fbd52d5ed12d14d3b9c and value
 4
 5[+] --- Cipher.init() ---
 6Mode: DECRYPT
 7Algorithm: AES/GCM/NoPadding
 8Key (Hex): e0ed5b45a01466efada647a212aa79f0
 9IV/Nonce (Hex): 471f692c44b6ab6319cb3a63
10
11[+] --- Cipher.doFinal() ---
12Input (Hex): b40833040922fad9c2ec9f4683c68fc6ce4f7faa3c62733f1b361f495bc508
13Output (Hex): 00a10131a2020100fe050300000000
14[*] Added a new String value to SharedPreferences with key: flutter.deviceCustomProtocolASHDK7U1F51501771 and value {"version":"v1.1.1","port_list":[{"title":"C1","ports":["C1"],"protocols":[{"range":"C1","subTitle":"C1","list":[{"name":"UFCS","status":1},{"name":"SCP","status":1},{"name":"PD 12V","status":0},{"name":"5-11V PPS","status":1},{"name":"5-16V PPS","status":1},{"name":"4.5-21V PPS","status":1}]}],"tlv":[59]},{"title":"C2","ports":["C2"],"protocols":[{"range":"C2","subTitle":"C2","list":[{"name":"UFCS","status":1},{"name":"SCP","status":1},{"name":"PD 12V","status":0},{"name":"5-11V PPS","status":1},{"name":"5-16V PPS","status":1},{"name":"4.5-21V PPS","status":1}]}],"tlv":[59]},{"title":"C3","ports":["C3"],"protocols":[{"range":"C3","subTitle":"C3","list":[{"name":"UFCS","status":1},{"name":"SCP","status":1},{"name":"PD 12V","status":0},{"name":"5-11V PPS","status":1},{"name":"5-16V PPS","status":1},{"name":"4.5-21V PPS","status":1}]}],"tlv":[59]}]}
Example log output including encryption/decryption, sending/receiving BLE data, key negotiation, and shared preferences.
 1[+] --- Cipher.init() ---
 2Mode: ENCRYPT
 3Algorithm: AES/GCM/NoPadding
 4Key (Hex): b8ff7422955d4eb6d554a2c470280559
 5IV/Nonce (Hex): 6ba3e3f2f3a60f2971ce5d1f
 6
 7[+] --- Cipher.doFinal() ---
 8Input (Hex): a104f178c169a30120a4022901a50144a60102
 9Output (Hex): 0a82ceaa27538ab3de100ae04aca6791257881fa9bded4360e2e18a10a4f37155e4646
10
11[BLE WRITE] UUID: 8c850002-0302-41c5-b46e-cf057c562025
12Data (Hex): ff092d0003000140050a82ceaa27538ab3de100ae04aca6791257881fa9bded4360e2e18a10a4f37155e46464e
13
14[BLE NOTIFY] UUID: 8c850003-0302-41c5-b46e-cf057c562025
15Data (Hex): ff091b000300014805abab709a595a803dd04246b78a927453cf65
16
17[+] --- Cipher.init() ---
18Mode: DECRYPT
19Algorithm: AES/GCM/NoPadding
20Key (Hex): b8ff7422955d4eb6d554a2c470280559
21IV/Nonce (Hex): 6ba3e3f2f3a60f2971ce5d1f
22
23[+] --- Cipher.doFinal() ---
24Input (Hex): abab709a595a803dd04246b78a927453cf
25Output (Hex): 00
26[*] getSharedPreferences called with name: com.anker.charging_ifs and mode: 0
27
28[*] Getting string value from SharedPreferences with key: ecdh_key and value
29
30[*] Added a new String value to SharedPreferences with key: ecdh_key and value {"appPrivateKey":"00f94c23e724cab6adf2b8b8767609af78af6423844b0ade6c1380b813b803af5e","appPublicKey":"0478242a84f8a9502b9d183e8c7da30d1630d9c2ca8d05800e79cf17e30745e4761486e35721036a8d21031ddc207807bf906e056d5aec1b1d1b7330d58e8552f9","serverPublicKey":"04c5c00c4f8d1197cc7c3167c52bf7acb054d722f0ef08dcd7e0883236e0d72a3868d9750cb47fa4619248f3d83f0f662671dadc6e2d31c2f41db0161651c7c076"}
31
32[+] --- Cipher.init() ---
33Mode: ENCRYPT
34Algorithm: AES/GCM/NoPadding
35Key (Hex): b8ff7422955d4eb6d554a2c470280559
36IV/Nonce (Hex): 6ba3e3f2f3a60f2971ce5d1f
37
38[+] --- Cipher.doFinal() ---
39Input (Hex): a14078242a84f8a9502b9d183e8c7da30d1630d9c2ca8d05800e79cf17e30745e4761486e35721036a8d21031ddc207807bf906e056d5aec1b1d1b7330d58e8552f9
40Output (Hex): 0ac647f6ccbed11bae9f95d175e31b768e6fb309f82e4d8776e1999923b2fc7b34ecd8c19dc1923cd3e1370ab601eb2454eebe3f0df91572b04f8c2fddb802cc5e8ac304fe7f9c34f41794528e1fc69e8417
41
42[BLE WRITE] UUID: 8c850002-0302-41c5-b46e-cf057c562025
43Data (Hex): ff095c0003000140210ac647f6ccbed11bae9f95d175e31b768e6fb309f82e4d8776e1999923b2fc7b34ecd8c19dc1923cd3e1370ab601eb2454eebe3f0df91572b04f8c2fddb802cc5e8ac304fe7f9c34f41794528e1fc69e84171a

Scripts

The scripts are provided below for convenience in addition to being in the main repository.

patch.sh

This script automates the patching of the Anker app

#!/bin/bash
#
# Script name   : patch.sh
# Description   : Script for injecting Frida gadget into Anker Android app
# Author        : Harvey Lelliott (@flip-dots)
# Date          : 23/03/26
# Usage         : ./patch.sh [ADB device (e.g 192.168.1.1:1234)]
# 
# License       : MIT
# Revision      : 1.0.0
#
set -euxo pipefail


#############
# Constants #
#############

FRIDA_VERSION="17.8.2"

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 source APK inside
APK_DECOMPILED_FOLDER="${DATA_FOLDER}/base_apk_decompiled"
mkdir -p $APK_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 "undetected-frida-gadget-${FRIDA_VERSION}-android-arm.so.xz"
cd $FRIDA_FOLDER && unxz "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

# Decompile main APK
echo "Decompiling main APK..."
apktool d "${APK_SOURCE_FOLDER}/base.apk" -o $APK_DECOMPILED_FOLDER -f


################
# Inject Frida #
################
echo "Injecting Frida gadget into main APK..."

# Copy Frida gadget binaries
mkdir -p "${APK_DECOMPILED_FOLDER}/lib/armeabi"
mkdir -p "${APK_DECOMPILED_FOLDER}/lib/armeabi-v7a"
mkdir -p "${APK_DECOMPILED_FOLDER}/lib/arm64-v8a"
cp "${FRIDA_FOLDER}/undetected-frida-gadget-${FRIDA_VERSION}-android-arm.so" "${APK_DECOMPILED_FOLDER}/lib/armeabi/libnative-utils.so"
cp "${FRIDA_FOLDER}/undetected-frida-gadget-${FRIDA_VERSION}-android-arm.so" "${APK_DECOMPILED_FOLDER}/lib/armeabi-v7a/libnative-utils.so"
cp "${FRIDA_FOLDER}/undetected-frida-gadget-${FRIDA_VERSION}-android-arm64.so" "${APK_DECOMPILED_FOLDER}/lib/arm64-v8a/libnative-utils.so"

cp "${WORKING_FOLDER}/frida_config.json" "${APK_DECOMPILED_FOLDER}/lib/armeabi/libnative-utils.config.so"
cp "${WORKING_FOLDER}/frida_config.json" "${APK_DECOMPILED_FOLDER}/lib/armeabi-v7a/libnative-utils.config.so"
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"

# Modify manifest to enable Frida loading
sed -i '' '/<application/,/>/ s/android:allowBackup="false"/android:allowBackup="true"/' "${APK_DECOMPILED_FOLDER}/AndroidManifest.xml"
sed -i '' '/<application/,/>/ s/android:extractNativeLibs="false"/android:extractNativeLibs="true" android:debuggable="true"/' "${APK_DECOMPILED_FOLDER}/AndroidManifest.xml"


##########################
# Re-package and re-sign #
##########################
echo "Re-packaging and re-signing APK..."

# Re-package base/main APK
apktool b -o "${APK_PATCHED_FOLDER}/base.apk" ${APK_DECOMPILED_FOLDER}

# 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_SOURCE_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"

frida.js

This script neuters the anti-tamper detection of the Anker app and hooks the shared preferences, encryption, and bluetooth functions to log their inputs and outputs.

/*
 * Script name   : frida.js
 * Description   : Frida script for preventing exit and extracting shared preferences
 * Author        : Harvey Lelliott (@flip-dots)
 * Date          : 23/03/26
 * 
 * License       : MIT
 * Revision      : 1.0.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() {
	Java.perform(function() {
		var contextWrapper = Java.use("android.content.ContextWrapper");
		contextWrapper.getSharedPreferences.overload('java.lang.String', 'int').implementation = function(var0, var1) {
			console.log("[*] getSharedPreferences called with name: " + var0 + " and mode: " + var1 + "\n");
			var sharedPreferences = this.getSharedPreferences(var0, var1);
			return sharedPreferences;
		};

		var sharedPreferencesEditor = Java.use("android.app.SharedPreferencesImpl$EditorImpl");
		sharedPreferencesEditor.putString.overload('java.lang.String', 'java.lang.String').implementation = function(var0, var1) {
			console.log("[*] Added a new String value to SharedPreferences with key: " + var0 + " and value " + var1 + "\n");
			var editor = this.putString(var0, var1);
			return editor;
		}

		sharedPreferencesEditor.putBoolean.overload('java.lang.String', 'boolean').implementation = function(var0, var1) {
			console.log("[*] Added a new boolean value to SharedPreferences with key: " + var0 + " and value " + var1 + "\n");
			var editor = this.putBoolean(var0, var1);
			return editor;
		}

		sharedPreferencesEditor.putFloat.overload('java.lang.String', 'float').implementation = function(var0, var1) {
			console.log("[*] Added a new float value to SharedPreferences with key: " + var0 + " and value " + var1 + "\n");
			var editor = this.putFloat(var0, var1);
			return editor;
		}

		sharedPreferencesEditor.putInt.overload('java.lang.String', 'int').implementation = function(var0, var1) {
			console.log("[*] [*] Added a new int value to SharedPreferences with key: " + var0 + " and value " + var1 + "\n");
			var editor = this.putInt(var0, var1);
			return editor;
		}

		sharedPreferencesEditor.putLong.overload('java.lang.String', 'long').implementation = function(var0, var1) {
			console.log("[*] Added a new long value to SharedPreferences with key: " + var0 + " and value " + var1 + "\n");
			var editor = this.putLong(var0, var1);
			return editor;
		}

		sharedPreferencesEditor.putStringSet.overload('java.lang.String', 'java.util.Set').implementation = function(var0, var1) {
			console.log("[*] Added a new string set to SharedPreferences with key: " + var0 + " and value " + var1 + "\n");
			var editor = this.putStringSet(var0, var1);
			return editor;
		}

		var sharedPreferences = Java.use("android.app.SharedPreferencesImpl");
		sharedPreferences.getString.overload('java.lang.String', 'java.lang.String').implementation = function(var0, var1) {
			console.log("[*] Getting string value from SharedPreferences with key: " + var0 + " and value " + var1 + "\n");
			var stringVal = this.getString(var0, var1);
			return stringVal;
		}
	});
});

// Log any encryption operations
Java.perform(function () {
    const Cipher = Java.use('javax.crypto.Cipher');

    function toHex(byteArray) {
        if (!byteArray) return "null";
        var result = "";
        for (var i = 0; i < byteArray.length; i++) {
            result += ('0' + (byteArray[i] & 0xFF).toString(16)).slice(-2);
        }
        return result;
    }

    // Hook init() to capture Key, IV, Nonce, and Mode
    const initOverloads = Cipher.init.overloads;
    initOverloads.forEach(function (overload) {
        overload.implementation = function () {
            const opmode = arguments[0];
            const key = arguments[1];
            const iv = this.getIV();
            const modeName = (opmode === 1) ? "ENCRYPT" : (opmode === 2) ? "DECRYPT" : opmode;

            console.log("\n[+] --- Cipher.init() ---");
            console.log("Mode: " + modeName);
            console.log("Algorithm: " + this.getAlgorithm());

            if (key) {
                console.log("Key (Hex): " + toHex(key.getEncoded()));
            }
            if (iv) {
                console.log("IV/Nonce (Hex): " + toHex(iv));
            }
            return overload.apply(this, arguments);
        };
    });

    // Hook doFinal() to capture input and output of encryption algorithm
    const doFinalOverloads = Cipher.doFinal.overloads;
    doFinalOverloads.forEach(function (overload) {
        overload.implementation = function () {
            const input = arguments[0];
            const result = overload.apply(this, arguments);

            console.log("\n[+] --- Cipher.doFinal() ---");
            if (input && input.length > 0) {
                console.log("Input (Hex): " + toHex(input));
            }
            if (result && result.length > 0) {
                console.log("Output (Hex): " + toHex(result));
            }
            return result;
        };
    });
});

// Log any Bluetooth I/O
Java.perform(function () {
    const BluetoothGatt = Java.use('android.bluetooth.BluetoothGatt');
    const BluetoothGattCharacteristic = Java.use('android.bluetooth.BluetoothGattCharacteristic');

    function toHex(byteArray) {
        if (!byteArray) return "null";
        var result = "";
        for (var i = 0; i < byteArray.length; i++) {
            result += ('0' + (byteArray[i] & 0xFF).toString(16)).slice(-2);
        }
        return result;
    }

    // Hook BluetoothGatt.writeCharacteristic (Phone -> Device)
    const writeOverloads = BluetoothGatt.writeCharacteristic.overloads;
    writeOverloads.forEach(function (overload) {
        overload.implementation = function () {
            const char = arguments[0];
            let data = (arguments.length >= 2) ? arguments[1] : char.getValue();

            console.log("\n[BLE WRITE] UUID: " + char.getUuid());
            console.log("Data (Hex): " + toHex(data));
            return overload.apply(this, arguments);
        };
    });

    // Hook BluetoothGattCharacteristic.setValue (Device -> Phone)
    const setValueOverloads = BluetoothGattCharacteristic.setValue.overloads;
    setValueOverloads.forEach(function (overload) {
        overload.implementation = function () {
            const uuid = this.getUuid().toString();
            const value = arguments[0];

            console.log("\n[BLE NOTIFY] UUID: " + uuid);
            console.log("Data (Hex): " + toHex(value))
            return overload.apply(this, arguments);
        };
    });
});

run.sh

This script executes the modified Anker app and logs the output to a file.

#!/bin/bash
#
# Script name   : run.sh
# Description   : Script for executing patched Anker app and starting Frida.
# Author        : Harvey Lelliott (@flip-dots)
# Date          : 23/03/26
# Usage         : ./run.sh [ADB device (e.g 192.168.1.1:1234)]
# 
# License       : MIT
# Revision      : 1.0.0
#
set -euxo pipefail


##################################
# 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

# The timestamp to use for log file
TIMESTAMP=$(date "+%Y-%m-%d %H:%M:%S")

# Check tools
command -v adb >/dev/null 2>&1 || { echo >&2 "adb 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
LOG_FOLDER="${WORKING_FOLDER}/logs"
mkdir -p $LOG_FOLDER


#############
# Execution #
#############
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!") &

# 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 \
    2>&1 | tee -a "${LOG_FOLDER}/${TIMESTAMP}.log"

echo "Done!"