• XSS.stack #1 – первый литературный журнал от юзеров форума

C&C [ ЗАМЕТКА 3] Ошибки, связанные с линкером Cobalt Strike 4.3 при использовании BOF

varwar

El Diff
Забанен
Регистрация
12.11.2020
Сообщения
1 383
Решения
5
Реакции
1 537
Пожалуйста, обратите внимание, что пользователь заблокирован
ДИСКЛЕЙМЕР.
Вся информация в этой заметке носит исключительно информативный характер. :)

СОДЕРЖАНИЕ
Вступление
Искусственный пример BOF и ошибки линкера
Unknown symbol `NTDLL$memmove`
Unknown symbols `.bss`
Unknown symbols `___chkstk_ms` и Unknown symbols `memset`

Вступление
В этой заметке я хотел бы описать некоторые нюансы написания BOF (Beacon Object File) применительно к написанию offensive-утилит для Cobalt Strike, в том числе ограничения с которыми столкнулся при портировании ядерного эксплойта для CVE-2020-17087. Ранее я уже публиковал статью с бинарным анализом патча для этой уязвимости, которая представляет собой переполнение в невыгружаемом пуле ядра (Non paged pool). Спустя некоторое время была реализована рабочая техника для эксплуатации данного типа уязвимостей на версиях Windows 10, использующих Segment Heap в ядре. Исследование и эксплойт вы можете найти по ссылке в конце статьи. Все эксперименты я проводил на виртуальной машине Windows 10 20H2.19042.572.

Этот текст навеян исключительно любопытством к инструменту, который, бесспорно, является популярным не только у киберпреступников.

Посвящается всем неравнодушным и буду очень благодарен за любые замечания.​

Искусственный пример BOF и ошибки линкера
Если вы не знаете, что такое BOF, то рекомендую прочитать короткую статью из блога разработчика. Если коротко, то это скомпилированный COFF-файл, использующий Dymanic Function Resolution (DFR) для доступа к библиотечным функциям.
В блоге есть пример по типу "Hello world!", но если мы немного усложним программу, то начинаются веселые приключения. В одном примере я постараюсь описать ошибки, с которыми скорее всего будут сталкиваться люди и возможные варианты решения проблем. Рекомендую скопировать листинг в текстовый редактор, чтобы он был постоянно на виду.

C++:
#include <windows.h>
#include <stdio.h>
#include <winternl.h>

#ifdef __cplusplus
/*
 * МЫ ИСПОЛЬЗУЕМ EXTERN "C", ЧТОБЫ ИЗБЕЖАТЬ C++ NAME MANGLING,
 * ПОТОМУ ЧТО ЛИНКЕР COBALT STRIKE НЕ ПОДДЕРЖИВАЕТ ЭТУ ФИЧУ
*/
extern "C" {
#endif
    // Инклюдим заголовочный файл CS для получения доступа к его внутренним
    // функциям; прим: BeaconPrintf и т.д.
#include "beacon.h"
    // Мы должны объявить EP (Entry Point) здесь, в противном случае линкер CS вернет ошибку
    int go();
    int BigStack();
    // Глобальная неинициализированная переменная, которая будет размещена в
    // .bss секции. CS НЕ МОЖЕТ корректно парсить .bss.
    int g_Var1;
    // Глобальная инициализированная переменная, которая будет расположена в
    // секции .data. Линкер CS МОЖЕТ корректно парсить .data.
    const int g_Var2 = 1;
    // Глобальная неинициализированная переменная, которая будет размещена в
    // .rdata секции. Линкер CS МОЖЕТ корректно парсить .rdata.
    const int g_Var3;

    WINBASEAPI HANDLE WINAPI KERNEL32$GetProcessHeap (VOID);
    DECLSPEC_IMPORT PVOID NTAPI NTDLL$RtlAllocateHeap(PVOID HeapHandle, ULONG Flags, SIZE_T Size);
    DECLSPEC_IMPORT BOOLEAN NTAPI NTDLL$RtlFreeHeap(PVOID HeapHandle, ULONG Flags, PVOID HeapBase);
    // Мы должны объявить прототип функции при помощи DECLSPEC_IMPORT т.к.
    // в противном случае линкер CS вернет ошибку <Unknown symbol>.
    void *__cdecl NTDLL$memmove(void *Destination, const void *Src, size_t Size);
    DECLSPEC_IMPORT WINBASEAPI FARPROC WINAPI KERNEL32$GetProcAddress (HMODULE hModule, LPCSTR lpProcName);
    WINBASEAPI HMODULE WINAPI KERNEL32$LoadLibraryA (LPCSTR lpLibFileName);
    DECLSPEC_IMPORT void * __cdecl NTDLL$memset(void *_Dst,int _Val,size_t _Size);
}

