- PVSM.RU - https://www.pvsm.ru -

Пугающие эксперименты с PDF: запускаем «Арканоид» в документе

Пугающие эксперименты с PDF: запускаем «Арканоид» в документе - 1

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

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

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

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

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


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

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

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 имеет совершенно огромную площадь поверхности [18]. Предположительно, скрипты могут выполнять такие действия, как соединение с произвольными базами данных [19], распознавание подключенных мониторов [20], импорт внешних ресурсов [21] и манипулирование 3D-объектами [22].

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

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

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

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

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

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

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

Breakout

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

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

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

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

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

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

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

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

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

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

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


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

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

Пугающие эксперименты с PDF: запускаем «Арканоид» в документе - 2 [40]

Автор: Mikhail

Источник [41]


Сайт-источник PVSM.RU: https://www.pvsm.ru

Путь до страницы источника: https://www.pvsm.ru/nenormal-noe-programmirovanie/360412

Ссылки в тексте:

[1] «Playing Breakout… inside a PDF!!»: https://www.youtube.com/watch?v=6rbJu10Telc

[2] файл breakout.pdf: https://cdn.jsdelivr.net/gh/osnr/horrifying-pdf-experiments@master/breakout.pdf

[3] 1310-страничной спецификации PDF: https://www.adobe.com/content/dam/Adobe/en/devnet/acrobat/pdfs/pdf_reference_1-7.pdf

[4] Встроенный Flash: https://www.adobe.com/content/dam/Adobe/en/devnet/acrobat/pdfs/pdf_reference_1-7.pdf#page=1123

[5] звука: https://www.adobe.com/content/dam/Adobe/en/devnet/acrobat/pdfs/pdf_reference_1-7.pdf#page=783

[6] видео: https://www.adobe.com/content/dam/Adobe/en/devnet/acrobat/pdfs/pdf_reference_1-7.pdf#page=784

[7] 3D-объектов: https://www.adobe.com/content/dam/Adobe/en/devnet/acrobat/pdfs/pdf_reference_1-7.pdf#page=789

[8] Web capture: https://www.adobe.com/content/dam/Adobe/en/devnet/acrobat/pdfs/pdf_reference_1-7.pdf#page=946

[9] Произвольные математические функции: https://www.adobe.com/content/dam/Adobe/en/devnet/acrobat/pdfs/pdf_reference_1-7.pdf#page=166

[10] Тьюринг-неполное подмножество PostScript: https://www.adobe.com/content/dam/Adobe/en/devnet/acrobat/pdfs/pdf_reference_1-7.pdf#page=176

[11] подмножество XHTML и CSS: https://www.adobe.com/content/dam/Adobe/en/devnet/acrobat/pdfs/pdf_reference_1-7.pdf#page=680

[12] файлов: https://www.adobe.com/content/dam/Adobe/en/devnet/acrobat/pdfs/pdf_reference_1-7.pdf#page=638

[13] коллекций файлов: https://www.adobe.com/content/dam/Adobe/en/devnet/acrobat/pdfs/pdf_reference_1-7.pdf#page=588

[14] скрипты JavaScript: https://www.adobe.com/content/dam/Adobe/en/devnet/acrobat/pdfs/pdf_reference_1-7.pdf#page=709

[15] стандартной библиотеки, совершенно отличающейся от библиотеки браузера: https://wwwimages2.adobe.com/content/dam/Adobe/en/devnet/acrobat/pdfs/js_api_reference.pdf

[16] этого поста о создании PDF с JS: https://mariomalwareanalysis.blogspot.com/2012/02/how-to-embed-javascript-into-pdf.html

[17] подобных заглушек: https://pdfium.googlesource.com/pdfium/+/chromium/2557/fpdfsdk/src/javascript/Document.cpp#258

[18] совершенно огромную площадь поверхности: https://wwwimages2.adobe.com/content/dam/Adobe/en/devnet/acrobat/pdfs/js_api_reference.pdf#page=3

[19] соединение с произвольными базами данных: https://wwwimages2.adobe.com/content/dam/Adobe/en/devnet/acrobat/pdfs/js_api_reference.pdf#page=36

[20] распознавание подключенных мониторов: https://wwwimages2.adobe.com/content/dam/Adobe/en/devnet/acrobat/pdfs/js_api_reference.pdf#page=537

[21] импорт внешних ресурсов: https://wwwimages2.adobe.com/content/dam/Adobe/en/devnet/acrobat/pdfs/js_api_reference.pdf#page=317

[22] манипулирование 3D-объектами: https://www.adobe.com/content/dam/Adobe/en/devnet/acrobat/pdfs/js_3d_api_reference.pdf

[23] Emscripten: https://kripken.github.io/emscripten-site/

[24] PostScript: https://en.wikipedia.org/wiki/PostScript

[25] NeWS: https://en.wikipedia.org/wiki/NeWS

[26] код программы чтения PDF из Foxit: https://plus.google.com/+FrancoisBeaufort/posts/9wwSiWDDKKP

[27] туториала: https://developer.mozilla.org/en-US/docs/Games/Tutorials/2D_Breakout_game_pure_JavaScript

[28] Field.cpp: https://pdfium.googlesource.com/pdfium/+/chromium/2524/fpdfsdk/src/javascript/Field.cpp

[29] менять цвет заливки: https://pdfium.googlesource.com/pdfium/+/chromium/2524/fpdfsdk/src/javascript/Field.cpp#1631

[30] менять прямоугольник его границ: https://pdfium.googlesource.com/pdfium/+/chromium/2524/fpdfsdk/src/javascript/Field.cpp#2356

[31] задавать стиль границ: https://pdfium.googlesource.com/pdfium/+/chromium/2524/fpdfsdk/src/javascript/Field.cpp#479

[32] считывать точное положение мыши: https://pdfium.googlesource.com/pdfium/+/chromium/2524/fpdfsdk/src/javascript/Document.cpp#1107

[33] скриптом: https://github.com/osnr/horrifying-pdf-experiments/blob/master/generate_breakout.py

[34] поток внешнего отображения: https://www.adobe.com/content/dam/Adobe/en/devnet/acrobat/pdfs/pdf_reference_1-7.pdf#page=612

[35] упрощённый словарь характеристик внешнего вида: https://www.adobe.com/content/dam/Adobe/en/devnet/acrobat/pdfs/pdf_reference_1-7.pdf#page=642

[36] Minimal PDF: https://brendanzagaeski.appspot.com/0004.html

[37] Hand-coded PDF: https://brendanzagaeski.appspot.com/0005.html

[38] PDF Inside and Out: https://blogs.adobe.com/pdfdevjunkie/files/pdfdevjunkie/PDF_Inside_and_Out.pdf

[39] pdfrw: https://github.com/pmaupin/pdfrw

[40] VDS любой конфигурации: https://vdsina.ru/cloud-servers?partner=habr223

[41] Источник: https://habr.com/ru/post/536200/?utm_source=habrahabr&utm_medium=rss&utm_campaign=536200