A lab for school required us to design 3 examples of memory bugs that are not detected by Address Sanitizer. I thought it was a pretty informative exercise so I decided to post it here. Enjoy.

Bypassing Address Sanitizer

The full C source code is a follows:

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

struct sample {
    char buf[8];
    uint32_t val;
};

void choice1() {
    puts("1. Overflowing a buffer within a struct.");

    struct sample sample_struct;
    printf("sample_struct.val = 0x%x\n", sample_struct.val);
    strncpy(sample_struct.buf, "AAAAAAAABBBB", 12);
    printf("sample_struct.val = 0x%x\n", sample_struct.val);
}

void choice2() {
    puts("2. Arbitrary write with integer overflows skipping over shadow mem.");

    char canary[10] = "IAMCANARY";
    char buf[10];

    printf("Canary = \"%s\"\n", canary);

    int position = 2147483616;
    position = position * 2;
    printf("position = %d\n", position);
    for (int i = 0; i < 9; i++) {
        buf[position + i] = 'X';
        printf("buf[%d] = 'X'\n", position + i);
    }

    printf("Canary = \"%s\"\n", canary);
}

void f1(int ** p) {
    int dangle = 0x41414141;
    *p = &dangle;
}

void f2() {
    int override = 0x42424242;
}

void choice3() {
    puts("3. Use of stack variable after returning from a function.");

    int * p = 0;
    printf("p = %p\n", p);
    printf("*p = undefined\n");

    f1(&p);
    printf("p = %p\n", p);
    printf("*p = 0x%x\n", *p);

    f2();
    printf("p = %p\n", p);
    printf("*p = 0x%x\n", *p);
}

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

    choice1();
    choice2();
    choice3();

    return 0;
}

Bug 1: Overflowing a Buffer within a Struct

struct sample {
    char buf[8];
    uint32_t val;
};

void choice1() {
    puts("1. Overflowing a buffer within a struct.");

    struct sample sample_struct;
    printf("sample_struct.val = 0x%x\n", sample_struct.val);
    strncpy(sample_struct.buf, "AAAAAAAABBBB", 12);
    printf("sample_struct.val = 0x%x\n", sample_struct.val);
}

This example actually features two bugs. The first is use of the uninitialised field sample_struct.val which results in printing of some garbage memory on the stack. The second bug is that the strncpy overflows the sample_struct.buf variable by 4 bytes. This causes sample_struct.val to be overwritten.

1. Overflowing a buffer within a struct.
sample_struct.val = 0x3ddd09c0
sample_struct.val = 0x42424242

Bug 2: Skipping Over Shadow Memory

void choice2() {
    puts("2. Skipping over shadow memory.");

    char canary[10] = "IAMCANARY";
    char buf[10];

    printf("Canary = \"%s\"\n", canary);

    int position = -64;
    printf("position = %d\n", position);
    for (int i = 0; i < 9; i++) {
        buf[position + i] = 'X';
        printf("buf[%d] = 'X'\n", position + i);
    }

    printf("Canary = \"%s\"\n", canary);
}

In this example, it is shown that ASAN can be bypassed if an arbitrary write primitive is abused to surgically write to a targeted location. An attacker can simply skip over the shadow bytes that protect a buffer if they know that ASAN was enabled on a binary to perform the same buffer underrun/overrun with a little calculation of the offsets.

2. Skipping over shadow memory.
Canary = "IAMCANARY"
position = -64
buf[-64] = 'X'
buf[-63] = 'X'
buf[-62] = 'X'
buf[-61] = 'X'
buf[-60] = 'X'
buf[-59] = 'X'
buf[-58] = 'X'
buf[-57] = 'X'
buf[-56] = 'X'
Canary = "XXXXXXXXX"

Bug 3: Use of a Stack Variable after Returning from a Function

void f1(int ** p) {
    int dangle = 0x41414141;
    *p = &dangle;
}

void f2() {
    int override = 0x42424242;
}

void choice3() {
    puts("3. Use of stack variable after returning from a function.");

    int * p = 0;
    printf("p = %p\n", p);
    printf("*p = undefined\n");

    f1(&p);
    printf("p = %p\n", p);
    printf("*p = 0x%x\n", *p);

    f2();
    printf("p = %p\n", p);
    printf("*p = 0x%x\n", *p);
}

ASAN does not typically detect stack-use-after-return bugs. Thus, any attempts to use stack pointers from an expired stack frame is not detected by ASAN as a bug.

3. Use of stack variable after returning from a function.
p = (nil)
*p = undefined
p = 0x7ffe3ddd08a0
*p = 0x41414141
p = 0x7ffe3ddd08a0
*p = 0x42424242

Leave a Comment