
Обфусцирует C/C++ через проходы LLVM: шифрование строк, уплощение потока управления, MBA-переписывание и анти-анализ для противодействия реверс-инжинирингу.
██╗ ███████╗███████╗████████╗
██║ ██╔════╝██╔════╝╚══██╔══╝
██║ █████╗ █████╗ ██║
██║ ██╔══╝ ██╔══╝ ██║
███████╗███████╗███████╗ ██║
╚══════╝╚══════╝╚══════╝ ╚═╝
██████╗ ██████╗ ███████╗██╗ ██╗███████╗ ██████╗ █████╗ ████████╗ ██████╗ ██████╗
██╔═══██╗██╔══██╗██╔════╝██║ ██║██╔════╝██╔════╝██╔══██╗╚══██╔══╝██╔═══██╗██╔══██╗
██║ ██║██████╔╝█████╗ ██║ ██║███████╗██║ ███████║ ██║ ██║ ██║██████╔╝
██║ ██║██╔══██╗██╔══╝ ██║ ██║╚════██║██║ ██╔══██║ ██║ ██║ ██║██╔══██╗
╚██████╔╝██████╔╝██║ ╚██████╔╝███████║╚██████╗██║ ██║ ██║ ╚██████╔╝██║ ██║
╚═════╝ ╚═════╝ ╚═╝ ╚═════╝ ╚══════╝ ╚═════╝╚═╝ ╚═╝ ╚═╝ ╚═════╝ ╚═╝ ╚═╝
A simple obfuscator for C/C++ x64 and x86 code
Это сделано как форк LLVM, поэтому для обфускации вам нужен фактический исходный код. Это не произвольный обфускатор исполняемых файлов. Это модифицированная версия компилятора.
Если вы хотите посмотреть результаты, я создал и обфусцировал с его помощью 2 крякми. Вы можете получить их в releases вместе с предварительно скомпилированным бинарником clang.
Всё это было скомпилировано с флагом -O3, и все скриншоты взяты из IDA Pro 9.4. Я также проверил это с Binary Ninja и Ghidra — результаты были либо такими же, либо хуже.
Шифрует строки во время компиляции и вставляет функцию расшифровки при каждом использовании зашифрованной строки. Это полностью исключает возможность поиска каких-либо строк в бинарнике. И каждая строка имеет свой собственный уникальный ключ, захардкоженный в функции расшифровки, что значительно усложняет их дамп и расшифровку. Эти функции расшифровки также будут встроены (inlined). (ниже не встроены, только для демонстрации)
![]() До прохода |
![]() После |
Заменяет арифметические операции их MBA-эквивалентами. Практически невозможно увидеть, что делала исходная операция, если только вы не пропустите её через MBA-деобфускатор. Конечно, эта обфускация довольно слабая, потому что MBA — самый старый трюк в книге, поэтому существует множество инструментов для борьбы с ней, например CoBRA, который успешно деобфусцирует это обратно в исходное выражение:
./cobra-cli --mba "((x^y) - (((x^y)&0xFF)&0xA) + 10) * ((x^y) - ((x^y)|0xA) + 10) + ((x^y) - (((x^y)&0xFF)|0xFFFFFFF5) - 11) * (~(x^y) - (~(x^y)|0xA) + 10)" --bitwidth 32
10 * (x ^ y)
Вот почему я сделал проход AAMBA.
![]() До прохода |
![]() После |
Заменяет операнды бинарных операций на ADC(X, 255) - 255 - CF и SBB(X, 255) + 255 + CF. Разумеется, это всегда вычисляется в X, но делает выражение зависимым от флага переноса. Если декомпилятор не отслеживает состояние CF (что иногда невозможно), он сильно запутается и не сможет свернуть эти выражения. Это очень хорошо сочетается с предыдущим проходом MBA, ещё сильнее обфусцируя арифметику. Как видно ниже, декомпилятор создал несколько дополнительных переменных и использует множество вызовов __PAIR64__ и __CFADD__, поэтому вставить это в такие инструменты, как CoBRA, становится намного сложнее (хотя всё ещё не невозможно), а плагин gooMBA для IDA также не помогает упростить это. Кроме того, декомпилятор IDA в некоторой степени отслеживает флаг переноса, но в сочетании с обфускацией потока управления отслеживание становится практически невозможным без выполнения, поскольку вы не знаете, какой была предыдущая операция — возможно, она установила CF в 1, а возможно, и нет, поэтому последующие проходы добавят к этому ещё больше.
![]() До прохода |
![]() После |
Собирает все блоки внутри функции и превращает их в одну гигантскую машину состояний: создаёт таблицу переходов в начале функции и размещает в ней все указатели на блоки. Затем вместо обычного перехода в конце каждого блока всё направляется через диспетчер, использующий косвенные переходы, которые почти невозможно разрешить статически без какого-либо выполнения. Он также переносит регистры в стек, поэтому если разделить блок пополам, все переменные из предыдущего блока окажутся в стеке, а значит, в каждом выражении будет ОЧЕНЬ много переменных. Если прогнать операции одного блока через CoBRA, она не сможет сильно его деобфусцировать, поскольку будет слишком много неизвестных переменных.
Создаёт множество фиктивных блоков, содержащих недопустимый ассемблерный код. Это чрезвычайно сбивает дизассемблеры с толку, потому что если дизассемблер встречает технически недопустимый байт, который никогда не выполняется, он всё равно попытается его осмыслить. Так что если байт неполный, он создаст инструкцию из любых байтов, оказавшихся после него, по сути поглотив их. Это создаёт рассинхронизацию, разрушающую каждую инструкцию после этого. В бинарниках Windows IDA сможет в некоторой степени восстановиться после этого, в редких случаях она сможет сгенерировать граф и декомпилировать то, что сможет (хотя это будет сломано и неполно), тогда как в бинарниках Linux это полностью ломает представление графа и отключает декомпиляцию. Дополнительно он вставляет таймер RDTSC и некоторые другие проверки на отладку: если обнаруживается отладчик, вы падаете.
![]() Windows |
![]() Linux |