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

Security Advisory (2458511)
спецы из Редмонда бодро отрапортовали, что они
уже взялись за расследование этой проблемы и вот-вот ее решат, но видите ли пока
не знают, в чем собственно там у них дело и что это вообще такое. При этом,
однако, было добавлено, что уязвимость уже активно используется "людями в черных
шляпах", а связана она, как ни странно, с неким явлением под названием memory
corrupt. Посоветовали не отключать DEP (якобы это должно помочь), забегая
немного вперед скажу, что это они приврали немного, а ASLR вообще от атаки на
эту уязвимость защищает как лист картона от пули. Дальше у мелкомягких инженеров
шел внушительный список affected систем.

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

 

Шаг первый: поиск готового эксплойта

Эксплойт не заставил себя долго ждать. Добрый человек по имени Matteo Memelli
не стал жадничать, а выложил его во всеобщее обозрение, найти его можно к
примеру здесь:

www.exploit-db.com/exploits/15421
.

 

Шаг второй: разбираем суть эксплойта.

Эксплойт, написанный на жабаскрипте, состоит из двух частей. Первая —
классическим способом (HeapSpray) загаживает кучу Ослика своим slice’ом (0x0d —
xor eax,0d0d0d0dh), дописывая в его конец простенький шелл-кодик, открывающий
4444-порт для передачи принятых данных в командную консоль. Написать похожий (и
даже лучший) можно за несколько минут/часов в Metasploit Framework’e.

Вторая часть вписывает в текущий HTML строку следующего содержания:

<table style=position:absolute;clip:rect(0)>

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

 

Шаг третий: ищем memory corrupt

Создаем HTML-документ, содержащий в себе одну-единственную строчку,
означенную выше, и даем IE ее немного пожевать. Жует он недолго, колесо ему явно
не по вкусу — выбрасывается окошко исключения по адресу: 0х0d7dc3c9. Надеюсь, ты
не забыл установить Olly в качестве Just-in-time отладчика? Если нет, то нажав
кнопку "отмена" мы увидим окно любимого отладчика с бесценной информацией,
касающейся: во-первых адреса возврата (7dc51a8d), указывающего куда-то в область
многострадальной mshtml (а куда же еще?), а во-вторых регистра еах (7dbe33f1).
На данном этапе отладчик нам ничего более полезного не сообщит, поэтому
запускаем IDA Pro, травим ее на mshtml.dll и даем ей загрузить в автоматическом
режиме отладочные символы с известного сервера.

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

mov ecx,edi — указатель this на *VTLB
call [eax+30h] — вызов виртуального метода с косвенной адресацией

Чуть ранее регистр еах инициализируется значением *VTLB, соответственно это
значение записано в нем на момент краха. Обрати внимание на младший байт
регистра. Ничего не замечаешь? Правильно! Он равен 1, т.е. адрес, записанный в
него, не выровнен по границе двойного слова. Чтобы понять, какие последствия за
этим следуют, необходимо пройти в дизассемблере по адресу 7dbe33f0. Там мы
обнаруживаем VTLB класса CDispContainer, по смещению 30 от начала которого
расположено аж 3! указателя на функцию CCursorConsumer, адрес которой выглядит
так: 0dc9c379. Понятно? Если мы сдвинем смещение в VTLB на 1 байт, то получим на
выходе по смещению 30 уже адрес 0d7dc3c9, который и фигурировал в мессаге об
исключении. Адрес же этот, как водится, указывает в область кучи, да к тому же
на момент исключения никак не инициализируется и вообще не выделяется.

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

Но прежде, чем приступать к тесту этих грязных планов, попробуем разобраться
— где именно происходит этот memory corrupt. Ты спросишь: "А на фига оно нам?
Ведь сплойт то уже готов — бери и юзай, чего огород городить?" Так-то оно так,
только нормальный профи всегда разберется в сути проблемы: во-первых просто
интересно, а во-вторых — беда носит достаточно глобальный характер и когда
прогеры заткнут эту течь, разве не приятно будет самому найти такую же, тем
более, что сплойт будет уже обкатанный и заменить в нем нужно всего одну
строчку?

