Извлекаем данные из первого незаконного простого числа

Наш мир полон странностей: иногда — это странности природы, иногда — странности человеческой сущности, а порой странности ожидают нас там, где мы больше всего ожидаем увидеть практичность и порядок. Эти два качества обычно присущи двум вещам: математике, которая описывает мир чисел и иных объектов, и юриспруденции, которая описывает и вводит правила и нормы, управляющие нашим обществом. Иногда математика и законодательство пересекаются, помогая друг другу, но изредка бывает так, что первая дисциплина сильно смущает другую…

Внимание! Данная публикация создана в ознакомительных и учебных целях, материалы данной статьи преследует познавательную цель. Использование данных из статьи иными способами, кроме как указанных в предыдущем предложении, может привести вас к судебному преследованию. Мы не рекомендуем проверять работоспособность информации, полученной с помощью способа, который будет представлен далее, авторы блога не несут ответственности за ваши дальнейшие (возможно, противоправные) действия с описанным материалом.

Законы математики универсальны для нашей Вселенной, и обладают наивысшим приоритетом, поскольку ее конструкции и объекты повсеместны. Именно в силу своего широкого проникновения и доступности для достаточно пытливого ума предметы математики представляют собой нечто, что находится над любым полем действия юридических норм.

Возьмем такой объект, как числа. Что такое число? Это запись некоторой идеи количества или качества с помощью цифр. К примеру, вы можете записать сколько у вас монет в кармане, а можете записать и нечто иное, например, оптическую плотность раствора в инфракрасном диапазоне. Но все это блекнет перед идеей представить некоторую программу или файл с ее исходным кодом в виде простого числа, особенно, если учесть что программа делает нечто нарушающее закон…

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

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

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

Первое незаконное простое число выглядит так (внимание, 1 811 цифра!):

4 85650 78965 73978 29309 84189 46942 86137 70744 20873 51357 92401 96520 73668 69851 34010 47237 44696 87974 39926 11751 09737 77701 02744 75280 49058 83138 40375 49709 98790 96539 55227 01171 21570 25974 66699 32402 26834 59661 96060 34851 74249 77358 46851 88556 74570 25712 54749 99648 21941 84655 71008 41190 86259 71694 79707 99152 00486 67099 75923 59606 13207 25973 79799 36188 60631 69144 73588 30024 53369 72781 81391 47979 55513 39994 93948 82899 84691 78361 00182 59789 01031 60196 18350 34344 89568 70538 45208 53804 58424 15654 82488 93338 04747 58711 28339 59896 85223 25446 08408 97111 97712 76941 20795 86244 05471 61321 00500 64598 20176 96177 18094 78113 62200 27234 48272 24932 32595 47234 68800 29277 76497 90614 81298 40428 34572 01463 48968 54716 90823 54737 83566 19721 86224 96943 16227 16663 93905 54302 41564 73292 48552 48991 22573 94665 48627 14048 21171 38124 38821 77176 02984 12552 44647 44505 58346 28144 88335 63190 27253 19590 43928 38737 64073 91689 12579 24055 01562 08897 87163 37599 91078 87084 90815 90975 48019 28576 84519 88596 30532 38234 90558 09203 29996 03234 47114 07760 19847 16353 11617 13078 57608 48622 36370 28357 01049 61259 56818 46785 96533 31007 70179 91614 67447 25492 72833 48691 60006 47585 91746 27812 12690 07351 83092 41530 10630 28932 95665 84366 20008 00476 77896 79843 82090 79761 98594 93646 30938 05863 36721 46969 59750 27968 77120 57249 96666 98056 14533 82074 12031 59337 70309 94915 27469 18356 59376 21022 20068 12679 82734 45760 93802 03044 79122 77498 09179 55938 38712 10005 88766 68925 84487 00470 77255 24970 60444 65212 71304 04321 18261 01035 91186 47666 29638 58495 08744 84973 73476 86142 08805 29443

