Блог О пользователеinterpol

Регистрация

Календарь

« Февраль 2009  
Пн Вт Ср Чт Пт Сб Вс
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

Шапка-петушок и другие высокие технологии

 

Энтропия и ее нормализация


    Что такое энтропия применительно к программному коду можно найти в гугле и на васме. Вкратце - числовая оценка эффективности использования информационного объема. Измерять ее будем в битах на 1 байт [0..8] с точностью до сотых. В этом нелегком деле нам поможет PEiD который можно скачать известно где. Итак, запускаем PEiD, кидаем на него, к примеру, невинного AcroRd32.exe, жмем " > > > >" и в появившемся диалоге вычисляем значение Entropy тыканием по соответствующей кнопке. Вот у меня, к примеру получилось 5.06 и рядом в скобках вердикт (Not packed). А теперь проделываем ту же операцию с чем-нибудь упакованным тем же UPX'ом - и получаем 7.82 (Packed)
    Собственно к чему весь этот бред: раз утилита может на основе математической оценки предположить, упакован файл или нет, то почему бы этим не воспользоваться и антивирусу? В данном деле особо отличился лидер по проверке PE-файлов на валидность "антивирус" Avira Antivir. Их последний генерик - ZPack.Gen (по аналогии с XPack.Gen) по наблюдениям выдается исключительно на основе анализа энтропии секции. Т.е. если пакер не определен но энтропия необычно высока - получите клеймо.
    Логичное решение в виде выжигания напалмом конторы дятлов представляется не слишком эффективным - они не первые и наверняка не последния. Ну а поскольку надо быть на шаг впереди, подумаем о симметричном ответе. Раз не нравится наша энтропия - надо ее улучшить. Т.е. нам надо "разбавить" наш экзе таким образом, чтобы эффективность хранения данных уменьшилась, или, другими словами, добавить большое количество однотипных данных.
    Просто добавив кусок nop'ов в секцию, мы долго не протянем и вызовем апдейт генерика до уровня удаления однотипного повторяющегося мусора. В таком случае нам надо сделать такой мусор, который никаким описанием с наскока не удалить. В идеале картина, представленная взгляду в Hiew должна выглядеть как равномерно-распределенный код. (Кстати рекомендую провести некоторое время просто разглядывая картинки экзешников в hex-редакторе) И нам определенно не надо выглядеть как архив или как код со вставками в виде архивов или зашифрованных кусков. Т.е. энтропия должна быть по возможности одинакова в различных отдельно взятых кусках кода/файла.
    Достичь этого достаточно просто - надо все включаемые куски зашифрованных данных "разбавить" мусором (в виде последовательности 00h например) по псевдослучайному алгоритму до тех пор, пока он не будет выглядеть (иметь значение энтропии) как обычный код. Экспериментально установлено что оптимальное значение тут лежит в пределах 6.1-6.8. Сам экзе при запуске будет инициализировать псевдослучайный генератор так, чтобы последовательность чисел, определяющая места вставки мусора, повторилась один-в-один. В итоге весь наш мусор будет удален и можно будет расшифровать блок с секретными данными.
 

