Данная программа просто копирует аргумент в символьный массив buffer . Мы заботимся о том, чтобы не допустить переполнения некоторых важных данных (атака при помощи строк форматирования более аккуратная чем переполнения буфера ;-)
>>gcc stack.c -o stack
>>./stack toto
buffer : [toto] (4)
i = 1 (bffff674)
Она работает так как мы и ожидали :) Перед тем как продолжить, посмотрим, что происходит с точки зрения стека при вызове snprintf() в строке 8.
Рисунок 1 изображает состояние стека в момент, когда программа заходит в функцию snprintf()(мы увидим, что это не так ... однако нам это нужно всего лишь за тем, чтобы дать вам представление, что происходит). Нам не интересен регистр %esp. Он где-то ниже регистра %ebp. Как мы видели в предыдущей статье, первые два значения, расположенные в %ebp и %ebp+4 содержат соответствующие резервные копии регистров %ebp и %eip. Далее идут аргументы функции snprintf():
адрес назначения;
число сиволов для копирования;
адрес строки форматирования argv[1], которая также выполняет функцию данных.
И наконец, стек завершается массивом 4 символов tmp, 64 байтами переменной buffer и целой переменной i.
Строка argv[1] используется одновременно и как строка форматирования и как данные. Согласно обычному порядку подпрограммы snprintf(), argv[1] выступает взамен строки форматирования. Так как вы можете использовать строку форматирования без директив формата (просто текст), все нормально :)
Что получается, если argv[1] также содержит и директивы форматирования? Обычно, snprintf() интерпретирует их так, какие они есть ... и нет причины, почему она будет вести себя по другому! Но здесь, вы можете удивиться, какие аргументы будут использованы в качестве данных для форматирования выходной строки? Фактически, snprintf() забирает данные из стека! Вы можете увидеть это при помощи нашей программы stack:
Сначала, строка "123 " копируется в buffer. Директива %x требует snprintf() перевести первое значение в шеснадцатиричный вид. Из рисунка 1 видно, что этот первый аргумент не что иное, как переменная tmp, которая содержит строку \x01\x02\x03\x00. Она отображается как шеснадцатиричное число 0x00030201 в соответствии с прямым порядоком байтов, который принят в процессорах x86.
Добавление второго %x дает возможность поднятся выше по стеку. Директива говорит snprintf() искать следующие 4 байта после переменной tmp. Эти 4 байта - фактически 4 первых байта buffer. Однако, buffer содержит строку "123 ", что мы можем увидеть, как шеснадцатиричное число 0x20333231 (0x20=пробел, 0x31='1'...). То есть, для каждого %x, snprintf() "прыгает" на 4 байта дальше в buffer (4 потому что unsigned int занимает 4 байта на процессоре x86). Эта переменная выступает как двойной агент, так как:
пишет в буфер назначения;
считывает данные для формата.
Мы можем "лезть вверх" по стеку до тех пор, пока наш буфер содержит байты: