Статьи

Защитите свои Flash-файлы от декомпиляторов с помощью шифрования

Дважды в месяц мы возвращаемся к любимым постам наших читателей на протяжении всей истории Activetuts +. Этот учебник был впервые опубликован в феврале 2010 года.

В этом уроке я продемонстрирую технику, которую я использую для защиты кода и активов от кражи.

Декомпиляторы действительно волнуют людей, которые создают Flash-контент. Вы можете приложить немало усилий, чтобы создать лучшую игру, затем кто-то может украсть ее, заменить логотип и разместить его на своем сайте, не спрашивая вас. Как? Использование Flash Decompiler. Если вы не наделили свою защиту SWF-файлом, ее можно декомпилировать одним нажатием кнопки, и декомпилятор выведет читаемый исходный код.


Я использовал свой небольшой проект, чтобы продемонстрировать, насколько уязвимы SWF для декомпиляции. Вы можете скачать его и проверить себя по ссылке на источник выше. Я использовал Sothink SWF Decompiler 5, чтобы декомпилировать SWF и заглянуть под него. Код вполне читабелен, и вы можете легко понять и использовать его повторно.


Я придумал метод защиты SWF-файлов от декомпиляторов, и я собираюсь продемонстрировать его в этом уроке. Мы должны быть в состоянии произвести это:

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

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

Вот что нам нужно:

  • SWF для защиты. Не стесняйтесь загружать SWF, над которым я буду работать .
  • Flex SDK. Мы будем использовать его для встраивания контента с помощью тега Embed. Вы можете скачать его с opensource.adobe.com .
  • Шестнадцатеричный редактор. Я буду использовать бесплатный редактор Hex-Ed. Вы можете скачать его с nielshorn.net или использовать редактор по вашему выбору.
  • Декомпилятор Хотя в этом нет необходимости, было бы неплохо проверить, действительно ли работает наша защита. Вы можете получить пробную версию Sothink SWF Decompiler с сайта sothink.com.

Откройте новый проект ActionScript 3.0 и установите его для компиляции с Flex SDK (я использую FlashDevelop для написания кода). Выберите SWF-файл, который вы хотите защитить, и вставьте его как двоичные данные, используя тег Embed:

1
2
3
[Embed (source = «VerletCloth.swf», mimeType = «application/octet-stream»)]
// source = path to the swf you want to protect
private var content:Class;

Теперь SWF-файл встроен в виде ByteArray в SWF-загрузчик и может быть загружен через Loader.loadBytes () .

1
2
3
var loader:Loader = new Loader();
addChild(loader);
loader.loadBytes(new content(), new LoaderContext(false, new ApplicationDomain()));

В конце концов мы должны иметь этот код:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
package
{
    import flash.display.Loader;
    import flash.display.Sprite;
    import flash.system.ApplicationDomain;
    import flash.system.LoaderContext;
     
    [SWF (width = 640, height = 423)] //the dimensions should be same as the loaded swf’s
    public class Main extends Sprite
    {
        [Embed (source = «VerletCloth.swf», mimeType = «application/octet-stream»)]
        // source = path to the swf you want to protect
        private var content:Class;
         
        public function Main():void
        {
            var loader:Loader = new Loader();
            addChild(loader);
            loader.loadBytes(new content(), new LoaderContext(false, new ApplicationDomain()));
        }
    }
     
}

Скомпилируйте и посмотрите, работает ли он (должен). Теперь я буду называть встроенный SWF-файл «защищенным SWF-файлом», а SWF-файл, который мы только что скомпилировали, — «загрузочным SWF-файлом».


Давайте попробуем декомпилировать и посмотрим, работает ли он.

Yey! Активы и оригинальный код исчезли! Теперь показан код, который загружает защищенный SWF, а не его содержимое. Это, вероятно, остановит большинство начинающих злоумышленников, которые не слишком знакомы с Flash, но все еще недостаточно хороши для защиты вашей работы от опытных злоумышленников, поскольку защищенный SWF ждет их нетронутыми внутри загрузочного SWF.


Давайте откроем загрузочный SWF с помощью шестнадцатеричного редактора:

Он должен выглядеть как случайные двоичные данные, потому что он сжат и должен начинаться с ASCII «CWS». Нам нужно распаковать это! (Если ваш SWF-файл начинается с «FWS» и вы видите значащие строки в SWF-файле, скорее всего, он не сжимался. Необходимо включить сжатие, чтобы следовать).

