Содержание статьи
- VM в шрифтах?
- Рейкастинг на пальцах
- Архитектура
- Компилятор
- Баги
- Хинтинг как поверхность атаки
- Как выглядит атака через шрифт
- Duqu (2011): шрифт, который ломал ядро
- Google Project Zero и год фаззинга шрифтов
- Operation Triangulation
- Scriptless-атаки через хинтинг
- Как от этого защищаются
- Windows: fontdrvhost.exe
- Windows: Untrusted Font Blocking
- Мораль
VM в шрифтах?
Да, в шрифтах действительно есть виртуальная машина, и она вполне может быть интересна как необычный вектор атаки.
Когда ты открываешь страницу, браузер загружает шрифт и должен как‑то нарисовать буквы. Казалось бы, берешь векторные контуры глифов и растеризуешь в нужном размере. Проблема в том, что если просто отмасштабировать вектор и залить пиксели, на мелких кеглях получается каша: тонкие засечки пропадают, вертикальные штрихи прыгают между пикселями, а элементы, которые дизайнер задумал одинаковой толщины, получаются разными. В 90-х, когда экраны были 72–96 dpi, это было катастрофой.
В 1991 году программисты Apple придумали решение: пусть сам шрифт содержит программу, которая подсказывает растеризатору, как правильно натянуть контуры на пиксельную сетку. Она называется хинтингом (от английского hint — «подсказка») и выполняется в стековой машине с почти 200 инструкциями.
В Microsoft подхватили идею — так TrueType стал стандартом де‑факто. Сегодня хинтинг есть в каждом файле TTF, и каждая буква на экране — результат выполнения нескольких сотен инструкций этой виртуальной машины. Интерпретатор живет в системе FreeType в Linux и Android, в Windows это называется DirectWrite, а в macOS — Core Text. До Windows 10 Anniversary Update разбор шрифтов вообще выполнялся в ядре (win32k.sys) — к этому мы еще вернемся в разделе про безопасность, будет интересно.
Так вот, набор инструкций этой VM оказался… щедрым. Слишком щедрым для задачи «сдвинь вершину контура на полпикселя». Там есть условные переходы (IF, ELSE и EIF), рекурсивные вызовы функций (FDEF и CALL), область памяти Storage Area на 65 535 ячеек, арифметика и логика. Все, что нужно, чтобы машина оказалась Тьюринг‑полной.
Кто‑то должен был использовать это для чего‑нибудь поинтереснее счетчика. Я решил: почему бы не Doom?
Рейкастинг на пальцах
Прежде чем лезть в байткод, разберемся, как вообще работает 3D в Doom и Wolfenstein 3D.
Идея рейкастинга гениальна тем, что позволяет рисовать псевдо-3D на железе, которое не умеет работать с 3D. Карта — это двумерная сетка 16×16 клеток, в каждой — пустота или стена. Игрок стоит где‑то на сетке и смотрит в какую‑то сторону.
Дальше алгоритм такой: экран делим на вертикальные полоски (например, 16 — по одной на каждый столбец). Для каждой полоски из позиции игрока «бросаем» луч в направлении этой части экрана. Луч летит по двумерной карте, пока не упрется в стену. Измеряем расстояние до стены. Чем дальше стена, тем ниже мы рисуем ее на экране. Все, 3D готово.
Тонкость в том, что «летит по карте» — это не буквально на пиксель за раз: так можно проскочить стену между шагами. Используется алгоритм DDA: прыгаем сразу до следующей границы клетки — горизонтальной или вертикальной, смотря какая ближе. Так гарантированно не проскочим.
Джон Кармак в 1992 году делал это на 386-м процессоре без FPU, поэтому все считалось в целых числах с эмуляцией дроби (fixed-point), а синусы и косинусы брались из предвычисленных таблиц. Сейчас это покажется нам очень знакомым — потому что внутри хинтинга TTF мы окажемся ровно в той же ситуации.
Архитектура
Идея TTF-DOOM — разделить обработку на две части.
JavaScript на странице читает клавиатуру (WASD и стрелки), отслеживает позицию игрока и угол поворота, рисует на Canvas все, для чего нужны спрайты: врагов, оружие, HUD. А шрифт делает тяжелую геометрическую работу: получает координаты игрока, бросает лучи, считает расстояния до стен и возвращает массив высот. По сути, это GPU, только очень странный и медленный.
Вопрос: как передать шрифту данные, если шрифт — статический файл? Через CSS, как ни странно.
В OpenType есть variable fonts — шрифты с «осями вариации». Ты задаешь в CSS такую строчку:
font-variation-settings: 'MOVX' 123, 'MOVY' 456, 'TURN' 789
А потом браузер должен перезапустить хинтинг при каждом изменении. В хинтинге есть специальная инструкция GETVARIATION (опкод 0x91) — она кладет на стек текущие значения всех осей. Вот твой IPC между миром JavaScript и миром стековой машины.
Продолжение доступно только участникам
Материалы из последних выпусков становятся доступны по отдельности только через два месяца после публикации. Чтобы продолжить чтение, необходимо стать участником сообщества «Xakep.ru».
Присоединяйся к сообществу «Xakep.ru»!
Членство в сообществе в течение указанного срока откроет тебе доступ ко ВСЕМ материалам «Хакера», позволит скачивать выпуски в PDF, отключит рекламу на сайте и увеличит личную накопительную скидку! Подробнее
