В статье будет продемонстрирован пример работы с так скажем инструментарием Dittobytes, он делает код метаморфным, при каждой компиляции. Метаморфный код простыми словами это усиленный вид обфускации, где код принимает различные трансформации и преобразования в области регистров и не только не изменяя правила запуска программы.
Пример работы Dittobytes на скрине ниже:
Смотрим на пример с данным куском кода и видим что, dittobytes:
1. Меняет номер регистров
2. Изменяет кол-во байт на резерв
3. Использует различные приемы обнуления
4. Смещает переменную по другому принципу
Сам смысл понятен, изменение приемов компиляции дефолт кода не затрагивая саму логику. Такой результат получился с помощью кастомной сборки LLVM с двумя транспайлерами.
Но в смысле Dittobytes, используются 2 транспайлера, они не используются в обычном понимании ЯП -> ЯП. 1 Транспайлер отвечает за изменение в IR (Intermediate Representation). IR это такой промежуточный процесс компиляции, то есть компилятор не сразу переводит код ЯП в машинный код, а проходит через IR
То-есть в 1 транспайлере упор идет на позиционно-НЕЗАВИСИМОСТЬ(PIC) кода
2 Транспайлер представляет собой манипуляции с регистрами, то что мы и рассмотрели выше
"Второй — машинный транспилятор, использующий устаревший проход функций LLVM для выполнения метаморфических преобразований"
(https://github.com/tijme/dittobytes?tab=readme-ov-file#-metamorphications)
В общем и целом Dittobytes собран из LLVM, 2 транспайлеров, ну и каких то своих встроенных плагинов. Как раз, все эти компоненты вместе создают метаморфный код.
1. Если вы юзер Windows скачиваем Docker Desktop(для удобства). Если же у вас Linux все тоже самое.
2. Я буду использовать Windows, после установки докера нужно установить Git если у вас его нет
Windows
Linux
Запускаем Docker, и начинаем клонировать репозиторий Dittobytes
Создаем нужную вам папку, и копируем туда Dittobytes
Далее перейдем в каталог для работы с ним
Были 2 проблемы которые у меня возникли при установке.
У меня падал билд с ошибкой из-за slim образа, а именно из-за его ограниченности
Проблема решилась скачиванием дополнительных системных пакетов, так же по желанию очищаем кэш. В dittobytes/Dockerfile заменяем:
на:
После чего у меня все заработало, но была еще одна проблема. Возможно проблемы у меня с провайдером, либо же Debian не долюбливает РУ IP))) После начала билда, у меня не отвечал дебиан, хотя проверил через ping у меня возвращало пакеты с их сервером, возможно проблема чем то другом, вот что мне выводило в консоли
Использовать зеркала //mirror.yandex не помогло, так-как у яндека есть не все поддомены, поэтому у меня скачивались не все пакеты
Пришлось прокинуть HTTP прокси через Docker для установки и все заработало.
Запускаем PowerShell и установить прокси для билда
docker build --build-arg http_proxy=PROXY --build-arg https_proxy=PROXY -t dittobytes .\
Кстати хочу подметить что смена DNS в самом докере и проксирование всего трафика даже через proxychains4 или Proxifier не помогло, только команда выше, после чего у меня все заработало
Сама установка у меня заняла около часа, хотя в гитхабе у создателя была информация что по обычно времени отнимает около 2.5 часа.
И сравним в Ghidra дизассемблированный варианты через обычный компилятор и Dittobytes
На скрине все понятно и видно, например те же байты с shellcode-а
Все понятно и наглядно, теперь скомпилим через Dittobytes
Запуск контейнера
или:
Запуск компиляции
Теперь можно закинуть в Ghidra
Явно видно что не находит исходную функцию main(). Единственная точка входа - entry
Видно обфусцированный код, который разделен по кускам, собирается и работает благодаря кастомному LLVM. Сам код становится вообще невозможен к сигнатуре.
Как правильно вызвать функцию из .dll
1. Получаем PEB, для точки входа
2. Парсинг списка модулей через PEB
3. PE парсинг Export Directory каждой DLL, для поиска функций по имени
4. Получение LoadLibraryA/GetProcAddress для загрузки API
5. Динамическая загрузка нужных модулей и функций во время выполнений
Закидываем готовый калькулятор в Ghidra и наблюдаем тоже самое. Обфускацию и метаморфный код! Вот например:
Наблюдаем что LoadLibraryA не хранится в бинарнике, вместо этого в регистры загружены две 64-битные константы, являющийся зашифрованными
После всех примеров, можно точно сказать что Dittobytes отлично обфусцирует код и делает его метаморфным, что и требовалось доказать.
Не удалось прогрузить медиа
Пример работы Dittobytes на скрине ниже:
Не удалось прогрузить медиа
Смотрим на пример с данным куском кода и видим что, dittobytes:
1. Меняет номер регистров
2. Изменяет кол-во байт на резерв
3. Использует различные приемы обнуления
4. Смещает переменную по другому принципу
Сам смысл понятен, изменение приемов компиляции дефолт кода не затрагивая саму логику. Такой результат получился с помощью кастомной сборки LLVM с двумя транспайлерами.
Что такое LLVM и транспайлер.
Быстро пройдемся по этим терминам что бы иметь общую картину, LLVM это так скажем инфраструктура для разработки компиляторов, включающий в себя модификации и утилиты, то есть вы можете экспериментировать как угодно с генерацией кода. Транспайлер простыми словами это компилятор который берет и переводит код с одного ЯП на другой.Но в смысле Dittobytes, используются 2 транспайлера, они не используются в обычном понимании ЯП -> ЯП. 1 Транспайлер отвечает за изменение в IR (Intermediate Representation). IR это такой промежуточный процесс компиляции, то есть компилятор не сразу переводит код ЯП в машинный код, а проходит через IR
Не удалось прогрузить медиа
То-есть в 1 транспайлере упор идет на позиционно-НЕЗАВИСИМОСТЬ(PIC) кода
2 Транспайлер представляет собой манипуляции с регистрами, то что мы и рассмотрели выше
"Второй — машинный транспилятор, использующий устаревший проход функций LLVM для выполнения метаморфических преобразований"
(https://github.com/tijme/dittobytes?tab=readme-ov-file#-metamorphications)
В общем и целом Dittobytes собран из LLVM, 2 транспайлеров, ну и каких то своих встроенных плагинов. Как раз, все эти компоненты вместе создают метаморфный код.
Установка.
Для начала нужно установить Docker, Git для работы Dittobytes, сам ditto составляет 20 гб, и его придется собрать и запустить в контейнере, так-как он юзает свой кастомный форк LLVM и другие репозитории. Все делается элементарно, но есть все же пару нюансов о которых я расскажу в ходе установки Dittobytes.1. Если вы юзер Windows скачиваем Docker Desktop(для удобства). Если же у вас Linux все тоже самое.
Не удалось прогрузить медиа
Docker: Accelerated Container Application Development
Docker is a platform designed to help developers build, share, and run container applications. We handle the tedious setup, so you can focus on the code.
Не удалось прогрузить медиа
www.docker.com
2. Я буду использовать Windows, после установки докера нужно установить Git если у вас его нет
Windows
Linux
apt-get install gitЗапускаем Docker, и начинаем клонировать репозиторий Dittobytes
Создаем нужную вам папку, и копируем туда Dittobytes
git clone https://github.com/tijme/dittobytes.gitДалее перейдем в каталог для работы с ним
cd ./dittobytes/Были 2 проблемы которые у меня возникли при установке.
У меня падал билд с ошибкой из-за slim образа, а именно из-за его ограниченности
Не удалось прогрузить медиа
Проблема решилась скачиванием дополнительных системных пакетов, так же по желанию очищаем кэш. В dittobytes/Dockerfile заменяем:
RUN apt update -yна:
RUN apt-get update && apt-get install -y --no-install-recommends \ gnupg2 wget ca-certificates apt-transport-https software-properties-common dirmngr \ && rm -rf /var/lib/apt/lists/*После чего у меня все заработало, но была еще одна проблема. Возможно проблемы у меня с провайдером, либо же Debian не долюбливает РУ IP))) После начала билда, у меня не отвечал дебиан, хотя проверил через ping у меня возвращало пакеты с их сервером, возможно проблема чем то другом, вот что мне выводило в консоли
Не удалось прогрузить медиа
Использовать зеркала //mirror.yandex не помогло, так-как у яндека есть не все поддомены, поэтому у меня скачивались не все пакеты
Не удалось прогрузить медиа
Пришлось прокинуть HTTP прокси через Docker для установки и все заработало.
Запускаем PowerShell и установить прокси для билда
docker build --build-arg http_proxy=PROXY --build-arg https_proxy=PROXY -t dittobytes .\
Кстати хочу подметить что смена DNS в самом докере и проксирование всего трафика даже через proxychains4 или Proxifier не помогло, только команда выше, после чего у меня все заработало
Не удалось прогрузить медиа
Сама установка у меня заняла около часа, хотя в гитхабе у создателя была информация что по обычно времени отнимает около 2.5 часа.
Не удалось прогрузить медиа
Компиляция.
В Dittobytes отключено подключение библиотек на уровне компиляции в makefile, то есть это чисто для шелла либо каких то еще маленьких участков кода, давайте попробуем написать самый просто шелл-код для проверки на С
C:
void __main(void) {}
int main(void) {
unsigned char buf[64];
unsigned char shellcode[] = { 0xB8, 0x2A, 0x00, 0x00, 0x00, 0xC3 };
int i;
for (i = 0; i < 6; ++i) {
buf[i] = shellcode[i];
}
return 0;
}
И сравним в Ghidra дизассемблированный варианты через обычный компилятор и Dittobytes
Не удалось прогрузить медиа
На скрине все понятно и видно, например те же байты с shellcode-а
Не удалось прогрузить медиа
Все понятно и наглядно, теперь скомпилим через Dittobytes
Запуск контейнера
docker run -it --rm -v $(pwd):/tmp/workdir dittobytesили:
docker run --rm -v ".:/tmp/workdir" -it dittobytesЗапуск компиляции
make beacon-win-amd64-exeТеперь можно закинуть в Ghidra
Не удалось прогрузить медиа
Явно видно что не находит исходную функцию main(). Единственная точка входа - entry
Не удалось прогрузить медиа
Видно обфусцированный код, который разделен по кускам, собирается и работает благодаря кастомному LLVM. Сам код становится вообще невозможен к сигнатуре.
Что предлагает Dittobytes.
В общем то библиотеки разрешены в компиляции НО!!, только когда они runtime. Когда именно код находит в памяти процесса, распарсить и получить адреса функций, это не дает скомпилировать код который будет много весить. Возьмем пример из самого Dittobytes calc.exe(https://github.com/tijme/dittobytes/blob/master/code/examples/example-calc/example-calc.c)Не удалось прогрузить медиа
Как правильно вызвать функцию из .dll
C:
uint32_t EntryFunction() {
struct Relocatable context;
InitializeRelocatable(&context);
PopulateTables(&context);
DEFINE_STRING(CalculatorBinary, "calc.exe");
return context.functions.WinExec(CalculatorBinary, SW_SHOW) > 31;
}
void InitializeRelocatable(struct Relocatable* context) {
DEFINE_STRING(Kernel32ModuleName, "KERNEL32.dll");
DEFINE_STRING(LoadLibraryAFunctionName, "LoadLibraryA");
DEFINE_STRING(GetProcAddressFunctionName, "GetProcAddress");
context->functions.LoadLibraryA = RelocatablePreliminaryGetProcAddress(
Kernel32ModuleName, LoadLibraryAFunctionName);
context->functions.GetProcAddress = RelocatablePreliminaryGetProcAddress(
Kernel32ModuleName, GetProcAddressFunctionName);
}
void* RelocatablePreliminaryGetProcAddress(const char *moduleName, const char *functionName) {
PEB *peb = RelocatableNtGetPeb();
LIST_ENTRY *first = peb->Ldr->InMemoryOrderModuleList.Flink;
LIST_ENTRY *ptr = first;
do {
LDR_DATA_TABLE_ENTRY *dte = RelocatableGetDataTableEntry(ptr);
uint8_t *base = (uint8_t *)dte->DllBase;
IMAGE_DOS_HEADER *dosHdr = (IMAGE_DOS_HEADER *)base;
IMAGE_NT_HEADERS *ntHdrs = (IMAGE_NT_HEADERS *)(base + dosHdr->e_lfanew);
IMAGE_EXPORT_DIRECTORY *expDir = (IMAGE_EXPORT_DIRECTORY *)(base + expDirRVA);
if (!RelocatableStrCmp(moduleName, (char *)(base + expDir->Name))) continue;
DWORD *nameRVAs = (DWORD *)(base + expDir->AddressOfNames);
DWORD *funcRVAs = (DWORD *)(base + expDir->AddressOfFunctions);
WORD *ordinals = (WORD *)(base + expDir->AddressOfNameOrdinals);
for (DWORD i = 0; i < expDir->NumberOfNames; i++) {
if (RelocatableStrCmp(functionName, (char *)(base + nameRVAs[i]))) {
return (void *)(base + funcRVAs[ordinals[i]]);
}
}
ptr = ptr->Flink;
} while (ptr != first);
return NULL;
}
void* RelocatableNtGetPeb() {
void* lpPEB;
lpPEB = (void*) __readgsqword(0x60);
return lpPEB;
}
void PopulateTables(struct Relocatable* context) {
DEFINE_STRING(Kernel32ModuleName, "KERNEL32.dll");
context->modules.hKernel32 = context->functions.LoadLibraryA(Kernel32ModuleName);
DEFINE_STRING(WinExecFunctionName, "WinExec");
context->functions.WinExec = context->functions.GetProcAddress(
context->modules.hKernel32, WinExecFunctionName);
}
1. Получаем PEB, для точки входа
2. Парсинг списка модулей через PEB
3. PE парсинг Export Directory каждой DLL, для поиска функций по имени
4. Получение LoadLibraryA/GetProcAddress для загрузки API
5. Динамическая загрузка нужных модулей и функций во время выполнений
Закидываем готовый калькулятор в Ghidra и наблюдаем тоже самое. Обфускацию и метаморфный код! Вот например:
Не удалось прогрузить медиа
Наблюдаем что LoadLibraryA не хранится в бинарнике, вместо этого в регистры загружены две 64-битные константы, являющийся зашифрованными
После всех примеров, можно точно сказать что Dittobytes отлично обфусцирует код и делает его метаморфным, что и требовалось доказать.
👍41
😁12
🤔6
😢4
LIMB