Продвинутое шифрование в DoSWF? Вызов принят!

Привет!
Я ранее уже писал про распаковку флэшек, в большинстве случаев все сводится к дампу рабочей флэшки из памяти, или к статической распаковке после анализа алгоритма расшифровки в загрузчике. Но бывают и исключения. Об одном таком я и расскажу в этой статье.

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

Предлагаю ознакомиться со всеми нюансами на конкретном примере (в статье много картинок!)…
Для начала, возьмем какую-нибудь подопытную флэшку, мне под руку попалась демка частиц движка Stardust:
Stardust

Декомпилируется она без проблем:
Clean SWF

Теперь о самом протекторе. Я для своих экспериментов использовал последнюю триальную версию (на данный момент 5.2.9), обычно доступную онлайн на сайте разработчика: http://www.doswf.cn/swfencrypt/
Так как наш академический интерес всецело сосредоточен на функционале упаковки, то при использовании протектора необходимо включить опции в разделе Encrypt:
DoSWF Encrypt
Кстати, я не использовал обфускацию оригинальной флэшки, дабы пример получился нагляднее.

На выходе получаем SWF, при декомпиляции которого можно увидеть нечто подобное:
DoSWF Loader

Вполне такое стандартное положение дел для любой упакованной флэшки — видим немного классов, которые по сути представляют собой загрузчик, расшифровывающий и подготавливающий флэшку к запуску. Также неподалёку обычно валяется включенный в ролик набор байт (или наборы байт), в котором и находится искомая флэшка, или её части 😉
Стоит отметить, что далеко не каждый декомпилятор в состоянии справиться с обфускацией DoSWF — некоторые падают или виснут при попытке прожевать такую кашу из байткода. Я рекомендую использовать Action Script Viewer (ASV) или AS3 Sorcerer — эти инструменты скушают такой байткод даже не поперхнувшись — автор знает своё дело! Привет, Burak! xD

Защищённая SWF работает нормально, при запуске виден ватермарк, который добавляется при использовании триальной версии протектора:
DoSWF Watermark
А также, через некоторые промежутки времени, флэшка открывает родной сайт протектора.

Теперь приступим непосредственно к распаковке — ролик проиграть можем — препятствий для дампа быть не должно.
Вы можете использовать любые устраивающие вас инструменты или сделать все вручную, я для этих целей могу порекомендовать SWF Revealer Ultimate — прокачанный дампер/анпакер от автора ASV. Если ролик работает в standalone проигрывателе, то SWFRUL сумеет вытащить из такого SWF все необходимое.
Кстати, SWFRUL — это лимитированное издание оригинального SWF Revealer’а, идущего в комплекте с ASV (но работает он совсем иначе). Всего будет продано лишь 15 лицензий по всему миру, так что спешите, если вас заинтересовал этот инструмент.

SWFRUL находит два SWF файла, которые можно вытянуть из защищенной флэшки:
SWFRUL

Сохраним их и изучим повнимательнее.

При запуске этих флэшек мы не увидим рабочий оригинал. В одной — лишь белый фон, в другой — глючащий интерфейс искомой флэшки. Ну что-ж, давайте разберемся что к чему.
Открыв первую флэшку в декомпиляторе можно увидеть код, очень похожий на то, что мы видели в оригинальном SWF:
Dumped SWF with code
В глаза сразу же бросается странность — декомпилятор не нашел document класс. Более того, в библиотеке этой флэшки пусто! Выходит, флэшка эта — лишь часть того, что нам надо и на первый взгляд содержит только код…
При беглом осмотре в любом инспекторе SWF, например, в Adobe SWF Investigator, сразу становится понятно почему декомпилятор не нашел document класс — нет необходимого для этого тэга SymbolClass, а если точнее, то там вообще нет никаких тэгов кроме трёх — FileAttributes, DoABC, ShowFrame:
SWF with code tags

Выходит, что всё остальное — во второй сдампленной флэшке, ведь как раз в ней мы видели глючный интерфейс оригинального SWF. Это легко подтверждается после просмотра в инспекторе:
Investigating second SWF
Заодно убеждаемся, что тут есть тэг SymbolClass, где указан document класс (Symbol с нулевым id).

Похоже, нам следует попытаться объединить оба SWF файла. На мой взгляд, проще перенести тэг DoABC из первой флэшки во вторую, чем переносить все остальное из второй в первую. Сделать это можно разными способами, я для этого использовал старый добрый SWiX — удобный XML-based редактор SWF файлов. Достаточно открыть в нем обе флэшки, а потом скопипастить то что надо. Копируем тэг DoABC и сохраняем:
Editing SWF in SWiX

