Немой укор за компьютерный хардкор Крис Касперски Спецвыпуск: Хакер, номер #048, стр. 048-058-5 Для успешной расшифровки процедуры нам необходимо определить стартовый адрес ее размещения в памяти. Это легко. Современные языки высокого уровня поддерживают операции с указателями на функцию. На Си/Си++ это выглядит приблизительно так: "void *p; p = (void*) func;". Сложнее измерять длину функции. Легальные средства языка не предоставляют такой возможности, и приходится хитрить, определяя длину как разность двух указателей: указателя на зашифрованную функцию и указателя на функцию, расположенную непосредственно за ее концом. Разумеется, если компилятору захочется нарушить естественный порядок следования функций, этот прием не сработает и расшифровка пойдет прахом. И последнее. Ни один из всех известных мне компиляторов не позволяет генерировать зашифрованный код, и эту операцию приходится осуществлять вручную – с помощью HIEW'а или своих собственных утилит. Но как мы найдем шифруемую функцию в двоичном файле? Взломщики используют несколько конкурирующих способов, в зависимости от ситуации отдавая предпочтение то одному, то другому. В простейшем случае шифруемая функция окантовывается маркерами – уникальными байтовыми последовательностями, гарантированно не встречающимися в остальных частях программы. Обычно маркеры задаются с помощью директивы _emit, представляющей собой аналог ассемблерного DB. Например, следующая конструкция создает текстовую строку "KPNC" – __asm _emit 'K' __asm _emit 'P' __asm _emit 'N' __asm _emit 'C'. Только не пытайся располагать маркеры внутри шифруемой функции. Процессор не поймет юмора и выплюнет исключение. Накалывай маркеры на вершину и дно функции, но не трогай ее тело! Выбор алгоритма шифрования непринципиален. Кто-то использует XOR, кто-то тяготеет к DES или RSA. Естественно, XOR ломается намного проще, особенно если длина ключа невелика. Однако в примере, приведенном ниже, мы остановимся именно на XOR, поскольку DES и RSA крайне громоздки и совершенно ненаглядны. #define CRYPT_LEN ((int)crypt_end - (int)for_crypt) // маркер начала mark_begin(){__asm _emit 'K' __asm _emit 'P' __asm _emit 'N' __asm _emit 'C'} // зашифрованная функция for_crypt(int a, int b) { return a + b; } crypt_end(){} // маркер конца mark_end (){__asm _emit 'K' __asm _emit 'P' __asm _emit 'N' __asm _emit 'C'} // расшифровщик crypt_it(unsigned char *p, int c) { int a; for (a = 0; a < c; a++) *p++ ^= 0x66; } main() { // расшифровываем защитную функцию crypt_it((unsigned char*) for_crypt, CRYPT_LEN); // вызываем защитную функцию printf("%02Xh\n",for_crypt(0x69, 0x66)); // зашифровываем опять crypt_it((unsigned char*) for_crypt, CRYPT_LEN); } Откомпилировав эту программу обычным образом (например, "cl.exe /c FileName.C"), мы получим объектный файл FileName.obj. Теперь нам необходимо скомпоновать исполняемый файл, предусмотрительно отключив защиту кодовой секции от записи. В линкере Microsoft Link за это отвечает ключ /SECTION, за которым идет имя секции и назначаемые ей атрибуты, например, "link.exe FileName.obj /FIXED /SECTION:.text,ERW". Здесь: /FIXED – ключ, удаляющий перемещаемые элементы (мы ведь помним, что перемещаемые элементы необходимо удалять), ".text" – имя кодовой секции, а "ERW" – это первые буквы Executable, Readable, Writable, хотя при желании Executable можно и опустить – на работоспособность файла это никак не повлияет. Другие линкеры используют свои ключи, описание которых можно найти в документации. Имя кодовой секции не всегда совпадает с ".text", поэтому, если у тебя что-то не получается, используй утилиту MS DUMPBIN для выяснения конкретных обстоятельств. |