![]() |
Модераторы: Daevaorn |
![]() ![]() ![]() |
|
Thunderbolt |
|
||||||||||||||
![]() DevRel ![]() Профиль Группа: Участник Сообщений: 122 Регистрация: 7.11.2007 Где: Тула Репутация: 10 Всего: 16 |
Андрей Карпов
ООО "СиПроВер" Оригинал статьи Безопасность 64-битного кода. Август 2009 Аннотация Введение Анализ программного кода Примеры некорректного и уязвимого кода Диагностика уязвимых мест в 64-битном коде Заключение Библиографический список Аннотация В статье рассматриваются вопросы обеспечения безопасности программного кода при его адаптации для 64-битных систем. Введение Не будем говорить об угрозе взлома программного обеспечения и о размере вреда, который эта угроза может причинить. Об этом написаны многие книги и статьи. Перейдем сразу к новому практическому вопросу в сфере повышения надежности программного кода, связанному с освоением 64-битных систем. И скорее всего, вас не удивит, что речь пойдет о языках Си/Си++, для которых вопросы обеспечения безопасности стоят наиболее остро. Из-за ошибок и недочетов программный код при переносе с 32-битных на 64-битные системы может стать более восприимчив к атакам, основанных на переполнении буферов. Это связанно с изменением размеров базовых типов данных, что может дать возможность воспользоваться ранее недоступными путями атаки на код. Другими словами код, которой в рамках 32-битных систем был безопасен и не давал возможности использовать его для целей вторжения в систему, после перекомпиляции для 64-битных систем может начать представлять потенциальную угрозу. Проблема безопасности 64-битного кода не является новой областью в сфере защиты информации. Проблемы различного поведения кода и возможности его взлома всегда зависели от используемой аппаратной платформы. Но массовый переход на 64-битные системы выделяет задачи по обеспечению безопасности 64-битного кода в отдельную категорию, которая заслуживает повышенного внимания и отдельного исследования. В данной статье мы сделаем попытку коснуться проблем безопасности 64-битного кода и показать разработчикам программного обеспечения и систем защиты на этот новый источник потенциальной опасности при разработке современных 64-битных решений. Анализ программного кода Существуют различные подходы к обеспечению безопасности программного кода. Мы остановимся на методе статического анализа кода, так как он наиболее подходит для задачи поиска уязвимостей при переносе кода на другую платформу. Существует достаточно много различных инструментов статического анализа, обеспечивающих диагностику потенциально опасных участков кода, которые могут быть использованы для различных видов атак. В качестве примера можно привести: ITS4, SourceScope, Flawfinder, АК-ВС [1]. Кстати, недавно познакомился с интересным фактом. Я всегда рассматривал инструменты статического анализа кода, как средства поиска ошибок в программах, с целью сделать ее более надежной и устойчивой к входным данным. Но оказывается, хакеры также используют инструменты статического анализа, но с противоположной целью [2]. Они выявляют потенциально ненадежные места в программах, для дальнейшего их подробного изучения. Вручную просматривать код современных приложений практически нереально из-за их размеров, и статический анализ оказывается им хорошим подспорьем. После дизассемблирования кода, хакеры с помощью статического анализа отсеивают наиболее интересные области кода для изучения. Например, они могут искать код, использующий копирование строк и при этом наличие рядом уменьшение/увеличение регистра или ячейки памяти на единицу. Программисты достаточно часто допускают ошибки при работе со строками, когда приходится резервировать дополнительный байт под терминальный символ 0x00 (конец строки). Этот код обычно содержит магические арифметические комбинации, где присутствует -1 или +1. И такой код конечно очень интересен для подробного изучения хакером, поскольку потенциально он может помочь осуществить атаку на основе переполнения буфера. Но мы отвлеклись. Статические анализаторы помогают программисту выявить потенциально уязвимые участки кода в своих программах и их пользу нельзя недооценивать. Рассмотрим теперь некоторые примера кода, который станет опасным или даже некорректным при переносе на 64-битную систему. Примеры некорректного и уязвимого кода С большой коллекцией ошибок, которые возникают в 64-битных программах можно познакомиться в статьях "20 ловушек переноса Си++ - кода на 64-битную платформу" [3] и "Проблемы 64-битного кода на примерах" [4]. Но в данных статьях акцент сделан на ошибки, которые приводят к неработоспособности программы, а не с точки зрения ее уязвимости для атаки. К сожалению, автору не удалось найти систематических работ по вопросам обеспечения безопасности 64-битного кода. И, по всей видимости, выделение паттернов уязвимостей, специфичных для 64-битных систем является новой задачей, еще ожидающей различных исследований. Попробуем все-таки рассмотреть некоторые примеры. Одним из методов атаки может стать передачу в программу большого объема данных, превышающий, например, размер 4 Gb. Разберем пример чтения данных из файла.
Другая разновидность угроз кроется в использовании фиксированных размеров буферов и магических констант. Особенно этим грешит старый код, написанный с десяток лет назад программистами, которые не задумывались, что когда-то изменится размер указателя или перемененной типа time_t. Рассмотрим простой пример переполнения буфера с жестко заданным размером:
Приведем другой пример, где использование магического числа 4 приводит к ошибке выделения необходимого объема памяти:
Подобные эффекты опасны не только в логических выражениях, но и при работе с массивами. Определенное сочетание данных в следующем примере приведет к записи за пределами массива на 64-битнйо системе:
Ошибки в логике программы легко могут возникнуть и в коде обрабатывающем отдельные биты. Следующий тип ошибки связан с операциями сдвига. Рассмотрим пример:
Потенциально манипулирую с входными данными подобных некорректных функций можно получить недопустимые права, если например, происходит обработка масок прав доступа, заданных отдельными битами. Если приводимые выше примеры кажутся вам надуманными и нереальными, то предлагаю познакомиться с еще одним кодом (в упрощенном виде), который использовался в реальном приложении в подсистеме UNDO/REDO, хотя выглядит он, пожалуй, наиболее странно:
Диагностика уязвимых мест в 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-битных проблем, но не заменяет их. Заключение Занимаясь вопросами обеспечения безопасности, никогда не увлекайтесь каким-то одним направлением, будь то статический или динамический анализ, тестирование на некорректных входных данных и так далее. Надежность системы определяется ее самым слабым звеном. Бывает, что надежность системы можно повысить на порядок простым административным методом, например, таким как замок. Существует толи легенда, толи правда, что однажды в одной компании при аудите безопасности ей была поставлена самая низкая оценка, еще даже до того, как специалисты приступили к проверке, дублируются ли данные, какое программное обеспечение установлено на сервере и так далее. Просто сервер стоял в одной из комнат с незапирающейся дверью, куда мог зайти кто угодно. Почему? Шумел сильно, вот и переставили его подальше от рабочих кабинетов, чтобы не мешал. Библиографический список
--------------------
Карпов Андрей, DevRel в PVS-Studio. |
||||||||||||||
|
|||||||||||||||
GoldFinch |
|
|||
![]() ![]() ![]() ![]() Профиль Группа: Завсегдатай Сообщений: 2141 Регистрация: 30.11.2008 Репутация: 15 Всего: 26 |
В статье говорится о языках С\С++ , однако в примера присутствует только С код, с незначительным использованием синтаксиса С++
Действительно есть сомнения в жизненности примеров: использование типов не по назначению (unsigned int вместо size_t) - неправильно само по себе; использование магических чисел - плохо в любом коде, который собираются повторно использовать; при использовании переменных со знаком и без знака в одном выражении, компилятор выдает соответствующее предупреждение, а игнорирование предупреждений компилятора - неправильные действия программиста. Таким образом приведенные примеры показывают не код, неправильный в плане переносимости на х64 системы, а код неправильный сам по себе. |
|||
|
||||
Lazin |
|
|||
![]() Эксперт ![]() ![]() ![]() ![]() Профиль Группа: Завсегдатай Сообщений: 3820 Регистрация: 11.12.2006 Где: paranoid oil empi re Репутация: 41 Всего: 154 |
этой осенью обещают Coverity Prevent, под x64
![]() Добавлено через 49 секунд кажется в ноябре |
|||
|
||||
Thunderbolt |
|
|||
![]() DevRel ![]() Профиль Группа: Участник Сообщений: 122 Регистрация: 7.11.2007 Где: Тула Репутация: 10 Всего: 16 |
Я часто слышу в различных интерпретациях фразу "Таким образом, приведенные примеры показывают не код, неправильный в плане переносимости на х64 системы, а код неправильный сам по себе". Захотелось немного пофилософствовать на эту тему.
Во-первых, можно начать с того, что любой код, написанный на Си++ уже неправильный сам по себе. Правильным будет только код, состоящий из пустой функции main, и то я не уверен. Невозможно написать идеальную корректную программу на Си/Си++. Ведь надо учесть, что программа должна работать на 12, 16, 32, 64, ... – битной системе. Еще надо, чтобы в ней не использовались функции scanf, ведь программу может понадобиться поместить в контроллер, где нет устройства ввода. В программе не должно использоваться приведение типов. Любое приведение типа, это потенциально какая-то ошибка на какой-то платформе. Еще возможно лучше писать программу с помощью триграфов. А то вдруг... ![]() В общем, я к тому, что идеально верных программ не бывает. Можно стремиться, но сделать такую программу нельзя. В реальном мире при написании программ выбирают какой-то приемлемый уровень корректности и предположения об окружающей среде исполнения и в рамках этой модели пишут программу. Итак, любой код неправильный сам по себе с точки зрения идеального программиста. Но можно считать, что определенный код корректен при определенных условиях. При изменении условий (окружения) код может становиться некорректен. В чем он становится некорректен - зависит от внешних изменений. Поиск возникших ошибок при смене среды исполнения, можно сгруппировать в группу и успешно диагностировать. В то время как подход "в программе все неправильно" не рационален. Рассмотрим пример. Имеем программу, переносимую в контроллер, который не будет иметь консоли. В программе есть некоторое количество cout, cin, printf, scanf. Необходимо найти эти функции и "обезвредить". Допустим, осуществлять ввод через порты, подсоединенные к какой-нибудь ручке на ящике прибора. Не имеет смысла говорить, что код плох, что программист, писавший его, плох, раз он сразу не предусмотрел, что консоли может не быть. Это нам не поможет. И нет смысла заниматься идеальным рефакторингом для создания идеальной программы. Надо просто найти и поправить нужные места. Можно придумать статический анализатор в духе диагностики "проблем ввода вывода в контроллерах". И он будет полезен! Хотя если по-честному, конечно все из-за неидеального кода. Пример выше сильно натянут, но просто хочется продемонстрировать, что когда человек пишет код, он не может предусмотреть все. Ну не знал он, что его код через 5 лет будет засовываться в контроллер или переноситься на 64-битную систему. Предусмотреть что-то заранее весьма непросто. Программисты имеют и поддерживают такой код, какой есть. В нем может быть полно магических чисел, ТЫСЯЧИ выражений, где вместе используются знаковые и беззнаковые типы, могут быть отключены многие предупреждения, из-за необходимости использования БОЛЬШИХ старых сторонних библиотек (я видел это на практике). И заниматься полномасштабным рефакторингом таких проектов, чтобы они стали красивее, переносимее и так далее, никто не будет. А того кто будет наставать – пожалуй начальству следует уволить. ![]() Моя позиция покажется многим неверной, как будто я призываю писать плохой код, а затем призываю использовать различные костыли, чтобы его местами подправить. Я просто практик. И еще я называю многие вещи своими именами. ![]() В большинстве своем код программ ПЛОХ. И более или менее работает, потому что ему везет. К сожалению, программисты упорно не хотят это признавать. Любая встряска кода (смена компилятора, среды и так далее) вскрывает пласт определенных типов ошибок. Я отлично понимаю, что нет "64-битных" ошибок. Есть просто ошибки в коде. Они всегда есть. Но определенные ошибки проявят себя именно на 64-битной системе. Я рассказываю о таких ошибках и надеюсь это полезно разработчикам. Уф. Сам удивился, сколько написал... ![]() --------------------
Карпов Андрей, DevRel в PVS-Studio. |
|||
|
||||
![]() ![]() ![]() |
Правила форума "С++:Общие вопросы" | |
|
Добро пожаловать!
Если Вам понравилась атмосфера форума, заходите к нам чаще! С уважением, Earnest Daevaorn |
1 Пользователей читают эту тему (1 Гостей и 0 Скрытых Пользователей) | |
0 Пользователей: | |
« Предыдущая тема | C/C++: Общие вопросы | Следующая тема » |
|
По вопросам размещения рекламы пишите на vladimir(sobaka)vingrad.ru
Отказ от ответственности Powered by Invision Power Board(R) 1.3 © 2003 IPS, Inc. |