Я читал о более старом эксплойте против GDI + в Windows XP и Windows Server 2003, который называется JPEG смерти для проекта, над которым я работаю.
Эксплойт подробно описан в следующей ссылке: http://www.infosecwriters.com/text_resources/pdf/JPEG.pdf
По сути, файл JPEG содержит раздел под названием COM, содержащий (возможно, пустое) поле комментария и двухбайтовое значение, содержащее размер COM. Если комментариев нет, размер равен 2. Программа чтения (GDI +) считывает размер, вычитает два и выделяет буфер подходящего размера для копирования комментариев в куче. Атака включает размещение значения 0
в поле. GDI + вычитает 2
, что приводит к -2 (0xFFFe)
преобразованию значения в целое число без знака с 0XFFFFFFFE
помощью memcpy
.
Образец кода:
unsigned int size;
size = len - 2;
char *comment = (char *)malloc(size + 1);
memcpy(comment, src, size);
Обратите внимание, что malloc(0)
в третьей строке должен возвращаться указатель на нераспределенную память в куче. Как запись 0XFFFFFFFE
байтов ( 4GB
!!!!) может не привести к сбою программы? Записывается ли это за пределы области кучи и в пространство других программ и ОС? Что происходит потом?
Насколько я понимаю memcpy
, он просто копирует n
символы из места назначения в источник. В этом случае источник должен быть в стеке, адресат в куче, и n
есть 4GB
.
malloc
ed составляет всего 2 байта, а не0xFFFFFFFE
. Этот огромный размер используется только для размера копии, а не для размера выделения.Ответы:
Эта уязвимость определенно была переполнением кучи .
Вероятно, так и будет, но в некоторых случаях у вас есть время для использования уязвимости до того, как произойдет сбой (иногда вы можете вернуть программу к нормальному выполнению и избежать сбоя).
При запуске memcpy () копия перезапишет либо некоторые другие блоки кучи, либо некоторые части структуры управления кучей (например, список свободных, список занятости и т. Д.).
В какой-то момент копия обнаружит невыделенную страницу и вызовет AV (нарушение доступа) при записи. Затем GDI + попытается выделить новый блок в куче (см. Ntdll! RtlAllocateHeap ) ... но теперь все структуры кучи испорчены.
На этом этапе, тщательно обработав свое изображение JPEG, вы можете перезаписать структуры управления кучей контролируемыми данными. Когда система пытается выделить новый блок, она, вероятно, отсоединит (свободный) блок от свободного списка.
Блок управляется (в частности) с помощью указателей flink (прямая ссылка; следующий блок в списке) и мигания (обратная ссылка; предыдущий блок в списке). Если вы контролируете и мигание, и мигание, у вас может быть возможность WRITE4 (условие записи What / Where), где вы контролируете, что вы можете писать и где вы можете писать.
На этом этапе вы можете перезаписать указатель функции (указатели SEH [Structured Exception Handlers] были предпочтительной целью в то время, еще в 2004 году) и получить выполнение кода.
См. Сообщение в блоге « Коррупция кучи: пример из практики» .
Примечание: хотя я писал об эксплуатации с использованием списка фрилансеров, злоумышленник может выбрать другой путь, используя другие метаданные кучи («метаданные кучи» - это структуры, используемые системой для управления кучей; мигание и мигание являются частью метаданных кучи), но использование unlink, вероятно, является самым "легким". Поиск в Google по запросу "использование кучи" даст многочисленные исследования по этому поводу.
Никогда. Современные ОС основаны на концепции виртуального адресного пространства, поэтому каждый процесс имеет свое собственное виртуальное адресное пространство, которое позволяет адресовать до 4 гигабайт памяти в 32-разрядной системе (на практике у вас только половина ее в пользовательском пространстве, остальное для ядра).
Короче говоря, процесс не может получить доступ к памяти другого процесса (кроме случаев, когда он запрашивает об этом у ядра через какую-то службу / API, но ядро проверяет, имеет ли вызывающий право на это право).
Я решил протестировать эту уязвимость в эти выходные, чтобы мы могли лучше понять, что происходит, а не просто домыслы. Уязвимости сейчас 10 лет, поэтому я подумал, что можно написать об этом, хотя я не объяснил часть эксплуатации в этом ответе.
Планирование
Самой сложной задачей было найти Windows XP только с SP1, как это было в 2004 году :)
Затем я загрузил изображение в формате JPEG, состоящее только из одного пикселя, как показано ниже (вырезано для краткости):
Изображение JPEG состоит из двоичных маркеров (которые вводят сегменты). На изображении выше
FF D8
это маркер SOI (начало изображения), аFF E0
, например, маркер приложения.Первый параметр в сегменте маркера (за исключением некоторых маркеров, таких как SOI) - это параметр двухбайтовой длины, который кодирует количество байтов в сегменте маркера, включая параметр длины и исключая двухбайтовый маркер.
Я просто добавил COM-маркер (0x
FFFE
) сразу после SOI, поскольку маркеры не имеют строгого порядка.Длина сегмента COM установлена,
00 00
чтобы вызвать уязвимость. Я также ввел байты 0xFFFC сразу после маркера COM с повторяющимся шаблоном, числом 4 байта в шестнадцатеричном формате, что пригодится при «эксплуатации» уязвимости.Отладка
Двойной щелчок по изображению немедленно вызовет ошибку в оболочке Windows (также известной как "explorer.exe") где-нибудь
gdiplus.dll
в функции с именемGpJpegDecoder::read_jpeg_marker()
.Эта функция вызывается для каждого маркера в изображении, она просто: считывает размер сегмента маркера, выделяет буфер, длина которого является размером сегмента, и копирует содержимое сегмента в этот вновь выделенный буфер.
Вот начало функции:
eax
Регистр указывает на размер сегмента иedi
представляет собой количество байтов, оставшихся в изображении.Затем код переходит к чтению размера сегмента, начиная со старшего байта (длина - это 16-битное значение):
И младший байт:
Как только это будет сделано, размер сегмента используется для выделения буфера после этого вычисления:
alloc_size = размер_сегмента + 2
Это делается с помощью кода ниже:
В нашем случае, поскольку размер сегмента равен 0, размер, выделенный для буфера, составляет 2 байта .
Уязвимость сразу после выделения:
Код просто вычитает размер segment_size (длина сегмента составляет 2 байта) из всего размера сегмента (0 в нашем случае) и заканчивается целочисленным недополнением: 0-2 = 0xFFFFFFFE
Затем код проверяет, остались ли байты в изображении для анализа (что верно), а затем переходит к копии:
Приведенный выше фрагмент показывает, что размер копии составляет 0xFFFFFFFE 32-битных фрагментов. Исходный буфер контролируется (содержимое изображения), а место назначения - буфер в куче.
Условие записи
Копия вызовет исключение нарушения доступа (AV), когда достигнет конца страницы памяти (это может быть указатель источника или указатель назначения). Когда запускается AV, куча уже находится в уязвимом состоянии, потому что копия уже перезаписала все последующие блоки кучи, пока не будет обнаружена не отображенная страница.
Что делает эту ошибку уязвимой, так это то, что 3 SEH (структурированный обработчик исключений; это try / за исключением низкого уровня) перехватывают исключения в этой части кода. Точнее, 1-й SEH размотает стек, чтобы он вернулся к синтаксическому анализу другого маркера JPEG, таким образом полностью пропустив маркер, вызвавший исключение.
Без SEH код просто разрушил бы всю программу. Таким образом, код пропускает сегмент COM и анализирует другой сегмент. Итак, мы возвращаемся к
GpJpegDecoder::read_jpeg_marker()
новому сегменту и когда код выделяет новый буфер:Система отключит блок от бесплатного списка. Бывает, что структуры метаданных были перезаписаны содержимым изображения; поэтому мы контролируем разъединение с помощью контролируемых метаданных. Приведенный ниже код находится где-то в системе (ntdll) в диспетчере кучи:
Теперь мы можем писать что хотим и где хотим ...
источник
Поскольку я не знаю кода из GDI, то, что ниже, - всего лишь предположение.
Что ж, одна вещь, которая приходит в голову, - это одно поведение, которое я заметил в некоторых ОС (я не знаю, было ли это в Windows XP), было при выделении с помощью new /
malloc
, вы действительно можете выделить больше, чем ваша оперативная память, если вы не пишете в эту память.На самом деле это поведение ядра Linux.
С www.kernel.org:
Чтобы попасть в резидентную память, должна сработать ошибка страницы.
В основном вам нужно загрязнить память, прежде чем она будет фактически выделена в системе:
Иногда на самом деле не происходит реального выделения в ОЗУ (ваша программа все равно не будет использовать 4 ГБ). Я знаю, что видел такое поведение в Linux, но, тем не менее, я не могу воспроизвести его сейчас в моей установке Windows 7.
Исходя из этого поведения возможен следующий сценарий.
Чтобы сделать эту память существующей в ОЗУ, вам нужно сделать ее грязной (в основном memset или другую запись в нее):
Однако уязвимость использует переполнение буфера, а не сбой выделения.
Другими словами, если бы у меня было это:
Это приведет к записи за буфером, потому что не существует такого понятия, как сегмент непрерывной памяти размером 4 ГБ.
Вы ничего не вставляли в p, чтобы испачкать все 4 ГБ памяти, и я не знаю,
memcpy
делает ли память грязной сразу или просто постранично (я думаю, постранично).В конце концов, это приведет к перезаписи кадра стека (переполнение буфера стека).
Еще одна возможная уязвимость заключалась в том, что изображение хранилось в памяти в виде байтового массива (чтение всего файла в буфер), а размер комментариев использовался только для пропуска неважной информации.
Например
Как вы упомянули, если GDI не выделяет этот размер, программа никогда не выйдет из строя.
источник
malloc(-1U)
он обязательно потерпит неудачу, вернетсяNULL
иmemcpy()
рухнет.