Основной задачей мусорных инструкций является скрытие/защита полезного кода (от аверов, зорких глаз реверсера и других любопытных). Однако, "неправильный" трэш может стать причиной обнаружения вирусного кода, сводя на нет все наши старания.
Этот текст о том, как улучшить качество генерируемого мусора...
---------------------------------------------------------------------------------------------------------------------------------
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
x x
x Умный мусор: построение логики x
x x
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
[x] Введение
Основной задачей мусорных инструкций является скрытие/защита полезного кода (от аверов, зорких глаз реверсера
и других любопытных). Однако, "неправильный" трэш может стать причиной обнаружения вирусного кода, сводя на
нет все наши старания.
Этот текст о том, как улучшить качество генерируемого мусора.
[x] Кто противник
Допустим, что происходит проверка файла, заражённого нашим вирусом. Антивирус может действовать так:
--------------------------------------------------------------------------------------------------------------
- запустит поверхностный анализ файла: его структуры и каких-то участков кода (сбор информации для
начала работы эмулятора, кодо-анализатора, эвристика, может чего-то ещё);
--------------------------------------------------------------------------------------------------------------
- далее, запускается эмуль, в процессе работы которого могут быть вызваны: анализатор кода (сбор
данных для (дальнейшей работы) эмулятора и эвристика), а также сигнатурный анализ (поиск известных
сигнатур в уже проэмулированном коде);
--------------------------------------------------------------------------------------------------------------
- после отработки эмуля в дело вступает эвристический анализ собранных данных (сигнатур для нашего
виря ещё не сделали=)), где происходит подсчёт баллов "опасности". Если полученный результат больше
заданного предела - получите клеймо heur-virus'a.
--------------------------------------------------------------------------------------------------------------
От эмуля нам поможет рабочая антиэмуль, от сигнатур - генератор мусора, который (как оказывается xD) использует
наш вирь. Но если созданный трэш-код окажется слабым, то нас накроет эвристика.
[x] План наступления
Итак, для построения более качественного трэш-кода, вначале я предлагаю выбрать, под генерируемый код какого
компилятора мы будем "косить": ms, borland etc. После того, как выбран компилер (например, ms), можно ещё
определить, под какой режим генерации/оптимизации мы будем подстраиваться ("min size"/"max speed"). Это всё,
конечно же, не обязательно, но желательно. Так как под разными режимами код генерируется по-другому. Например,
для ms-компилера, команда занесения единицы в регистр в режиме "max speed" (в основном) будет такая:
----------------------------------------------------------------------------------------------------------------
mov eax, 1 ;0xB8 0x01 0x00 0x00 0x00
----------------------------------------------------------------------------------------------------------------
А для "min size"
----------------------------------------------------------------------------------------------------------------
xor eax, eax ;0x33 0xC0
inc eax ;0x40
----------------------------------------------------------------------------------------------------------------
Трэшген может генерировать оба эти варианта, но более правдоподобно смотрится, если держаться одной тактики
(неизвестно, какие извращения будут в новых версиях эвристиков).
Далее, помимо разных фичезов, которые вы встроите в трэшген, он также должен уметь генерировать "реалистичный"
код (похожий на обычный код стандартных программ, написанных на ЯВУ), а именно:
--------------------------------------------------------------------------------------------------------------
+ "правильные" инструкции (опкоды и операнды - например, команда "mov eax, ecx" может быть закодирована
с помощью двух разных опкодов: 0x8B 0xC1 и 0x89 0xC8 -> ms-компилер юзает первый вариант; некоторые
команды с использованием регистра EAX, имеющие "оптимизированные" варианты опкодов; etc);
--------------------------------------------------------------------------------------------------------------
+ "правильные" конструкции (например, test/cmp без последующей инструкции jmp/jxx - очевидное палево);
--------------------------------------------------------------------------------------------------------------
+ множество различных команд (использующих регистры, адреса памяти и т.п.), функции (с прологами/
резервированием стэка/эпилогами etc и команды с использованием локальных переменных, входящих
параметров), winapi и другие;
--------------------------------------------------------------------------------------------------------------
+ "правильная" статистика частоты встречаемости опкодов (собираем стату в обычных прогах и используем её;
для более точного результата можно собирать стату только в файлах, собранных выбранным ранее
компилером);
--------------------------------------------------------------------------------------------------------------
+ нормальная энтропия (в битах ~ [5.5; 6.8]; кстати, энтропия будет примерно в заданном диапазоне, если
генерить "правильный" код + использовать стату встречи опкодов (сюда можно добавить и логику команд));
--------------------------------------------------------------------------------------------------------------
+ только живой код (который может выполниться);
--------------------------------------------------------------------------------------------------------------
+ etc;
--------------------------------------------------------------------------------------------------------------
Но даже такой трэш-код, построенный с учётом данных пунктов, может легко ловиться эвристикой.
[x] Полезный мусор
Основная засада в том, что мусорный код - это только мусор, набор бесполезных инструкций. В этом и кроются причины
гнева эвристиков. А раз так, значит трэш должен стать полезным. Для этого надо реализовать ещё 2 задачи:
-------------------------------------------------------------------------------------------------------------
1. полезный код должен использовать результат работы трэш-кода (или наоборот, трэш-код должен как-либо
повлиять на работу полезного кода) aka "псевдо-цель";
-------------------------------------------------------------------------------------------------------------
2. "LOGICAL TRASH" technique;
-------------------------------------------------------------------------------------------------------------
Первый пункт в общем случае реализуется довольно просто: генерируем мусор, запускаем его на выполнение, и после
отработки трэша полученный результат используем в полезном коде. Например, сгенерировали такой код:
----------------------------------------------------------------------------------------------------------------
mov eax, 100
mov ecx, eax
sub ecx, 95
----------------------------------------------------------------------------------------------------------------
После его выполнения ECX = 5. И данное значение можно добавить к ключу для расшифровки вирусного кода (применений
куча).
Однако, сгенерированный трэш-код может быть и таким:
----------------------------------------------------------------------------------------------------------------
mov eax, 100 ;1
mov ecx, eax ;2
mov ecx, eax ;3
mov ecx, eax ;4
sub ecx, 95 ;5
----------------------------------------------------------------------------------------------------------------
После его выполения ECX также равно 5. Но команды 2 и 3 выдают себя с потрохами, за что будут наказаны эвристикой.
Решение состоит в построении "логичного" мусора.
[x] "LOGICAL TRASH" technique
Идея заключается в том, чтобы мусорный код сделать логичным, подобно логике кода обычных программ. Нормальный код
вначале инициализирует параметры (регистры, локальные переменные etc); затем выполняет команды, использующие и/или
как-либо влияющие на эти параметры. Причём команды являются одним целым - выполняют общую задачу, и среди них нет
лишних - мусорных. Нет повторных инициализаций, использования и обращения к (значениям) неинициализированным
параметрам. Все инструкции связаны друг с другом, каждая влияет на дальнейший ход выполнения кода.
Примерно такую логику я реализовал в новой версии своего движка xTG (v2.0.0), который работает по следующей
схеме:
________
| |
| начало |
|________|
|
|
|
| +--------------------------------------------+
| | |
| | |
| ________________________ | ________________________ |
| | | | | | |
| | модуль генерации | | | модуль логики | |
| | команд | | | | |
| | | | | | |
| | | | | | |
| | ---------------------- | | | ---------------------- | |
+-->| генерация |<-----+ +--->| парсер команд | |
+------>| правильно | | | | |
| | построенной | адрес команды | | ---------------------- | |
| | команды |-----------------+ | эмулятор команд | |
| | | | | |
| | ---------------------- | | ---------------------- | 0 |
| | корректировка адреса |<----------------+ | анализатор команд/ |---+
| 1 | для генерации новой | | 1 | корректор логики |
+-------| команды | 0 +----| команд |
| |------+ | |
| ---------------------- | | | ---------------------- |
|________________________| | |________________________|
|
|
|
________ |
| | |
| конец |<-----------------------------+
|________|
Распишем подробно:
----------------------------------------------------------------------------------------------------------
I. вначале, конечно же, вызываем модуль генерации команд;
----------------------------------------------------------------------------------------------------------
II. генерируем "правильную" команду: правильные опкоды и остальные байты. Да, кстати, если разработанный
двигатель логики (ДЛ) будет применяться к сторонним трэшгенам (или другим движкам), то ДЛ также
должен проверять, правильно ли построена команда (aka проверка на уровне байтов);
----------------------------------------------------------------------------------------------------------
III. вызываем модуль логики, передавая в него адрес только что созданной команды;
----------------------------------------------------------------------------------------------------------
IV. за дело принимается парсер команд. Парсер может быть функцей, являющейся частью модуля логики, а может
быть и отдельным самодостаточным движком (дизасм). Первый случай хорошо подходит, если модуль логики
является частью генератора мусора. Тогда в функции парсера будут разобраны только те команды, которые
может генерировать двигл. Второй случай хорошо подходит, если модуль логики является самостоятельным
движком. И при этом мы не знаем, какие команды могут генерироваться.
Парсер выясняет, какая перед ним команда, и получает её параметры (операнды: регистры, адреса etc) - в
соответствии с этим сохраняет в некоторую структуру данные параметры и выставляет определённые флаги.
Заполненная структура будет использоваться анализатором команд (об этом ниже).
Также, например, если встретилась команда mov ecx, dword ptr [403008h] и т.п., тогда парсер заменит
адрес 403008h на другой, соответствующий ему адрес в выделенной памяти для корректной эмуляции команды;
----------------------------------------------------------------------------------------------------------
V. затем эмулируем (скорректированную) команду. Эмуль, по аналогии с парсером, может быть как встроенной
функцией в модуле логики, так и полноценным движком-пирожком;
Эмуль получает адрес команды, подготавливает специальную среду, копирует туда команду и эмулирует. Причём
эмуляция может быть как минимум 3-х видов: прямой запуск в специальной среде, полная имитация выполнения
команды и сочетание этих двух методов (для большинства команд хватает 1-ого метода). Результат эмуляции
(текущие значения параметров команды и др.) сохраняем в переменных: виртуальных регистрах и др.
Кстати, эмуль - козырная технология для вирей, с помощью которой можно творить очень интересные темы
(для UEP'a, виртуальных машин, "logical trash" tech, морфинга и прочих вкусностей);
----------------------------------------------------------------------------------------------------------
VI. и после вызываем анализатор команд/корректор логики. Анализатор, по аналогии с парсером и эмулем, может
быть как встроенной функцией в модуле логике, так и полноценным двиглом;
Анализатор, на основе данных от парсера (заполненная структура) и эмуля, решает, подходит ли команда по
логике или нет.
Анализ команды проходит в 2 этапа:
1. Проверка параметров команды.
Сначала анализатор должен понять, какие есть параметры, и как их проверять. Для этого он использует
флаги, переданные парсером. Набор флагов может быть такой:
--------------------------------------------------------------------------------------------------------
LGC_INSTR_INIT equ 00000000000000000000000000000001b ;команда инициализации параметров;
LGC_INSTR_CHG equ 00000000000000000000000000000010b ;команда изменения параметров;
LGC_P1_DST equ 00000000000000000000000000000100b ;первый парам - приёмник
LGC_P1_SRC equ 00000000000000000000000000001000b ;первый парам - источник
LGC_P2_DST equ 00000000000000000000000000010000b ;второй парам - приёмник
LGC_P2_SRC equ 00000000000000000000000000100000b ;второй парам - источник
LGC_P1_REG equ 00000000000000000000000001000000b ;первый парам - регистр
LGC_P1_ADDR equ 00000000000000000000000010000000b ;первый парам - адрес
LGC_P1_NUM equ 00000000000000000000000100000000b ;первый парам - число
LGC_P2_REG equ 00000000000000000000001000000000b ;второй парам - регистр
LGC_P2_ADDR equ 00000000000000000000010000000000b ;второй парам - адрес
LGC_P2_NUM equ 00000000000000000000100000000000b ;второй парам - число
--------------------------------------------------------------------------------------------------------
Этими флагами можно описать почти все инструкции (некоторые флаги можно убрать). Если инструкция
содержит больше 2 параметров, тогда остальные хранятся в отдельном поле.
Далее, по флагам определяется, что и как чекать: проверки на возможность инициализации параметров,
на изменение их значений, на использование их в других командах и многое другое. Результат каждой
проверки заносится в маски. Их 2: regs_init & regs_used. Грубо говоря, это 2 dword'a, где каждый бит
соответствует определённому параметру (например, какому-то регистру). Причём, биты в regs_init
показывают, можно ли инициализировать параметр или нет (защита от повторной инициализации). А по
битам в regs_used узнаём, можно ли вообще использовать параметр в командах или нет.
2. Проверка состояний параметров команды
Итак, если первый этап пройден, то это означает, что параметры годные. Продолжим.
Состояние - это некоторое сохранённое значение, которое принимал параметр. Состояния всех параметров
хранятся в таблице состояний, которая представляет собой буфер определённого размера.
Значит, анализатор берёт текущее значение параметра, которое мы получили с помощью эмуляции (и
сохранили, например, в виртуальном регистре), и сверяет его со всеми накопленными состояниями данного
параметра.
Если совпадение найдено, тогда команду считаем мусорной, проверка не пройдена. В этом случае
из таблицы состояний берём последнее сохранённое состояние данного параметра и делаем его текущим
(сохраняем это состояние в виртуальном регистре); а также восстановим маски на предыдущие значения.
Если совпадение не найдено - значит это новое состояние параметра, команда прошла проверку. Добавляем
это значение в таблицу состояний;
----------------------------------------------------------------------------------------------------------
VII. переходим снова в модуль генерации команд. Смотрим, какое значение вернул нам модуль логики:
если 0, тогда команда не подходит по логике - по её же адресу сгенерируем новую команду (перезапишем).
Прыгаем на пункт II.
если 1, тогда команда подходит по логике. Прыгаем на пункт VIII.
----------------------------------------------------------------------------------------------------------
VIII. увеличиваем адрес (для генерации новой команды) на размер проверенной команды. И выясним: если мы
сгенерировали нужное количество байтов, тогда прыгаем на пункт IX. Если не все, тогда на пункт II.
----------------------------------------------------------------------------------------------------------
IX. выходим;
----------------------------------------------------------------------------------------------------------
[x] Примеры генерации простого мусора
--------------------------------------- ------------------------------------
| такой код не пройдёт проверку логики, | | такой код пройдёт проверку логики, |
| а значит не сгенерируется | | а значит сгенерируется |
--------------------------------------- ------------------------------------
----------------------------------------------------------------------------------------------------------------
;1.1 ;2.1
mov edi, 1000 mov edi, 1000
mov esi, edi mov esi, edi
mov edi, esi mov edi, 2000
----------------------------------------------------------------------------------------------------------------
;1.2 ;2.2
mov eax, 1000 mov eax, 1000
mov ecx, 2000 mov ecx, 2000
xchg eax, ecx xchg eax, ecx
xchg eax, ecx inc ecx
xchg eax, ecx
----------------------------------------------------------------------------------------------------------------
;1.3 ;2.3
xor edi, edi xor edi, edi
xor ebx, ebx xor ebx, ebx
add edi, ebx inc ebx
add edi, ebx
----------------------------------------------------------------------------------------------------------------
;1.4 ;2.4
mov edx, 1000 mov edx, 1000
mov eax, edx mov eax, edx
dec edx mov edx, 2000
mov edx, 2000
----------------------------------------------------------------------------------------------------------------
;1.5 ;2.5
mov eax, 1000 mov eax, 1000
mov ecx, 1001 mov ecx, 1001
sub ecx, eax sub ecx, eax
sub eax, ecx sub eax, ecx
inc eax dec eax
----------------------------------------------------------------------------------------------------------------
------------------------------------------------------------------------------------------------------------------------
В примере 1.1 первые 2 команды нормальные, а третья - мусорная. Регистр EDI инициализировать можно, но он примет
такое же значение, какое имеет сейчас - ненужная инициализация. Пример 2.1 (и все остальные в дальнейшем) показывает
правильный вариант инициализации.
------------------------------------------------------------------------------------------------------------------------
В примере 1.2 первые 3 команды правильные, а 4-ая - мусорная. Регистры EAX & ECX снова примут значения, которые уже
имели (проверка состояний параметров).
------------------------------------------------------------------------------------------------------------------------
В примере 1.3 первые 2 команды правильные, а 3-я - мусорная. EDI += 0 (проверка состояний);
------------------------------------------------------------------------------------------------------------------------
в примере 1.4 первые 3 команды правильные, а 4-я - мусорная. Регистр EDX нельзя инициализировать, если он прежде не
повлиял на значение другого параметра (проверка параметров);
------------------------------------------------------------------------------------------------------------------------
в примере 1.5 первые 4 команды правильные, а 5-ая - мусорная. После выполнения первых 4-x команд состояния регистра
EAX будут такие: 1000, 999. А после выполнения 5-ой команды EAX = 1000. Такое состояние уже было (проверка состояний
параметров).
------------------------------------------------------------------------------------------------------------------------
[x] Позитив =)
Реализацию техники "логичного мусора", наглядные примеры генерации трэш-кода, а также более полное понимание
задумки - всё это ты найдешь в сорцах xTG v2.0.0.
Следует сказать, что в xTG наиболее лучшее качество логики получается при генерации линейного трэш-кода без
winapi-функций.
В остальных случаях логика будет нечёткой, но будет: на ветвлениях и с винапишками. Это связано с модулем
логики - чем он мощнее, тем качественней выхлоп.
Если же не устраивает логика каких-либо инструкций, достаточно просто установить другие флаги.
Также, возможен вариант применения логики для уже сгенерированного кода. Однако искомый код может быть на 100%
отличный от исходного.
Используя данную технику, наш мусор становится полезным и логичным, что позволяет довольно эффективно обходить
эвристику. И только комплексное применение техник воплотят наши желания в реальность. Ура!
[x] Используемая инфа
1. beauty on the fire "Эмуляция программного кода", 2004, http://uinc.ru/articles/47/
2. beauty on the fire "Анализаторы кода в антивирусах", 2004, http://uinc.ru/articles/45/
3. Sl0n "Полиморфизм. Новые техники", 2004, http://vx.netlux.org/lib/vsl05.html
[+]
август, 2011
m1x
pr0mix@mail.ru
EOF
вирмэйкинг для себя...искусство вечно
|