Упакованный SWF. Как распаковать?

Привет, друзья!
В этом посте я хотел бы рассказать о том, что из себя представляет упакованный SWF и как его можно распаковать динамически и статически.

Итак, что же такое упакованный SWF?

Если Вы никогда ранее его не видели, не поленитесь скачать простой классический пример упакованного SWF:
http://codestage.ru/files/flood/security/packed.swf
Запустив SWF, вы увидите немного текста в левом верхнем углу. Однако, если вы попробуете декомпилировать этот файл, то вы не увидите кода, который этот текст показывает.
Стоит отметить, что если бы в упакованном SWF были ещё какие-нибудь ресурсы (изображения, звуки, шрифты и т.д.), то их вы бы тоже не увидели. Это из-за того, что оригинальный SWF находится внутри того SWF что вы скачали и декомпилировали, и часто в зашифрованном виде.
Обычно все, что вы можете получить из таких SWF с помощью декомпиляторов — это код распаковщика\расшифратора и, возможно, сам зашифрованный SWF в виде набора байт (зависит от того, умеет ли используемый вами декомпилятор отображать тэг DefineBinaryData).
Такая «упаковка» на самом деле достигается с помощью простого Flex тэга [Embed].

Динамическая распаковка (dumping)

Динамическая — значит для распаковки потребуется исполнение SWF во Flash Player’е (далее — FP).
Т.к. FP не умеет проигрывать зашифрованную неведомо как SWF, то логично что перед проигрыванием, SWF должна расшифровываться и загружаться уже в чистом виде (например, с помощью Loader.loadBytes()).
Что более важно, распакованный и расшифрованный SWF будет в памяти все время, пока он исполняется FP’ром.

И это наш шанс достать его!
Существует два основных способа сделать это.

1) Динамическая распаковка с помощью утилит
Используя сторонние или даже самописные утилиты для поиска SWF в памяти процесса FP. Это самый быстрый, самый простой и обычно самый эффективный способ. Но он не вызывает ощущения что вы — тру крякер 😉
Существуют различные платные и бесплатные утилиты для этих целей.
Одна из наиболее продвинутых — это SWF Revealer, бесплатная утилита для владельцев лицензий на ASV. В некоторых случаях, она может обходить проверки на домен (которые не дают распаковаться и расшифроваться SWFке) и заставлять SWF запускаться в вашей среде.
Также достаточно легко гуглятся разные бесплатные дамперы.

2) Ручная динамическая распаковка
Поиск SWFки вручную, с помощью нахождения CWS (сжатый SWF) или FWS (несжатый SWF) заголовков, которые являются началом любого SWF. Если вы никогда не пробовали такой способ, я очень рекомендую попробовать! Он не только позволит немного подтянуть ваши скиллы в hex, но и подарит вам ощущение, что вы тру-крякер!)
Для поиска заголовков можно использовать любой HEX редактор, который умеет читать память процессов и имеет функцию поиска.
Если вы хотите искать упакованный SWF в памяти FP в котором проигрывается файл-пример по ссылке выше, то стоит начать с поиска FWS подписи (несжатый SWF) — просто ищите строку ‘FWS’, т.к. перед исполнением FP разжимает SWF, если он был сжат.
Если вы будете запускать и искать SWF в браузере, то закройте все лишние вкладки, чтобы снизить количество лишних SWF в памяти.

Обычно при таком поиске вы найдете несколько заголовков в памяти, т.к. сам FP держит там разные служебные SWF, например ту, что показывается после входа в полноэкранный режим.
Так что если вы сомневаетесь, то лучше проверить все найденные заголовки.
Итак, что же делать с найденным заголовками, спросите вы? Как их проверить, как узнать, где заканчивается SWF?
Пожалуйста, взгляните на этот скриншот:

Это заголовок одной из SWF в памяти FP при проигрывании файла-примера, найденный поиском по строке ‘FWS’ (совершенно случайно это оказался заголовок искомого упакованного файла, который мы и хотим найти ;))
Что же дальше? А дальше необходимо посмотреть какой длины получается найденный SWF. Длина расположена в 4 байтах начиная с 4го:

Как я узнал? Я просто прочитал спецификацию формата SWF: «SWF File Format Specification» http://www.adobe.com/content/dam/Adobe/en/devnet/swf/pdf/swf_file_format_spec_v10.pdf (раздел «The SWF header»)
Т.к. это шестнадцатеричное число, записанное в память, вам стоит знать, что порядок записи его байт — справа налево. Поэтому в результате число такое:
00 00 04 DB в hex и 1243 десятичном представлении.
Теперь отмеряем эти 1243 байт начиная с FWS подписи.
Т.к. подпись начинается на 053DD020, окончание SWF файла должно находиться по адресу 053DD4FB (053DD020 + 4DB):

Пожалуйста, имейте ввиду, что адрес расположения SWF в памяти будет отличаться на разных ОС и на разном железе.

