Модераторы: Daevaorn
  

Поиск:

Ответ в темуСоздание новой темы Создание опроса
> Безопасность 64-битного кода, В статье рассматриваются вопросы обеспеч 
:(
    Опции темы
Thunderbolt
Дата 15.8.2009, 20:36 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


DevRel
*


Профиль
Группа: Участник
Сообщений: 122
Регистрация: 7.11.2007
Где: Тула

Репутация: 10
Всего: 16



Андрей Карпов
ООО "СиПроВер"

Оригинал статьи Безопасность 64-битного кода.

 Август 2009

Аннотация
Введение
Анализ программного кода
Примеры некорректного и уязвимого кода
Диагностика уязвимых мест в 64-битном коде
Заключение
Библиографический список



Аннотация

В статье рассматриваются вопросы обеспечения безопасности программного кода при его адаптации для 64-битных систем.

Введение

Не будем говорить об угрозе взлома программного обеспечения и о размере вреда, который эта угроза может причинить. Об этом написаны многие книги и статьи. Перейдем сразу к новому практическому вопросу в сфере повышения надежности программного кода, связанному с освоением 64-битных систем. И скорее всего, вас не удивит, что речь пойдет о языках Си/Си++, для которых вопросы обеспечения безопасности стоят наиболее остро.
Из-за ошибок и недочетов программный код при переносе с 32-битных на 64-битные системы может стать более восприимчив к атакам, основанных на переполнении буферов. Это связанно с изменением размеров базовых типов данных, что может дать возможность воспользоваться ранее недоступными путями атаки на код. Другими словами код, которой в рамках 32-битных систем был безопасен и не давал возможности использовать его для целей вторжения в систему, после перекомпиляции для 64-битных систем может начать представлять потенциальную угрозу.
Проблема безопасности 64-битного кода не является новой областью в сфере защиты информации. Проблемы различного поведения кода и возможности его взлома всегда зависели от используемой аппаратной платформы. Но массовый переход на 64-битные системы выделяет задачи по обеспечению безопасности 64-битного кода в отдельную категорию, которая заслуживает повышенного внимания и отдельного исследования. В данной статье мы сделаем попытку коснуться проблем безопасности 64-битного кода и показать разработчикам программного обеспечения и систем защиты на этот новый источник потенциальной опасности при разработке современных 64-битных решений.

Анализ программного кода

Существуют различные подходы к обеспечению безопасности программного кода. Мы остановимся на методе статического анализа кода, так как он наиболее подходит для задачи поиска уязвимостей при переносе кода на другую платформу.
Существует достаточно много различных инструментов статического анализа, обеспечивающих диагностику потенциально опасных участков кода, которые могут быть использованы для различных видов атак. В качестве примера можно привести: ITS4SourceScopeFlawfinderАК-ВС [1]. 
Кстати, недавно познакомился с интересным фактом. Я всегда рассматривал инструменты статического анализа кода, как средства поиска ошибок в программах, с целью сделать ее более надежной и устойчивой к входным данным. Но оказывается, хакеры также используют инструменты статического анализа, но с противоположной целью [2]. Они выявляют потенциально ненадежные места в программах, для дальнейшего их подробного изучения. Вручную просматривать код современных приложений практически нереально из-за их размеров, и статический анализ оказывается им хорошим подспорьем. После дизассемблирования кода, хакеры с помощью статического анализа отсеивают наиболее интересные области кода для изучения. Например, они могут искать код, использующий копирование строк и при этом наличие рядом уменьшение/увеличение регистра или ячейки памяти на единицу. Программисты достаточно часто допускают ошибки при работе со строками, когда приходится резервировать дополнительный байт под терминальный символ 0x00 (конец строки). Этот код обычно содержит магические арифметические комбинации, где присутствует -1 или +1. И такой код конечно очень интересен для подробного изучения хакером, поскольку потенциально он может помочь осуществить атаку на основе переполнения буфера.
Но мы отвлеклись. Статические анализаторы помогают программисту выявить потенциально уязвимые участки кода в своих программах и их пользу нельзя недооценивать. Рассмотрим теперь некоторые примера кода, который станет опасным или даже некорректным при переносе на 64-битную систему.

Примеры некорректного и уязвимого кода

С большой коллекцией ошибок, которые возникают в 64-битных программах можно познакомиться в статьях "20 ловушек переноса Си++ - кода на 64-битную платформу" [3] и "Проблемы 64-битного кода на примерах" [4]. Но в данных статьях акцент сделан на ошибки, которые приводят к неработоспособности программы, а не с точки зрения ее уязвимости для атаки.
К сожалению, автору не удалось найти систематических работ по вопросам обеспечения безопасности 64-битного кода. И, по всей видимости, выделение паттернов уязвимостей, специфичных для 64-битных систем является новой задачей, еще ожидающей различных исследований. Попробуем все-таки рассмотреть некоторые примеры.
Одним из методов атаки может стать передачу в программу большого объема данных, превышающий, например, размер 4 Gb. Разберем пример чтения данных из файла.
Код
void *SpecificMalloc(unsigned int size) {
  return malloc(size);

...
char *buf;
size_t len; 
read(fd, &len, sizeof(len)); 
buf = SpecificMalloc(len);
read(fd, buf, len);
Напомним, что в 64-битных системах (Linux, Windows) размер типа int составляет 32-бита, а size_t- 64-бита. Ошибка заключается в приведении типа size_t к типу unsigned int при вызове функции SpecificMalloc. Если размер файла будет больше 4 гигабайт, то при чтении данных из файла мы выйдем за границы массива, что является недопустимой ситуацией. Конечно, ошибка в данном примере очевидна, но пример показывает опасность явного и неявного приведения типов, которое может возникнуть в 64-битном коде, смешено использующие 32-битыне и 64-битыне типы для хранения размеров, индексов и так далее.
Другая разновидность угроз кроется в использовании фиксированных размеров буферов и магических констант. Особенно этим грешит старый код, написанный с десяток лет назад программистами, которые не задумывались, что когда-то изменится размер указателя или перемененной типа time_t.
Рассмотрим простой пример переполнения буфера с жестко заданным размером:
Код
char buf[9];
sprintf(buf, "%p", pointer);
Да, такое бывает в программах. Особенно в старых.
Приведем другой пример, где использование магического числа 4 приводит к ошибке выделения необходимого объема памяти:
Код
LPARAM *CopyParamList(LPARAM *source, size_t n)
{
  LPARAM *ptr = (LPARAM *)malloc(n * 4);
  if (ptr)
    memcpy(ptr, source, n * sizeof(LPARAM);
  return ptr;
}
Неожиданному изменению может подвергнуться и логика работы программы:
Код
int a = -2;
unsigned b = 1;
ptrdiff_t c = a + b;
if (c == -1)
{
  printf("Case: 32-bit\n");
} else {
  printf("Case: 64-bit\n");
}
В этом неаккуратном коде в зависимости от разрядности платформы будут выполняться различные ветки в оператора 'if'. Согласно правилам языка Си++ выражение "ptrdiff_t c = a + b;" будет вычисляться следующим образом:
  • Значение типа int, равное -2 будет преобразовано в тип unsigned со значением 0xFFFFFFFEu.
  • Произойдет сложение двух 32-битных значений 0x00000001u и 0xFFFFFFFEu, результатом чего станет число 0xFFFFFFFFu размерностью 32-бита.
  • Значение 0xFFFFFFFFu будет помещено в 64-битную переменную знакового типа. Для 32-битной системе это означает, что переменная будет содержать значение -1. В случае 64-битной системы переменная так и будет иметь значение 0xFFFFFFFF.
 

Подобные эффекты опасны не только в логических выражениях, но и при работе с массивами. Определенное сочетание данных в следующем примере приведет к записи за пределами массива на 64-битнйо системе:
Код
int A = -2;
unsigned B = 1;
int array[5] = { 1, 2, 3, 4, 5 };
int *ptr = array + 3;
ptr = ptr + (A + B);
*ptr = 10; // Обращение к памяти за пределами массива
           // в случае 64-битной среды.
Подобную ошибку можно использовать, если удастся установить некорректное значение переменных A и B таким образом, чтобы произвести запись в желаемую область памяти.

Ошибки в логике программы легко могут возникнуть и в коде обрабатывающем отдельные биты. Следующий тип ошибки связан с операциями сдвига. Рассмотрим пример:
Код
ptrdiff_t SetBitN(ptrdiff_t value, unsigned bitNum) {
  ptrdiff_t mask = 1 << bitNum;
  return value | mask;
}
Приведенный код работоспособен на 32-битной архитектуре и позволяет выставлять бит с номерами от 0 до 31 в единицу. После переноса программы на 64-битную платформу возникнет необходимость выставлять биты от 0 до 63. Но данный код никогда не выставит биты, с номерами 32-63. Обратите внимание, что "1" имеет тип int и при сдвиге на 32 позиции произойдет переполнение. В результате мы получим значение 0 или 1, что зависит от реализации компилятора. Заметим также, что неисправленный код приведет еще к одной интересной ошибке. При выставлении 31-ого бита на 64-битной системе результатом работы функции будет значение 0xffffffff80000000. Результатом выражения "1 << 31" является отрицательное число -2147483648. Это число представляется в 64-битной целой переменной как 0xffffffff80000000.
Потенциально манипулирую с входными данными подобных некорректных функций можно получить недопустимые права, если например, происходит обработка масок прав доступа, заданных отдельными битами.

Если приводимые выше примеры кажутся вам надуманными и нереальными, то предлагаю познакомиться с еще одним кодом (в упрощенном виде), который использовался в реальном приложении в подсистеме UNDO/REDO, хотя выглядит он, пожалуй, наиболее странно:
Код
// Здесь указатели сохранялись в виде строки
int *p1, *p2;
....
char str[128];
sprintf(str, "%X %X", p1, p2);
// А в другой функции данная строка
// обрабатывалась следующим образом:
void foo(char *str)
{
  int *p1, *p2;
  sscanf(str, "%X %X", &p1, &p2);
  // Результат - некорректное значение указателей p1 и p2.
  ...
}
Результатом манипуляций указателями с использованием %X стало некорректное поведение программы на 64-битной системе. Данный пример не столько демонстрирует проблему безопасности 64-битного кода, сколько показывает, как опасны потаенные дебри больших и сложных проектов, которые пишутся многими годами. Если проект достаточно велик и стар, то с большой вероятностью в нем существуют уязвимости и ошибки связанные с предположениями о размерах различных структурах данных, правилах выравнивания данных и много других сюрпризов.

Диагностика уязвимых мест в 64-битном коде

Вначале систематизируем типы целей, которые могут стать доступными для атаки при переносе кода на 64-битную систему:
  1. Опасны участки кода с арифметическими выражениями, в которых смешанно используются 32-битные и 64-битные типы данных.
  2. Опасны участки кода с адресной арифметикой, где присутствуют операции с 32-битными типами данных.
  3. Внимание заслуживают выражения, содержавшие магические константы, которые могут означать размер типов данных, максимально допустимые значения, смещение данных в структурах данных.
  4. Целью атаки может стать код содержащий операторы сдвига или иные битовые операции. 
  5. Потенциальную опасность могут представлять различные операции явного и неявного приведения 32-битных и 64-битных типов между собой.
  6. Высокому риску подвержен код, реализующий чтение или ввод данных в которых присутствуют типы, изменившие свой размер на 64-битной системе.
Приведенный список пока нельзя назвать полным, поскольку, это, пожалуй, одна из первых исследовательских статей по теме безопасности кода переносимого на 64-битную систему. Однако даже верификация перечисленных объектов позволит существенно повысить надежность кода и устранить как многие уязвимости, так и ошибки, которые могут возникать даже на корректных данных.
На данный момент нет выделенного продукта для контроля безопасности кода при переносе его на 64-битные системы. Однако существует статический анализатор кода PVS-Studio, который полностью поддерживает диагностику всех описанных в этой статье 64-битных проблем уязвимости.
Программный продукт PVS-Studio представляет собой разработку российской компании ООО "Системы программной верификации" и предназначен для верификации современных приложений, использующих такие технологии, как параллельное программирование (OpenMP) и 64-битные архитектуры [5]. PVS-Studio встраивается в среду Microsoft Visual Studio 2005/2008, а также в справочную систему MSDN.
Входящая в состав PVS-Studio подсистема Viva64, помогает специалисту отслеживать в исходном коде Си/Си++-программ потенциально опасные фрагменты, связанные с переходом от 32-битных систем к 64-битным. Анализатор помогает писать безопасный корректный и оптимизированный код для 64-битных систем.
Возможности PVS-Studio покрывают диагностику описанных ранее проблем уязвимостей в 64-битном программном коде. Диагностические возможности данного анализатора избыточны для решения исключительно задач обеспечения безопасности 64-битного кода, поскольку он предназначен не только для выявления потенциальных ошибок, но и например для поиска неоптимальных структур данных. Впрочем, лишние диагностические сообщения легко отключить, используя настройки.
На всякий случай хочется подчеркнуть, что PVS-Studio предназначен для выявления ошибок, возникающих при переносе 32-битных программ в 64-битные системы или при разработке новых 64-битных программ. Но PVS-Studio не диагностирует ошибки, которые могут возникать при использовании небезопасных на любых платформах функций, таких как sprintf, strncpy и так далее. Для диагностики подобных ошибок не следует забывать про уже упомянутые инструменты, такие как ITS4, SourceScope, Flawfinder, АК-ВС. PVS-Studio дополняет ряд этих инструментов, закрывая брешь в диагностики 64-битных проблем, но не заменяет их.

Заключение

Занимаясь вопросами обеспечения безопасности, никогда не увлекайтесь каким-то одним направлением, будь то статический или динамический анализ, тестирование на некорректных входных данных и так далее. Надежность системы определяется ее самым слабым звеном. Бывает, что надежность системы можно повысить на порядок простым административным методом, например, таким как замок.
Существует толи легенда, толи правда, что однажды в одной компании при аудите безопасности ей была поставлена самая низкая оценка, еще даже до того, как специалисты приступили к проверке, дублируются ли данные, какое программное обеспечение установлено на сервере и так далее. Просто сервер стоял в одной из комнат с незапирающейся дверью, куда мог зайти кто угодно. Почему? Шумел сильно, вот и переставили его подальше от рабочих кабинетов, чтобы не мешал.

Библиографический список
  1. Касперски, Крис. Компьютерные вирусы изнутри и снаружи. - СПб. : Питер, 2006. - 526 с. : ил. - ISBN 5-469-00982-3.
  2. Хогланд, Грег, Мак-Гроу, Гарри. Взлом программного обеспечения: анализ и использование кода.: Пер. с англ. - М.: Издательский дом "Вильямс", 2005. - 400 с. : ил. - Парал. тит. англ. ISBN 5-8459-0785-3. ББК 32.973.26-018.2.75.
  3. Андрей Карпов, Евгений Рыжков. 20 ловушек переноса Си++ - кода на 64-битную платформу. http://www.viva64.com/art-1-1-1958348565.html
  4. Евгений Рыжков. Проблемы 64-битного кода на примерах. http://www.viva64.com/art-1-1-1757483679.html
  5. Евгений Рыжков. Учебное пособие по PVS-Studio. http://www.viva64.com/art-4-1-1796251700.html

--------------------
Карпов Андрей, DevRel в PVS-Studio.
PM MAIL WWW   Вверх
GoldFinch
Дата 15.8.2009, 21:16 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата



****


Профиль
Группа: Завсегдатай
Сообщений: 2141
Регистрация: 30.11.2008

Репутация: 15
Всего: 26



В статье говорится о языках С\С++ , однако в примера присутствует только С код, с незначительным использованием синтаксиса С++

Действительно есть сомнения в жизненности примеров:
использование типов не по назначению (unsigned int вместо size_t) - неправильно само по себе;
использование магических чисел - плохо в любом коде, который собираются повторно использовать;
при использовании переменных со знаком и без знака в одном выражении, компилятор выдает соответствующее предупреждение, а игнорирование предупреждений компилятора - неправильные действия программиста.

Таким образом приведенные примеры показывают не код, неправильный в плане переносимости на х64 системы, а код неправильный сам по себе.
PM MAIL ICQ   Вверх
Lazin
Дата 21.8.2009, 13:28 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Эксперт
****


Профиль
Группа: Завсегдатай
Сообщений: 3820
Регистрация: 11.12.2006
Где: paranoid oil empi re

Репутация: 41
Всего: 154



этой осенью обещают Coverity Prevent, под x64 smile

Добавлено через 49 секунд
кажется в ноябре
PM MAIL Skype GTalk   Вверх
Thunderbolt
Дата 5.10.2009, 10:52 (ссылка) |    (голосов:1) Загрузка ... Загрузка ... Быстрая цитата Цитата


DevRel
*


Профиль
Группа: Участник
Сообщений: 122
Регистрация: 7.11.2007
Где: Тула

Репутация: 10
Всего: 16



Я часто слышу в различных интерпретациях фразу "Таким образом, приведенные примеры показывают не код, неправильный в плане переносимости на х64 системы, а код неправильный сам по себе". Захотелось немного пофилософствовать на эту тему.

Во-первых, можно начать с того, что любой код, написанный на Си++ уже неправильный сам по себе. Правильным будет только код, состоящий из пустой функции main, и то я не уверен. Невозможно написать идеальную корректную программу на Си/Си++. Ведь надо учесть, что программа должна работать на 12, 16, 32, 64, ... – битной системе. Еще надо, чтобы в ней не использовались функции scanf, ведь программу может понадобиться поместить  в контроллер, где нет устройства ввода. В программе не должно использоваться приведение типов. Любое приведение типа, это потенциально какая-то ошибка на какой-то платформе. Еще возможно лучше писать программу с помощью триграфов. А то вдруг... smile

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

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

Рассмотрим пример. Имеем программу, переносимую в контроллер, который не будет иметь консоли. В программе есть некоторое количество cout, cin, printf, scanf. Необходимо найти эти функции и "обезвредить". Допустим, осуществлять ввод через порты, подсоединенные к какой-нибудь ручке на ящике прибора. Не имеет смысла говорить, что код плох, что программист, писавший его, плох, раз он сразу не предусмотрел, что консоли может не быть. Это нам не поможет. И нет смысла заниматься идеальным рефакторингом для создания идеальной программы. Надо просто найти и поправить нужные места. Можно придумать статический анализатор в духе диагностики "проблем ввода вывода в контроллерах". И он будет полезен! Хотя если по-честному, конечно  все из-за неидеального кода.

Пример выше сильно натянут, но просто хочется продемонстрировать, что когда человек пишет код, он не может предусмотреть все. Ну не знал он, что его код через 5 лет будет засовываться в контроллер или переноситься на 64-битную систему. Предусмотреть что-то заранее весьма непросто.

Программисты имеют и поддерживают такой код, какой есть. В нем может быть полно магических чисел, ТЫСЯЧИ выражений, где вместе используются знаковые и беззнаковые типы, могут быть отключены многие предупреждения, из-за необходимости использования БОЛЬШИХ старых  сторонних библиотек (я видел это на практике). И заниматься полномасштабным рефакторингом таких проектов, чтобы они стали красивее, переносимее и так далее, никто не будет. А того кто будет наставать – пожалуй начальству следует уволить. smile В реальном мире надо решать реальные задачи. Добавлять новую функциональность, поддерживать работоспособность на существующих системах. Если понадобится – перенести код на 64 бита. Но при переносе кода на 64-битную систему, будет решаться именно эта задача, а не как сделать код максимально переносимым. И вот здесь возникает практическая задача найти определенные магические числа (но не все), найти опасные выражения со знаковыми и беззнаковыми типами (но не все).

Моя позиция покажется многим неверной, как будто я призываю писать плохой код, а затем призываю использовать различные костыли, чтобы его местами подправить. Я просто практик. И еще я называю многие вещи своими именами. smile

В большинстве своем код программ ПЛОХ. И более или менее работает, потому что ему везет. К сожалению, программисты упорно не хотят это признавать. Любая встряска кода (смена компилятора, среды и так далее) вскрывает пласт определенных типов ошибок. Я отлично понимаю, что нет "64-битных" ошибок. Есть просто ошибки в коде. Они всегда есть. Но определенные ошибки проявят себя именно на 64-битной системе. Я рассказываю о таких ошибках и надеюсь это полезно разработчикам.

Уф. Сам удивился, сколько написал...  smile
 

--------------------
Карпов Андрей, DevRel в PVS-Studio.
PM MAIL WWW   Вверх
  
Ответ в темуСоздание новой темы Создание опроса
Правила форума "С++:Общие вопросы"
Earnest Daevaorn

Добро пожаловать!

  • Черновик стандарта C++ (за октябрь 2005) можно скачать с этого сайта. Прямая ссылка на файл черновика(4.4мб).
  • Черновик стандарта C (за сентябрь 2005) можно скачать с этого сайта. Прямая ссылка на файл черновика (3.4мб).
  • Прежде чем задать вопрос, прочтите это и/или это!
  • Здесь хранится весь мировой запас ссылок на документы, связанные с C++ :)
  • Не брезгуйте пользоваться тегами [code=cpp][/code].
  • Пожалуйста, не просите написать за вас программы в этом разделе - для этого существует "Центр Помощи".
  • C++ FAQ

Если Вам понравилась атмосфера форума, заходите к нам чаще! С уважением, Earnest Daevaorn

 
1 Пользователей читают эту тему (1 Гостей и 0 Скрытых Пользователей)
0 Пользователей:
« Предыдущая тема | C/C++: Общие вопросы | Следующая тема »


 




[ Время генерации скрипта: 0.0929 ]   [ Использовано запросов: 22 ]   [ GZIP включён ]


Реклама на сайте     Информационное спонсорство

 
По вопросам размещения рекламы пишите на vladimir(sobaka)vingrad.ru
Отказ от ответственности     Powered by Invision Power Board(R) 1.3 © 2003  IPS, Inc.