Издательский дом ООО "Гейм Лэнд"СПЕЦВЫПУСК ЖУРНАЛА ХАКЕР #48, НОЯБРЬ 2004 г.

Немой укор за компьютерный хардкор

Крис Касперски

Спецвыпуск: Хакер, номер #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 для выяснения конкретных обстоятельств.

Назад на стр. 048-058-4  Содержание  Вперед на стр. 048-058-6