451: Use of data in viruses
Use of data in viruses
GETD v 1.0
Recently, the development of permutation has achieved quite a lot of success. Today you can easily "disassemble" the code of a virus / program into something similar to the source code, although in a form that is more pleasant for processing. Then, you can modify it by mutating, introducing garbage, generating false branches, shuffling the code, and other tricks. After that, the "source" is assembled again and the resulting code has a completely different signature.
From the point of view of a heuristic antivirus that determines whether a certain code belongs to a certain permutator (engine), it is important to analyze the commands received as input and then discard the engine variants that do not contain them. This action gives an increase in the speed of detection, and, consequently, scanning. Because the number of entries in the today's antivirus databases is growing rapidly, such a criterion when buying an antivirus (the speed of scanning) will only strengthen its position. Although all this is purely subjective, as the computing power of computers is also growing.
Well, go to the hell with it.
Practice has shown that the most bottleneck of the permutation engine is the mutating part. It is rather difficult to implement. For perfect mutation (ie mutating almost ALL instructions) it is necessary to know the entire structure of the program, and this will already entail complete reversing and emulation. Moreover, it is necessary to write code that mutates each command separately, because each of them has its own characteristics, which increases the size of the engine and the virus as a whole.
Next comes the garbage generator. It is easier to implement for several reasons:
- Initial adjustment of the code to the generator, i.e. with a known code behavior.
- Examining the code for data/flags/registers usage and subsequent code generation.
In the second case, a complete analysis of the operation of the code is necessary. Most likely, some part of the 1st option will be used.
What is the most important thing in the garbage generator? Of course, the set of generated commands and the number of options for each command. Junk instructions mainly work with registers and the stack, even using addressing, they are variants of commands for working with registers. Often this is a LEA command, due to its effect on flags, i.e. their complete absence ;)
lea ebx,[ebx+0]
But back to the heuristic emulator, although it was deservedly sent to a place where it will be truly appreciated :) From his point of view, these commands are easy to discard. Firstly, they do not change the contents of registers/flags, but if this is not the case, then the commands filter by the mask and "discard" the current versions of the generators that can create them, thereby speeding up scanning, raising the "speed prestige" of the heuristic antivirus itself.
The reason is that each generator has a static set of supported commands that it generates. To complicate the filtering of commands, a method can be used, which will be described here.
DATA
Every serious program uses data: bytes, words, dwords, arrays...
So it is logical to assume that these same "dwords" after loading the program into memory have their own address so that the program itself can access them. So why not take advantage of them in a virus? It is possible to refer to the data of an infected program, both by reading and by writing. This method allows you to generate commands that access absolute memory. What does it give?
- Commands access memory, i.e. they are more difficult to filter out as garbage. the call can go to read/write.
- Use of program data in permuting viruses to store their structures and other things. (Probably one of the ways that can facilitate the writing of permuting viruses).
- A more general view of the virus code, "by eye" similar to the program code. :)
GETD
To find the data accessed by a program, GETD was written.
The input is a pointer to the already read PE file, the output is a pointer to the data table. To get the data used by the program, in theory, you need to:
- check file for validity (very general concept ;)
- disassemble fixups
- check them for relevance to imports and code
- disassemble the code from the entry point in order to identify the addresses of the commands that access the data, and the size of the data itself (1/2/4).
Based on the results obtained, a table is generated containing records consisting of the data address, command address, command size, data size and access type (read / write):
getd_node struc
getd_dRVA dd ?
getd_cRVA dd ?
getd_cSize dd ?
getd_cdType dd ?
ends
In the last field, the first byte is equal to the type of access (read - 0 / write -1 ), the second is equal to the size of the data.
The first part of the table indicates the number of elements, then comes the table itself.
As a side result, a table of memory regions is created, i.e. consecutive data. It follows immediately after the data table and has the following structure elements:
getd_reg_node struc
getd_regionRVA dd ? ; RVA of the region
getd_regionCnt dd ? ; number of 4-byte blocks
ends
This table is convenient to use for organizing junk commands that work with chained commands: movs, scas, stos, cmps, lods, using repetition prefixes.
The general structure of the output table is shown below:
-------------------------------------------
0000: table size #1 N
-------------------------------------------
... elements
... .....
-------------------------------------------
N*16: table size #2 M
-------------------------------------------
... elements
... .....
-------------------------------------------
In case of error, GETD returns 0 instead of the pointer to the tables.
HOW TO USE IT?
There may be many applications for this, but so far I see only 3: garbage generators, mutators and data in permuting viruses. I would like to dwell a little more on the garbage generator, which should use this principle.
Let's say that the engine worked and spat out a couple of hundred addresses that were filtered out and passed to the garbage generator. If you want to use writing to data, which is most likely, then you must either remember their previous values and then restore them, which is pointless, or reverse the entire program to detect blocks with commands for accessing data for writing. But reversing is not the only way out, you can simply find an element with a record in the data, the data address is passed to the garbage generator, and it only uses it. Further, this command is assigned CALL/JMP/PUSH/RET to the entry point to the virus or something else, while preserving the original bytes of the program, i.e. it turns out also UEP.
Another option is splicing ExitProcess on imports or simply writing the transition to your code directly into the program, which is also not always suitable. Or restarting the process, but this will require a loader that does not have access to data and already contains a fair amount of the virus (partly the import procedure), which will weaken the decryptor for detection.
Further, if the virus is completely non-permutable (only the decryptor is permuted), then nothing else is needed. Otherwise, the situation is called full-mophs: when infected from a new instance, the data used in that instance will be passed on to the next one, and that one will fuck with it when it starts up. Therefore, it is simply necessary, when recompiling commands in the permutator, to discard commands with an address greater than imagebase. The heuristic antivirus or the person studying the code of the program does not know what addresses we use and whether we use them at all, but we know that we can, and that’s all;). Along with the garbage, virus commands will also fly off, which is also not good, although it also depends on its algorithm.
It also does not hurt to completely kill the garbage accessing the data at some stage in order to complicate detection again, but this should be done rarely and, of course, randomly.
You can do it ... and a lot more is possible.
original russian text
Использование данных в вирусах
GETD v 1.0
В последнее время развитие пермутации достигло достаточно больших успехов- теперь можно без проблем "разобрать" код вируса/программы на что-то подобное исходнику, хотя и в более приятном для обработки виде. Затем модифицировать его путем мутации, внесения мусора, генерации ложных ветвей, перемешиванием кода и других приемов. После этого "исходник" снова ассемблируется и полученный код уже имеет совершенно иную сигнатуру.
С точки зрения эвристика, определяющего принадлежность некого кода к какому-то определенному пермутатору (движку), важно просмотреть полученные на входе команды и после этого отбрасывать варианты движков, не содержащие их. Данное действие дает прирост в скорости детектирования,а, следовательно, и сканирования. А т.к. уже сейчас количество записей в базах антивирусов стремительно растет, то такой критерий при покупке антивируса, как скорость сканирования будет только укреплять свои позиции. Хотя все это сугубо субъективно, поскольку вычислительная способность компьютеров тоже растет.
Ну да к хуям это.
Практика показала, что наиболее узкими местами пермутационного движка является мутирующая часть. Она достаточно сложна в реализации т.к. для идеального мутирования (т.е. мутирования почти ВСЕХ команд) необходимо знать всю структуру программы, а это уже повлечет за собой полный реверсинг и эмуляцию. Мало того, необходимо написание кода, мутирующего отдельно каждую команду, потому как каждая оная имеет свои особенности, что повышает размеры движка и вируса вцелом.
Следующим идет генератор мусора. Его проще реализовать по нескольким причинам:
- Изначальной подгонкой кода под генератор, т.е. с известным образом действия кода.
- Исследование кода на использование данных/флагов/регистров и последующая генерация кода.
Во втором случае необходим полный анализ действия кода. Скорее всего, будет использоваться некая часть 1-го варианта.
Что же самое главное в генераторе мусора? Конечно же, набор генерируемых команд и количество вариантов каждой команды. Мусорные команды главным образом работают с регистрами и стеком, даже используя адресацию, они являются вариантами команд работы с регистрами. Часто это команда LEA,из-за ее действия на флаги, т.е. полное их отсутствие ;) :
lea ebx,[ebx+0]
Но вернемся к эмулятору-эвристику, хотя он был вполне заслуженно отправлен в место, где его по-настоящему оценят :). С его точки зрения эти команды легко отбросить. Во-первых, они не изменяют содержимое регистров/флагов, если же это не так, то команды фильтруют по маске и "отбрасывают" текущие варианты генераторов, которые могут их создать, ускоряя тем самым сканирование, поднимая "скоростной престиж" самого эвристика.
Причина в том, что каждый генератор характеризуется статичным набором поддерживаемых команд, которые он и генерирует. Для усложнения фильтрации команд может использоваться метод, о котором тут будет написано.
ДАННЫЕ
Каждая серьезная программа использует данные: байты, ворды, дворды, массивы...
Так вот логично предположить, что эти самые "дворды" после загрузки программы в память имеют свой адрес, чтобы программа сама могла обращаться к ним. Так почему бы ни воспользоваться ими в вирусе ? Т.е. можно на ссылаться на данные инфицированной программы, как посредством чтения, так и посредством записи. Этот метод позволяет генерировать команды, обращающиеся к абсолютной памяти. Что это дает?
- Команды обращаются к памяти, т.е. их труднее отфильтровать, как мусор т.к. обращение может идти на чтение/запись.
- Использование данных программы в пермутирующих вирусах для хранения их структур и прочего. (Наверное, один из способов способный облегчить написание пермутирующих вирусов).
- Более общий вид кода вируса, "на глаз" похожий на код программы. :)
GETD
Для нахождения данных, к которым обращается программа, был написан GETD.
На вход подается поинтер на считанный уже PE-файл, на выходе - поинтер на таблицу данных. Чтобы получить данные, использующиеся программой, по идее необходимо:
- проверить файл на валидность (очень общее понятие;)
- разобрать фиксапы
- проверить их на отношение к импорту и коду
- дизассемблировать код с точки входа с целью выявления адресов команд, которые обращаются к данным, и размера самих данных (1/2/4).
Исходя из полученных результатов, формируется таблица, содержащая записи, состоящие из адреса данных, адреса команды, размера команды, размера данных и тип обращения (чтение/запись):
getd_node struc
getd_dRVA dd ?
getd_cRVA dd ?
getd_cSize dd ?
getd_cdType dd ?
ends
В последнем поле первый байт равен типу обращения (чтение - 0 /запись -1 ), второй - размеру данных.
Первым двордом в таблице указывается количество элементов, затем идет сама таблица.
Как побочный результат формируется таблица регионов памяти, т.е. подряд располагающихся данных. Она следует сразу за таблицей данных и имеет элементами вот такие структуры:
getd_reg_node struc
getd_regionRVA dd ? ; RVA региона
getd_regionCnt dd ? ; количество 4-байтных блоков
ends
Данную табличку удобно использовать для организации мусорных команд, работающих с цепочечными командами : movs, scas, stos, cmps, lods, использующих префиксы повторения.
Общая стуктура выходной таблицы показана ниже:
-------------------------------------------
0000: размер таблицы #1 N
-------------------------------------------
... элементы
... .....
-------------------------------------------
N*16: размер таблицы #2 M
-------------------------------------------
... элементы
... .....
-------------------------------------------
В случае ошибки ,GETD вместо поинтера на таблицы ,возвращает 0.
КАК ЭТО ИСПОЛЬЗОВАТЬ?
Применений этому может и много, но я вижу пока только 3 - генераторы мусора, мутаторы и данные в пермутирующих вирусах. Хотелось бы еще немного остановиться на генераторе мусора, который должен использовать этот принцип.
Допустим, что движок отработал и выплюнул пару сотен адресов, которые отфильтровались и были переданы генератору мусора. Если хочется использовать запись в данные,что наиболее вероятно, то надо либо запоминать их предыдущие значения, а затем восстанавливать, что есть бессмысленно, либо реверсить всю программу на предмет обнаружения блоков с командами обращения к данным на запись. Но реверсить это не единственный выход, можно просто найти элемент с записью в данные, адрес данных передается генератору мусора, и он использует только его. Далее на эту команду ставится CALL/JMP/PUSH/RET на точку входа в вирус или что-то еще, с сохранением оригинальных байтов программы, т.е. получается еще и UEP.
Другой вариант - сплайсинг ExitProcess на импортах или же просто записью перехода на свой код прямо в программу, что тоже не всегда подходит. Либо же перезапуск процесса, но для этого потребуется лоадер, не имеющий обращения к данным и уже содержащий изрядный кусок вируса (отчасти процедуры импорта), что ослабит декриптор на предмет детектирования.
Далее, если вирус является полностью непермутирующим (пермутируется лишь декриптор), то ничего больше не надо. Иначе дело обстоит в т.к. называемых full-moph'ах: при инфицировании из нового экземпляра, данные, используемые в этом экземпляре перейдут к следующему и тот при запуске ебанется на них. Поэтому надо просто, при рекомпиляции команд в пермутаторе, отбрасывать команды с адресом большим imagebase. Это эвристик или исследующий программу не знает про то, какие адреса мы используем и используем ли вообще, но мы-то знаем, нам можно, причем все ;).Но следует чуть поизвращать исходник вируса, чтобы он не обращался напрямую в память, а то вместе с мусором слетят и вирусные команды, что тоже нехорошо, хотя это зависит и от его алгоритма.
Не помешает также полное убивание мусора обращающегося к данным на некотором этапе, чтобы снова усложнить детектирование, но делать это надо редко и, естественно, рандомно.
А еще можно... а много чего еще можно.