Техника защиты компакт-дисков от копирования

         

Шифровка файлов


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

Поэтому, разработчики защиты предпочитают отталкиваться от уже существующих форматов (например, того же MP3), просто зашифровывая файлы перед записью на мастер-диск, а при проигрывании —– расшифровывая их содержимое "на лету". Минус такого подхода в том, что защиты подобного типа очень легко взломать, —– достаточно просто установить точку останова на функцию CreateFile и, дождавшись открытия нужного нам файла, "подсмотреть" значение регистра EAX на выходе из функции —– оно-то и будет представлять собой дескриптор открытого файла. Теперь остается лишь поставить точки останова на функции SetFilePointer/ReadFile, приказав отладчику "всплывать" только в случае передачи им "нашего" дескриптора. Точка останова, установленная на область памяти, содержащую прочитанные с диска данные, выведет хакера непосредственно на процедуру расшифровки, проанализировав алгоритм которой, хакер сможет написать свой собственный расшифровщик!

А, если алгоритм шифровки представляет собой тривиальную операциюый XOR (как в подавляющем большинстве случаев и наблюдается), взломать содержимое диска можно еще быстрее! Ведь практически все стандартные форматы файлов содержат в себе некоторое количество более или менее предсказуемой информации и потому могут быть расшифрованы атакой по открытому тексту. И AVI, и MP2/MP3, и WMA, и ASF- файлы содержат в себе длинные цепочки подряд идущих нулей (и/или символов с кодом "FF"), а потому ключ шифрования обнаруживается тривиальным просмотром содержимого защищенного файла в любом HEX-редакторе.


Рассмотрим следующий пример. Пусть у нас имеется мультимедийный диск "Depeche Mode The best", содержимое одного из файлов которого выглядит так как это показано в листинге 8.8.:

Листинг 8.8. Hex-дамп заголовка исследуемого файла

K:\sex\1\03 - Strangerlove.dat                 DOS    3472405

00000000:  9D 9A F0 39 62 61 60 3A ¦ 65 A4 8B F0 52 C1 01 98   ЭЪЁ9ba`:eдЛЁR+OШ

00000010:  BA DB 03 5A 54 27 A6 4C ¦ 43 ED 46 1D 8B 21 ED 9A   ¦-¦ZT'жLCэF-Л!эЪ

00000020:  D3 C7 7B 58 4B A6 78 5D ¦ F6 FA F0 A9 55 63 66 A8   L¦{XKжx]Ў·ЁйUcfи

00000030:  7E 6A 5A 79 61 68 E8 7B ¦ 69 47 F9 7B 60 22 E3 88   ~jZyahш{iG•{`"уИ

00000040:  61 E2 67 98 E0 E2 2D ED ¦ 13 AD E3 38 C5 A5 71 FB   aтgШрт-э!ну8+еqv

00000050:  1A 01 C0 B6 85 77 5A 49 ¦ 46 4F 93 7B BF 30 A5 9D   >OL¦ЕwZIFOУ{¬0еЭ

Листинг 76 hex-дамп заголовка исследуемого файла

По внешнему виду это не похоже ни на MP3 (MP3-файлы начинаются с сигнатуры FF FB, прочем, не всегда расположенной в самом начале), ни на WAV (WAV-файлы начинаются с сигнатуры "RIFF"), ни на RealAudio (RealAudio-файлы начинаются с ".RMF"), но… ведь каким-то же образом они все-таки воспроизводятся! И вряд ли это собственный формат разработчиков мультимедийного диска. Скорее всего, файл просто зашифрован. А, раз так, то – его можно попробовать расшифровать!

Прокручиваем экран HEX-редактора вниз, пока не встречаем следующую регулярную последовательность (листинг 8.9).:





Листинг 8.9. Регулярная последовательность, обнаруженная внутри исследуемого файла

K:\sex\1\03 - Strangerlove.dat                               DOS    3472405

000001E0:  C3 5A AF F8 70 4A D8 83 ¦ 5D 9E 9D 86 9D 9E 9D 86   +Zп°pJ+Г]ЮЭЖЭЮЭЖ

