Форум   Статьи   Новости   Файлы   Bugtraq   Сниффер   Друзья   О Клубе

Последнее на Форуме

Контакты

Связь с Администрацией

hpcteam1[@]gmail.com

Статьи rss

[ Добавить Статью на сайт ]

Статьи / Программирование / C/C++, C#

Защита от переполнения. Часть 1

Доброго здравия, дорогие читатели. Думал-думал, чем вас удивить, ничего сенсационного придумать не удалось. Однако, проглядывая существующие на сайте статьи, я не нашел ответа на вопрос, который бы мне хотелось освятить, а именно: методики защиты ПО от переполнения буфера.
В своей статье я собираюсь рассказать о всем, на что успел наткнуться сам, поделиться собственным опытом так сказать, чтобы вы, дорогие читатели, в ходе разработке приложений не повторяли моих (да и не только) ошибок.
Итак, приступим….

Немного теории


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

Часть 1. Использование безопасных функций


Как это ни странно, большинство переполнений возможны именно благодаря использованию функций, не проверяющих длину входных/выходных параметров. Такими функциями являются, например, strcpy, strcat, gets и т.д. Наиболее полный список можно посмотреть здесь.
А вот и пример уязвимого кода в серверном приложении:

char result[255]; int i=0;
 ZeroMemory(result,255);
 i=recv(s,buff,sizeof(buff));
 do
	{
		strcat(result,buff);
	}
while (i!=0);

Давайте взглянем, что же здесь происходит. С сокета s принимается информация в buff размеров sizeof(buff), здесь переполнения не будет. Но вот дальше в цикле к массиву result прибавляется массив buff без проверки длины, что в результате приведен к переполнению, если сервер примет информацию более 255 байт. А ведь все что требуется, всего лишь проверять длину входящих параметров, например, следующим образом:
If (lstrlen(buff)+lstrlen(result)<sizeof(result)) strcat(result,buff);
Данное условие отлично подойдет для проверки, разумеется, если вы не передаете информацию в бинарном виде (например изображение – в бинарных файлах могут встречаться нулевые байты, а функция lstrlen возвращается количество байт массива до первого нулевого) . Ну думаю, проблем с написанием функции, возвращающем длину до EOF быть не должно, а посему я перехожу к следующей части.

Часть 2. Явное преобразование


Иногда можно встретить ситуации, когда отсутствует явное преобразование (приведение типа переменной из одного к другому). Например, к переменной типа int приравнивается результат вычислений типов double,float,long int и т.д. В данном случае переполнение будет несущественным (пару байт), однако все зависит от того, где находятся эти переменные и какие идут перед ними. Ведь все равно, в том же серверном приложении можно заставить его принять шеллкод (пускай даже он не выполниться в виду проверки размеров), но через уязвимость в явном преобразовании можно передать указатель на шеллкод (разумеется, в наше время это достаточно тяжело в виду динамической адресации и всего остального, но все же не исключает вероятность эксплуатации уязвимости полностью). Давайте взглянем на пример уязвимого кода:

int R,xq,yq;
            R=100;
            for(double a=0;a<=2*M_PI;a+=0.015)
                {
                         Rectangle(hdc,0,0,rc.right,rc.bottom);
                         xq=R*sin(3*a+M_PI/2)+x0;
                         yq=R*sin(a)+y0;
                         Sphere(hdc,xq,yq,25,0,255,0);
                         BitBlt(hdcWin,0,0,rc.right,rc.bottom,hdc,0,0,SRCCOPY); 
                }

Думаю, здесь невооруженным взглядом видно, что в переменные xq,yq возвращаются результаты вычислений над синусом, который возвращает double. Так что правильнее было сделать так:

xq=(int)(R*sin(3*a+M_PI/2)+x0);
                         yq=(int)(R*sin(a)+y0);

Еще раз повторюсь, эксплуатация такой узявимости маловероятна, на данной возможности не исключает.

Часть 3. Нулевые указатели


Одна из проблем, с которой я когда столкнулся, передача пустого массива в качестве строкового аргумента функции. Без лишней нагрузки перехожу сразу к коду:

HANDLE hData = GetClipboardData(CF_TEXT);//извлекаем текст из буфера обмена
  if (hData!=0)
  {
   char* chBuffer= (char*)GlobalLock(hData);//блокируем память
		// Some security actions
	 if (lstrlen(chBuffer)<5000)	lstrcpy(clipboard,chBuffer); 
   GlobalUnlock(hData);//разблокируем память
   CloseClipboard();//закрываем буфер обмена
  }

Итак, данный фрагмент кода получает содержимое буфера обмена. Причем он у меня прекрасно работал, когда я его написал и тестировал. Какого же было мое удивление, когда я запустил файл с таким алгоритмов на только что стартовавшей ОС. «Память не может быть read…» думаю знакомы все. Итак, дело все в том, что в закомментированный участок памяти я передаю строку равную NULL (если ОС только запустилась, буфер обмена пуст). В итоге функция не может правильно отработать и приложение терпит крах. Выход из этой ситуации я нашел следующий:

if (sizeof(chBuffer)>0 && (strstr(clipboard,chBuffer)==NULL | lstrlen(chBuffer)!=lstrlen(clipboard)))
   {
	// Some security actions
	 if (lstrlen(chBuffer)<5000)	lstrcpy(clipboard,chBuffer); 

	}
   GlobalUnlock(hData);//разблокируем память
   CloseClipboard();//закрываем буфер обмена


Часть 3. Ошибка на единицу


Довольно часто среди кода людей, пересевших на с++ с других языков (например, VB 6.0) можно встретить так называемую «ошибку на единицу». Суть ее заключается в том, что программисты могут забыть, что счетчик в с++ по умолчанию начинается с нуля, и соответственно первый элемент массива имеет индекс 0. Достаточно просто попросить знак поставить правильный знак между двумя массивами (больше, меньше, равно).

Dim mas(5) ? int mas[5]

Разумеется, массив, объявленный в с++ будет иметь 6 элементов, в то время как массив VB 5. Исходя из этого можно допустить следующую ошибку:

If (a<0 || a>mas[i])

В то время как грамотный код имеет знак >=:

If (a<0 || a>=mas[i])

Кстати говоря, подобная уязвимость, насколько я помню, была обнаружена в OpenSSH.

Заключение


Мораль моей статьи такова: везде и всегда необходимо использовать проверки на выделяемую память в приложениях (все таки с++ как раз и отличается от других языков «ручным» выделением памяти). Надеюсь, кому-то моя статья пошла на пользу, комментируйте, вносите конструктивную критику, в будущем я планирую писать по этой же теме.
Всего наилучшего.

Автор: 1nt

Материал добавил 1nt


Комментарии(0)

Дата: 2011-05-30 22:14:35

Добавить Комментарий к Материалу

Вы должны быть авторизованы на форуме чтобы добавлять комментарии. Регистрация Уже авторизованы?

Комментариев к материалу пока нет.

Последнее на Сайте

Новости

Статьи

Bugtraq

Файлы

Copyright © 2008 - 2017 «HPC». При копировании материалов ставьте ссылку на источник.
Все материалы представлены только в ознакомительных целях, администрация за их использование ответственности не несет.
Пользовательское соглашение Реклама на сайте