Сначала это может звучать сложно, но это не так. Формат SWF — это открытый формат, и есть документ, который описывает его. Загрузите его с adobe.com и прокрутите вниз до страницы 25 в документе. Существует описание заголовка и того, как SWF-файл сжимается, поэтому мы можем легко его распаковать.

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

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
private function decompress(data:ByteArray):ByteArray
{
    var header:ByteArray = new ByteArray();
    var compressed:ByteArray = new ByteArray();
    var decompressed:ByteArray = new ByteArray();
    
    header.writeBytes(data, 3, 5);
    compressed.writeBytes(data, 8);
    
    compressed.uncompress();
    
    decompressed.writeMultiByte(«FWS», «us-ascii»);
    decompressed.writeBytes(header);
    decompressed.writeBytes(compressed);
    
    return decompressed;
}

Функция делает несколько вещей:

  1. Он читает несжатый заголовок (первые 8 байтов) без подписи и запоминает его.
  2. Он читает остальные данные и распаковывает их.
  3. Он записывает обратно заголовок (с подписью «FWS») и несжатые данные, создавая новый несжатый SWF.

Далее мы создадим удобную утилиту во Flash для сжатия и распаковки SWF-файлов. В новом проекте AS3 скомпилируйте следующий класс как класс документа:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
package
{
    import flash.display.Sprite;
    import flash.events.Event;
    import flash.net.FileFilter;
    import flash.net.FileReference;
    import flash.utils.ByteArray;
     
    public class Compressor extends Sprite
    {
        private var ref:FileReference;
         
        public function Compressor()
        {
            ref = new FileReference();
            ref.addEventListener(Event.SELECT, load);
            ref.browse([new FileFilter(«SWF Files», «*.swf»)]);
        }
         
        private function load(e:Event):void
        {
            ref.addEventListener(Event.COMPLETE, processSWF);
            ref.load();
        }
         
        private function processSWF(e:Event):void
        {
            var swf:ByteArray;
            switch(ref.data.readMultiByte(3, «us-ascii»))
            {
                case «CWS»:
                    swf = decompress(ref.data);
                    break;
                case «FWS»:
                    swf = compress(ref.data);
                    break;
                default:
                    throw Error(«Not SWF…»);
                    break;
            }
             
            new FileReference().save(swf);
        }
         
        private function compress(data:ByteArray):ByteArray
       {
            var header:ByteArray = new ByteArray();
            var decompressed:ByteArray = new ByteArray();
            var compressed:ByteArray = new ByteArray();
             
            header.writeBytes(data, 3, 5);
            decompressed.writeBytes(data, 8);
             
            decompressed.compress();
             
            compressed.writeMultiByte(«CWS», «us-ascii»);
            compressed.writeBytes(header);
            compressed.writeBytes(decompressed);
             
            return compressed;
        }
         
        private function decompress(data:ByteArray):ByteArray
        {
            var header:ByteArray = new ByteArray();
            var compressed:ByteArray = new ByteArray();
            var decompressed:ByteArray = new ByteArray();
             
            header.writeBytes(data, 3, 5);
            compressed.writeBytes(data, 8);
             
            compressed.uncompress();
             
            decompressed.writeMultiByte(«FWS», «us-ascii»);
            decompressed.writeBytes(header);
            decompressed.writeBytes(compressed);
             
            return decompressed;
        }
         
    }
 
}

Как вы, наверное, заметили, я добавил 2 вещи: загрузку файлов и функцию сжатия.

Функция сжатия идентична функции распаковки, но в обратном порядке. Загрузка файла выполняется с использованием FileReference (требуется FP10), а загруженный файл является сжатым или несжатым. Обратите внимание, что вы должны запускать SWF локально из автономного проигрывателя, поскольку FileReference.browse () должен вызываться при взаимодействии с пользователем (но локальный автономный проигрыватель позволяет запускать его без него).


Чтобы проверить инструмент, запустите его, выберите загрузочный SWF-файл и укажите, где его сохранить. Затем откройте его с помощью шестнадцатеричного редактора и пролистайте. Вы должны увидеть строки ascii внутри следующим образом:


Давайте вернемся к шагу 2. Хотя декомпилятор не показал никакой полезной информации о защищенном SWF-файле, довольно просто получить SWF-файл из несжатого загрузчика; просто найдите подпись «CWS» (если защищенный SWF-файл распакован, найдите «FWS») и посмотрите результаты:

Мы обнаружили тег DefineBinaryData, который содержит защищенный SWF-файл, и извлечение его из него — это очень просто. Мы собираемся добавить еще один уровень защиты загрузки SWF: Шифрование.


