техника переполнения в стеке - PullRequest
0 голосов
/ 22 мая 2010
int main(void) {
   problem2();
}

void doit2(void) {
    int overflowme[16];
    //overflowme[37] =0;
}

void problem2(void) {
    int x = 42;
    doit2();
    printf("x is %d\n", x);
    printf("the address of x is 0x%x\n", &x);
}

Кто-нибудь поможет мне понять, почему overflowme [37] = 0; из doit2 функция перезапишет значение х? (пожалуйста, укажите в вашем объяснении счетчик программ и указатель кадра функции doit2)

Он работает каждый раз на компьютере с Windows x86 (ок! так что это не неопределенное поведение.

Ответы [ 5 ]

5 голосов
/ 22 мая 2010

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

Системный стек обычно растет с высокого адреса на более низкий адрес.Если указатель стека равен 0x1234, и вы вводите значение (в 32-битной {4-байтной системе)), то указатель стека становится 0x1230.

Массивы адресуются от самого низкого адреса к самому высокому адресу.Если у вас есть

char a[2];

и [0] в 0x0122, тогда [1] будет в 0x0123.

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

Выделение пространства в стеке для этого массива означает, что указатель стека на sizeof(int)*16 меньше, чем если бы этот массив отсутствовал.Указатель стека, скорее всего, указывает на overflowme[0] в то время как в doit2.

Есть другие вещи, которые могут быть в стеке, и пара вещей, которые должны были быть в стеке.Вещи, которые должны были быть в стеке, - это указатели возврата, которые помещались туда при вызове функций.В 32-битной системе они должны занимать 4 байта каждый.Вещи, которые могли бы быть в стеке (если бы компилятор хотел его использовать), это указатель предыдущего кадра.(Явный *) стековые фреймы на x86-32 - это просто пространство между ESP и EBP, но они не нужны, поэтому часто они не используются, а EBP используется вместо этого как регистр общего назначения (доступны более общие регистры общего назначения.в общем хорошо).Однако использование стековых фреймов полезно, поскольку они значительно упрощают отладку, поскольку ESP и EBP действуют как маркеры для краев локальных переменных.Иногда требуются стековые фреймы, например, когда вы используете автоматические массивы alloca или C99 с переменным размером, потому что они позволяют отбрасывать пространство локальных переменных для функции mov EPB, ESP или эквивалентным инструкциям, а не sub size_of_local_variable, ESP, поэтому компилятор не 'Я должен знать размер кадра.Они также позволяют адресовать локальные переменные относительно EBP, а не ESP, который в случае alloca изменяется.EBP в этом случае не изменится до конца текущей функции, если только она не была изменена и восстановлена ​​путем вызова функций.

При компиляции без включенных оптимизаций компиляторы часто всегда используют стековые фреймы из-за упрощения отладки.Также проще смоделировать код с помощью стековых фреймов, а затем преобразовать код, чтобы он не использовался, после того как он доказал, что они не нужны.

Таким образом, предыдущее значение EBP может находиться или не находиться в стеке.между обратным адресом (где-то в problem2) и последним элементом в doit2 overflowme.Компилятор также может свободно помещать в стек все что угодно, так что, кто знает, что еще может быть там? * Локальная переменная

problem2 int x может находиться в регистре или встек.При компиляции без включенных оптимизаций локальные переменные часто попадают в стек, даже когда они могут попадать в регистры.

Итак, давайте предположим, что существует массив doit2 overflowme, старый указатель кадра,адрес возврата и problem2 x в стеке (и еще кое-что в разделе {который на самом деле находится по более высокому адресу}).

Поскольку &(overflowme[i]) - это то же самое, что и добавление адреса первого элемента overflowme в (i * {размер int}), а старый EBP находится после последнего элемента overflowme и адрес возврата лежит после старого EBP, а int x - после адреса возврата, x определенно стоит прямо на пути переполнения буфера.

Почему это происходит для индекса 37, не ясно. Математическая схема указателя (при условии, что только элементы, которые я описал выше, находится в стеке между массивом и x), не предполагает, что он должен основываться на 4-байтовых указателях (32-битная машина), хотя, если это 8-байтовый указатель система (64-битная машина), то математика ближе к адресу, по которому я ожидал бы x, если sizeof(int) == 8. Компилятор также мог бы пойти дальше и выделить место в стеке для вызовов printf (аргументы переменной после строки формата должны идти в стек), что могло бы повлиять на математику (а также побудить компилятор поместить x в стека, потому что это все равно пришлось бы толкать туда).

Если вы хотите получить более подробный ответ на свой вопрос, посмотрите сборку для этого кода и определитесь с точной адресацией.

  • Можно подумать, что кадр стека существует, даже если EBP не используется в качестве базового указателя кадра, но тогда кадр не будет в рамке.
3 голосов
/ 22 мая 2010

Возможно, нет.Расположение переменных в стеке зависит от компилятора и платформы.

2 голосов
/ 22 мая 2010

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

2 голосов
/ 22 мая 2010

Ваш стек будет выглядеть примерно так:

char overflowme[16]
return address to problem2() from calling doit2()
parameters for doit2(), if it had any
int x = 42
return address to main() from calling problem2()
parameters for problem2(), if it had any
local variables for main(), if it had any

Когда вы пишете в overflowme[37], вы пройдете через конец overflowme (так как это всего 16 байтов)и после обратного адреса из вызова doit2() и перезаписи x.

Как уже упоминалось, это сильно зависит от платформы и компилятора, но должно дать вам хорошую визуализацию проблемы.Попробуйте пройтись по коду с открытым окном отладки и показать вам стек.

0 голосов
/ 22 мая 2010

Это не так:

x is 42
the address of x is 0xbff9ea1c

Вышеуказанное происходит каждый раз на One True Compiler и Platform (изменение адреса), так что вы абсолютно правы, что это не неопределенное поведение.

...