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

Как писать обфускаторы?

YaR1k

RAID-массив
Пользователь
Регистрация
26.05.2024
Сообщения
52
Реакции
29
Может быть, мой вопрос кажется немного странным, но я не смог найти какие-либо обучающие материалы по этой теме. Если есть - посоветуйте, пожалуйста.
Заранее спасибо.
 
Do your own research. А так, слишком общий вопрос, что ты имеешь в виду под "обфускатором"? - Это слишком общее определение. На самом деле мы даже предположить не можем, что ты хочешь узнать, потому что для этого как минимум нужно знать на каком языке написана программа, которую ты хочешь обфусцировать, каким компилятором и под какую систему она собирается, какая разрядность и так далее. Условно "обфускатор" под .NET будет очень сильно отличаться от обфускатора под плюсы, да и даже в пределах одного языка, системы и архитектуры они могут сильно отличаться и работать очень по-разному, причем как уровне исходника, так и на уровне бинарника.
 
лучший обфускатор - это собственный мини-интерпретатор (сам обфусцированный и параметризованный причём, чтобы антивирь не мог выкусить сигнатуру самого интерпретатора) собственного байткода ;-)
можете почитать про такую старую штуку как "полиморфные вирусы", например - на них в 90-е "ломался" aidstest который работал по сигнатурам, но drweb и каспер первые научились с ними бороться.
 
Пожалуйста, обратите внимание, что пользователь заблокирован
Смотря для какого языка, и на уровне чего это сделать. Тебе нужно найти и использовать библиотеку, которая сможет разобрать программу, модифицировать ее и записать обратно. Например, более менее универсальное решение для большинства языков на уровне абстрактного синтаксического дерева: https://xss.pro/threads/106900/ - но у каждого языка скорее всего найдется библиотека, которая будет разбирать язык лучше общего решения: типа libclang для C/C++, Roslyn для C#, Esprima для JS, ast для Python и тд. Можно писать обфускаторы на базе более низкоуровневого представления, типа LLVM или GIMPLE для C/C++/итд или MSIL для дотнетов (dnlib, Mono.Cecil или AsmResolver).
 
Смотря для какого языка, и на уровне чего это сделать. Тебе нужно найти и использовать библиотеку, которая сможет разобрать программу, модифицировать ее и записать обратно. Например, более менее универсальное решение для большинства языков на уровне абстрактного синтаксического дерева: https://xss.pro/threads/106900/ - но у каждого языка скорее всего найдется библиотека, которая будет разбирать язык лучше общего решения: типа libclang для C/C++, Roslyn для C#, Esprima для JS, ast для Python и тд. Можно писать обфускаторы на базе более низкоуровневого представления, типа LLVM или GIMPLE для C/C++/итд или MSIL для дотнетов (dnlib, Mono.Cecil или AsmResolver).
Подскажешь, что лучше под чистые C/C++ LLVM или GIMPLE?
 
Пожалуйста, обратите внимание, что пользователь заблокирован
Подскажешь, что лучше под чистые C/C++ LLVM или GIMPLE?
Мне для С/С++ всегда хватало манипуляций на уровне AST, то есть работа только на уровне исходных текстов. Это проще, и для большинства вещей вполне хватает.

GIMPLE - это промежуточное представления у GCC/MinGW, тоже SSA, как и LLVM. Но чтобы до него добраться и что-то поменять нужно писать плагин к компилятору. Вот очень старое видео на тему, но там довольно неплохо рассказывается концепт:

По поводу LLVM, я экспериментировал с этим, но так и не доделал, тк переключился на обработку сорсов. Плюс LLVM (в отличии от GIMPLE) в том, что ты можешь сдампить LLVM IR, модифицировать его и скомпилировать, без необходимости оформлять это в виде плагина к компилятору. На тему LLVM есть несколько презентаций на тытрубе, например вот это:
 
Мне для С/С++ всегда хватало манипуляций на уровне AST, то есть работа только на уровне исходных текстов. Это проще, и для большинства вещей вполне хватает.

GIMPLE - это промежуточное представления у GCC/MinGW, тоже SSA, как и LLVM. Но чтобы до него добраться и что-то поменять нужно писать плагин к компилятору. Вот очень старое видео на тему, но там довольно неплохо рассказывается концепт:

По поводу LLVM, я экспериментировал с этим, но так и не доделал, тк переключился на обработку сорсов. Плюс LLVM (в отличии от GIMPLE) в том, что ты можешь сдампить LLVM IR, модифицировать его и скомпилировать, без необходимости оформлять это в виде плагина к компилятору. На тему LLVM есть несколько презентаций на тытрубе, например вот это:
Большое спасибо
 