Чтобы сделать защищенный SWF-файл менее «доступным», мы добавим какое-то шифрование. Я решил использовать as3crypto, и вы можете скачать его с code.google.com . Вместо этого вы можете использовать любую библиотеку, которую захотите (или даже свою собственную реализацию), единственное требование — она ​​должна иметь возможность шифровать и дешифровать двоичные данные с помощью ключа.


Первое, что мы хотим сделать — это написать утилиту для шифрования защищенного SWF-файла перед его внедрением. Это требует базовых знаний о библиотеке as3crypto, и это довольно просто. Добавьте библиотеку в путь к вашей библиотеке, и давайте начнем с написания следующего:

1
2
3
4
var aes:AESKey = new AESKey(binKey);
var bytesToEncrypt:int = (data.length & ~15);
for (var i:int = 0; i < bytesToEncrypt; i += 16)
        aes.encrypt(data, i);

Что тут происходит? Мы используем класс из as3crypto под названием AESKey для шифрования контента. Класс зашифровывает 16 байтов за раз (128-бит), и мы должны зацикливаться на данных, чтобы зашифровать их все. Обратите внимание на вторую строку: data.length & ~ 15. Это гарантирует, что количество зашифрованных байтов может быть разделено на 16, и мы не исчерпываем данные при вызове aes.encrypt () .

Примечание. В этом случае важно понимать смысл шифрования. На самом деле это не шифрование, а запутывание, поскольку мы включаем ключ в SWF. Цель состоит в том, чтобы превратить данные в двоичный мусор, и приведенный выше код выполняет свою работу, хотя он может оставить до 15 незашифрованных байтов (что не имеет значения в нашем случае). Я не криптограф, и я совершенно уверен, что приведенный выше код может выглядеть неубедительным и слабым с точки зрения криптографа, но, как я уже сказал, он совершенно не имеет значения, поскольку мы включаем ключ в SWF.


Пришло время создать еще одну утилиту, которая поможет нам шифровать SWF-файлы. Он почти такой же, как компрессор, который мы создали ранее, поэтому я не буду много говорить об этом. Скомпилируйте его в новый проект как класс документа:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
package
{
    import com.hurlant.crypto.symmetric.AESKey;
    import flash.display.Sprite;
    import flash.events.Event;
    import flash.net.FileReference;
    import flash.utils.ByteArray;
     
    public class Encryptor extends Sprite
    {
        private var key:String = «activetuts»;
        private var ref:FileReference;
         
        public function Encryptor()
        {
            ref = new FileReference();
            ref.addEventListener(Event.SELECT, load);
            ref.browse();
        }
         
        private function load(e:Event):void
        {
            ref.addEventListener(Event.COMPLETE, encrypt);
            ref.load();
        }
         
        private function encrypt(e:Event):void
        {
            var data:ByteArray = ref.data;
             
            var binKey:ByteArray = new ByteArray();
            binKey.writeUTF(key);
             
            var aes:AESKey = new AESKey(binKey);
            var bytesToEncrypt:int = (data.length & ~15);
            for (var i:int = 0; i < bytesToEncrypt; i += 16)
                aes.encrypt(data, i);
             
            new FileReference().save(data);
        }
         
    }
 
}

Теперь запустите его и создайте зашифрованную копию защищенного SWF-файла, сначала выбрав его, а затем сохранив под другим именем.


Вернитесь к загрузочному SWF-проекту. Поскольку контент теперь зашифрован, нам нужно изменить загрузочный SWF и добавить в него код дешифрования. Не забудьте изменить src в теге Embed, чтобы он указывал на зашифрованный SWF.

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
package
{
    import com.hurlant.crypto.symmetric.AESKey;
    import flash.display.Loader;
    import flash.display.Sprite;
    import flash.system.ApplicationDomain;
    import flash.system.LoaderContext;
    import flash.utils.ByteArray;
     
    [SWF (width = 640, height = 423)] //the dimensions should be same as the loaded swf’s
    public class Main extends Sprite
    {
        [Embed (source = «VerletClothEn.swf», mimeType = «application/octet-stream»)]
        // source = path to the swf you want to protect
        private var content:Class;
         
        private var key:String = «activetuts»;
         
        public function Main():void
        {
            var data:ByteArray = new content();
             
            var binKey:ByteArray = new ByteArray();
            binKey.writeUTF(key);
             
            var aes:AESKey = new AESKey(binKey);
            var bytesToDecrypt:int = (data.length & ~15);
            for (var i:int = 0; i < bytesToDecrypt; i += 16)
                aes.decrypt(data, i);
             
            var loader:Loader = new Loader();
            addChild(loader);
            loader.loadBytes(data, new LoaderContext(false, new ApplicationDomain()));
        }
    }
     
}

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