Обфускация импорта - часть 2


    Поскольку дело наше живее всех живых, я вернулся к разработке темы для широких масс. В данной части речь пойдет о динамическом импорте.
    Хрестоматийными на просторах онторнета стали примеры кода, когда детектируют только вышедний из-под компилятора бинарник. Для примера скомпильте что-нибудь с UrlDownloadToFile и WinExec/ShellExecute/CreateProcess (drweb) или CopyFile в папку полученную от GetWindowsDirectory/GetSystemDirectory (nod32), а затем проверьте на вирустотале или аналогичном сервисе.
    На данный момент еще существуют "антивирусы" которые позволяют себе детектировать на основе текстовых шаблонов в секции импортов. Они могут предварительно снять упаковщик и посмотреть на импорт, однако суть одна и та же. А раз клеймят нас по импорту, значит надо от него избавиться. (Если список импортов собирается и в процессе эмуляции, то надо будет дополнительно сделать эмуляцию невозможной)
    Общее название решения данной проблемы - динамический импорт. Разница идет в деталях. Прежде всего, поскольку нам надо избавиться от имен функций открытым текстом, то надо бы их зашифровать. Можно пойти дальше - поскольку теоретически расшифрованные строки может подсмотреть эмулятор или "дятел" из вирлаба, то есть смысл сделать их невосстановимыми - хэшировать по определенному алгоритму. Вообще я рекомендую использовать сравнение хэшей вместо сравнения строк везде где только это возможно для сокрытия от лишних глаз. К примеру, ищет ваша программка файлы pronpass.pwd в c:\windows. Высокотехнологичный онторнет-поиск по гуглу покажет довольно быстро, что это файл от суперсекретного хранителя паролей и управлятора личным счетом в оффшорном банке, и вуаля - о вашем творении начинают трубить на каждом углу вплоть до официального сайта оффшорного банка =) А будь там сравнение по хэшам - никто бы и не догадался о функционале, поскольку иметь весь возможный софт, в особенности не столь широко распространенный, на машине для анализа малвари, невозможно и/или неразумно.
    Возвращаемся к импорту. Довольно часто можно встретить примеры, когда в начале кода приведен список хэшей используемых функций, а затем в процессе инициализации по ним идет заполнение некоей структуры адресами найденных функций. Не удивлюсь, если у автором тех кодесов имеются садо-мазохистские наклонности. Во-первых, хэши надо чем-то подсчитать и вписать. Во-вторых, ужасно неудобно проводить оптимизацию или корректировку такого кода. Ну и самое главное - это же просто рассадник статических сигнатур, от которых нам надо всеми способами избавляться.
    Конечно же, весь процесс работы с хэшированным импортом надо автоматизировать и отладить так, чтобы впоследствии к нему не нужно было возвращаться. В общих чертах наш код должен быть легко внедряемым в имеющиеся исходники, автоматически заниматься преобразованием имени функции в хэш на стадии компиляции, а в рантайме - получать адрес API и при этом использовать различные техники антиэмуляции.


;invoke replacer
;kernel32.Sleep - use conversion
;Sleep - leave as usual import
OPTION NOKEYWORD: < invoke > ;переопределяем стандартный макрос
invoke MACRO LibNFunc:REQ, p1,p2,p3,p4,p5,p6,p7,p8,p9,p10,p11,p12,p13,p14,p15,p16,p17,p18,p19,p20
LOCAL pos,counter
LOCAL LibNFuncLen ;имя функции (или длл.имя функции)
LOCAL iDotPos ;положение разделителя - точки - в строке



LibNFuncLen SIZESTR < LibNFunc > ;вычисляем длину строки параметра LibNFunc
iDotPos INSTR < LibNFunc >, < . > ;получаем позицию точки в LibNFunc

%IF iDotPos
;если обнаружена точка - значит разбиваем переданный параметр на имя длл и имя функции
sLib SUBSTR < LibNFunc >, 1, @CatStr(%iDotPos)-1
sFunc SUBSTR < LibNFunc >, @CatStr(%iDotPos)+1, @CatStr(%LibNFuncLen)-@CatStr(%iDotPos)


;проверка не определена ли уже такая длл
sStr CATSTR < imf_ >, sLib ;создаем имя флага по шаблону imf_имядлл
% IFNDEF sStr ;условная компиляция, если еще не определ флаг - определяем его
% echo sStr dll ;вывод отладочного сообщения
%sStr equ 1 ;определяем флаг imf_имядлл
;а также определяем имя длл в текущей позиции кода
.code ;это уже идет в код
jmp @F ;перепрыгиваем данные с именем длл до следующей инструкции
% sz_&sLib& byte 0 ;определяем в коде имя метки по шаблону sz_имядлл чтобы впоследствии передавать ее в процедуру загрузки длл
% $CR sLib ; здесь у меня свой макрос, шифрующий строку, но можно открытым текстом, код вроде будет таким % byte 'sLib',0
@@:
ELSE
;% echo sStr already def ;вывод отладочного сообщения
ENDIF