В «правильном» виде оно представляет собой небольшой архив, созданный с помощью gzip, с приблизительным размером в 582 байта. Данный архив содержит в себе лишь один файл, который является текcтовым. В том файле записан исходный код на языке программирования C, который запрещен к хранению и распространению в рамках DMCA (насчет ситуации в нашей стране непонятно, законно ли использование представленного кода или нет), поскольку код из файла представляет собой реализацию алгоритма дешифрования DeCSS, который может быть использован для обхода защиты от копирования в DVD.

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

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

С учетом того, что незаконное простое число условно незаконно, то и описание его распаковки найти сложно, потому пришлось думать самим…

Идея распаковки базируется на том, что любой файл в компьютере — это просто последовательность байтов. Первое простое незаконное число — это тоже последовательность байтов, но представленная не в текстовом виде, а в бинарном.

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

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

Вот собственно и вся схема интерпретации, которую мы реализуем с помощью простого кода с использованием самодельного диапазона ввода (InputRange) и ряда алгоритмов из стандартной библиотеки:

import std.algorithm;
import std.bigint;
import std.range;
import std.stdio;
import std.string;

immutable ILLEGAL_PRIME_NUMBER = `число`;
auto illegalBytes(string bigNumber)
{
    struct ResiduesRange
    {
        private BigInt _number;
        
        this(string bigNumber)
        {
            _number = BigInt(bigNumber);
        }
        
        bool empty() @property const
        {
            return (_number == 0);
        }
        
        ubyte front() @property
        {
            return cast(ubyte) (_number % 256);
        }
        
        void popFront()
        {
            _number /= 256;
        }
    }
    
    return ResiduesRange(bigNumber);
}

void main()
{ 
    File file;
    file.open(`decss.gz`, `wb`);
    
    ILLEGAL_PRIME_NUMBER
                        .replace(" ", "")
                        .illegalBytes
                        .array
                        .reverse
                        .each!(a => file.write(cast(char) a));
    
}

Некоторые пояснения относительно кода: replace нужно для того, чтобы из числа честно скопированного со страницы Википедии убрать все пробелы между цифрами и чтобы с помощью BigInt сформировать число из длинной арифметики (т.е. число с большим количеством цифр), и корректно его обработать (напоминаю, BigInt это структура из std.bigint, которая содержит в себе корректную и оптимизированную реализацию длинной арифметики). Функция illegalBytes возвращает так называемый Voldemort-тип (тип, который нельзя упомянуть по имени, или проще говоря анонимный тип), представляющий собой активированный диапазон ввода (InputRange).

Примечание автора. Это диапазон ввода, поскольку структура которая его реализует содержит в себе три метода: empty — свойство проверки на пустоту (или «выбранность», т.е. свойство отвечает на вопрос «есть ли еще элементы в диапазоне?»), front — свойство, выдающее новый элемент диапазона и popFront — метод, генерирующий новое состояние диапазона (метод, дающий данные для front) или манипулирующий данными, которые удерживаются в диапазоне.

Трансформация в массив с помощью array необходима для того, чтобы смог отработать алгоритм reverse, который переворачивает массив: в целом, данной операцией достигается правильный порядок байтов, поскольку он в случае архитектуры x86_64 (мой рабочий компьютер) является обратным к порядку следования остатков при делении. Дальнейшие действия тривиальны: с помощью each диапазон байтов раскручивается до конца, записываясь в итоге, в бинарный файл с именем decss.gz.

В результаты работы этой скромной программы получается файл decss, который следует распаковать с помощью gunzip, поскольку, эта программа способна игнорировать мусорные данные в конце полученного файла (по этой причине, на Windows операция распаковки не приведет к успеху, т.к архиваторы под Windows не умеют игнорировать ненужное (да, даже 7z) — и вуаля, получаем исходный код на С. Часть этого кода вы можете увидеть на скриншоте:

Как вы видите, извлечь исходный код из числа не так уж и сложно, но гораздо сложнее все это было придумать… На этом все, и пожалуйста, будьте аккуратными, если будете проводить подобные «изыскания».

Добавить комментарий