МЕНЮ

Практические основы языков C и C++ с нуля. Уроки для начинающих безопасниковУметь писать грамотный код для безопасника – это далеко не самое главное. Главное – это умение находить в нём ошибки и тыкать в них носом малограмотных разработчиков. Zip File, мамкины хаЦкеры. С вами Денчик и сегодня мы познакомимся с наиболее популярными багами, которые встречаются при написании прог на языке Си. Поговорим про классическое переполнение и переполнение буфера, рассмотрим принципы хранения данных, узнаем, что такое Credentials и Input validation. А также само-собой попутно взломаем пару компьютеров пентагона. Если Вам интересна данная тема и вы жаждите увидеть реальные практические примеры косяков, встречающихся в софте, работающем на борту современных операционных систем. Тогда устраивайтесь по удобней, наливайте себе кофеёк по дешевше, вдыхайте поглубже запах дырявых носков и приготовьтесь к погружению в мир, где правят патлатые очкозавры, нагибающие в комментах любого с двух рук посредством клавиатуры. Погнали.

И начнём мы с самых простых примеров написанных на языке «Си». Вместо того, чтобы делать системные вызовы, приложение обычно пишется так, чтобы использовать стандартную библиотеку.

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

Эти функции уже сами обращаются к ядру операционной системы с помощью системных вызовов, а вам лишь остаётся писать вызовы функций, которые и сделают всю хитро**ную работу.

Хранение данных

Прежде чем переходить к детальному рассмотрению синтаксиса языка «Си», давайте для начала разберёмся с понятием данных. В обычный жизни, каждый из нас работает с десятичной позиционной системой счисления.

И в этой концепции число 123 будет выглядеть следующим образом. Позиции нумеруются справа налево. Само-собой начиная с нуля. Сама же позиция фактически означает «вес цифры» в числе.

Этот «вес» в свою очередь характеризует то, как мы эту цифру будем интерпретировать. Иными словами, мы каждую цифру должны помножить на 10 в степени её позиции и сложив эту историю получить итоговое число. 

Компьютеры же в своей работе тоже используют подобное представление чисел. Только не в десятичной, а в двоичной системе счисления. С битами. Соответственно каждый бит – это число, принимающее значение 0 или 1.

Это так называемая минимальная единица информации. На бумаге рядом с числом принято писать индекс 2. Тем самым давая понять, что оно записано, как раз таки в двоичной системе.

Байт в свою очередь является минимально квантуемой единицей информации для компьютера. Т.е. ваш процессор в моменте не может написать или прочитать меньше одного байта.

А теперь внимание вопрос. Если мы можем в ячейках памяти хранить только 0 или 1, то какие числа мы можем хранить? Ответ напрашивается сам собой. Мы можем хранить только положительные целые числа. 

А для вычислений – это, сами понимаете, очень плохо. При таком раскладе мы не сможем нормально "считать", т.к. в идеале у нас должны быть ещё отрицательные числа и вещественные числа.

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

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

Однако триклятая математика и особенность архитектуры процессоров, тут конкретно подложили свинью очкастым ботанам. Ибо при такой схеме, привычные правила арифметики не работают.

И в итоге им пришлось-таки выдумать новый велосипед. Мы берём положительное число 1. Инвертируем, т.е. заменяем 0 на 1 и наоборот. А затем прибавляем 1.

Получается, что процессор, которому фиолетово и на знаки, и на людские договорённости, при сложении положительных и отрицательных чисел получит нужный нам результат.

Давайте резюмируем. Первое. Отрицательные числа для процессора представляются в особом виде. Второе. Для процессора все числе выглядят одинаково положительно.

И третье. Во время работы процессора с числами не исключена ситуация переполнения. Т.е. число, которое не умещается в выделенное место отбрасывает старшие битики до тех пор, пока не будет вмещаться.

Данные особенности работы процессора представляют для вас, как действующих или будущих специалистов в сфере ИБ, прямой интерес. Ибо переполнение, она же ошибка overflow, достаточно популярная уязвимость, которую частенько эксплуатируют матёрые хаЦкеры. 

Проблема переполнения (overflow)

По умолчанию целые числа со знаком, т.е. те, которые могут быть, как положительными, так и отрицательными, представлены типом int. Он имеет размер 2 байта на x32 и 4 байта на x64.

Давайте пробежимся по синтаксису. Решётка, инклуд означает так называемую директиву для препроцессора. Препроцессор — это такая программа, которая сначала читает ваш исходный код, а затем его переписывает. 

В данном примере у нас получается следующее. Мы говорим компьютеру: найди файл stdio.h и впиши его вот сюда. Если полистаете документацию или пару статеек в гугле, то узнаете, что в данном файле содержатся функции для работы с вводом/выводом.

Дальше идёт объявление функции main с возвращаемым значением int, говорящая нам о том, что далее какое-то число должно быть отдано. Вот в этом месте. Return. 

