Вирус - это небольшой инфекционный агент, который размножается только внутри живых клеток организма × _ ×

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

ПРИМЕЧАНИЕ. Эта статья является продолжением серии - Этот волшебный ELF по разработке вредоносных программ, в которой мы впервые окунулись в мир двоичных файлов ELF.

Предпосылки

  • Понимание формата файла ELF обязательно.
  • Приличное количество навыков программирования на C вместе с концепциями системного программирования (связанными для быстрого изучения) под Linux очень поможет в понимании статьи в полной мере.
  • Кроме того, мы будем использовать структуры данных, определенные в elf.h (присутствующие в /usr/include/ каталоге вашей локальной файловой системы Linux) для программного доступа и управления частями двоичного файла хоста. Поэтому я призываю читателя пройтись по структурам. В качестве альтернативы на эти структуры данных можно ссылаться на странице 5 руководства Linux - $ man 5 elf.

Что мы собираемся делать в мире снайперов и ручных гранат?

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

В памяти программа размещается в виде сегментов (каждый сегмент составляет один или несколько разделов). Давайте посмотрим на таблицу заголовков программ (PHT) утилиты /bin/ls (которая перечисляет содержимое текущего каталога) -

Выше мы видим, что есть 2 загружаемых сегмента (помеченных как тип LOAD) с разрешениями на сегменты R-E (чтение-выполнение) и RW- (чтение-запись). Глядя на раздел для сопоставления сегментов, можно с уверенностью сделать вывод, что это сегменты КОД / ТЕКСТ и ДАННЫЕ соответственно.

После загрузки в память невинной программы сегменты выравниваются по страницам, но разделы (содержащие сегменты) редко выравниваются по границе страницы, тем самым оставляя пространство между текущим и соседним сегментом. Это пространство дополняется нулевыми байтами, когда двоичный файл загружается в память. На приведенном выше рисунке заполнение представлено серией P, что вызывает выравнивание сегментов. Мы будем использовать эту подкладку, чтобы предоставить паразиту место жительства, так как это, кажется, хорошее удобное укрытие. Однако бывают случаи, когда область заполнения меньше, чем размер паразита, и в этом случае этот метод заражения (также известный как заражение заполнением сегмента в мире UNIX и Обрушение кода в мире Windows) не сможет заразить этот двоичный файл. Ниже приведен алгоритм механизма заражения.

-x-x- Load parasite from file on-disk into memory                       
1. Get parasite_size and parasite_code address (location in allocated memory) 
                                                                    
-x-x- Find the padding_size (unused space) between CODE segment and the NEXT segment after CODE segment(usually data segment)                      
2. In the CODE segment PHT entry, increase the following-                         
-> p_filesz   (by parasite size)                         
-> p_memsz    (by parasite size)
Get and Set respectively,                         
-> padding_size  = (offset of next segment (after CODE segment)) - (end of CODE segment)                        
-> parasite_offset = (end of CODE segment) or (end of last section of CODE segment)
-> parasite_load_address = virtual address (vaddr) + sizeof CODE segment (filesz) 
-x-x- PATCH Host entry point                       
3. Save the original_entry_point of host binary.
4. Alter the host entry to point to the location parasite_offset/parasite_load_address (i.e. the location where parasite is to be injected into the host binary) 
                                                                     
-x-x- PATCH SHT                       
5. Find the last section in CODE Segment and increase -                                
-> sh_size    (by parasite size)
                                                                     
-x-x- PATCH Parasite offset                       
6. Find and replace Parasite jmp-on-exit address/offset placeholder with original_entry_point 0x????????????????   
                                                                  
-x-x- Inject Parasite to Host (mapped @ host_mapping)                       
7. Inject parasite code @ (host_mapping + parasite_offset), i.e. to the end of the last section (among all other sections in CODE segment)
8. Write the infection to disk ×_×

ПРИМЕЧАНИЕ. У этой точки заражения есть недостаток. Поскольку сегмент CODE не имеет прав на запись, самомодифицирующийся код паразита работать не будет.

Пусть КОД говорит сам за себя × _ ×

Имея все знания об ELF и наших черных толстовках, пришло время погрузиться в реальную реализацию алгоритма заражения на языке программирования C. Я рассмотрю исходный код модуля заражения Kaal Bhairav.

Пролог

Сначала объявим несколько переменных. Я надеюсь, что приведенных выше комментариев будет достаточно для ясного понимания того, что хранит переменная.

