#!/usr/bin/env bash
#
# =============================================================================
#  Fenrir.pro : script de collecte forensique pour Linux
#  Version 1.1
# =============================================================================
#
#  A QUOI SERT CE SCRIPT ?
#  Il collecte, sur VOTRE machine, les traces utiles a l'analyse d'une
#  suspicion d'intrusion (programmes actifs, connexions reseau, journaux,
#  mecanismes de persistance, etc.), puis les regroupe dans une archive
#  scellee par une empreinte SHA-256 a transmettre a votre analyste Fenrir.
#
#  IL EST CONCU POUR ETRE SUR :
#   - Il ne supprime rien et ne modifie pas la configuration du systeme.
#   - Il se contente de LIRE des informations et de les COPIER dans un dossier.
#   - Il suit l'ordre de volatilite (traces fragiles d'abord) recommande par
#     le RFC 3227 et les fiches reflexes du CERT-FR / ANSSI.
#
#  BONNE PRATIQUE AVANT DE L'EXECUTER :
#   1. Telechargez-le depuis https://fenrir.pro/collection_script.sh
#   2. Ouvrez-le et relisez-le (c'est un simple fichier texte).
#   3. Lancez-le ensuite, de preference PAS via "curl | bash".
#
#      chmod +x collection_script.sh
#      ./collection_script.sh
#
#  Options :
#   -a, --auto   Mode automatique (aucune question, enchaine toutes les etapes)
#   -s, --step   Mode pas a pas (confirmation avant chaque etape)
#   -h, --help   Affiche cette aide
#
#  IMPORTANT : n'eteignez pas et ne redemarrez pas la machine avant d'avoir
#  termine, et ne lancez pas d'antivirus ou de nettoyage pour l'instant.
# =============================================================================

VERSION="1.1"

# ------------------------------- Presentation --------------------------------
if [ -t 1 ] && [ -z "${NO_COLOR:-}" ]; then
  BOLD=$'\033[1m'; DIM=$'\033[2m'; RESET=$'\033[0m'
  RED=$'\033[31m'; GREEN=$'\033[32m'; YELLOW=$'\033[33m'
  BLUE=$'\033[34m'; CYAN=$'\033[36m'
else
  BOLD=""; DIM=""; RESET=""; RED=""; GREEN=""; YELLOW=""; BLUE=""; CYAN=""
fi

hr(){ printf "${DIM}%s${RESET}\n" "----------------------------------------------------------------------"; }
title(){ printf "\n${BOLD}${CYAN}%s${RESET}\n" "$1"; }
info(){ printf "%s\n" "$1"; }
ok(){ printf "  ${GREEN}ok${RESET}\n"; }
skip(){ printf "  ${YELLOW}ignore${RESET}\n"; }

# --------------------------------- Options -----------------------------------
MODE="ask"
for arg in "$@"; do
  case "$arg" in
    -a|--auto) MODE="auto" ;;
    -s|--step) MODE="step" ;;
    -h|--help)
      sed -n '2,45p' "$0" | sed 's/^#\{0,1\} \{0,1\}//'
      exit 0 ;;
    *) printf "Option inconnue : %s\n" "$arg"; exit 2 ;;
  esac
done

# Si l'entree n'est pas un terminal (script pipe), on bascule en automatique
# pour ne pas rester bloque sur une question.
TTY="/dev/tty"
if [ ! -r "$TTY" ] || [ ! -t 0 ]; then
  [ "$MODE" = "ask" ] && MODE="auto"
  [ "$MODE" = "step" ] && MODE="auto"
fi

ask_yes(){ # ask_yes "question"  -> 0 si oui
  local q="$1" r
  if [ "$MODE" = "auto" ]; then return 0; fi
  printf "%s ${DIM}[O/n]${RESET} " "$q" > "$TTY"
  read -r r < "$TTY" || return 0
  case "$r" in n|N|non|NON|Non) return 1 ;; *) return 0 ;; esac
}