Итак, мы видим, что найденный SWF действительно заканчивается на 053DD4FB, так что мы можем смело выделить все байты начиная с 053DD020, заканчивая 053DD4FB и скопировать их в новый SWF файл.
После проделывания этой операции со всеми вхождениями FWS, которые вам покажутся подходящими, среди сохраненных SWF файлов будет один искомый, распакованный SWF!
Теперь у вас не должно возникнуть проблем с его декомпиляцией.

Некоторые виды пакеров усложняют поиск искомого SWF с помощью размещения множества фальшивых FWS заголовков в памяти. Так что вам следует более тщательно подбирать FWS — проверять его длину, и то как он в целом выглядит. Опытные в реверсинге люди (вроде меня :p) могут на глаз отличить фальшивый заголовок от настоящего, глянув на сам заголовок и на несколько десятков байт после него.

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

Статическая распаковка

Статическая — значит без запуска SWF во FP.
В целом, к этому типу распаковки прибегают когда не вышло распаковать SWF динамически (кто его знает, почему у вас не получилось запустить SWF?)
Статическая распаковка может быть очень сложной задачей, т.к. есть не один способ её усложнить и сделать мучительно долгой.

Итак, с чего начать при статической распаковке? Для начала, вам следует получить доступ как минимум к двум вещам в SWF:
1 — DefineBinaryData тэг(и).
2 — Декомпилированный AS или abc байткод распаковщика\загрузчика.
Также, в некоторых случаях понадобится
3 — SymbolClass тэг
Для этого используйте доступные утилиты (ASV, Adobe SWF Investigator, SWiX, и т.д.).

Как найти тэг DefineBinaryData в SWF?
Сначала отмечу, что некоторые утилиты, например, ASV, могут вам явно указать на наличие этого тэга, сразу после открытия SWF. В них же можно этот тэг сохранить в виде двоичного файла.
Также можно найти его вручную, с помощью различных инспекторов тэгов, вроде упомянутого выше Adobe SWF Investigator.
Для получения содержимого тэга DefineBinaryData из файла-примера с помощью Adobe SWF Investigator, просто откройте файл, перейдите на вкладку Tag Viewer, выберите тэг DefineBinaryData и нажмите на кнопку Dump To File.

Иногда в этом списке тэгов может быть множество фальшивых, чтобы сбить с толку незадачливого крякера. Для поиска необходимого тэга придётся немного изучить код загрузчика\распаковщика и отследить там обращение к упакованным данным.
Обычно оно выглядит так:

var someVar:ByteArray = new SomeClass();

Где SomeClass имеет тип Class и наследуется от класса ByteArrayAsset.

Давайте посмотрим в код загрузчика и поищем что-то похожее.
Ага, вот и оно!

private var content:Class;
//...
var _local3:ByteArray = new this.content();

Теперь нам следует поискать класс с именем оканчивающимся на «_content» и наследующийся от ByteArrayAsset.
А вот и он:

public class MainTimeline_focus_loader_content extends ByteArrayAsset

Чтобы выяснить, какой тэг DefineBinaryData связан с этим классом, нам следует заглянуть в тэг SymbolClass и поискать там запись с названием найденного класса «MainTimeline_focus_loader_content».
В нашем случае эта запись выглядит так (в Adobe SWF Investigator):

<Symbol idref='1' className='MainTimeline_focus_loader_content' />

Запомните значение поля idref. Это id нужного тэга DefineBinaryData!
Теперь ищите тэг с таким id среди всех тэгов DefineBinaryData.
После того, как вы его найдёте, его можно сохранить в файл и продолжать распаковку.
Почему я попросил запомнить idref, а не название класса? Потому что в том случае, если AS распаковщика обфусцирован, работать с именами классов может быть очень затруднительно.

Теперь успех вашего мероприятия зависит от количества времени, которое вы готовы потратить, сложности упаковщика и удачи)
Если повезёт, то полученные из DefineBinaryData данные окажутся чистым SWF без какого-либо шифрования и распаковку можно считать оконченной.
Но чаще всего, на этом этапе все самое интересное только начинается и впереди реверсинг загрузчика — разбор алгоритмов шифрования и написания собственного дешифровщика.

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

private function decryptFile(_arg1:ByteArray):void
{
_arg1.position = 0;
var _local2:int = -1;
var _local3:uint = _arg1.length;
var _local4:uint = uint("55");
while (_local2++ < _local3)
{
_arg1[_local2] = (_arg1[_local2] ^ _local4);
};
}

И узнаем, что для получения оригинального SWF достаточно каждый его байт поксорить на 55.
Теперь вы можете написать свой декриптор, или скрипт, который сделает все операции для расшифровки SWF. Вот и все, распаковка закончена. После расшифровки вы получите оригинальный SWF, код и ресурсы которого видны в декомпиляторе.
Остаётся надеяться, что это так и распакованный SWF не окажется точно так же расшифровщиком SWF, который вы только что ковыряли, хахаха!)
Иногда при упаковке используют принцип матрёшки — запихивают один расшифровщик в другой — и так десятки раз, да ещё и алгоритмы расшифровки везде разные.
В любом случае, динамическая распаковка все это обходит.
Запомните — в мире Flash, ничего, кроме названий, нельзя скрыть от глаз профессионала высокого класса с достаточным уровнем мотивации 😉