Откройте новый загрузочный SWF-файл с помощью декомпилятора и посмотрите.

Он содержит более тысячи строк жесткого кода шифрования, и, вероятно, получить из него защищенный SWF труднее. Мы добавили еще несколько шагов, которые должен предпринять атакующий:

  1. Он (или она) должен найти DefineBinaryData, который содержит зашифрованный контент, и извлечь его.
  2. Он должен создать утилиту для его расшифровки.

Проблема в том, что создать утилиту так же просто, как вставить копию из декомпилятора в редактор кода и немного подправить код. Я попытался сломать свою защиту сам, и это было довольно легко — мне удалось сделать это приблизительно через 5 минут. Таким образом, мы собираемся провести некоторые измерения против этого.


Сначала мы поместили защищенный SWF-файл в загрузочный SWF-файл, затем зашифровали его, и теперь мы добавим последние штрихи к загрузочному SWF-файлу. Мы переименуем классы, функции и переменные в недопустимые имена.

Говоря недопустимые имена, я имею в виду такие имена, как;; @@, ^ # ^ и (^ _ ^). Круто то, что это важно для компилятора, но не для Flash Player. Когда компилятор обнаруживает недопустимые символы внутри идентификаторов, он не может их проанализировать и, следовательно, проект не может быть скомпилирован. С другой стороны, у игрока нет проблем с этими нелегальными именами. Мы можем скомпилировать SWF с легальными идентификаторами, распаковать его и переименовать в кучу бессмысленных нелегальных символов. Декомпилятор выведет недопустимый код, и злоумышленнику придется вручную просмотреть сотни строк кода, удалив недопустимые идентификаторы, прежде чем он сможет скомпилировать его. Он это заслужил!

Вот как это выглядит перед запутыванием любой строки:

Давайте начнем! Распакуйте загрузочный SWF-файл, используя утилиту, которую мы создали ранее, и запустите шестнадцатеричный редактор.


Попробуем переименовать класс документа. Предполагая, что вы оставили оригинальное имя (Main), давайте поищем его в несжатом загрузчике SWF с помощью шестнадцатеричного редактора:

Переименуйте » Main » в ;;;; , Теперь найдите другие «Main» и переименуйте их в ;;;; тоже.

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

Сохраните и запустите SWF. Оно работает! И посмотрите, что говорит декомпилятор:

Победа !! 🙂


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

После завершения переименования классов и пакетов вы можете начать переименовывать функции и переменные. Их даже легче переименовать, поскольку они обычно появляются только один раз в одном большом облаке. Опять же, убедитесь, что вы переименовываете только «ваши» методы, а не встроенные методы Flash. Убедитесь, что вы не стираете ключ (в нашем случае это «activetuts»).


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


Откройте его в последний раз и посмотрите. Классы, переменные и имена методов запутываются, а защищенный SWF-файл где-то внутри зашифрован. Сначала этот метод может быть медленным, но через несколько раз это займет всего несколько минут.

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

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

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


Поскольку кража SWF является большой проблемой в мире Flash, существуют и другие варианты защиты SWF. Существует множество программ для маскировки AS на уровне байт-кода (например, secureSWF от Kindisoft). Они испортили скомпилированный байт-код, и когда декомпилятор попытается вывести код, он потерпит неудачу и даже иногда будет зависать. Конечно, эта защита лучше с точки зрения безопасности, но стоит $$$, поэтому перед тем, как выбрать, как защитить свой SWF, подумайте о необходимой безопасности. Если речь идет о защите запатентованного алгоритма, который ваша студия с 50 сотрудниками разрабатывает в течение последних двух лет, вы можете подумать о чем-то лучше, чем переименовать переменные. С другой стороны, если вы хотите, чтобы дети не давали ложные высокие оценки, вы можете рассмотреть возможность использования этой техники.

Что мне нравится в этой технике, так это то, что ваш защищенный SWF-файл остается нетронутым при запуске. Обфускация AS вмешивается в байт-код, и это может повредить SWF-файл и вызвать ошибки (хотя я сам с ними не сталкивался).

На сегодня все, надеюсь, вам понравился урок и вы узнали что-то новое! Если у вас есть какие-либо вопросы, не стесняйтесь оставлять комментарии.