В круглых скобках обычно указывают аргументы. Если помните урок по Башу, там было почти тоже самое. Ну и в фигурных скобочках тело функции. В примере main вызывает функцию print f передавая ей 2 аргумента.

Строчку в двойных кавычках. Это говорит о том, что внутри текст. %d, он же плейсхолдер. Не поленитесь и прямо сейчас загуглите, что же это такое. И напишите в комментах, а то я же тупой и, как обычно, просто где-то услышал умное слово. 

Окей. Таким образом программа в примере выведет строчку с отрицательным значением. Такую ситуацию называют переполнением. Это случилось из-за того, что мы использовали самое большое положительное число, которое только можно использовать в типе int.

Прибавили к нему единичку. Соответственно число в конечном итоге стало отрицательным. И казалось бы, ну и ладно. Переполнение, так переполнение. Кому от этого тошно?

Не всё так просто, друзья мои. Что если эти данные использовались бы для расчёта баланса на банковском счёте? Или для расчёта каких-то параметров умных датчиков? 

Да, что далеко ходить. В 1996 году ракета «Ариан-5» взорвалась во время выхода спутника на орбиту по причине переполнения, возникшего в программе, отвечающей за работу двигателя.

Или вспомним 15 год. Когда федеральная администрации авиации США предупредила всех операторов Боингов 787, о том, чтобы они периодически перезагружали всю электронику в самолётах. 

Это было связано с косяком зашитом в прошивке. Счётчик времени работал от начала запуска считая время в сотых долях секунд. В итоге примерно каждые 249 дней счётчик переполнялся и начинались кошмарные глюки. 

Сами понимаете, словить такую ошибку в полёте было бы совсем не по кайфу. И всё это происходит именно из-за особенностей работы языка программирования Си.

Он попросту не расширяет автоматически "размер" ячеек памяти для хранимых данных, даже если происходит переполнение. Тогда, как в других, более современных языках программирование (в том же Python), данная проблема уже решена по дефолту за счёт автоматического изменения типа данных.

В рейтинге самых опасных "уязвимостей" программного обеспечения за 2020 год, Integer Overflow занимает почётное 11 место. И учитывая, сколько уязвимостей в принципе существует, это достаточно большая проблема.

Переполнение буфера

По договорённости, строки в Си — это набор байт, который заканчивается нулевым байтом. Можно записывать строку в двойных кавычках, тогда компилятор сам поймёт, где ставить нулевой байт.

Давайте сходу рассмотрим пример. Тут мы видим переменную Permissions. У неё тип без знаковый int, и она равна числу записанному в двоичной системе счисления, оно состоит из трёх ноликов и представляет собой без знаковой число.

Помимо этого, есть 2 переменные с размером 5 байт. В них мы записываем какие-то данные. И затем ниже выводим эту историю print эфом. Где же хранятся все эти переменные?

Конечно же в памяти. Данные складываются по порядку в специальном сегменте – стеке, который работает по принципу - последний пришёл, первый ушёл. Этот подход придумал старик фон Нейман.

А теперь представим, что пользователь введёт в эту программу никнейм или пассворд значительно длиннее 4 символов. В данном конкретном случае это приведёт к достаточно интересным последствиям. 

По сути, он сможет буквально "переписать" всё, включая права. Проблема в том, что массивы в Си — это фактически, указатели на участки в памяти (их ещё иногда зовут адресами).

И компилятор Си не проверяет, что вы выходите за границы участков, которые были изначально объявлены. Отсутствие проверки границ выделенных для хранения данных буферов — это, пожалуй, ключевая проблема, которая по итогу и приводит к возникновению большинства уязвимостей. 

Но самое страшно, что такое поведение наблюдается не только в коде, написанном нами или другими программистами, но и в стандартной библиотеке Си присутствуют функции, которые при работе с подобными буферами не контролируют размеры источника и приёмника данных.

При этом не обязательно только "писать" за границы, вполне можно и "читать" (если опустить \0), тогда злоумышленник получит возможность прочитать данные, для этого не предназначенные.

Почему это работает таким образом – вопрос философский. Как и в компьютерных сетях, которые являются моей профильной специализацией на протяжении многих лет, ситуация банально до чёртиков.

Просто в то время, когда это всё проектировалось, вопрос безопасности не стояли ребром. Приоритетным был вопрос эффективности. Поэтому никто особо не парился и среднестатистическому программисту давался полный контроль. 

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

Однако на само деле, всё не так грустно. Конечно же, разработчики компиляторов знают о проблеме и периодически предлагают интересные варианты решений.

Так, если мы скомпилируем приложение с опцией -fstack-protector-all* в gcc, то будут вставлены дополнительные проверки. Это в свою очередь ударит по производительности и обрушит лажовое приложение. 

Нормальные программисты включают ftack-protector по умолчанию. Суть в том, что пользователь банально не сможет вбить такие данные, с помощью которых он сможет изменить поведение программы или прочитать секретные данные. 

Компилятор поставит защиту, чтобы программа падала в момент попытки. Но, по правде говоря, это не панацея от нападений. Ведь даже если злоумышленник не сможет получить повышенные привилегии или извлечь данные, то он сможет банально обрушать ваш сервис, засылая "плохие" данные с завидной периодичностью. Проще говоря DoS’ить сервис.

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

Так что достаточно иметь уязвимость в библиотеке, а не в коде, написанном программистом. Кроме того, есть целое отдельное направление - реверс-инжиниринг, которое при отсутствии исходников позволяет находить "узкие" места в приложении.

Ну и не стоит забывать про fuzzing. Т.е. "бомбардировку" приложения случайными данными. Таким образом хаЦкеры пытаются найти дыры незащищённые от подобных бомбёжек.

Credentials, Activation

Ух и настращал я вас нынче. Круче чем Соловьёв на пару с Милоновым. Ну ничего. Всё во благо развития движения безопасников. Ладненько. Ещё один вид уязвимостей – это хранение так называемых Credentials.

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

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

Это вполне могут быть данные, с которыми само приложение подключается к внешним ресурсам. Сейчас встречается довольно часто, поскольку разработчику проще зашить данные подключения прямо в бинарный файл. 

Поэтому и в рейтингах она находится значительно ниже и занимает почётное 20-е место. Также следует поговорить о пробелах оффлайн-активации. Во многих приложениях присутствуют механизмы позволяющие активировать прогу на месте.

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

Или разгадать алгоритм генерации ключей, то вполне можно "пропатчить" ПО или на худой конец создать генератор ключей путём ревёрс-инжмниринга о котором я уже упоминал ранее. 

Сегодня данные способы становятся всё менее популярными, поскольку большая часть ПО либо уже работает в облаке, либо является клиентом облачного сервиса, либо вообще периодически требует подключения к сервисам активации.

Ну а те, кто всё-таки, кто продолжает выпускать оффлайновые версии своих программулин, используют различные защитные антиотладочные механизмы. Бороться с пиратством это правда особо не помогает, но как плацдарм для практики начинающих взломщиков – вполне себе годно.

Input Validation

Ещё один вид распространённых ошибок – это Input Validation. Т.е. валидация вводимых пользователем данных. Этот момент очень важен. Ведь даже если мы избавились от возможности выйти за границы буфера, безопаснее от этого прога не стала.

Поэтому любые данные, приходящие от пользователя, стоит валидировать достаточно строго. Чаще всего для этого есть несколько подходов. Первый – это whitelist (он же разрешённые списки) и blacklist (запрещённые списки).

Если речь заходит о сетевой безопасности, то стоит проверять на допустимость адреса хостов, их имена, либо шаблоны - например, 192.168.0.x – это в whitelist. Всё остальное в black. 

Также можно агриться на запрещённые имена. Шаблоны, в которых выявляется, отсутствие пробелов, знаков и т.д. Отмечу, что в сетевой безопасности чаще используется подход whitelist, поскольку достаточно сложно указать все запрещённые символы.

Ну и конечно стоит сказать, что если забить и полностью вырубить валидацию пользовательского ввода, то злоумышленнику будет достаточно банальной команды ;sh, которая мигом предоставит на блюдечке shell с правами, с которыми запущено приложение.

И если ваш веб-сервер или ваша программа запущена из-под Рута через systemd, то пиши пропало. Последствия от такого несанкционированного доступа будете разгребать ещё долго.

Окей, друзья. Несмотря на то, что современная разработка ПО имеет практически полу-вековую историю, безопасней от этого она не становится. В программах до сих пор встречаются баги и уязвимости.

Основополагающие принципы, на которых стоит весь современный IT-мир это будто Колос, но на глиняных ножках. Он стоит и исправно работает ровно до того момента, пока кто-нибудь не брызнет на него водой из детского пистолета.

Не надо из-за этого впадать в депрессуху. Просто примите это, как данность и отблагодарите меня жирнейшим лайкосиком за сегодняшний выпуск. Я очень старался и собирал для вас материал, который в открытом доступе х** найдёшь.

Надеюсь, что когда-нибудь, боги сполна вознаградят меня за подобную самоотверженность. А если даже и нет, пофигу. Главное, что я кайфую от того, что делюсь с вами ценнейшими знаниями.

Не забываем подписываться на канал, писать в комментариях, какие темы вы бы хотели увидеть в следующих выпусках и, быть может, я однажды даже прислушаюсь к вашему мнению.

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

Берегите себя и свои проги. Не пишите мудацкий код. Заботьтесь в безопасности. И будем вам счастье. До новых встреч, мои кайфные друже. Всем пока.

Не можешь понять, куда делись видео по взломам и хакингу? Они переехали в наш уютный паблик в телеге

telegram chanel

Хочешь больше контента? Подписывайся на YouTube-канал!

Курс «Диплом за неделю»

Пособие «Библия вардрайвинга»

Курс Cisco «CCNA: Introduction to Networks»

© 2022. IT-спец. Денис Курец.