Короче, кому не интересно, листайте этот абзац. Смотрим какому методу
принадлежит call [eax+30h]. Выясняется, что это Ensure DispNodeBackground
(7dc51a3f). И на момент его вызова VTLB контейнера уже повреждена (указатель на
нее передается ему в качестве неявного аргумента this). Вызывается же он из
EnsureTableDispNode. Там мы и будем искать corrupt. Для разбора полета нам снова
будет необходим отладчик. Аттачимся им к новому экземпляру ослика и ставим бряк
по адресу 7dc85fd0 (посмотрел раньше адрес EnsureTableDispNode?). Открываем наш
подопытный файл в ослике снова, ждем бряка, а дождавшись начинаем потихоньку
трассировать. Достаточно быстро выходим на адрес 7dc8609f, где, согласно
отладочной информации, гнездится NewCDispContainer. Из названия функции, думаю,
все понятно. Далее с новым контейнером программа пытается что-то сделать, а
именно по адресу 7dcea161 происходит замес с аттрибутом clip (которому, кстати,
передается структура tagRECT, инициализированная нулями — вспомни текст сплойта)
вызовом SetUserClip. Этой функции в качестве неявного аргумента this передается
указатель на VTLB экземпляра CDispContainer, созданного ранее. Зайдя внутрь этой
функции можно увидеть ряд математических преобразований, выполняемых со вторым
двойным словом VTLВ и содержимым структуры RECT, в результате которых по замыслу
разработчиков должно получиться смещение в глобальном массиве extraSizeTable, но
наше смещение равно нулю, смещение из размера таблиц тоже нулевое и в результате
у функции не выходит получить нужный ей элемент в VTLB и инструкция:

7ddce3b9 or dword ptr[eax],1

тупо коробит первый байт указателя на контейнер, выдавая необходимое для
атаки смещение адресов.

 

Шаг четвертый: тестим вторую часть эксплойта

Взглянем еще раз на эксплойт. Вот ключевые моменты порчи памяти подопытного
животного:

function alloc(bytes, mystr) {

var shellcode = unescape('%u9090%u9090%ue8fc%u0089.......') //тут у нас массив с
шеллом

while (mystr.length< bytes) mystr += mystr; //тут мы создаем непомерную slice
строку длиной в bytes
return mystr.substr(0, (bytes-6)/2) + shellcode; } //тут мы втыкаем шелл и
возвращаем полученную строку

var evil = new Array();
var FAKEOBJ = unescape("%u0d0d%u0d0d"); //зародыш slice-массива
//FAKEOBJ = alloc(233120, FAKEOBJ); // IE6 mshtml.dll Version 6.0.2900.5512
//FAKEOBJ = alloc(241748, FAKEOBJ); // IE6 mshtml.dll Version 6.00.2900.6036
//FAKEOBJ = alloc(733120, FAKEOBJ); // IE7 mshtml.dll Version 7.00.6000.17080

//FAKEOBJ = alloc(433120, FAKEOBJ); // IE8 mshtml.dll Version 8.00.6001.18939

//FAKEOBJ = alloc(1294464, FAKEOBJ); // IE8 mshtml.dll Version 8.00.6001.18975

//FAKEOBJ = alloc(1550371, FAKEOBJ); // oy oy oy huge spray!
for (var k = 0; k < 1000; k++) {
evil[k] = FAKEOBJ.substr(0, FAKEOBJ.length);//этот цикл, собственно и выделяет
память, заполняя массив элементами-строками
//с шелкодом
}

Комментарии, заботливо оставленные Matteo, информируют нас, что в разных
версиях браузера память нужно выделять разными блоками.

