✍️ Статья Патчим JSXBIN. Как править бинарные скрипты Adobe без перекомпиляции

The 146X Project Dublikat Web Studio Avram Lincoln AL Service Navigator Knyaz

BlackPope

Команда форума
Модератор DeepWeb ✔️
PR-Group DeepWeb 🔷
Регистрация
27.04.2020
Сообщения
230
Говорят, что, если бы в JavaScript нормально работал garbage collector, весь код улетал бы в треш сразу после написания. У этого языка и впрямь миллионы преданных хейтеров, но тем не менее его продолжают активно использовать. Например, корпорация Adobe — когда создает расширения и дополнения для собственных продуктов. А то, что однажды было создано, при желании может быть сломано или пропатчено. Об этом увлекательном процессе мы сегодня и поговорим.

Практически каждая современная адобовская программа использует три вида дополнительных модулей: плагины (plugins), расширения (extensions) и скрипты. Для создания таких модулей используется JavaScript, и, чтобы порадовать программистов, в Adobe придумали собственную среду разработки под названием ExtendScript Toolkit c блек-джеком, объектными моделями, отладчиком и даже примитивным компилятором в бинарный код.

Да-да, чувак, ты не ослышался: я сказал «бинарный код». Несмотря на то что JavaScript — это интерпретируемый язык, программы на котором представляют собой обычный pain text, такой эксгибиционизм по вкусу далеко не всем. Жадные коммерсы хотят защитить свой код от чужих глаз и шаловливых рук. Именно для подобных разработчиков Adobe создала свой собственный уникальный и почти бинарный формат скрипта — jsxbin. Для его создания даже не требуется никакой дополнительный компилятор, достаточно в диалоге сохранения кода в ESTK указать не текстовый формат .jsx, а бинарный .jsxbin.

Словосочетание «почти бинарный» тут присутствует неслучайно. Он, строго говоря, не бинарный в общепринятом смысле этого слова, ибо набор символов, который в нем используется, — базовый текстовый, даже без спецсимволов, что позволяет вставлять такой «бинарный» код в текст обычных скриптов. Тем не менее это реально компилированный синтаксический псевдокод, восстановить исходный текст из которого — задача весьма неоднозначная и нетривиальная. Этот фарш сложно провернуть назад.

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

Я не собираюсь углубляться в бездонные пучины унылого кодинга. Наша цель несколько другая: научиться без лишнего погружения в сухую теорию быстро и просто вносить исправления в псевдокод адобовских расширений.

Конечно, в самом идеальном случае было бы лучше полностью реверсировать исходный код один в один и вносить изменения именно в него. Причем к этому есть вполне определенные предпосылки: несмотря на недокументированность формата, существует несколько вполне хороших, годных декомпиляторов разной степени удобства и функциональности. Не буду приводить ссылки на них по этическим соображениям, но пару названий укажу: это jsxbindec и jsxbin-to-jsx. Причем последний представлен даже в свободных исходниках, изучая которые дотошный и неленивый читатель может получить исчерпывающее представление о принципах компиляции формата jsxbin.

Хорошо, если нужный код отлично декомпилируется, а полученный результат запускается и работает идентично исходному плагину. Однако такое случается редко. Декомпиляторы неидеальны, а их присутствие в свободном доступе дает полный простор авторам-обфускаторам выдумывать всевозможные ловушки, сводящие декомпилятор с ума. Да и вообще, поскольку преобразование jsx -> jsxbin — это все-таки компиляция, оно не всегда однозначно обратимо. На практике это чаще всего выглядит так: jsxbin успешно декомпилируется в несколько мегабайтов кода, который в лучшем случае выдает синтаксические ошибки (их потом придется кропотливо и уныло фиксить), а в худшем — вызывает ошибки времени выполнения вида