ask_val(){ # ask_val "question" "defaut" -> echo reponse
  local q="$1" d="$2" r
  if [ "$MODE" = "auto" ]; then printf "%s" "$d"; return; fi
  printf "%s " "$q" > "$TTY"
  read -r r < "$TTY" || r=""
  [ -z "$r" ] && r="$d"
  printf "%s" "$r"
}

# --------------------------- Outils disponibles ------------------------------
have(){ command -v "$1" >/dev/null 2>&1; }

if have sha256sum; then HASH="sha256sum"
elif have shasum; then HASH="shasum -a 256"
else HASH=""; fi

# ------------------------------ Introduction ---------------------------------
clear 2>/dev/null || true
hr
printf "${BOLD}  Fenrir.pro : collecte forensique Linux  (v%s)${RESET}\n" "$VERSION"
hr
cat <<'TXT'

Ce script va rassembler des informations techniques sur cet ordinateur afin
d'aider votre analyste a determiner s'il a ete compromis, et comment.

Il NE SUPPRIME RIEN et NE MODIFIE PAS votre systeme : il lit des informations
et les copie dans un dossier, puis cree une archive a nous transmettre.
Par transparence, chaque commande executee est affichee a l'ecran.

Rappels importants :
  - N'eteignez pas et ne redemarrez pas la machine pendant la collecte.
  - Ne lancez pas d'antivirus ni de nettoyage pour l'instant.
  - Si vous pensez qu'une intrusion est en cours, coupez le reseau (Wi-Fi ou
    cable) AVANT de continuer : la collecte fonctionne aussi hors ligne.

TXT

if ! ask_yes "Voulez-vous demarrer la collecte maintenant ?"; then
  info "Collecte annulee. Aucun fichier n'a ete cree."
  exit 0
fi

if [ "$MODE" = "ask" ]; then
  info ""
  info "Deux modes possibles :"
  info "  1) Automatique : le script enchaine toutes les etapes."
  info "  2) Pas a pas   : le script vous demande de confirmer chaque etape."
  choice="$(ask_val 'Votre choix [1/2] (defaut 1) :' '1')"
  case "$choice" in 2) MODE="step" ;; *) MODE="auto" ;; esac
fi

# ------------------------- Dossier de collecte -------------------------------
# Nom fixe, identique a la procedure manuelle du guide :
#   dossier   ~/fenrir_collecte
#   archive   ~/fenrir_collecte.tar.gz
#   empreinte ~/fenrir_collecte.tar.gz.sha256
STAMP="$(date -u +%Y%m%dT%H%M%SZ)"
BASE="fenrir_collecte"
DEST="$HOME/$BASE"
ARCHIVE="$HOME/${BASE}.tar.gz"

# Pour ne pas melanger deux collectes tout en gardant un nom fixe, on met de
# cote une eventuelle collecte precedente portant le meme nom.
if [ -e "$DEST" ]; then
  mv "$DEST" "${DEST}_precedent_${STAMP}" 2>/dev/null \
    && info "Une collecte precedente a ete renommee : ${DEST}_precedent_${STAMP}"
fi
[ -e "$ARCHIVE" ] && mv "$ARCHIVE" "${DEST}_precedent_${STAMP}.tar.gz" 2>/dev/null
[ -e "${ARCHIVE}.sha256" ] && mv "${ARCHIVE}.sha256" "${DEST}_precedent_${STAMP}.tar.gz.sha256" 2>/dev/null

mkdir -p "$DEST" || { printf "${RED}Impossible de creer le dossier de collecte.${RESET}\n"; exit 1; }
LOG="$DEST/collecte.log"
: > "$LOG"

log(){ printf '%s\n' "$*" >> "$LOG"; }
log "Fenrir collecte v$VERSION"
log "Debut (UTC) : $(date -u '+%Y-%m-%d %H:%M:%S %Z')"
log "Debut (local) : $(date '+%Y-%m-%d %H:%M:%S %Z')"