Пробуем запустить сохраненный ролик и видим следующее:
Corrupted SWF

С одной стороны, зрелище весьма неприятное — наблюдаются баги в интерфейсе, не работают частички, отладочный проигрыватель сыпет ошибками. С другой стороны — появился ватермарк протектора и работают Stats — значит код подцепился таки и теперь это не просто мёртвый интерфейс!

Теперь следует разобраться что же у нас сломалось и почему. Откроем SWF в декомпиляторе и посмотрим на код конструктора:
Decompiled corrupted SWF

Умный ASV заменил страшные обфусцированные имена на _SafeStr*. Так как флэшка не была обфусцирована, то в данном случае весьма просто отличить инородный код, который в неё добавил обфускатор, в реальной жизни все несколько сложнее, однако в нашем случае лишняя наглядность не помешает.

Итак, в конструкторе мы видим подозрительный вызов метода _SafeStr4(), а так же пустой статический метод _SafeStr3(), похожий на мусор.
Если просмотреть остальной код в главном классе, то будет заметно довольно много методов, добавленных протектором, но судя по всему, всё это отключается удалением вызова _SafeStr4() из конструктора.
Так что так и сделаем — почикаем все лишнее из конструктора с помощью какого-нибудь редактора байткода. Я обычно для таких целей использую RABCDAsm и какой-нибудь GUI для него, например WinRABCDAsm.

Итак, дизассемблируем флэшку и ищем байткод конструктора в файле главного класса (Waypoints.class.asasm). А вот и он:

getlocal0
pushscope

getlocal0
callpropvoid        QName(PackageNamespace(""), "@doswf__mnɿ"), 0

findproperty        QName(PrivateNamespace(null, "Waypoints#0"), "matrix")
findpropstrict      QName(PackageNamespace("flash.geom"), "Matrix")
pushdouble          0.5
pushbyte            0
dup
pushdouble          0.5
constructprop       QName(PackageNamespace("flash.geom"), "Matrix"), 4
initproperty        QName(PrivateNamespace(null, "Waypoints#0"), "matrix")

getlocal0
constructsuper      0

findpropstrict      QName(PackageInternalNs(""), "__setProp_xml_btn_Scene1_XMLbtn_1")
callpropvoid        QName(PackageInternalNs(""), "__setProp_xml_btn_Scene1_XMLbtn_1"), 0

returnvoid

В самом начале листинга виден вызов того подозрительного метода: @doswf__mnɿ (ASV переименовал его в _SafeStr4).
Удаляем вызов (как правильно править байткод я рассказывать не буду, достаточно почитать спеку и все станет понятно), пересобираем флэшку, смотрим.
Почти ничего не изменилось — все те же баги и т.д., только вот ватермарк пропал 😉

Кроме глюков все еще остался неприятный эффект от триальной версии протектора — через разные промежутки времени открывается его страница.
Найти код, который это делает, можно с помощью поиска вызовов метода navigateToURL по всему исходнику. Совпадений нашлось немного, и просматривая окрестности вызовов, вскоре можно наткнуться на вызов инлайн метода, в котором и будет код, который периодически открывает главную страницу протектора. В моем случае он был добавлен в класс idv.cjcat.stardust.common.particles.Particle. В вашем случае это может быть любой другой класс, т.к. судя по всему, расположение этого кода выбирается случайно на этапе обработки SWF протектором.
Кстати там есть такая вот лепота:

var _local2 = "ht" + "tp:/" + "/ww" + "w.d" + "osw" + "f.c" + "om";

Низкая эффективность компилятора во всей красе.

Итак, надо бы посмотреть на дизасм этого класса. Стоит обратить внимание на статический конструктор — именно там и происходит вызов этого мерзкого инлайн-метода =)

getlocal0
pushscope

findpropstrict      Multiname("Particle", [PackageNamespace("idv.cjcat.stardust.common.particles")])
getlex              QName(PackageNamespace(""), "Object")
pushscope

getlex              QName(PackageNamespace(""), "Object")
newclass            "idv.cjcat.stardust.common.particles:Particle"
popscope
initproperty        QName(PackageNamespace("idv.cjcat.stardust.common.particles"), "Particle")

newfunction         "idv.cjcat.stardust.common.particles:Particle.sinit/inline_method#0"
pushnull
call                0
pop
returnvoid

Вот он и попался — вызов происходит прямо перед выходом из статического конструктора. Убедиться, что это именно тот самый инлайн, можно изучив его дизасм. Удаляем вызов, пересобираем — voilà! Флэшка больше не открывает сайт протектора.
Кстати, мы только что удалили ограничения триальной версии протектора 😉 Правда в коде осталось много уже не используемых ошмётков, по которым можно понять, что накрыта флэшка была именно триальной версией.

