diff --git a/.gitattributes b/.gitattributes index dfe0770..0ae55e3 100644 --- a/.gitattributes +++ b/.gitattributes @@ -1,2 +1,21 @@ # Auto detect text files and perform LF normalization * text=auto + +# Enforce LF line endings for build.prop and related files +*.sh text eol=lf +*.prop text eol=lf +*.config text eol=lf + +# README.md should also have consistent line endings +README.md text eol=lf + +# Mark shell scripts as text and enforce LF +*.sh text eol=lf + +# Binary files +*.png binary +*.jpg binary +*.jpeg binary +*.gif binary +*.ico binary +*.zip binary diff --git a/META-INF/com/google/android/update-binary b/META-INF/com/google/android/update-binary new file mode 100644 index 0000000..994f7e3 --- /dev/null +++ b/META-INF/com/google/android/update-binary @@ -0,0 +1,31 @@ +#!/sbin/sh + +################# +# Initialization +################# + +umask 022 + +# echo before loading util_functions +ui_print() { echo "$1"; } + +require_new_magisk() { + ui_print "*******************************" + ui_print " Please install Magisk v20.4+! " + ui_print "*******************************" + exit 1 +} + +######################### +# Load util_functions.sh +######################### + +OUTFD=$2 +ZIPFILE=$3 + +[ -f /data/adb/magisk/util_functions.sh ] || require_new_magisk +. /data/adb/magisk/util_functions.sh +[ $MAGISK_VER_CODE -lt 20400 ] && require_new_magisk + +install_module +exit 0 diff --git a/META-INF/com/google/android/updater-script b/META-INF/com/google/android/updater-script new file mode 100644 index 0000000..11d5c96 --- /dev/null +++ b/META-INF/com/google/android/updater-script @@ -0,0 +1 @@ +#MAGISK diff --git a/README.md b/README.md index 858b186..5103e6a 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,43 @@ -# BetterKnownInstalled - +# BetterKnownInstalled (BKI) + +This Magisk/KernelSU/APatch module patches the `packages.xml` and cleans the `packages-warnings.xml` files to address DroidGuard's `UNKNOWN_INSTALLED` status. This helps resolve issues related to app installation and verification on devices with Play Services. + +## Description + +DroidGuard uses several attributes within the `packages.xml` file to track app installations. This module modifies these attributes to ensure that apps are recognized as properly installed, preventing the `UNKNOWN_INSTALLED` status and related problems. Specifically, it: + +* Sets the `installer`, `installInitiator`, and `installerUid` attributes within each `` tag in `packages.xml` to `com.android.vending` (the Google Play Store's package name identifier) and the corresponding user ID. This helps DroidGuard recognize the installation source as legitimate. +* Removes the `installOriginator` attribute, as it can sometimes cause conflicts. +* Cleans the `packages-warnings.xml` file by setting its content to an empty `` tag. This file can sometimes contain warnings that contribute to the `playProtectVerdict` issues. + +This module uses `abx2xml` and `xml2abx` binaries if not in your system by default (provided by [rhythmcache/android-xml-converter](https://github.com/rhythmcache/android-xml-converter)) to convert between binary and text XML formats, as `packages.xml` is typically in binary XML format. The module includes architecture-specific binaries for `aarch64`, `armv7aeabi`, `i686`, and `x86_64`. + +**Important Warning:** The environment check verdict modification implemented by this module is a relatively new technique. Google's implementation of this check is not yet robust enough to guarantee that all your packages will be correctly identified. It's possible that some apps might still trigger Play Protect warnings or behave unexpectedly, even after using this module. This is due to limitations and potential inconsistencies in how Google verifies app installations. Use this module with caution and be aware of the potential for these issues. + +## Installation + +1. Install this module through Magisk Manager, KernelSU, or APatch. +2. Reboot your device for the changes to take effect. + +**Important:** Installation from recovery is *not* supported. You *must* install this module from within a running Android environment (Magisk/KernelSU/APatch). + +## Usage + +This module works automatically upon installation and reboot. No further user interaction is required. It modifies the necessary XML files during the boot process. + +## Backups + +The module creates backups of the original `packages.xml` and `packages-warnings.xml` files in `/data/adb/modules/BetterKnownInstalled/backup/` before making any changes. Up to five backups of each file type are kept, including the latest one. The backups are timestamped for easy identification. + +## Logging + +The module logs its activity to `/data/adb/modules/BetterKnownInstalled/BetterKnownInstalled.log`. This log file can be helpful for troubleshooting. + +## Credits + +* [rhythmcache aka winter](https://github.com/rhythmcache/android-xml-converter) for the `abx2xml` and `xml2abx` binaries. +* [@T3SL4](https://t.me/T3SL4) (Me) for creating this module. + +## License + +This project is licensed under the terms of the GNU General Public License v3.0. See the [LICENSE](./LICENSE) file for details. diff --git a/common/addon/abx/install.sh b/common/addon/abx/install.sh new file mode 100644 index 0000000..78f7b4c --- /dev/null +++ b/common/addon/abx/install.sh @@ -0,0 +1,28 @@ +#!/system/bin/busybox sh + +# MODPATH is already defined in post-fs-data.sh +TOOL_PATH="$MODPATH/common/addon/abx/tools" + +# Determine architecture +current_arch=$(get_arch) +get_arch_status=$? + +if [ $get_arch_status -ne 0 ]; then + exit 1 # Exit if get_arch failed +fi + +# Check if binaries exist in the determined path +if [ ! -f "$TOOL_PATH/abx2xml-$current_arch" ] || [ ! -f "$TOOL_PATH/xml2abx-$current_arch" ]; then + ui_print "Error: Binaries for $current_arch not found in $TOOL_PATH" + exit 1 +fi + +# Prepend the architecture-specific bin directory to PATH +export PATH="$TOOL_PATH:$PATH" + +# Rename the right binaries arch to abx2xml and xml2abx +ln -sf "$TOOL_PATH/abx2xml-$current_arch" "$TOOL_PATH/abx2xml" +ln -sf "$TOOL_PATH/xml2abx-$current_arch" "$TOOL_PATH/xml2abx" + +ui_print "Successfully installed abx tools for $current_arch." +ui_print "Credit: rhythmcache/android-xml-converter for the binaries." diff --git a/common/addon/abx/tools/abx2xml-aarch64 b/common/addon/abx/tools/abx2xml-aarch64 new file mode 100644 index 0000000..66d5df1 Binary files /dev/null and b/common/addon/abx/tools/abx2xml-aarch64 differ diff --git a/common/addon/abx/tools/abx2xml-armv7aeabi b/common/addon/abx/tools/abx2xml-armv7aeabi new file mode 100644 index 0000000..0ac397f Binary files /dev/null and b/common/addon/abx/tools/abx2xml-armv7aeabi differ diff --git a/common/addon/abx/tools/abx2xml-i686 b/common/addon/abx/tools/abx2xml-i686 new file mode 100644 index 0000000..30c5d8c Binary files /dev/null and b/common/addon/abx/tools/abx2xml-i686 differ diff --git a/common/addon/abx/tools/abx2xml-x86_64 b/common/addon/abx/tools/abx2xml-x86_64 new file mode 100644 index 0000000..b0f5fca Binary files /dev/null and b/common/addon/abx/tools/abx2xml-x86_64 differ diff --git a/common/addon/abx/tools/xml2abx-aarch64 b/common/addon/abx/tools/xml2abx-aarch64 new file mode 100644 index 0000000..117f979 Binary files /dev/null and b/common/addon/abx/tools/xml2abx-aarch64 differ diff --git a/common/addon/abx/tools/xml2abx-armv7aeabi b/common/addon/abx/tools/xml2abx-armv7aeabi new file mode 100644 index 0000000..35716c1 Binary files /dev/null and b/common/addon/abx/tools/xml2abx-armv7aeabi differ diff --git a/common/addon/abx/tools/xml2abx-i686 b/common/addon/abx/tools/xml2abx-i686 new file mode 100644 index 0000000..6e5f5d4 Binary files /dev/null and b/common/addon/abx/tools/xml2abx-i686 differ diff --git a/common/addon/abx/tools/xml2abx-x86_64 b/common/addon/abx/tools/xml2abx-x86_64 new file mode 100644 index 0000000..0cf456a Binary files /dev/null and b/common/addon/abx/tools/xml2abx-x86_64 differ diff --git a/customize.sh b/customize.sh new file mode 100644 index 0000000..707798d --- /dev/null +++ b/customize.sh @@ -0,0 +1,14 @@ +#!/system/bin/busybox sh + +enforce_install_from_app() { + if [ ! "$BOOTMODE" ]; then + ui_print "****************************************************" + ui_print "! Install from Recovery is NOT supported !" + ui_print "! Please install from Magisk / KernelSU / APatch !" + abort "****************************************************" + fi +} + +enforce_install_from_app + +ui_print "- Restart your device to confirm changes." diff --git a/module.prop b/module.prop new file mode 100644 index 0000000..4d5be8a --- /dev/null +++ b/module.prop @@ -0,0 +1,6 @@ +id=BetterKnownInstalled +name=BetterKnownInstalled (BKI) +version=v1.3.3 +versionCode=1337 +author=@T3SL4 +description=Patching (installer, installerUid, installInitiator, installOriginator) in packages.xml and cleaning packaga-warnings.xml for defeating DroidGuard: UNKNOWN_INSTALLED diff --git a/post-fs-data.sh b/post-fs-data.sh new file mode 100644 index 0000000..13b6fb6 --- /dev/null +++ b/post-fs-data.sh @@ -0,0 +1,248 @@ +#!/system/bin/busybox sh + +MODPATH="${0%/*}" +MODNAME="${MODPATH##*/}" + +PACKAGES_XML="/data/system/packages.xml" +WARNINGS_XML="/data/system/packages-warnings.xml" +BACKUP_DIR="$MODPATH/backup" # Directory to store backups +MAX_BACKUP_FILES=4 # Maximum number of backup files to keep per type +CURRENT_TIMESTAMP=$(date +%s) + +# If MODPATH is empty or is not default modules path, use current path +[ -z "$MODPATH" ] || ! echo "$MODPATH" | grep -q '/data/adb/modules/' && + MODPATH="$(dirname "$(readlink -f "$0")")" + +# Using util_functions.sh +[ -f "$MODPATH/util_functions.sh" ] && . "$MODPATH/util_functions.sh" || abort "! util_functions.sh not found!" + +# Check if /data is mounted and accessible +wait_for_data() { + max_wait_time=30 # Maximum time to wait in seconds + wait_interval=1 # Seconds to wait between checks + + i=0 + while [ "$i" -lt "$max_wait_time" ]; do + if mount | grep -q "/data " && [ -f "$PACKAGES_XML" ]; then + ui_print "/data is mounted and accessible." + return 0 + fi + ui_print "Waiting for /data to become accessible..." + sleep "$wait_interval" + i=$((i + wait_interval)) + done + + ui_print "Error: /data or $PACKAGES_XML did not become accessible within $max_wait_time seconds." + return 1 +} + +process_xml() { + xml_file="$1" + base_name=$(basename "$xml_file") + base_name_no_ext="${base_name%.*}" + + xml_temp="$MODPATH/temp_${base_name_no_ext}_$CURRENT_TIMESTAMP.xml" + xml_backup_file="$BACKUP_DIR/${base_name_no_ext}_$CURRENT_TIMESTAMP.xml" + abxml_backup_file="$BACKUP_DIR/${base_name_no_ext}_$CURRENT_TIMESTAMP.abxml" + + ui_print "Starting to process XML file: $xml_file" + + # Create backup *before* any modifications + ui_print "Creating backup of $xml_file..." + backup_file_path=$(backup_file "$xml_file" "bak") + backup_status=$? + + if [ $backup_status -eq 0 ]; then + ui_print "Backup file path: $backup_file_path" + abxml_original_backup_file="$backup_file_path" + else + ui_print "Error: backup_file failed." + return 1 + fi + + # Determine the file type early on + file_type=$(file -b "$abxml_original_backup_file") + is_text_xml=false + if echo "$file_type" | grep -q -E "XML .* text|text"; then + is_text_xml=true + fi + + # Log original backup file type + ui_print "abxml_original_backup_file is: Text XML" + + # Convert to text XML if necessary + if boolval "$is_text_xml"; then + ui_print "File is already text XML. Proceeding with modifications." + cp "$abxml_original_backup_file" "$xml_temp" + else + ui_print "Converting ABX to text: $abxml_original_backup_file -> $xml_temp" + if ! abx_to_text "$abxml_original_backup_file" "$xml_temp"; then + ui_print "Error: abx_to_text failed!" + cat "$abxml_original_backup_file" >"$MODPATH/abx_to_text_error_${base_name_no_ext}_$CURRENT_TIMESTAMP.abx" + rm "$xml_temp" 2>/dev/null + return 1 + fi + fi + + # Check if the file is packages-warnings.xml + if [ "$xml_file" = "$WARNINGS_XML" ]; then + ui_print "Processing $WARNINGS_XML: Setting content to '' (empty)." + + # Set content of packages-warnings.xml to be empty packages + echo "" >"$xml_temp" + + ui_print "Content of $WARNINGS_XML set to ''." + else + # Get the userId of com.android.vending from packages.xml + ui_print "Finding userId for com.android.vending in $xml_temp..." + vending_uid=$(sed -n -E '/]*)/ \1 installer="com.android.vending"/g + ' "$xml_temp" + + # 2. Replace or add installInitiator attribute. + sed -i -E ' + /installInitiator=/ { + s/(installInitiator=")[^"]*/\1com.android.vending/g + } + /installInitiator=/! s/(]*)/ \1 installInitiator="com.android.vending"/g + ' "$xml_temp" + + # 3. Replace or add installerUid attribute. + sed -i -E ' + /installerUid(-int)?=/ { + s/(installerUid(-int)?=")[^"]*/\1'"$vending_uid"'/g + } + /installerUid(-int)?=/! s/(]*)/ \1 installerUid="'"$vending_uid"'"/g + ' "$xml_temp" + + # 4. Remove installOriginator attribute. + sed -i -E 's/[\r\n ]*installOriginator="[^"]*"//g' "$xml_temp" + fi + + # Rotate backups + ui_print "Rotating backups..." + rotate_files "${base_name_no_ext}*_$CURRENT_TIMESTAMP.xml" "$MAX_BACKUP_FILES" + rotate_files "${base_name_no_ext}*_$CURRENT_TIMESTAMP.abxml" "$MAX_BACKUP_FILES" + + # Replace and verify based on original file type + if boolval "$is_text_xml"; then + # It was originally a text XML file + ui_print "Original file was text XML. Replacing with modified text XML." + cp "$xml_temp" "$xml_backup_file" # Backup modified text XML + replacement_source="$xml_backup_file" + else + # It was originally an ABX file + ui_print "Original file was ABX. Converting text to ABX before replacing." + text_to_abx "$xml_temp" "$abxml_backup_file" # Convert modified text to ABX + replacement_source="$abxml_backup_file" + fi + + # Replace and verify + ui_print "Replacing original file: $xml_file <- $replacement_source" + if ! cp -f "$replacement_source" "$xml_file"; then + ui_print "Error: Failed to copy modified file. Permissions issue?" + rm "$xml_temp" 2>/dev/null + return 1 + fi + + ui_print "Verifying replacement: $xml_file vs $replacement_source" + if ! cmp -s "$xml_file" "$replacement_source"; then + ui_print "Error: Verification failed after replacement!" + diff -u "$xml_file" "$replacement_source" >"$MODPATH/diff_error_${base_name_no_ext}_$CURRENT_TIMESTAMP.diff" + rm "$xml_temp" 2>/dev/null + return 1 + fi + + ui_print "Cleaning up temporary files..." + rm "$xml_temp" 2>/dev/null + + ui_print "Successfully processed XML file: $xml_file" + return 0 +} + +# Check for required commands and use included binaries if necessary +if ! command_exists abx2xml || ! command_exists xml2abx; then + ui_print "Error: abx2xml and xml2abx are required. Installing from addons..." + + # Running addons + for addon in "$MODPATH"/common/addon/*/install.sh; do + if [ -f "$addon" ]; then + addon_basedirname=$(basename "$(dirname "$addon")") + ui_print "Running $addon_basedirname addon..." + . "$addon" + if [ $? -ne 0 ]; then + ui_print "Error: Addon $addon_basedirname failed to install." + exit 1 + fi + fi + done + + # Check again if the commands exist after running addons + if ! command_exists abx2xml || ! command_exists xml2abx; then + ui_print "Error: abx2xml and xml2abx are still missing after running addons." + exit 1 + fi +fi + +# Wait for /data to become mounted and accessible +wait_for_data + +# Process packages.xml +ui_print "Processing $PACKAGES_XML..." +process_xml "$PACKAGES_XML" + +# Process packages-warnings.xml +ui_print "Processing $WARNINGS_XML..." +process_xml "$WARNINGS_XML" + +# Restore permissions and SELinux context (if applicable) +ui_print "Restoring permissions and SELinux context..." +for file in "$PACKAGES_XML" "$WARNINGS_XML"; do + chown system:system "$file" + chmod 640 "$file" + if command_exists restorecon; then + restorecon "$file" + fi +done + +ui_print "----------------------------------------" +ui_print "Process completed successfully." +ui_print "Original and modified files backed up to: $BACKUP_DIR" +ui_print "It is recommended to reboot your device for changes to take effect." +ui_print "by @T3SL4" +ui_print "----------------------------------------" diff --git a/util_functions.sh b/util_functions.sh new file mode 100644 index 0000000..cfdae53 --- /dev/null +++ b/util_functions.sh @@ -0,0 +1,222 @@ +#!/system/bin/busybox sh +MODPATH="${0%/*}" + +LOG_FILE="$MODPATH/$MODNAME.log" +MAX_LOG_SIZE=$((1024 * 1024)) # Maximum log file size (1MB) +MAX_LOG_FILES=5 # Maximum number of log files to keep + +# Function to check for command existence +command_exists() { + command -v "$1" >/dev/null 2>&1 +} + +# Function that normalizes a boolean value and returns 0, 1, or a string +# Usage: boolval "value" +boolval() { + case "$(printf "%s" "${1:-}" | tr '[:upper:]' '[:lower:]')" in + 1 | true | on | enabled) return 0 ;; # Truely + 0 | false | off | disabled) return 1 ;; # Falsely + *) return 1 ;; # Everything else - return a string + esac +} + +# Function to write to log file with rotation and enhanced debugging +ui_print() { + message="$1" + timestamp=$(date +'%Y-%m-%d %H:%M:%S') + log_entry="[$timestamp] $message" + + # Check log file size and rotate if necessary + if [ -f "$LOG_FILE" ] && [ "$(stat -c%s "$LOG_FILE")" -ge "$MAX_LOG_SIZE" ]; then + rotate_files "$LOG_FILE" "$MAX_LOG_FILES" + fi + + # Write to log file + echo "$log_entry" >>"$LOG_FILE" +} + +# Get the architecture +get_arch() { + arch=$(getprop ro.product.cpu.abi | tr -d '\r') + + # Map architectures to binary names + case "$arch" in + "armeabi-v7a") echo "armv7aeabi" ;; + "arm64-v8a") echo "aarch64" ;; + "x86") echo "i686" ;; + "x86_64") echo "x86_64" ;; + *) + # Handle cases where arch is not found or not supported + ui_print "Error: Could not determine architecture or architecture not supported: $arch" + return 1 + ;; + esac +} + +# Function to convert binary XML to text XML +abx_to_text() { + input_file="$1" + output_file="$2" + + ui_print "Attempting to convert $input_file to text XML" + + # Check if input file exists + if [ ! -f "$input_file" ]; then + ui_print "Error: Input file '$input_file' does not exist." + return 1 + fi + + # If output file is not provided, use the default (input file name with .xml) + if [ -z "$output_file" ]; then + output_file="${input_file%.*}.xml" + fi + + # Check if output directory is writable (only if output is not stdout) + if [ "$output_file" != "-" ]; then + output_dir=$(dirname "$output_file") + if [ ! -w "$output_dir" ]; then + ui_print "Error: Output directory '$output_dir' is not writable." + return 1 + fi + fi + + # Check if the input is likely Android Binary XML using 'file' + file_type=$(file -b "$input_file") + if ! echo "$file_type" | grep -q "Binary XML"; then + ui_print "Error: Input file '$input_file' is not recognized as a valid binary XML. File type: $file_type" + return 1 + fi + + # Use abx2xml for conversion + # Pipe stdout directly to a while loop, handling line by line + # stderr is combined with stdout + abx2xml "$input_file" "$output_file" 2>&1 | while read -r line; do + ui_print "abx2xml: $line" + done + result=$? + + # Print a generic error message if abx2xml failed + if [ $result -ne 0 ]; then + ui_print "Error: Failed to convert '$input_file'. Check abx2xml output for potential errors." + fi + + if [ $result -eq 0 ]; then + ui_print "Successfully converted '$input_file' at '$output_file'." + fi + + return "$result" +} + +# Function to convert text XML to binary XML +text_to_abx() { + input_file="$1" + output_file="$2" + + ui_print "Attempting to convert '$input_file' to Android Binary XML" + + # Check if input file exists + if [ ! -f "$input_file" ]; then + ui_print "Error: Input file '$input_file' does not exist." + return 1 + fi + + # If output file is not provided, use the default (input file name with .abxml) + if [ -z "$output_file" ]; then + output_file="${input_file%.*}.abxml" + fi + + # Check if output directory is writable (only if output is not stdout) + if [ "$output_file" != "-" ]; then + output_dir=$(dirname "$output_file") + if [ ! -w "$output_dir" ]; then + ui_print "Error: Output directory '$output_dir' is not writable." + return 1 + fi + fi + + # Check if the input is likely text XML using 'file' + file_type=$(file -b "$input_file") + if ! echo "$file_type" | grep -q -E "XML .* text|text"; then + ui_print "Error: Input file '$input_file' is not recognized as a valid text XML file. File type: $file_type" + return 1 + fi + + # Use xml2abx for conversion + # Pipe stdout directly to a while loop, handling line by line + # stderr is combined with stdout + xml2abx "$input_file" "$output_file" 2>&1 | while read -r line; do + ui_print "xml2abx: $line" + done + result=$? + + # Print a generic error message if xml2abx failed + if [ $result -ne 0 ]; then + ui_print "Error: Failed to convert '$input_file'. Check xml2abx output for potential errors." + fi + + if [ $result -eq 0 ]; then + ui_print "Successfully converted '$input_file' at '$output_file'." + fi + + return "$result" +} + +# Function to rotate files, keeping the specified number of backups with enhanced logging +rotate_files() { + file_pattern="$1" + max_files="$2" + + ui_print "Rotating files matching pattern: $file_pattern, keeping maximum $max_files files." + + set -- "$(find "$BACKUP_DIR" -maxdepth 1 -name "$file_pattern" -type f | sort)" + + num_files=$# + + ui_print "Found $num_files files matching the pattern." + + if [ "$num_files" -gt "$max_files" ]; then + files_to_delete=$((num_files - max_files)) + ui_print "Need to delete $files_to_delete old files." + i=1 + for file in "$@"; do + if [ "$i" -le "$files_to_delete" ]; then + ui_print "Deleting old backup: $file" + rm "$file" + else + break + fi + i=$((i + 1)) + done + else + ui_print "No files to delete. Number of files is within the limit." + fi +} + +# Function to create a timestamped backup of a file using the script's timestamp +backup_file() { + original_file="$1" + original_file_basename=$(basename "$original_file") + original_file_no_ext="${original_file_basename%.*}" + backup_type="$2" + backup_file="$BACKUP_DIR/${original_file_no_ext}_$CURRENT_TIMESTAMP.$backup_type" + + ui_print "Preparing to back up '$original_file_basename' to '$backup_file'" + + if [ ! -d "$BACKUP_DIR" ]; then + ui_print "Backup directory '$BACKUP_DIR' does not exist. Creating..." + mkdir -p "$BACKUP_DIR" + fi + + ui_print "Rotating existing backups for $original_file_basename of type (.$backup_type)" + rotate_files "${original_file_no_ext}_*$backup_type" "$MAX_BACKUP_FILES" # Modified glob pattern + + ui_print "Creating backup..." + if cp "$original_file" "$backup_file"; then # Check if cp was successful + ui_print "Backup created at '$backup_file'" + echo "$backup_file" # Return the backup file path + return 0 # Indicate success + else + ui_print "Error: Failed to create backup!" + return 1 # Indicate failure + fi +}