# collect "<libelle>" "<commande shell>"
# Le libelle est affiche, puis la commande reellement executee (transparence),
# puis son statut. La commande gere elle-meme sa redirection de sortie.
collect(){
  local label="$1"; local cmd="$2"
  printf "  ${BOLD}• %s${RESET}\n" "$label"
  printf "      ${DIM}\$ %s${RESET}\n" "$cmd"
  if bash -c "$cmd" >>"$LOG" 2>&1; then
    printf "      ${GREEN}→ ok${RESET}\n"; log "[ok]   $label :: $cmd"
  else
    printf "      ${YELLOW}→ ignore${RESET}\n"; log "[skip] $label :: $cmd"
  fi
}

start_step(){ # start_step "Titre" "explication"
  title "$1"
  info "$2"
  if [ "$MODE" = "step" ]; then
    if ! ask_yes "  Lancer cette etape ?"; then
      log "[user-skip] $1"; info "  ${YELLOW}Etape ignoree.${RESET}"; return 1
    fi
  fi
  return 0
}

# ----------------------- Droits administrateur -------------------------------
SUDO=""
if [ "$(id -u)" -ne 0 ]; then
  if have sudo; then
    SUDO="sudo"
    info ""
    info "Certaines etapes ont besoin des droits administrateur."
    info "Votre mot de passe peut etre demande (il ne s'affiche pas : c'est normal)."
    if ! sudo -v 2>/dev/null < "$TTY"; then
      info "${YELLOW}Droits administrateur indisponibles : les etapes concernees seront ignorees.${RESET}"
      SUDO=""
    fi
  else
    info "${YELLOW}Commande sudo absente : les etapes necessitant l'administrateur seront ignorees.${RESET}"
  fi
fi

# ------------------- Notes de tracabilite (chaine de custody) ----------------
title "Etape 0 : Informations de tracabilite"
info "Ces quelques informations permettent a l'analyste de savoir qui a realise"
info "la collecte et a quel moment. Elles n'ont rien d'obligatoire."
OPERATOR="$(ask_val '  Votre nom ou vos initiales (facultatif) :' 'non renseigne')"
OBSERV="$(ask_val '  Ce que vous avez remarque d anormal (facultatif) :' 'non renseigne')"
{
  echo "=== Fiche de collecte Fenrir ==="
  echo "Version du script : $VERSION"
  echo "Operateur         : $OPERATOR"
  echo "Observations      : $OBSERV"
  echo "Debut collecte UTC   : $(date -u '+%Y-%m-%d %H:%M:%S %Z')"
  echo "Debut collecte local : $(date '+%Y-%m-%d %H:%M:%S %Z')"
  echo "Nom de machine    : $(hostname 2>/dev/null)"
  echo "Utilisateur       : $(id -un 2>/dev/null) (uid $(id -u))"
} > "$DEST/00_fiche_collecte.txt"
ok

# =============================================================================
#  ORDRE DE VOLATILITE : on capture d'abord les traces les plus fragiles
#  (memoire vive : processus, connexions), puis les traces du disque.
# =============================================================================

if start_step "Etape 1 : Contexte et horloge du systeme" \
   "On note la version du systeme et l'heure (UTC + locale), pour recaler la chronologie."; then
  collect "Version du noyau"           "uname -a > '$DEST/info_systeme.txt'"
  collect "Distribution"               "cat /etc/os-release >> '$DEST/info_systeme.txt' 2>&1"
  collect "Machine (hostnamectl)"      "hostnamectl >> '$DEST/info_systeme.txt' 2>&1"
  collect "Heure UTC"                  "date -u >> '$DEST/info_systeme.txt'"
  collect "Heure locale"              "date >> '$DEST/info_systeme.txt'"
  collect "Duree depuis demarrage"     "uptime >> '$DEST/info_systeme.txt'"
fi