Все это хорошо, но нас больше интересует восстановление полной работоспособности флэшки. Пришло время присмотреться к ошибкам, которыми сыпет отладочный проигрыватель. В моём случае окончание стэктрейса первой ошибки, которую показывает Flash Player, такое:
TypeError: Error #1009: Cannot access a property or method of a null object reference.
at fl.core::UIComponent/setSize()
at …

Итак, стоит проверить в декомпиляторе, что же может быть не так в fl.core::UIComponent/setSize().
код там такой:

_width = _arg1;
_height = _arg2;
invalidate(InvalidationType.SIZE);
dispatchEvent(new ComponentEvent(ComponentEvent.RESIZE, false));

Судя по ошибке, где-то у нас ссылка на null, значит первые две строки вне подозрения. Смотрим на 3тью — nullом там может быть константа SIZE.
Смотрим на класс InvalidationType:

public class InvalidationType
{

public static const ALL:String = "all";
public static const SIZE:String = "size";
public static const STYLES:String = "styles";
public static const RENDERER_STYLES:String = "rendererStyles";
public static const STATE:String = "state";
public static const DATA:String = "data";
public static const SCROLL:String = "scroll";
public static const SELECTED:String = "selected";

public function InvalidationType()
{
if (!ApplicationDomain.currentDomain.hasDefinition("Ȋ"))
{
return;
};
super();
}

}

import flash.system.ApplicationDomain;

if (!ApplicationDomain.currentDomain.hasDefinition("Ȋ"))
{
return;
};

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

Стоит посмотреть на это чудо китайской мысли в дизассемблере:

getlocal0
pushscope

findpropstrict      Multiname("InvalidationType", [PackageNamespace("fl.core")])
getlex              QName(PackageNamespace("flash.system"), "ApplicationDomain")
getproperty         QName(PackageNamespace(""), "currentDomain")
pushstring          "Ȋ"
callproplex         QName(PackageNamespace(""), "hasDefinition"), 1
iftrue              L9

returnvoid

L9:
getlex              QName(PackageNamespace(""), "Object")
pushscope

getlex              QName(PackageNamespace(""), "Object")
newclass            "fl.core:InvalidationType"
popscope
initproperty        QName(PackageNamespace("fl.core"), "InvalidationType")

returnvoid

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

Можно предположить, что таких проверок во флэшке много — вот почемы мы видим все эти ошибки и неработающий в итоге файл.
Так что теперь можно поискать обращения к ApplicationDomain, ну или создать фейковый класс, чтобы проверки его находили 😉 Я сторонник чистоты в коде, так что предпочитаю первый вариант, который, кстати, очень легко автоматизировать.
Таким образом разбираемся со всеми ошибками и восстанавливаем работоспособность SWF файла. Других подлянок данная версия протектора не предполагает.

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

Резюмируя, можно сказать, что даже такие хитрости как разбиение SWF файла на части с последующей их загрузкой в один домен, внедрение дополнительных проверок в защищаемый SWF и прочие приемы, использованные в данном протекторе не способны надежно защитить SWF файл. Единственная необратимая защита в as3 — была, есть и будет обфускация имён — все остальное обратимо и лишь увеличит трудозатраты на взлом.
Если у вас есть что добавить или спросить — пишите! Любые комментарии, как всегда, приветствуются!

И да, чуть не забыл, с прошедшей Пасхой! 😉

Комментарии

Продвинутое шифрование в DoSWF? Вызов принят! — 5 комментариев

  1. Интересная статья. Спасибо. Интересует вопрос о возможности 100% защиты flash — возможно ли в принципе?

    • Спасибо за комментарий! Что касается 100% защиты — однозначно нет.

  2. Отличная статья, спасибо!
    Вопрос по протекторам — насколько хорош этот DoSWF в отличии от его более известных братьев? Да и вообще интересно личное мнение, какой из протекторов лучше (по личным ощущениям, предпочтениям)?

    • Привет! Спасибо за комментарий.
      По поводу протекторов — я стараюсь не обсуждать такие вопросы на публике, чтобы не разводить холивары, однако всегда буду рад таким обсуждениям в уютном скайп-чате, к примеру =) Так что пишите — буду рад предложить свои соображения и проверенный в деле опыт в этой сфере!

      В дополнение могу сказать, что основную мысль, которая отражает мою позицию в данном вопросе, я высказал в самом конце статьи)

      • Основную мысль понял, спасибо )) Как только вопрос станет более актуальным обязательно напишу. Спасибо за готовность поделится опытом.