wmkhoo / taintgrind

A taint-tracking plugin for the Valgrind memory checking tool
GNU General Public License v2.0
249 stars 42 forks source link

Detecting a classic buffer overflow #17

Closed wmkhoo closed 6 years ago

wmkhoo commented 6 years ago

I am trying to use Taintgrind for Dynamic Information Flow Tracking in the context of buffer overrun attacks.

A classical example would be:

/* misprint.c */
#include <stdio.h>
#include <stdlib.h>

void hello_function()
{
    printf("INFO: Hello World!\n");
}

/* 
 * $ VAL=`readelf -s misprint | grep secret_function | awk '{print $2}'` && printf "%d\n" 0x$VAL
 */
void secret_function()
{
    printf("INFO: Oh no! The application is compromised!\n");
}

int main(int argc, char** argv)
{
    TNT_START_PRINT();

    int i;
    int val;
    int iter;
    int buffer[10];
    int canary;
    void (*func)(void);

    if (argc != 3) { return(1); }

    iter = atoi(argv[1]);
    val = atoi(argv[2]);

    /* It should print Hello World! */
    func = &hello_function; 

    canary = UNINTIALIZED;

    /* If iter > 10 then a buffer overflow will occur */
    for (i = 0; i < iter; i++)
        buffer[i] = val;

    /* 
     * ... if the buffer overflow is "severe enough", 
     * it may overwrite the function pointer with user data, 
     * e.g. a pointer to the secret function
     */
    func();

    TNT_STOP_PRINT();

    return(0);
}

If I compile the application:

$ cc -g -O0 -fno-inline -fno-omit-frame-pointer -fno-stack-protector -m32 misprint.c -o misprint

And run it with "safe" parameters, it simply prints Hello World.

$ ./misprint 1 1
INFO: Hello World!

If I run it with parameters that lead to a buffer overrun attack (you may have to find the pointer to the secret function):

$ VAL=`readelf -s misprint | grep secret_function | awk '{print $2}'` && printf "%d\n" 0x$VAL
134513809
$ ./misprint 12 134513809
INFO: Oh no! The application is compromised!

Now I am trying to figuring out if Valgrind + Taintgrind can allow me to intercept the attack. I would like to propagate the tag to the "secret function" call.

But, for the moment I get a SIGSEGV process termination (segfault).

$ valgrind --tool=taintgrind ./misprint 12 134513809
....
0x8048491: VALGRIND_PRINTF (valgrind.h:6264) | t13_10656 = r28_1848 I32 | 0xfee5bad8 | 0x0 | 
0x8048491: VALGRIND_PRINTF (valgrind.h:6264) | t12_10940 = Add32 t13_10656 0x1 | 0xfee5bad9 | 0x0 | 
0x8048491: VALGRIND_PRINTF (valgrind.h:6264) | r28_1849 = t12_10940 | 0xfee5bad9 | 0x0 | 
0x8048491: VALGRIND_PRINTF (valgrind.h:6264) | r68_64762 = 0x8048492 | 0x8048492 | 0x0 | 
0x8048492: VALGRIND_PRINTF (valgrind.h:6264) | t20_8100 = r12_8724 I32 | 0xfee5bd2a | 0x0 | 
0x8048492: VALGRIND_PRINTF (valgrind.h:6264) | t19_8372 = Add32 t20_8100 0x458de445 | 0x4473a16f | 0x0 | 
==11286== 
==11286== Process terminating with default action of signal 11 (SIGSEGV)
==11286==  Access not within mapped region at address 0x4473A16F
==11286==    at 0x8048492: VALGRIND_PRINTF (valgrind.h:6264)
==11286==    by 0x3040689: ???
==11286==  If you believe this happened as a result of a stack
==11286==  overflow in your program's main thread (unlikely but
==11286==  possible), you can try to increase the size of the
==11286==  main thread stack using the --main-stacksize= flag.
==11286==  The main thread stack size used in this run was 8388608.
==11286== 
make: *** [run-taintgrind-m32] Segmentation fault

Is there a way to perform DIFT (Dynamic Information Flow Track) with Taintgrind? Credit: Giuseppe Di Guglielmo

wmkhoo commented 6 years ago

Thanks @GiuseppeDiGuglielmo

wmkhoo commented 6 years ago

Running this test case with valgrind --tool=taintgrind -- taintgrind/tests/misprint 12 134513924 gives me this output

0x8048615: main (misprint.c:45) | STORE t18_10366 = t14_7982 | 0x8048504 | 0xffffffff | canary <- t14_7982
...
0x8048623: main (misprint.c:44) | t55_450 = LOAD I32 t53_1077 | 0x8048504 | 0xffffffff | t55_450 <- val
...
0x8048615: main (misprint.c:45) | STORE t59_291 = t55_450 | 0x8048504 | 0xffffffff | func <- t55_450
...
0x8048625: main (misprint.c:52) | t5_11953 = LOAD I32 t3_7605 | 0x8048504 | 0xffffffff | t5_11953 <- func
0x8048628: main (misprint.c:52) | JMP t5_11953 | 0x8048504 | 0xffffffff | t5_11953

It's quite hard to tell, but the jump target at line 52 is tainted. Taint flows from value to func to the jump. Not sure why it crashes. You're running CentOS?

wmkhoo commented 6 years ago

You can generate the taint graph by running:

valgrind --tool=taintgrind -- taintgrind/tests/misprint 12 134513924 2>&1 | python taintgrind/log2dot.py | tee misprint.dot
dot -Tpng misprint.dot -o misprint.png

The taint graph is equally hard to tell, but I tried to highlight the tainted jump in red. misprint

wmkhoo commented 6 years ago

Created wiki page for this: Detecting a classic buffer overflow