// BOF EP
int go()
{
    char buf[] = "Some data";
    HANDLE hHeap;
    PVOID hc1, hc2;
    // Получаем хэндл кучи текущего процесса
    hHeap = KERNEL32$GetProcessHeap();
    // Выделяем 32 байта для первого чанка
    hc1 = NTDLL$RtlAllocateHeap(hHeap, 0, 0x20);
    // Инициализируем чанк кучи
    NTDLL$memset(hHeap, 0x41, 0x20);
    // Выводим адрес чанка оператору CS
    BeaconPrintf(CALLBACK_OUTPUT, "Allocation address of the hc1  = %p\n", hc1);
    // Копируем строку в качестве примера
    NTDLL$memmove(hc1, buf, sizeof(buf));
    // Еще одна бесполезная операция копирования
    NTDLL$memmove(hc1, &g_Var1, sizeof(g_Var1));
    // Освобождаем первый чанк
    NTDLL$RtlFreeHeap(hHeap, NULL, hc1);
    // Выделяем 32 байта для второго чанка, инициализированного нулем
    hc2 = NTDLL$RtlAllocateHeap(hHeap, HEAP_ZERO_MEMORY, 0x20);
    // Выводим адрес чанка оператору CS
    BeaconPrintf(CALLBACK_OUTPUT, "Allocation address of the hc2 = %p\n", hc2);
    // Освобождаем второй чанк
    NTDLL$RtlFreeHeap(hHeap, NULL, hc2);
    // Вызываем функцию BigStack
    BigStack();
    return 0;
}

int BigStack()
{
    // Массив на стеке увеличивает фрейм функции > PAGE_SIZE, что приводит к
    // вызову хелпер-функции ___chkstk_ms и в ряде случаев еще memset, которую
    // не может обработать линкер CS. Мы должны использовать динамическую
    // память, чтобы избежать этого (если такая возможность имеется)
    int stack_arr[0x1000] = {0};
    return 0;
}

На листинге выше я подробно прокомментировал практически каждую строчку кода, функции программы просты и не особо полезны, однако, в ней используются элементы языка, которые вызывают проблемы. Повторюсь, с аналогичными ошибками я столкнулся при портировании эксплойта. Кого-то может смутить, что я использую компилятор для c++, однако, его использование допустимо, что было проверено.

Содержимое файла Makefile выглядит следующим образом:

Код:
BOFNAME :=  BOFTEST
CC_64   :=  x86_64-w64-mingw32-g++-win32

all:
        $(CC_64) -o $(BOFNAME).x64.o -c test.cpp -nostdlib --entry=go -masm=intel  -fpermissive

Теперь выполним BOF в сессии бекона при помощи команды inline-execute.

bof4.png

Ожидаемо, мы получили ошибки линкера в выводе, которые были умышленно созданы в примере выше. Давайте разбираться по порядку.

Unknown symbol `NTDLL$memmove`
Поскольку в BOF используется DFR, то мы должны особым образом объявлять прототипы функций. Если посмотрим на код выше, то увидим, что я объявил функцию memmove как:

C++:
void *__cdecl NTDLL$memmove(void *Destination, const void *Src, size_t Size);

