·─T─┌O┐─T─┌A┐L···─Z┐┌0┐┌M┐┌B·i┌F─i┌C─┌A┐─T─i┌O┐┬N┬··························
│ │ │ │ ├─┤│ / │ ││││├┴┐┬├─ ┬│ ├─┤ │ ┬│ ││└┤ Issue #1, January-2001
··│·└─┘·│·│·│└─··└──└─┘│·│└─┘││··│└──│·│·│·│└─┘│·│··························
············································································
СТЭКОВЫЙ ПОЛИМОРФНЫЙ ДВИЖОК KME v3.50 [kme350.zip]
············································································
Структура декриптора
Алгоритм стэковый, то есть не существует отдельно декриптора и
зашифрованных данных. Результатом работы движка является только
ассемблерный код, при помощи которого вычисляются данные, которые в
обратном порядке кладутся на стэк и после этого идет переход на ESP.
исходные данные: зашифрованные данные:
... ...
nop XOR EAX, EBX
db 22 ADD EAX, (88776655h - curr_eax)
db 33 ROR EBX, 4
db 44 XOR EBX, (44332290h xor curr_ebx)
db 55 ...
db 66 PUSH EAX
db 77 ...
db 88 PUSH EBX
... ...
Легко заметить, что если обычный полиморфный движок производит данные
длиной РАЗМЕР_ДЕКРИПТОРА + ДЛИНА_ИСХОДНЫХ_ДАННЫХ, то стэковый движок
производит данные длиной ДЛИНА_ИСХОДНЫХ_ДАННЫХ * k, где k -- некоторый
коэффициент увеличения данных, зависящий от многих параметров.
Длина расшифровщика/Компрессия
У KME при всех включенных фичах код увеличивается в 2-3 раза. Но тут
возможен интересный эффект: при отсутствии "логики" (т.е. без шифровки
регистров, см. FLAG_NOCMD) и одинаковых шифруемых данных (1 и тот же
повторяющийся дворд/ворд/байт), декриптор получится меньше, то есть
произойдет сжатие. Максимальное сжатие для одного полиморфного слоя -- в 4
раза, а при нескольких слоях возможно и еще больше.
Возможности
Пользовать KME можно практически везде -- ring3 и ring0, NT и win9X
как минимум, для остальных операционок х/з. Используется флэт-модель
памяти, никаких сегментных регистров. Внутри движка нет никаких системных
вызовов, одни регистры и память. Все параметры передаются на стэке.
Внутренние переменные лежат там же. Код движка, как и код декриптора, не
чувствителен к изменению оффсета.
У декриптора можно регулировать:
- используемые регистры (минимум 1, максимум 7),
- используемые команды (около десятка),
- наличие и параметры "пятен" (код разбросан по памяти и связан jmp-ми),
а также некоторые другие детали
Декриптор генерируется за 1 проход, и никакой дополнительной памяти по
этому поводу не используется. В результате размер исходных данных и
декриптора не ограничен. Таким образом реально создать расшифровщик в 100
мегабайт и оттуда запускать вирус при каждой загрузке. Возможно также
несколькими вызовами KME создавать полиморфные "слои", то есть
зашифровывать данные многократно. (см. примеры)
Как вызывать
KME имеет всего одну PUBLIC near-процедуру, kme_main. Все параметры
типа DWORD, 17 штук. Тип вызова cdecl, то есть выход из процедуры по
RET, и вызывающий код должен сделать ADD ESP, 4*17.
push Flags ; флаги, FLAG_XXX
push CommandMask ; маска команд, CMD_XXX
push CommandMask2 ; маска команд, CMD2_XXX
push RegMask ; маска регистров, REG_XXX
push offset my_random ; внешний рандомер
push JmpProb ; (1/вероятность) jmp-ов. JMP если rnd(JmpProb)==0
push OutEntryPtr ; указатель на DWORD, в который будет записана
точка входа в декриптор.
если FLAG_EIP0, то это будет 0
push OutSizePtr ; указатель на DWORD, в который будет записан
размер полученного декриптора.
без "пятен" -- здесь будет ~InputSize*k,
с "пятнами" -- k смысла не имеет,
здесь будет значение OutMaxSize
push OutFiller ; байт, которым инициализировать декриптор
push OutMaxSize ; максимальный размер буфера декриптора
push OutPtr ; указатель на буфер в котором будет декриптор
push InputEntry ; точка входа в зашифровываемые данные (куда
декриптор отдаст управление)
push InputSize ; размер исходных данных
push InputPtr ; буфер с исходными данными (вирусом)
push offset initregs ; указатели на 8-двордовые буфера со
push offset exitregs ; значениями регистров, -1=не используется
push user_param ; пользовательский параметр, передается в рандомер
call kme_main
add esp, 17*4
or eax, eax ; 0=все OK
jnz error
Возвращаемое значение:
Регистры без изменения, DF=0
EAX=0 если все в порядке
EAX=1 если ошибка (отсутствует свободное место в выходном буфере)
Комментарии:
Значения констант описаны в KME32.INT. Все константы суть степени
двойки. Можно их ORить либо складывать.
Flags (первый параметр)
FLAG_DEBUG вставить INT3 (0CCh) в начало и в конец декриптора
FLAG_NOLOGIC отключить команды изменяющие значения регистров
FLAG_NOJMPS не использовать "пятна" (jmp-ы)
FLAG_EIP0 точка входа в декриптор совпадает с его началом,
а не выбирается случайно. Актуально только если
включены JMPы (отсутствует FLAG_NOJMPS)
FLAG_NOSHORT отключить использование "коротких" инструкций
для EAX (которые на 1 байт меньше -- XOR,ADD,SUB,...)
CommandMask/CommandMask2 (второй/третий параметр)
Задает набор команд, которые можно использовать в декрипторе.
CMD_xxx см. KME32.INT
CMD_ALL использовать все команды
Глобально все команды типа CMD_xxx могут быть отключены битом
FLAG_NOLOGIC в параметре Flags, и тогда актуальны только CMD2_xxx. В
случае отсутствия сразу CMD2_ADD, CMD2_SUB и CMD2_XOR, будет
использоваться CMD2_XOR.
Естественно, при отключении всех команд некоторые из них таки будут
использоваться:
MOV для загрузки начальных значений регистров,
PUSH и XOR для записи данных в стэк
ADD и JMPreg для выхода из декриптора
RegMask (четвертый параметр):
Задает набор регистров, которые можно использовать в декрипторе. Всего
7 регистров (все РОНы кроме ESP)
REG_xxx см. KME32.INT
REG_ALL использовать все возможные регистры
(EAX/EBX/ECX/EDX/ESI/EDI/EBP)
Если не задано ни одного регистра, используется EAX
Все точки входа (InputEntry и OutputEntryPtr) -- относительные
смещения от начал своих буферов.
Параметры initregs/exitregs
Эти два указателя показывают на буфера, в каждом по 8 двордов, под
дворду для соответствующего регистра. -1 означает, что значение не
используется.
Initregs -- массив значений регистров, которые будут переданы
полиморфному расшифровщику при вызове. Расшифровщик будет сгенерен
завязанным на эти значения, и если хоть одно из них не совпадет -- нихуя
не расшифруется.
Exitregs -- массив значений регистров, которые будут получены после
выхода из расшифровщика. То есть расшифровщик будет сгенерен таким,
чтобы при выходе в заданных регистрах образовались именно эти значения. К
этим значениям можно привязать вирус.
Получение управления от декриптора
После расшифровки исходных данных в стэк происходит JMP
(ESP+InputEntry), то есть передача управления вирусу.
При этом разрушены все регистры из RegMask, а в стэке находится сам
вирус а также N-1 декриптор в случае N слоев. Размер всей этой хрени в
стэке вычисляется как сумма длин вируса и всех декрипторов кроме первого,
причем каждая длина выровнена на границу 4-х байт:
((InputSize+3) and (not 3)).
На этом движке были написаны вирусы ZMorph, а также он был использован
в I-Worm.Hybris.
············································································