Скопипиздил статейку и исходник с сайта delfcode.ru.
Доброго времени суток.
Исходник полиморфа Win32.Beetle.
Автор pest.
Характеристики
- заражает PE файлы (DLL/EXE);
- не изменяет атрибутов секций заражаемого файла;
- в исполняемой секции хранит первый полиморфный декриптор;
- расширяет последнюю секцию и хранит там полезную нагрузку, грабленные данные, дополнительную информацию; все это хранится в шифрованном виде и с разными ключами;
- для передачи управления на первый декриптор применяет технику "Расширенный EPO";
- второй декриптор построен по технологии "Адский треш", который призван усложнить лечение;
- написан на си с асм вставками;
- каждый зараженный файл будет содержать полезную нагрузку.
Принцип работы
Каждый зараженный файл будет играть роль носителя полезной нагрузки. В качестве примера рассмотрим зараженный winmine.exe, у которого в качестве полезной нагрузки будет calc.exe.
Секции зараженного файла:
Шифрованные данные в последней секции выглядят так:
После запуска файла с помощью расширенного EPO произойдет переход на первый декриптор:
Первый полиморфный декриптор располагается на месте одной сграбленной функции из оригинального файла, и поэтому этот декриптор имеет ограниченный размер:
Декриптор выделяет память, копирует данные из последней секции, расшифровывает второй «адский» декриптор и передает управление на него.
Адский декриптор представляет очень большое количество вложенных полиморфных функций. И операции декриптора «размазаны» по этим функциям.
Начало второго декриптора:
После его исполнения расшифруется шеллкод, который запустит полезную нагрузку и восстановит всю краденную информацию у зараженного файла, продолжит его управление.
Для заражения файлов используется две техники:
- расширенное EPO
- адский треш
Расширенное EPO
Поиск CALL производится не от точки входа, а по всей исполняемой секции. Причем для поиска не применяются никакие сложные алгоритмы. Алгоритм выглядит так:
1. ищет байт 0xE8 и определяет адрес перехода;
2. смотрит, если адрес попадает в диапазон исполняемой секции, то переходит на него, иначе на пункт 1;
3. после перехода читает первые байты и сравнивает со стандартным прологом push ebb / mov ebp,esp. Если условие выполняется — считает, что функция найдена, иначе пункт 1.
После таких нехитрых операций вирус получает все call вызовы в программе.
Далее он пытается определить размер этих функций. Для этого выполняет следующий алгоритм:
1. По найденным call адресам идет дизассемблером длин.
2. И смотрит опкоды ret/ret xx: если они равны, то это есть минимальная длина функции.
В результате получится список функций и длина. Используя эти данные, инфектор рандомно выбирает одну, «крадет» ее и встраивает туда свой первый декриптор. Но нужно не забывать, что на эту функцию могут указывать релоки, которые "изуродуют" код декриптора. И чтобы этого не произошло, нужно отключить релоки для адресов, которые переходят внутрь функции.
Кроме этого, инфектор не знает логику выполнения программы, и, возможно, что эта грабленная функция никогда не исполнится. Для увеличения вероятности он переадресует несколько случайно выбранных call на код, а точнее на полиморфный декриптор.
Полиморфный генератор
Любой генератор должен уметь создавать произвольные опкоды. В инфекторе генерация элементарных опкодов производится достаточно просто. Для генерации инструкций в памяти введены следующие функции для работы с памятью:
Описание регистров и основных операций:
И сделаны простые функции, которые генерируют как один опкод, так целый класс опкодов:
… их там много …
Количество опкодов можно легко наращивать.
Вот пример простого декриптора:
А вот как это будет выглядеть с использованием функций:
Этот код уже легко можно усложнить, например, между инструкциями добавлять мусор, менять регистры, морфить основные команды. Все это реализовано при генерации первого декриптора. А вот второй декриптор будет гораздо сложнее.
"Адский" мусор
Для усложнения декриптора было решено генерировать большое количество вложенных функций и инструкции декриптора размазать по ним.
В первом декрипторе одна инструкция равна одной полезной команде. Было решено одну полезную инструкцию, пусть будет "mov eax, 0x12345678", помещать в функцию. В результате должен получиться такой код:
В результате таких нехитрых действий можно потерять значение eax после выполнения кода в trash_2. Так как trash может генерировать различные инструкции, которые будут работать с eax. Для этого был введен "контекст", в котором задаются рабочие регистры. И если при генерации мусора выполняется работа с рабочим регистром, то он заносится в локальную переменную для этой функции.
И вот пример, как это будет работать:
1. инструкция, которая должна быть сгенерирована: "mov eax, 12345678";
2. инициализируется количество локальных переменных: 8*8+‹rnd›*8 (0x88);
3. reg[_RAX].st=true; указывает, что _RAX - рабочий регистр;
4. инициализируется номер переменной для хранения регистра: reg[_RAX].local=‹rnd›*8 (0x80);
5. В случае генерации мусора, который будет использовать eax, значение регистра будет перемещено в локальную переменную по смещению 0x80;
6. Когда нужно будет выполнить команду, регистр выгружается из переменной и используется в полезной инструкции.
Вот так выглядит одна функция, которая выполняет mov eax, 12345678:
Назовем такой генератор gen_call_op. Так как сейчас можно контролировать состояние регистров в генерируемых функциях. Можно строить более сложные конструкции. Например, генерировать несколько функций, и объединяю их в func4:
И так можно сделать несколько слоев. Граф выполнения всего одной полезной команды выглядит так:
В этом графе полезная инструкция может быть на месте любой функции. Для данного графа функция, которая содержит инструкцию, обведена красным квадратом. Назовем такой генератор gen_tree_op.
И вот из таких инструкций (gen_tree_op) инфектор строит свой декриптор. По моему, выглядит «адски».
Не составит труда переделать его под x64, большинство инструкций отличаются лишь префиксом.
Свойства генератора:
- Данный способ позволяет генерировать инструкции практически любой сложности.
- Поиск полезных инструкций становится крайне затруднительным статическим анализом.
- Генерируемый код похожий на тот, который генерируют компиляторы.
- Не строит код, по которому можно было бы взять сигнатуру.
Шеллкод
Код, который выполняет полезную нагрузку и восстанавливает грабленные части файла: cвободно-релоцируемый и написан полностью на си. Обычно такой код пишется на ассемблере, возможно, для версии x64 придется его и использовать, но для x86 можно обойтись си. Принцип написания базонезависимого кода был взят из вируса 0x02 pr0m1x. Из критических изменений можно выделить поиск базы kernel32.dll. В моем коде ищется через PEB, перечисляются все имена загруженных dll и считается их хеш. Такой способ работает на всей линейке XP до WIN8.
Выводы:
- расширенный OEP усложняет детект вируса ав;
- первый и второй полиморфный декриптор усложняют детект вируса;
- второй полиморфный декриптор усложняет статический поиск необходимых значений для расшифровки остальных частей, что значительно осложнит лечение;
- минимум аномалий после заражения файла;
- файл сильно перестроен после заражения, что тоже должно сильно усложнить лечение.
Все эти характеристики должны создать головную боль аверам, и если это будет так, то все мои труды были потрачены не зря.
Доброго времени суток.
Исходник полиморфа Win32.Beetle.
Автор pest.
Характеристики
- заражает PE файлы (DLL/EXE);
- не изменяет атрибутов секций заражаемого файла;
- в исполняемой секции хранит первый полиморфный декриптор;
- расширяет последнюю секцию и хранит там полезную нагрузку, грабленные данные, дополнительную информацию; все это хранится в шифрованном виде и с разными ключами;
- для передачи управления на первый декриптор применяет технику "Расширенный EPO";
- второй декриптор построен по технологии "Адский треш", который призван усложнить лечение;
- написан на си с асм вставками;
- каждый зараженный файл будет содержать полезную нагрузку.
Принцип работы
Каждый зараженный файл будет играть роль носителя полезной нагрузки. В качестве примера рассмотрим зараженный winmine.exe, у которого в качестве полезной нагрузки будет calc.exe.
Секции зараженного файла:
Шифрованные данные в последней секции выглядят так:
После запуска файла с помощью расширенного EPO произойдет переход на первый декриптор:
Первый полиморфный декриптор располагается на месте одной сграбленной функции из оригинального файла, и поэтому этот декриптор имеет ограниченный размер:
Декриптор выделяет память, копирует данные из последней секции, расшифровывает второй «адский» декриптор и передает управление на него.
Адский декриптор представляет очень большое количество вложенных полиморфных функций. И операции декриптора «размазаны» по этим функциям.
Начало второго декриптора:
После его исполнения расшифруется шеллкод, который запустит полезную нагрузку и восстановит всю краденную информацию у зараженного файла, продолжит его управление.
Для заражения файлов используется две техники:
- расширенное EPO
- адский треш
Расширенное EPO
Поиск CALL производится не от точки входа, а по всей исполняемой секции. Причем для поиска не применяются никакие сложные алгоритмы. Алгоритм выглядит так:
1. ищет байт 0xE8 и определяет адрес перехода;
2. смотрит, если адрес попадает в диапазон исполняемой секции, то переходит на него, иначе на пункт 1;
3. после перехода читает первые байты и сравнивает со стандартным прологом push ebb / mov ebp,esp. Если условие выполняется — считает, что функция найдена, иначе пункт 1.
После таких нехитрых операций вирус получает все call вызовы в программе.
Далее он пытается определить размер этих функций. Для этого выполняет следующий алгоритм:
1. По найденным call адресам идет дизассемблером длин.
2. И смотрит опкоды ret/ret xx: если они равны, то это есть минимальная длина функции.
В результате получится список функций и длина. Используя эти данные, инфектор рандомно выбирает одну, «крадет» ее и встраивает туда свой первый декриптор. Но нужно не забывать, что на эту функцию могут указывать релоки, которые "изуродуют" код декриптора. И чтобы этого не произошло, нужно отключить релоки для адресов, которые переходят внутрь функции.
Кроме этого, инфектор не знает логику выполнения программы, и, возможно, что эта грабленная функция никогда не исполнится. Для увеличения вероятности он переадресует несколько случайно выбранных call на код, а точнее на полиморфный декриптор.
Полиморфный генератор
Любой генератор должен уметь создавать произвольные опкоды. В инфекторе генерация элементарных опкодов производится достаточно просто. Для генерации инструкций в памяти введены следующие функции для работы с памятью:
Код:
struct BLOCK{
uint32 size;
uint8* data;
};
bool add_block(BLOCK *b,uint8* data,int size){
if (!b->size){
b->data=(uint8*)malloc(size);
if (!b->data) return false;
}else{
b->data=(uint8*)realloc(b->data,b->size+size);
if (!b->data) return false;
}
memcpy(&b->data[b->size],data,size);
b->size+=size;
return true;
}
Описание регистров и основных операций:
Код:
enum REG{
_RAX=0,
_RCX=1,
_RDX=2,
_RBX=3,
_RSP=4,
_RBP=5,
_RSI=6,
_RDI=7,
};
enum OP2{
_XOR = 0x30,
_ADD = 0x00,
_SUB = 0x28,
_AND = 0x20,
_OR = 0x08,
_MOV = 0x88,
_CMP = 0x38,
_TEST =0x82,
};
И сделаны простые функции, которые генерируют как один опкод, так целый класс опкодов:
Код:
struct OPCODE_2{
uint8 o1;
uint8 o2;
};
int _OP_RR(BLOCK *b,uint32 o,uint8 r1,uint8 r2,bool x64=false){
int i=0;
if (x64){
uint8 pref=0x48;
add_block(b,(uint8*)&pref,sizeof(pref));
i++;
}
OPCODE_2 op;
op.o1=0x03+o&0xFF;
op.o2=0xC0+r2+8*r1;
add_block(b,(uint8*)&op,sizeof(OPCODE_2));
i+=2;
return i;
}
int _PUSH_R(BLOCK *b,uint8 reg){
int i=1;
uint8 op=0x50|reg;
add_block(b,&op,1);
return i;
}
… их там много …
Количество опкодов можно легко наращивать.
Вот пример простого декриптора:
Код:
mov edx, key
mov eax, data
mov ecx, size/4
l1:
xor [eax],edx
add eax, 4
loop l1
А вот как это будет выглядеть с использованием функций:
Код:
BLOCK b;
b.size=0;
int i=0;
l+=_OP_RC(&b, _MOV, _RDX, key);
l+=_OP_RC(&b, _MOV, _RAX, data);
l+=_OP_RC(&b, _MOV, _RCX, size/4);
int l1=l;
l+=_OP_ArR(&b, _XOR, _RAX, _RDX);
l+=_OP_RC(&b, _ADD, _RAX, 4);
l+=LOOP(l1-(l+2));
Этот код уже легко можно усложнить, например, между инструкциями добавлять мусор, менять регистры, морфить основные команды. Все это реализовано при генерации первого декриптора. А вот второй декриптор будет гораздо сложнее.
"Адский" мусор
Для усложнения декриптора было решено генерировать большое количество вложенных функций и инструкции декриптора размазать по ним.
В первом декрипторе одна инструкция равна одной полезной команде. Было решено одну полезную инструкцию, пусть будет "mov eax, 0x12345678", помещать в функцию. В результате должен получиться такой код:
Код:
push ebp
mov ebp, esp
sub esp, local_size
trash_1 — мусорные инструкции
mov eax, 0x12345678 — полезная команда
trash_2 — мусорные инструкции
leave
ret
В результате таких нехитрых действий можно потерять значение eax после выполнения кода в trash_2. Так как trash может генерировать различные инструкции, которые будут работать с eax. Для этого был введен "контекст", в котором задаются рабочие регистры. И если при генерации мусора выполняется работа с рабочим регистром, то он заносится в локальную переменную для этой функции.
Код:
struct REG_ITEM{[/I]
uint32 value; //значение регистра
uint32 local; //номер локальной переменной
bool st; //флаг включения регистра
bool l_st; //флаг, информирующий, что регистр перемещен в локальную переменную
};
REG_ITEM reg[8];
И вот пример, как это будет работать:
1. инструкция, которая должна быть сгенерирована: "mov eax, 12345678";
2. инициализируется количество локальных переменных: 8*8+‹rnd›*8 (0x88);
3. reg[_RAX].st=true; указывает, что _RAX - рабочий регистр;
4. инициализируется номер переменной для хранения регистра: reg[_RAX].local=‹rnd›*8 (0x80);
5. В случае генерации мусора, который будет использовать eax, значение регистра будет перемещено в локальную переменную по смещению 0x80;
6. Когда нужно будет выполнить команду, регистр выгружается из переменной и используется в полезной инструкции.
Вот так выглядит одна функция, которая выполняет mov eax, 12345678:
https://ru-sfera.pw/data/MetaMirrorCache/delfcode.ru__fr_12_2711448.png
Назовем такой генератор gen_call_op. Так как сейчас можно контролировать состояние регистров в генерируемых функциях. Можно строить более сложные конструкции. Например, генерировать несколько функций, и объединяю их в func4:
Код:
func1:
..
func2:
..
func3:
mov eax,12345678
…
func4:
call func1
trash1
call func2
trash2
call func3
trash3
И так можно сделать несколько слоев. Граф выполнения всего одной полезной команды выглядит так:
В этом графе полезная инструкция может быть на месте любой функции. Для данного графа функция, которая содержит инструкцию, обведена красным квадратом. Назовем такой генератор gen_tree_op.
И вот из таких инструкций (gen_tree_op) инфектор строит свой декриптор. По моему, выглядит «адски».
Не составит труда переделать его под x64, большинство инструкций отличаются лишь префиксом.
Свойства генератора:
- Данный способ позволяет генерировать инструкции практически любой сложности.
- Поиск полезных инструкций становится крайне затруднительным статическим анализом.
- Генерируемый код похожий на тот, который генерируют компиляторы.
- Не строит код, по которому можно было бы взять сигнатуру.
Шеллкод
Код, который выполняет полезную нагрузку и восстанавливает грабленные части файла: cвободно-релоцируемый и написан полностью на си. Обычно такой код пишется на ассемблере, возможно, для версии x64 придется его и использовать, но для x86 можно обойтись си. Принцип написания базонезависимого кода был взят из вируса 0x02 pr0m1x. Из критических изменений можно выделить поиск базы kernel32.dll. В моем коде ищется через PEB, перечисляются все имена загруженных dll и считается их хеш. Такой способ работает на всей линейке XP до WIN8.
Выводы:
- расширенный OEP усложняет детект вируса ав;
- первый и второй полиморфный декриптор усложняют детект вируса;
- второй полиморфный декриптор усложняет статический поиск необходимых значений для расшифровки остальных частей, что значительно осложнит лечение;
- минимум аномалий после заражения файла;
- файл сильно перестроен после заражения, что тоже должно сильно усложнить лечение.
Все эти характеристики должны создать головную боль аверам, и если это будет так, то все мои труды были потрачены не зря.