451
451
virus magazine, issue #1
ЛП    ФП    Мутирование    Мусор    Реализация    LTME    Ремарки   
index

Long Time Mutation Engine
LTME
v 1.1

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

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

 Помимо этого, при изменении кода,изменятся и адреса переменных,кторые он 
 использует ,и использование данных в таком пермутируемом коде неприменимо,
 также изменяются и смещения процедур/команд ,и код не должет использовать
 абсолютные смещения.

 Первым шагом при пермутации и является дизассемблирование .Команды 
 дизассемблируются ,а результат этой операции используется на этапе 
 формирования списка. Далее необходимо скорректировать все JMPS,LOOP,
 JECXZ/JCXZ,JCCS на эквивалентные команды ,использующие максимальный операнд
 (дворд) ,т.к. вышеперечисленные используют короткий операнд (+-127 байт), а 
 при изменении команд не факт,что этого промежутка хватит для последующей 
 линковки.

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

 После этого можно начинать изменение кода.
Логические перестановки(ЛП)

 Логические перестановки это всего лишь перестановка рядом стоящих команд
 между собой.Т.е. берется пара команд и смотрятся условия перестановки,если 
 они выполняются,то ЛП происходит.Это один из наилучших способов мутации.
 Переставленные команды дадут новый код ,эквивалентный по логике программы 
 (отсюда и название "логическая перестановка"), но с измененным crc и ,что  
 приятно, после этой операции определить первоначальное положение команд 
 невозможно.Всего если есть группа из n команд,которая подвергается ЛП ,то 
 может быть n! вариантов их положения.Можно лишь перебирать комбинации и 
 считать их crc, либо разумно проксорить их всех по некому модулю и считать 
 это за crc группы ,таких групп может быть много и потребуется соответственно 
 много сигнатур.

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

 Пример ЛП:

  mov eax,[ebx]             ;\
  mov esi,[ebp+12345678h]   ; } Эти команды подвержены ЛП т.к. они не
  mov ecx,[esi]             ;/  используют результаты работы друг друга.
       |
       |
       +------------------+-------+------------------+-------+
       |                  |       |                  |       |
  mov ecx,[esi]           |  mov eax,[ebx]           | mov esi,[ebp+12345678h]
  mov eax,[ebx]	          |  mov ecx,[esi]           | mov ecx,[esi]	
  mov esi,[ebp+12345678h] |  mov esi,[ebp+12345678h] | mov eax,[ebx]
                          |                          |
                          |                          |
  		     mov ecx,[esi]             mov esi,[ebp+12345678h]
   		     mov esi,[ebp+12345678h]   mov eax,[ebx]
   		     mov eax,[ebx]             mov ecx,[esi]

 Вот другая группа команд и ее ЛП,где она дает совсем не эквивалентный код
 т.к. вторая команда использует результаты первой :
                               
   add ebx,[ecx+40]          add acx,[ebx]
   add acx,[ebx]             add ebx,[ecx+40]

 Пример команд ,которые не используют результаты друг друга в явном виде ,но
 выполняют операции разного типа,что и приводит к неэквивалентности после ЛП:

	in al, 666  	    	; абсолютное изменение
	add al,15               ; обратимое изменение

 Что-то похожее:

        sub ebx,00345678h	; обратимое изменение
	and ebx,00000001h       ; необратимое изменение

 Так как в основном переставляются команды имеющие одинаковый опкод ,то
 обращать внимание на изменение флагов не требуется ,иначе если используется
 сравнение типов изменений ,необходимо и это учитывать.

Физические перестановки(ФП)

 Логические перестановки применимы не всегда ,но еще используются физические 
 перестановки.Смысл ФП состоит в обмене частей кода между собой или вставкой
 одних частей в другие и последующем связывании команд командами перехода,
 чаще всего JMP'ами т.к. CALL модифицирует стек и это не хорошо,хотя и можно.

 Вот пример ФП с помощью вставки:

     ...                                  	...
                                          	
     mov edx,offset HELLoWORLD;    (*0)       	mov edx,offset HELLoWORLD;(*0)
     push edx                ;     (*1)	        jmp x1
     push size hello         ;     (*1)     	call print         ; (*3)
     call print		     ;	   (*3)	x2:  
					   	...
     ...				  	jmp x3
	                             	x1:
     (*2)                                     	push edx           ; (*1)
                                          	push size hello    ; (*1)
                                          	jmp x2
                                     	x3:     
                                                (*2)
						...
 
 Как видно часть всего блока команд (*1) была вырезана и вставлена между 
 частью (*3) и (*2), далее код был связан с помощью JMP'ов в порядке своего 
 следования в немутированом блоке.

