Привет, друзья!
В этом посте я хотел бы рассказать о том, что из себя представляет упакованный 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, ничего, кроме названий, нельзя скрыть от глаз профессионала высокого класса с достаточным уровнем мотивации 😉
Есть вопросы, идеи, комментарии? Оставляйте всё в виде комментариев к посту, пишите!
Что могу сказать… пиздато =)
Отличная статья!
Буду иметь ее ввиду когда буду писать свою систему защиты для флеш игр.
Благодарю. Если ещё не видели — обратите внимание на мою давнюю статью на хабре (http://habrahabr.ru/post/110686/) по теме. Многое из неё ещё актуально.
А где именно в программе SWF Investigator есть запись
если не сложно скажите
Привет! А вы о какой записи?
Вот эта записьНе могу найти её
Всё ещё не понимаю, про какую запись вы говорите(
Привет
Извиняюсь за прошлые комментарии запись не появилась в них
вот это idref=’1′ не могу найти его SWF Investigator
Ага, не страшно. Эту запись надо искать в тэге SymbolClass, как и написано в статье.
Спасибо хорошая статья
Не могли бы подсказать есть еще как-нибудь статьи по поводу Флеш обфускации
как ее можно снять
Не подумайте что для воровства у кого-то
просто до этого никогда с флешь обфускацией толком не связывался, а тут криптованая флешка попалась с абракадаброй в качестве названия ресурсов охота снять
Любые статьи хоть на английском
Боюсь что других дельных статей я подсказать не смогу кроме уже упомянутой выше (http://habrahabr.ru/post/110686/), т.к. сам не читал ничего такого.
Наверняка я смогу вам помочь с вашей флэшкой, если хотите продолжить обсуждение — пишите лучше лично, буду рад (http://blog.codestage.ru/ru/contacts/), ибо через комменты весьма неудобно.
Да с HEX редакторами нужны хорошие знания. Есть ли способ попроще, когда нужно изменить айпи внутри флешки на другой?
Да, как минимум можно попробовать FFDec последней версии, либо RABCDAsm.
Поменять строку или число даже в обфусцированном файле обычно довольно таки легко.
Investigator делает дамп в jpg и он само собой ничем толком не открывается, в чём фишка?
Не понятно почему он у вас делает дамп в jpg =)
Может прот упаковывает данные в jpg? В таком случае надо смотреть на его распаковщик и выяснять как он потом извлекает данные обратно. В общем не очень понял ваш кейс.
Всем привет.
Спасибо автору за ваши статьи, в том числе и за эту https://habrahabr.ru/post/110686/
У меня возник вопрос. Как вы считаете уместны эти меры защиты для браузерной онлайн-игры? Может быть только некоторые из них? было бы интересно узнать ваше мнение… как я понял 2,3 хорошо проработанных мер защиты достаточно, вопрос только какие именно актуальны для онлайн игр?
За ранее благодарен.
Привет!
Чем больше палок в колёса — тем медленнее они едут. Если позволяет время и средства — реализуйте как можно больше препятствий.
Если в ресурсах ограничены, то реализуйте в порядке снижения эффективности, начиная с обфускации (главное — имена).
Учитывая специфику онлайн игр — я бы рекомендовал кодить тонкий клиент, и всю логику и механику реализовывать на стороне сервера. Если по какой-либо причине на сервер не вынести, то стоит хотя бы накрыть протокол чем-нибудь криптографическим — El Gamal и т.п.
Ты имеешь введу обфускацию имен пакетов, классов, методов?
А Строковые значения обфускато может зашифровать? например ключ на клиенте?
Да, большинство обфускаторов умеют шифровать строки. Стоит отметить что расшифровка обычно тривиальна и поддается автоматизации, но в качестве дополнительного препятствия — годится.
Да я так и понял, что 100% гарантий защиты нет. Пока я остановился на вещах в которых я более менее разобрался:
Упаковка SWF.
Подмена ресурсов.
Защита переменных.
Обфускатор.
Еще бы все это зашифровать как-то и делов… )
Кстати, в твоей статье на Харбре, в примерах с защитой переменных, есть два класса для кодирования числовых и стоковых переменных, я так понимаю, что эти классы тоже стоит упаковывать в SWF и далее внедрять через Embed?
Не думаю что пользы от этого будет больше, чем вреда, при таком внедрении пользоваться ими будет неудобно, а если уж кто-то полезет расковыривать до конца — и до них доберется.
А как иначе можно их еще спрятать?
Как бы вы их ни прятали — потеряете в удобстве использования. Чтобы сохранить комфорт использования, достаточно их обфусцировать как и всё остальное.
Спасибо Дмитрий за ответы и за в ваши труды. Всего доброго.