Переполнение буфера в C - PullRequest
18 голосов
/ 03 июня 2011

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

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;
}

Вот часть дампа ассемблера main:

...
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 работает на моей машине.Я использовал подход Немо для определения обратного адреса.

Ответы [ 5 ]

12 голосов
/ 03 июня 2011

Этот 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), используя двойной %%, чтобы получить один % в выходной сборке. Это описано в этом документе ABI . Искать 8(%rbp).

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

4 голосов
/ 03 июня 2011

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

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

1 голос
/ 03 июня 2011

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

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

0 голосов
/ 03 июня 2011

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

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 вашим ожиданиям.

0 голосов
/ 03 июня 2011

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

...