ElfParser() принимает параметр с именем filepath (путь к двоичному файлу хоста) и следует каждому шагу алгоритма заражения, координируя свои действия с другими служебными функциями.

  • (Строка 84) Абстрагируется использование mmap() для отображения двоичного файла хоста в память для дальнейшей модификации. Первый байт хоста сопоставляется @ location, указанный в host_mapping.
  • (Строка 86 - 90) Он переводит заголовок Elf двоичного файла хоста в host_header (тип struct Elf64_Ehdr, указанный в elf.h). Он анализирует типы ET_EXEC и ET_DYN (исполняемые и общие объекты) 64-битных двоичных файлов класса, пропуская ET_REL (перемещаемый), ET_CORE (ядро) или двоичные файлы класса ELFCLASS32 (32-разрядные двоичные файлы). Здесь ET_EXEC, ET_DYN, ET_REL, ET_CORE - это макросы, определенные в elf.h.
  • (Строка 93 - 94) Вызывает LoadParasite() , чтобы паразит попал в память, подготавливая его для внедрения в хост. Создание кода-паразита (который отличается как для исполняемого файла LSB, так и для типа разделяемого объекта) будет объяснено в следующей статье.
  • (Строка 96 - 101) Вызывает GetPaddingSize(), который получает размер заполнения (размер укрытия) и проверяет, может ли хост разместить паразита в заполнении.
  • (Строка 103 - 107) Сохраняет original_entry_point двоичного файла хоста и изменяет точку входа хоста в зависимости от местоположения, куда должен быть введен паразит ( parasite_offset и parasite_load_address настроены GetPaddingSize()). Расположение паразитов будет различным как для исполняемых файлов, так и для двоичных файлов общих объектов. В хосте ET_EXEC точка входа указывается адресом, тогда как в общих объектах (ET_DYN) точка входа указывается смещением, поскольку он содержит PIC (P osition I ndependent C оду).

  • (Строка 109 - 115) PatchSHT() - это наша служебная функция, которая вносит изменения в S ection H eader T способен вместить паразита. Затем мы используем FindAndReplace() для исправления адреса / смещения jmp-on-exit кода паразита с помощью original_entry_point двоичного файла хоста, после чего мы вводим паразита в хост через memcpy() и используем munmap() для отмены сопоставления двоичного файла и записи отображение зараженного хоста на диск.

mmapFile () - Сохранение хоста на расстоянии одной руки

Функция mmapFile() принимает путь к хосту в качестве параметра, отображает хост в адресное пространство текущего процесса и возвращает указатель на первый байт отображаемого двоичного файла хоста.

  • (Строка 317 - 321) Открывает двоичный файл хоста в режиме чтения-записи.
  • (Строка 323 - 328) lstat() для получения информации о хосте, в частности о размере двоичного файла хоста (размер на диске), который будет использоваться mmap (). Прочтите $ man stat.
  • (Строка 330 - 334) mmap() отображает двоичный файл хоста в виртуальное адресное пространство вызывающего процесса (в данном случае Kaal Bhairav). Хост отображается по адресу, выбранному ядром (указан NULL в качестве первого параметра для mmap). PROT_WRITE здесь описывает защиту памяти отображения, то есть страницы в отображении могут быть записаны. Флаг MAP_SHARED указывает, что обновления сопоставления будут отражаться в файле на диске, как только будут выполнены какие-либо изменения сопоставления (кроме того, предпочтительно использование msync ()). Прочтите $ man mmap.
  • (Строка 336 - 337) Наконец close() двоичный файл хоста и возврат адреса в первый байт отображаемого двоичного файла хоста.

LoadParasite () - Яд в памяти

Идея состоит в том, чтобы внедрить паразита в отображение хоста (для которого установлен флаг MAP_SHARED). Для этого код паразита должен находиться где-то в адресном пространстве процесса, чтобы он был доступен напрямую в нужное время. LoadParasite()which принимает путь к файлу, содержащему код паразита () в качестве параметра, и помещает паразита в память, определяя глобальные переменные parasite_size и parasite_code (которые хранят размер и местоположение кода паразита в объем памяти). Это происходит с помощью -

  • (Строка 270 - 282) Открывает файл, содержащий код паразита, в режиме только для чтения и использует lstat() (ранее использовавшийся в mmapFile()) для получения свойств файла паразита.
  • (Строка 284 - 290) Код получает размер паразита в parasite_size и выделяет parasite_size байты в сегменте кучи. через malloc(), который возвращает местоположение выделенного пространства в parasite_code. Адрес, возвращаемый malloc(), сохраняется в parasite_code типа int8_t * (указатель на значение байта).
  • (Строка 292 - 296). Наконец, мы выполняем read() системный вызов для чтения байтов parasite_size из parasite_fd (дескриптор файла-паразита, возвращаемый open()).

ПРИМЕЧАНИЕ. Мы полагаемся на сегмент кучи для загрузки кода паразита, поскольку он остается доступным до конца выполнения программы. Если мы загружаем код паразита в массив (пространство для которого выделяется в сегменте стека), он будет уничтожен вместе с кадром стека LoadParasite(), как только функция вернется (во время эпилога функции).