Что является неправильной формой записи. Корректно будет:

C++:
DECLSPEC_IMPORT void *__cdecl NTDLL$memmove(void *Destination, const void *Src, size_t Size);

Люди, которые не знакомы с бесконечным количеством типов и дефайнов в Windows SDK, справедливо спросят: "Что такое DECLSPEC_IMPORT? Почему его нет там, где указан WINBASEAPI?".
В SDK Это выглядит следующим образом:

C++:
#define DECLSPEC_IMPORT __declspec(dllimport)
#define WINBASEAPI DECLSPEC_IMPORT

Т.е. WINBASEAPI и DECLSPEC_IMPORT - это просто макрос __declspec(dllimport).
Наиболее простым способом обнаружения этой ошибки на этапе компиляции будет просмотр таблицы релоков при помощи objdump.
На скриншоте ниже можно увидеть, что у memmove нет префикса __imp_, как у других функций. Это простой визуальный индикатор того, что линкер CS выдаст ошибку.

bof5.png

И после добавления DECLSPEC_IMPORT.

bof6.png

Unknown symbols `.bss`
Эта ошибка связана с тем, что (судя по всему) линкер CS не может парсить .bss секцию и неинициализированные данные в ней. В нашем примере ошибку вызвала переменная g_Var1. Это легко также можно увидеть в дизассемблере, открыв вкладку Segments и нажав на .bss. В рабочем BOF секция .bss должна быть пустой.

bof7.png

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

bof8.png

bof9.png

Unknown symbols `___chkstk_ms` и Unknown symbols `memset`
Я объединил эти две ошибки, т.к. они возникают вместе в результате увеличения стекового фрейма функции. В нашем примере это происходит в функции BigStack. Это накладывает серьезные ограничения на использование BOF, а устранение этой ошибки может быть подчас невозможным. Функции ___chkstk_ms и memset добавляются автоматически компилятором. В дизассемблере функция BigStack выглядит следующим образом:

bof3.png

Также, компилятор почему-то увеличивает стек в 4 раза. Т.е. вместо ~ 4кб фрейм стека будет ~16 кб. Я не знаю с чем это связано.

bof2.png

В общем, линкер CS не сможет вызвать эти функции и проблема решается только использованием динамической памяти. Либо как-то еще.
Фикс и запуск BOF оставляю за читателем в качестве упражнения.

Ссылки:
 
Пожалуйста, обратите внимание, что пользователь заблокирован
Unknown symbols `.bss`
Эта ошибка связана с тем, что (судя по всему) линкер CS не может парсить .bss секцию и неинициализированные данные в ней. В нашем примере ошибку вызвала переменная g_Var1. Это легко также можно увидеть в дизассемблере, открыв вкладку Segments и нажав на .bss. В рабочем BOF секция .bss должна быть пустой.
Удалось побороть эту ошибку при помощи опции компоновщика -fno-zero-initialized-in-bss. В таком случае g_Var1 кладется в .data. Однако, есть нюанс. Переменная должна быть инициализирована нулем.

C:
// Без опции -fno-zero-initialized-in-bss помещается в .bss
int g_Var1 = 0;
// Без опции -fno-zero-initialized-in-bss помещается в .bss
int g_Var1;
//  C -fno-zero-initialized-in-bss  помещается в .bss
int g_Var1;
// C  -fno-zero-initialized-in-bss  помещается в .data
int g_Var1 = 0;

Вопрос с Unknown symbols `___chkstk_ms` и Unknown symbols `memset` остается открытым.
 
Пожалуйста, обратите внимание, что пользователь заблокирован
Вопрос с Unknown ... остается открытым
дааа. Действительно.
спс за статью, многим полездно.
 
Пожалуйста, обратите внимание, что пользователь заблокирован
спс за статью, многим полездно.
Пожалуйста. Если не троллишь конечно.
 


Напишите ответ...
  • Вставить:
Прикрепить файлы
Верх