Недостатки форматной строки.
Строка форматирования в языке С++, и ему подобных используется множеством программистов, по всему миру, вообще это одно из самых удобных средств форматирования выводимых данных, но и за удобство приходится платить, как и все остальное это решение придумано и реализовано человеком, а посему не лишено недостатков (
как сказали в одном фильме: «Наше совершенство в нашем несовершенстве»)
http://ru.wikipedia.org/wiki/Printf
printf - обобщённое название семейства функций или методов стандартных или широкоизвестных коммерческих библиотек, или встроенных операторов некоторых языков программирования, используемых для форматного вывода — вывода в различные потоки
значений разных типов, отформатированных согласно заданному шаблону. Этот шаблон определяется составленной по специальным правилам строкой (форматной строкой).
Немного о вводе.
Как вы могли догадаться речь в статье пойдет о форматной строке, её уязвимостях, методах их эксплуатирования, и естественно, о способах безопасного использования данной функции Но прежде я хотел бы задержать ваше внимание на функции не имеющей ни какого
отношение к выводу информации, а как раз наоборот к её вводу.
char user[13];
char pass[13];
gets(&user[0]);
gets(&pass[0]);
Фрагмент кода кажется безопасным и правильным, но на самом деле его выполнение может привести к критическим последствиям, как минимум отказ в обслуживание, как максимум захват контроля на
ПК, который использует уязвимое приложение(
если оно сетевое, и выше приведенный фрагмент кода , есть ничто иное как просьба авторизоваться), все дело тут в том что функция
Gets, никак не проверяет количество символов введенных пользователем, для переменных выделяется по
13 байт, а если пользователь вводит больше символов, чем запланировано, то они выходят за границы буфера и затирают находящиеся за ним значения (
Другие переменные, иногда даже участки кода, если user и pass, объявлены локально, то они хранятся в стеке, а как следствие, можно затереть и адрес возврата из функции, что позволяет, выполнить атаку Срыв стека). Ошибка тут вовсе не программиста, а непосредственно разработчиков функции (не сделали проверки).
Вот аналог выше приведенного кода на Delphi (Object Pascal)
var
user:
string[13];
pass:
string[13];
begin
readln(user);
readln(pass);
end;
Этот код полностью безопасен в отличие от своего собрата на языке С++, функция которая читает введенные пользователем данные(readln) получает в регистре
ecx значение размера буфера, затем сохраняет его и сравнивает с количеством введенным пользователем символов, и берет только первые символы пока не кончится буфер. Что - бы защититься от данной «проблемы» достаточно использовать безопасный аналог функции gets, а именно fgets.
char user[13];
char pass[13];
fgets(&user[0],13,stdin);
fgets(&pass[0],13,stdin);
Защита от переполнения функции Read/Readln в Delphi

Читаем стек.
Теперь перейдем непосредственно к форматной строке:
#include "stdafx.h"
int main()
{
char str[17];
fgets(&str[0],17,stdin);
printf(&str[0]);
}
С первого взгляда все в порядке, но тут кроется одна большая неприятность, если запустить
проект и ввести в поле ввода %x %x %x, то в консоль выведется вовсе не это, а
241fe4 12f7bc 7ffdd000
(У вас результат может отличаться и даже должен, это как повезет)
Обратим внимание на текст, который мы вводим, вообще — то он эквивалентен записи:
printf(“%x %x %x”);
Откомпилируйте проект и снова увидите нечто подобное первому результату, получается что мы как — бы «обманываем программу» (
не находите, очень похоже на sql – инъекцию)
%x — говорит о том, что нужно достать значение из стека, и записать в буфер, а из буфера вывести на экран
(ну а вообще-то переводит переменную типа int в строку).
Нормальный расклад таков:
printf(“%x %x %x”,a,b,c);
Где
а,b,c – некоторые переменные, тогда на экран будут выведены значения этих переменных.
Состояние стека при нормальном раскладе:
Погрузившись в отладчике в
printf, мы понимаем, что произошло, когда мы не передали ни каких аргументов, но передали три спецификатора, это значит ей нужно распечатать три аргумента, она «не знает», что аргументов ей не дали и поэтому достает их с того места где по ее мнению они должны быть.
Состояние стека, когда аргументы не были переданы:

На месте Случайных данных, должны быть переменные, но мы их не передали, следовательно, там «валяется» разная информация - значение других переменных, адреса возврата, и т.д. Таким образом, мы получаем возможность читать значения в стеке, где при чрезмерном везение оказываются пароли и всякая полезная для взломщика информация.
Читаем код программы.
Помимо
%x, у
printf существует еще огромная куча спецификаторов, один из них
%s, спецификатор
%s указывает на строку, то есть в переменной хранится адрес, нате данные, которые нужно вывести, запустите предыдущий пример и введите
%s. Посмотрим, что произошло.
Мы считали данные по адресу
7C910208, потому что функция «подумала» - это аргумент. А теперь задумаемся, что если — бы мы могли заменить этот аргумент, на свой собственный, например на адрес точки входа в программу? Да мы получили бы код программы до первого попавшегося ноля, т.к ноль завершает строку, но это не всегда возможно, как повезет.
Перезапись ячейки памяти.
Спецификатор %n, для этого как раз и предназначен, запускайте и вводите, 2222%%x%x%x%x%n, %x - «возьмут на себя» те ячейки, в которые запись запрещена, если все же попытаться записать туда, то произойдет исключение и программа «упадет», мы же записываем по адресу
12ff50, а туда запись разрешена...
Спецификаторы очень опасная вещь, хотя и очень удобна в использование, в Object Pascal ничего подобного нет и от этого программирование на нем уже более безопасно, вместо спецификаторов там используются функции, например строка
printf(“%x ”,a);
На Delphi выглядит так:
write(inttostr(a));
Можно понять, что тут используется функция inttost(), и по-моему такое решение гораздо удачливее. Как же защититься? Можно, например, фильтровать вводимые пользователем данные, как это делается в случае с sql-инъекцией, но это было - бы полным идиотизмом, лучше
printf(str);
Заменить на
printf(”%s”,str);
И все будет ок...
Сегодня мы рассмотрели недостатки строки формата, конечно с помощью них врядли удастся получить контроль над удаленной тачкой, но то что они становятся верными помощниками и друзьями взломщика сомневаться не приходится.
sa-sec.org
(с)Kerny
Жду ваших отзывов.
p.s Здесь я не сравнивал Delphi и C так, что базар по этому поводу не разводить.