if start_step "Etape 2 : Programmes actifs et connexions reseau (le plus volatil)" \
   "Ces traces disparaissent des l'extinction : on les capture en priorite."; then
  collect "Programmes actifs"          "ps auxww > '$DEST/programmes_actifs.txt'"
  collect "Arborescence des processus" "ps -ejH > '$DEST/arborescence_processus.txt' 2>&1"
  if have ss; then
    collect "Connexions reseau (ss)"   "$SUDO ss -tunap > '$DEST/connexions_reseau.txt' 2>&1"
  elif have netstat; then
    collect "Connexions reseau"        "$SUDO netstat -tunap > '$DEST/connexions_reseau.txt' 2>&1"
  fi
  if have lsof; then
    collect "Fichiers ouverts (reseau)" "$SUDO lsof -i > '$DEST/connexions_ouvertes.txt' 2>&1"
  fi
  collect "Table de routage"           "ip route > '$DEST/reseau_routes.txt' 2>&1; ip addr >> '$DEST/reseau_routes.txt' 2>&1"
fi

if start_step "Etape 3 : Qui est (ou s'est) connecte" \
   "Sessions ouvertes et historique des connexions, reussies comme echouees."; then
  collect "Sessions ouvertes (who/w)"  "who > '$DEST/utilisateur_actuel.txt'; w >> '$DEST/utilisateur_actuel.txt' 2>&1"
  collect "Connexions reussies"        "last > '$DEST/historique_connexions.txt' 2>&1"
  collect "Connexions echouees"        "$SUDO lastb > '$DEST/connexions_echouees.txt' 2>&1"
fi

if start_step "Etape 4 : Mecanismes de demarrage automatique (persistance)" \
   "Les intrus se relancent souvent au demarrage : services, taches planifiees, autostart."; then
  collect "Services systeme actives"   "systemctl list-unit-files --type=service --state=enabled > '$DEST/services_systeme.txt' 2>&1"
  collect "Services utilisateur"       "systemctl --user list-unit-files --type=service --state=enabled > '$DEST/services_utilisateur.txt' 2>&1"
  collect "Minuteries systemd"         "systemctl list-timers --all > '$DEST/minuteries.txt' 2>&1"
  collect "Cron utilisateur"           "crontab -l > '$DEST/cron_utilisateur.txt' 2>&1"
  collect "Cron root"                  "$SUDO crontab -l > '$DEST/cron_root.txt' 2>&1"
  collect "Cron systeme"               "ls -la /etc/cron.* > '$DEST/cron_systeme.txt' 2>&1; cat /etc/crontab >> '$DEST/cron_systeme.txt' 2>&1"
  collect "Autostart (liste)"          "ls -la ~/.config/autostart/ > '$DEST/autostart.txt' 2>&1; ls -la /etc/xdg/autostart/ >> '$DEST/autostart.txt' 2>&1"
  collect "Autostart (copie)"          "cp -r ~/.config/autostart/ '$DEST/autostart_utilisateur/' 2>/dev/null; cp -r ~/.config/systemd/ '$DEST/systemd_utilisateur/' 2>/dev/null; true"
fi

if start_step "Etape 5 : Comptes, privileges et acces distant" \
   "On verifie les comptes existants, les droits administrateur et l'etat de SSH."; then
  collect "Liste des comptes"          "getent passwd > '$DEST/comptes_utilisateurs.txt' 2>&1"
  collect "Droits sudo (config)"       "$SUDO cp /etc/sudoers '$DEST/sudoers.txt' 2>/dev/null; $SUDO ls -la /etc/sudoers.d/ >> '$DEST/sudoers.txt' 2>&1; true"
  collect "Cles SSH autorisees"        "cat ~/.ssh/authorized_keys > '$DEST/ssh_cles_autorisees.txt' 2>&1"
  collect "Prechargement (ld.so)"      "cat /etc/ld.so.preload > '$DEST/ld_preload.txt' 2>&1"
  collect "Etat du service SSH"        "$SUDO systemctl status ssh sshd > '$DEST/etat_ssh.txt' 2>&1"
fi