;проверяем, определен ли уже флаг imf_имядлл_имяфункции, нужно чтобы не допустить дубликатов при вызове одной и той же функции
sStr CATSTR < imf_ >, sLib, < _ >, sFunc
% IFNDEF sStr
% echo sStr import
%sStr equ 1
; определяем значение хеша в текущей позиции кода
.code
jmp @F ;переходим на следующую инструкцию
% dw_&sLib&_&sFunc& DWORD $HashString(%sFunc) ;определяем переменную вида dw_имядлл_имяфункции типа DWORD, макрос $HashString() нам подставляет значение хэша
@@:
ELSE
;% echo sStr already def
ENDIF


ELSE
; здесь мы в случае если разделитель-точка не был найден в переданном имени функции, а значит перед нами вызов локальной функции (или обычной winapi),
; в данном случае мы не используем хеширование, но выводим предупреждение
IFNDEF DEBUG
%echo @FileCur(@CatStr(%@Line)) : [LibNFunc] invoked as usual import
ENDIF
ENDIF




;тут скопипастеный у когото кусок по анализу и запихиванию параметров в стек
counter=0
FOR arg,< p20,p19,p18,p17,p16,p15,p14,p13,p12,p11,p10,p9,p8,p7,p6,p5,p4,p3,p2,p1 >

IFNB < arg >


counter=counter+4
pos=@InStr(1,arg,< ADDR >) OR @InStr(1,arg,< addr >) OR @InStr(1,arg,< Addr >)

IF pos

IF (OPATTR(@SubStr(arg,%pos+5))) EQ 98
lea eax,@SubStr(< arg >,%pos+5)
push eax
ELSE
push OFFSET @SubStr(< arg >,%pos+5)
ENDIF

ELSE
push arg
ENDIF
ENDIF
ENDM

; подходим к вызову самой функции

%IF iDotPos ; ##### тут у нас необычный вызов #####

% push dw_&sLib&_&sFunc&
% push offset sz_&sLib&
call find_api ;вызываем функцию поиска функции ;) Определена как find_api proc lpszLibName: DWORD, dwHashValue: DWORD

call eax ; в eax мы получаем найденный адрес. Тут еще бы надо контроль на нулевой указатель и обработчик ошибок чтобы легче было искать проблему


ELSE ; #### тут у нас обычный вызов #####


call LibNFunc


ENDIF ;IFE iDotPos

;wsprintf fix - а это хрень чтобы подчистить стек для хитрых функций
IF ((OPATTR(LibNFunc)) AND 11100000000y) EQ 00100000000y
IF counter NE 0
add esp,counter
ENDIF
ENDIF

ENDM

Код с комментариями взаимствован и немного поправлен от одного товарища "в теме", думаю он будет не против.
    В результате мы получаем макрос, который подменит собой стандартный оператор масма invoke, и примет на себя всю работу по созданию динамического импорта. Нераскрытой здесь осталась тема хэширования (в виде макроса и процедуры) и поиска адреса функции по хэшу. Жду фидбеков, возможно опубликую и эти части.
 