Смотря для какого языка, и на уровне чего это сделать. Тебе нужно найти и использовать библиотеку, которая сможет разобрать программу, модифицировать ее и записать обратно. Например, более менее универсальное решение для большинства языков на уровне абстрактного синтаксического дерева: https://xss.pro/threads/106900/ - но у каждого языка скорее всего найдется библиотека, которая будет разбирать язык лучше общего решения: типа libclang для C/C++, Roslyn для C#, Esprima для JS, ast для Python и тд. Можно писать обфускаторы на базе более низкоуровневого представления, типа LLVM или GIMPLE для C/C++/итд или MSIL для дотнетов (dnlib, Mono.Cecil или AsmResolver).
Спасибо. Меня по большей части плюсы интересовали.
 
Подскажешь, что лучше под чистые C/C++ LLVM или GIMPLE?
Работать с LLVM-IR приятнее за счёт более богатого тулчейна - есть LLVM-C, который можно использовать из любого ЯП. Дока по IR так же очень подробная, нет ограничения по ЯП, которые можно обфусицировать/морфить - можно поддерживать любой ЯП, умеющий собираться в байткод LLVM. Так же JIT, который можно использовать для автоматических тестов обработанного кода.

Немного дополню: LLVM-C не имеет некоторых очень удобных утилит (например BasicBlock::splitBasicBlock) по сравнению с плюсовым апи, поэтому приходится писать некие "мосты" - C++ -> C, генерировать для них биндинги и использовать уже из своего ЯП.
 
Последнее редактирование:
Хотелось бы присоедениться к вопросу, пишу морфер на основе LLVM-байткода на расте. Уже умею генерировать непрозрачные предикаты, за основу взял MBA+winapi, результат обработки выражения a ^ b выглядит примерно так:
%global_var% + %stack_var% * %random_winapi_res% + %stack_var% * ~(%operand_a% ^ %operand_b%) + %global_var% * ~%random_winapi_res%, где:
%global_var% - рандомная глобальная переменная
%stack_var% - рандомная переменная на стеке
%random_winapi_res% - рандомный результат выполнения винапи, не влияющий на результат (например
GetCurrentProcessId())
%operand_a% - исходный операнд A - результат вызова винапи с известным результатом (условный lstrlenW(L"sssss") = 5)
%operand_b% - исходный операнд B
Каждое мат. выражение на выходе уникально (при условии разного seed для rng), кол-во операций регулируется из конфига.
Пока что морфлю только константы, в планах так же строки, но что делать с CFG? Как правильно генерировать if/switch/loop брэнчи?
 
Пожалуйста, обратите внимание, что пользователь заблокирован
Как правильно генерировать if/switch/loop брэнчи?
Для ветвлений есть инструкция "br": https://mapping-high-level-construc...n/latest/control-structures/if-then-else.html - но в любом случае тебе придется разобраться с "phi", а это довольно странная вещь. Я бы тебе посоветовал для ускорения процесса смотреть, как clang делает, и делать также. То есть, накодил на С/С++ for+switch для control flow flattening, скомпилил clang'ом в LLVM IR, посмотрел, что получилось, и сделал также.
 
Для ветвлений есть инструкция "br"
Проблема не в том как вставить ту или иную инструкцию, а скорее куда и каким образом, чтоб это выглядело как легитимный код - не ясна сама логика. Возможно вопрос очень глупый, но у меня не было опыта написания обфускатором-морфером.
накодил на С/С++ for+switch для control flow flattening, скомпилил clang'ом в LLVM IR, посмотрел, что получилось, и сделал также
Дельный совет - уже использую.
 
Заинтересовала техника Control Flow Flattening - выглядит просто: создаём глобальный диспатчер (switch), проходимся по всем basic blocks, патчим все (un)conditional branches - смотрим в какие базовые блоки прыгает, сохраняем условный айди базового блока в регистр switch_key, прыгаем в диспатчер (после окончания обработки функции нужно пофиксить стек). Достаточно эффективный метод для запутывания CFG (при условии что switch_key нельзя предугадать статически), но проблема в том что результат выполнения, по сути, всегда одинаковый (если не проходиться несколько раз) - нужно добавлять фейковые базовые блоки и прыгать туда, но каким кодом наполнить эти фейк-блоки?
 
Пожалуйста, обратите внимание, что пользователь заблокирован
но проблема в том что результат выполнения, по сути, всегда одинаковый
Так а в чем проблема? Одинаковый и одинаковый.

но каким кодом наполнить эти фейк-блоки
Ну теме же неявными предикатами и наполни. Можешь еще добавить кучу функций с предикатами, которые никогда не выполняются, и вставлять вызовы этих функций с рандомными параметрами.
 
Так а в чем проблема? Одинаковый и одинаковый.
Хотелось бы добиться максимальной уникальности, иначе морфер не морфер.
Ну теме же неявными предикатами и наполни.
Кстати, как вариант. Можно прямо в предикаты встроить генерацию фейк-базовых блоков, на подобии:
C:
int a = 10;
int res = 0;
if (true) {
    res = a ^ 5;
} else {
    res = a + 42;
}
assert_eq(res, 15);
На уровне инструкций я заменяю все константы на выражения - в таком случае фейк-блоки уже будут, т.к CFF работает на уровне функции (уже после пасса constant unfolding).
 


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