if start_step "Etape 6 : Historique des commandes et des configurations" \
   "Les commandes tapees et les fichiers de configuration personnels peuvent trahir l'intrus."; then
  collect "Historique bash"            "cp ~/.bash_history '$DEST/historique_bash.txt' 2>/dev/null; true"
  collect "Historique zsh"             "cp ~/.zsh_history '$DEST/historique_zsh.txt' 2>/dev/null; true"
  collect "Fichiers de config perso"   "cp ~/.bashrc '$DEST/bashrc.txt' 2>/dev/null; cp ~/.profile '$DEST/profile.txt' 2>/dev/null; cp ~/.bash_profile '$DEST/bash_profile.txt' 2>/dev/null; true"
fi

if start_step "Etape 7 : Historique des navigateurs" \
   "On copie l'historique de Firefox et des navigateurs Chrome/Chromium s'ils existent."; then
  collect "Historique navigateurs"     "mkdir -p '$DEST/navigateurs'; for f in ~/.mozilla/firefox/*/places.sqlite ~/.config/google-chrome/*/History ~/.config/chromium/*/History ~/.config/BraveSoftware/*/*/History ~/.config/microsoft-edge/*/History; do [ -f \"\$f\" ] && cp \"\$f\" \"$DEST/navigateurs/\$(echo \"\$f\" | tr '/' '_')\" 2>/dev/null; done; true"
fi

if start_step "Etape 8 : Fichiers recemment modifies" \
   "Une liste (bornee) des fichiers recemment modifies dans les zones sensibles."; then
  collect "Fichiers recents systeme"   "{ find /etc /usr/local -xdev -type f -mtime -7 2>/dev/null | head -n 3000; } > '$DEST/fichiers_recents.txt'"
  collect "Fichiers recents temporaires" "{ find /tmp /var/tmp /dev/shm -xdev -type f -mtime -7 2>/dev/null | head -n 2000; } >> '$DEST/fichiers_recents.txt' 2>&1"
  collect "Fichiers recents perso"     "{ find \"\$HOME\" -maxdepth 3 -type f -mtime -3 2>/dev/null | grep -v '$BASE' | head -n 2000; } >> '$DEST/fichiers_recents.txt' 2>&1"
fi

if start_step "Etape 9 : Journaux systeme (72 dernieres heures)" \
   "Cette etape peut durer quelques minutes. Merci de patienter."; then
  if have journalctl; then
    collect "Journal (72h)"            "$SUDO journalctl --since '72 hours ago' > '$DEST/journaux_systeme.txt' 2>&1"
  fi
  collect "Journaux d'authentification" "$SUDO cp /var/log/auth.log '$DEST/' 2>/dev/null; $SUDO cp /var/log/secure '$DEST/' 2>/dev/null; true"
fi

# =============================================================================
#  SCELLEMENT : empreinte de chaque fichier, puis archive + empreinte globale
# =============================================================================
title "Etape 10 : Scellement et archive"
info "On calcule une empreinte de chaque fichier (integrite), puis on cree l'archive."

# Certains fichiers ont ete copies par sudo et appartiennent a root avec des
# droits restrictifs (ex. sudoers, auth.log). Sans cela, tar et sha256sum
# lances sous votre compte ne peuvent pas les lire (Permission denied => tar
# renvoie le code 2 et l'archive est incomplete). On remet donc tout le dossier
# de collecte sous votre propriete avant de l'empreinte et de l'archiver.
if [ -n "$SUDO" ]; then
  info "  ${DIM}\$ $SUDO chown -R $(id -un):$(id -gn) '$DEST'${RESET}"
  $SUDO chown -R "$(id -u):$(id -g)" "$DEST" 2>/dev/null
  $SUDO chmod -R u+rwX "$DEST" 2>/dev/null
fi

if [ -n "$HASH" ]; then
  info "  ${DIM}\$ ( cd '$DEST' && find . -type f ! -name manifest_sha256.txt -print0 | xargs -0 $HASH ) > manifest_sha256.txt${RESET}"
  ( cd "$DEST" && find . -type f ! -name 'manifest_sha256.txt' -print0 \
      | xargs -0 $HASH ) > "$DEST/manifest_sha256.txt" 2>/dev/null
  info "  Manifeste des empreintes : ${GREEN}manifest_sha256.txt${RESET}"