Обфускация импорта - часть 1


    Еще одной важной деталью, которая может сразу выдать настоящее назначение представленного на анализ файла - это импорт. Вполне логично, что отнюдь не каждой программе необходимо использование, к примеру, сочетания UrlDownloadToFile и WinExec/ShellExecute/CreateProcess. Здесь конечно налицо существенное упрощение, однако реальные правила, по которым приложение набирает "очки подозрительности" базируется в том числе и на этих простых принципах. Вот пример более приближенный к реальной жизни (на момент написания этих строк). Avira AntiVir клеит файлу ярлык TR/Crypt.XPACK.Gen в случае, если в импорте присутствует только небольшое количество функций из kernel32.dll и более никаких других библиотек. В целом такой подход правилен - поскольку отсутствие импорта или его урезание до LoadLibrary/GetProcAddress скорее всего свидетельствует именно о обработка криптором/пакером. Но стоит добавить в импорт несколько API из других библиотек - и Авира молчит. Возможно помимо импорта там вычисляется энтропия секций и что-нибудь еще, однако описанный выше трюк на сегодня достаточен (конечно в случае если нет детекта по другим признакам).
    Итак, давно известно что в импорте должен быть минимум функций, характерных для обычного приложения в системе. Поэтому я думаю многие в какой-то момент в своей практике перешли к использованию т.н. динамического импорта - когда получением адресов нужных API занимается не стандартный загрузчик Windows, а свой собственный код. В данном случае мы выводим из импорта подозрительные API, однако все еще остается некоторый шанс быть пойманными эвристическим анализатором, а значит процедура получения адресов должна периодически обновлять свой арсенал анти-эвристических трюков.
    Про вариации динамической подгрузки импорта, сохраненного как закриптованные строки или хэши строк будет рассказано в следующей части, а сейчас - про обфускацию импорта через набивание его рандомными API.
    Тем, кто плотно работал с NOD32, известна любовь этого антивируса делать сигнатуры на основе импорта. Фактически импорт порой является достаточным для уникальной идентификации зловреда средством. Значит, следуя банальной логике, если убрать почти все, что мы используем из импорта, и набить его случайными API для каждого билда, то сигнатуру по импорту будет сделать невозможно.
    Для реализации этого на макроязыке MASM надо будет развить представленный ранее генератор мусорных инструкций и добавить в него генератор вызовов рандомных API. Данная задача сводится к макросу, выбирающему случайную строку из списка и выводящему в код call < имя_API >. Конечно, это упрощенный вариант, можно использовать гораздо больший набор вариантов:
lea < регистр >, < имя_API >
mov < регистр >, offset < имя_API >
add/sub < регистр >, offset < имя_API >

и другие.
Список для использования в генераторе импорта лучше составлять на  основе  стандартных компонентов Windows - Блокнот, Pait, WordPad и прочих. Необходимо отсеивать из подготавливаемых списков недокументированные API для обеспечение работоспособности на различных версиях многострадальной ОС.
    Итак, после подготовки списка создаем макрос по следующему шаблону

$RndИмяБиблиотекиAPI macro
    LOCAL a   ; определяем макропеременную

a=@Random(КоличествоAPI-1)+1   ; выбираем случайное значение == порядковый номер API

; выбираем соответствующее имя API
if a eq 1
exitm < ИмяAPI1 >
elseif a eq 2
exitm < ИмяAPI2 >
elseif a eq 3
exitm < ИмяAPI3 >
...
endif

endm

Получится несколько громоздко, однако (имхо) для большей автоматизации уже не хватает возможностей макроязыка. Несмотря на это, поставленную задачу мы выполним, и затем, вставив в случайные места кода например

call $RndKernel32API()

мы в каждой сборке получим вызовы случайных API и недетектируемость по импорту (конечно при отсутстсвии в нем большинства реально используемых кодом API).
 

 
Теги: avira|импорт
 
 

