В данной статье будет приведён пример защиты исполняемого кода от различных анализаторов и прочей бяки. Данный способ может использоваться как для защиты кода от крякеров, так и для написания зловредного ПО (чего вам крайне не рекомендую), которое делает неизвестно что… код то зашифрован. Исходники на C++.
Итак, для начала делаем заготовку для нашей программы. Выглядеть это будет примерно так:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | #include "stdafx.h" #include #include #include using namespace std; int main(int argc, char *argv[]) { cout << "Encrypted mega-code ;-)"; return 0; }[/code] |
В общем то эта вся основная «функциональная» часть нашей программы. Не много, но для того чтобы понять логику достаточно. Далее нам потребуется как-то найти в скомпилированном коде ту часть, которую требуется зашифровать/расшифровать. Для этого неплохо было бы пометить её. Вставим перед выводом сообщения и после него метки:
1 2 3 4 5 6 7 8 | [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 байт), расшифровать код в ней и записать обратно. Вот как это выглядит:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 | [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, а нечётные — уменьшать) или написать программку которая за Вас будет это делать.
После этих нехитрых манипуляций программа должна вновь заработать, а Ваш код, который хранится в исполняемом файле, хоть как-то защищён. Но, это только основы.
Полный код программы:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 | #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] |