D2
Администратор
- Регистрация
- 19 Фев 2025
- Сообщения
- 4,380
- Реакции
- 0
Привет, друзья. В нашем уютненьком комьюнити то и дело всплывают вопросы про обфускацию С и/или С++. Почти в каждой такой теме я пишу, мол все просто: берете libclang или pycparser или srcml (в зависимости от ваших нужд) и пишите себе обфускатор. Но в ответ частенько натыкаюсь на возражения, мол «как просто»? «Ничего же не понятно». В плоть до обвинений меня в том, что я только трепать языком на форуме могу. Ну что ж, мне остается только одно — показать вам, дорогие мои кулхацкеры, как писать обфускатор для С и/или С++ с использованием либшланг (ну libclang, ну вы поняли, такая очень смешная шутка). Для этого мы будем использовать самый любимый язык всех кулхацкеров мира — Python, а обфусцировать мы будем на уровне абстрактного синтаксического дерева.
Для начала немножечко дисклеймеров. Во-первых, задача этой статьи показать вам, что и как делается, а не забросать вас элитными алгоритмами и готовыми тулзами. Так что все алгоритмы достаточно простые, я бы даже сказал, слишком простые для современного обфускатора, но да ладно. Вы же статьи читаете, чтобы учиться, правильно? Во-вторых, мой скилл Питона немножечко заржавел, так что не упускайте возможность потроллить ветерана форумных срачей недостатками его кода. В-третьих, обфускатор в текущей реализации делает несколько предположений о том, как выглядит ваш С/С++ код. Для того, чтобы он работал на всем множестве С/С++ исходников придется его серьезно допиливать, что выходит за рамки одной статьи. Но надеюсь, что с теми базовыми знаниями, которые вы получите в этой статье, проблем с этим у вас не возникнет.
Итак, мы будем обфусцировать код на базе абстрактного синтаксического дерева. Что же это такое? В реализации подавляющего большинства компиляторов (разве что Lisp и Forth может без этого обойтись) для внутреннего представления кода используется древовидная рекурсивная структура, которая называется AST (abstract syntax tree). Давайте рассмотрим следующий пример, основанный на AST языка Питон.
Python: Скопировать в буфер обмена
Вот такие простые две строчки питоновского кода были переведены в такое громоздкое на первый взгляд древовидное представление. Вы можете зайти на сайт https://vpyast.appspot.com/ и сами поэкспериментировать с кодом, рассматривая различные синтаксические деревья, создаваемые для разного кода. С первого взгляда такое представление кажется избыточным и неудобным, однако оно содержит в себе всю необходимую информацию для работы с исходным кодом. Да и в принципе, синтаксис языка программирования в большинстве случаев сам по себе рекурсивная структура (например, выражения могут иметь вложенные выражения), поэтому представления исходного кода в виде дерева, ну… как минимум логично. И да, я пробовал для обфускаторов использовать регулярные выражения и токенизаторы, но ничего особо хорошего из этого не вышло.
Давайте рассмотрим структуру AST на картинке подробнее. Как мы видим в корне дерева находится узел, который олицетворяет питоновский модуль. У модуля есть два потомка — наши две строчки с выражениями присваивания. У каждого выражения присваивания есть потомок, определяющий кому производится присваивание (в данном случае это — идентификаторы «a» и «d» соответственно), а так же потомок, определяющий что именно будет присвоено. В нашем дереве этим потомком является бинарный оператор, у которого соответсвенно есть левая часть, оператор и правая часть. Ну и так далее. Даже небольшие фрагменты исходного кода могут генерировать большие абстрактные синтаксические деревья. В контексте нашей сегодняшней задачи нам нужно каким-то образом обойти подобную древовидную структуру, выделить интересующие нас узлы дерева и произвести их модификации.
Не то чтобы очень удобным, но точно наиболее часто используемым методом обхода абстрактного синтаксического дерева является паттерн «Посетитель» (он же «Visitor»). Его мы и реализуем для обхода абстрактного синтаксического дерева библиотеки либшланг. Мюсье «Посититель» будет «посещать» узел дерева, а затем рекурсивно вызывать себя же для всех потомков текущего узла. В зависимости от типа текущего узла, мюсье «Посетитель» будет вызывать наши колбеки для обработки. По сути дела, это алгоритм «поиска в глубину» («depth first search»), если вам проще думать об этом в терминах институтского курса «Алгоритмы и структуры данных».
Ну что ж, с вводной теоретической частью мы покончили, давайте плавно переходить к практике. Для разработки нам потребуется установленный интерпретатор языка программирования Python, я буду использовать версию 3.8, потому что люблю подобие статической типизации и некоторые другие фичи современных Питонов. Кроме того, нам понадобится библиотека либшланг с байндингами для нашего Питона. Если вы сидите на Линуксах, то библиотека и байндинги скорее всего будут установлены вместе с компилятором clang. Но если вы на Венде или не хотите ставить весь компилятор, то произвести установку можно с помощью утилиты pip. Нужно запустить комнаду «pip install libclang» и байндинги и библиотека будет установлена. Обратите внимание, что пакет libclang из PyPl содержит только 64-битную версию библиотеки либшланг. То есть вам либо понадобится 64-битный интерпретатор Питона, либо поставить байндинги отдельно «pip install clang» и Скачать
Для начала немножечко дисклеймеров. Во-первых, задача этой статьи показать вам, что и как делается, а не забросать вас элитными алгоритмами и готовыми тулзами. Так что все алгоритмы достаточно простые, я бы даже сказал, слишком простые для современного обфускатора, но да ладно. Вы же статьи читаете, чтобы учиться, правильно? Во-вторых, мой скилл Питона немножечко заржавел, так что не упускайте возможность потроллить ветерана форумных срачей недостатками его кода. В-третьих, обфускатор в текущей реализации делает несколько предположений о том, как выглядит ваш С/С++ код. Для того, чтобы он работал на всем множестве С/С++ исходников придется его серьезно допиливать, что выходит за рамки одной статьи. Но надеюсь, что с теми базовыми знаниями, которые вы получите в этой статье, проблем с этим у вас не возникнет.
Итак, мы будем обфусцировать код на базе абстрактного синтаксического дерева. Что же это такое? В реализации подавляющего большинства компиляторов (разве что Lisp и Forth может без этого обойтись) для внутреннего представления кода используется древовидная рекурсивная структура, которая называется AST (abstract syntax tree). Давайте рассмотрим следующий пример, основанный на AST языка Питон.
Python: Скопировать в буфер обмена
Код:
a = b + c
d = e * f
Вот такие простые две строчки питоновского кода были переведены в такое громоздкое на первый взгляд древовидное представление. Вы можете зайти на сайт https://vpyast.appspot.com/ и сами поэкспериментировать с кодом, рассматривая различные синтаксические деревья, создаваемые для разного кода. С первого взгляда такое представление кажется избыточным и неудобным, однако оно содержит в себе всю необходимую информацию для работы с исходным кодом. Да и в принципе, синтаксис языка программирования в большинстве случаев сам по себе рекурсивная структура (например, выражения могут иметь вложенные выражения), поэтому представления исходного кода в виде дерева, ну… как минимум логично. И да, я пробовал для обфускаторов использовать регулярные выражения и токенизаторы, но ничего особо хорошего из этого не вышло.
Давайте рассмотрим структуру AST на картинке подробнее. Как мы видим в корне дерева находится узел, который олицетворяет питоновский модуль. У модуля есть два потомка — наши две строчки с выражениями присваивания. У каждого выражения присваивания есть потомок, определяющий кому производится присваивание (в данном случае это — идентификаторы «a» и «d» соответственно), а так же потомок, определяющий что именно будет присвоено. В нашем дереве этим потомком является бинарный оператор, у которого соответсвенно есть левая часть, оператор и правая часть. Ну и так далее. Даже небольшие фрагменты исходного кода могут генерировать большие абстрактные синтаксические деревья. В контексте нашей сегодняшней задачи нам нужно каким-то образом обойти подобную древовидную структуру, выделить интересующие нас узлы дерева и произвести их модификации.
Не то чтобы очень удобным, но точно наиболее часто используемым методом обхода абстрактного синтаксического дерева является паттерн «Посетитель» (он же «Visitor»). Его мы и реализуем для обхода абстрактного синтаксического дерева библиотеки либшланг. Мюсье «Посититель» будет «посещать» узел дерева, а затем рекурсивно вызывать себя же для всех потомков текущего узла. В зависимости от типа текущего узла, мюсье «Посетитель» будет вызывать наши колбеки для обработки. По сути дела, это алгоритм «поиска в глубину» («depth first search»), если вам проще думать об этом в терминах институтского курса «Алгоритмы и структуры данных».
Ну что ж, с вводной теоретической частью мы покончили, давайте плавно переходить к практике. Для разработки нам потребуется установленный интерпретатор языка программирования Python, я буду использовать версию 3.8, потому что люблю подобие статической типизации и некоторые другие фичи современных Питонов. Кроме того, нам понадобится библиотека либшланг с байндингами для нашего Питона. Если вы сидите на Линуксах, то библиотека и байндинги скорее всего будут установлены вместе с компилятором clang. Но если вы на Венде или не хотите ставить весь компилятор, то произвести установку можно с помощью утилиты pip. Нужно запустить комнаду «pip install libclang» и байндинги и библиотека будет установлена. Обратите внимание, что пакет libclang из PyPl содержит только 64-битную версию библиотеки либшланг. То есть вам либо понадобится 64-битный интерпретатор Питона, либо поставить байндинги отдельно «pip install clang» и Скачать