Метаморф в сырцах - с глаз долой


    Исходный код на асме можно грубо разбить на две составные части - собственно код (.code) и инициализированные данные (.data). Для обеспечения максимальной изменчивости каждого билда необходимо применить особый подход в обоих случаях. Поскольку сейчас мы не ставим целью использование антиотладки и антиэвристики, то целью будет сокрытие типичных сигнатур
 по которым сканер или беглый тренированный взгляд может оценить характер представленного в exe кода. (Покачто не рассматриваем эвристику и сигнатурный скан по импортам).
    Добиться этого можно "криптованием" строковых ресурсов следующими способами:

    * через внешнюю утилиту которая или криптанет (читай поксорит с небольшими вариациями) часть бинарника или его весь. Плюс - отсутствие дополнительных проблем на этапе разработки. Минус - время на создание и отладку внешней утилиты, относительная простота в детектировании т.к. обычно после декрипта весь кусок висит в памяти
    * через конструкции наподобие
     

      X equ 011h
      .data
      szString byte "T" xor X, "e" xor X, "s" xor X, "t" xor X, 0 xor X
      .code
      invoke XorData, addr szString, X
      invoke MessageBox, 0, addr szString, NULL, MB_OK
     

      Это код без использования макросов, вручную определяем ключ и затем каждый символ берем и ксорим. Если определение ключа сделать рандомным (используя макрос из предыдущего поста) уже будет некое подобие сокрытия от сигнатурного детекта. Однако такой код ужасно выглядит и неудобен в написании и соответственно модификации.
    * через вызов макросов
     

      .code
      invoke MessageBox, 0, $CRSTR(Test), NULL, MB_OK
     

      Не правдали красиво? ;)

   В последнем варианте вся остальная работа осталась "за кадром". Рассмотрим ее поподробнее. Итак, мы ставим вместо указателя на строку макрос. Что он может нам вернуть, т.е. вместо него после работы что мы можем получить? Если рассматривать только этот вызов invoke - то обычный параметр - или регистр, или ссылку на адрес памяти. Принимая во внимание тот факт, что нам бы еще надо делать крипт-декрипт, и еще бы строку убивать после использования - пусть там будет регистр, указывающий на буфер со строкой, сгенерированной  после работы функции-декриптора. Функция декрипта должна принимать на вход указатель на область памяти с криптованной строкой. Ну и конечно же нам бы сгенерировать код наподобие описанного во втором варианте. Мы только что прошли от простого к сложному путь проектирования макро-криптора строк. Определены основные моменты, дело теперь лишь за реализацией.
    Для начала - макрос по генерированию криптованой строки


$CR macro sStr: REQ
    LOCAL bRx, bXored
    LOCAL iLen

    bRx=@Random(253)+1   ; generate random xor base

    * iLen SIZESTR < sStr >   ; calculate string len

     FORC char, < sStr >
      bXored = bRx xor '&char' ; prepare out string
      * byte @CatStr(*bXored) ; output prepared string
     ENDM

     * byte @CatStr(*iLen)   ; output string len
     * byte bRx    ; output xor key
   
endm


    Этот макрос выбирает рандомный ключ для ксора (заметьте, что из диапазона случайных числе исключен ноль) и затем создает в месте вызова примерно следующий код


    byte 'T' xor 13   ; поксоренные байты
    byte 'e' xor 13
    byte 's' xor 13
    byte 't' xor 13
    byte 4   ; длина строки
    byte 13   ; ключ для ксора


    Для простоты здесь используется простой ксор, однако в макросе можно изменять ключ на каждой итерации по какому-нибудь алгоритму.
    Итак, закриптованные данные мы можем сгенерировать, теперь надо реализовать часть по декрипту и передаче данных в вызвавшее макрос место =) Да будет так


$CRSTR macro sStr: REQ

jmp @F
    $CR sStr
@@:

    push $
    call Decrypt
    exitm < eax >

endm


    Этот макрос прямо в коде производит вызов $CR, обрамляет его результат в jmp @F // @@:, и затем производит вызов декриптора с передачей в него текущего смещения. Декриптор в данном случае должен идти в сторону уменьшения переданного адреса и обрабатывать переданную структуру, сгенерировать буфер и записать в него расшифрованный кусок и вернуть его адрес в регистре eax. Никакие другие регистры по выходу не должны быть изменены.
    В результате мы получаем достаточно простой и элегантный способ криптования строк без написания каждый раз вручную лишнего кода, а значит остается больше времени для этих вкусных кодесов и успешной "компеляции в месте" =)
   
 

