Пожалуйста, обратите внимание, что пользователь заблокирован
ДИСКЛЕЙМЕР.
Вся информация в этой заметке носит исключительно информативный характер.
СОДЕРЖАНИЕ
Вступление
Искусственный пример BOF и ошибки линкера
Unknown symbol `NTDLL$memmove`
Unknown symbols `.bss`
Unknown symbols `___chkstk_ms` и Unknown symbols `memset`
Вступление
Искусственный пример BOF и ошибки линкера
Если вы не знаете, что такое BOF, то рекомендую прочитать короткую статью из блога разработчика. Если коротко, то это скомпилированный COFF-файл, использующий Dymanic Function Resolution (DFR) для доступа к библиотечным функциям.
В блоге есть пример по типу "Hello world!", но если мы немного усложним программу, то начинаются веселые приключения. В одном примере я постараюсь описать ошибки, с которыми скорее всего будут сталкиваться люди и возможные варианты решения проблем. Рекомендую скопировать листинг в текстовый редактор, чтобы он был постоянно на виду.
На листинге выше я подробно прокомментировал практически каждую строчку кода, функции программы просты и не особо полезны, однако, в ней используются элементы языка, которые вызывают проблемы. Повторюсь, с аналогичными ошибками я столкнулся при портировании эксплойта. Кого-то может смутить, что я использую компилятор для c++, однако, его использование допустимо, что было проверено.
Содержимое файла Makefile выглядит следующим образом:
Теперь выполним BOF в сессии бекона при помощи команды

Ожидаемо, мы получили ошибки линкера в выводе, которые были умышленно созданы в примере выше. Давайте разбираться по порядку.
Unknown symbol `NTDLL$memmove`
Поскольку в BOF используется DFR, то мы должны особым образом объявлять прототипы функций. Если посмотрим на код выше, то увидим, что я объявил функцию
Что является неправильной формой записи. Корректно будет:
Люди, которые не знакомы с бесконечным количеством типов и дефайнов в Windows SDK, справедливо спросят: "Что такое DECLSPEC_IMPORT? Почему его нет там, где указан WINBASEAPI?".
В SDK Это выглядит следующим образом:
Т.е. WINBASEAPI и DECLSPEC_IMPORT - это просто макрос __declspec(dllimport).
Наиболее простым способом обнаружения этой ошибки на этапе компиляции будет просмотр таблицы релоков при помощи
На скриншоте ниже можно увидеть, что у

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

Unknown symbols `.bss`
Эта ошибка связана с тем, что (судя по всему) линкер CS не может парсить

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


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

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

В общем, линкер CS не сможет вызвать эти функции и проблема решается только использованием динамической памяти. Либо как-то еще.
Фикс и запуск BOF оставляю за читателем в качестве упражнения.
Ссылки:
github.com
Вся информация в этой заметке носит исключительно информативный характер.
СОДЕРЖАНИЕ
Вступление
Искусственный пример 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.
Ожидаемо, мы получили ошибки линкера в выводе, которые были умышленно созданы в примере выше. Давайте разбираться по порядку.
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 выдаст ошибку.
И после добавления DECLSPEC_IMPORT.

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

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

В общем, линкер CS не сможет вызвать эти функции и проблема решается только использованием динамической памяти. Либо как-то еще.
Фикс и запуск BOF оставляю за читателем в качестве упражнения.
Ссылки:
GitHub - vp777/Windows-Non-Paged-Pool-Overflow-Exploitation: Techniques based on named pipes for pool overflow exploitation targeting the most recent (and oldest) Windows versions demonstrated on CVE-2020-17087 and an off-by-one overflow
Techniques based on named pipes for pool overflow exploitation targeting the most recent (and oldest) Windows versions demonstrated on CVE-2020-17087 and an off-by-one overflow - vp777/Windows-Non-...