Kali NetHunter Installation Guide
Transform your Samsung Galaxy S10 (or other supported Android device) into a portable penetration testing platform with Kali NetHunter. This guide walks you through every step—from unlocking to first boot.
Before You Begin
- This will wipe your device. Back up everything first.
- You need a Linux machine (Kali preferred) for flashing.
- Ensure your device has at least 60% battery.
- Unlocking the bootloader may void your warranty and trip Knox.
Always Verify Latest Versions
Firmware, recoveries, and tools update frequently. Before flashing, confirm you have the latest compatible versions:
- LineageOS: wiki.lineageos.org/devices/beyond1lte — required for Samsung S10 NetHunter builds
- TWRP: twrp.me/samsung/samsunggalaxys10.html
- NetHunter: kali.org/get-kali/#kali-mobile — match your device codename and LineageOS version
- Magisk: github.com/topjohnwu/Magisk/releases
In This Guide
1. Prerequisites & Downloads
Supported Devices
NetHunter has three editions. Choose based on your device:
NetHunter (Full)
Requires a device with an officially supported kernel. Full HID attacks, BadUSB, WiFi injection.
Examples: OnePlus, Nexus, some Samsung models
NetHunter Rootless
No root required. Runs in Termux. Limited hardware access.
Works on almost any Android 5+
NetHunter Lite
Rooted device, no custom kernel. App + chroot, no HID attacks.
Works on most rooted devices
This Guide Covers Full NetHunter
LineageOS Required for Samsung Devices
What You'll Need
Hardware
- ☑️ Samsung Galaxy S10 (SM-G973F/Exynos) — or your supported device
- ☑️ USB-C cable (data-capable, not charge-only)
- ☑️ Linux PC/laptop (Kali Linux recommended)
- ☑️ microSD card (optional, for storing images/wordlists)
- ☑️ USB OTG adapter (for external WiFi adapters later)
Software Downloads
Download all files to a folder on your Kali machine (e.g., ~/nethunter-install/).
| File | Source | Notes |
|---|---|---|
| Odin (Linux: Heimdall) | GitHub | Flash tool for Samsung |
| TWRP Recovery | twrp.me | Search for your exact model |
| LineageOS ROM | LineageOS Wiki | Required — flash before NetHunter (match your codename) |
| NetHunter ZIP | kali.org | Pick your device codename (e.g., beyond1lte) |
| Magisk APK/ZIP | GitHub | For root access |
| no-verity-opt-encrypt | GitHub | Disables DM-verity (stock Samsung only — not needed for LineageOS) |
Auto-Download Script for Samsung Galaxy Devices
Run this script on your Kali machine to automatically download all prerequisites. Supports multiple Samsung models — enter your model number or press Enter to use S10 defaults.
Note: If you see "apt update" errors about mirror sync or 404s, this is a temporary Kali mirror issue.
The script will continue anyway. Run sudo apt update --fix-missing later if needed.
#!/bin/bash
# ============================================================
# NetHunter Samsung Auto-Installer (v3 — Feb 2026)
# Supports: S10, S10+, S10e, S10 5G, S7, S9, Note 10 series
# Run on: Kali Linux (with phone connected via USB)
# Downloads to: the directory you run this script from
#
# IMPORTANT: Current Kali NetHunter Samsung S10/S20/Note images
# require LineageOS — NOT stock Samsung firmware.
#
# Features:
# • Auto-detects device via ADB (no manual model entry needed)
# • Skips files that are already downloaded and valid
# • Downloads LineageOS ROM automatically when required
# • Verifies SHA256 checksums for NetHunter images
# • Pushes files to device via ADB (if in TWRP)
# • Flashes TWRP via Heimdall (if in Download Mode)
# ============================================================
set +e # Don't exit on first error
# Colors
GREEN='\033[0;32m'
CYAN='\033[0;36m'
YELLOW='\033[1;33m'
RED='\033[0;31m'
BOLD='\033[1m'
NC='\033[0m'
# ============================================================
# Failure tracking — collected and reported at the end
# ============================================================
FAILED_DOWNLOADS=() # "LABEL|FILENAME|URL|MANUAL_URL|MIN_SIZE|HINT"
PASSED_DOWNLOADS=() # "LABEL|FILENAME|SIZE_HUMAN"
SKIPPED_DOWNLOADS=() # "LABEL|REASON"
# Minimum expected file sizes (bytes) for validation
MIN_TWRP=1048576 # 1 MB — real TWRP images are ~30-50 MB
MIN_MAGISK=1048576 # 1 MB — real Magisk APK is ~12 MB
MIN_NETHUNTER=10485760 # 10 MB — real NetHunter ZIPs are ~2.4 GB
MIN_NOVERITY=5120 # 5 KB — real zip is ~10-20 KB
MIN_LINEAGEOS=209715200 # 200 MB — real LOS ZIPs are ~700 MB+
# ============================================================
# Helper: validate a file by minimum size
# ============================================================
validate_download() {
local label="$1" file="$2" min_size="$3" url="$4" manual_url="$5" hint="${6:-}"
if [ ! -f "$file" ]; then
echo -e "${RED} ✗ $file not found — download failed.${NC}"
FAILED_DOWNLOADS+=("$label|$file|$url|$manual_url|$min_size|$hint")
return 1
fi
local actual_size
actual_size=$(stat -c%s "$file" 2>/dev/null || stat -f%z "$file" 2>/dev/null || echo 0)
local human_size
human_size=$(ls -lh "$file" | awk '{print $5}')
if [ "$actual_size" -lt "$min_size" ]; then
echo -e "${RED} ✗ $file is only $human_size — expected at least $(numfmt --to=iec $min_size 2>/dev/null || echo "$min_size bytes").${NC}"
echo -e "${RED} File is likely an error page or empty. Removing it.${NC}"
rm -f "$file"
FAILED_DOWNLOADS+=("$label|$file|$url|$manual_url|$min_size|$hint")
return 1
fi
echo -e "${GREEN} ✓ $file — $human_size — OK${NC}"
PASSED_DOWNLOADS+=("$label|$file|$human_size")
return 0
}
# ============================================================
# Helper: check if a valid file already exists → skip download
# ============================================================
already_have() {
local label="$1" file="$2" min_size="$3"
if [ -f "$file" ]; then
local actual_size
actual_size=$(stat -c%s "$file" 2>/dev/null || stat -f%z "$file" 2>/dev/null || echo 0)
if [ "$actual_size" -ge "$min_size" ]; then
local human_size
human_size=$(ls -lh "$file" | awk '{print $5}')
echo -e "${GREEN} ✓ Already have $file ($human_size) — skipping download.${NC}"
PASSED_DOWNLOADS+=("$label|$file|$human_size (cached)")
return 0
fi
fi
return 1
}
echo -e "${CYAN}╔════════════════════════════════════════════════════════════╗${NC}"
echo -e "${CYAN}║ NetHunter Samsung Auto-Installer (v3) ║${NC}"
echo -e "${CYAN}╚════════════════════════════════════════════════════════════╝${NC}"
# ============================================================
# Working directory = wherever the script is run from
# ============================================================
INSTALL_DIR="$(pwd)"
echo -e "\n${GREEN}[+] Working directory: $INSTALL_DIR${NC}"
# ============================================================
# Device Selection — auto-detect via ADB, fallback to manual
# ============================================================
echo -e "\n${CYAN}[Device Configuration]${NC}"
ADB_DETECTED=""
if command -v adb &>/dev/null; then
# Start ADB server quietly
adb start-server 2>/dev/null
# Try to read model from connected device
ADB_MODEL=$(adb shell getprop ro.product.model 2>/dev/null | tr -d '\r\n')
if [ -n "$ADB_MODEL" ]; then
ADB_DETECTED="$ADB_MODEL"
echo -e "${GREEN} 📱 Detected device via ADB: $ADB_MODEL${NC}"
# Try to get the full model number (SM-XXXXX)
ADB_SKU=$(adb shell getprop ro.product.name 2>/dev/null | tr -d '\r\n')
ADB_DEVICE=$(adb shell getprop ro.product.device 2>/dev/null | tr -d '\r\n')
ADB_MODELNUM=$(adb shell getprop ro.boot.em.model 2>/dev/null | tr -d '\r\n')
# Samsung stores full model in sku or modelnum
[ -z "$ADB_MODELNUM" ] && ADB_MODELNUM=$(adb shell getprop ro.product.vendor.model 2>/dev/null | tr -d '\r\n')
if [ -n "$ADB_MODELNUM" ]; then
echo -e "${GREEN} Model number: $ADB_MODELNUM${NC}"
echo -e "\nUse detected model ${GREEN}$ADB_MODELNUM${NC}? [Y/n] "
read -r USE_DETECTED
if [[ ! "$USE_DETECTED" =~ ^[Nn] ]]; then
MODEL_INPUT="$ADB_MODELNUM"
fi
else
echo -e "${YELLOW} Could not read model number. Device code: $ADB_DEVICE${NC}"
fi
else
echo -e "${YELLOW} No device detected via ADB (phone not connected or USB debugging off).${NC}"
fi
fi
if [ -z "$MODEL_INPUT" ]; then
echo -e "Enter your Samsung model number (e.g., SM-G973F, SM-G975F, SM-N975F)"
echo -e "Or press Enter to use default: ${GREEN}SM-G973F (Galaxy S10 Exynos)${NC}"
echo ""
read -p "Model number [SM-G973F]: " MODEL_INPUT
fi
MODEL=${MODEL_INPUT:-SM-G973F}
MODEL_UPPER=$(echo "$MODEL" | tr '[:lower:]' '[:upper:]')
echo -e "\n${GREEN}[+] Selected model: $MODEL_UPPER${NC}"
# Model → codename mapping
# Format: CODENAME|FRIENDLY_NAME|TWRP_DEVICE
declare -A MODEL_MAP=(
# Galaxy S10 series (Exynos)
["SM-G970F"]="beyond0lte|Galaxy S10e|samsunggalaxys10e"
["SM-G973F"]="beyond1lte|Galaxy S10|samsunggalaxys10"
["SM-G975F"]="beyond2lte|Galaxy S10+|samsunggalaxys10plus"
["SM-G977B"]="beyondx|Galaxy S10 5G|samsunggalaxys105g"
# Galaxy S10 series (Snapdragon — bootloader often locked!)
["SM-G970U"]="beyond0lte|Galaxy S10e (SD)|samsunggalaxys10e"
["SM-G973U"]="beyond1lte|Galaxy S10 (SD)|samsunggalaxys10"
["SM-G975U"]="beyond2lte|Galaxy S10+ (SD)|samsunggalaxys10plus"
# Galaxy S7 series (Samsung UI — OUI)
["SM-G935F"]="hero2lte|Galaxy S7 Edge|samsunggalaxys7"
["SM-G930F"]="herolte|Galaxy S7|samsunggalaxys7"
# Galaxy S9 series
["SM-G960F"]="starlte|Galaxy S9|samsunggalaxys9"
["SM-G965F"]="star2lte|Galaxy S9+|samsunggalaxys9plus"
# Galaxy Note series
["SM-N970F"]="d1|Galaxy Note 10|samsunggalaxynote10"
["SM-N975F"]="d2s|Galaxy Note 10+|samsunggalaxynote10plus"
)
# Parse model info
if [[ -v MODEL_MAP[$MODEL_UPPER] ]]; then
IFS='|' read -r CODENAME DEVICE_FRIENDLY TWRP_DEVICE <<< "${MODEL_MAP[$MODEL_UPPER]}"
echo -e "${GREEN} Device: $DEVICE_FRIENDLY${NC}"
echo -e "${GREEN} Codename: $CODENAME${NC}"
echo -e "${GREEN} TWRP key: $TWRP_DEVICE${NC}"
else
echo -e "${YELLOW}[!] Model not in database. Using S10 defaults.${NC}"
echo -e "${YELLOW} You may need to manually find correct TWRP/NetHunter files.${NC}"
CODENAME="beyond1lte"
DEVICE_FRIENDLY="Galaxy S10"
TWRP_DEVICE="samsunggalaxys10"
fi
# Detect chipset
if [[ "$MODEL_UPPER" =~ U[0-9]*$ ]]; then
CHIPSET="snapdragon"
echo -e "${YELLOW} Chipset: Snapdragon (US/China variant)${NC}"
echo -e "${RED} ⚠ Warning: Many Snapdragon Samsung devices have locked bootloaders!${NC}"
else
CHIPSET="exynos"
echo -e "${GREEN} Chipset: Exynos (International variant)${NC}"
fi
# Save device config
cat > "$INSTALL_DIR/device-config.sh" << EOF
# Device configuration — source this file in other scripts
export MODEL="$MODEL_UPPER"
export CODENAME="$CODENAME"
export DEVICE_FRIENDLY="$DEVICE_FRIENDLY"
export TWRP_DEVICE="$TWRP_DEVICE"
export CHIPSET="$CHIPSET"
EOF
echo -e "${GREEN}[+] Saved device config to: $INSTALL_DIR/device-config.sh${NC}"
# ============================================================
# 1. Install Heimdall & tools
# ============================================================
echo -e "\n${CYAN}[1/7] Installing Heimdall & tools...${NC}"
# Check which tools are already installed
MISSING_PKGS=()
for cmd in heimdall adb fastboot curl wget; do
if ! command -v "$cmd" &>/dev/null; then
MISSING_PKGS+=("$cmd")
fi
done
if [ ${#MISSING_PKGS[@]} -eq 0 ]; then
echo -e "${GREEN} All tools already installed — skipping.${NC}"
else
echo -e " Missing: ${MISSING_PKGS[*]}. Installing..."
sudo apt update --allow-releaseinfo-change 2>/dev/null || {
echo -e "${YELLOW}[!] apt update had some errors (mirror sync issues are common).${NC}"
echo -e "${YELLOW} Continuing anyway — packages may still install fine.${NC}"
}
sudo apt install -y heimdall-flash heimdall-flash-frontend adb fastboot curl wget || {
echo -e "${RED}[!] Package installation failed. Try:${NC}"
echo -e " sudo apt update --fix-missing"
echo -e " sudo apt install -y heimdall-flash adb fastboot curl wget"
echo -e "${YELLOW} Continuing with script...${NC}"
}
fi
# ============================================================
# 2. Download TWRP Recovery
# ============================================================
echo -e "\n${CYAN}[2/7] TWRP Recovery for $CODENAME...${NC}"
TWRP_DL_URL="https://dl.twrp.me/$CODENAME/"
TWRP_MANUAL_URL="https://twrp.me/samsung/$TWRP_DEVICE.html"
if already_have "TWRP Recovery" "twrp.img" "$MIN_TWRP"; then
: # already_have prints confirmation and adds to PASSED
else
echo -e " Checking twrp.me for latest $CODENAME image..."
TWRP_PAGE=$(curl -sL "$TWRP_DL_URL")
TWRP_FILE=$(echo "$TWRP_PAGE" | grep -oP 'twrp-[0-9.]+_[0-9]+-[0-9]+-'"$CODENAME"'\.img' | head -1)
if [ -n "$TWRP_FILE" ]; then
TWRP_FULL_URL="https://dl.twrp.me/$CODENAME/$TWRP_FILE"
echo -e "${GREEN} Found: $TWRP_FILE${NC}"
else
TWRP_FILE="twrp-3.7.0_12-0-$CODENAME.img"
TWRP_FULL_URL="https://dl.twrp.me/$CODENAME/$TWRP_FILE"
echo -e "${YELLOW} Using fallback: $TWRP_FILE${NC}"
fi
echo -e " Downloading twrp.img ..."
wget --content-disposition --header="Referer: $TWRP_DL_URL" \
-O twrp.img "$TWRP_FULL_URL" 2>&1 || true
validate_download "TWRP Recovery" "twrp.img" "$MIN_TWRP" \
"$TWRP_FULL_URL" "$TWRP_MANUAL_URL" \
"twrp.me blocks direct wget. Open the Manual URL in a browser and download manually."
fi
# ============================================================
# 3. Download Magisk (latest stable)
# ============================================================
echo -e "\n${CYAN}[3/7] Magisk...${NC}"
MAGISK_MANUAL_URL="https://github.com/topjohnwu/Magisk/releases"
# Check for any existing Magisk APK
EXISTING_MAGISK=$(ls -1 Magisk-*.apk 2>/dev/null | head -1)
if [ -n "$EXISTING_MAGISK" ] && already_have "Magisk" "$EXISTING_MAGISK" "$MIN_MAGISK"; then
MAGISK_FILE="$EXISTING_MAGISK"
else
MAGISK_URL=$(curl -sL https://api.github.com/repos/topjohnwu/Magisk/releases/latest \
| grep "browser_download_url.*Magisk-.*\.apk" | head -1 | cut -d '"' -f 4)
if [ -n "$MAGISK_URL" ]; then
MAGISK_FILE=$(basename "$MAGISK_URL")
echo -e " Downloading $MAGISK_FILE ..."
wget -O "$MAGISK_FILE" "$MAGISK_URL" 2>&1 || true
validate_download "Magisk" "$MAGISK_FILE" "$MIN_MAGISK" \
"$MAGISK_URL" "$MAGISK_MANUAL_URL" \
"Download the latest Magisk-vXX.X.apk from the GitHub releases page."
else
echo -e "${RED} ✗ Could not resolve Magisk URL from GitHub API.${NC}"
MAGISK_FILE="Magisk-latest.apk"
FAILED_DOWNLOADS+=("Magisk|$MAGISK_FILE|<GitHub API failed>|$MAGISK_MANUAL_URL|$MIN_MAGISK|Download the latest Magisk-vXX.X.apk from GitHub releases.")
fi
fi
# ============================================================
# 4. Download NetHunter (auto-detect from Kali mirror)
# ============================================================
echo -e "\n${CYAN}[4/7] NetHunter for $CODENAME...${NC}"
NH_MANUAL_URL="https://www.kali.org/get-kali/#kali-mobile"
NH_BASE="https://kali.download/nethunter-images/current/"
# Check for any existing NetHunter ZIP matching our codename
EXISTING_NH=$(ls -1 kali-nethunter-*"$CODENAME"*-full.zip 2>/dev/null | head -1)
if [ -n "$EXISTING_NH" ] && already_have "NetHunter" "$EXISTING_NH" "$MIN_NETHUNTER"; then
NH_FILE="$EXISTING_NH"
else
echo -e " Fetching available images from Kali mirror..."
NH_DIR_LISTING=$(curl -sL "$NH_BASE")
echo -e " Searching for codename: ${BOLD}$CODENAME${NC}"
NH_MATCHES=$(echo "$NH_DIR_LISTING" | grep -oP 'kali-nethunter-[0-9.]+-'"$CODENAME"'-[^"]+?-full\.zip' | sort -u)
if [ -n "$NH_MATCHES" ]; then
NH_COUNT=$(echo "$NH_MATCHES" | wc -l)
if [ "$NH_COUNT" -eq 1 ]; then
NH_FILE="$NH_MATCHES"
echo -e "${GREEN} Found: $NH_FILE${NC}"
else
echo -e "${GREEN} Found $NH_COUNT images for $CODENAME:${NC}"
i=1
while IFS= read -r f; do
echo -e " $i) $f"
i=$((i + 1))
done <<< "$NH_MATCHES"
read -p " Select image [1]: " NH_PICK
NH_PICK=${NH_PICK:-1}
NH_FILE=$(echo "$NH_MATCHES" | sed -n "${NH_PICK}p")
[ -z "$NH_FILE" ] && NH_FILE=$(echo "$NH_MATCHES" | head -1)
echo -e "${GREEN} Selected: $NH_FILE${NC}"
fi
NH_URL="${NH_BASE}${NH_FILE}"
else
echo -e "${YELLOW} No image found for codename '$CODENAME'.${NC}"
echo -e "${YELLOW} Searching for any 'beyond' (S10 family) image...${NC}"
NH_FALLBACK=$(echo "$NH_DIR_LISTING" | grep -oP 'kali-nethunter-[0-9.]+-beyond1lte-[^"]+?-full\.zip' | head -1)
if [ -n "$NH_FALLBACK" ]; then
NH_FILE="$NH_FALLBACK"
NH_URL="${NH_BASE}${NH_FILE}"
echo -e "${GREEN} Found fallback: $NH_FILE${NC}"
else
NH_FILE="kali-nethunter-$CODENAME-full.zip"
NH_URL=""
echo -e "${RED} ✗ No matching image found on the Kali mirror.${NC}"
fi
fi
# Detect LineageOS requirement
if [[ "$NH_FILE" == *"-los-"* ]]; then
LOS_VERSION=$(echo "$NH_FILE" | grep -oP '(?<=-los-)[a-z]+')
echo -e ""
echo -e "${YELLOW} ╔══════════════════════════════════════════════════════╗${NC}"
echo -e "${YELLOW} ║ ⚠ LINEAGEOS REQUIRED ║${NC}"
echo -e "${YELLOW} ╚══════════════════════════════════════════════════════╝${NC}"
echo -e "${YELLOW} This NetHunter image requires LineageOS (not stock Samsung).${NC}"
echo -e "${YELLOW} LineageOS version in this build: $LOS_VERSION${NC}"
echo -e "${YELLOW} LineageOS will be downloaded automatically in step 5.${NC}"
echo ""
echo "export REQUIRES_LINEAGEOS=\"true\"" >> "$INSTALL_DIR/device-config.sh"
echo "export LOS_VERSION=\"$LOS_VERSION\"" >> "$INSTALL_DIR/device-config.sh"
fi
if [ -n "$NH_URL" ]; then
echo -e " Downloading $NH_FILE (~2.4 GB — be patient)..."
wget -O "$NH_FILE" "$NH_URL" 2>&1 || true
validate_download "NetHunter" "$NH_FILE" "$MIN_NETHUNTER" \
"$NH_URL" "$NH_MANUAL_URL" \
"Go to kali.org/get-kali → Mobile → Samsung section. Look for $DEVICE_FRIENDLY."
else
FAILED_DOWNLOADS+=("NetHunter|$NH_FILE|<no URL found>|$NH_MANUAL_URL|$MIN_NETHUNTER|Go to kali.org/get-kali → Mobile → Samsung and download the image for $DEVICE_FRIENDLY.")
fi
fi
# ============================================================
# 4b. Verify SHA256 checksum for NetHunter
# ============================================================
if [ -f "$NH_FILE" ]; then
echo -e "\n${CYAN} Verifying SHA256 checksum for $NH_FILE...${NC}"
SHA_URL="${NH_BASE}SHA256SUMS"
SHA_SUMS=$(curl -sL "$SHA_URL")
if [ -n "$SHA_SUMS" ]; then
EXPECTED_SHA=$(echo "$SHA_SUMS" | grep "$NH_FILE" | awk '{print $1}')
if [ -n "$EXPECTED_SHA" ]; then
ACTUAL_SHA=$(sha256sum "$NH_FILE" | awk '{print $1}')
if [ "$EXPECTED_SHA" = "$ACTUAL_SHA" ]; then
echo -e "${GREEN} ✓ SHA256 checksum verified — file is authentic.${NC}"
else
echo -e "${RED} ✗ SHA256 MISMATCH!${NC}"
echo -e "${RED} Expected: $EXPECTED_SHA${NC}"
echo -e "${RED} Got: $ACTUAL_SHA${NC}"
echo -e "${RED} The file may be corrupted. Consider re-downloading.${NC}"
fi
else
echo -e "${YELLOW} No checksum found for $NH_FILE in SHA256SUMS.${NC}"
fi
else
echo -e "${YELLOW} Could not fetch SHA256SUMS from $SHA_URL${NC}"
fi
fi
# ============================================================
# 5. Download LineageOS (if required)
# ============================================================
REQUIRES_LOS=false
[[ "$NH_FILE" == *"-los-"* ]] && REQUIRES_LOS=true
if [ "$REQUIRES_LOS" = true ]; then
echo -e "\n${CYAN}[5/7] LineageOS ROM for $CODENAME...${NC}"
LOS_MANUAL_URL="https://wiki.lineageos.org/devices/$CODENAME"
# Check for existing LineageOS ZIP
EXISTING_LOS=$(ls -1 lineage-*"$CODENAME"*.zip 2>/dev/null | head -1)
if [ -n "$EXISTING_LOS" ] && already_have "LineageOS" "$EXISTING_LOS" "$MIN_LINEAGEOS"; then
LOS_FILE="$EXISTING_LOS"
else
# LineageOS provides an API to find the latest build
LOS_API="https://download.lineageos.org/api/v2/devices/$CODENAME"
echo -e " Querying LineageOS API for $CODENAME..."
LOS_JSON=$(curl -sL "$LOS_API")
# Try the download API for builds
LOS_BUILDS_API="https://download.lineageos.org/api/v2/devices/$CODENAME/builds"
LOS_BUILDS=$(curl -sL "$LOS_BUILDS_API")
# Extract the latest build download URL
LOS_DL_URL=$(echo "$LOS_BUILDS" | grep -oP '"url"\s*:\s*"\K[^"]+\.zip' | head -1)
if [ -n "$LOS_DL_URL" ]; then
LOS_FILE=$(basename "$LOS_DL_URL")
echo -e "${GREEN} Found: $LOS_FILE${NC}"
echo -e " Downloading LineageOS (~700 MB)..."
wget -O "$LOS_FILE" "$LOS_DL_URL" 2>&1 || true
validate_download "LineageOS" "$LOS_FILE" "$MIN_LINEAGEOS" \
"$LOS_DL_URL" "$LOS_MANUAL_URL" \
"Visit $LOS_MANUAL_URL and download the latest build."
else
# Fallback: try scraping the device page
echo -e "${YELLOW} API did not return a build. Checking download page...${NC}"
LOS_PAGE=$(curl -sL "https://download.lineageos.org/$CODENAME")
LOS_DL_URL=$(echo "$LOS_PAGE" | grep -oP 'https://[^"]+lineage-[0-9.]+-[0-9]+-nightly-'"$CODENAME"'-signed\.zip' | head -1)
if [ -n "$LOS_DL_URL" ]; then
LOS_FILE=$(basename "$LOS_DL_URL")
echo -e "${GREEN} Found: $LOS_FILE${NC}"
echo -e " Downloading LineageOS (~700 MB)..."
wget -O "$LOS_FILE" "$LOS_DL_URL" 2>&1 || true
validate_download "LineageOS" "$LOS_FILE" "$MIN_LINEAGEOS" \
"$LOS_DL_URL" "$LOS_MANUAL_URL" \
"Visit $LOS_MANUAL_URL and download the latest build."
else
LOS_FILE="lineage-$CODENAME.zip"
echo -e "${RED} ✗ Could not find LineageOS download for $CODENAME.${NC}"
FAILED_DOWNLOADS+=("LineageOS|$LOS_FILE|<auto-detect failed>|$LOS_MANUAL_URL|$MIN_LINEAGEOS|Visit $LOS_MANUAL_URL, download the latest nightly ZIP, and save it here.")
fi
fi
fi
else
echo -e "\n${CYAN}[5/7] LineageOS — not required for this build.${NC}"
SKIPPED_DOWNLOADS+=("LineageOS|Not required — NetHunter image uses stock firmware")
fi
# ============================================================
# 6. Download no-verity-opt-encrypt
# ============================================================
echo -e "\n${CYAN}[6/7] no-verity-opt-encrypt...${NC}"
NOVERITY_FILE="no-verity-opt-encrypt.zip"
NOVERITY_MANUAL_URL="https://github.com/AMP-1/no-verity-opt-encrypt"
NOVERITY_DOWNLOADED=false
if [ "$REQUIRES_LOS" = true ]; then
echo -e "${YELLOW} LineageOS build detected — no-verity is NOT needed.${NC}"
echo -e "${YELLOW} Skipping (only required for stock Samsung firmware).${NC}"
SKIPPED_DOWNLOADS+=("no-verity-opt-encrypt|Not needed for LineageOS builds")
elif already_have "no-verity-opt-encrypt" "$NOVERITY_FILE" "$MIN_NOVERITY"; then
NOVERITY_DOWNLOADED=true
else
NOVERITY_URLS=(
"https://artifacts.kali.org/images-nethunter/nethunter-installer/no-verity-opt-encrypt-6.1.zip"
"https://github.com/AMP-1/no-verity-opt-encrypt/releases/download/noencrypt/no-verity-opt-encrypt-6.1.zip"
)
for NOVERITY_URL in "${NOVERITY_URLS[@]}"; do
echo -e " Trying: $NOVERITY_URL"
wget -O "$NOVERITY_FILE" "$NOVERITY_URL" 2>&1 || true
if validate_download "no-verity-opt-encrypt" "$NOVERITY_FILE" "$MIN_NOVERITY" \
"$NOVERITY_URL" "$NOVERITY_MANUAL_URL" \
"Search GitHub for 'no-verity-opt-encrypt' and download the latest ZIP."; then
NOVERITY_DOWNLOADED=true
break
fi
done
fi
# ============================================================
# 7. Device actions — push files / flash TWRP
# ============================================================
echo -e "\n${CYAN}[7/7] Device actions...${NC}"
# Detect device state via ADB / Heimdall
DEVICE_STATE="none"
if command -v adb &>/dev/null; then
ADB_STATE=$(adb get-state 2>/dev/null | tr -d '\r\n')
case "$ADB_STATE" in
device) DEVICE_STATE="adb-normal" ;;
recovery) DEVICE_STATE="adb-recovery" ;;
sideload) DEVICE_STATE="adb-sideload" ;;
esac
fi
if [ "$DEVICE_STATE" = "none" ] && command -v heimdall &>/dev/null; then
if heimdall detect &>/dev/null; then
DEVICE_STATE="download-mode"
fi
fi
case "$DEVICE_STATE" in
adb-recovery)
# Phone is in TWRP recovery — push all files automatically
echo -e "${GREEN} 📱 Device detected in TWRP recovery mode!${NC}"
echo -e " Pushing files to /sdcard/NetHunter/ ..."
adb shell mkdir -p /sdcard/NetHunter 2>/dev/null
PUSH_FILES=()
# LineageOS first (must be flashed first)
if [ "$REQUIRES_LOS" = true ] && [ -f "${LOS_FILE:-}" ]; then
PUSH_FILES+=("$LOS_FILE")
fi
# Magisk
[ -f "${MAGISK_FILE:-}" ] && PUSH_FILES+=("$MAGISK_FILE")
# NetHunter
[ -f "${NH_FILE:-}" ] && PUSH_FILES+=("$NH_FILE")
# no-verity (only for stock)
if [ "$REQUIRES_LOS" = false ] && [ -f "$NOVERITY_FILE" ]; then
PUSH_FILES+=("$NOVERITY_FILE")
fi
if [ ${#PUSH_FILES[@]} -gt 0 ]; then
for pf in "${PUSH_FILES[@]}"; do
echo -e " Pushing $pf ..."
adb push "$pf" /sdcard/NetHunter/ 2>&1
if [ $? -eq 0 ]; then
echo -e "${GREEN} ✓ $pf pushed${NC}"
else
echo -e "${RED} ✗ Failed to push $pf${NC}"
fi
done
echo -e "\n${GREEN} Files on device:${NC}"
adb shell ls -la /sdcard/NetHunter/
else
echo -e "${YELLOW} No valid files to push.${NC}"
fi
echo -e "\n${GREEN} ✓ Files pushed! In TWRP:${NC}"
if [ "$REQUIRES_LOS" = true ]; then
echo -e " 1. Install → select LineageOS ZIP → Swipe to flash"
echo -e " 2. Install → select Magisk APK → Swipe to flash"
echo -e " 3. Install → select NetHunter ZIP → Swipe to flash"
echo -e " 4. Wipe → Advanced Wipe → Dalvik/ART Cache → Swipe"
echo -e " 5. Reboot → System"
else
echo -e " 1. Install → select Magisk APK → Swipe to flash"
echo -e " 2. Install → select no-verity ZIP → Swipe to flash"
echo -e " 3. Install → select NetHunter ZIP → Swipe to flash"
echo -e " 4. Wipe → Advanced Wipe → Dalvik/ART Cache → Swipe"
echo -e " 5. Reboot → System"
fi
;;
download-mode)
# Phone is in Download Mode — offer to flash TWRP
echo -e "${GREEN} 📱 Device detected in Download Mode!${NC}"
if [ -f "twrp.img" ]; then
echo -e ""
echo -e "${YELLOW} Flash TWRP recovery to this device now?${NC}"
echo -e "${YELLOW} This will write twrp.img to the RECOVERY partition.${NC}"
echo -e ""
read -p " Flash TWRP? [y/N]: " FLASH_CONFIRM
if [[ "$FLASH_CONFIRM" =~ ^[Yy] ]]; then
echo -e " Flashing TWRP..."
heimdall flash --RECOVERY twrp.img --no-reboot
if [ $? -eq 0 ]; then
echo -e "${GREEN} ✓ TWRP flashed successfully!${NC}"
echo -e ""
echo -e "${CYAN} Next: Boot into TWRP recovery:${NC}"
echo -e " 1. Disconnect USB"
echo -e " 2. Hold Volume Up + Bixby + Power"
echo -e " 3. Release when Samsung logo appears"
echo -e " 4. Re-run this script once in TWRP to auto-push files"
else
echo -e "${RED} ✗ Heimdall flash failed. Try manually:${NC}"
echo -e " heimdall flash --RECOVERY twrp.img --no-reboot"
fi
else
echo -e " Skipped. Flash manually later with:"
echo -e " heimdall flash --RECOVERY twrp.img --no-reboot"
fi
else
echo -e "${YELLOW} twrp.img not found — cannot flash.${NC}"
echo -e " Resolve TWRP download above, then re-run."
fi
;;
adb-normal)
echo -e "${YELLOW} 📱 Device detected in normal Android mode.${NC}"
echo -e " To continue installation:"
echo -e " 1. Boot to Download Mode (Vol Up+Down, plug USB) → re-run to flash TWRP"
echo -e " 2. Or boot to TWRP (Vol Up+Bixby+Power) → re-run to push files"
;;
*)
echo -e "${YELLOW} No device detected via ADB or Heimdall.${NC}"
echo -e " Connect your phone and re-run this script to:"
echo -e " • Download Mode → auto-flash TWRP"
echo -e " • TWRP Recovery → auto-push all ZIPs to device"
;;
esac
# ============================================================
# Final Report
# ============================================================
echo -e "\n${CYAN}════════════════════════════════════════════════════════════${NC}"
echo -e "\n${GREEN}Files in $INSTALL_DIR:${NC}"
ls -lh "$INSTALL_DIR"
echo ""
echo -e "${CYAN}╔════════════════════════════════════════════════════════════╗${NC}"
echo -e "${CYAN}║ DOWNLOAD REPORT ║${NC}"
echo -e "${CYAN}╚════════════════════════════════════════════════════════════╝${NC}"
echo -e "\n${GREEN}Device:${NC} $MODEL_UPPER ($DEVICE_FRIENDLY)"
echo -e "${GREEN}Codename:${NC} $CODENAME"
echo -e "${GREEN}Chipset:${NC} $CHIPSET"
echo -e "${GREEN}Directory:${NC} $INSTALL_DIR"
# --- Successful downloads ---
if [ ${#PASSED_DOWNLOADS[@]} -gt 0 ]; then
echo -e "\n${GREEN}✓ Successful (${#PASSED_DOWNLOADS[@]}):${NC}"
for entry in "${PASSED_DOWNLOADS[@]}"; do
IFS='|' read -r label file size <<< "$entry"
echo -e " ${GREEN}✓${NC} $label → $file ($size)"
done
fi
# --- Skipped downloads ---
if [ ${#SKIPPED_DOWNLOADS[@]} -gt 0 ]; then
echo -e "\n${YELLOW}⊘ Skipped (${#SKIPPED_DOWNLOADS[@]}):${NC}"
for entry in "${SKIPPED_DOWNLOADS[@]}"; do
IFS='|' read -r label reason <<< "$entry"
echo -e " ${YELLOW}⊘${NC} $label — $reason"
done
fi
# --- Failed downloads ---
CLEAN_FAILED=()
for entry in "${FAILED_DOWNLOADS[@]}"; do
[ -n "$entry" ] && CLEAN_FAILED+=("$entry")
done
FAILED_DOWNLOADS=("${CLEAN_FAILED[@]}")
if [ ${#FAILED_DOWNLOADS[@]} -gt 0 ]; then
echo -e "\n${RED}╔════════════════════════════════════════════════════════════╗${NC}"
echo -e "${RED}║ ✗ FAILED (${#FAILED_DOWNLOADS[@]}) — Manual action required ║${NC}"
echo -e "${RED}╚════════════════════════════════════════════════════════════╝${NC}"
echo ""
for entry in "${FAILED_DOWNLOADS[@]}"; do
IFS='|' read -r label file url manual min hint <<< "$entry"
echo -e " ${RED}✗ $label${NC}"
echo -e " File needed : ${BOLD}$file${NC}"
echo -e " Tried URL : $url"
echo -e " Manual URL : ${CYAN}$manual${NC}"
echo -e " Save to : ${BOLD}$INSTALL_DIR/$file${NC}"
echo -e " Min size : $(numfmt --to=iec $min 2>/dev/null || echo "$min bytes")"
[ -n "$hint" ] && echo -e " ${YELLOW}Hint: $hint${NC}"
echo ""
done
echo -e "${YELLOW}TIP: Some sites block direct wget downloads.${NC}"
echo -e "${YELLOW} Open the Manual URL in a browser, download the file,${NC}"
echo -e "${YELLOW} then copy/move it into: $INSTALL_DIR/${NC}"
else
echo -e "\n${GREEN}All downloads completed successfully! ✓${NC}"
fi
# Next steps (context-aware)
echo -e "\n${CYAN}╔════════════════════════════════════════════════════════════╗${NC}"
echo -e "${CYAN}║ WHAT TO DO NEXT ║${NC}"
echo -e "${CYAN}╚════════════════════════════════════════════════════════════╝${NC}"
case "$DEVICE_STATE" in
adb-recovery)
echo -e " ${GREEN}Files are on the device. Flash them in TWRP now!${NC}"
;;
download-mode)
echo -e " TWRP is flashed (or ready to flash). Next:"
echo -e " 1. Boot into TWRP recovery"
echo -e " 2. Re-run: ${BOLD}./download-nethunter.sh${NC} (will skip downloads, push files)"
;;
*)
echo -e " 1. Resolve any failed downloads above"
echo -e " 2. Unlock bootloader (if not done already)"
echo -e " 3. Boot to Download Mode → re-run to flash TWRP"
echo -e " 4. Boot to TWRP → re-run to push files and see flash instructions"
;;
esac
echo -e "\n Source device config in future scripts:"
echo -e " ${BOLD}source $INSTALL_DIR/device-config.sh${NC}"
echo ""
echo -e "${GREEN}All files saved to: $INSTALL_DIR${NC}"#!/bin/bash
# ============================================================
# NetHunter Samsung Auto-Installer (v3 — Feb 2026)
# Supports: S10, S10+, S10e, S10 5G, S7, S9, Note 10 series
# Run on: Kali Linux (with phone connected via USB)
# Downloads to: the directory you run this script from
#
# IMPORTANT: Current Kali NetHunter Samsung S10/S20/Note images
# require LineageOS — NOT stock Samsung firmware.
#
# Features:
# • Auto-detects device via ADB (no manual model entry needed)
# • Skips files that are already downloaded and valid
# • Downloads LineageOS ROM automatically when required
# • Verifies SHA256 checksums for NetHunter images
# • Pushes files to device via ADB (if in TWRP)
# • Flashes TWRP via Heimdall (if in Download Mode)
# ============================================================
set +e # Don't exit on first error
# Colors
GREEN='\033[0;32m'
CYAN='\033[0;36m'
YELLOW='\033[1;33m'
RED='\033[0;31m'
BOLD='\033[1m'
NC='\033[0m'
# ============================================================
# Failure tracking — collected and reported at the end
# ============================================================
FAILED_DOWNLOADS=() # "LABEL|FILENAME|URL|MANUAL_URL|MIN_SIZE|HINT"
PASSED_DOWNLOADS=() # "LABEL|FILENAME|SIZE_HUMAN"
SKIPPED_DOWNLOADS=() # "LABEL|REASON"
# Minimum expected file sizes (bytes) for validation
MIN_TWRP=1048576 # 1 MB — real TWRP images are ~30-50 MB
MIN_MAGISK=1048576 # 1 MB — real Magisk APK is ~12 MB
MIN_NETHUNTER=10485760 # 10 MB — real NetHunter ZIPs are ~2.4 GB
MIN_NOVERITY=5120 # 5 KB — real zip is ~10-20 KB
MIN_LINEAGEOS=209715200 # 200 MB — real LOS ZIPs are ~700 MB+
# ============================================================
# Helper: validate a file by minimum size
# ============================================================
validate_download() {
local label="$1" file="$2" min_size="$3" url="$4" manual_url="$5" hint="${6:-}"
if [ ! -f "$file" ]; then
echo -e "${RED} ✗ $file not found — download failed.${NC}"
FAILED_DOWNLOADS+=("$label|$file|$url|$manual_url|$min_size|$hint")
return 1
fi
local actual_size
actual_size=$(stat -c%s "$file" 2>/dev/null || stat -f%z "$file" 2>/dev/null || echo 0)
local human_size
human_size=$(ls -lh "$file" | awk '{print $5}')
if [ "$actual_size" -lt "$min_size" ]; then
echo -e "${RED} ✗ $file is only $human_size — expected at least $(numfmt --to=iec $min_size 2>/dev/null || echo "$min_size bytes").${NC}"
echo -e "${RED} File is likely an error page or empty. Removing it.${NC}"
rm -f "$file"
FAILED_DOWNLOADS+=("$label|$file|$url|$manual_url|$min_size|$hint")
return 1
fi
echo -e "${GREEN} ✓ $file — $human_size — OK${NC}"
PASSED_DOWNLOADS+=("$label|$file|$human_size")
return 0
}
# ============================================================
# Helper: check if a valid file already exists → skip download
# ============================================================
already_have() {
local label="$1" file="$2" min_size="$3"
if [ -f "$file" ]; then
local actual_size
actual_size=$(stat -c%s "$file" 2>/dev/null || stat -f%z "$file" 2>/dev/null || echo 0)
if [ "$actual_size" -ge "$min_size" ]; then
local human_size
human_size=$(ls -lh "$file" | awk '{print $5}')
echo -e "${GREEN} ✓ Already have $file ($human_size) — skipping download.${NC}"
PASSED_DOWNLOADS+=("$label|$file|$human_size (cached)")
return 0
fi
fi
return 1
}
echo -e "${CYAN}╔════════════════════════════════════════════════════════════╗${NC}"
echo -e "${CYAN}║ NetHunter Samsung Auto-Installer (v3) ║${NC}"
echo -e "${CYAN}╚════════════════════════════════════════════════════════════╝${NC}"
# ============================================================
# Working directory = wherever the script is run from
# ============================================================
INSTALL_DIR="$(pwd)"
echo -e "\n${GREEN}[+] Working directory: $INSTALL_DIR${NC}"
# ============================================================
# Device Selection — auto-detect via ADB, fallback to manual
# ============================================================
echo -e "\n${CYAN}[Device Configuration]${NC}"
ADB_DETECTED=""
if command -v adb &>/dev/null; then
# Start ADB server quietly
adb start-server 2>/dev/null
# Try to read model from connected device
ADB_MODEL=$(adb shell getprop ro.product.model 2>/dev/null | tr -d '\r\n')
if [ -n "$ADB_MODEL" ]; then
ADB_DETECTED="$ADB_MODEL"
echo -e "${GREEN} 📱 Detected device via ADB: $ADB_MODEL${NC}"
# Try to get the full model number (SM-XXXXX)
ADB_SKU=$(adb shell getprop ro.product.name 2>/dev/null | tr -d '\r\n')
ADB_DEVICE=$(adb shell getprop ro.product.device 2>/dev/null | tr -d '\r\n')
ADB_MODELNUM=$(adb shell getprop ro.boot.em.model 2>/dev/null | tr -d '\r\n')
# Samsung stores full model in sku or modelnum
[ -z "$ADB_MODELNUM" ] && ADB_MODELNUM=$(adb shell getprop ro.product.vendor.model 2>/dev/null | tr -d '\r\n')
if [ -n "$ADB_MODELNUM" ]; then
echo -e "${GREEN} Model number: $ADB_MODELNUM${NC}"
echo -e "\nUse detected model ${GREEN}$ADB_MODELNUM${NC}? [Y/n] "
read -r USE_DETECTED
if [[ ! "$USE_DETECTED" =~ ^[Nn] ]]; then
MODEL_INPUT="$ADB_MODELNUM"
fi
else
echo -e "${YELLOW} Could not read model number. Device code: $ADB_DEVICE${NC}"
fi
else
echo -e "${YELLOW} No device detected via ADB (phone not connected or USB debugging off).${NC}"
fi
fi
if [ -z "$MODEL_INPUT" ]; then
echo -e "Enter your Samsung model number (e.g., SM-G973F, SM-G975F, SM-N975F)"
echo -e "Or press Enter to use default: ${GREEN}SM-G973F (Galaxy S10 Exynos)${NC}"
echo ""
read -p "Model number [SM-G973F]: " MODEL_INPUT
fi
MODEL=${MODEL_INPUT:-SM-G973F}
MODEL_UPPER=$(echo "$MODEL" | tr '[:lower:]' '[:upper:]')
echo -e "\n${GREEN}[+] Selected model: $MODEL_UPPER${NC}"
# Model → codename mapping
# Format: CODENAME|FRIENDLY_NAME|TWRP_DEVICE
declare -A MODEL_MAP=(
# Galaxy S10 series (Exynos)
["SM-G970F"]="beyond0lte|Galaxy S10e|samsunggalaxys10e"
["SM-G973F"]="beyond1lte|Galaxy S10|samsunggalaxys10"
["SM-G975F"]="beyond2lte|Galaxy S10+|samsunggalaxys10plus"
["SM-G977B"]="beyondx|Galaxy S10 5G|samsunggalaxys105g"
# Galaxy S10 series (Snapdragon — bootloader often locked!)
["SM-G970U"]="beyond0lte|Galaxy S10e (SD)|samsunggalaxys10e"
["SM-G973U"]="beyond1lte|Galaxy S10 (SD)|samsunggalaxys10"
["SM-G975U"]="beyond2lte|Galaxy S10+ (SD)|samsunggalaxys10plus"
# Galaxy S7 series (Samsung UI — OUI)
["SM-G935F"]="hero2lte|Galaxy S7 Edge|samsunggalaxys7"
["SM-G930F"]="herolte|Galaxy S7|samsunggalaxys7"
# Galaxy S9 series
["SM-G960F"]="starlte|Galaxy S9|samsunggalaxys9"
["SM-G965F"]="star2lte|Galaxy S9+|samsunggalaxys9plus"
# Galaxy Note series
["SM-N970F"]="d1|Galaxy Note 10|samsunggalaxynote10"
["SM-N975F"]="d2s|Galaxy Note 10+|samsunggalaxynote10plus"
)
# Parse model info
if [[ -v MODEL_MAP[$MODEL_UPPER] ]]; then
IFS='|' read -r CODENAME DEVICE_FRIENDLY TWRP_DEVICE <<< "${MODEL_MAP[$MODEL_UPPER]}"
echo -e "${GREEN} Device: $DEVICE_FRIENDLY${NC}"
echo -e "${GREEN} Codename: $CODENAME${NC}"
echo -e "${GREEN} TWRP key: $TWRP_DEVICE${NC}"
else
echo -e "${YELLOW}[!] Model not in database. Using S10 defaults.${NC}"
echo -e "${YELLOW} You may need to manually find correct TWRP/NetHunter files.${NC}"
CODENAME="beyond1lte"
DEVICE_FRIENDLY="Galaxy S10"
TWRP_DEVICE="samsunggalaxys10"
fi
# Detect chipset
if [[ "$MODEL_UPPER" =~ U[0-9]*$ ]]; then
CHIPSET="snapdragon"
echo -e "${YELLOW} Chipset: Snapdragon (US/China variant)${NC}"
echo -e "${RED} ⚠ Warning: Many Snapdragon Samsung devices have locked bootloaders!${NC}"
else
CHIPSET="exynos"
echo -e "${GREEN} Chipset: Exynos (International variant)${NC}"
fi
# Save device config
cat > "$INSTALL_DIR/device-config.sh" << EOF
# Device configuration — source this file in other scripts
export MODEL="$MODEL_UPPER"
export CODENAME="$CODENAME"
export DEVICE_FRIENDLY="$DEVICE_FRIENDLY"
export TWRP_DEVICE="$TWRP_DEVICE"
export CHIPSET="$CHIPSET"
EOF
echo -e "${GREEN}[+] Saved device config to: $INSTALL_DIR/device-config.sh${NC}"
# ============================================================
# 1. Install Heimdall & tools
# ============================================================
echo -e "\n${CYAN}[1/7] Installing Heimdall & tools...${NC}"
# Check which tools are already installed
MISSING_PKGS=()
for cmd in heimdall adb fastboot curl wget; do
if ! command -v "$cmd" &>/dev/null; then
MISSING_PKGS+=("$cmd")
fi
done
if [ ${#MISSING_PKGS[@]} -eq 0 ]; then
echo -e "${GREEN} All tools already installed — skipping.${NC}"
else
echo -e " Missing: ${MISSING_PKGS[*]}. Installing..."
sudo apt update --allow-releaseinfo-change 2>/dev/null || {
echo -e "${YELLOW}[!] apt update had some errors (mirror sync issues are common).${NC}"
echo -e "${YELLOW} Continuing anyway — packages may still install fine.${NC}"
}
sudo apt install -y heimdall-flash heimdall-flash-frontend adb fastboot curl wget || {
echo -e "${RED}[!] Package installation failed. Try:${NC}"
echo -e " sudo apt update --fix-missing"
echo -e " sudo apt install -y heimdall-flash adb fastboot curl wget"
echo -e "${YELLOW} Continuing with script...${NC}"
}
fi
# ============================================================
# 2. Download TWRP Recovery
# ============================================================
echo -e "\n${CYAN}[2/7] TWRP Recovery for $CODENAME...${NC}"
TWRP_DL_URL="https://dl.twrp.me/$CODENAME/"
TWRP_MANUAL_URL="https://twrp.me/samsung/$TWRP_DEVICE.html"
if already_have "TWRP Recovery" "twrp.img" "$MIN_TWRP"; then
: # already_have prints confirmation and adds to PASSED
else
echo -e " Checking twrp.me for latest $CODENAME image..."
TWRP_PAGE=$(curl -sL "$TWRP_DL_URL")
TWRP_FILE=$(echo "$TWRP_PAGE" | grep -oP 'twrp-[0-9.]+_[0-9]+-[0-9]+-'"$CODENAME"'\.img' | head -1)
if [ -n "$TWRP_FILE" ]; then
TWRP_FULL_URL="https://dl.twrp.me/$CODENAME/$TWRP_FILE"
echo -e "${GREEN} Found: $TWRP_FILE${NC}"
else
TWRP_FILE="twrp-3.7.0_12-0-$CODENAME.img"
TWRP_FULL_URL="https://dl.twrp.me/$CODENAME/$TWRP_FILE"
echo -e "${YELLOW} Using fallback: $TWRP_FILE${NC}"
fi
echo -e " Downloading twrp.img ..."
wget --content-disposition --header="Referer: $TWRP_DL_URL" \
-O twrp.img "$TWRP_FULL_URL" 2>&1 || true
validate_download "TWRP Recovery" "twrp.img" "$MIN_TWRP" \
"$TWRP_FULL_URL" "$TWRP_MANUAL_URL" \
"twrp.me blocks direct wget. Open the Manual URL in a browser and download manually."
fi
# ============================================================
# 3. Download Magisk (latest stable)
# ============================================================
echo -e "\n${CYAN}[3/7] Magisk...${NC}"
MAGISK_MANUAL_URL="https://github.com/topjohnwu/Magisk/releases"
# Check for any existing Magisk APK
EXISTING_MAGISK=$(ls -1 Magisk-*.apk 2>/dev/null | head -1)
if [ -n "$EXISTING_MAGISK" ] && already_have "Magisk" "$EXISTING_MAGISK" "$MIN_MAGISK"; then
MAGISK_FILE="$EXISTING_MAGISK"
else
MAGISK_URL=$(curl -sL https://api.github.com/repos/topjohnwu/Magisk/releases/latest \
| grep "browser_download_url.*Magisk-.*\.apk" | head -1 | cut -d '"' -f 4)
if [ -n "$MAGISK_URL" ]; then
MAGISK_FILE=$(basename "$MAGISK_URL")
echo -e " Downloading $MAGISK_FILE ..."
wget -O "$MAGISK_FILE" "$MAGISK_URL" 2>&1 || true
validate_download "Magisk" "$MAGISK_FILE" "$MIN_MAGISK" \
"$MAGISK_URL" "$MAGISK_MANUAL_URL" \
"Download the latest Magisk-vXX.X.apk from the GitHub releases page."
else
echo -e "${RED} ✗ Could not resolve Magisk URL from GitHub API.${NC}"
MAGISK_FILE="Magisk-latest.apk"
FAILED_DOWNLOADS+=("Magisk|$MAGISK_FILE|<GitHub API failed>|$MAGISK_MANUAL_URL|$MIN_MAGISK|Download the latest Magisk-vXX.X.apk from GitHub releases.")
fi
fi
# ============================================================
# 4. Download NetHunter (auto-detect from Kali mirror)
# ============================================================
echo -e "\n${CYAN}[4/7] NetHunter for $CODENAME...${NC}"
NH_MANUAL_URL="https://www.kali.org/get-kali/#kali-mobile"
NH_BASE="https://kali.download/nethunter-images/current/"
# Check for any existing NetHunter ZIP matching our codename
EXISTING_NH=$(ls -1 kali-nethunter-*"$CODENAME"*-full.zip 2>/dev/null | head -1)
if [ -n "$EXISTING_NH" ] && already_have "NetHunter" "$EXISTING_NH" "$MIN_NETHUNTER"; then
NH_FILE="$EXISTING_NH"
else
echo -e " Fetching available images from Kali mirror..."
NH_DIR_LISTING=$(curl -sL "$NH_BASE")
echo -e " Searching for codename: ${BOLD}$CODENAME${NC}"
NH_MATCHES=$(echo "$NH_DIR_LISTING" | grep -oP 'kali-nethunter-[0-9.]+-'"$CODENAME"'-[^"]+?-full\.zip' | sort -u)
if [ -n "$NH_MATCHES" ]; then
NH_COUNT=$(echo "$NH_MATCHES" | wc -l)
if [ "$NH_COUNT" -eq 1 ]; then
NH_FILE="$NH_MATCHES"
echo -e "${GREEN} Found: $NH_FILE${NC}"
else
echo -e "${GREEN} Found $NH_COUNT images for $CODENAME:${NC}"
i=1
while IFS= read -r f; do
echo -e " $i) $f"
i=$((i + 1))
done <<< "$NH_MATCHES"
read -p " Select image [1]: " NH_PICK
NH_PICK=${NH_PICK:-1}
NH_FILE=$(echo "$NH_MATCHES" | sed -n "${NH_PICK}p")
[ -z "$NH_FILE" ] && NH_FILE=$(echo "$NH_MATCHES" | head -1)
echo -e "${GREEN} Selected: $NH_FILE${NC}"
fi
NH_URL="${NH_BASE}${NH_FILE}"
else
echo -e "${YELLOW} No image found for codename '$CODENAME'.${NC}"
echo -e "${YELLOW} Searching for any 'beyond' (S10 family) image...${NC}"
NH_FALLBACK=$(echo "$NH_DIR_LISTING" | grep -oP 'kali-nethunter-[0-9.]+-beyond1lte-[^"]+?-full\.zip' | head -1)
if [ -n "$NH_FALLBACK" ]; then
NH_FILE="$NH_FALLBACK"
NH_URL="${NH_BASE}${NH_FILE}"
echo -e "${GREEN} Found fallback: $NH_FILE${NC}"
else
NH_FILE="kali-nethunter-$CODENAME-full.zip"
NH_URL=""
echo -e "${RED} ✗ No matching image found on the Kali mirror.${NC}"
fi
fi
# Detect LineageOS requirement
if [[ "$NH_FILE" == *"-los-"* ]]; then
LOS_VERSION=$(echo "$NH_FILE" | grep -oP '(?<=-los-)[a-z]+')
echo -e ""
echo -e "${YELLOW} ╔══════════════════════════════════════════════════════╗${NC}"
echo -e "${YELLOW} ║ ⚠ LINEAGEOS REQUIRED ║${NC}"
echo -e "${YELLOW} ╚══════════════════════════════════════════════════════╝${NC}"
echo -e "${YELLOW} This NetHunter image requires LineageOS (not stock Samsung).${NC}"
echo -e "${YELLOW} LineageOS version in this build: $LOS_VERSION${NC}"
echo -e "${YELLOW} LineageOS will be downloaded automatically in step 5.${NC}"
echo ""
echo "export REQUIRES_LINEAGEOS=\"true\"" >> "$INSTALL_DIR/device-config.sh"
echo "export LOS_VERSION=\"$LOS_VERSION\"" >> "$INSTALL_DIR/device-config.sh"
fi
if [ -n "$NH_URL" ]; then
echo -e " Downloading $NH_FILE (~2.4 GB — be patient)..."
wget -O "$NH_FILE" "$NH_URL" 2>&1 || true
validate_download "NetHunter" "$NH_FILE" "$MIN_NETHUNTER" \
"$NH_URL" "$NH_MANUAL_URL" \
"Go to kali.org/get-kali → Mobile → Samsung section. Look for $DEVICE_FRIENDLY."
else
FAILED_DOWNLOADS+=("NetHunter|$NH_FILE|<no URL found>|$NH_MANUAL_URL|$MIN_NETHUNTER|Go to kali.org/get-kali → Mobile → Samsung and download the image for $DEVICE_FRIENDLY.")
fi
fi
# ============================================================
# 4b. Verify SHA256 checksum for NetHunter
# ============================================================
if [ -f "$NH_FILE" ]; then
echo -e "\n${CYAN} Verifying SHA256 checksum for $NH_FILE...${NC}"
SHA_URL="${NH_BASE}SHA256SUMS"
SHA_SUMS=$(curl -sL "$SHA_URL")
if [ -n "$SHA_SUMS" ]; then
EXPECTED_SHA=$(echo "$SHA_SUMS" | grep "$NH_FILE" | awk '{print $1}')
if [ -n "$EXPECTED_SHA" ]; then
ACTUAL_SHA=$(sha256sum "$NH_FILE" | awk '{print $1}')
if [ "$EXPECTED_SHA" = "$ACTUAL_SHA" ]; then
echo -e "${GREEN} ✓ SHA256 checksum verified — file is authentic.${NC}"
else
echo -e "${RED} ✗ SHA256 MISMATCH!${NC}"
echo -e "${RED} Expected: $EXPECTED_SHA${NC}"
echo -e "${RED} Got: $ACTUAL_SHA${NC}"
echo -e "${RED} The file may be corrupted. Consider re-downloading.${NC}"
fi
else
echo -e "${YELLOW} No checksum found for $NH_FILE in SHA256SUMS.${NC}"
fi
else
echo -e "${YELLOW} Could not fetch SHA256SUMS from $SHA_URL${NC}"
fi
fi
# ============================================================
# 5. Download LineageOS (if required)
# ============================================================
REQUIRES_LOS=false
[[ "$NH_FILE" == *"-los-"* ]] && REQUIRES_LOS=true
if [ "$REQUIRES_LOS" = true ]; then
echo -e "\n${CYAN}[5/7] LineageOS ROM for $CODENAME...${NC}"
LOS_MANUAL_URL="https://wiki.lineageos.org/devices/$CODENAME"
# Check for existing LineageOS ZIP
EXISTING_LOS=$(ls -1 lineage-*"$CODENAME"*.zip 2>/dev/null | head -1)
if [ -n "$EXISTING_LOS" ] && already_have "LineageOS" "$EXISTING_LOS" "$MIN_LINEAGEOS"; then
LOS_FILE="$EXISTING_LOS"
else
# LineageOS provides an API to find the latest build
LOS_API="https://download.lineageos.org/api/v2/devices/$CODENAME"
echo -e " Querying LineageOS API for $CODENAME..."
LOS_JSON=$(curl -sL "$LOS_API")
# Try the download API for builds
LOS_BUILDS_API="https://download.lineageos.org/api/v2/devices/$CODENAME/builds"
LOS_BUILDS=$(curl -sL "$LOS_BUILDS_API")
# Extract the latest build download URL
LOS_DL_URL=$(echo "$LOS_BUILDS" | grep -oP '"url"\s*:\s*"\K[^"]+\.zip' | head -1)
if [ -n "$LOS_DL_URL" ]; then
LOS_FILE=$(basename "$LOS_DL_URL")
echo -e "${GREEN} Found: $LOS_FILE${NC}"
echo -e " Downloading LineageOS (~700 MB)..."
wget -O "$LOS_FILE" "$LOS_DL_URL" 2>&1 || true
validate_download "LineageOS" "$LOS_FILE" "$MIN_LINEAGEOS" \
"$LOS_DL_URL" "$LOS_MANUAL_URL" \
"Visit $LOS_MANUAL_URL and download the latest build."
else
# Fallback: try scraping the device page
echo -e "${YELLOW} API did not return a build. Checking download page...${NC}"
LOS_PAGE=$(curl -sL "https://download.lineageos.org/$CODENAME")
LOS_DL_URL=$(echo "$LOS_PAGE" | grep -oP 'https://[^"]+lineage-[0-9.]+-[0-9]+-nightly-'"$CODENAME"'-signed\.zip' | head -1)
if [ -n "$LOS_DL_URL" ]; then
LOS_FILE=$(basename "$LOS_DL_URL")
echo -e "${GREEN} Found: $LOS_FILE${NC}"
echo -e " Downloading LineageOS (~700 MB)..."
wget -O "$LOS_FILE" "$LOS_DL_URL" 2>&1 || true
validate_download "LineageOS" "$LOS_FILE" "$MIN_LINEAGEOS" \
"$LOS_DL_URL" "$LOS_MANUAL_URL" \
"Visit $LOS_MANUAL_URL and download the latest build."
else
LOS_FILE="lineage-$CODENAME.zip"
echo -e "${RED} ✗ Could not find LineageOS download for $CODENAME.${NC}"
FAILED_DOWNLOADS+=("LineageOS|$LOS_FILE|<auto-detect failed>|$LOS_MANUAL_URL|$MIN_LINEAGEOS|Visit $LOS_MANUAL_URL, download the latest nightly ZIP, and save it here.")
fi
fi
fi
else
echo -e "\n${CYAN}[5/7] LineageOS — not required for this build.${NC}"
SKIPPED_DOWNLOADS+=("LineageOS|Not required — NetHunter image uses stock firmware")
fi
# ============================================================
# 6. Download no-verity-opt-encrypt
# ============================================================
echo -e "\n${CYAN}[6/7] no-verity-opt-encrypt...${NC}"
NOVERITY_FILE="no-verity-opt-encrypt.zip"
NOVERITY_MANUAL_URL="https://github.com/AMP-1/no-verity-opt-encrypt"
NOVERITY_DOWNLOADED=false
if [ "$REQUIRES_LOS" = true ]; then
echo -e "${YELLOW} LineageOS build detected — no-verity is NOT needed.${NC}"
echo -e "${YELLOW} Skipping (only required for stock Samsung firmware).${NC}"
SKIPPED_DOWNLOADS+=("no-verity-opt-encrypt|Not needed for LineageOS builds")
elif already_have "no-verity-opt-encrypt" "$NOVERITY_FILE" "$MIN_NOVERITY"; then
NOVERITY_DOWNLOADED=true
else
NOVERITY_URLS=(
"https://artifacts.kali.org/images-nethunter/nethunter-installer/no-verity-opt-encrypt-6.1.zip"
"https://github.com/AMP-1/no-verity-opt-encrypt/releases/download/noencrypt/no-verity-opt-encrypt-6.1.zip"
)
for NOVERITY_URL in "${NOVERITY_URLS[@]}"; do
echo -e " Trying: $NOVERITY_URL"
wget -O "$NOVERITY_FILE" "$NOVERITY_URL" 2>&1 || true
if validate_download "no-verity-opt-encrypt" "$NOVERITY_FILE" "$MIN_NOVERITY" \
"$NOVERITY_URL" "$NOVERITY_MANUAL_URL" \
"Search GitHub for 'no-verity-opt-encrypt' and download the latest ZIP."; then
NOVERITY_DOWNLOADED=true
break
fi
done
fi
# ============================================================
# 7. Device actions — push files / flash TWRP
# ============================================================
echo -e "\n${CYAN}[7/7] Device actions...${NC}"
# Detect device state via ADB / Heimdall
DEVICE_STATE="none"
if command -v adb &>/dev/null; then
ADB_STATE=$(adb get-state 2>/dev/null | tr -d '\r\n')
case "$ADB_STATE" in
device) DEVICE_STATE="adb-normal" ;;
recovery) DEVICE_STATE="adb-recovery" ;;
sideload) DEVICE_STATE="adb-sideload" ;;
esac
fi
if [ "$DEVICE_STATE" = "none" ] && command -v heimdall &>/dev/null; then
if heimdall detect &>/dev/null; then
DEVICE_STATE="download-mode"
fi
fi
case "$DEVICE_STATE" in
adb-recovery)
# Phone is in TWRP recovery — push all files automatically
echo -e "${GREEN} 📱 Device detected in TWRP recovery mode!${NC}"
echo -e " Pushing files to /sdcard/NetHunter/ ..."
adb shell mkdir -p /sdcard/NetHunter 2>/dev/null
PUSH_FILES=()
# LineageOS first (must be flashed first)
if [ "$REQUIRES_LOS" = true ] && [ -f "${LOS_FILE:-}" ]; then
PUSH_FILES+=("$LOS_FILE")
fi
# Magisk
[ -f "${MAGISK_FILE:-}" ] && PUSH_FILES+=("$MAGISK_FILE")
# NetHunter
[ -f "${NH_FILE:-}" ] && PUSH_FILES+=("$NH_FILE")
# no-verity (only for stock)
if [ "$REQUIRES_LOS" = false ] && [ -f "$NOVERITY_FILE" ]; then
PUSH_FILES+=("$NOVERITY_FILE")
fi
if [ ${#PUSH_FILES[@]} -gt 0 ]; then
for pf in "${PUSH_FILES[@]}"; do
echo -e " Pushing $pf ..."
adb push "$pf" /sdcard/NetHunter/ 2>&1
if [ $? -eq 0 ]; then
echo -e "${GREEN} ✓ $pf pushed${NC}"
else
echo -e "${RED} ✗ Failed to push $pf${NC}"
fi
done
echo -e "\n${GREEN} Files on device:${NC}"
adb shell ls -la /sdcard/NetHunter/
else
echo -e "${YELLOW} No valid files to push.${NC}"
fi
echo -e "\n${GREEN} ✓ Files pushed! In TWRP:${NC}"
if [ "$REQUIRES_LOS" = true ]; then
echo -e " 1. Install → select LineageOS ZIP → Swipe to flash"
echo -e " 2. Install → select Magisk APK → Swipe to flash"
echo -e " 3. Install → select NetHunter ZIP → Swipe to flash"
echo -e " 4. Wipe → Advanced Wipe → Dalvik/ART Cache → Swipe"
echo -e " 5. Reboot → System"
else
echo -e " 1. Install → select Magisk APK → Swipe to flash"
echo -e " 2. Install → select no-verity ZIP → Swipe to flash"
echo -e " 3. Install → select NetHunter ZIP → Swipe to flash"
echo -e " 4. Wipe → Advanced Wipe → Dalvik/ART Cache → Swipe"
echo -e " 5. Reboot → System"
fi
;;
download-mode)
# Phone is in Download Mode — offer to flash TWRP
echo -e "${GREEN} 📱 Device detected in Download Mode!${NC}"
if [ -f "twrp.img" ]; then
echo -e ""
echo -e "${YELLOW} Flash TWRP recovery to this device now?${NC}"
echo -e "${YELLOW} This will write twrp.img to the RECOVERY partition.${NC}"
echo -e ""
read -p " Flash TWRP? [y/N]: " FLASH_CONFIRM
if [[ "$FLASH_CONFIRM" =~ ^[Yy] ]]; then
echo -e " Flashing TWRP..."
heimdall flash --RECOVERY twrp.img --no-reboot
if [ $? -eq 0 ]; then
echo -e "${GREEN} ✓ TWRP flashed successfully!${NC}"
echo -e ""
echo -e "${CYAN} Next: Boot into TWRP recovery:${NC}"
echo -e " 1. Disconnect USB"
echo -e " 2. Hold Volume Up + Bixby + Power"
echo -e " 3. Release when Samsung logo appears"
echo -e " 4. Re-run this script once in TWRP to auto-push files"
else
echo -e "${RED} ✗ Heimdall flash failed. Try manually:${NC}"
echo -e " heimdall flash --RECOVERY twrp.img --no-reboot"
fi
else
echo -e " Skipped. Flash manually later with:"
echo -e " heimdall flash --RECOVERY twrp.img --no-reboot"
fi
else
echo -e "${YELLOW} twrp.img not found — cannot flash.${NC}"
echo -e " Resolve TWRP download above, then re-run."
fi
;;
adb-normal)
echo -e "${YELLOW} 📱 Device detected in normal Android mode.${NC}"
echo -e " To continue installation:"
echo -e " 1. Boot to Download Mode (Vol Up+Down, plug USB) → re-run to flash TWRP"
echo -e " 2. Or boot to TWRP (Vol Up+Bixby+Power) → re-run to push files"
;;
*)
echo -e "${YELLOW} No device detected via ADB or Heimdall.${NC}"
echo -e " Connect your phone and re-run this script to:"
echo -e " • Download Mode → auto-flash TWRP"
echo -e " • TWRP Recovery → auto-push all ZIPs to device"
;;
esac
# ============================================================
# Final Report
# ============================================================
echo -e "\n${CYAN}════════════════════════════════════════════════════════════${NC}"
echo -e "\n${GREEN}Files in $INSTALL_DIR:${NC}"
ls -lh "$INSTALL_DIR"
echo ""
echo -e "${CYAN}╔════════════════════════════════════════════════════════════╗${NC}"
echo -e "${CYAN}║ DOWNLOAD REPORT ║${NC}"
echo -e "${CYAN}╚════════════════════════════════════════════════════════════╝${NC}"
echo -e "\n${GREEN}Device:${NC} $MODEL_UPPER ($DEVICE_FRIENDLY)"
echo -e "${GREEN}Codename:${NC} $CODENAME"
echo -e "${GREEN}Chipset:${NC} $CHIPSET"
echo -e "${GREEN}Directory:${NC} $INSTALL_DIR"
# --- Successful downloads ---
if [ ${#PASSED_DOWNLOADS[@]} -gt 0 ]; then
echo -e "\n${GREEN}✓ Successful (${#PASSED_DOWNLOADS[@]}):${NC}"
for entry in "${PASSED_DOWNLOADS[@]}"; do
IFS='|' read -r label file size <<< "$entry"
echo -e " ${GREEN}✓${NC} $label → $file ($size)"
done
fi
# --- Skipped downloads ---
if [ ${#SKIPPED_DOWNLOADS[@]} -gt 0 ]; then
echo -e "\n${YELLOW}⊘ Skipped (${#SKIPPED_DOWNLOADS[@]}):${NC}"
for entry in "${SKIPPED_DOWNLOADS[@]}"; do
IFS='|' read -r label reason <<< "$entry"
echo -e " ${YELLOW}⊘${NC} $label — $reason"
done
fi
# --- Failed downloads ---
CLEAN_FAILED=()
for entry in "${FAILED_DOWNLOADS[@]}"; do
[ -n "$entry" ] && CLEAN_FAILED+=("$entry")
done
FAILED_DOWNLOADS=("${CLEAN_FAILED[@]}")
if [ ${#FAILED_DOWNLOADS[@]} -gt 0 ]; then
echo -e "\n${RED}╔════════════════════════════════════════════════════════════╗${NC}"
echo -e "${RED}║ ✗ FAILED (${#FAILED_DOWNLOADS[@]}) — Manual action required ║${NC}"
echo -e "${RED}╚════════════════════════════════════════════════════════════╝${NC}"
echo ""
for entry in "${FAILED_DOWNLOADS[@]}"; do
IFS='|' read -r label file url manual min hint <<< "$entry"
echo -e " ${RED}✗ $label${NC}"
echo -e " File needed : ${BOLD}$file${NC}"
echo -e " Tried URL : $url"
echo -e " Manual URL : ${CYAN}$manual${NC}"
echo -e " Save to : ${BOLD}$INSTALL_DIR/$file${NC}"
echo -e " Min size : $(numfmt --to=iec $min 2>/dev/null || echo "$min bytes")"
[ -n "$hint" ] && echo -e " ${YELLOW}Hint: $hint${NC}"
echo ""
done
echo -e "${YELLOW}TIP: Some sites block direct wget downloads.${NC}"
echo -e "${YELLOW} Open the Manual URL in a browser, download the file,${NC}"
echo -e "${YELLOW} then copy/move it into: $INSTALL_DIR/${NC}"
else
echo -e "\n${GREEN}All downloads completed successfully! ✓${NC}"
fi
# Next steps (context-aware)
echo -e "\n${CYAN}╔════════════════════════════════════════════════════════════╗${NC}"
echo -e "${CYAN}║ WHAT TO DO NEXT ║${NC}"
echo -e "${CYAN}╚════════════════════════════════════════════════════════════╝${NC}"
case "$DEVICE_STATE" in
adb-recovery)
echo -e " ${GREEN}Files are on the device. Flash them in TWRP now!${NC}"
;;
download-mode)
echo -e " TWRP is flashed (or ready to flash). Next:"
echo -e " 1. Boot into TWRP recovery"
echo -e " 2. Re-run: ${BOLD}./download-nethunter.sh${NC} (will skip downloads, push files)"
;;
*)
echo -e " 1. Resolve any failed downloads above"
echo -e " 2. Unlock bootloader (if not done already)"
echo -e " 3. Boot to Download Mode → re-run to flash TWRP"
echo -e " 4. Boot to TWRP → re-run to push files and see flash instructions"
;;
esac
echo -e "\n Source device config in future scripts:"
echo -e " ${BOLD}source $INSTALL_DIR/device-config.sh${NC}"
echo ""
echo -e "${GREEN}All files saved to: $INSTALL_DIR${NC}"
Save as download-nethunter.sh, then run: chmod +x download-nethunter.sh && ./download-nethunter.sh
Re-run friendly: The script skips files already downloaded. Connect your phone in different modes to unlock new actions — Download Mode auto-flashes TWRP, TWRP recovery auto-pushes all ZIPs.
2. Unlock the Bootloader
Knox Will Be Tripped
Step 2.1: Enable Developer Options
- Go to Settings → About Phone → Software Information
- Tap Build Number 7 times until you see "Developer mode enabled"
- Go back to Settings → Developer Options
- Enable OEM Unlocking (if grayed out, see troubleshooting)
- Enable USB Debugging
Step 2.2: Boot to Download Mode
- Power off the phone completely
- Connect USB cable to your Kali machine (leave phone end disconnected)
- Hold Volume Up + Volume Down simultaneously
- While holding both buttons, connect the USB cable to the phone
- Keep holding until you see the warning screen
- Press Volume Up to continue to Download Mode
Alternative Method
Step 2.3: Unlock via Heimdall
# Verify device is detected
heimdall detect
# If detected, you'll see:
# Device detected
# Unlock bootloader (this triggers factory reset)
heimdall flash --no-reboot# Verify device is detected
heimdall detect
# If detected, you'll see:
# Device detected
# Unlock bootloader (this triggers factory reset)
heimdall flash --no-rebootAfter unlocking, the phone will factory reset. Complete initial Android setup (skip Google account for now), then re-enable Developer Options and USB Debugging.
3. Flash TWRP Recovery
Step 3.1: Download Correct TWRP
Go to twrp.me/Devices and search for your exact model:
| Device | Model Number | Codename |
|---|---|---|
| Galaxy S10 (Exynos) | SM-G973F | beyond1lte |
| Galaxy S10+ (Exynos) | SM-G975F | beyond2lte |
| Galaxy S10e (Exynos) | SM-G970F | beyond0lte |
| Galaxy S10 (Snapdragon) | SM-G973U | beyond1q |
Exynos vs Snapdragon
Step 3.2: Flash TWRP with Heimdall
# Navigate to your download folder
cd ~/nethunter-install
# Rename TWRP file for convenience (adjust filename as needed)
mv twrp-*.img twrp.img
# Boot phone to Download Mode (Vol Up + Vol Down + USB)
# Verify device detected
heimdall detect
# Flash TWRP to recovery partition
heimdall flash --RECOVERY twrp.img --no-reboot# Navigate to your download folder
cd ~/nethunter-install
# Rename TWRP file for convenience (adjust filename as needed)
mv twrp-*.img twrp.img
# Boot phone to Download Mode (Vol Up + Vol Down + USB)
# Verify device detected
heimdall detect
# Flash TWRP to recovery partition
heimdall flash --RECOVERY twrp.img --no-rebootStep 3.3: Boot into TWRP
Important: You must boot directly into TWRP before booting Android, or Samsung will overwrite recovery.
- Disconnect USB cable
- Hold Volume Down + Bixby + Power for 10+ seconds (force restart)
- When screen goes black, immediately switch to Volume Up + Bixby + Power
- Keep holding until you see the TWRP logo
- Swipe to allow modifications when prompted
TWRP Password Prompt
4. Flash NetHunter
Step 4.1: Copy Files to Device
LineageOS Must Be Flashed First
With TWRP running, your phone mounts as a storage device. Push the required ZIPs:
# Connect phone (in TWRP) via USB
# Create folder on device
adb shell mkdir -p /sdcard/NetHunter
# Push all required files
adb push lineage-*.zip /sdcard/NetHunter/ # LineageOS ROM
adb push Magisk-*.apk /sdcard/NetHunter/
adb push kali-nethunter-*.zip /sdcard/NetHunter/
# adb push no-verity-*.zip /sdcard/NetHunter/ # Only if using stock Samsung (not LOS)
# Verify files are there
adb shell ls -la /sdcard/NetHunter/# Connect phone (in TWRP) via USB
# Create folder on device
adb shell mkdir -p /sdcard/NetHunter
# Push all required files
adb push lineage-*.zip /sdcard/NetHunter/ # LineageOS ROM
adb push Magisk-*.apk /sdcard/NetHunter/
adb push kali-nethunter-*.zip /sdcard/NetHunter/
# adb push no-verity-*.zip /sdcard/NetHunter/ # Only if using stock Samsung (not LOS)
# Verify files are there
adb shell ls -la /sdcard/NetHunter/Step 4.2: Install Magisk (Root)
- In TWRP, go to Install
- Navigate to /sdcard/NetHunter/
- Select Magisk-*.apk (yes, the APK — TWRP will handle it)
- Swipe to confirm flash
- Wait for completion (do NOT reboot yet)
Step 4.3: Install no-verity-opt-encrypt (Stock Samsung Only)
Skip This Step on LineageOS
Only for stock Samsung firmware:
- In TWRP, tap the back arrow
- Go to Install again
- Select no-verity-opt-encrypt-*.zip
- Swipe to confirm flash
- Wait for completion (do NOT reboot yet)
Why This Module?
Step 4.4: Install NetHunter
- In TWRP, go to Install
- Select nethunter-*.zip
- Swipe to confirm flash
- This takes 5-10 minutes — be patient
- When complete, tap Reboot System
First Boot Takes Time
5. First Boot & Configuration
Step 5.1: Initial Android Setup
- Complete Android setup wizard (WiFi, skip Google account for now)
- Open the app drawer — you should see NetHunter, NetHunter Store, NetHunter Terminal, and Magisk
- Open Magisk and verify it shows "Installed" with a version number
Step 5.2: Grant Root Access
- Open NetHunter app
- A Magisk superuser prompt will appear — tap Grant
- The app will initialize and download additional components
- Open NetHunter Terminal — grant root when prompted
Step 5.3: Verify Installation
# Open NetHunter Terminal and run:
# Check root access
su -c id
# Should show: uid=0(root)
# Check NetHunter chroot
nethunter
# Should drop you into Kali shell
# Verify Kali
cat /etc/os-release
# Should show Kali Linux
# Check kernel features
nethunter -c "ls /lib/modules"
# Should list kernel modules
# Exit chroot
exit# Open NetHunter Terminal and run:
# Check root access
su -c id
# Should show: uid=0(root)
# Check NetHunter chroot
nethunter
# Should drop you into Kali shell
# Verify Kali
cat /etc/os-release
# Should show Kali Linux
# Check kernel features
nethunter -c "ls /lib/modules"
# Should list kernel modules
# Exit chroot
exit6. Post-Install Setup
Update Kali Chroot
# Enter NetHunter chroot
nethunter
# Update package lists and upgrade
apt update && apt full-upgrade -y
# Install additional tools
apt install -y seclists wordlists nmap sqlmap
# Clean up
apt autoremove -y && apt autoclean
# Exit chroot
exit# Enter NetHunter chroot
nethunter
# Update package lists and upgrade
apt update && apt full-upgrade -y
# Install additional tools
apt install -y seclists wordlists nmap sqlmap
# Clean up
apt autoremove -y && apt autoclean
# Exit chroot
exitConfigure Kali Services
NetHunter includes a service manager. Open the NetHunter app and navigate to:
SSH Server
Enable to SSH into your phone from another machine. Change default credentials!
Apache + MySQL
Host phishing pages or run local web tools.
VNC Server
Get a full Kali desktop on your phone.
Bluetooth Arsenal
Bluetooth scanning and attacks.
External WiFi Adapter Setup
For packet injection and monitor mode, connect a supported external USB WiFi adapter via OTG:
# Recommended adapters with NetHunter kernel support:
# - Alfa AWUS036ACH (RTL8812AU)
# - Alfa AWUS036NHA (Atheros AR9271)
# - Panda PAU05 (RT5372)
# Connect adapter via OTG cable, then:
nethunter
# Check interface
ip a
# Should show wlan1 or similar
# Enable monitor mode (using NetHunter app is easier)
airmon-ng check kill
airmon-ng start wlan1
# Verify monitor mode
iwconfig
# Should show wlan1mon in Monitor mode# Recommended adapters with NetHunter kernel support:
# - Alfa AWUS036ACH (RTL8812AU)
# - Alfa AWUS036NHA (Atheros AR9271)
# - Panda PAU05 (RT5372)
# Connect adapter via OTG cable, then:
nethunter
# Check interface
ip a
# Should show wlan1 or similar
# Enable monitor mode (using NetHunter app is easier)
airmon-ng check kill
airmon-ng start wlan1
# Verify monitor mode
iwconfig
# Should show wlan1mon in Monitor modeHID Attacks (Keyboard Emulation)
One of NetHunter's killer features — your phone acts as a USB keyboard to type payloads:
- Open NetHunter → HID Attacks
- Choose a payload (e.g., PowerShell reverse shell, Windows backdoor)
- Connect phone to target Windows PC via USB
- Tap Execute — phone types the payload automatically
Authorized Use Only
7. Troubleshooting
OEM Unlock is grayed out
Connect to WiFi and leave the phone on for 7 days (Samsung's waiting period for new devices). If it's a carrier-locked phone, you may need to unlock from carrier first.
Heimdall doesn't detect device
# Add udev rules for Samsung
sudo tee /etc/udev/rules.d/51-android.rules << 'EOF'
SUBSYSTEM=="usb", ATTR{idVendor}=="04e8", MODE="0666", GROUP="plugdev"
EOF
# Reload udev
sudo udevadm control --reload-rules
sudo udevadm trigger
# Reconnect device and try again# Add udev rules for Samsung
sudo tee /etc/udev/rules.d/51-android.rules << 'EOF'
SUBSYSTEM=="usb", ATTR{idVendor}=="04e8", MODE="0666", GROUP="plugdev"
EOF
# Reload udev
sudo udevadm control --reload-rules
sudo udevadm trigger
# Reconnect device and try againBoot loop after flashing
Boot back into TWRP (Vol Up + Bixby + Power) and:
- Go to Wipe → Format Data → type "yes"
- Re-flash Magisk and NetHunter in that order (add no-verity only if on stock Samsung, not LineageOS)
- If still failing, try an older NetHunter version
NetHunter app shows "Chroot not found"
The chroot may not have extracted. Open NetHunter → Kali Chroot Manager and tap Install Kali Chroot. Choose "Full" for all tools or "Minimal" for faster install.
WiFi injection not working
Your device may not have a custom kernel with injection patches. Check the NetHunter supported devices page. If not listed, you'll need NetHunter Lite with an external adapter.
8. Notes for Other Devices
OnePlus Devices
Excellent NetHunter support. Bootloader unlock is simpler (Settings → Developer Options → OEM Unlock is instant).
- • Use fastboot instead of Heimdall
- •
fastboot flash recovery twrp.img - • OnePlus 7/8/9 series have full kernel support
Google Pixel
Great development support but limited NetHunter kernel features.
- • Use fastboot
- •
fastboot flashing unlock - • NetHunter Lite recommended
Xiaomi Devices
Bootloader unlock requires Mi Unlock tool and a waiting period (72h-30d).
- • Apply via Mi Unlock
- • Some models have excellent kernel support
- • Check XDA forums for device-specific guides
Snapdragon Samsung
US carrier variants often have locked bootloaders that cannot be unlocked.
- • Check model: SM-G973U (locked) vs SM-G973U1 (unlocked)
- • U1 (unlocked) variants may work
- • Consider NetHunter Rootless as fallback
Best Devices for NetHunter (2026)
- OnePlus 7 Pro / 7T / 8T — Best overall support, easy unlock, full features
- Samsung Galaxy S10/S20 (Exynos) — Great hardware, HID support
- Nexus 6P / Nexus 5 — Classic, well-tested, cheap on eBay
- Xiaomi Poco F1 — Budget option with good support
Quick Reference Card
S10 Boot Key Combos
| Download Mode | Vol Up + Vol Down + USB cable |
| Recovery (TWRP) | Vol Up + Bixby + Power (hold until logo) |
| Force Restart | Vol Down + Power (hold 10+ sec) |
| Safe Mode | Hold Vol Down during boot logo |
# === NETHUNTER QUICK COMMANDS ===
# Enter Kali chroot
nethunter
# Start Kali chroot with command
nethunter -c "nmap -sV 192.168.1.1"
# Start KeX (VNC desktop)
nethunter kex &
# Check root
su -c id
# WiFi monitor mode (in chroot)
airmon-ng start wlan1
# Packet capture
airodump-ng wlan1mon
# === HEIMDALL COMMANDS ===
heimdall detect
heimdall flash --RECOVERY twrp.img --no-reboot
heimdall print-pit # Print partition table# === NETHUNTER QUICK COMMANDS ===
# Enter Kali chroot
nethunter
# Start Kali chroot with command
nethunter -c "nmap -sV 192.168.1.1"
# Start KeX (VNC desktop)
nethunter kex &
# Check root
su -c id
# WiFi monitor mode (in chroot)
airmon-ng start wlan1
# Packet capture
airodump-ng wlan1mon
# === HEIMDALL COMMANDS ===
heimdall detect
heimdall flash --RECOVERY twrp.img --no-reboot
heimdall print-pit # Print partition table