Метаморф в сырцах


    Давно обдумывал идею морфа с максимальной отдачей. Изучение доступных источников указывало на необходимость большого количества кода с чем-то наподобие специфичного дизассемблера и таблицы пере(под)становок. Однако метод изменения уже готового кода имеет свои технические ограничения - всего не предусмотришь и не поменяешь, а чем больше кодеса таскать с собой, тем выше шансы залететь по банальной сигнатуре. Значит надо нечто такое, чтобы сигнатуры не было в принципе.
    Один из вариантов - полиморфный криптор. В первом приближении состоит из расшифровывающего полиморфного кода и криптоконтейнера. Внутри контейнера согласно банальной логике будет некий постоянный код. Требует постоянного наращивания примочек для предотвращения полной эмуляции.
    Идеалом морфа был бы такой код, чтобы две его морф-копии отличались максимально, вплоть до отсутствия похожих инструкций. Достичь такого сложно, однако можно попытаться приблизиться. Взяв за основу макроязык пакета MASM, основательно покопавшись в его небогатой документации, приходим к выводу, что можно на стадии сборки исходного кода подменять инструкции на аналоги, встраивать мусорный код, генерить рандомную таблицу импорта, шифровать строковые ресурсы и многое другое, от чего у среднестатистического вирусного аналитика волосы зашевелятся не только на голове.

    Основой и первым модулем в наборе будет генератор рандомных чисел. Все было бы прекрасно, однако макро препроцессор не знает такого. Попытка прочитать рандомное значение через переменную окружения RANDOM не увенчалась успехом. Пришлось изобретать велосипед - обертку вокруг компилятора ML.exe которая при каждом вызове генерила рандомное значение в переменной окружения RNDSEED (локально для текущего процесса и его потомков). Это значение уже считывалось макросом. Итак, кодес


@Init_rnd macro
local    t
     ;check if RNDSEED set
     if @SizeStr(@Environ(RNDSEED)) le 1
         .ERR < RNDSEED environment var wasnt set!! Use ml.exe wrapper to randomly set it >
     endif
 t SubStr @Environ(RNDSEED),1,@SizeStr(@Environ(RNDSEED))
 RNDSEED =  t
endm

; gen random [0..Num]
@Random macro Num:REQ
 RNDSEED = (((RNDSEED*25733d) and 0FFFFh) 13849d) and 0FFFFh
 exitm < (RNDSEED*(Num 1)) shr 16 >
endm

@Init_rnd


    Модуль самоинициализируется вызовом @Init_rnd, где проверяется наличие переменной окружения и выполняется ее сохранение в переменную RNDSEED. Теперь вызовом макроса @Random(х) мы получим макро-случайное-значение (макропеременная числового типа), с приемлемой "случайностью", которое сможем использовать в дальнейших макросах.
   
    Теперь можно осилить и генератор мусорного кода на макросах. Поскольку вставлять этот код мы будем в различные места исходного творения, необходимо предусмотреть возможность защиты реального кодеса от действий мусорного (треш в регистрах, флагах к примеру). Самое банальное - обрамлять мусор в pushad / popad или аналогичные конструкции. Не подходит, ибо в данном случае появляется возможность создать алгоритм, который бы этот мусор отсеивал. Значит сделаем генерацию мусора так, чтобы необходимые нам на данном этапе регистры не использовались мусором.
    На первом этапе не будем усложнять жизнь условными переходами, вызовами процедур и прочей радостью, просто будем создавать мусор для того чтобы сбить сигнатурную маску. Для этого воспользуемся следующим набором команд:
 [mov/add/sub/xor/or/cmp/rol/ror/shl/shr/inc/dec/neg/not/nop]
    Необходимо учитывать, что у каждой из команд свое количество операндов. Его определением займется следующий макрос:

@GetParamCount macro arg:REQ
    if @InStr(1,mov add sub xor or  cmp,arg) gt 0
        exitm < 2 >
    elseif @InStr(1,rol ror shl shr,arg) gt 0   
        exitm < 21 >
    elseif @InStr(1,inc dec neg not,arg) gt 0   
        exitm < 1 >
    elseif     @InStr(1,nop das daa aaa aad aam aas,arg) gt 0   
        exitm < 0 >
    else
        .ERR unknown param passed to @GetParamCount macro
    endif       
endm

    На выходе мы получаем следующие значения:
2 - два операнда, второй может быть как регистром соответствующей разрядности, так и числом
21 - два операнда, второе обязательно число
1 - один операнд
0 - без операндов
    На всякий случай еще надо бы учесть что если нам передадут в качестве регистра который нельзя использовать eax, то соотвественно и ah, и al трогать нельзя. В итоге после небольшого шаманства получаем следующий кодес


; Generate Random Instruction, n times
@GRIn macro Count:REQ, ExclRegsIn: REQ
    LOCAL i
    LOCAL Op1
    LOCAL Op2
    LOCAL Cmd
    LOCAL R
    LOCAL ExclRegs  
 
    LOCAL x

    i=Count

 
    ExclRegs TEXTEQU < ExclRegsIn >

    ;expand eax - > ah, al, etc
    * if @InStr(1, *ExclRegs, < eax >) gt 0
     ExclRegs CatStr ExclRegs, < ah al >
    endif
    if @InStr(1, *ExclRegs, < ebx >) gt 0
    ExclRegs CatStr ExclRegs, < bh bl >
    endif
    if @InStr(1, *ExclRegs, < ecx >) gt 0
    ExclRegs CatStr ExclRegs, < ch cl >
    endif
    if @InStr(1, *ExclRegs, < edx >) ne 0
    ExclRegs CatStr ExclRegs, < dh dl >
    endif
 
    WHILE i gt 0
 
    ;select instruction
    Cmd Substr < mov/add/sub/xor/or /cmp/rol/ror/shl/shr/inc/dec/neg/not/nop >,(1+(@Random(14)*4)),3
 
    ;select 16/32 registers which is not in passed exclude list
    if @Random(1) eq 1
            R=1
            *WHILE R ne 0
                ;Op1 Substr @GetReg16(),1,2
                Op1 Substr < ah/al/dh/dl/bh/bl/ch/cl >,(1+(@Random(7)*3)),2
                R=@InStr(1, *ExclRegs, *Op1)
            ENDM

            R=1
            *WHILE R ne 0
                ;Op2 Substr @GetReg16(),1,2
                Op2 Substr < ah/al/dh/dl/bh/bl/ch/cl >,(1+(@Random(7)*3)),2
                R=@InStr(1, *ExclRegs, *Op2)
            ENDM
    else
            R=1
            *WHILE R ne 0
                ;Op1 Substr @GetReg32(),1,3
                Op1 Substr < eax/edx/edi/esi/ebx/ecx >,(1+(@Random(5)*4)),3
                R=@InStr(1, *ExclRegs, *Op1)
            ENDM

            R=1
            *WHILE R ne 0
                ;Op2 Substr @GetReg32(),1,3
                Op2 Substr < eax/edx/edi/esi/ebx/ecx >,(1+(@Random(5)*4)),3
                R=@InStr(1, *ExclRegs, *Op2)
            ENDM
    endif
 

    ;select type
    x=@GetParamCount(*Cmd)
    if x eq 0  ;no operands
        Cmd
    endif
    if @GetParamCount(*Cmd) eq 2  ;standart 2 opers 2nd may be a number
        if @Random(1) eq 1
            Cmd Op1, Op2
        else
            Cmd Op1, @Random(255)          
        endif
    endif
    if @GetParamCount(*Cmd) eq 21  ;2 params second must be a number
        Cmd Op1, @Random(31)
    endif
    if @GetParamCount(*Cmd) eq 1 ;only 1 param
        Cmd Op1          
    endif

    i=i-1

    ENDM
 
endm

   
    Пришлось заменить все знаки процента перед переменными и операторами на звездочку, не то изза кривизны собственных рук, не то изза ебанутости данной блогоплощадки. Кому надо, думаю поймет и исправит.
    Итак, теперь вставив в код вызов

@GRIn 10, eax

мы получим 10 рандомных инструкций которые не будут трогать регистр eax. Сталобыть камменты приветствуются, а в следующих постах я опишу другие важные аспекты макроморфа.

 
Теги: метаморф