000001F0:  9D 9E 9D 86 9D 9E 9D 86 ¦ 9D 9E 9D 86 9D 9E 9D 86   ЭЮЭЖЭЮЭЖЭЮЭЖЭЮЭЖ

00000200:  9D 9E 9D 86 9D 9E 9D 86 ¦ 9D 9E 9D 86 9D 9E 9D 86   ЭЮЭЖЭЮЭЖЭЮЭЖЭЮЭЖ

00000210:  9D 9E 9D 86 AA C2 62 79 ¦ 62 B4 C0 6A 9D 9E 9D 86   ЭЮЭЖкTbyb+LjЭЮЭЖ



00000220:  9D 9E 9D 86 9D 9E 9D 86 ¦ 9D 9E 9D 86 9D 9E 9D 86   ЭЮЭЖЭЮЭЖЭЮЭЖЭЮЭЖ

00000230:  9D 9E 9D 86 9D 9E 9D 86 ¦ 9D 9E 9D 86 9D 9E 9D 86   ЭЮЭЖЭЮЭЖЭЮЭЖЭЮЭЖ

00000240:  9D 9E 9D 86 9D 9E 9D 86 ¦ 70 B8 46 0B 3B 61 63 30   ЭЮЭЖЭЮЭЖp¬F>;ac0

Листинг 77 регулярная последовательность обнаруженная внутри исследуемого файла

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

Поскольку, XOR —– симметричная операция, то ((A XOR B) XOR A) == B, то есть повторная шифровка файла его исходным содержимым дает нам ключ. Предположим, что на этом месте были нули, то – тогда искомый ключ шифрования будет равен ""…9E 9D 86 9D…"". Точки по обе стороны от ключа означают, что мы еще не готовы выделить начало и конец регулярной последовательности. В самом деле, это может быть как "9E 9D 86 9D", так и "9D 86 9E 9D", и даже "86 9D 9E 9D" или вообще "9D 9E 9D 86". Однако, вместо того, чтобы тупо перебирать все четыре варианта, давайте обратим внимание вот на что. Длина регулярной последовательности равна четырем, так? Следовательно, первый байт каждого "периода" должен лежать по смещению, кратноыму четырем. Отсюда, искомая последовательность имеет вид "9D 9E 9D 86", а все остальные варианты неверны, т. к. лежат по неподходящим адресам. Поскольку, начальные адреса HEX-строк, выводимых редактором, выровнены по границе 0х10 байт (а 0x10 кратно 4), то первый байт ключа должен совпадать с начальным адресом любой из строк.

Теперь допустим, что в данном месте оригинального файла находилась цепочка нулей. Тогда ключ шифровки должен выглядеть так: "9D 9E 9D 86"

(т. к. (A XOR 0) == A).


Запускаем HEX-редактор HIEW, нажатием <Enter> переводим его в шестнадцатеричный режим, давим <F3> для активизации режима редактирования, далее нажимаем <F8> и в появившееся диалоговое окно "Enter XOR mask" вводим следующую HEX-последовательность и, уронив "кирпич" на <F8>, идем пить чай, так как HIEW расшифровывает файл ну очень долго и потому ничего не остается, как садиться за свой любимый компилятор и писать собственную программу расшифровки. Листингу, Приведенному листингуниже (листинг 8.10), до образца программистского искусства, конечно, далеко, но как рабочий вариант, сделанный на скорую руку, он вполне сойдет.

Листинг 8.10. [/etc/DeXOR.c] Демонстрационный вариант простейшего шифровщика

/*----------------------------------------------------------------------------

 *

 *                              XORит СОДЕРЖИМОЕ ФАЙЛА ПО ПРОИЗВОЛЬНОЙ МАСКЕ

 *                              ============================================

 *

 * Build 0x001 @ 09.07.2003

----------------------------------------------------------------------------*/

#include <stdio.h>

#define MAX_LEN                 666                                             // макс длина маски

#define MAX_BUF_SIZE            (100*1024)                              // размер буфера чтения

#define FDECODE                 "decrypt.dat"                           // имя расшифрованного файла

main(int argc, char **argv)

