Подробнее об этом хаке и особенностях его работы можно узнать из доклада на !!con 2020 «Playing Breakout… inside a PDF!!»

Если вы его не смотрели, то попробуйте открыть файл breakout.pdf в Chrome.

Как и многие из вас, я всегда считал PDF довольно безопасным форматом: автор создаёт текст и графику, после чего он открывается в программе просмотра PDF, больше ничего не делая. Несколько лет назад я мимоходом слышал об уязвимостях Adobe Reader, но особо не задумывался о том, как они могут возникать.

Изначально Adobe сделала PDF именно для этого, но мы уже выяснили, что сегодня это совсем не так. В 1310-страничной спецификации PDF (на самом деле довольно понятном и интересном чтиве) описывается безумное количество возможностей, в том числе:


но самое интересное для нас…


Разумеется, большинство программ для чтения PDF (кроме Adobe Reader) не реализует основную часть этих возможностей. Однако Chrome реализует JavaScript! Если вы откроете подобный файл PDF в Chrome, то он запустит скрипт. Я выяснил, повторив действия из этого поста о создании PDF с JS.

Однако здесь есть хитрость. Chrome реализует только крошечное подмножество огромной поверхности Acrobat JavaScript API. Реализация API в PDFium браузера Chrome в основном состоит из подобных заглушек:

FX_BOOL Document::addAnnot(IJS_Context* cc,
                           const CJS_Parameters& params,
                           CJS_Value& vRet,
                           CFX_WideString& sError) {
  // Not supported.
  return TRUE;
}
FX_BOOL Document::addField(IJS_Context* cc,
                           const CJS_Parameters& params,
                           CJS_Value& vRet,
                           CFX_WideString& sError) {
  // Not supported.
  return TRUE;
}
FX_BOOL Document::exportAsText(IJS_Context* cc,
                               const CJS_Parameters& params,
                               CJS_Value& vRet,
                               CFX_WideString& sError) {
  // Unsafe, not supported.
  return TRUE;
}

И я понимаю опасения разработчиков — этот Adobe JavaScript API имеет совершенно огромную площадь поверхности. Предположительно, скрипты могут выполнять такие действия, как соединение с произвольными базами данных, распознавание подключенных мониторов, импорт внешних ресурсов и манипулирование 3D-объектами.

Поэтому в Chrome получилась такая странная ситуация: мы можем выполнять произвольные вычисления, однако имеем эту странную, ограниченную поверхность API, при которой ввод-вывод и передачу данных между программой и пользователем реализовывать очень неудобно.

Вероятно, можно встроить в PDF компилятор C, скомпилировав его в JS, например, с помощью Emscripten, но тогда компилятор C должен будет получать ввод из формы простого текста (plain text), а вывод выполнять снова в поле формы.

На самом деле, я заинтересовался PDF пару недель назад из-за PostScript; я читал посты Дона Хопкинса о NeWS — напоминающей AJAX системе, но реализованной в 80-х на PostScript.

Забавно, что PDF стал реакцией на PostScript, который был слишком выразительным (являясь полнофункциональным языком программирования), слишком сложным в анализе и в��сприятии. Наверно, PDF по-прежнему остаётся в этом плане шагом вперёд, но всё равно смешно, что он разросся всеми этими возможностями.

Ещё один любопытный момент: как любой долгоживущий цифровой формат (лично я испытываю нежные чувства к файловой системе FAT), PDF сам по себе является своего рода историческим документом. Мы можем отследить, как поколения инженеров добавляли нужные им в своё время функции, пытаясь при этом не поломать уже существующие.

Я не совсем понимаю, зачем разработчики Chrome вообще заморачивались поддержкой JS. Они взяли код программы чтения PDF из Foxit; возможно, у Foxit был какой-то клиент, использовавший валидацию форм через JavaScript?

Кроме того, Chrome использует тот же рантайм, что и в браузере, хоть и не раскрывает браузерных API. Насколько я понимаю, это значит, что можно использовать такие возможности ES6, как стрелочные функции и прокси.

Breakout


Так что же мы можем сделать с предоставляемой нам Chrome поверхностью API?

Кстати, должен извиниться за неидеальное распознавание коллизий и непостоянную скорость игры. БОльшую часть игры я содрал с туториала.

Первые доступные пользователю точки ввода-вывода, которые я смог найти в реализации PDF API браузера Chrome, находились в Field.cpp.

Мы не можем менять цвет заливки текстового поля во время выполнения, зато можем менять прямоугольник его границ и задавать стиль границ. Мы не можем считывать точное положение мыши, однако можем при создании PDF привязать к полям скрипты mouse-enter и mouse-leave. Также во время выполнения нельзя добавлять поля: придётся ограничиться тем, что мы поместили в PDF в момент создания. Любопытно, почему разработчики выбрали именно эти методы? Это похоже на какой-то стереотип о программировании на олдскульном FORTRAN: необходимо объявлять все переменные заранее, чтобы компилятор мог статически выделить под них память.

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

  • Ракетку
  • Кирпичи
  • Мяч
  • Очки
  • Жизни

Но для правильной работы игры мы также внесём некоторые хаки.

Во-первых, мы создаём тонкую длинную «полосу» текстового поля для каждого столбца нижней половины экрана. Полосы получают событие mouse-enter, когда игрок перемещает мышь по оси X, поэтому ракетка может перемещаться при движении мыши.

Во-вторых, мы создаём поле под названием «whole», закрывающее всю верхнюю половину экрана. Chrome не ожидает, что отображение PDF будет меняться, поэтому если перемещать поля в JS, то получатся довольно сильные артефакты. Это поле «whole» решает данную проблему — мы включаем/отключаем его во вр��мя рендеринга кадра. Этот трюк заставляет Chrome подчищать артефакты.

Кроме того, при перемещении поля, похоже, сбрасывается его поток внешнего отображения. Выбранный вами красивый внешний вид из произвольной PDF-графики «слетает» и заменяется простым залитым прямоугольником с границей. Поэтому в моей игре используется упрощённый словарь характеристик внешнего вида. В самом крайнем случае указанный там цвет заливки при перемещении виджета остаётся неизменным.

Полезные ресурсы





На правах рекламы


Закажите сервер и сразу начинайте работать! Создание VDS любой конфигурации в течение минуты, в том числе серверов для хранения большого объёма данных до 4000 ГБ. Эпичненько :)