Издательский дом ООО "Гейм Лэнд"СПЕЦВЫПУСК ЖУРНАЛА ХАКЕР #71, ОКТЯБРЬ 2006 г.

утечка мозга

КРИС КАСПЕРСКИ АКА МЫЩЪХ

Спецвыпуск: Хакер, номер #071, стр. 071-020-3


struct slist *next;

struct slist *first;

struct slist *last;

};

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

#define STR_SIZE 256

struct slist

{

unsigned int len;

unsigned char buf[STR_SIZE];

struct slist *prev;

struct slist *next;

struct slist *first;

struct slist *last;

};

Размер буфера может быть как фиксированным, так и динамическим. Хорошей стратег выделяет под первый элемент списка 64 байта, под второй 128 байт и так далее, вплоть до 1000h, что позволяет обрабатывать как длинные, так и короткие сроки с минимальным оверхидом.

Списки на 100% защищены от ошибок переполнения (исключение составляют попытки обработать строку свыше 2 Гб, вызывающую исчерпание свободной памяти, но, во-первых, исчерпание - это все-таки не переполнение, и заслать shell-код злоумышленник не сможет, а во-вторых, это явная ошибка, которую легко обработать, установив предельный лимит на максимально разумную длину строки).

Хуже другое. Реализовав свою библиотеку для работы со «списочными строками», мы будем вынуждены переписать все остальные библиотеки, создавая обертки для каждой строковой функции, включая fopen, CreateProcess и т. д., поскольку все они ожидают увидеть непрерывный массив байт, а вовсе не список! Это чрезвычайно утомительная работа, но зато когда она будет закончена, о переполнениях можно забыть раз и навсегда. Правда, производительность (за счет постоянного преобразования типов) падает весьма значительно...

А вот более быстрое, но менее надежное решение. Отказываемся от стековых буферов, переходя на динамическую память. Выделяем каждому блоку на одну страницу больше и присваиваем последней странице атрибут PAGE_NOACCESS, чтобы каждое обращение к ней вызывало исключение, отлавливаемое нашим обработчиком, который в зависимости от ситуации либо увеличивал размер буфера, либо завершал работу программы с сообщением об ошибке. На коротких строках оверхид весьма значителен, но на длинных он минимален. Но к сожалению, такая защита страхует лишь от последовательного переполнения, но бессильна предотвратить индексное (подробнее о видах переполнения можно прочитать в моей книжке «shellcoders's programming uncovered», которую можно найти в Осле), к тому же переход на динамические массивы порождает проблему утечек памяти и получается так, что одно лечим, а другое калечим.

Тем не менее, лишний раз подстраховаться никогда не помешает! Чтобы защититься от переполнения кучи (которое в последнее время приобретает все большую популярность) после вызова любой функции, работающей с динамической памятью, необходимо защищать служебные данные кучи атрибутом PAGE_NOACCESS, а перед вызовом функции — снимать их. Для этого нам, опять-таки, потребуется написать обертки вокруг всех функций менеджера памяти, что требует времени. К тому же, в реализации кучи от Microsoft Visual C++, служебные данные лежат по смещению -10h от начала выделенного блока, а защищать мы можем только всю страницу целиком. Поэтому, во-первых, необходимо увеличить размер каждого блока до 512 Кбайт, чтобы начальный адрес совпадал с началом страницы, а во-вторых, использовать блок только со второй страницы. В результате, при работе с мелкими блоками мы получаем чудовищный оверхид, но зато компенсируемый надежной защитой от переполнения. Так что данный метод, при всех его недостатках, все-таки имеет право на жизнь.

Назад на стр. 071-020-2  Содержание  Вперед на стр. 071-020-4