GetPaddingSize () - Это удобное убежище!

До этого момента мы отображали двоичный файл хоста в адресное пространство процесса, и наш паразитный код остыл в сегменте кучи. Затем нам нужно найти приспособление к отображению двоичного файла хоста, где наш паразитный код мог бы сидеть и выполняться беззвучно. Функция GetPaddingSize() анализирует P диаграмму H eader T (PHT) (отображаемого хоста @ host_mapping), чтобы найти размер заполнения между сегментом CODE и СЛЕДУЮЩИМ сегментом после сегмента CODE (обычно сегмент DATA) и возвращает размер заполнения (который позволяет нам узнать, может ли хост вместить паразита).

  • (Строка 211 - 213) Получает elf_header из host_mapping (который всегда начинается с 0-го смещения двоичного файла). Из заголовка elf он получает и сохраняет смещение PHT в переменной с именем pht_offset и количество заголовков программы (в PHT) в переменную с именем pht_entry_count.
  • (Строка 216) Устанавливает переменную с именем phdr_entry так, чтобы она указывала на первую запись в PHT, которая равна @ (host_mapping + pht_offset)
  • (Строка 217 - 219) Устанавливает для CODE_SEGMENT_FOUND значение 0, которое указывает, найден ли сегмент CODE или нет. Затем мы начинаем перебирать записи PHT, начиная с 0-го заголовка программы.
  • (Строка 221 - 225) Проверяет, равен ли CODE_SEGMENT_FOUND 0 (т. Е. Сегмент CODE еще не найден) и тип сегмента (заданный p_type атрибута struct Elf64_Phdrdefined в elf.h): PT_LOAD (то есть один из сегментов LOAD двоичного файла) и p_flags (Атрибут флагов разрешений заголовка программы) устанавливается на поразрядное ИЛИ PF_R (чтение) и PF_X (бит выполнения) чтение-выполнение (RX). Если все три условия верны, сегмент помечается как CODE seg. Патч-код паразита для незаметной передачи управления исходному кодированию путем установки для CODE_SEGMENT_FOUND значения 1.
  • (Строка 227 - 229) Здесь phdr_entry указывает на текущий заголовок программы, а конец сегмента задается добавлением p_filesz (размер сегмента на диске) до p_offset (смещение текущего сегмента). Поскольку мы хотим разместить код паразита в конце самого сегмента CODE, мы устанавливаем parasite_offset на code_segment_end_offset. Для исполняемых файлов LSB мы устанавливаем parasite_load_address (то есть место, в котором паразит будет найден во время выполнения двоичного файла хоста) равным (p_vaddr + p_filesz) сегмента CODE.
  • (Строка 231 - 232) На втором этапе алгоритма заражения мы увеличиваем filesz (размер сегмента на диске) и memsz (размер сегмента в памяти) атрибутов заголовка текущей программы на parasite_size. Это сделано для размещения паразита как на диске, так и в памяти.
  • (Строка 235 - 240). Это блок для сегмента DATA, который выполняется, если для CODE_SEGMENT_FOUND установлено значение 1 (т. Е. Сегмент CODE уже проанализирован. и изменен указанным выше кодом), а тип сегмента (атрибут p_type из struct Elf64_Phdrdefined в elf.h) установлен на PT_LOAD (т.е. загружаемый сегмент), а права доступа к сегменту установлены на Чтение-Запись (RW-). Он возвращает padding_size, который вычисляется путем вычитания смещения до конца сегмента
    CODE из смещения в сегмент DATA (т.е. p_offset (сегмента DATA) - code_segment_end_offset).
  • (Строка 242 - 245) Затем он увеличивает phdr_entry, чтобы указать на следующую запись заголовка программы. Если цикл перебирает все записи и поток кода каким-то образом достигает строки с номером 245, это означает, что мы не попали в блок if сегмента DATA и, следовательно, возвращает 0, что означает отсутствие места для кода паразита. .

ПРИМЕЧАНИЕ. Здесь PF_R, PF_W и PF_X - это макросы, которые описывают права доступа к сегменту и определены в /usr/include/elf.h. PF_X равно (1 ‹* 0), PF_W равно (1 ‹* 1) и PF_R равно (1 ‹› 2). Мы можем использовать эти флаги, выполняя побитовое ИЛИ над этими макросами, как описано в $ man 5 elf.

PatchSHT () - Да здравствует наш паразит!

