Гошко С.В., Защита исполнимого кода, Argc&Argv, январь 2003, стр.43-48
Защита исполнимого кода Данная статья посвящена защите исполнимого кода от отладки. Для чего эта защита необходима? Рассмотрим самый распространённый пример. Вы разработчик программного обеспечения написали какой-то продукт пользующийся спросом на рынке, защитили его традиционной комбинацией - имя пользователя, идентификационный код. Но уже через неделю - месяц вы будете удивлены количеством программ в Интернете, написанных с целью снятия вашей защиты, причём довольно успешно работающих. Чтобы такая ситуация с вами не случилась необходимо предусмотреть защиту от отладки и дизассемблирования ваших программ, тогда снятие защиты с вашей программы очень сильно усложнится. И цель данной статьи защитить ваши программы от взлома. В начале мы рассмотрим простейшую атакуемую программу и варианты защиты её от отладчиков. Рассмотрим пример атакуемой программы: //----------------------------------------------------------------------------- #include// Подключаемые библиотеки #include #include bool test(char *name1,char *pass1) // Функция проверки корректности пароля { int a,i,z; char str1[300]=""; char valid_pass[300]=""; a=strlen(name1); for (i=0;i<=a-1;i++) { z=(int)name1[i]; z=z+33; itoa(z,str1,10); strcat(valid_pass,str1); } if (strcmp(pass1,valid_pass)==0) // !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! return true; return false; } void main() // Основная функция { char name[1024]; char pass[1024]; printf("Enter user name: "); scanf("%50s",name); printf("Enter registration code: "); scanf("%250s",pass); if (!test(name,pass)) // !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! { printf("Invalid user name or registration code :( \n"); exit(0); } printf("Registration succesfull ;) \n"); } //----------------------------------------------------------------------------- Данная программа в начале получает от пользователя имя пользователя и регистрационный код. Затем в функции test() происходит преобразование имени пользователя по определённому алгоритму (к значению символа прибавляется 33). После преобразования получается строка, которая содержит корректный регистрационный ключ для данного имени пользователя, в конце функции проверяется, соответствует ли введёный пользователем регистрационный код только что сгенерированному, если да, то функция возвращает 1 иначе 0. От результата функции зависит финальное сообщение, успешно ли прошла регистрация или нет. В местах отмеченных восклицательными знаками, чаще всего происходит взлом программы. В начале при помощи отладчика эти места обнаруживаются, а затем при помощи hex-редактора "исправляются". Так вот, первая часть данной статьи посвящена защите от отладчика. Какие существуют возможности защиты от отладчика? Первый и самый простой способ использовать предоставленную разработчиками Windows функцию. В операционных системах Windows, начиная с Windows NT существует функция IsDebuggerPresent. Данная функция аргументов не получает, а в качестве результата возвращает 1, если отладчик обнаружен и 0 в противном случае. Использовать её довольно легко: //----------------------------------------------------------------------------- if (!IsDebuggerPresent()) goto no_debugger //........................................ no_debugger: //----------------------------------------------------------------------------- Воспользуемся ею для того, чтобы при отладке не происходило успешной регистрации в нашей программе. Но любой взломщик со стажем определит, что это за функция по имени и сможет обойти эту защиту. Поэтому мы должны покопаться в содержимом самой функции и там мы увидим, что она состоит всего из 4 идущих ниже ассемблерных инструкций: ;------------------------------------------------------------------------------ mov eax,fs:[018h] mov eax,[eax+30h] movzx eax,byte ptr [eax+02] ret ;------------------------------------------------------------------------------ Для нас существенны только первые три, мы можем из них построить свою функцию, аналогичную IsDebuggerPresent. Перейдём к листингу: //----------------------------------------------------------------------------- #include #include #include #include int debug1() // Функция проверки на присутствие { // Отладчика, она возвращает 1 если asm mov eax,fs:[018h] // отладчик есть и 0, если его нет asm mov eax,[eax+30h] asm movzx eax,byte ptr [eax+02] } bool test(char *name1,char *pass1) { int a,i,z; char str1[300]=""; char valid_pass[300]=""; a=strlen(name1); for (i=0;i<=a-1;i++) { z=(int)name1[i]; z=z+33; itoa(z,str1,10); strcat(valid_pass,str1); } if ((strcmp(pass1,valid_pass)==0)) return true; return false; } void main() { char name[1024]; char pass[1024]; int a; printf("Enter user name: "); scanf("%50s",name); printf("Enter registration code: "); scanf("%250s",pass); if (debug1()||!test(name,pass)) { printf("Invalid user name or registration code :( \n"); exit(0); } printf("Registration succesfull ;) \n"); } //----------------------------------------------------------------------------- Наша программа была изменена таким образом, что даже при вводе правильного пароля в контексте отладчика она будет сообщать, что он не верен. Это будет происходить благодаря нашей функции debug1() и дополнительной проверке условия. Существует так же много других способов определения присутствия отладчика, которые могут находиться в вашей программе. Но нужно хорошо знать ассемблер и особенности процессоров семейства x86, чтобы хорошо придумывать свои трюки защиты от отладчика. Так же относительной защитой вашей программы могут быть добавления множества ложных переходов и вложенных функций. Данная техника позволит очень сильно усложнить жизнь взломщику. Но все данные возможности защиты от отладчиков по прежнему бессильны перед хорошим дизассемблером. Для защиты от дизассемблера чаще всего применяется два приёма. Первый - это так называемый перекрывающийся код, а второй - полное шифрование кода. Рассмотрим сущность перекрывающегося кода и применим его для защиты нашей программы. Рассмотрим листинг: ;------------------------------------------------------------------------------ mov eax,04ebh jmp $-4 next: ;------------------------------------------------------------------------------ Что же делает выделенная восклицательными знаками часть кода? 1) В eax помещается значение 04ebh(это опкод команды jmp $+4) 2) jmp $-4, переходит на значение 04ebh 3) jmp $+4, переходит на метку next Таким образом, можно строить сколь угодно сложный перекрывающийся код. Ещё в перекрывающемся коде есть один большой плюс, с его помощью можно прятать не приятные для кодоанализаторов инструкции. Теперь напишем функцию для нашей программы, которая использует все прелести перекрывающегося кода. Рассмотрим листинг: //----------------------------------------------------------------------------- int a_dizasm() { asm mov eax,04ebh asm jmp $-4 } //----------------------------------------------------------------------------- Благодаря применению антиотладочных и антидизассемблерных трюков наша программа стала гораздо более защищённой. Но всё равно этого недостаточно. Поэтому я рекомендую каждому программисту разработать свою внешнюю защиту, которая будет сочетать трюки, охраняющие от отладки и дизассемблирования. А так же использовать полное криптование программного кода. Для демонстрации всех описанных выше возможностей, мной была написана следующая программа: ;-----------------------------------------------------------------------------; ; ; ; -= SECURE CODE V. 1.3 BY SLON =- ; ; (+) AntiDizasm ; ; (+) AntiDebug ; ; (+) Crypt 32 bit XOR (basic code) ; ; (+) Armoured by SEH ; ; (+) Armoured by CRC ; ; (+) Polymorphizm (PGS 0.4) ; ; (+) Vampirizm ; ; (+) Anti Virus Capability ; ; (+) EPO ; ; ; ;-----------------------------------------------------------------------------; ; ; ; Usage: secure.exe [your_program.exe] ; ; ; ;xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx; .386 .model flat extrn ExitProcess:PROC extrn GetFileSize:PROC extrn CreateFileA:PROC extrn CreateFileMappingA:PROC extrn MapViewOfFile:PROC extrn UnmapViewOfFile:PROC extrn CloseHandle:PROC extrn SetFilePointer:PROC extrn SetEndOfFile:PROC extrn printf:PROC extrn GetTickCount:PROC extrn GetCommandLineA:PROC extrn Sleep:PROC extrn CopyFileA:PROC extrn lstrcatA:PROC extrn scanf:PROC extrn GetComputerNameA:PROC extrn WriteFile:PROC extrn ReadFile:PROC .data start: ;-----------------------------------------------------------------------------; push offset message2 ; Выводим сообщение об call printf ; использовании программы pop eax call commandline ; Получаем argv[1] call attach ; Усиливаем файл ;-----------------------------------------------------------------------------; End_it: cmp eax,-1 ; Если была ошибка jne armoured ; то push offset message3 ; Выводим сообщение об ошибке call printf ; и завершаем программу jmp zakat ; armoured: push offset message4 ; Если всё хорошо, то выводим call printf ; другое сообщение и завершаем zakat: ; программу push 0 ; call ExitProcess ; Завершение процесса ;xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx; ; ATTACH SUBROUTINE ; ;-----------------------------------------------------------------------------; ; [ IN ] ; ; ; ; NO INPUT IN SUBROTINE ; ;-----------------------------------------------------------------------------; ; [ OUT ] ; ; ; ; NO OUTPUT IN SUBROTINE ; ;xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx; attach: ; Подпрограмма модификации ; исполнимого файла OpenFile: push 0 push 00000080h push 3 push 0 push 00000001h OR 00000002h push 40000000h OR 80000000h push esi call CreateFileA ; Открытие файла ; для чтения/записи mov fHnd,eax ; Сохранение хэндла файла cmp eax,-1 je InfectionError push offset message1 call printf pop eax call backup_file ;-----------------------------------------------------------------------------; push 0 ; push fHnd ; Получаем размер файла call GetFileSize ; mov filesize,eax ; Сохраняем размер файла add eax,finish-decrypt+10000h ; filesize + decryptor mov memory,eax ; +workspace=memory ;-----------------------------------------------------------------------------; CreateFileMapping: ; выделяем память push 0 ; имя файла хэндл = 0 push memory ; макс. размер = memory push 0 ; минимальный размер = 0 push 4 ; доступ чтение/запись push 0 ; push fHnd call CreateFileMappingA ; eax = хэндл mov mHnd,eax or eax,eax jz CloseFile MapViewOfFile1: push memory ; количество памяти ; для работы push 0 ; push 0 ; push 2 ; Режим записи push eax ; хэндл call MapViewOfFile ; Вызываем функцию or eax,eax jz CloseMap mov esi,eax ; mov mapaddress,esi ; Сохраняем базу памяти DoSomeChecks: cmp word ptr [esi],'ZM' ; Это EXE файл? jne UnmapView cmp word ptr [esi + 38h],'nf' ; Уже усиленный файл? je UnmapView OkGo: mov ebx,[esi + 3ch] cmp ebx,200h ja UnmapView add ebx,esi cmp word ptr [ebx],'EP' ; Это PE файл ? jne UnmapView mov PEheader,ebx ; сохраняем PE заголовок mov esi,ebx mov eax,[esi + 28h] mov oldip,eax ; Сохраняем старую точку ; входа mov eax,[esi + 34h] mov imagebase,eax ; Сохраняем ; виртуальный адрес ; начала программы call find_code ; Вызов подпрограммы поиска ; и усиления кода call epo ; Вызываем функцию их ; подмены ;-----------------------------------------------------------------------------; lea eax,decrypt ; mov edi,offset buff2 ; Заполняем параметры и ; вызываем полиморфный mov ecx,finish-decrypt ; генератор PGS call morph ; mov mega_size,ecx mov mega_offset,edi ;-----------------------------------------------------------------------------; LocateBeginOfLastSection: mov ebx,[esi + 74h] ; shl ebx,3 ; xor eax,eax mov ax,[esi + 6h] ; Количество объектов dec eax ; (нам нужен последний-1 mov ecx,28h ; заголовок секции) mul ecx ; * размер заголовка add esi,78h ; теперь esi указывает ; на начало последнего add esi,ebx ; заголовка секции add esi,eax ChangeLastSectionHeader: or [esi + 24h],00000020h or 20000000h or 80000000h NewPhysicalSize: mov eax,[esi+10h] ; Старый физический ; размер add eax,mega_size mov [esi+10h],eax ; Сохраняем его VirtualSizeCheck: mov edi,[esi + 8h] ; Получаем старый cmp eax, edi ; виртуальный размер jge NewVirtualSize VirtualSizeIsVirtual: add edi,mega_size mov eax,edi NewVirtualSize: mov ecx,PEheader mov ecx,[ecx + 38h] div ecx ; и выравниваем к inc eax ; секции выравнивания mul ecx mov [esi + 8h],eax ; Сохраняем новое ; значение NewAlignedImageSize: mov eax,[esi + 0ch] ; получаем виртуальное ; смещение add eax,[esi + 8h] ; + новый виртуальный ; размер mov imagesize,eax ; = новый виртуальный ; размер NewAlignedFileSize: mov eax,[esi+10h] ; получаем новый ; физический размер add eax,[esi + 14h] ; добавляем смещение ; физического mov filesize,eax ; размера = размер файла CalculateNewIp: mov eax,[esi+10h] ; новый физический ; размер add eax,[esi + 0ch] ; + виртуальное смещение sub eax,mega_size ; - размер декриптора mov newip,eax ; новая точка входа call epo2 ; Вызываем функцию их ; подмены CopyDecryptorToEndOfFile: mov edi,[esi+10h] ; Новый физический ; размер sub edi,mega_size add edi,mapaddress ; mapaddress add edi,[esi + 14h] ; добавляем смещение ; потоковых данных mov esi,mega_offset mov ecx,mega_size cld ; Переносим декриптор rep movsb ; в файл UpdatePEHeaderWithChanges: mov esi,mapaddress mov word ptr [esi + 38h],'nf' ; Устанавливаем метку mov esi,PEheader ; изменённости mov eax,imagesize ; mov [esi + 50h],eax ; Устанавливаем новый ; виртуальный размер UnmapView: push mapaddress ; call UnmapViewOfFile ; Закончиваем изменение ; файла в памяти и ложим ; его обратно CloseMap: push mHnd ; call CloseHandle ; Закрываем хэндл push 0 push 0 push filesize ; push fHnd ; Переходим в конец ; файла call SetFilePointer ; push fHnd ; call SetEndOfFile ; Устанавливаем символ ; конца файла ;-----------------------------------------------------------------------------; CloseFile: push fHnd ; call CloseHandle ; Закрываем файл InfectionError: ret ; Выходим из процедуры ;xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx; ; FIND CODE SUBROUTINE ; ;-----------------------------------------------------------------------------; ; [ IN ] ; ; ; ; NO INPUT IN SUBROTINE ; ;-----------------------------------------------------------------------------; ; [ OUT ] ; ; ; ; NO OUTPUT IN SUBROTINE ; ;xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx; find_code: ; Подпрограмма поиска и ; усиления кодовой секции ; pusha mov esi,ebx ; Теперь esi указывает ; на PE mov edi,esi ; mov ebx,[esi + 74h] ; shl ebx,3 ; Получаем xor eax,eax mov ax,[esi + 6h] ; Количество объектов find2: mov esi,edi dec eax push eax mov ecx,28h ; mul ecx ; add esi,78h ; теперь esi указывает ; на начало последнего add esi,ebx ; заголовка секции add esi,eax ; mov eax,oldip ; В eax точку входа mov edx,[esi+0ch] ; В edx адрес куда будет ; мапиться ; текущая секция cmp edx,eax ; Проверяем pop eax ; Вынимаем из стэка eax jg find2 ; Если больше ищем дальше add edx,[esi+08h] ; Добавляем виртуальный ; размер секции cmp edx,oldip ; Проверяем jl find2 ; Если меньше ищем ; дальше xor eax,eax push esi mov edx,[esi+0ch] ; Далее вычисляем add edx,imagebase ; физическое mov start_code,edx add eax,[esi+14h] ; add eax,mapaddress ; И добавляем базу ; памяти mov crypt_code,eax ; Сохраняем начало кода mov eax,[esi+10h] ; Сохраняем размер кода mov size_code,eax ; or [esi + 24h],00000020h or 20000000h or 80000000h ; Меняем аттрибуты ; кодовой секции mov esi,offset decrypt ; Подсчитываем mov ecx,final-decrypt ; контрольную сумму call calc_CRC ; декриптора call random ; Вызываем генератор ; псевдослучайного числа ; Это число будет ; использоваться в ; качестве части ключа mov key,eax ; сохраняем часть ключа add edx,eax ; добавляем CRC mov esi,crypt_code ; mov ecx,size_code ; call crypt ; Криптуем кодовую секцию pop esi mov epo_st,esi ; Сохраняем указатель на ; заголовок секции mov eax,oldip ; Вычисляем расположение mov edx,epo_st ; первых пяти байт кода sub eax,[edx+0ch] ; усиливаемой программы add eax,crypt_code ; mov z_bytes,eax popa ret ; Возврат из процедуры ;xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx; ; EPO SUBROUTINE ; ;-----------------------------------------------------------------------------; ; [ IN ] ; ; ; ; NO INPUT IN SUBROTINE ; ;-----------------------------------------------------------------------------; ; [ OUT ] ; ; ; ; NO OUTPUT IN SUBROTINE ; ;xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx; epo: pushad mov esi,z_bytes ; Сохраняем lea edi,s_bytes ; оригинальные 11 mov ecx,11 ; байт rep movsb ; ; popad ret ; Возврат из процедуры ;-----------------------------------------------------------------------------; epo2: pushad mov esi,z_bytes ; Сохраняем ; mov eax,newip ; add eax,imagebase mov newip1,eax ; ; Записываем на их mov edi,esi ; место переход на lea esi,lichina ; наш декриптор mov ecx,end_lichina-lichina ; rep movsb ; popad ret ; Возврат из процедуры ;-----------------------------------------------------------------------------; lichina: push 0 db 0b8h newip1 dd 0 mov [esp],eax ret end_lichina: ;xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx; ; CODE ENCRYPT SUBROUTINE ( 32 BIT XOR ENCRYPTION) ; ;-----------------------------------------------------------------------------; ; [ IN ] ; ; ; ; start of code -> esi ; ; size of code -> ecx ; ; key for encryption -> edx ; ;-----------------------------------------------------------------------------; ; [ OUT ] ; ; ; ; NO OUTPUT IN SUBROTINE ; ;xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx; crypt: ; Подпрограмма криптования pusha ; кодовой секции mov edi,esi ; cr_work: ; lodsd ; Шифруем сразу по двойному xor eax,edx ; слову 32 битным XOR'ом stosd ; Всю кодовую секцию ; sub ecx,3 ; loop cr_work ; popa ret ; Возврат из подпрограммы ;xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx; ; COMMAND LINE SUBROUTINE ; ;-----------------------------------------------------------------------------; ; [ IN ] ; ; ; ; NO INPUT IN SUBROTINE ; ;-----------------------------------------------------------------------------; ; [ OUT ] ; ; ; ; argv[1] -> esi ; ;xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx; commandline: ; Процедура получения ; аргумента ; командной строки ; (на C - argv[1]) call GetCommandLineA ; Получаем всю коммандную ; строку mov esi,eax ; Помещаем указатель на ; командную строку в esi test1: ; lodsb ; Проверяем все символы на cmp al,20h ; равенство пробелу jne test1 ; Если нашли пробел, то ; теперь esi указывает на ; argv[1] ret ;xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx; ; RANDOM NUMBER GENERATOR SUBROUTINE ; ;-----------------------------------------------------------------------------; ; [ IN ] ; ; ; ; NO INPUT IN SUBROTINE ; ;-----------------------------------------------------------------------------; ; [ OUT ] ; ; ; ; random number -> eax ; ;xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx; random: ; Процедура получения ; псевдослучайного числа push edx ecx db 0fh,31h ; Получаем случайное число pop ecx edx ret ;xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx; ; BACKUP SUBROUTINE ; ;-----------------------------------------------------------------------------; ; [ IN ] ; ; ; ; name of file -> esi ; ;-----------------------------------------------------------------------------; ; [ OUT ] ; ; ; ; NO OUTPUT IN SUBROTINE ; ;xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx; backup_file: ; Подпрограмма сохранения ; изменяемого файла push esi ; lea edi,backup_name ; finder: ; lodsb ; Имя изменяемого файла stosb ; сохраняем в переменной cmp al,0 ; backup_name jne finder ; dec edi ; lea esi,back ; Подготавливаем имя *.bak mov ecx,5 ; файла rep movsb ; pop esi ; push 0 ; Копируем изменяемый файл push offset backup_name ; в резервную копию (*.bak) push esi ; call CopyFileA ; ret ; Возврат из процедуры ;xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx; ; DECRYPT SUBROUTINE ; ;-----------------------------------------------------------------------------; ; [ IN ] ; ; ; ; NO INPUT IN SUBROTINE ; ;-----------------------------------------------------------------------------; ; [ OUT ] ; ; ; ; NO OUTPUT IN SUBROTINE ; ;xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx; decrypt: ; Декриптор с антиотладочными ; и антидизассемблерными ; Трюками call setup_SEH mov esp,[esp+8] ;-----------------------------------------------------------------------------; mov eax,04ebh ; Перекрывающийся код - это jmp $-4 ; трюк против дизассемблера f_diz: ; ; cld mov eax,fs:[018h] ; А это содержимое функции mov eax,[eax+30h] ; WIN API movzx eax,byte ptr [eax+02] ; IsDebuggerPresent cmp eax,0 ; Если отладчик есть, то jne final ; не расшифровываем код push ss ; Трюк против Soft Ice pop ss ; call supa_delta ; Вычисляем ;-----------------------------------------------------------------------------; supa_delta: ; pop ebp ; дельта смещение sub ebp,offset supa_delta ; mov eax,[ebp + oldip] ; Восстанавливаем старую ; точку входа add eax,[ebp + imagebase] ; и виртуальный адрес начала push eax ; Кладём в стэк адрес ; возврата call restore ; Восстановление ; оригинальных 11 байт lea esi,[ebp+decrypt] ; Вычисляем контрольную mov ecx,final-decrypt ; контрольную сумму call calc_CRC ; декриптора call vampirizm ; Вызов подпрограммы mesto: ; вампиризма mov ecx,12345678h nop ; И ей расшифровываем ключ add ecx,edx ; для декриптования кодовой xchg ecx,edx ; секции mov esi,[ebp+start_code] mov ecx,[ebp+size_code] ; mov edi,esi ; decrypt1: ; Не посредственно сам lodsd ; цикл декриптования кодовой xor eax,edx ; секции stosd ; ; sub ecx,3 ; loop decrypt1 ; ret ;xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx; ; SETUP SEH SUBROUTINE ; ;-----------------------------------------------------------------------------; ; [ IN ] ; ; ; ; NO INPUT IN SUBROTINE ; ;-----------------------------------------------------------------------------; ; [ OUT ] ; ; ; ; NO OUTPUT IN SUBROTINE ; ;xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx; setup_SEH: push dword ptr fs:[0] ; Push'им оригинальный ; обработчик SEH mov fs:[0],esp ; И помещаем новый (который ; находится после первого ; call) ; Пытаемся писать в ядро (что mov eax,012345678h ; вызовет исключение) ;-----------------------------------------------------------------------------; vamp1: ; mov ecx,[ebp+key] ; Настоящая команда ;-----------------------------------------------------------------------------; xor ecx,ecx xchg eax,[ecx] ;xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx; ; VAMPIRIZM SUBROUTINE ; ;-----------------------------------------------------------------------------; ; [ IN ] ; ; ; ; NO INPUT IN SUBROTINE ; ;-----------------------------------------------------------------------------; ; [ OUT ] ; ; ; ; NO OUTPUT IN SUBROTINE ; ;xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx; vampirizm: lea edi,[ebp+mesto] ; Перемещаем настоящую lea esi,[ebp+vamp1] ; команду на место movsd ; поддельной movsw ; ret ; ;xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx; ; CALCULATE CRC SUBROUTINE ; ;-----------------------------------------------------------------------------; ; [ IN ] ; ; ; ; start of code -> esi ; ; size of code -> ecx ; ;-----------------------------------------------------------------------------; ; [ OUT ] ; ; ; ; CRC -> edx ; ;xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx; calc_CRC: ; Подпрограмма вычисления ; контрольной суммы ; декриптора xor edx,edx ; Обнуляем edx xor eax,eax ; и eax calc1: lodsb ; add edx,eax ; Вычисление CRC ; loop calc1 ; ret ;xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx; ; RESTORE SUBROUTINE ; ;-----------------------------------------------------------------------------; ; [ IN ] ; ; ; ; NO INPUT IN SUBROTINE ; ;-----------------------------------------------------------------------------; ; [ OUT ] ; ; ; ; NO OUTPUT IN SUBROTINE ; ;xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx; restore: ; Восстанавливаем mov edi,eax ; оригинальные пять lea esi,[ebp+s_bytes] ; байт mov ecx,11 ; rep movsb ; ret ; Возврат из процедуры ;-----------------------------------------------------------------------------; final: oldip dd 0h imagebase dd 0h size_code dd 0h start_code dd 0h key dd 0h s_bytes db 11 dup(0) ;*****************************************************************************; finish: dot_dot db ".",0 message1 db "WORKING ",0 message2 db 79 dup("-") db 10 db "[ SECURE CODE V. 1.3 BY SL0N ]",10 db "Usage: secure.exe [your_program.exe]",10 db 79 dup ("-") db 10,0 message3 db 10,"[ ERROR - PROGRAM WAS TERMINATED !!! ]",10,0 message4 db 10,"[ PROGRAM WAS ARMOURED SUCCESSFULLY !!! ]",10,0 crypt_code dd 0h size2read dd 0h fHnd dd 0h mHnd dd 0h memory dd 0h mapaddress dd 0h PEheader dd 0h filesize dd 0h imagesize dd 0h newip dd 0h epo_st dd 0h name1 db 'hello.exe',0 mega_size dd 0h mega_offset dd 0h backup_name db 100 dup(0) back db '.bak',0 z_bytes dd 0 include pgs2.asm ; Подключается PGS include rnd32.asm include garbage32.asm buff2 db 400000 dup(?) ; И буфер для готового ; декриптора ;-----------------------------------------------------------------------------; .code nop ;-----------------------------------------------------------------------------; end start ;-----------------------------------------------------------------------------; Теперь рассмотрим как работает данная навесная защита. Вот так выглядит программа до изменения её Secure cod'ом: |-----------------------------------| | MZ заголовок | |-----------------------------------| |-------| PE заголовок | | |-----------------------------------| | | | !!!!!!!!!!!!!!!!!!! | | |-----------------------------------| |------>| Кодовая секция | |-----------------------------------| | Последняя секция | |-----------------------------------| После же того, как на программу была установлена защита Secure code, она будет выглядеть следующим образом: |-----------------------------------| | MZ заголовок | |-----------------------------------| |-------| PE заголовок | | |-----------------------------------| | | | !!!!!!!!!!!!!!!!!!! | | |-----------------------------------| | | Закриптованная |<----------| | | Кодовая секция | | | |-----------------------------------| | | | Последняя секция | | | |----------------| | | |------>| Декриптор | | | |----|-----------|------------------| | |------------------------------------------| Декриптор был усилен всеми описанными выше приёмами, а так же в нём использовался один из мощнейших антиотладочных трюков с применением SEH'a. Данный приём заключается в том, что в качестве SEH обработчика у нас устанавливается наш декриптор. Затем мы намерено вызываем ошибку, попыткой записи в ядро. При обработке этой ошибки управление передаётся на наш декриптор при нормальном исполнении программы и завершается в контекстах отладчика и кодо эмулятора. Для использования программы после компоновки следует запустить программу следующим образом: C:\secure\secure.exe your_program.exe Где your_program.exe - программа, которую вы хотите усилить. В данной статье были рассмотрены основные приёмы защиты программного кода и их применение для разработки программы, которая оберегает код от взлома. ;-------------------------------------------------------------[slon (2002)]----; ... Dis Is Cheat of Mind ...