mirror of
https://github.com/NohamR/RMHook-Android.git
synced 2026-10-10 18:29:42 +00:00
Add Android build and APK injection scripts
This commit is contained in:
136
libs/build_deps.sh
Executable file
136
libs/build_deps.sh
Executable file
@@ -0,0 +1,136 @@
|
||||
#!/bin/bash
|
||||
set -euo pipefail
|
||||
|
||||
# Builds shadowhook as a static library for Android arm64.
|
||||
# Produces: libs/libshadowhook.a and libs/libshadowhook_nothing.so
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
||||
PROJECT_DIR="$(dirname "$SCRIPT_DIR")"
|
||||
BUILD_DIR="$PROJECT_DIR/build/deps"
|
||||
mkdir -p "$BUILD_DIR"
|
||||
|
||||
# --- Auto-detect Android NDK (same logic as build.sh) ---
|
||||
find_ndk() {
|
||||
if [ -n "${ANDROID_NDK_HOME:-}" ] && [ -d "$ANDROID_NDK_HOME" ]; then
|
||||
echo "$ANDROID_NDK_HOME"; return
|
||||
fi
|
||||
local brew_ndk
|
||||
brew_ndk=$(find /opt/homebrew /usr/local -path "*/android-ndk/*/toolchains/llvm/prebuilt" -type d 2>/dev/null | head -1)
|
||||
if [ -n "$brew_ndk" ]; then
|
||||
echo "$(dirname "$(dirname "$(dirname "$brew_ndk")")")"; return
|
||||
fi
|
||||
local sdk_ndk
|
||||
sdk_ndk=$(find "$HOME/Library/Android/sdk/ndk" -maxdepth 1 -type d 2>/dev/null | sort -V | tail -1)
|
||||
if [ -n "$sdk_ndk" ] && [ -d "$sdk_ndk" ]; then
|
||||
echo "$sdk_ndk"; return
|
||||
fi
|
||||
for p in /opt/android-ndk /usr/local/android-ndk "$HOME/android-ndk"; do
|
||||
if [ -d "$p" ]; then echo "$p"; return; fi
|
||||
done
|
||||
echo ""
|
||||
}
|
||||
|
||||
NDK_HOME="$(find_ndk)"
|
||||
API_LEVEL="${ANDROID_API_LEVEL:-21}"
|
||||
CC=""
|
||||
if [ -n "$NDK_HOME" ]; then
|
||||
CC=$(find "$NDK_HOME/toolchains/llvm/prebuilt" -type f \
|
||||
-name "aarch64-linux-android${API_LEVEL}-clang" -print -quit 2>/dev/null)
|
||||
fi
|
||||
if [ -z "$CC" ]; then
|
||||
echo "ERROR: aarch64-linux-android clang not found."
|
||||
echo " Set ANDROID_NDK_HOME to your NDK path."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
TOOLCHAIN_DIR="$(dirname "$CC")"
|
||||
AR="$TOOLCHAIN_DIR/llvm-ar"
|
||||
CFLAGS="-O2 -ffunction-sections -fdata-sections -fPIC -DANDROID -Wall -Wextra -Wno-unused-parameter -Wno-unused-function"
|
||||
|
||||
OUT_DIR="$SCRIPT_DIR"
|
||||
|
||||
echo "=== Building shadowhook (arm64-v8a) ==="
|
||||
|
||||
SH="$SCRIPT_DIR/shadowhook/shadowhook/src/main/cpp"
|
||||
|
||||
SH_SRCS="
|
||||
$SH/sh_elf.c
|
||||
$SH/sh_enter.c
|
||||
$SH/sh_hub.c
|
||||
$SH/sh_island.c
|
||||
$SH/sh_linker.c
|
||||
$SH/sh_recorder.c
|
||||
$SH/sh_safe.c
|
||||
$SH/sh_switch.c
|
||||
$SH/sh_task.c
|
||||
$SH/sh_xdl.c
|
||||
$SH/shadowhook.c
|
||||
$SH/arch/arm64/sh_a64.c
|
||||
$SH/arch/arm64/sh_inst.c
|
||||
$SH/arch/arm64/sh_glue.S
|
||||
$SH/common/bytesig.c
|
||||
$SH/common/sh_errno.c
|
||||
$SH/common/sh_log.c
|
||||
$SH/common/sh_ref.c
|
||||
$SH/common/sh_trampo.c
|
||||
$SH/common/sh_util.c
|
||||
$SH/third_party/xdl/xdl.c
|
||||
$SH/third_party/xdl/xdl_iterate.c
|
||||
$SH/third_party/xdl/xdl_linker.c
|
||||
$SH/third_party/xdl/xdl_lzma.c
|
||||
$SH/third_party/xdl/xdl_util.c
|
||||
"
|
||||
|
||||
SH_INCLUDES="
|
||||
-I$SH
|
||||
-I$SH/include
|
||||
-I$SH/arch/arm64
|
||||
-I$SH/common
|
||||
-I$SH/third_party/xdl
|
||||
-I$SH/third_party/bsd
|
||||
-I$SH/third_party/lss
|
||||
"
|
||||
|
||||
SH_OBJS=""
|
||||
for src in $SH_SRCS; do
|
||||
[ -z "$src" ] && continue
|
||||
src=$(echo "$src" | xargs)
|
||||
if [ ! -f "$src" ]; then
|
||||
echo "ERROR: shadowhook source not found: $src"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
basename=$(echo "$src" | sed "s|$SH/||" | tr '/' '_')
|
||||
obj="$BUILD_DIR/sh_${basename%.c}.o"
|
||||
obj="${obj%.S.o}.o"
|
||||
|
||||
if [[ "$src" == *.S ]]; then
|
||||
"$CC" $CFLAGS $SH_INCLUDES -c "$src" -o "$obj"
|
||||
else
|
||||
"$CC" $CFLAGS $SH_INCLUDES -c "$src" -o "$obj"
|
||||
fi
|
||||
SH_OBJS="$SH_OBJS $obj"
|
||||
done
|
||||
|
||||
if [ -z "$SH_OBJS" ]; then
|
||||
echo "ERROR: shadowhook produced no object files."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
"$AR" rcs "$OUT_DIR/libshadowhook.a" $SH_OBJS
|
||||
SH_SIZE=$(wc -c < "$OUT_DIR/libshadowhook.a" 2>/dev/null || echo "unknown")
|
||||
echo " Built $OUT_DIR/libshadowhook.a ($SH_SIZE bytes)"
|
||||
|
||||
NOTHING_SRC="$SH/nothing/sh_nothing.c"
|
||||
if [ ! -f "$NOTHING_SRC" ]; then
|
||||
echo "ERROR: ShadowHook companion source not found: $NOTHING_SRC"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
"$CC" -shared -Oz -fPIC -fno-ident -fno-unwind-tables -fno-asynchronous-unwind-tables \
|
||||
-nostdlib -nostartfiles -nodefaultlibs \
|
||||
-Wl,-z,max-page-size=16384 \
|
||||
-Wl,--strip-all -Wl,--as-needed -Wl,--hash-style=sysv \
|
||||
-Wl,--build-id=none \
|
||||
-o "$OUT_DIR/libshadowhook_nothing.so" "$NOTHING_SRC"
|
||||
echo " Built $OUT_DIR/libshadowhook_nothing.so"
|
||||
123
script/build.sh
Executable file
123
script/build.sh
Executable file
@@ -0,0 +1,123 @@
|
||||
#!/bin/bash
|
||||
# Script to compile the native hooking library for Android
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
PROJECT_DIR=$(cd "$(dirname "$0")/.." && pwd)
|
||||
SRC_DIR="$PROJECT_DIR/src"
|
||||
BUILD_DIR="$PROJECT_DIR/build"
|
||||
LIBS_DIR="$PROJECT_DIR/libs"
|
||||
API_LEVEL="${ANDROID_API_LEVEL:-21}"
|
||||
|
||||
# Qt path detection
|
||||
QT_DIR="${QT_DIR:-$HOME/Qt}"
|
||||
QT_VERSION="${QT_VERSION:-6.10.2}"
|
||||
QT_HOST_DIR="$QT_DIR/$QT_VERSION/android_arm64_v8a"
|
||||
QT_LIB_DIR="$QT_HOST_DIR/lib"
|
||||
|
||||
SO_NAME="librmhook.so"
|
||||
|
||||
# --- Auto-detect Android NDK ---
|
||||
find_ndk() {
|
||||
if [ -n "${ANDROID_NDK_HOME:-}" ] && [ -d "$ANDROID_NDK_HOME" ]; then
|
||||
echo "$ANDROID_NDK_HOME"; return
|
||||
fi
|
||||
local brew_ndk
|
||||
brew_ndk=$(find /opt/homebrew /usr/local -path "*/android-ndk/*/toolchains/llvm/prebuilt" -type d 2>/dev/null | head -1)
|
||||
if [ -n "$brew_ndk" ]; then
|
||||
echo "$(dirname "$(dirname "$(dirname "$brew_ndk")")")"; return
|
||||
fi
|
||||
local sdk_ndk
|
||||
sdk_ndk=$(find "$HOME/Library/Android/sdk/ndk" -maxdepth 1 -type d 2>/dev/null | sort -V | tail -1)
|
||||
if [ -n "$sdk_ndk" ] && [ -d "$sdk_ndk" ]; then
|
||||
echo "$sdk_ndk"; return
|
||||
fi
|
||||
for p in /opt/android-ndk /usr/local/android-ndk "$HOME/android-ndk"; do
|
||||
if [ -d "$p" ]; then echo "$p"; return; fi
|
||||
done
|
||||
echo ""
|
||||
}
|
||||
|
||||
NDK_HOME="$(find_ndk)"
|
||||
CC=""
|
||||
if [ -n "$NDK_HOME" ]; then
|
||||
CC=$(find "$NDK_HOME/toolchains/llvm/prebuilt" -type f \
|
||||
-name "aarch64-linux-android${API_LEVEL}-clang" -print -quit 2>/dev/null)
|
||||
fi
|
||||
if [ -z "$CC" ]; then
|
||||
echo "❌ aarch64-linux-android clang not found."
|
||||
echo " Set ANDROID_NDK_HOME to your NDK path, or install via:"
|
||||
echo " brew install --cask android-ndk"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "🔨 Compiling $SO_NAME..."
|
||||
echo "📦 Qt path: $QT_HOST_DIR"
|
||||
echo "📦 NDK: $NDK_HOME"
|
||||
echo ""
|
||||
|
||||
# Clean and create build directories
|
||||
rm -rf "$BUILD_DIR"
|
||||
mkdir -p "$BUILD_DIR"
|
||||
|
||||
# --- Step 0: Build dependencies (shadowhook) ---
|
||||
echo "[0/2] Building hooking library (shadowhook)..."
|
||||
|
||||
"$LIBS_DIR/build_deps.sh"
|
||||
|
||||
# --- Step 1: Prepare Qt headers ---
|
||||
echo "[1/2] Preparing Qt headers..."
|
||||
|
||||
if [ ! -d "$QT_HOST_DIR/include" ]; then
|
||||
echo "⚠️ Qt not found at $QT_HOST_DIR/include"
|
||||
echo " Install with: aqt install-qt all_os android $QT_VERSION -m qtwebsockets --outputdir $QT_DIR"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
for qt_lib in \
|
||||
"$QT_LIB_DIR/libQt6Core_arm64-v8a.so" \
|
||||
"$QT_LIB_DIR/libQt6Network_arm64-v8a.so" \
|
||||
"$QT_LIB_DIR/libQt6WebSockets_arm64-v8a.so"; do
|
||||
if [ ! -f "$qt_lib" ]; then
|
||||
echo "ERROR: Required Qt library not found: $qt_lib"
|
||||
exit 1
|
||||
fi
|
||||
done
|
||||
|
||||
QT_INCLUDES="-I$QT_HOST_DIR/include"
|
||||
for subdir in "$QT_HOST_DIR/include"/*/; do
|
||||
QT_INCLUDES="$QT_INCLUDES -I$subdir"
|
||||
done
|
||||
|
||||
echo "✅ Qt headers ready at $QT_HOST_DIR"
|
||||
echo "✅ Qt libraries ready at $QT_LIB_DIR"
|
||||
|
||||
# --- Step 2: Compile native hooking library ---
|
||||
echo "[2/2] Compiling $SO_NAME..."
|
||||
|
||||
"$CC" -shared -fPIC -O2 -std=c++17 -stdlib=libc++ -fno-exceptions -fno-rtti \
|
||||
-DQT_NO_VERSION_TAGGING \
|
||||
-o "$BUILD_DIR/$SO_NAME" \
|
||||
"$SRC_DIR/rmhook.cpp" \
|
||||
"$SRC_DIR/Config.cpp" \
|
||||
$QT_INCLUDES \
|
||||
-I"$LIBS_DIR/shadowhook/shadowhook/src/main/cpp/include" \
|
||||
-I"$SRC_DIR" \
|
||||
-L"$LIBS_DIR" -lshadowhook \
|
||||
-L"$QT_LIB_DIR" \
|
||||
-Wl,--no-undefined \
|
||||
-Wl,-z,defs \
|
||||
-l:libQt6WebSockets_arm64-v8a.so \
|
||||
-l:libQt6Network_arm64-v8a.so \
|
||||
-l:libQt6Core_arm64-v8a.so \
|
||||
-lc++_shared \
|
||||
-llog -ldl -lm \
|
||||
-Wno-incompatible-pointer-types
|
||||
|
||||
echo ""
|
||||
echo "✅ Compilation successful!"
|
||||
echo "📍 Library: $BUILD_DIR/$SO_NAME ($(ls -lh "$BUILD_DIR/$SO_NAME" | awk '{print $5}'))"
|
||||
echo ""
|
||||
echo "🚀 To inject into the reMarkable application:"
|
||||
echo " ./script/inject.sh"
|
||||
echo ""
|
||||
166
script/inject.sh
Executable file
166
script/inject.sh
Executable file
@@ -0,0 +1,166 @@
|
||||
#!/bin/bash
|
||||
# Script to patch and sign the reMarkable Android APKs
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
PROJECT_DIR=$(cd "$(dirname "$0")/.." && pwd)
|
||||
BUILD_DIR="$PROJECT_DIR/build"
|
||||
OUTPUT_DIR="$PROJECT_DIR/output"
|
||||
LIBS_DIR="$PROJECT_DIR/libs"
|
||||
|
||||
# --- Auto-detect Android SDK build tools ---
|
||||
find_build_tools() {
|
||||
local sdk_root="${ANDROID_SDK_ROOT:-$HOME/Library/Android/sdk}"
|
||||
local bt
|
||||
bt=$(find "$sdk_root/build-tools" -maxdepth 1 -type d 2>/dev/null | sort -V | tail -1)
|
||||
if [ -n "$bt" ] && [ -d "$bt" ]; then
|
||||
echo "$bt"; return
|
||||
fi
|
||||
echo ""
|
||||
}
|
||||
|
||||
BUILD_TOOLS="${ANDROID_SDK_BUILD_TOOLS:-$(find_build_tools)}"
|
||||
if [ -z "$BUILD_TOOLS" ] || [ ! -d "$BUILD_TOOLS" ]; then
|
||||
echo "❌ Android SDK build-tools not found."
|
||||
echo " Set ANDROID_SDK_BUILD_TOOLS to your build-tools path."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
ZIPALIGN="$BUILD_TOOLS/zipalign"
|
||||
APKSIGNER="$BUILD_TOOLS/apksigner"
|
||||
|
||||
KEYSTORE="$PROJECT_DIR/config/rmhook.keystore"
|
||||
KEYSTORE_PASS="rmhook"
|
||||
KEY_ALIAS="rmhook"
|
||||
|
||||
usage() {
|
||||
echo "Usage: $0 [apk_dir]"
|
||||
echo ""
|
||||
echo " apk_dir - Directory containing the split APKs (default: ~/reMarkable/app)"
|
||||
echo ""
|
||||
echo "Output: patched APKs in $OUTPUT_DIR/"
|
||||
exit 1
|
||||
}
|
||||
|
||||
APK_DIR="${1:-$HOME/reMarkable/app}"
|
||||
|
||||
echo "💉 Injecting RMHook into reMarkable APKs..."
|
||||
echo "📦 APK source: $APK_DIR"
|
||||
echo ""
|
||||
|
||||
if [ ! -f "$BUILD_DIR/librmhook.so" ]; then
|
||||
echo "❌ librmhook.so not found. Run ./script/build.sh first."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Clean and create output directory
|
||||
rm -rf "$OUTPUT_DIR"
|
||||
mkdir -p "$OUTPUT_DIR"
|
||||
|
||||
# --- Step 1: Decompile and patch base.apk (smali injection only, no resources) ---
|
||||
echo "[1/4] Patching base.apk (smali injection)..."
|
||||
# Use -r (--no-res) to skip decoding/recoding resources. We only need to modify the DEX.
|
||||
apktool d -r "$APK_DIR/base.apk" -o "$BUILD_DIR/base" -f
|
||||
|
||||
MAIN_SMALI="$BUILD_DIR/base/smali/com/remarkable/mobile/MainActivity.smali"
|
||||
if [ ! -f "$MAIN_SMALI" ]; then
|
||||
echo "❌ MainActivity.smali not found"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Insert System.loadLibrary("rmhook") at the very start of <clinit>
|
||||
sed -i '' '/.method static constructor <clinit>()V/{
|
||||
n
|
||||
/.locals/{
|
||||
a\
|
||||
\
|
||||
const-string v0, "rmhook"\
|
||||
\
|
||||
invoke-static {v0}, Ljava/lang/System;->loadLibrary(Ljava/lang/String;)V
|
||||
}
|
||||
}' "$MAIN_SMALI"
|
||||
|
||||
echo "✅ Injected System.loadLibrary(\"rmhook\") into MainActivity.<clinit>"
|
||||
|
||||
apktool b "$BUILD_DIR/base" -o "$BUILD_DIR/base_patched.apk"
|
||||
|
||||
# --- Step 2: Patch split_config.arm64_v8a.apk (add hook library) ---
|
||||
echo "[2/4] Injecting ShadowHook libraries into arm64 split..."
|
||||
SPLIT_ARM64="$APK_DIR/split_config.arm64_v8a.apk"
|
||||
if [ ! -f "$SPLIT_ARM64" ]; then
|
||||
echo "❌ $SPLIT_ARM64 not found"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
mkdir -p "$BUILD_DIR/split_arm64"
|
||||
unzip -q "$SPLIT_ARM64" -d "$BUILD_DIR/split_arm64"
|
||||
|
||||
cp "$BUILD_DIR/librmhook.so" "$BUILD_DIR/split_arm64/lib/arm64-v8a/"
|
||||
cp "$LIBS_DIR/libshadowhook_nothing.so" "$BUILD_DIR/split_arm64/lib/arm64-v8a/"
|
||||
|
||||
rm -rf "$BUILD_DIR/split_arm64/META-INF"
|
||||
|
||||
(cd "$BUILD_DIR/split_arm64" && zip -q -r -0 "$BUILD_DIR/split_arm64_raw.apk" . -x "*.so")
|
||||
(cd "$BUILD_DIR/split_arm64" && zip -q -r "$BUILD_DIR/split_arm64_raw.apk" lib/)
|
||||
|
||||
# --- Step 3: Align APKs ---
|
||||
echo "[3/4] Aligning APKs..."
|
||||
"$ZIPALIGN" -p -f 4 "$BUILD_DIR/base_patched.apk" "$BUILD_DIR/base_aligned.apk"
|
||||
"$ZIPALIGN" -p -f 4 "$BUILD_DIR/split_arm64_raw.apk" "$BUILD_DIR/split_arm64_aligned.apk"
|
||||
|
||||
SPLIT_XXHDPI="$APK_DIR/split_config.xxhdpi.apk"
|
||||
if [ -f "$SPLIT_XXHDPI" ]; then
|
||||
mkdir -p "$BUILD_DIR/split_xxhdpi"
|
||||
unzip -q "$SPLIT_XXHDPI" -d "$BUILD_DIR/split_xxhdpi"
|
||||
rm -rf "$BUILD_DIR/split_xxhdpi/META-INF"
|
||||
(cd "$BUILD_DIR/split_xxhdpi" && zip -q -r -0 "$BUILD_DIR/split_xxhdpi_raw.apk" .)
|
||||
"$ZIPALIGN" -p -f 4 "$BUILD_DIR/split_xxhdpi_raw.apk" "$BUILD_DIR/split_xxhdpi_aligned.apk"
|
||||
fi
|
||||
|
||||
# --- Step 4: Sign all APKs ---
|
||||
echo "[4/4] Signing APKs..."
|
||||
|
||||
if [ ! -f "$KEYSTORE" ]; then
|
||||
mkdir -p "$(dirname "$KEYSTORE")"
|
||||
echo "🔑 Generating signing keystore..."
|
||||
keytool -genkeypair -v \
|
||||
-keystore "$KEYSTORE" \
|
||||
-alias "$KEY_ALIAS" \
|
||||
-keyalg RSA -keysize 2048 \
|
||||
-validity 10000 \
|
||||
-storepass "$KEYSTORE_PASS" \
|
||||
-keypass "$KEYSTORE_PASS" \
|
||||
-dname "CN=RMHook, OU=Dev, O=RMHook, L=Unknown, ST=Unknown, C=US" \
|
||||
2>/dev/null
|
||||
fi
|
||||
|
||||
sign_apk() {
|
||||
local input="$1"
|
||||
local output="$2"
|
||||
"$APKSIGNER" sign \
|
||||
--ks "$KEYSTORE" \
|
||||
--ks-pass "pass:$KEYSTORE_PASS" \
|
||||
--ks-key-alias "$KEY_ALIAS" \
|
||||
--key-pass "pass:$KEYSTORE_PASS" \
|
||||
--out "$output" \
|
||||
"$input"
|
||||
}
|
||||
|
||||
sign_apk "$BUILD_DIR/base_aligned.apk" "$OUTPUT_DIR/base.apk"
|
||||
sign_apk "$BUILD_DIR/split_arm64_aligned.apk" "$OUTPUT_DIR/split_config.arm64_v8a.apk"
|
||||
if [ -f "$BUILD_DIR/split_xxhdpi_aligned.apk" ]; then
|
||||
sign_apk "$BUILD_DIR/split_xxhdpi_aligned.apk" "$OUTPUT_DIR/split_config.xxhdpi.apk"
|
||||
fi
|
||||
|
||||
echo ""
|
||||
echo "✅ Injection successful!"
|
||||
echo "📍 Patched APKs in: $OUTPUT_DIR/"
|
||||
ls -lh "$OUTPUT_DIR/"*.apk
|
||||
echo ""
|
||||
echo "🚀 To install on your reMarkable:"
|
||||
echo " adb uninstall com.remarkable.mobile"
|
||||
echo " adb install-multiple --no-streaming $OUTPUT_DIR/base.apk $OUTPUT_DIR/split_config.arm64_v8a.apk $OUTPUT_DIR/split_config.xxhdpi.apk"
|
||||
echo ""
|
||||
echo "🔍 To debug:"
|
||||
echo " adb logcat -s 'RMHook:*'"
|
||||
echo ""
|
||||
Reference in New Issue
Block a user