Ок, раскомментируем свою версию (у меня старенькая шестерка — на ослах не
езжу), во избежание сюрпризов от автора шелла меняем первые пять-десять байт
шелла на %uCCCC и запускаем сплойт. Хлоп — выскочившее исключение знакомым
адресом 0d7dc3c9 красноречиво говорит, что никакой шелл-код никакого управления
не получал. Хорошо, увеличим размер кучи-в-куче, раскомментировав по очереди
остальные строчки по методу brute-force. Только на последнем варианте (в моем
случае) ослик впал в глубокую задумчивость, минуты 4 жевал брошенный ему код и в
итоге выдал адрес: 0d80a841. Дрожащими руками жму "отмена" и в отладчике по
означенному адресу с удовлетворением вижу инструкцию int3, выше нее много-много
or eax,0d0d0d0d, а ниже — шеллкод во всей его бинарной красе (уже
дизассемблированный).

Быстро смотрим в карту памяти. Необходимый нам блок начинается с адреса
0d690000 и имеет размер 17b000 и атрибуты rw, которые без включенного
перманентного DEP означают "исполняй чего хочешь". А как быть с DEP? Это тема
отдельного разговора. Но наметки готовы уже сейчас. Кто может помешать нам
портить кучу не строками с эксплойтом, а SWF-файлами с эксплойтом, меняющим
аттрибуты страниц? Правильно — никто (кроме закона, конечно). А клоню я к методу
профессора Блазакиса (всем уже известная технология
JIT-spray).

Тонкости, как всегда, есть и тут. Например — время заполнения кучи. Такой
внушительный объем памяти выделяется, а тем более инициализируется, далеко не
мгновенно, в чем мы уже убедились (несмотря на заверения Matteo, что это очень
quick and dirty exploit). Квиком тут и не пахнет, а если мы будем читать в
память файлы — юзверь уснет, пока мы его атакуем. Или, засуетившись, кликом по
известному кресту убьет наши начинания в зародыше, решив, что ослик в коме.
Другой момент — гранулярность выделения памяти (ограничения JIT-spray техники,
которые обусловлены конструктивными особенностями исполнения флеш-файлов).
Несложно подсчитать, что под полезное тело эксплойта нам остается 0d7dd000
(следующая страница) — 0d7c3c9 (текущий адрес) = c37 байт под рабочий код, что
не так уж много. Шеллу придется сильно похудеть, чтобы успеть сделать
выполняемой страницу с основным шеллкодом и прыгнуть на нее. Ну и, наконец,
ослик, в итоге все равно падает, высаживая клиента на измену (попробуй ради
смеха одну из инструкций сс(int3) заменить на с3(ret) — следующий виртуальный
вызов из этой VTLB попробует скакнуть в область старших системных адресов). Так
что перед возвращением управления основной программе нужно либо уничтожать
объект (исправляя за прогеров их ляп), либо чесать репу как скакнуть сразу на
обработку следующего тега, не кончая с этим.

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

 

Шаг пятый: выход на глобальный уровень

Заткнув течь, Корпорация не решит проблем ООП с вызовом виртуальных методов
класса в С++. И эта дыра в mshtml.dll далеко не единственная. Обнаружить другие
поможет поиск инструкций логических операций с косвенным операндом и контектный
анализ окружающего кода. Если перед этим регистр, указывающий на приемник,
инициализируется указателем на VTLB, то нам остается только выяснить из
отладочной информации что это за метод, после чего нарисовать простенький
жабаскрипт, вызывающий его со значениями "от нуля и до …", в общем до очень
больших значений пока осел не ляжет.

Кто-то скажет: "Неэффективно, неавтоматизированно, слишком много человеческой
работы, долго… !" Так-то оно так, только разработчики ведь тоже поставлены в
такие же рамки, как и мы, а значит, что чем менее процесс автоматизирован, тем
меньше у них шансов быстро переловить все такие баги. Ручками ковыряться в
дизассемблерных листингах они ох как не любят. Используя подобный прием ваш
покорный слуга уже откопал пару прелюбопытных насекомых. Но о них я тут писать
не буду, кому надо — сам найдет.

Так что, как говорится, продолжение следует…

Оставить мнение

Check Also

Безопасность смарт-контрактов. Топ-10 уязвимостей децентрализованных приложений на примере спецификации DASP

Количество смарт-контрактов в блокчейне Ethereum только за первую половину 2018 года вырос…