0

I ran into an integer overflow problem, which I managed to solve, but only by trial and error.

Since it's an integer overflow problem, I've wrote some code to print out the buffer. The beginning of the buffer is the address where array[0] is stored. Then, I started to pass MAX_INT and MIN_INT values to the program. I've noticed that when I passed MIN_INT value to argv[1], it overwrote the begining of the buffer. so I passed MIN_INT+1 value, and noticed that it overwrote the second address of the buffer. from there it was easy to solve.

The thing is, I dont understand why this works. In place_int_array function, when i pass, let's say, MIN_INT+6 value to argv[1], the if statement "if (slot > 3)" returns false, so it goes to the "else" statement, since MIN_INT+6 is a negative value, so it's less than or equal to 3, so slot's value got interpreted as "MIN_INT+6" value. But at the "else" statement, array[slot] went up in the buffer to the address of "array[0]+6", which means, slot's value as an index of "array" was interpreted as "6".

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

int secretCode = 10;

void dump_stack(void **stack, size_t n, void **arg0) {
    printf("Stack dump (stack at %p, len %d): \n", stack, n);
    //if the buffer if not aligned on a dword boundary - force alignment of print

    void** alignedStack = (void**)(((unsigned int)stack >> 2) << 2);
    while (n-- > 0) {
        printf("0x%08x: 0x%08x", (unsigned int)&alignedStack[n], (unsigned int)alignedStack[n]);
        if (n == 0) {
            printf(" (beginning of buffer)");
        }
        if (&alignedStack[n] == arg0 + 6+1) {
            printf(" (main first argument (argc))");
        }
        if (&alignedStack[n] == arg0 + 6) {
            printf(" (main return address (saved eip))");
        }
        if (&alignedStack[n] == arg0) {
            printf(" (second argument)");
        }
        if (&alignedStack[n] == arg0 - 1) {
            printf(" (first argument)");
        }
        if (&alignedStack[n] == arg0 - 2) {
            printf(" (place_int_array return address (saved eip))");
        }
        if (&alignedStack[n] == arg0 - 3) {
            printf(" (saved ebp)");
        }

        printf("\n");
    }
}

void print_secret_code() {
    //TODO - call this from somewhere...
    printf("You get better at this stuff, ah?! Go get your treasure, the code is %d\n", secretCode);
}

void  fill_array(int array[], size_t len) {
    unsigned int i;
    for (i = 0; i < len; i++) {
        array[i] = i * 10;
    }
}

void place_int_array(int slot, int value) {
    int array[3];

    printf("place_int_array ret address: %p\n‬", __builtin_return_address(0));
    printf("&array: %p\n", &array);
    printf("slot: %d\n", slot);
    printf("&array[slot]: %p\n", &array[slot]);


    fill_array(array, sizeof(array) / sizeof(array[0]));

    printf("slot: %d\n", slot);
    if (slot > 3) //we stop bad guys here
        printf("safe number is greater than 3, out of bounds.\n");
    else {
        array[slot] = value;
        dump_stack((void **) array, 30, (void **) &value);
        printf("filled safe %d with %d.\n", slot, value);
    }
    return;
}

int main(int argc, char **argv) {
    printf("print_secret_code function = %p\n", print_secret_code);

    if (argc != 3) {
        printf("Welcome to Alladin's magic cave!\n");
        printf("Enter the secret number into the right safe and get the treasure cave entrance code!\n");
        printf("syntax: %s [SAFE NUMBER] [SECRET NUMBER]\n", argv[0]);

        //TEMP TEMP - for debugging only
        printf("print_secret_code function = %p\n", print_secret_code);
    }
    else
    {

        place_int_array(atoi(argv[1]), atoi(argv[2]));
    }

    exit(0);
}

Here is the output:

[lab8_IntegerOverflow]$ ./aladdinSafe -2147483648 1
print_secret_code function = 0x804864b
place_int_array ret address: 0x804880d
&array: 0xffbd8464
slot: -2147483648
&array[slot]: 0xffbd8464
slot: -2147483648
Stack dump (stack at 0xffbd8464, len 30): 
0xffbd84d8: 0xbca9b1bd
0xffbd84d4: 0x72f595ac
0xffbd84d0: 0x00000000
0xffbd84cc: 0x00000000
0xffbd84c8: 0x00000000
0xffbd84c4: 0xf773c000
0xffbd84c0: 0x0804825c
0xffbd84bc: 0x0804a01c
0xffbd84b8: 0xffbd84d4
0xffbd84b4: 0xffbd8534
0xffbd84b0: 0x00000003
0xffbd84ac: 0xf7765cca
0xffbd84a8: 0xffbd8544
0xffbd84a4: 0xffbd8534
0xffbd84a0: 0x00000003 (main first argument (argc))
0xffbd849c: 0xf75aaaf3 (main return address (saved eip))
0xffbd8498: 0x00000000
0xffbd8494: 0xf773c000
0xffbd8490: 0x08048820
0xffbd848c: 0xf773c000
0xffbd8488: 0x0804882b
0xffbd8484: 0x00000001 (second argument)
0xffbd8480: 0x80000000 (first argument)
0xffbd847c: 0x0804880d (place_int_array return address (saved eip))
0xffbd8478: 0xffbd8498 (saved ebp)
0xffbd8474: 0xf7778938
0xffbd8470: 0xffbda5cb
0xffbd846c: 0x00000014 //address of array[2], filled at "fill_array" function with 2*10 = 20 = 0x14
0xffbd8468: 0x0000000a //address of array[1], filled at "fill_array" function with 1*10 = 10 = 0xa
0xffbd8464: 0x00000001 (beginning of buffer) //address of array[0], overwritten with argv[2] = 1.
filled safe -2147483648 with 1.

