![]() |
В программировании принято разделять шифрование на статическое и динамическое. Первый вариант подразумевает крипт всей тушки дискового образа программы, и обратный декрипт его при загрузки в память. Он прост в практической реализации, но как и следует из алгоритма, защищает лишь двоичный образ софта на жёстком диске, а в памяти ОЗУ код уже сбрасывает с себя всю маскировку и становится полностью доступным исследователю. Остаётся тупо снять дамп памяти, и в оффлайне не торопясь разбирать защиту.
Чтобы хоть как-то этому противостоять, программисты придумали "динамическое шифрование" (Dynamic Encryption), когда прожка криптуется не вся целиком, а отдельными блоками, в качестве которых могут выступать, например, процедуры. Теперь, после загрузки образа в память, расшифровывается только исполняемый на текущий момент блок кода, а остальная часть остаётся лежать как и прежде в зашифрованном виде. Когда исполнение доходит до следующего блока он разворачивается, а отработанный опять сворачивается. Таким образом, снятие дампа со-всей программы становится бесполезным, и взломщику приходится дампить каждую процедуру в отдельности. В данной статье рассматривается возможный вариант подобного метода шифрования в динамике. https://forum.antichat.xyz/attachmen...59531bbf5d.png Оглавление: 1. Плюсы и минусы идеи; 2. Реализация в примитивах; 3. SEH – обработчик исключений; 4. Модуль расшифровки и шифрования процедур; 5. Сбор информации о шифровании; 6. Практика – пример программы; 7. Заключение. ----------------------------------------------------------- 1. Плюсы и минусы динамического шифрования Когда коду есть-что скрывать от общественности, самый простой вариант – зашифровать его к чертям. В природе встречаются горячие головы, которые применяют даже многослойный крипт, по типу матрёшки. Это конечно-же перебор, но при грамотном проектировании тактика безотказная, ведь в конечном счёте подразделения вражески настроенной публики столкнутся (пусть и с небольшим) препятствием, и мы добьёмся своей цели. Только вот умиротворённый пейзаж портит софт, позволяющий дампить процессы, сводя на нет все наши старания. Тут-то и всплывает буйком динамическое шифрование кода/данных, использованию которого на практике препятствуют в основном следующие две проблемы: • Во-первых оптимизация и производительность, ..а точнее полное отсутствие таковых. В силу того, что вызов каждой из процедур нужно предварять расшифровкой их содержимого, так после отработки требуется время и на обратную шифровку, чтобы восстановить программу в первоначальный вид – иначе в динамическом крипте просто теряется смысл. Это основной недостаток, который неизбежно отнимает у нас фору по-времени. • Во-вторых – некоторая сложность разработки и трудоёмкость интерактивной отладки, что подтвердит практическая часть. Ведь мало набросать в блокноте правильный план, так нужно ещё заставить его работать в боевых условиях. По сути отлаживать и статический крипт не просто, так-что в отличии первого пункта это ещё можно как-то пережить. Ну а в остальном, динамическое шифрование превосходит статическое по всем фронтам, а что самое главное – это отличный объект для практики. Разрабатывая его алгоритм можно забрести в такие дебри, куда не каждый взломщик решится залезть. К примеру, ничто не мешает шифровать разные блоки разными ключами, связывать эти блоки (аля процедуры) между собой, как это делает тот-же AES в режимецепочки CBC(Cipher-Block-Chaining), и т.д.п. В общем направлений для самовыражения здесь предостаточно. 2. Реализация в примитивах Теперь поговорим о реалиях.. Пусть наша программа имеет с десяток-другой самостоятельных процедур, живописно разбросанных по всему коду. Задача состоит в том, чтобы зашифровать эти процедуры и при их вызове перехватывать управление, для расшифровки и последующего исполнения. Соответственно нужна какая-то служебная функция для этих целей, которую мы оформим и поместим в специальную "промзону" внутри программы чуть позже. Функция будет отслеживать запуск всех рабочих процедур: расшифровывать их на старте, и обратно шифровать на выходе. Здесь притаившись в окопах нас поджидают несколько проблем: 1. Служебная функция НЕ должна принимать никаких аргументов от основного кода, иначе взломщик сможет ухватиться за них и раскурить весь наш тайный план. Функцию нужно наделить как-минимум "байтом" собственного интеллекта, чтобы она на автомате вычисляла, к какой именно процедуре идёт обращение. Вариантов тут у нас всего два – это аппаратные "точки-останова" BreakPoint с использованиемотладочных регистров DR0-DR7(в этом случае мы сможем обслуживать не более четырёх процедур по кол-ву регистров DRx), или-же задействовать свой структурированный обработчик исключений SEH (Structured Exсeption Handler). Остальные варианты производные от них. Дабы не ограничивать себя в кол-ве рабочих процедур, я выбрал второй вариант SEH – ему без разницы, сколько клиентов тащить на себе. В следующей главе мы рассмотрим его детали. 2. Непосредственно крипт/декрипт исполняемых процедур. Это атомный реактор всей программы – именно на нём будет держаться вся конструкция, а потому нужно отнестись к данному модулю с особой внимательностью. Байки про то, как сложно организовать шифрование сильно преувеличены: тут главное чётко представлять себе конечную цель. Основное требование заключается лишь в определении начального адреса блока шифрования, и его длины. Можно было использовать в примере более серьёзный метод шифрования типа AES, позвав на помощь функцию BСryptEncrypt() из либы Bcrypt.dll, но для демонстрационного примера это громоздко, и я ограничился обычным XOR. На рисунке ниже представлен алгоритм всей программы из части(6) данной статьи. Это секция-кода, которая начинается с выделенных синим трёх рабочих процедур А,В,С, хотя их может быть сколько угодно. Сразу за процедурами следует обёрнутая в модуль SEH служебная функция, и далее начинается непосредственно сам код с точкой-входа в программу Код:
start:В самом хвосте адресного пространства валяется (в специально выделенной для этих целей секции) временный "криптор". Его задача – собрать информацию о процедурах типа: начало\размер\ключ блока шифрования, и вывести все эти данные на консоль. Это избавит нас от выносящего мозг ручного вычисления паспортов процедур. Модуль обязательно должен находиться в самом конце программы, чтобы после его удаления не съехали со-своих позиций все адреса. После того-как он выполнит свою задачу, нужно будет в HEX-редакторе вписать вместо него Код:
JMPhttps://forum.antichat.xyz/attachmen...d5775ba507.png В данном случае из кода вызывается процедура(А) и поскольку модулю SEH система передаёт управление только в случае возникновения исключений "Exception", то вызов процедуры нужно будет предварить какой-нибудь исключительной ситуацией типа: деление на нуль, чтение с ядерного или иного недоступного прикладной задаче адреса, или-же специально предназначенной для этих целей инструкцией генерирования ошибок Код:
UD2Код:
UD2Создаём переменную со-значением нуль и делаем вид, что она предназначена для какого-нибудь указателя. На самом деле это утка и мы вообще не планируем ложить туда линк, а просто считываем этот нуль в любой регистр и используя его как адрес, пытаемся прочитать с него. В результате получаем нарушение прав доступа "Access Violation" с кодом исключения Код:
0xC0000005Код:
UD2На момент, когда SEH получит управление, диспетчер исключений мастдая положит в стек адрес глючной инструкции, и если прибавить к нему длину этой инструкции (в случае с UD2 и чтения с нулевого адреса это 2 байта), то в аккурат получим смещение к запрашиваемой процедуре, которая находится ниже аварийной: C-подобный: [CODE] . data zero dd 0 ; //Pass-->Info-->mySeh. Следующий момент – это ключи шифрования, и адрес возврата. Поскольку это просто пример, я храню их в открытом виде, хотя и выделил под них отдельную программную секцию, чтобы они не светились в дизассемблере. Паспорт каждой из процедур храню в своих массивах, которые выбираю табличным методом – вот фрагмент:[/FONT] C-подобный: Код:
sectionВ первом поле каждого из массивов лежат адреса процедур, а дальше.. для вычисления размера первой процедуры "Mess", я беру её разницу в байтах со-следующей "Pass", и оставив в резерве байт(-1) делю на 4. Это потому, что в третьем поле ключ шифрования у меня 4-байтный, и планирую шифровать процедуры блоками такого-же размера. (для справки: 1-байтный ключ можно подобрать за 256 итераций, 2-байтный уже за 65536, а 4-байтный более чем за 4 млрд). Когда внутри SEH из контекста регистров мы получим "адрес метки" вызываемой процедуры в лице значения EIP+2, нужно будет пройтись по таблице "procTable" где все они собраны, и получить указатель на соответствующий массив "keyMess" или другой (см.второй dword в procTable). Для расшифровки и последующего шифрования любой из процедур больше ничего и не требуется. C-подобный: [CODE] ; //-------------------------------------------------// ; //-------- Обработчик исключений SEH --------------// ; //-------------------------------------------------// proc mySeh pRecord , pFrame , pContext , pParam local pRet dd 0 mov ebx , [ pContext ] mov ebx , [ ebx + 184 ] ; // Context.EIP add ebx , 2 ; // EBX = адрес метки процедуры для вызова mov [ pRet ] , ebx ; // ...(адрес возврата в лок.переменную) mov esi , procTable ; // ESI = таблица указателей mov ecx , 3 ; // ECX = число записей в ней @@ : lodsd ; // EAX = очередное значение из таблицы cmp eax , ebx ; // ищем массив записей процедуры je @procFound ; // если нашли! add esi , 4 ; // иначе: переход к сл.записи loop @b ; // пройтись по всей таблице @procFound : mov ebx , [ esi ] ; // EBX = указатель на массив данных mov esi , [ ebx ] ; // ESI = адрес процедуры (первый DWORD из массива) mov ecx , [ ebx + 04 ] ; // ECX = длина процедуры (второй DWORD) mov eax , [ ebx + 08 ] ; // EAX = ключ шифрования (третий DWORD) push esi ecx eax esi ; // запомнить все данные для обратного шифрования ; //--- Декрипт @@ : xor [ esi ] , eax add esi , 4 loop @b pop esi call esi ; // , esi , ecx , ebx mov esi , Pass mov ecx , [ keyPass + 4 ] mov ebx , [ keyPass + 8 ] cinvoke printf , , esi , ecx , ebx mov esi , Info mov ecx , [ keyInfo + 4 ] mov ebx , [ keyInfo + 8 ] cinvoke printf , , esi , ecx , ebx jmp @next push start ; //F6[/COLOR] выбираем секцию кода. Далее манипулируя стрелками находим нужный адрес, который мы получили на предыдущем этапе (в данном случае 0x00403000, начало секции), и задаём ключ шифрования последовательностью F3-->F8 (Edit, XOR). По-умолчанию Hiew ксорит байтами и в этом окне нет возможности изменить длину ключа. Но поскольку ключ у нас 4-байтный, то придётся записывать его задом-наперёд, как показано на скрине ниже:[/FONT] https://forum.antichat.xyz/attachmen...43be094e41.png После того-как определились с ключом, подтверждаем свои намерения клавишей Enter и всё той-же F8 приступаем к шифрованию первой процедуры. Как видно из логов, размер её 20 двойных слов DWORD, так-что жмём F8 ровно 20-раз (цвет зашифрованных байт должен меняться на жёлтый). По окончании сохраняем изменения по F9 и выходим из редактора по F10. Выйти надо для того, чтобы сменить значение ключа для шифрования следующей процедуры. Проделываем аналогичные операции с остальными двумя блоками кода. Посмотрите на предыдущий фрагмент.. В самом его хвосте имеются строки Код:
push start --> retОни вставлены для того, что получить опкоды перехода на EntryPoint в программу. Если посмотреть в отладчике, этим инструкциям (в данном случае) будет соответствовать последовательность байт Код:
0x6846324000c3https://forum.antichat.xyz/attachmen...8610bde523.png 6. Практика – пример программы Соберём теперь всё вышеизложенное под один капот. Обратите внимание, что в шапке программы директива Код:
entry @hideCryptКод:
JMPКод:
start:C-подобный: Код:
format pe consoleКстати если вскормить этот код аверам, то из 13-ти всего двое посчитали его малварью, хотя на самом деле в нём нет ничего особого, а только крипт. Так-что слепо доверять антивирусам тоже не есть гуд, хотя у каждого своё право. https://forum.antichat.xyz/attachmen...1909acc1bb.png 7. Заключение Здесь был рассмотрен классический вариант динамического шифрования, ..как говорят фундаментальные основы. В реальных-же программах имеет смысл воспользоваться более продвинутыми методами шифрования, с использованием специальных API-функций из библиотек Advapi32.dll, Bcrypt.dll и прочих. Потренировавшись "на кошках" и освоив эту технику можно смело двигаться дальше и придумать алгоритм, при котором шифруемые процедуры связывались-бы между собой в цепочку так, что одна без другой просто не функционировала-бы. Хорошие результаты даёт и периодический пересчёт контрольной суммы отдельных процедур и всего кода в целом. Это позволит предотвратить всякого рода мод двоичного кода, если вдруг кто-то захочет изменить Код:
JZКод:
JNZВ скрепку кладу исполняемый файл данного исходника – попробуйте погонять его в отладчике и найти пароль. Для тех-кто захочет попрактиковаться в шифровании и собрать исходник своими руками, в скрепке имеется и HEX-редактор "Hiew 8.66", способный редактировать как 32, так и 64-бит приложения. Всем удачи, пока! |
Судя по скриншотам с результатами детекта антивирусов - они обиделись на инструкцию XOR. Возможно, её тоже можно замаскировать.
|
Статья, как всегда, на высоте! спасибо!
Цитата:
|
Цитата:
Код:
XORАнтивирусы гоняют код на своих вирт.машинах и по своим шаблонам (штам) смотрят, что он делает. К примеру вот тот-же Код:
XORC-подобный: Код:
includeЦитата:
Код:
QWORD |
Разве на x64 это сработает, там же safeseh ?
|
Цитата:
https://forum.antichat.xyz/attachmen...871b6edc57.png |
Господи, да это лучший раздел. Спасибо,Marylin. Очень приятно тебя читать.
|
Спасибо! Как всегда супер!
|
Цитата:
ps: Marylin спасибо огромное тебе за твои статьи, тот случай, когда один человек вывозит весь форум (не в обиду остальным авторам) |
Цитата:
Статья шикарная, спасибо! |
| Время: 06:20 |