299 lines
10 KiB
Bash
299 lines
10 KiB
Bash
|
|
#!/system/bin/sh
|
||
|
|
# Driver Spoofing System
|
||
|
|
#
|
||
|
|
# Makes Android think stock drivers are loaded while custom drivers
|
||
|
|
# are actually running. Uses mount namespace isolation so:
|
||
|
|
#
|
||
|
|
# - Verification tools see: stock file (original hash, signature, SELinux context)
|
||
|
|
# - Driver loader sees: custom file (our patched/modified driver)
|
||
|
|
#
|
||
|
|
# How it works:
|
||
|
|
# 1. Stock driver stays at /vendor/lib64/... untouched (dm-verity happy)
|
||
|
|
# 2. Custom driver lives in /data/adb/modules/driver-manager/drivers/
|
||
|
|
# 3. We clone the stock file's metadata (timestamps, SELinux context, owner, perms)
|
||
|
|
# onto the custom driver
|
||
|
|
# 4. We bind-mount the custom driver over the stock path ONLY in the mount
|
||
|
|
# namespace of the process that loads it (e.g. surfaceflinger, wpa_supplicant)
|
||
|
|
# 5. All other processes (Play Integrity, banking apps, SafetyNet) see the
|
||
|
|
# stock file at the original path with the original hash
|
||
|
|
#
|
||
|
|
# This is NOT file replacement. The stock file is never modified.
|
||
|
|
# dm-verity remains intact. Verified boot passes.
|
||
|
|
|
||
|
|
MODDIR="/data/adb/modules/driver-manager"
|
||
|
|
CONFDIR="$MODDIR/config"
|
||
|
|
LOGFILE="$MODDIR/driver-manager.log"
|
||
|
|
DRIVERDIR="$MODDIR/drivers"
|
||
|
|
STOCKDIR="$MODDIR/drivers/.stock_meta"
|
||
|
|
|
||
|
|
mkdir -p "$DRIVERDIR" "$STOCKDIR"
|
||
|
|
|
||
|
|
mlog() {
|
||
|
|
STEALTH=$(cat "$CONFDIR/stealth_mode" 2>/dev/null || echo "off")
|
||
|
|
[ "$STEALTH" = "full" ] && return
|
||
|
|
echo "$(date '+%Y-%m-%d %H:%M:%S') [spoof] $1" >> "$LOGFILE"
|
||
|
|
}
|
||
|
|
|
||
|
|
# =================================================================
|
||
|
|
# Clone stock file metadata onto custom driver
|
||
|
|
# =================================================================
|
||
|
|
# Copies: SELinux context, ownership, permissions, timestamps
|
||
|
|
# Does NOT copy file contents — that's the whole point
|
||
|
|
clone_metadata() {
|
||
|
|
STOCK_PATH="$1"
|
||
|
|
CUSTOM_PATH="$2"
|
||
|
|
|
||
|
|
if [ ! -f "$STOCK_PATH" ] || [ ! -f "$CUSTOM_PATH" ]; then
|
||
|
|
mlog "ERROR: clone_metadata missing file: $STOCK_PATH or $CUSTOM_PATH"
|
||
|
|
return 1
|
||
|
|
fi
|
||
|
|
|
||
|
|
# Save stock metadata for verification
|
||
|
|
METAFILE="$STOCKDIR/$(echo "$STOCK_PATH" | tr '/' '_')"
|
||
|
|
ls -laZ "$STOCK_PATH" > "$METAFILE" 2>/dev/null
|
||
|
|
sha256sum "$STOCK_PATH" >> "$METAFILE" 2>/dev/null
|
||
|
|
|
||
|
|
# Clone SELinux context
|
||
|
|
CONTEXT=$(ls -Z "$STOCK_PATH" 2>/dev/null | awk '{print $1}')
|
||
|
|
if [ -n "$CONTEXT" ]; then
|
||
|
|
chcon "$CONTEXT" "$CUSTOM_PATH" 2>/dev/null
|
||
|
|
mlog "SELinux context: $CONTEXT -> $(basename "$CUSTOM_PATH")"
|
||
|
|
fi
|
||
|
|
|
||
|
|
# Clone ownership
|
||
|
|
OWNER=$(stat -c '%u:%g' "$STOCK_PATH" 2>/dev/null)
|
||
|
|
if [ -n "$OWNER" ]; then
|
||
|
|
chown "$OWNER" "$CUSTOM_PATH" 2>/dev/null
|
||
|
|
fi
|
||
|
|
|
||
|
|
# Clone permissions
|
||
|
|
PERMS=$(stat -c '%a' "$STOCK_PATH" 2>/dev/null)
|
||
|
|
if [ -n "$PERMS" ]; then
|
||
|
|
chmod "$PERMS" "$CUSTOM_PATH" 2>/dev/null
|
||
|
|
fi
|
||
|
|
|
||
|
|
# Clone timestamps (access + modify)
|
||
|
|
touch -r "$STOCK_PATH" "$CUSTOM_PATH" 2>/dev/null
|
||
|
|
|
||
|
|
mlog "Metadata cloned: $STOCK_PATH -> $CUSTOM_PATH"
|
||
|
|
}
|
||
|
|
|
||
|
|
# =================================================================
|
||
|
|
# Bind mount custom driver over stock path in a specific process
|
||
|
|
# =================================================================
|
||
|
|
# Uses nsenter to enter the target process's mount namespace
|
||
|
|
# and bind-mount the custom file over the stock path.
|
||
|
|
# Only that process (and its children) see the custom file.
|
||
|
|
spoof_for_process() {
|
||
|
|
PROCESS_NAME="$1"
|
||
|
|
STOCK_PATH="$2"
|
||
|
|
CUSTOM_PATH="$3"
|
||
|
|
|
||
|
|
# Find the PID of the target process
|
||
|
|
PID=$(pidof "$PROCESS_NAME" 2>/dev/null)
|
||
|
|
if [ -z "$PID" ]; then
|
||
|
|
mlog "Process not running: $PROCESS_NAME"
|
||
|
|
return 1
|
||
|
|
fi
|
||
|
|
|
||
|
|
# Clone metadata first
|
||
|
|
clone_metadata "$STOCK_PATH" "$CUSTOM_PATH"
|
||
|
|
|
||
|
|
# Enter the process's mount namespace and bind-mount
|
||
|
|
nsenter -t "$PID" -m -- mount --bind "$CUSTOM_PATH" "$STOCK_PATH" 2>/dev/null
|
||
|
|
RET=$?
|
||
|
|
|
||
|
|
if [ $RET -eq 0 ]; then
|
||
|
|
mlog "Spoofed: $PROCESS_NAME (PID $PID) sees $CUSTOM_PATH at $STOCK_PATH"
|
||
|
|
else
|
||
|
|
mlog "nsenter failed for $PROCESS_NAME — falling back to global mount"
|
||
|
|
# Fallback: global bind mount (less stealthy but works)
|
||
|
|
mount --bind "$CUSTOM_PATH" "$STOCK_PATH" 2>/dev/null
|
||
|
|
mlog "Global bind mount: $CUSTOM_PATH -> $STOCK_PATH"
|
||
|
|
fi
|
||
|
|
}
|
||
|
|
|
||
|
|
# =================================================================
|
||
|
|
# Global spoof — bind mount for ALL processes
|
||
|
|
# =================================================================
|
||
|
|
# Less stealthy but simpler. Use when you don't care about
|
||
|
|
# hiding from verification tools (e.g. custom ROM, no banking apps)
|
||
|
|
spoof_global() {
|
||
|
|
STOCK_PATH="$1"
|
||
|
|
CUSTOM_PATH="$2"
|
||
|
|
|
||
|
|
clone_metadata "$STOCK_PATH" "$CUSTOM_PATH"
|
||
|
|
mount --bind "$CUSTOM_PATH" "$STOCK_PATH" 2>/dev/null
|
||
|
|
|
||
|
|
if [ $? -eq 0 ]; then
|
||
|
|
mlog "Global spoof: $CUSTOM_PATH -> $STOCK_PATH"
|
||
|
|
else
|
||
|
|
mlog "ERROR: global mount failed for $STOCK_PATH"
|
||
|
|
fi
|
||
|
|
}
|
||
|
|
|
||
|
|
# =================================================================
|
||
|
|
# Restore stock driver (unmount bind)
|
||
|
|
# =================================================================
|
||
|
|
restore_stock() {
|
||
|
|
STOCK_PATH="$1"
|
||
|
|
umount "$STOCK_PATH" 2>/dev/null
|
||
|
|
mlog "Restored stock: $STOCK_PATH"
|
||
|
|
}
|
||
|
|
|
||
|
|
# =================================================================
|
||
|
|
# Verify spoof is active
|
||
|
|
# =================================================================
|
||
|
|
# Checks if the file at the stock path is actually our custom driver
|
||
|
|
verify_spoof() {
|
||
|
|
STOCK_PATH="$1"
|
||
|
|
CUSTOM_PATH="$2"
|
||
|
|
|
||
|
|
STOCK_HASH=$(sha256sum "$STOCK_PATH" 2>/dev/null | awk '{print $1}')
|
||
|
|
CUSTOM_HASH=$(sha256sum "$CUSTOM_PATH" 2>/dev/null | awk '{print $1}')
|
||
|
|
|
||
|
|
if [ "$STOCK_HASH" = "$CUSTOM_HASH" ]; then
|
||
|
|
echo "ACTIVE — custom driver loaded at $STOCK_PATH"
|
||
|
|
else
|
||
|
|
echo "INACTIVE — stock driver at $STOCK_PATH"
|
||
|
|
fi
|
||
|
|
}
|
||
|
|
|
||
|
|
# =================================================================
|
||
|
|
# Driver mapping configuration
|
||
|
|
# =================================================================
|
||
|
|
# Format: stock_path|custom_filename|target_process
|
||
|
|
# Custom files go in /data/adb/modules/driver-manager/drivers/
|
||
|
|
DRIVER_MAP="$CONFDIR/driver_map.conf"
|
||
|
|
|
||
|
|
load_driver_map() {
|
||
|
|
if [ ! -f "$DRIVER_MAP" ]; then
|
||
|
|
cat > "$DRIVER_MAP" << 'MAP'
|
||
|
|
# Driver Spoof Map
|
||
|
|
# Format: stock_path|custom_filename|target_process|spoof_type
|
||
|
|
# spoof_type: process (per-process ns) or global (all processes)
|
||
|
|
#
|
||
|
|
# GPU — PowerVR DXT-48-1536
|
||
|
|
# Place custom GPU driver as: drivers/libGLES_powervr_custom.so
|
||
|
|
#/vendor/lib64/egl/libGLES_powervr.so|libGLES_powervr_custom.so|surfaceflinger|process
|
||
|
|
#/vendor/lib64/libPVROCL.so|libPVROCL_custom.so|surfaceflinger|process
|
||
|
|
#
|
||
|
|
# WiFi — BCM4390 firmware
|
||
|
|
# Place custom firmware as: drivers/fw_bcm4390_custom.bin
|
||
|
|
#/vendor/firmware/fw_bcmdhd4390.bin|fw_bcm4390_custom.bin|wpa_supplicant|process
|
||
|
|
#
|
||
|
|
# Bluetooth — QCA firmware
|
||
|
|
#/vendor/firmware/qca/nvm_00440200.bin|nvm_custom.bin|bluetooth|process
|
||
|
|
#
|
||
|
|
# Uncomment and edit lines above to activate spoofing for each driver.
|
||
|
|
# The stock file is NEVER modified. Custom files must be placed in:
|
||
|
|
# /data/adb/modules/driver-manager/drivers/
|
||
|
|
MAP
|
||
|
|
mlog "Created default driver map at $DRIVER_MAP"
|
||
|
|
fi
|
||
|
|
}
|
||
|
|
|
||
|
|
# =================================================================
|
||
|
|
# Apply all configured spoofs
|
||
|
|
# =================================================================
|
||
|
|
apply_all() {
|
||
|
|
load_driver_map
|
||
|
|
|
||
|
|
while IFS='|' read -r STOCK_PATH CUSTOM_FILE TARGET_PROC SPOOF_TYPE; do
|
||
|
|
# Skip comments and empty lines
|
||
|
|
case "$STOCK_PATH" in
|
||
|
|
\#*|"") continue ;;
|
||
|
|
esac
|
||
|
|
|
||
|
|
CUSTOM_PATH="$DRIVERDIR/$CUSTOM_FILE"
|
||
|
|
|
||
|
|
if [ ! -f "$CUSTOM_PATH" ]; then
|
||
|
|
mlog "SKIP: custom driver not found: $CUSTOM_PATH"
|
||
|
|
continue
|
||
|
|
fi
|
||
|
|
|
||
|
|
if [ ! -f "$STOCK_PATH" ]; then
|
||
|
|
mlog "SKIP: stock path not found: $STOCK_PATH"
|
||
|
|
continue
|
||
|
|
fi
|
||
|
|
|
||
|
|
case "$SPOOF_TYPE" in
|
||
|
|
process)
|
||
|
|
spoof_for_process "$TARGET_PROC" "$STOCK_PATH" "$CUSTOM_PATH"
|
||
|
|
;;
|
||
|
|
global)
|
||
|
|
spoof_global "$STOCK_PATH" "$CUSTOM_PATH"
|
||
|
|
;;
|
||
|
|
*)
|
||
|
|
mlog "Unknown spoof type: $SPOOF_TYPE for $STOCK_PATH"
|
||
|
|
;;
|
||
|
|
esac
|
||
|
|
done < "$DRIVER_MAP"
|
||
|
|
}
|
||
|
|
|
||
|
|
# =================================================================
|
||
|
|
# Status — show all active spoofs
|
||
|
|
# =================================================================
|
||
|
|
show_status() {
|
||
|
|
load_driver_map
|
||
|
|
echo "=== Driver Spoof Status ==="
|
||
|
|
|
||
|
|
while IFS='|' read -r STOCK_PATH CUSTOM_FILE TARGET_PROC SPOOF_TYPE; do
|
||
|
|
case "$STOCK_PATH" in
|
||
|
|
\#*|"") continue ;;
|
||
|
|
esac
|
||
|
|
CUSTOM_PATH="$DRIVERDIR/$CUSTOM_FILE"
|
||
|
|
STATUS=$(verify_spoof "$STOCK_PATH" "$CUSTOM_PATH")
|
||
|
|
echo " $STOCK_PATH -> $CUSTOM_FILE [$TARGET_PROC] $STATUS"
|
||
|
|
done < "$DRIVER_MAP"
|
||
|
|
|
||
|
|
echo ""
|
||
|
|
echo "=== Mount binds ==="
|
||
|
|
mount | grep "$DRIVERDIR" 2>/dev/null || echo " (none active)"
|
||
|
|
}
|
||
|
|
|
||
|
|
# =================================================================
|
||
|
|
# Main
|
||
|
|
# =================================================================
|
||
|
|
case "$1" in
|
||
|
|
apply)
|
||
|
|
apply_all
|
||
|
|
;;
|
||
|
|
restore)
|
||
|
|
if [ -n "$2" ]; then
|
||
|
|
restore_stock "$2"
|
||
|
|
else
|
||
|
|
# Restore all
|
||
|
|
load_driver_map
|
||
|
|
while IFS='|' read -r STOCK_PATH CUSTOM_FILE TARGET_PROC SPOOF_TYPE; do
|
||
|
|
case "$STOCK_PATH" in
|
||
|
|
\#*|"") continue ;;
|
||
|
|
esac
|
||
|
|
restore_stock "$STOCK_PATH"
|
||
|
|
done < "$DRIVER_MAP"
|
||
|
|
fi
|
||
|
|
;;
|
||
|
|
status)
|
||
|
|
show_status
|
||
|
|
;;
|
||
|
|
clone)
|
||
|
|
# Clone metadata only: driver_spoof.sh clone /vendor/path /custom/path
|
||
|
|
clone_metadata "$2" "$3"
|
||
|
|
;;
|
||
|
|
verify)
|
||
|
|
verify_spoof "$2" "$3"
|
||
|
|
;;
|
||
|
|
*)
|
||
|
|
echo "Usage: driver_spoof.sh {apply|restore|status|clone|verify}"
|
||
|
|
echo ""
|
||
|
|
echo " apply Apply all driver spoofs from driver_map.conf"
|
||
|
|
echo " restore [path] Restore stock driver(s)"
|
||
|
|
echo " status Show active spoofs"
|
||
|
|
echo " clone src dst Clone file metadata (SELinux, perms, timestamps)"
|
||
|
|
echo " verify stock cust Check if spoof is active"
|
||
|
|
echo ""
|
||
|
|
echo "Driver map: $DRIVER_MAP"
|
||
|
|
echo "Custom drivers: $DRIVERDIR/"
|
||
|
|
;;
|
||
|
|
esac
|