Мутирование команд
 Мутирование команд подразумевает под собой замену команды на эквивалентную 
 ей команду или группу команд:

   lea ebx,[esi+edx*2]      ->    mov ebx,esi
                                  lea ebx,[ebx+edx*2]

 Это достаточно просто, но следует помнить о флагах т.к. заменой команды, 
 можно повлиять на действие ее на флаги, тем самым сделав результат 
 непредсказуемым. Далее следует работа со стеком, тут либо надо отказаться от 
 работы с ESP ,как со временным регистром, либо не мутировать команды 
 работающие с ESP.
 
 Каждая команда имеет свой принцип работы, поэтому заменять все команды 
 приходится не универсально, на основе опкода, а приходится специально 
 че-нибудь писать для замены отдельных команд. 

 Некоторой разновидностью мутации является изменение команд условного 
 перехода типа JCC. Тут условие меняется на противоположное и следом за 
 командой ставится JMP на метку, на которую по идее должно было передаться 
 управление, если бы условие (до мутации) выполнялось. Метка JCC же должна 
 теперь указывать на команду после JMP:

        	до                     	после

		...        		...
               jnc a        		jc  b
                            		jmp a
		...	  	  b:	...
 a:	                        	
                          	  a:     ...
                         

Мусор

 Для того ,чтобы изменить чексуммы кода и оставить его в рабочем состоянии 
 также применяются мусорные команды , не влияющие на работу программы.
 При "разбавлении" оригинального кода мусором затрудняется его анализ
 и восприятие,но добавление мусора в код заметно снижает скорость выполнения 
 кода и он может выполняться очень медленно .Тут следует либо убирать мусор
 перед мутацией, если это возможно, и затем добавлять его по новому, либо 
 очень редко его генерировать т.е. с малой вероятностью.
 При первом варианте может возникнуть такая ситуация,когда мусор был 
 промутирован и его нельзя так просто определить.Следовательно мутатор надо 
 вызывать до генерирования мусора,но тогда снижается степень видоизмененности 
 мусора.
 
 Т.к. код может использовать флаги, то мусор не должен их изменять, или же
 по крайней мере не влиять га логику кода, это может быть реализовано путем
 добавления мусорных команд, меняющих флаги перед командами также меняющими 
 флаги.

Реализация

 Четко разделим функции пермутирующего движка на части.Целесообразно вынести 
 функции дизассемблирования / линковки и др. в отдельную часть т.к. она редко 
 подвергается изменениям и должна присутствовать всегда.Рассмотрим что должна 
 сделать эта часть.

 При дизассемблировании хорошо себя проявляет двусвязный список,который 
 состоит из элементов, подобных этому
 
 struct	 list{
         	DWORD next;
	 	DWORD prev;
	 	DWORD link;
 	 	DWORD label;
	 	used_data struct;
              }
 
 Где next - это указатель на следующий элемент, prev - на предыдущий.
 В качестве этих указателей может быть как индекс в массиве, так и 
 непосредственно адрес элемента, что увеличивает скорость работы со списком.

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

 link -  номер метки, с которой связана команда.
 label - уникальный номер метки команды, причем значения link и label не 
 являются порядковыми номерами команд в списке, а определяются неким другим 
 способом. Допустим 0 - это признак отсутсвия метки,а номера 1-n определяют 
 ее номер. Иначе если бы номер элемента в списке определял метку, то при 
 вставке новой команды, пришлось бы реструктуризовать весь список.

 Пример описанного списка:

	+----------+   +------------+   +----------+
	|   next-----> |   next-------> |    next-------> ...
 ... <------prev   |<------prev     |<------ prev    |
        |   link:33 -+ |   link:0   |   |    link:0  |
        |   label:0| | |   abel:0   |   |    label:33|
	+----------+ | +------------+   +------|-----+
	             +-------------------------+

 Затем вызывается мутатор и после этого весь список реассемблируется обратно 
 в код с учетом содержания полей link/label.

 Структура user_data содержит информацию о команде (саму команду, опкод, 
 операнд, флаги и т.д.) и нужна для мутации и анализа. LTME использует 
 структуру, размер которой составляет что-то около 60-ти байт. 

