Kali NetHunter Installation Guide
Install full Kali NetHunter on an unlockable Android device with a Samsung Galaxy S10 Exynos path as the primary example. This refresh prioritizes version checks, device fit, and recovery checkpoints so you can stop early if your hardware or firmware path is wrong.
Read This First
- This will wipe your device. Back up everything first.
- Samsung Galaxy S10-family NetHunter builds currently require a matching LineageOS-based path, not stock Samsung firmware.
- Exynos Samsung models are the intended target. Many Snapdragon carrier variants still cannot be unlocked reliably.
- Use a Linux machine for flashing, a known-good data cable, and a direct motherboard USB port instead of a hub or dock.
- Set aside 2-4 hours, 4+ GB of downloads, and at least 60% battery on both the phone and laptop.
- Unlocking the bootloader permanently trips Knox and may disable Samsung Pay, Secure Folder, and some enterprise controls.
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 — beyond1lte currently lists LineageOS 23.2 (Android 16)
- TWRP: twrp.me/samsung/samsunggalaxys10.html
- NetHunter: kali.org/get-kali/#kali-mobile — match the exact codename and Android generation in the filename, such as los-sixteen or A16
- Magisk: github.com/topjohnwu/Magisk/releases
Freshness Rule for This Page
Use This Guide
You have an unlockable device, you are comfortable wiping it, and you want full NetHunter with root and recovery-based flashing.
Choose Lite Instead
Your phone can be rooted but does not have a practical custom-kernel path. You still want the app, Kali container, and post-exploitation tooling.
Choose Rootless Instead
Your bootloader is locked, your model is a carrier Snapdragon Samsung, or you only need portable Kali tooling without kernel features.
Pre-Flight Checks
- Confirm your exact model number in Settings → About Phone, then map it to the correct codename before downloading anything.
- Confirm your NetHunter filename and your LineageOS build target the same Android generation.
- Test adb devices and heimdall detect before you start flashing so cable and driver problems show up early.
- Download everything to local disk first. Do not depend on live mirror scraping while the phone is already in recovery mode.
- Keep a manual recovery path ready: official LineageOS install page, official NetHunter download page, and a fallback flashing machine if Heimdall misbehaves.
In This Guide
1. Prerequisites & Downloads
Supported Devices
NetHunter has three practical installation paths. Pick the one that matches your hardware reality before you collect files:
NetHunter (Full)
Requires an unlockable device and an officially supported kernel path. This is the only route that gives you the full HID, BadUSB, and wireless feature set.
Use this page only if this is your target state.
NetHunter Rootless
No root required. Runs in Termux and avoids bootloader, recovery, and firmware changes.
Best fallback when your bootloader path is blocked.
NetHunter Lite
Rooted device with no custom-kernel dependency. App + Kali container, but not the full hardware feature set.
Useful when full NetHunter support is incomplete for your model.
This Guide Covers Full NetHunter
LineageOS Required for Samsung Devices
Expected Time and Failure Points
What You'll Need
Hardware
- ☑️ Samsung Galaxy S10 (SM-G973F/Exynos) — or your supported device
- ☑️ Short, known-good USB-C data cable connected directly to the computer
- ☑️ Linux PC/laptop (Kali Linux recommended)
- ☑️ microSD card (optional, for storing images/wordlists)
- ☑️ USB OTG adapter (for external WiFi adapters later)
Cable Quality Matters
Software Downloads
Download all files to a folder on your Kali machine (e.g., ~/nethunter-install/).
| File | Source | Notes |
|---|---|---|
| Odin (Linux: Heimdall) | GitHub | Use Heimdall on Linux first; keep a fallback flashing option ready if USB detection stays unreliable |
| 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) |
Optional Helper Script for Samsung Galaxy Devices
Use this only as a convenience layer after you understand the manual path. It can save time, but vendor mirrors, API responses, and file naming conventions change regularly, so you still need to verify every file yourself.
Best use: download files to disk before the phone is in recovery. If any URL lookup fails, fall back to the official download links above instead of forcing the script to work.
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 on the Device
- From the Download Mode warning screen, long-press Volume Up to open the bootloader unlock prompt.
- Read the warning carefully, then confirm the unlock on the phone itself.
- Wait for the phone to wipe and reboot automatically.
- Complete basic Android setup again, then return to Developer Options and re-enable USB Debugging.
Do Not Use Heimdall to Unlock
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
If Heimdall Is Unstable
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
Recommended Flash Order
- Flash LineageOS if your NetHunter build requires it.
- If recovery cannot mount storage afterward, format data and reboot back into recovery once.
- Flash Magisk.
- Flash NetHunter.
- Flash no-verity only when you are intentionally staying on stock Samsung firmware.
- Wipe Dalvik/ART cache, then reboot system and wait through the long first boot.
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
Healthy First Boot
Boot animation runs for several minutes, then Android setup appears and Magisk plus NetHunter apps are present.
Recovery Loop
Usually points to a bad recovery handoff, storage mount issue, or an incomplete ROM flash.
Samsung Logo Loop
Usually means version mismatch, encryption not cleared, or the wrong flash order. Go back to recovery instead of repeatedly hard rebooting.
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
First eliminate transport issues: swap the cable, move to a direct USB port, disconnect hubs, and boot the phone fully back into Download Mode before changing packages.
# 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 againSamsung boots back to stock recovery
That usually means Android booted once before you entered TWRP. Re-flash recovery, then boot directly into TWRP using the hardware key combo without allowing a normal Android boot in between.
Boot loop after flashing
Boot back into TWRP (Vol Up + Bixby + Power) and:
- Go to Wipe → Format Data → type "yes"
- Verify your ROM and NetHunter filenames target the same Android generation, then re-flash in the correct order
- Re-flash Magisk and NetHunter in that order (add no-verity only if on stock Samsung, not LineageOS)
- If still failing, return to the official LineageOS install guide for a clean ROM flash before retrying NetHunter
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.
Magisk opens as a stub or root prompts never appear
Launch Magisk once after first boot and allow it to complete any in-app environment setup it requests. If root is still missing, return to recovery and re-flash the current Magisk APK before troubleshooting NetHunter itself.
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
Use Vendor-Specific Unlock Paths
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