Есть вопросы, идеи, комментарии? Оставляйте всё в виде комментариев к посту, пишите!

Комментарии

Упакованный SWF. Как распаковать? — 24 комментария

  1. Отличная статья!
    Буду иметь ее ввиду когда буду писать свою систему защиты для флеш игр.

  2. Благодарю. Если ещё не видели — обратите внимание на мою давнюю статью на хабре (http://habrahabr.ru/post/110686/) по теме. Многое из неё ещё актуально.

  3. А где именно в программе SWF Investigator есть запись

    если не сложно скажите

  4. Вот эта записьНе могу найти её

    • Всё ещё не понимаю, про какую запись вы говорите(

  5. Привет
    Извиняюсь за прошлые комментарии запись не появилась в них
    вот это idref=’1′ не могу найти его SWF Investigator

    • Ага, не страшно. Эту запись надо искать в тэге SymbolClass, как и написано в статье.

  6. Спасибо хорошая статья

    Не могли бы подсказать есть еще как-нибудь статьи по поводу Флеш обфускации
    как ее можно снять
    Не подумайте что для воровства у кого-то
    просто до этого никогда с флешь обфускацией толком не связывался, а тут криптованая флешка попалась с абракадаброй в качестве названия ресурсов охота снять
    Любые статьи хоть на английском

    • Боюсь что других дельных статей я подсказать не смогу кроме уже упомянутой выше (http://habrahabr.ru/post/110686/), т.к. сам не читал ничего такого.

      Наверняка я смогу вам помочь с вашей флэшкой, если хотите продолжить обсуждение — пишите лучше лично, буду рад (http://blog.codestage.ru/ru/contacts/), ибо через комменты весьма неудобно.

  7. Да с HEX редакторами нужны хорошие знания. Есть ли способ попроще, когда нужно изменить айпи внутри флешки на другой?

    • Да, как минимум можно попробовать FFDec последней версии, либо RABCDAsm.
      Поменять строку или число даже в обфусцированном файле обычно довольно таки легко.

  8. Investigator делает дамп в jpg и он само собой ничем толком не открывается, в чём фишка?

    • Не понятно почему он у вас делает дамп в jpg =)
      Может прот упаковывает данные в jpg? В таком случае надо смотреть на его распаковщик и выяснять как он потом извлекает данные обратно. В общем не очень понял ваш кейс.

  9. Всем привет.
    Спасибо автору за ваши статьи, в том числе и за эту https://habrahabr.ru/post/110686/
    У меня возник вопрос. Как вы считаете уместны эти меры защиты для браузерной онлайн-игры? Может быть только некоторые из них? было бы интересно узнать ваше мнение… как я понял 2,3 хорошо проработанных мер защиты достаточно, вопрос только какие именно актуальны для онлайн игр?
    За ранее благодарен.

    • Привет!

      Чем больше палок в колёса — тем медленнее они едут. Если позволяет время и средства — реализуйте как можно больше препятствий.

      Если в ресурсах ограничены, то реализуйте в порядке снижения эффективности, начиная с обфускации (главное — имена).

      Учитывая специфику онлайн игр — я бы рекомендовал кодить тонкий клиент, и всю логику и механику реализовывать на стороне сервера. Если по какой-либо причине на сервер не вынести, то стоит хотя бы накрыть протокол чем-нибудь криптографическим — El Gamal и т.п.

  10. Ты имеешь введу обфускацию имен пакетов, классов, методов?
    А Строковые значения обфускато может зашифровать? например ключ на клиенте?

    • Да, большинство обфускаторов умеют шифровать строки. Стоит отметить что расшифровка обычно тривиальна и поддается автоматизации, но в качестве дополнительного препятствия — годится.

  11. Да я так и понял, что 100% гарантий защиты нет. Пока я остановился на вещах в которых я более менее разобрался:

    Упаковка SWF.
    Подмена ресурсов.
    Защита переменных.
    Обфускатор.
    Еще бы все это зашифровать как-то и делов… )

    Кстати, в твоей статье на Харбре, в примерах с защитой переменных, есть два класса для кодирования числовых и стоковых переменных, я так понимаю, что эти классы тоже стоит упаковывать в SWF и далее внедрять через Embed?

    • Не думаю что пользы от этого будет больше, чем вреда, при таком внедрении пользоваться ими будет неудобно, а если уж кто-то полезет расковыривать до конца — и до них доберется.

    • Как бы вы их ни прятали — потеряете в удобстве использования. Чтобы сохранить комфорт использования, достаточно их обфусцировать как и всё остальное.