else
  info "  ${YELLOW}Aucun outil d'empreinte disponible : manifeste non genere.${RESET}"
fi

log "Fin (UTC) : $(date -u '+%Y-%m-%d %H:%M:%S %Z')"

# La sortie d'erreur de tar est ecrite HORS du dossier archive : sinon tar
# modifierait collecte.log pendant qu'il le lit, ce qui provoque le code de
# sortie 1 ("file changed as we read it") meme quand l'archive est correcte.
TAR_ERR="$(mktemp "${TMPDIR:-/tmp}/fenrir_tar.XXXXXX" 2>/dev/null || printf '%s' "/tmp/fenrir_tar.$$")"
info "  ${DIM}\$ tar -czf '$ARCHIVE' -C '$HOME' '$BASE'${RESET}"
tar -czf "$ARCHIVE" -C "$HOME" "$BASE" 2>"$TAR_ERR"
TAR_RC=$?
[ -s "$TAR_ERR" ] && cat "$TAR_ERR" >> "$LOG" 2>/dev/null
rm -f "$TAR_ERR" 2>/dev/null

# tar renvoie 0 (succes) ou 1 (simples avertissements). Le code 2 signale une
# erreur (souvent un fichier illisible). Si malgre tout une archive non vide
# existe, elle reste exploitable : on le dit clairement plutot que d'annoncer
# un echec sec.
if [ "$TAR_RC" -le 1 ] && [ -s "$ARCHIVE" ]; then
  info "  Archive creee : ${GREEN}${ARCHIVE}${RESET}"
elif [ -s "$ARCHIVE" ]; then
  info "  ${YELLOW}Archive creee, mais incomplete${RESET} (code tar : $TAR_RC)."
  info "  ${YELLOW}Certains fichiers n'ont pas pu etre lus ; le detail est dans collecte.log.${RESET}"
  info "  Vous pouvez tout de meme envoyer cette archive a votre analyste."
else
  info "  ${RED}Echec de la creation de l'archive.${RESET} (code tar : $TAR_RC)"
fi

SEAL=""
if [ -n "$HASH" ] && [ -f "$ARCHIVE" ]; then
  info "  ${DIM}\$ $HASH ${BASE}.tar.gz > ${BASE}.tar.gz.sha256${RESET}"
  ( cd "$HOME" && $HASH "${BASE}.tar.gz" > "${BASE}.tar.gz.sha256" )
  SEAL="$(cut -d' ' -f1 < "${ARCHIVE}.sha256" 2>/dev/null)"
fi

# ------------------------------- Recapitulatif -------------------------------
hr
printf "${BOLD}${GREEN}  Collecte terminee.${RESET}\n"
hr
info ""
info "A transmettre a votre analyste Fenrir :"
info "  ${BOLD}${ARCHIVE}${RESET}"
[ -f "${ARCHIVE}.sha256" ] && info "  ${BOLD}${ARCHIVE}.sha256${RESET}   (empreinte d'integrite)"
[ -n "$SEAL" ] && info ""
[ -n "$SEAL" ] && info "  Empreinte SHA-256 de l'archive :"
[ -n "$SEAL" ] && info "  ${CYAN}${SEAL}${RESET}"
info ""
info "${BOLD}Ensuite :${RESET}"
info "  - Envoyez l'archive ET le fichier .sha256 par le canal securise indique"
info "    par votre analyste (pas par simple e-mail non chiffre)."
info "  - N'envoyez pas l'archive depuis cette machine si elle est peut-etre"
info "    encore sous controle : utilisez de preference un autre appareil de confiance."
info "  - N'eteignez pas et ne redemarrez pas tant que l'analyste ne vous l'a pas dit."
info "  - Ne lancez pas encore d'antivirus ni de nettoyage."
info ""
info "Merci. Ce script suit l'ordre de volatilite du RFC 3227 et les fiches"
info "reflexes du CERT-FR / ANSSI. Guide detaille : https://fenrir.pro/fr/blog/pc-linux-pirate/"
hr

exit 0
