Главная > Программирование, системное > Безопасность: Защита исполняемого кода шифрованием.

Безопасность: Защита исполняемого кода шифрованием.

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

Итак, для начала делаем заготовку для нашей программы. Выглядеть это будет примерно так:

#include "stdafx.h"

#include
#include
#include

using namespace std;

int main(int argc, char *argv[])
{

cout << "Encrypted mega-code ;-)"; return 0; }[/code] В общем то эта вся основная "функциональная" часть нашей программы. Не много, но для того чтобы понять логику достаточно. Далее нам потребуется как-то найти в скомпилированном коде ту часть, которую требуется зашифровать/расшифровать. Для этого неплохо было бы пометить её. Вставим перед выводом сообщения и после него метки: [code lang="cpp"]__asm { NOP; NOP; NOP; NOP; NOP; }[/code] Ассемблерные вставки при дизасcемблировании не изменятся, поэтому их будет достаточно легко найти. Чтобы наша программа в своём конечном виде работала неплохо было бы расшифровать её код при записи. Сделаем заготовку определим пустую (пока что) функцию decrypt() типа void и вставим её вызов в самое начало нашего приложения. Теперь скомпилируем всё это дело. Теперь открываем OllyDbg или любой другой debugger, запускаем в нём наше приложение в пошаговом режиме и ищем нужный код между нашими NOP'ами. Запоминаем виртуальные адреса в памяти и сколько байт кода между метками надо зашифровать. У меня начало искомого диапазона равно 0x00411648 (в Вашем приложение значение будет отличаться), конец - 0x0041165b. Пришло время дописать функцию расшифровки. Будет использовано три API-функции - ReadProcessMemory() и WriteProcessMemory(), их смысл, думаю, понятен, и функция OpenProcess() для доступа к виртуальной памяти процесса. Для шифрования выбран единственный шифр, для которого доказана абсолютная криптографическая стойкость - шифр Вернама. Если кто-то не понял, то шифровать будем с помощью XOR'а (операция "исключающего или", или, побитовое сложение по модулю 2). Ключ, правда, в этом примере будет примитивным - каждый байт будем XOR'ить с единичкой. Итак, наша функция должна считать нужную область памяти (19 байт), расшифровать код в ней и записать обратно. Вот как это выглядит:
[code lang="cpp"]void decrypt()
{
HANDLE hProc = OpenProcess(
PROCESS_VM_OPERATION | PROCESS_VM_WRITE | PROCESS_VM_READ,
TRUE,
GetCurrentProcessId()
);

BYTE buffer[19];
UINT* memaddr = (UINT*)0x00411648;

ReadProcessMemory(hProc, (PBYTE*)memaddr, buffer, sizeof(buffer), NULL);

for(int i = 0; i < 19; i++)
{
buffer[i] ^= 1;
}

WriteProcessMemory(hProc, (PBYTE*)memaddr, &buffer, sizeof(buffer), NULL);
}

Попробуем перекомпилировать нашу программу и запустить. С компилированием проблем возникнуть не должно, а вот при запуске - мы получим ошибку. Дело в том, что наша программа пытается расшифровать незашифрованный участок кода и в итоге получает полный бред. Исправить это не сложно. Открываем исполняемый файл в любом hex-редакторе (например, WinHex) и ищем шестнадцатеричное значение 9090909090 - это наша метка, после первого вхождения через несколько байт (в моём случае это было через 19 байт) встретится вторая метка, как и было в исходном коде. Как Вы наверное уже догадались команде ассемблера NOP соответствует машинный код длиной в 1 байт и равный 0x90. Теперь зашифровываем участок между меток и сохраняем изменения. Шифровать можно вручную (при ключе равным единице чётные значения байт надо увеличивать на 1, а нечётные - уменьшать) или написать программку которая за Вас будет это делать.

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

Полный код программы:

#include "stdafx.h"

#include
#include
#include

using namespace std;

void decrypt()
{
HANDLE hProc = OpenProcess(
PROCESS_VM_OPERATION | PROCESS_VM_WRITE | PROCESS_VM_READ,
TRUE,
GetCurrentProcessId()
);

BYTE buffer[19];
UINT* memaddr = (UINT*)0x00411648;

ReadProcessMemory(hProc, (PBYTE*)memaddr, buffer, sizeof(buffer), NULL);

for(int i = 0; i < 19; i++)
{
buffer[i] ^= 1;
}

WriteProcessMemory(hProc, (PBYTE*)memaddr, &buffer, sizeof(buffer), NULL);
}

int main(int argc, char *argv[])
{
decrypt();

__asm
{
NOP;
NOP;
NOP;
NOP;
NOP;
}

cout << "Encrypted mega-code ;-)"; __asm { NOP; NOP; NOP; NOP; NOP; } return 0; }[/code] Ссылки к статье: Описание шифра Вернама.

Пожалуйста, оцените полезность и качество данной статьи. Одна звезда - плохо, 5 - хорошо.
1/5. Мы будем признательны, если вы напишете комментарий с причиной низкой оценки.2/5. Мы будем признательны, если вы напишете комментарий с причиной низкой оценки.3/5. Мы будем признательны, если вы напишете комментарий с причиной низкой оценки.4/5.5/5. (2 голосов, средний: 5,00 из 5)
Загрузка...
  1. Пока что нет комментариев.
  1. Пока что нет уведомлений.