{

              long             a, b;

              long             key_len;

              FILE             *fin, *fout;

              long             buf_size, real_size;

              unsigned char key[MAX_LEN];

              unsigned char buf[MAX_BUF_SIZE];

             

              if (argc<3)                     // HELP по ключам командой строки

              {

                      fprintf(stderr,"USAGE: DeXOR.exe file_name AA BB CC DD EE...\n");

                      return 0;



              }

             

              // определение длины ключа и установка размера буфера кратной длине ключа

              key_len = argc - 2;             buf_size = MAX_BUF_SIZE - (MAX_BUF_SIZE % key_len);

             

              // извлекаем ключи из командной строки в массив key

              for(a = 0; a <  key_len; a++)

              {

                      // преобразование из HEX-ASCII в long

                      b = strtol(argv[a+2],&" ",16);

                      

                      if (b > 0xFF)                   // проверка на предельно допустимое значение

                              {fprintf(stderr, "-ERR: val %x  not a byte\x7\n",b); return -1;}

                      

                      key[a] = b;                     // заносим значение очередного байта ключа

              }

             

              printf("build a key:"); // выводим ключ на экран (для контроля)

              for(a=0;a<key_len;a++) printf("%02X",key[a]); printf("\n");

             

              // открываем файлы на для чтения/записи        

              fin = fopen(argv[1],"rb"); fout=fopen(FDECODE,"wb");

              if ((fin==0) || (fout==0))

                      { fprintf(stderr, "-ERR: file open error\x7\n"); return -1; }

              

              // основной цикл обработки

              while(real_size=fread(buf,1, buf_size, fin))

              {

                      // цикл по буферу

                      for (a=0; a<real_size; a+=key_len)

                      {      

                              // цикл по ключу

                              for(b=0; b < key_len; b++)

                                      buf[a+b] ^= key[b];

                      }

                      

                      // скидываем зашифрованный (расшифрованный) буфер на диск

                      if (0 == fwrite(buf, 1, real_size, fout))



                      {

                              fprintf(stderr,"-ERR: file write error\x7\n");

                              return -1;

                      }

              }

              // сваливаем

}

Листинг 78 [/etc/DeXOR.c] демонстрационный вариант простейшего шифровщика

Компилируем и запускаем: ""DeXOR.c "03 – Strangerlove.dat" 9D 9E 9D 86" и… ничего не получается! Расшифрованный файл все равно не похож ни на MP3, ни на другие форматы. Что ж, значит это была не цепочка нулей, а что-то другое, например, последовательность символов "FF". В плане проверки нашей гипотезы выполним операцию "проXOR'им имнад" регулярнойуюую последовательностью 9D9E9D86h и числом FFFFFFh и, если нам повезет, в результате этой операции мы получим оригинальный ключ. Для осуществления задуманного нам вновь понадобиться HEX-редактор HIEW или штатный калькулятор Windows-калькулятор. Запустим его и, кликнув мышкой по меню "Вид"

найдем пункт "Инженерный". Теперь, выберем переместим радио-конпкуположение Hex (шестнадцатеричная система) для переключателя, ответственногоую за выбор системы исчисления из позиции Dec (десятичная система), в которой она находилась по умолчанию, в позицию "Hex" (шестнадцатеричная система), или же попросту нажмем <F5>. Используя мышь и/или клавиатуру введем "9D9E9D86", затем нажмем кнопочкукнопку с надписью "Xor" и введем "FFFFFFFF". Подтвердим серьезность своих намерений нажатием <Enter>. Калькулятор выдаст следующий результатответит: "62616279". Это и есть искомый ключ, вводим его в программу DeXOR, разделяя байты пробелами и…

…и после переименования Strangerlove.dat в Strangerlove.mp3 он соглашается воспроизводиться любым MP3-плейером. Аналогичным путем расшифровываются и все остальные файлы, находящиеся на защищенном диске (ключ шифрования у них разный, но методика его поиска общая).



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

На самом деле, задача получения длинного непериодического ключа элегантно решается с помощью… генератора случайных чисел (или точнее псевдослучайных

чисел). Как наверное помните, псевдослучайная последовательность, генерируемая библиотечной функцией rand() постоянная при каждом запуске программы, поэтому представляет собой отличный и в то же время категорически неочевидный ключ! Собственно говоря, приведенная далее ниже программа (листинг 8.11) именно так и делает.:

