LIMB
Вход Регистрация

тема

Dittobytes - компиляция метаморфного C/C++ кода

Dead_Prince · · 1 ответов · 864 просмотров

В статье будет продемонстрирован пример работы с так скажем инструментарием 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
В данной статье нашел следующие проблемы:
1. Особого смысла данная статья не несет, потому что почти весь ее материал есть на странице проекта dittobytes на гитхабе, то есть просто сделан перевод.
2. Были приведены максимально простые примеры, которые явно недостаточны для того, чтобы необходимость использования dittobytes была достаточно ясна. То есть по примерам можно было взять более интересные и нужные варианты кода.
3. Также в части рассказа про llvm, IR, траспилятор и процесс компиляции не дотянул. То есть раскрытия этой части не было, а также она особого смысла не несет в рамках того, что было написано в данном варианте статьи.

Вся статья сводится к тому, что просто рассказывается о существовании некоторой утилиты и дается гайд, как ей пользоваться, который уже есть на странице проекта.
Если действительно хочется написать статью про dittobytes, то в таком случае нет смысла описывать использование этой утилиты на протяжении всей статьи, а необходимо привести примеры использования в более сложных случаях, показав, что реверсинг будет происходить намного сложнее, пояснив общую необходимость всех этих действий.
👍4 ❤️1

Гостевой просмотр. Чтобы отвечать и ставить реакции, войдите или зарегистрируйтесь.