Error 21: is not an object. Line: 3164 -> _0x20031 += ((parseInt(_0x1FFFD[_$_f79d[178]](_0x1f435)

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

Итак, сформулируем задачу. Предположим, исходный код определенной степени достоверности получен, проанализирован, искомое место найдено. Нам нужно сделать патч в бинарном коде без ущерба для работоспособности в целом. Например, мы нашли в декомпилированном коде конструкцию вида _isDemo=true, а нам надо заменить ее _isDemo=false без полной переcборки исходника.

Что ж, это вполне возможно. Для начала нужно найти это место. Идеальный случай — если декомпилятор умеет выполнять смещения до команд. Скажем, у нас есть смещение 0xD927, по которому в бинарнике мы видим строку BjzJifjEjFjNjPiNjPjEjF2iUBfnctf… Упс, я тебя обманул, слегка вникать в структуру jsxbin все-таки придется.

Постараюсь объяснить как можно проще, а более подробную информацию желающие могут получить, вдумчиво покурив исходники jsxbin-to-jsx. Первый символ у нас B — это код синтаксической конструкции, соответствующей условной метакоманде Assign, в которую транслируются глобальные присваивания. Подобные конструкции, строго говоря, могут быть многоэтажными и чудовищно сложными и растягиваться на несколько килобайтов кода. Но, по счастью, у нас идет простое присваивание булевой константы глобальной переменной (в случае локальной было бы LocalAssign, соответственно, с кодом S). Что мы и видим дальше: следующий опкод — j (Variable). Затем символ z указывает на то, что переменная в коде встречается первый раз, а значит, далее следует ее имя — текстовая строка. Строки кодируются так: первый символ — счетчик, затем идут символы строки. Поскольку мы ограничены набором латинских букв, каждый байт (включая счетчик) кодируется следующим образом:

0 : A
1 : B
2 : C
...
25 : Z
26 : ga
27 : gb
28 : gc
29 : gd
30 : ge
31 : gf
32 : gA
33 : gB
...
58: gZ
59: ha
60: hb
...

По этой логике счетчик "J"=9, следующий символ "if"=0x5F="_", "jE"=0x64="d", то eсть "ifjEjFjNjPiNjPjEjF"=9,"_demoMode".


Вообще говоря, константы вовсе не обязательно могут быть однобайтовыми. Например, если первый символ константы 2, это значит, что она 16-битная и ее байты формируются из следующих за ней однобайтовых констант. То есть идущие за именем переменной символы 2iUB расшифровываются как 16-битная константа "B" << 8+"iU".

Смысл этой константы — индекс данной переменной в общем стеке идентификаторов программы. То есть при следующем обращении (это было первое) код переменной будет без символа z, просто j2iUB. Запомним этот факт, если придется искать переменную дальше. Теперь смотрим следующие символы: три символа fnc представляют собой обычную последовательность синтаксической конструкции Assign, а вот следующий символ t — кодированная логическая константа true — искомый второй операнд присваивания.

Несложно догадаться, что противоположная ей по смыслу константа false кодируется как f, то есть для того, чтобы данный оператор поменял свой смысл на _isDemo=false, надо всего-навсего исправить один этот символ, так, чтобы искомая строка приобрела вид BjzJifjEjFjNjPiNjPjEjF2iUBfncff.

Что приятно — для этого даже не нужен Hex-редактор, ведь по сути формат jsxbin текстовый, такой файл можно редактировать хоть в notepad.exe. Более того, можно даже не заботиться о сохранении длин участков кода, ведь в нем отсутствуют прямые смещения и операторы перехода! Можно менять длины строк, переменных, констант, добавлять и удалять целые синтаксические выражения, разумеется, c сохранением валидности синтаксических конструкций, проверяя их декомпиляцией полученного кода.

Попробуем усложнить задачу. У нас нет прямого смещения до нужного оператора, однако есть номер нужной строки. Скажем, вот такой:

// Line: 1704
_demoMode = true;


Снова придется обратиться к теории: каждое законченное синтаксическое выражение кодируется конструкцией ExprStatement с опкодом J. В нашем примере, слегка отодвинувшись от кода Assign, мы видим прямо перед ним последовательность J2lIGnA. Символ J — опкод ExprStatement, эта конструкция стоит в начале каждой скомпилированной строки. 2lIG — это 16-битная константа, равная "G"<<8+"lI"=0x6A0=1704. Далее следует стандартная последовательность nA.

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

Теперь ложка дегтя. Как гласит древняя восточная мудрость, жаба хитра, но гадюка намного хитрее ее. С тех пор как прогрессивное человечество научилось реверсить jsxbin, авторы скриптов уже не надеются на этот формат и выдумывают способы обфускации один затейливее другого. К примеру, можно завернуть jsxbin в jsx, затем еще в один jsxbin и так далее — прямой поиск строк и переменных в таком слоеном бинарнике ничего не даст.

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

📌 Золотая реклама

AnonPaste

Верх