Create on-device Bluetooth patcher module (arm64)

Tested and working on Android 15 QPR2 (library: `/apex/com.android.btservices/lib64/libbluetooth_jni.so`)
This commit is contained in:
Paul
2025-01-25 14:55:34 +01:00
parent 93838d2d10
commit e1dc2c8925
4 changed files with 157 additions and 54 deletions

2
.gitignore vendored
View File

@@ -1,3 +1,5 @@
btl2capfix.zip
.vscode
testing.py
.DS_Store

11
build-magisk-module.sh Executable file
View File

@@ -0,0 +1,11 @@
#!/bin/sh
set -eux
cd root-module
rm -f ../btl2capfix.zip
# COPYFILE_DISABLE env is a macOS fix to avoid parasitic files in ZIPs: https://superuser.com/a/260264
export COPYFILE_DISABLE=1
zip -r ../btl2capfix.zip . -x \*.DS_Store \*__MACOSX \*DEBIAN ._\* .gitignore

View File

@@ -1,59 +1,148 @@
#!/system/bin/sh
API_URL="https://aln.kavishdevar.me/api"
TEMP_DIR="$TMPDIR/aln_patch"
PATCHED_FILE_NAME=""
# Note: these two exec redirs are not strictly POSIX-compliant, so they can be commented out if we notice that it shows a syntax error in some environments (unlikely to happen)
# Redirect stdout to ui_print otherwise it's not shown
exec 1> >(while read -r line; do ui_print "[O] $line"; done)
# Redirect stderr to ui_print otherwise it's not shown + ignore useless radare2 warning that clutters the logs
exec 2> >(while read -r line; do echo "$line" | grep -qv "Cannot determine entrypoint, using" && ui_print "[E] $line"; done)
TEMP_DIR="/data/local/tmp/aln_patch"
# Note: this dir cannot be changed without recompiling radare2 because this prefix are hardcoded inside the radare2 binaries: /data/local/tmp/aln_unzip/org.radare.radare2installer/radare2/
UNZIP_DIR="/data/local/tmp/aln_unzip"
SOURCE_FILE=""
LIBRARY_NAME=""
APEX_DIR=false
mkdir -p "$TEMP_DIR"
# Clean things up if the script crashes or exits
trap 'rm -rf "$TEMP_DIR" "$UNZIP_DIR"' EXIT INT TERM
CURL_CMD=$(command -v curl || echo "$MODPATH/system/bin/curl")
export LD_LIBRARY_PATH="$MODPATH/system/lib64:$LD_LIBRARY_PATH"
# https://github.com/Magisk-Modules-Repo/busybox-ndk/blob/master/busybox-arm64
BUSYBOX="$UNZIP_DIR/busybox/busybox-arm64"
XZ="$UNZIP_DIR/busybox/xz"
if [ -f "/apex/com.android.btservices/lib64/libbluetooth_jni.so" ]; then
SOURCE_FILE="/apex/com.android.btservices/lib64/libbluetooth_jni.so"
LIBRARY_NAME="libbluetooth_jni.so"
PATCHED_FILE_NAME="libbluetooth_jni_patched.so"
ui_print "Detected library: libbluetooth_jni.so in /apex/com.android.btservices/lib64/"
elif [ -f "/system/lib64/libbluetooth_jni.so" ]; then
SOURCE_FILE="/system/lib64/libbluetooth_jni.so"
LIBRARY_NAME="libbluetooth_jni.so"
PATCHED_FILE_NAME="libbluetooth_jni_patched.so"
ui_print "Detected library: libbluetooth_jni.so in /system/lib64/"
elif [ -f "/system/lib64/libbluetooth_qti.so" ]; then
SOURCE_FILE="/system/lib64/libbluetooth_qti.so"
LIBRARY_NAME="libbluetooth_qti.so"
PATCHED_FILE_NAME="libbluetooth_qti_patched.so"
ui_print "Detected QTI library: libbluetooth_qti.so in /system/lib64/"
elif [ -f "/system_ext/lib64/libbluetooth_qti.so" ]; then
SOURCE_FILE="/system_ext/lib64/libbluetooth_qti.so"
LIBRARY_NAME="libbluetooth_qti.so"
PATCHED_FILE_NAME="libbluetooth_qti_patched.so"
ui_print "Detected QTI library: libbluetooth_qti.so in /system_ext/lib64/"
rm -rf "$TEMP_DIR" "$UNZIP_DIR"
mkdir -p "$TEMP_DIR" "$UNZIP_DIR"
# Manually extract the $ZIPFILE to a temporary directory
ui_print "Extracting module files..."
unzip -d "$UNZIP_DIR" -oq "$ZIPFILE" || {
ui_print "Error: Failed to extract module files."
abort "Failed to unzip $ZIPFILE"
}
set_perm "$BUSYBOX" 0 0 755
set_perm "$XZ" 0 0 755
# The bundled radare2 is a custom build that works without Termux: https://github.com/devnoname120/radare2
ui_print "Extracting radare2 to /data/local/tmp/aln_unzip..."
$BUSYBOX tar xzf "$UNZIP_DIR/radare2-5.9.9-android-aarch64.tar.gz" -C / || {
abort "Failed to extract "$UNZIP_DIR/radare2-5.9.9-android-aarch64.tar.gz"."
}
if [ "$(uname -m)" = "aarch64" ]; then
export LD_LIBRARY_PATH="$UNZIP_DIR/org.radare.radare2installer/radare2/lib:$LD_LIBRARY_PATH"
export PATH="$UNZIP_DIR/org.radare.radare2installer/radare2/bin:$PATH"
export PATH="$UNZIP_DIR/busybox:$PATH"
export RABIN2="$UNZIP_DIR/org.radare.radare2installer/radare2/bin/rabin2"
export RADARE2="$UNZIP_DIR/org.radare.radare2installer/radare2/bin/radare2"
else
ui_print "No target library found. Exiting."
abort "No target library found."
abort "arm64 archicture required, arm32 not supported"
fi
ui_print "Uploading $LIBRARY_NAME to the API for patching..."
ui_print "If you're concerned about privacy, review the source code of the API at https://github.com/kavishdevar/aln/blob/main/root-module-manual/server.py"
PATCHED_FILE_NAME="patched_$LIBRARY_NAME"
set_perm "$RABIN2" 0 0 755
set_perm "$RADARE2" 0 0 755
$CURL_CMD -s -X POST "$API_URL" \
-F "file=@$SOURCE_FILE" \
-F "library_name=$LIBRARY_NAME" \
-o "$TEMP_DIR/$PATCHED_FILE_NAME" \
-D "$TEMP_DIR/headers.txt"
if [ -f "$RABIN2" ]; then
ui_print "rabin2 binary is ready."
else
ui_print "Error: rabin2 binary not found."
abort "rabin2 binary not found."
fi
if [ -f "$TEMP_DIR/$PATCHED_FILE_NAME" ]; then
ui_print "Received patched file from the API."
ui_print "Installing patched file to the module's directory..."
if [ -f "$RADARE2" ]; then
ui_print "radare2 binary is ready."
else
ui_print "Error: radare2 binary not found."
abort "radare2 binary not found."
fi
if [[ "$SOURCE_FILE" == *"/system/lib64"* ]]; then
if [ -f "$BUSYBOX" ]; then
ui_print "busybox binary is ready."
else
ui_print "Error: busybox binary not found."
abort "busybox binary not found."
fi
if [ -f "$XZ" ]; then
ui_print "xz shim is ready."
else
ui_print "Error: xz shim not found."
abort "xz shim not found."
fi
for lib_path in \
"/apex/com.android.btservices/lib64/libbluetooth_jni.so" \
"/system/lib64/libbluetooth_jni.so" \
"/system/lib64/libbluetooth_qti.so" \
"/system_ext/lib64/libbluetooth_qti.so"; do
if [ -f "$lib_path" ]; then
SOURCE_FILE="$lib_path"
LIBRARY_NAME="$(basename "$lib_path")"
ui_print "Detected library: $SOURCE_FILE"
break
fi
done
[ -z "$SOURCE_FILE" ] && {
ui_print "Error: No target library found."
abort "No target library found."
}
ui_print "Calculating patch addresses for $SOURCE_FILE..."
# export R2_LIBDIR="$UNZIP_DIR/radare2-android/libs/arm64-v8a"
# export R2_BINDIR="$UNZIP_DIR/radare2-android/bin/arm64-v8a"
# $RADARE2 -H 1>&2
# ldd $RABIN2 1>&2
# ldd $RADARE2 1>&2
symbols="$($RABIN2 -q -E "$SOURCE_FILE")" || abort "Failed to extract symbols from $SOURCE_FILE."
get_symbol_address() {
symb_address=$(echo "$symbols" | grep "$1" | cut -d ' ' -f1 | tr -d '\n')
[ -n "$symb_address" ] || abort "Failed to obtain address for symbol $1"
echo "$symb_address"
}
l2c_fcr_chk_chan_modes_address="$(get_symbol_address 'l2c_fcr_chk_chan_modes')"
ui_print " l2c_fcr_chk_chan_modes_address=$l2c_fcr_chk_chan_modes_address"
l2cu_send_peer_info_req_address="$(get_symbol_address 'l2cu_send_peer_info_req')"
ui_print " l2cu_send_peer_info_req_address=$l2cu_send_peer_info_req_address"
cp "$SOURCE_FILE" "$TEMP_DIR"
ui_print "Patching $LIBRARY_NAME..."
apply_patch() {
$RADARE2 -q -e bin.cache=true -w -c "s $1; wx $2; wci" "$TEMP_DIR/$LIBRARY_NAME" || abort "Failed to apply $1 patch."
}
apply_patch "$l2c_fcr_chk_chan_modes_address" "20008052c0035fd6"
apply_patch "$l2cu_send_peer_info_req_address" "c0035fd6"
if [ -f "$TEMP_DIR/$LIBRARY_NAME" ]; then
ui_print "Installing patched file..."
if echo "$SOURCE_FILE" | grep -q "/system/lib64"; then
TARGET_DIR="$MODPATH/system/lib64"
elif [[ "$SOURCE_FILE" == *"/apex/"* ]]; then
elif echo "$SOURCE_FILE" | grep -q "/apex/"; then
TARGET_DIR="$MODPATH/system/lib64"
APEX_DIR=true
else
@@ -62,7 +151,7 @@ if [ -f "$TEMP_DIR/$PATCHED_FILE_NAME" ]; then
mkdir -p "$TARGET_DIR"
cp "$TEMP_DIR/$PATCHED_FILE_NAME" "$TARGET_DIR/$LIBRARY_NAME"
cp "$TEMP_DIR/$LIBRARY_NAME" "$TARGET_DIR/$LIBRARY_NAME"
set_perm "$TARGET_DIR/$LIBRARY_NAME" 0 0 644
ui_print "Patched file installed at $TARGET_DIR/$LIBRARY_NAME"
@@ -72,10 +161,9 @@ if [ -f "$TEMP_DIR/$PATCHED_FILE_NAME" ]; then
MOD_APEX_LIB_DIR="$MODPATH/apex/com.android.btservices/lib64"
WORK_DIR="$MODPATH/apex/com.android.btservices/work"
mkdir -p "$MOD_APEX_LIB_DIR"
mkdir -p "$WORK_DIR"
mkdir -p "$MOD_APEX_LIB_DIR" "$WORK_DIR"
cp "$TEMP_DIR/$PATCHED_FILE_NAME" "$MOD_APEX_LIB_DIR/$LIBRARY_NAME"
cp "$TEMP_DIR/$LIBRARY_NAME" "$MOD_APEX_LIB_DIR/$LIBRARY_NAME"
set_perm "$MOD_APEX_LIB_DIR/$LIBRARY_NAME" 0 0 644
cat <<EOF > "$POST_DATA_FS_SCRIPT"
@@ -84,13 +172,15 @@ mount -t overlay overlay -o lowerdir=$APEX_LIB_DIR,upperdir=$MOD_APEX_LIB_DIR,wo
EOF
set_perm "$POST_DATA_FS_SCRIPT" 0 0 755
ui_print "Created post-data-fs.sh script for apex library handling."
ui_print "Created script for apex library handling."
ui_print "You can now restart your device and test aln!"
ui_print "Note: If your Bluetooth doesn't work anymore after restarting, then uninstall this module and report the issue at the link below."
ui_print "https://github.com/kavishdevar/aln/issues/new"
fi
else
ERROR_MESSAGE=$(grep -oP '(?<="error": ")[^"]+' "$TEMP_DIR/headers.txt")
ui_print "API Error: $ERROR_MESSAGE"
rm -rf "$TEMP_DIR"
ui_print "Error: patched file missing."
rm -rf "$TEMP_DIR" "$UNZIP_DIR"
abort "Failed to patch the library."
fi
rm -rf "$TEMP_DIR"
rm -rf "$TEMP_DIR" "$UNZIP_DIR"

View File

@@ -1,6 +1,6 @@
id=btl2capfix
name=Bluetooth L2CAP workaround for AirPods
version=v1
versionCode=1
author=kavishdevar
version=v3
versionCode=3
author=@devnoname120 and @kavishdevar
description=Fixes the Bluetooth L2CAP connection issue with AirPods