Skip to content

Instantly share code, notes, and snippets.

@forcequitOS
Created March 3, 2025 00:18
Show Gist options
  • Save forcequitOS/c4526c303cfb1e990eb33e2b3cfbe997 to your computer and use it in GitHub Desktop.
Save forcequitOS/c4526c303cfb1e990eb33e2b3cfbe997 to your computer and use it in GitHub Desktop.
Extract an Apple Watch backup from an unencrypted iPhone backup. This script was written 100% by AI, good luck.
#!/bin/bash
# Set your iPhone's backup path before complaining
# Also, please make sure your iPhone actually has/had a paired Apple Watch. Otherwise, it's going to be very embarrasing if the script doesn't work.
# This script was genuinely written 100% by Claude. I am almost 100% sure it's missing a LOT of stuff. But, hey, it's something, and it's the only open-source way to extract user-files from watchOS /shrug.
# This script is intended for usage on macOS. It might work on Linux too, I don't know, and I haven't tested it there. Good luck.
BACKUP_PATH="$HOME/Library/Application Support/MobileSync"
OUTPUT_DIR="$HOME/NanoBackupExtract"
# Create output directory
mkdir -p "$OUTPUT_DIR"
echo "Extracting files from $BACKUP_PATH to $OUTPUT_DIR..."
# Clear previous output to start fresh
rm -rf "$OUTPUT_DIR"
mkdir -p "$OUTPUT_DIR"
# Function to find a file in the backup
find_backup_file() {
local file_id="$1"
local domain="$2"
local rel_path="$3"
# Try to find the file in various possible locations
# 1. Direct file ID
if [ -f "$BACKUP_PATH/$file_id" ]; then
echo "$BACKUP_PATH/$file_id"
return 0
fi
# 2. In Backup directory
if [ -f "$BACKUP_PATH/Backup/$file_id" ]; then
echo "$BACKUP_PATH/Backup/$file_id"
return 0
fi
# 3. In two-char subdirectory structure (common in iOS backups)
local first_two="${file_id:0:2}"
if [ -f "$BACKUP_PATH/$first_two/$file_id" ]; then
echo "$BACKUP_PATH/$first_two/$file_id"
return 0
fi
# 4. Try with domain-path hash method
local hash_input="${domain}-${rel_path}"
local hash=$(echo -n "$hash_input" | openssl sha1 | awk '{print $2}')
# Try direct hash
if [ -f "$BACKUP_PATH/$hash" ]; then
echo "$BACKUP_PATH/$hash"
return 0
fi
# Try in two-char subdirectory
local hash_first_two="${hash:0:2}"
if [ -f "$BACKUP_PATH/$hash_first_two/$hash" ]; then
echo "$BACKUP_PATH/$hash_first_two/$hash"
return 0
fi
echo ""
return 1
}
# Query and process the files
sqlite3 "$BACKUP_PATH/Manifest.db" \
"SELECT fileID, domain, relativePath FROM Files WHERE relativePath LIKE 'Library/NanoBackup/%';" | \
while IFS='|' read -r file_id domain rel_path; do
echo "Processing: $domain - $rel_path"
# Determine if this is likely a file or directory
# We'll assume it's a directory if the path ends with / or doesn't contain a period in the last component
is_dir=0
if [[ "$rel_path" == */ ]]; then
is_dir=1
else
# Get the basename
basename=$(basename "$rel_path")
# If no dot, or dot is first character (hidden file but no extension), treat as directory
if [[ "$basename" != *.* || "$basename" == .* && "$basename" != *.*.* ]]; then
is_dir=1
fi
fi
# Create destination path
dest_path="$OUTPUT_DIR/$domain/$rel_path"
if [ $is_dir -eq 1 ]; then
# It's a directory, just create it
echo " → Creating directory: $dest_path"
mkdir -p "$dest_path"
else
# It's a file
# Make sure parent directory exists
parent_dir=$(dirname "$dest_path")
mkdir -p "$parent_dir"
# Find the source file
source_path=$(find_backup_file "$file_id" "$domain" "$rel_path")
if [ -n "$source_path" ]; then
echo " → Copying file: $source_path → $dest_path"
cp "$source_path" "$dest_path"
if [ $? -eq 0 ]; then
echo " ✓ Success"
else
echo " ✗ Failed to copy"
fi
else
echo " → File not found, trying database BLOB extract"
# Try extracting directly from the database as a last resort
sqlite3 "$BACKUP_PATH/Manifest.db" \
"SELECT writefile('$dest_path', file) FROM Files WHERE fileID='$file_id';"
if [ -f "$dest_path" ]; then
echo " ✓ Extracted from database BLOB"
else
echo " ✗ Failed to extract file"
fi
fi
fi
done
echo "Extraction complete! Files are in: $OUTPUT_DIR"
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment