внутри сети ДРОЗДОВ АНДРЕЙ AKA SULVERUS Спецвыпуск: Хакер, номер #072, стр. 072-044-4 [исследуем управление памятью.] Ранее я уже упоминал о системе автоматического управления памятью, теперь рассмотрим ее детальнее. Основным принципом работы сборщика мусора является отслеживание объектов в памяти. Когда создается какая-нибудь переменная или объект в динамической памяти, ему отводится специальное адресное пространство, после этого создается специальная ссылка, указывающая на его адрес в памяти или на самое начало. Когда сборщик начинает свою работу, он делит всю динамическую память на несколько частей: root и остальные объекты. От корня сборщик отсчитывает все смещения на объекты в памяти и, кроме того, он сохраняет там все переменные и параметры функций. Алгоритм работы этого бота довольно прост: идет расчет ссылок от корней к объектам. Все объекты, ссылок на которые нет в корне, считаются мусором. Возможно, что в скором будущем хакеры научатся обманывать систему управления памяти, и тогда можно будет «обрубать» какие-то функции в приложении. Сборщик, выкинув «правильные функции» из памяти, может привести к сбою всю программу или даже систему выполнения. Сборщик запускается, как только количество объектов в памяти достигает определенного придела, - в это время все процессы приостанавливаются, и таким образом можно попробовать повлиять на них. Повлиять на корни будет гораздо сложнее, поскольку во время выполнения кода составляется специальная таблица корней (root table), аналогичная таблице импорта в C++, разница только в том, что в таблице корней хранится информация о наборе регистров процессора и объектах. Составив такую таблицу, сборщик проверяет все объекты в памяти и удаляет объекты, на которые нет ссылок в таблице. Когда весь мусор удален, сборщик начинает оптимизировать память, передвигая все объекты ближе к корням и уменьшая время выполнения кода приложения. [подробнее о верификации.] Из-за того, что компиляторы в .NET создают только безопасный код, невольно встает вопрос: а зачем вообще нужен верификатор кода? Во-первых, не все компиляторы действительно создают безопасный код (например, Visual C++ .NET compiler), во-вторых, даже в C# есть некоторые конструкции, приводящие к сбою программы (unsafe-методы), в-третьих, если злоумышленник захочет внедрить какой либо вредоносный код в программу, он сможет это сделать напрямую из ILasm'a. Рассмотрим три основных метода верификации в .NET: 1 ПРОВЕРКА НА СОВМЕСТИМОСТЬ ТИПОВ. 2 ПРОВЕРКА КОНФИГУРАЦИИ СТЕКА. 3 ПРОВЕРКА АЛГОРИТМОВ. С проверкой на совместимость типов все понятно: если типы несовместимы - возможны ошибки (например, если объектный тип сравнивать с типом-значением). При проверке конфигурации стека исключаются любые переполнения буфера, поскольку верификатор сравнивает количество слотов, зарезервированных программой и количество слотов, используемых методом. После такой проверки создается новая конфигурация стека, и согласно ей проверяется сам код. Ранее уже упоминалось, что весь код делится на неизменяемые и изменяемые данные. Проверка проходит следующим образом: берется массив данных (неизменяемые данные), содержащий инструкции, и берется некая конфигурация стека для каждой инструкции взятого массива. |