Подтвердить что ты не робот

Переполнение буфера в C

Я пытаюсь написать простой переполнение буфера, используя C на Mac OS X 10.6 64-бит. Здесь понятие:

void function() {
    char buffer[64];
    buffer[offset] += 7;    // i'm not sure how large offset needs to be, or if
                            // 7 is correct.
}

int main() {

    int x = 0;
    function();
    x += 1;
    printf("%d\n", x);      // the idea is to modify the return address so that
                            // the x += 1 expression is not executed and 0 gets
                            // printed

    return 0;
}

Здесь находится основная свалка ассемблера:

...
0x0000000100000ebe <main+30>:   callq  0x100000e30 <function>
0x0000000100000ec3 <main+35>:   movl   $0x1,-0x8(%rbp)
0x0000000100000eca <main+42>:   mov    -0x8(%rbp),%esi
0x0000000100000ecd <main+45>:   xor    %al,%al
0x0000000100000ecf <main+47>:   lea    0x56(%rip),%rdi        # 0x100000f2c
0x0000000100000ed6 <main+54>:   callq  0x100000ef4 <dyld_stub_printf>
...

Я хочу перепрыгнуть через инструкцию movl, что означало бы, что мне нужно увеличить обратный адрес на 42-35 = 7 (правильно?). Теперь мне нужно знать, где хранится адрес возврата, поэтому я могу рассчитать правильное смещение.

Я попытался найти правильное значение вручную, но либо 1 печатается, либо я получаю abort trap - может ли быть какая-то защита переполнения буфера?


Использование смещения 88 работает на моей машине. Я использовал метод Nemo для определения адреса возврата.

4b9b3361

Ответ 1

Этот 32-разрядный пример иллюстрирует, как вы можете это понять, см. ниже для 64-битного:

#include <stdio.h>

void function() {
    char buffer[64];
    char *p;
    asm("lea 4(%%ebp),%0" : "=r" (p));  // loads address of return address
    printf("%d\n", p - buffer);         // computes offset
    buffer[p - buffer] += 9;            // 9 from disassembling main
}

int main() {
    volatile int x = 7;
    function();
    x++;
    printf("x = %d\n", x); // prints 7, not 8
}

В моей системе смещение равно 76. Это 64 байта буфера (помните, что стек растет, так что начало буфера далека от обратного адреса), плюс любой другой фрагмент находится между ними.

Очевидно, что если вы атакуете существующую программу, вы не можете рассчитывать, что она рассчитает ответ для вас, но я думаю, что это иллюстрирует принцип.

(Кроме того, нам повезло, что +9 не выполняет в другой байт. В противном случае однобайтовый приращение не установил бы адрес возврата, как мы ожидали. Этот пример может нарушиться, если вам не повезло с адресом возврата внутри main)

Я почему-то забыл о 64-битте исходного вопроса. Эквивалент для x86-64 равен 8(%rbp), поскольку указатели имеют длину 8 байтов. В этом случае моя тестовая сборка создает смещение 104. В приведенном выше коде замените 8(%%rbp) с помощью double %%, чтобы получить одиночный % в сборке вывода. Это описано в в этом документе ABI. Найдите 8(%rbp).

В комментариях есть жалоба, что 4(%ebp) так же магия, как 76 или любое другое произвольное число. Фактически значение регистра %ebp (также называемое "указателем фрейма" ) и его отношение к местоположению обратного адреса в стеке стандартизовано. Одна иллюстрация, которую я быстро googled, здесь. В этой статье используется терминология "базовый указатель". Если вы хотите использовать переполнение буфера на других архитектурах, для этого потребуется аналогичное подробное знание вызывающих соглашений этого CPU.

Ответ 2

Родди прав, что вам нужно использовать значения размера указателя.

Я бы начал с чтения значений в вашей функции эксплойта (и их печати) вместо написания. Когда вы проползаете мимо конца вашего массива, вы должны начать видеть значения из стека. Вскоре вы должны найти обратный адрес и сможете выровнять его с помощью дампа дизассемблера.

Ответ 3

Разберите function() и посмотрите, как он выглядит.

Смещение должно быть отрицательным положительным, может быть, 64 + 8, так как это 64-разрядный адрес. Кроме того, вы должны сделать "+7" на объекте размера указателя, а не на char. В противном случае, если два адреса пересекают 256-байтовую границу, вы будете использовать ваш эксплойт....

Ответ 4

Вы можете попробовать запустить свой код в отладчике, одновременно наступая на каждую сборочную линию и анализируя пространство памяти стека, а также регистры.

Ответ 5

Мне всегда нравится работать с хорошими типами данных, например:

struct stackframe {
    char *sf_bp;
    char *sf_return_address;
};

void function() {
    /* the following code is dirty. */
    char *dummy;
    dummy = (char *)&dummy;
    struct stackframe *stackframe = dummy + 24; /* try multiples of 4 here. */

    /* here starts the beautiful code. */    
    stackframe->sf_return_address += 7;
}

Используя этот код, вы можете легко проверить с помощью отладчика, соответствует ли значение в stackframe->sf_return_address вашим ожиданиям.