I expect that if "slot > 3"'s slot was interpreted as MIN_INT+6, then "array[slot]"'s slot would be interpreted the same.

Why is slot's value changed according to its use?

Elyasaf755
  • 2,239
  • 18
  • 24
  • 1
    If you don't understand how you (seem to have) fixed the problem, then how are you confident that it arises from an integer overflow ? – John Bollinger May 07 '19 at 11:57
  • 4
    ```if (slot > 3) //we stop bad guys here``` i'm a bad guy and if I put ```slot = 3``` or ```slot = -1```, you don't stop me. – Guillaume D May 07 '19 at 11:57
  • 2
    Signed integers means signed arithmetic and comparison. You only check if `slot` is larger than three, but you never check if `slot` is less than zero (which it happens to be). That means you go ***way*** out of bounds of your array (and C don't have bounds-checking) and you'll have [*undefined behavior*](https://en.wikipedia.org/wiki/Undefined_behavior). – Some programmer dude May 07 '19 at 11:58
  • What's up with all those casts? – machine_1 May 07 '19 at 12:01
  • Wait, are you casting from pointer to integer? What is the reason behind this black magic? `uintptr_t` is the only way to do that sanely – alx - recommends codidact May 07 '19 at 12:12
  • @Guillaume D good, you got the problem in the code. If you bothered to read my post, you would have understood that. that's not answering my question. Why slot as an index (array[slot]) is different than slot in comporison operator (if slot > 3) ? – Elyasaf755 May 07 '19 at 12:42
  • So you’re wondering why assigning to `array[INT_MIN + slot]` does the same thing as assigning to `array[slot]`? – Ry- May 07 '19 at 12:53
  • Exactly. btw, I've just printed "INT_MIN+slot" as an unsigned short, and it got me "0". I dont know what to think rn. – Elyasaf755 May 07 '19 at 12:59
  • and ofcourse, if it happens that array[INT_MIN+6] is the same as array[6], why does INT_MIN + 6 > 3 produces false? – Elyasaf755 May 07 '19 at 13:14
  • 1
    Ignoring the undefined behaviour for a moment, and assuming `sizeof(int)` is 4, then by the rules of C pointer arithmetic `INT_MIN + slot` will be multiplied by `sizeof(int)` before being added to the address of `array[0]` and it is likely that the `INT_MIN * 4` part has overflowed to 0, leaving behind `slot * 4`. – Ian Abbott May 07 '19 at 13:56
  • @4386427 maybe i ran a copy of my program. anyway, I recompiled the program and ran it again. I edited the original post with the correct code and correct output. sorry about that. for the "printf("print_secret_code function = %p\n", print_secret_code);" code, i print a pointer, not an integer, i used %p. – Elyasaf755 May 07 '19 at 15:33
  • @IanAbbott I dont think it has to do anything with the multiplication of 4 to calculate the number of addresses to jump. it overflows (or underflows) to 0, but i dont understand why it does that at the index, and not in the if-else statement. – Elyasaf755 May 07 '19 at 15:34
  • @Elyasaf755 There is no arithmetic overflow in the `if (slot > 0)` test. On the other hand, multiplying the `int` value `MIN_INT` by 4 typically results in the value 0 on systems that use 2's complement signed integers with no arithmetic overflow checking. – Ian Abbott May 07 '19 at 16:46
  • @4386427 Actually `print_secret_code` is a pointer (to a function), but is the wrong sort of pointer for `%p` (which requires a `void *`). – Ian Abbott May 07 '19 at 16:48
  • @IanAbbott OMG you actually got it! I wrote this C code: printf("min: %d\n", INT_MIN*4); printf("min * 4 + 1: %d\n", INT_MIN*4 + 1); and the output i got was: min: 0 min * 4 + 1: 1 – Elyasaf755 May 07 '19 at 17:35

1 Answers1

0

@IanAbbott got it. The integer "slot" underflows at "array[slot]" because the compiler multiply "slot" by 4. so when INT_MIN is multiplied by 4 - it under flows to 0. and when slot = INT_MIN + 6, when trying to calculate the address of array[slot], it first multilies slot by 4, which equals to (INT_MIN + 6)*4, which underflows to 0 + 6*4, which is exactly 6 addresses above the address of "array[0]". but at this statement "if (slot > 3)" - slot is simply a negative number, which is INT_MIN + 6, so "slot > 3" returns false.

Elyasaf755
  • 2,239
  • 18
  • 24