Дерни printf за хвост Крис Касперски aka мыщъх Спецвыпуск Xakep, номер #045, стр. 045-032-5 Пошагово трассируя программу по F10 (Step Over – трассировка без захода внутрь функций), введи заданную строку, когда тебя об этом попросят (экран консоли начнет призывно мигать) и продолжай трассировку вплоть до достижения строки 0040103Сh, вызывающей функцию printf. Теперь перейди в окно дампа памяти и введи в адресной строке "ESP", сообщая отладчику, что нам угодно просмотреть содержимое стека, а затем вернись к дизассемблерному коду и нажми F10 еще раз. Содержимое буфера пользовательского ввода немедленно изменится, подсвечивая ядовито-красным цветом число "0F 00 00 00", записанное в его начале. Перезапись выбранной ячейки памяти успешно состоялась! Напоминаю, если спецификаторы перекрывают буфер пользовательского ввода, мы можем самостоятельно сформировать указатель, перезаписывая выбранные ячейки памяти произвольным образом. То есть почти произвольным. К ограничениям выбора целевых адресов теперь еще присоединяются и ограничения выбора перезаписываемого значения, которые, между прочим, очень жестки. Дисбаланс спецификаторов Каждому спецификатору должен соответствовать парный аргумент. Но должен еще не значит обязан. Ведь спецификаторы и аргументы программисту приходится набивать вручную, и ему ничего не стоит ошибиться! Транслятор откомпилирует такую программу вполне нормально, возможно, негромко выругавшись при этом и выдав на экран предупреждающий warning. Но что произойдет потом? Если аргументов окажется больше, чем спецификаторов, "лишние" будут проигнорированы. В противном случае функция форматированного вывода, не зная сколько ей аргументов реально передали, снимет со стека первый встретившийся ей мусор. События будут развиваться по сценарию, описанному выше ("Навязывание собственных спецификаторов"), с той лишь разницей, что навязывать спецификаторы злоумышленник сможет только косвенно или не сможет вовсе. Переполнение буфера-приемника Функция sprintf относится к числу самых опасных, и все руководства по безопасности в один голос твердят, что лучше пользоваться ее безопасным аналогом – snprintf. Почему? Природа форматированного вывода такова, что предельно достижимую длину результирующей строки очень трудно рассчитать заранее. Рассмотрим следующий код: Переполним буфер-приемник? f() { char buf[???]; sprintf(buf,"имя:%s возраст:%02d вес:%03d рост:%03d\n", name, age, m, h); ... } Как вы думаете, каких размеров буфер нам потребуется? Из неизвестных факторов здесь присутствуют: длина строки name и "длина" целочисленных переменных age, m, h, преобразуемых функцией sprintf в символьное представление. Кажется логичным, если мы отводим два столбца на возраст и по три на рост и вес, то за вычетом имени и длины форматной строки нам потребуется всего 8 байт. Правильно? А вот и нет! Если строковое представление переменных не умещается в отведенных ему позициях, оно автоматически расширяется, дабы избежать усечения результата. В действительности же, десятичное представление 32-разрядных переменных типа int требует резервирования 11 байт памяти, в противном случае возникает угроза переполнения буфера. |