LTME

 Как можно догадаться LTME - пермутирующий движок,реализующий почти все 
 описаное выше.Он состоит из 2-х частей - core и mutator.

 Core дизассемблирует код,преобразует jmps/jccs в их аналоги с 4- байтовым 
 операндом,связывет команды перехода и метки, после этого вызывается мутатор.
 Эта схема позволяет использовать различные мутаторы ,что собственно очень 
 важно при разработке пермутирующего вируса т.к. требуется специфический 
 мутатор для каждого отдельного вируса, который "знает" о месте хранения, 
 необходимой для работы вируса, информации. Другой случай - использование 
 движка во время работы на перехватываемых функциях ring0,там важна скорость, 
 поэтому целесообразно использовать более простой мутатор и подключается он 
 очень просто, без вмешательства в работу core

 Core используется всегда ,это что-то вроде оболочки ,которая предлагает свой
 интерфейс,а метод мутирования команд - это пользоватьельский процесс.

 Вызов Core (Во всех процедурах используется C call) :
 
 VOID*	__cdecl	ltme_core(
			VOID* 	ibuf,			// входной буфер с 
							// кодом
			VOID*   *malloc	(
					DWORD cnt	// количество байт
				      	),

			VOID*   free	(
					VOID* bufer	// указатель на буфер
				      	),
			DWORD	csize,			// размер кода
			VOID*	mutator	(
							// мутатор
							// .....
					),

			DWORD*	dasm	(		
					VOID* dasm_ibuf,// входной буфер
					CMD*  dasm_obuf,// указатель на cmd
					VOID* dasm_table// указатель на 
							// таблицы
		                        )

			VOID*	dasm_tables,		// таблицы 
							// дизассемблера

			DWORD	flags,			// флаги
			DWORD*	params,			// указатель на 
							// параметры
			DWORD*	rnd	(
					DWORD* seed,	// указатель на seed
					DWORD  range	// предел
					)
			DWORD*	seed			// указатель на seed
			);
 

 Указатели на функции malloc/free должны указывать на процедуру выделения/
 освобождения памяти.

 Dasm - указатель на дизассемблер ,возвращающий информацию о команде в 
 структуре cmd:
 
 typedef  struct {

                	BYTE		lc_size;	// длина команды
                	BYTE		lc_psize;	// длина префиксов

                	DWORD		lc_flags;	// флаги
                	BYTE		lc_tttn;	// tttn

                	BYTE		lc_sib;		// sib
                	BYTE		lc_modrm;	// modrm

			BYTE		lc_reg;		// reg
                	BYTE		lc_mod;		// mod
                	BYTE		lc_ro;		// r/o

                	BYTE		lc_rm;		// r/m

                	BYTE		lc_base;	// base
                	BYTE		lc_index;	// index
                	BYTE		lc_scale;	// scale

                	DWORD		lc_offset;	// смещение

                	BYTE		lc_operand[6];  // операнд

                	BYTE		lc_soffset;	// длина смещения
                	BYTE		lc_soperand;	// длина операнда

			BYTE		lc_mask1;	// маска
			BYTE		lc_mask2;       //
		} cmd;
 

 rnd - указатель на какой-нибуди ГСЧ с указанными параметрами.

 Поле params является указателем на структуру неимеющую четко определенного 
 формата, лишь 3 первых поля статичны и предназначены для использвания core и 
 mutator'oм. Т.к. параметры передаются в mutator, то остальное пространство 
 в параметрах может быть использовано в целях нестандартного мутатора.

 Если при работе core произошла ошибка, то в EAX возвращается FFFFFFF.
 Иначе - 0, размер и смещение пермутированого кода в параметрах. Полученный 
 буфер с кодом, после использования, надо освобождать через функцию free, 
 передаваемую движку.
 
 typedef		struct{

			DWORD	build_size,		// длина 
							// полученного кода

			DWORD	build_offset,           // указатель на буфер
							// с полученным кодом

			DWORD	mixer_maxswp,		// количестфо блоков 
							// для ФП (max)
		      } ltmeparam;
 
 Чем больше значение mixer_maxswp ,тем более будет "перемешанный" код и тем 
 менее он быст.

 mutator - указатель на процедуру мутирования .В принципе, следует писать 
 свой мутатор, вставляя в него вызов стандартного тем самым рашриряя его 
 возможности:
 
 VOID*	__cdecl	ltme_mutator(
			DWORD	csize,			// размер кода
			VOID*	list,           	// указатель на 
							// список

			DWORD	lastlabelPtr,		// последняя 
							// использованая 
							// метка
	
			DWORD	flags,          	// флаги
			DWORD*	params,			// указатель на 
	                                                // параметры

			DWORD*	rnd	(	
					DWORD* seed,	// указатель на seed
					DWORD  range    // предел
					)
			DWORD*	seed     		// указатель на seed
			VOID*   *malloc	(
					DWORD cnt	// количество байт
				      	),

			VOID*   free	(
					VOID* bufer	// указатель на буфер
				      	)
			    )								
 

 В процессе работы, LTME выделяет буфер для организации списка/таблиц ,это
 может потребовать как минимум 60*количество_команд байт плюс память на 
 таблицы. 

 list - указатель на список ,сгенерированый core , в его начале расположены 3 
 дворда :

 	* указатель на первый элемент списка
	* указатель на последний элемент списка
	* указатель на область памяти , доступную для записи в нее нового 
	  элемента списка.

 Далее идет сам список,состоящий из элементов структуры one:
 
 typedef	struct	{
				DWORD		one_next,
				DWORD		one_prev,
				ltmedata	one_data
			} one;		

 typedef	struct	{

				cmd	ltmed_desc,
				BYTE	ltmed_command[23],
				DWORD	ltmed_link,
				DWORD	ltmed_label,
				DWORD	ltmed_flags

			} ltmedata;
 
 Для работы со списком имеется несколько процедур,расположенных в 
 core\list.inc и mutator\list.inc:

 Инициализация списка:
 	*list_init(	EBX=указатель на память для списка)

 Добавление элемента в список:
 	*list_add(	EBX=список,
	 		EDX=номер элемента,за которым будет включение
	 		(FFFFFFFFh если за последним,0 - перед первым)
	 		EDI=указатель на включаемый ltmedata)

 Удаление элемента из списка:
	*list_kill(	EBX=список,
	 		EDX=номер элемента,за который будет удален
	 		(FFFFFFFFh если за последним,1 - первый))

 Нахождение адреса элемента:
	*list_get(	EBX=список,
	 		EDX=номер элемента,адрес которого требуется найти 
			(1<->FFFFFFFF))
	 EAX = адрес элемента.

 Обмен местами значения элементов:
 	*list_swap(	EBX=список,
	 		EDX = номер элемента #1,
	 		ECX = номер элемента #2)

 Получение количества элементов в списке:
 	*list_getmax(	EBX=список)
	 EAX = количество элементов.

 Если у элемента в поле ltmed_flags стоит флаг LTMED_EXTERNAL, то этот 
 элемент имеет ссылку за пределы рассматриваемого кода.

Флаги

 В качестве флагов  в core и mutator передается комбинация следующих бит,
 определяющих работу движка.Конечно можно передавать и свои флаги в мутатор,
 например при расширении его стандартных возможностей:

LTMEF_MSTACK 00000001b Использовать команды,работающие со стеком
LTMEF_GARBAGE 00000010b Генерировать мусор
LTMEF_PSWAP 00000100b Использовать ФП
LTMEF_LSWAP 00001000b Использовать ЛП
LTMEF_JCC 00010000b Изменять условия JCC
LTMEF_CMD 00100000b Мутировать команды
LTMEF_ALL 00111111b Использовать все;)

Ремарки
 Стандартный мутатор расчитан на вызов только лишь один раз т.к. не 
 модифицирует данные, необходимые для еще одной такой же мутации(т.е. не
 изменяет структуру cmd мутированых команд).

 И если требуется ,чтобы команда не была промутирована,то следует поставить 
 во флагах ltmed_desc.lc_flags бит LF_RAW ,в таком случае считается,что 
 команда не до конца продизассемблирована и она не мутируется.