Листинг 8.11. [crackme.765B98ECh.c] Использование rand() для хранения ключа шифровки

/*----------------------------------------------------------------------------

 *

 *                      НЕЯВНАЯ ГЕНЕРАЦИЯ КЛЮЧА РАСШИФРОВКИ С ПОМОЩЬЮ RAND()

 *                      ====================================================

 *

 *                      шифрует/расшифровывает файлы, используя функцию rand() для генерации

 * ключа; поскольку функция rand() при каждом запуске дает одну и ту же

 * последовательность, устанавливаемую srand(), мы получаем оччччень длинный

 * и не периодичный ключ шифрования, "ослепляющий" атаку по открытому тексту.

 * К тому же, если чуть-чуть изменить код rand(), то IDA не сможет ее

 * распознать (правда, это не сильно усложнит взлом, т.к. реализация функции

 * rand() в подавляющем большинстве случаев до смешного проста).

 *                      дополнительные уровни защиты обеспечиваются запутываем алгоритма

 * обработки данных (можно, например, создать целую серию расшифровщиков,

 * из которых полезные данные будет выдавать только  один, а  остальные



 * станут взращать бессмысленный мусор)

 *

 *                      NOTE: чтобы зашифровать оригинальный файл, запустите программу

 * с ключом "-crypt" это достаточно сделать один раз (файл, поставляемый

 * на CD-диске, прилагаемом к книге, уже зашифрован и попытка его повторной

 * зашифровке приведет к прямо противоположному результату - файл будет

 * записан на диск в расшифрованном виде)

 *

 * Build 0x001 09.07.2003

----------------------------------------------------------------------------*/

 

#include <stdio.h>

#include <math.h>

#define FNAME                   "file.dat"                      // имя файла для (за|ра)сшифровки

#define MAX_SIZE        (100*1024)                      // максимально возможный размер файла

#define SEED                    0x666                                   // установка последовательности rand()

                                                                                        // это число может быть любым, главное

                                                                                        // чтобы оно было!

//--[crypt]-------------------------------------------------------------------

// fname       - имя файла

// buf                  - указатель на буфер, куда помещать расшифрованные данные

// buf_size    - размер буфера

// need_store  - требуется ли записывать (за|ра)сшифрованный файл на диск

//                      :0 - не записывать, != 0 - записывать

//----------------------------------------------------------------------------

crypt(char *fname, char *buf, int buf_size, int need_store)

{

              FILE            *f;

              long            a, b;

             

              // не забудьте явно проинициализировать генератор случайных чисел, иначе

              // если другие ветки программы так же используют rand(), результат

              // расшифровки получается "плавающим"

              srand(SEED);



             

              // открытие расшифровываемого файла

              f=fopen(fname, "rb"); if (f==0) return -1;

             

              // загрузить данные в буфер

              a = fread(buf, 1, buf_size, f); if (!a || (a == buf_size)) return -1;

             

              // (за|ра) сшифровать содержимое буфера ключом, налету генерируемом

              // функцией rand()

              for (b = 0; b < a; b++) buf[b] ^= (rand() % 255); fclose(f);

             

              // отладочный шлюз для автоматической шифровки файла

              if (need_store)

              {

                      f=fopen(fname, "wb"); if (f==0)  return -1;

                      fwrite(buf, 1, a, f); fclose(f); return -1;

              }

              return a;      

}

main(int argc, char** argv)

{

              long         a,x;

              long         need_store  = 0;

              unsigned char buf[MAX_SIZE];

             

              // TITLE

              fprintf(srderr,"crackme 765b98ec by Kris Kaspersky\n");

             

              // если есть отладочный ключ -crypt зашифровать

              if ((argc > 1) && !strcmp(argv[1],"-crypt")) need_store++;

             

              // загрузить файл FNAME, расшифровать и вывести его содержимое на экран

              if ((x=Crypt(FNAME, buf, MAX_SIZE, need_store))!=-1)

                      for (a=0;a<x;a++) printf("%c",buf[a]);

}


Содержание раздела