Недостатки форматной строки. Строка форматирования в языке С++, и ему подобных используется множеством программистов, по всему миру, вообще это одно из самых удобных средств форматирования выводимых данных, но и за удобство приходится платить, как и все остальное это решение придумано и реализовано человеком, а посему не лишено недостатков (как сказали в одном фильме: «Наше совершенство в нашем несовершенстве») 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 так, что базар по этому поводу не разводить.