Мы нашли место для паразита внутри хозяина, но как нам избавиться от его незащищенности по поводу того, что нас не выбросят из найденного нами убежища. Существуют инструменты, присущие двоичным файлам (например, strip), которые не хотят, чтобы дополнительный контент (то есть что-либо кроме того, что необходимо для выполнения программы) оставался внутри двоичного файла. Strip отбрасывать символы, отладочную информацию и части двоичного файла, которые не влияют на выполнение программы. Такие инструменты, как strip, часто используются разработчиками программного обеспечения для уменьшения размера двоичного файла (перед выпуском его в производство), а также для того, чтобы усложнить жизнь реверс-инженерам. Наряду с этим он удаляет любой код / ​​данные, которые не попадают ни в один раздел, что кажется нашему паразиту пугающим. Чтобы сделать наш будущий троянизированный хост защищенным от взлома, нам нужно будет проанализировать таблицу заголовков разделов (SHT) двоичного файла и увеличить размер последней части сегмента CODE на parasite_size, чтобы обмануть strip в полагая, что паразит является частью секции.

  • (Строка 145 - 150). Она устанавливает sht_offset (смещение для таблицы заголовка раздела) и sht_entry_count (количество записи в SHT) из заголовка elf двоичного файла хоста. Определяет section_entry (который имеет тип Elf64_Shdr *defined в elf.h) для 1-й записи SHT.
  • (Строка 153 - 162). Итерируя по SHT, мы проверяем, равно ли смещение до конца текущего раздела (current_section_end_offset) со смещением до конец сегмента кода (code_segment_end_offset). Если это последний раздел сегмента CODE, увеличьте его sh_size для размещения кода паразита. Позже увеличьте указатель section_entry, чтобы он указывал на следующую запись в SHT.

FindAndReplace () - Исправление паразита

Когда злоумышленник заражает двоичный файл, он намеревается захватить поток кода двоичного файла, чтобы заставить его делать то, для чего он не был предназначен (трюки, выполняемые паразитом). Часть процесса, связанная с захватом, может осуществляться с помощью различных методов заражения, в зависимости от точки заражения, используемой автором вредоносной программы. Это можно сделать до выполнения main () (с помощью техники модификации точки входа или перехвата конструкторов), между выполнением main () (путем перехвата библиотечных функций - Отравление GOT или перенаправление PLT / GOT, заражение через трамплины функций), а также может быть выполнено после выполнения main () (захват деструкторы). Здесь мы сосредоточились на первом из этих методов, то есть на модификации точки входа для перехвата потока кода двоичного файла хоста.

После того, как мы захватим поток кода и передадим поток кода паразиту, паразит несет ответственность за молчаливую передачу потока кода обратно на предполагаемое выполнение кода двоичного файла хоста, чтобы код выполнялся молча, не создавая шума. Для этого нам нужно как-то сообщить паразиту, куда ему нужно передать поток кода после выполнения. Наша служебная функция - FindAndReplace() выполняет эту задачу за нас. Он заменяет значение заполнителя (внутри кода паразита) на исходную_центру_по_объекта двоичного файла хоста. Называя это - FindAndReplace(parasite_code, 0xAAAAAAAAAAAAAAAA, original_entry_point);.

  • (Строка 125 - 128) Инициализирует указатель uint8_t *ptr на паразита и перебирает каждый байт кода паразита.
  • (Строка 130 - 135) В x86–64-битных архитектурах адрес памяти занимает 8 байтов в памяти. Помня об этом, current_QWORD - это переменная длинного типа, в которой хранится 8-байтовое значение. if statement сравнивает значения, выполняя XOR для find_value и current_QWORD (1 XOR 1 равно 0) , так что он перезаписывает 8 байтов заполнителя с replace_value и возвращают (т. Е. original_entry_point)

ElfParser: ввести код паразита и отключить двоичный файл

В ElfParser(), memcpy() копируются байты parasite_size из местоположения parasite_code (в сегменте кучи) в (host_mapping + parasite_offset), который является приспособлением для паразита. munmap() отключает двоичный файл хоста из адресного пространства процесса Kaal Bhairav, и заражение записывается на диск.

Мой любимый бинарник болен

Достаточно больно, чтобы проявлять резкое поведение (по крайней мере). Процесс заражения не завершится, пока мы не научимся создавать паразитов. Паразит - это то, что завершает заражение, определяя, что добро или зло происходит после того, как мы захватим поток кода хоста. В следующей статье мы обсудим дизайн паразита, который различается для двоичных файлов типа ET_EXEC и ET_DYN (то есть как для исполняемых файлов LSB, так и для общих объектов).
Полный исходный код для elf Infctor можно найти здесь.

Ура,
Абхинав Тхакур
(он же compilepeace)

Github: https://github.com/compilepeace
Linkedin: https://www.linkedin.com/in/abhinav-thakur- 795a96157 /