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

прочее Дерева переписыватель

DildoFagins

TPU unit
Забанен
Регистрация
11.08.2020
Сообщения
4 315
Решения
2
Реакции
5 265
Пожалуйста, обратите внимание, что пользователь заблокирован
intro.jpeg


Знаете, в современном жестоком мире, наши с вами редтимерские тулзы без обфускации и/или морфинга не живут долго. Морфить код можно по-разному, у каждого подхода есть свои преимущества и недостатки. Но наиболее простым, при этом практически всегда достаточным, на мой взгляд, является трансформация исходного кода. Не говоря уже о том, что для скриптовых языков этот способ — по сути единственный. Проблема в том, как разпарсить исходный код таким образом, чтобы он был понятен морферу?

Для каждого языка программирования скорее всего найдется одна лидирующая в парсинге этого языка библиотека. Для С/С++ — это очевидно будет libclang, для C# — это будет Roslyn, для JavaScript — это будет Esprima, ну и так далее. Но все эти библиотеки очень сильно отличаются друг от друга, как по API, так и по представлению абстрактного синтаксического дерева (AST). Более того, каждую из таких библиотек скорее всего придется использовать из своего языка, что сводит к минимуму возможность переиспользования кода морфера для разных языков, а переключать контекст своего мышления между ними накладно для уже стареющего мозга). Но у меня есть решение… наверное.

Когда динозавры были большими, еще существовал такой редактор кода, аля VSCode, который назывался Atom. Разрабатывали его в основном челики из GitHub, но когда мелкомягкие выкупили GitHub, стало понятно, что господин Atom уже не жилец, ибо нехер ВсКоду конкурентов иметь. Сейчас зомби редактор на базе Atom все еще существует, его разрабатывает комьюнити, но он уже давно не торт. Так вот, одной из интересных разработок команды редактора Atom была библиотека «Дерева сидетель» (она же tree-sitter). Смысл был в том, что подсветка синтаксиса классическими регулярочками была уж очень тупой, а запуск полноценного LSP для языка — уж очень накладным. Поэтому они решили запилить библиотеку, которая бы понимала все языки лучше, чем это делают регулярочки, но не слишком хорошо, чтобы оставаться быстрой. Эта библиотека основывалась на грамматиках, и могла отличить, условно, класс от функции. Понимаете, к чему я клоню?

Я тут попробовал поиспользовать tree-sitter для задач обфускации и морфинга моих редтимерских тулзов и мне в принципе понравилось. В процессе я написал маленькую библиотечку для модного молодежного Петона, которой, собственно, и спешу с вами поделиться. Назвал я ее «дерева переписыватель» в честь ее основы — «дерева сидельщика». Поскольку я использовал ее только на своих кодовых базах, в ней могут быть баги, но кто из нас не без греха?

Ну давайте начнем. Чтобы вы понимали, библиотека tree-sitter написана на чистом православном Цэ, поэтому в принципе ее можно использовать из любого языка при условии наличия нормальных байндингов. Я выбрал Петон потому, что не люблю страдать, элитные цэшные и плюсовые господа, можете портировать функционал библиотеки в свою уютные языки, я не против, только за.

Для Петона есть готовые байндинги, но они предполагают, что вы их соберете у себя на компуктерах, а для этого нужно настраивать сишный тулчейн. Если он у вас есть и вы чувствуете, что хотите приключений, дерзайте. Сама библиотека тут: https://pypi.org/project/tree-sitter/ - а байндиги множества языков можно найти тут: https://pypi.org/project/tree-sitter-languages/ - судя по мануалу, собирается все достаточно просто, но… Страдать я не люблю, и для таких же, как я, есть готовая сборка обоих библиотек: https://pypi.org/project/tree-sitter-builds/ - она отстает от текущей версии и в ней нет всякой так горячо любимой мной маргинальщины, типа Дэ, Нима и всякого такого. Но зато она ставится одной командой «pip install tree-sitter-builds» и пошуршали делать свои морферы. В общем, выбор за вами, аргументом против установки бинарных пакетов часто бывает, что в них куда легче встроить малварь, но не то, чтобы мы с вами сами проверяли каждый скаченный пакет с интернетов даже в виде исходников, к тому же:
risk.jpg


После того, как вы поставили петоновский байндинги для tree-sitter, просто берете мой модуль tree_rewriter.py и начинаем писать. В модуле всего два класса. Первый из них называется Rewriter. Это — базовый класс, от которого по моей задумке должен наследоваться класс, реализующий один алгоритм обфускации или морфинга. Класс абстрагирует для вас парсинг и обход AST-дерева по паттерну «Посетитель» (Visitor), то есть он сам найдет и рекурсивно посетит для вас все необходимые вам узлы, а вам останется только их подменить.

Вы создаете свой класс, наследуете его от Rewriter, в конструкторе вызываете родительский конструктор, передавая туда название языка, с которым вы будете работать. Далее, вызываете метод visit, который осуществит парсинг исходного кода, переданного в виде строки или в виде массива байт, а так же в динамике вызовет все методы посещения узлов, которые вы определите. Но об этом чуть позже.

Начнем мы рассматривать эту абстракцию с класса Dumper. У класса Rewriter есть метод dump, передав узел дерева в который вы в консоли увидите всю его структуру, включая все дочерние узлы. На мой вкус это — очень удобный способ исследовать структуру AST-дерева и выискивать то, что вам нужно. Но, когда вы только начинаете работать с AST-деревьями, бывает полезно просто вывести целиком дерево всего кода, который вы подаете на вход. Для этого я реализовал класс Dumper. Вам нужно создать объект этого класса, передав в конструктор название языка, который вы будете парсить. Затем при вызове метода visit в консоль будет выплюнуто AST-дерево целиком. Давайте посмотрим это на примере example1.py:
Python:
import tree_rewriter

test = {
    'c': '''
        int test(int a, int b, int c) {
            return a + b - c;
        }
    ''',
    
    'cpp': '''
        int test(int a, int b, int c) {
            return a + b - c;
        }
    ''',

    'c_sharp': '''
        int Test(int a, int b, int c) {
            return a + b - c;
        }
    ''',

    'go': '''
        func Test(a int, b int, c int) {
            return a + b - c
        }
    ''',

    'javascript': '''
        function test(a, b, c) {
            return a + b - c;
        }
    ''',

    'php': '''
        <?php
        function test($a, $b, $c) {
            return $a + $b - $c;
        }
    ''',

    'python': '''
        def test(a: int, b: int, c: int) -> int:
            return a + b - c
    ''',

    'rust': '''
        fn test(a: i32, b: i32, c: i32) -> i32 {
            return a + b - c;
        }
    '''
}

for language in test.keys():
    print("***", language.upper(), "***")
    dumper = tree_rewriter.Dumper(language)
    dumper.visit(test[language])
    print()

Здесь в качестве примера я создаю словарь из имен языков и маленьких фрагментов кода на этих языках, потом я в цикле обхожу словарь и с помощью класса Dumper вывожу AST-деревья фрагментов кода для каждого из языков. Полный список доступных из коробки языков можно посмотреть в библиотеке tree-sitter-languages, их довольно много, но всякую маргинальщину, которой там нет придется руками дособирать по мануалам. Вот так выглядит вывод первого примера:
Код:
> python example1.py
*** C ***
translation_unit b'int test(int a, int b, int c)...'
  function_definition b'int test(int a, int b, int c)...'
    primitive_type b'int'
    function_declarator b'test(int a, int b, int c)'
      identifier b'test'
      parameter_list b'(int a, int b, int c)'
        ( b'('
        parameter_declaration b'int a'
          primitive_type b'int'
          identifier b'a'
        , b','
        parameter_declaration b'int b'
          primitive_type b'int'
          identifier b'b'
        , b','
        parameter_declaration b'int c'
          primitive_type b'int'
          identifier b'c'
        ) b')'
    compound_statement b'{\n            return a + b - ...'
      { b'{'
      return_statement b'return a + b - c;'
        return b'return'
        binary_expression b'a + b - c'
          binary_expression b'a + b'
            identifier b'a'
            + b'+'
            identifier b'b'
          - b'-'
          identifier b'c'
        ; b';'
      } b'}'

*** CPP ***
translation_unit b'int test(int a, int b, int c)...'
  function_definition b'int test(int a, int b, int c)...'
    primitive_type b'int'
    function_declarator b'test(int a, int b, int c)'
      identifier b'test'
      parameter_list b'(int a, int b, int c)'
        ( b'('
        parameter_declaration b'int a'
          primitive_type b'int'
          identifier b'a'
        , b','
        parameter_declaration b'int b'
          primitive_type b'int'
          identifier b'b'
        , b','
        parameter_declaration b'int c'
          primitive_type b'int'
          identifier b'c'
        ) b')'
    compound_statement b'{\n            return a + b - ...'
      { b'{'
      return_statement b'return a + b - c;'
        return b'return'
        binary_expression b'a + b - c'
          binary_expression b'a + b'
            identifier b'a'
            + b'+'
            identifier b'b'
          - b'-'
          identifier b'c'
        ; b';'
      } b'}'

*** C_SHARP ***
compilation_unit b'int Test(int a, int b, int c)...'
  global_statement b'int Test(int a, int b, int c)...'
    local_function_statement b'int Test(int a, int b, int c)...'
      predefined_type b'int'
      identifier b'Test'
      parameter_list b'(int a, int b, int c)'
        ( b'('
        parameter b'int a'
          predefined_type b'int'
          identifier b'a'
        , b','
        parameter b'int b'
          predefined_type b'int'
          identifier b'b'
        , b','
        parameter b'int c'
          predefined_type b'int'
          identifier b'c'
        ) b')'
      block b'{\n            return a + b - ...'
        { b'{'
        return_statement b'return a + b - c;'
          return b'return'
          binary_expression b'a + b - c'
            binary_expression b'a + b'
              identifier b'a'
              + b'+'
              identifier b'b'
            - b'-'
            identifier b'c'
          ; b';'
        } b'}'

*** GO ***
source_file b'func Test(a int, b int, c int...'
  function_declaration b'func Test(a int, b int, c int...'
    func b'func'
    identifier b'Test'
    parameter_list b'(a int, b int, c int)'
      ( b'('
      parameter_declaration b'a int'
        identifier b'a'
        type_identifier b'int'
      , b','
      parameter_declaration b'b int'
        identifier b'b'
        type_identifier b'int'
      , b','
      parameter_declaration b'c int'
        identifier b'c'
        type_identifier b'int'
      ) b')'
    block b'{\n            return a + b - ...'
      { b'{'
      return_statement b'return a + b - c'
        return b'return'
        expression_list b'a + b - c'
          binary_expression b'a + b - c'
            binary_expression b'a + b'
              identifier b'a'
              + b'+'
              identifier b'b'
            - b'-'
            identifier b'c'
      
 b'\n'
      } b'}'
 
 b'\n'

*** JAVASCRIPT ***
program b'function test(a, b, c) {\n    ...'
  function_declaration b'function test(a, b, c) {\n    ...'
    function b'function'
    identifier b'test'
    formal_parameters b'(a, b, c)'
      ( b'('
      identifier b'a'
      , b','
      identifier b'b'
      , b','
      identifier b'c'
      ) b')'
    statement_block b'{\n            return a + b - ...'
      { b'{'
      return_statement b'return a + b - c;'
        return b'return'
        binary_expression b'a + b - c'
          binary_expression b'a + b'
            identifier b'a'
            + b'+'
            identifier b'b'
          - b'-'
          identifier b'c'
        ; b';'
      } b'}'

*** PHP ***
program b'<?php\n        function test($...'
  php_tag b'<?php'
  function_definition b'function test($a, $b, $c) {\n ...'
    function b'function'
    name b'test'
    formal_parameters b'($a, $b, $c)'
      ( b'('
      simple_parameter b'$a'
        variable_name b'$a'
          $ b'$'
          name b'a'
      , b','
      simple_parameter b'$b'
        variable_name b'$b'
          $ b'$'
          name b'b'
      , b','
      simple_parameter b'$c'
        variable_name b'$c'
          $ b'$'
          name b'c'
      ) b')'
    compound_statement b'{\n            return $a + $b ...'
      { b'{'
      return_statement b'return $a + $b - $c;'
        return b'return'
        binary_expression b'$a + $b - $c'
          binary_expression b'$a + $b'
            variable_name b'$a'
              $ b'$'
              name b'a'
            + b'+'
            variable_name b'$b'
              $ b'$'
              name b'b'
          - b'-'
          variable_name b'$c'
            $ b'$'
            name b'c'
        ; b';'
      } b'}'

*** PYTHON ***
module b'def test(a: int, b: int, c: i...'
  function_definition b'def test(a: int, b: int, c: i...'
    def b'def'
    identifier b'test'
    parameters b'(a: int, b: int, c: int)'
      ( b'('
      typed_parameter b'a: int'
        identifier b'a'
        : b':'
        type b'int'
          identifier b'int'
      , b','
      typed_parameter b'b: int'
        identifier b'b'
        : b':'
        type b'int'
          identifier b'int'
      , b','
      typed_parameter b'c: int'
        identifier b'c'
        : b':'
        type b'int'
          identifier b'int'
      ) b')'
    -> b'->'
    type b'int'
      identifier b'int'
    : b':'
    block b'return a + b - c'
      return_statement b'return a + b - c'
        return b'return'
        binary_operator b'a + b - c'
          binary_operator b'a + b'
            identifier b'a'
            + b'+'
            identifier b'b'
          - b'-'
          identifier b'c'

*** RUST ***
source_file b'fn test(a: i32, b: i32, c: i3...'
  function_item b'fn test(a: i32, b: i32, c: i3...'
    fn b'fn'
    identifier b'test'
    parameters b'(a: i32, b: i32, c: i32)'
      ( b'('
      parameter b'a: i32'
        identifier b'a'
        : b':'
        primitive_type b'i32'
      , b','
      parameter b'b: i32'
        identifier b'b'
        : b':'
        primitive_type b'i32'
      , b','
      parameter b'c: i32'
        identifier b'c'
        : b':'
        primitive_type b'i32'
      ) b')'
    -> b'->'
    primitive_type b'i32'
    block b'{\n            return a + b - ...'
      { b'{'
      expression_statement b'return a + b - c;'
        return_expression b'return a + b - c'
          return b'return'
          binary_expression b'a + b - c'
            binary_expression b'a + b'
              identifier b'a'
              + b'+'
              identifier b'b'
            - b'-'
            identifier b'c'
        ; b';'
      } b'}'

Структура дерева сначала выглядит очень страшно, но к этому быстро привыкаешь, ведь это не так сложно, как кажется. Вложенность (зависимости родитель-потомки) узлов дерева показана уровнем индентации (количеством пробелов) с начала строки. После пробелов идет тип узла дерева, а за типом небольшой фрагмент исходного кода, который соответствует этому узлу. Обратите внимание, что чтобы избежать слишком длинных строк, я обрезаю этот фрагмент до 32 символов, если фрагмент был обрезан, то он заканчивается на «...», что логично, наверное.

Изучив структуру AST-дерева вы можете понять, какие его узлы вас будут интересовать для реализации того, или иного алгоритма обфускации или морфинга. Чтобы произвести какую-то работу над определенным узлом вам нужно определить в своем классе метод «visit_<тип_узла>». Базовый класс при посещении узлов увидит ваши методы и вызовет их для узлов необходимого типа. Это реализуется через рефлекшн над атрибутами класса (такое страшное метапрограммирование, ууу), но не суть важно. Давайте рассмотрим простой пример (example2.py):
Python:
import tree_rewriter
import tree_sitter

class Inserter(tree_rewriter.Rewriter):
    def __init__(self):
        super().__init__("c")

    def visit_return_statement(self, node: tree_sitter.Node):
        self.insert_before(node, "/* BEFORE RETURN */")
        self.insert_after(node, "/* AFTER RETURN */")
        return False

class Replacer(tree_rewriter.Rewriter):
    def __init__(self):
        super().__init__("c")

    def visit_return_statement(self, node: tree_sitter.Node):
        self.replace_node(node, "return 0;")
        return False


src = '''
void main(int argc, char** argv) {
    return 100;
}
'''

inserter = Inserter()
replacer = Replacer()
print(inserter.visit(src))
print(replacer.visit(src))

В этом примере я объявил два класса, которые показывают, как происходит замена узлов на произвольный код (Replacer) и вставка произвольного кода до и после узла (Inserter). По сути, этих трех примитивных действий над кодом достаточно, чтобы реализовать достаточно сложные алгоритмы обфускации. И там, и там я определил метод «visit_return_statement», который автоматически вызывается, когда базовый класс встречает в дереве узел типа «return_statement», то есть выражение return. Методы с говорящими названиями заменяют узел (replace_node), вставляют код до узла (insert_before) или после узла (insert_after). Давайте посмотрим, что выходит из этого в результате:
Код:
> python example2.py

void main(int argc, char** argv) {
    /* BEFORE RETURN */return 100;/* AFTER RETURN */
}


void main(int argc, char** argv) {
    return 0;
}

Важно понимать, что tree-sitter — это библиотека, предназначенная исключительно для быстрого парсинга, трансформировать AST-дерево обратно в код она не умеет. Поэтому Rewriter запоминает все правки исходного кода, а затем после посещения всех узлов сортирует и применяет правки к исходному коду. Это простой и быстрый алгоритм, использовать что-то заумнее для tree-sitter’а для меня не представляется возможным и необходимым. У такого подхода есть один недостаток, если вы подменяете какой-то узел, то добавлять правки для дочерних для него узлов не имеет никакого смысла, вы просто останетесь с поломанным кодом в результате. Поэтому, я рекомендую разбить обфускатор на небольшие алгоритмы, которые применяются отдельно друг от друга, но друг за другом. Это мы рассмотрим чуть позднее.

А теперь, давайте приступать к рассмотрению типичных для обфускаторов и морферов алгоритмов трансформации AST-деревьев. Самая простая и очевидная вещь, которую мы могли бы сделать, это — зашифровать строки. Очень часто сигнатурный детект аверов использует константные строки. В примерах я буду показывать, как осуществлять трансформацию кода, а сами алгоритмы обфускации я предлагаю придумать вам самим. Рассмотрим example3.py:
Python:
import tree_rewriter
import tree_sitter

class StringEncryptor(tree_rewriter.Rewriter):
    def __init__(self):
        super().__init__("python")

    def visit_string(self, node: tree_sitter.Node):
        encrypted = "decryptString(/* encrypted string here */)"
        self.replace_node(node, encrypted)
        return False

src = '''
def doSomeShit():
    string = "I'm a string, bitches!"
    doSomeShitWithString("Some other string")
    return "Gonna return another string"
'''

print(StringEncryptor().visit(src))

Здесь я реализовал класс StringEncryptor, который заменяет константные строки, которые на языке разработчиков языков программирования часто называют строковыми литералами, но в грамматике Петона их назвали просто «string». Строковые литералы мы зашифровываем и заменяем на вызов функции расшифровки. Это, можно сказать, классика. Не то чтобы нетленная, но не важно. Опять же сам алгоритм шифрования и реализация функции «decryptString» остается за вами:
Код:
> python example3.py

def doSomeShit():
    string = decryptString(/* encrypted string here */)
    doSomeShitWithString(decryptString(/* encrypted string here */))
    return decryptString(/* encrypted string here */)

Выглядит все очень просто, но есть нюанс. Дело в том, что являясь библиотекой для парсинга грамматика tree-sitter часто не позволяет получить непосредственное значение строкового литерала, чтобы его можно было удобно и просто зашифровать. Что я имею ввиду? Как насчет ескейп символов в строке? У узла есть свойство text, которое возвращает строку так, как она была написана в исходнике, а не так, как ее понимает язык программирования. Давайте посмотрим на примере example4:
Python:
import tree_rewriter
import tree_sitter

class StringEncryptor(tree_rewriter.Rewriter):
    def __init__(self):
        super().__init__("javascript")

    def visit_string(self, node: tree_sitter.Node):
        print("unescape:\n", self.unescape(node.text[1:-1]))
        print("my_unescape:\n", self.my_unescape(node))
        
        encrypted = "decryptString(/* encrypted string */)"
        self.replace_node(node, encrypted)
        return False
    
    def my_unescape(self, node: tree_sitter.Node) -> str:
        result = ''
        for i in range(1, len(node.children) - 1):
            child = node.children[i]
            if child.type == "string_fragment":
                result += child.text.decode()
            elif child.type == "escape_sequence":
                if child.text == b'\\n': result += '\n'
                elif child.text == b'\\t': result += '\t'
                elif child.text.startswith(b'\\x'):
                    byte = child.text[2:4].decode()
                    result += chr(int(byte, 16))
                elif child.text.startswith(b"\\u"):
                    word = child.text[2:6].decode()
                    result += chr(int(word, 16))
                
        return result

src = '''
function doSomeShit() {
    return "Escaped as fuck, bro: \\n\\t\\x61\\x62\\x63\\x64\\u0410\\u0411\\u0412\\u0413 yo!";
}
'''

#tree_rewriter.Dumper("javascript").visit(src)
print(StringEncryptor().visit(src))

Обратите внимание, что в строке есть разного рода ескейп символы. Разбор строки для каждого языка по хорошему должен быть свой, однако для вас я заботливо реализовал метод escape, который корректно отработает для многих языков программирования, поскольку много где эти эскеп последовательности описываются примерно одинаково. Что делать, если метод escape не тащит нужные вам эскейп символы? Я сделал пример для языка JavaScript в виде метода my_escape. Парсер языка JavaScript разбирает строку, преобразуя ее в последовательность узлов типа string_fragment (фрагмент строки) и escape_sequence (эскейп символ или символы), разбирая эту структуру можно руками составить строку (см. AST-дерево этого кода):
Код:
> python example4.py
unescape:
 Escaped as fuck, bro:
        abcdАБВГ yo!
my_unescape:
 Escaped as fuck, bro:
        abcdАБВГ yo!

function doSomeShit() {
    return decryptString(/* encrypted string */);
}

Дальше чуть больше. Языки со сборщиками мусора и с нормальной реализацией строк — это, конечно, хорошо, но что делать с Цэ и Плюсами. Там же нет сборщика мусора, который подчистит за нами расшифрованную строку. За годы изучения разных обфускаторов и морферов я видел разные подходы. Кто-то просто высирал расшифрованные строки на кучу процесса и никогда не освобождал выделенную таким образом память. Кто-то делал глобальные буферы в секции данных исполняемого файла. Но, наверное, наиболее простым и понятным способом (на мой взгляд) является просто выделить буфер на стеке, но для этого тоже потребуется немножно поработать, смотрим example5:
Python:
import tree_rewriter
import tree_sitter

class StringEncryptor(tree_rewriter.Rewriter):
    def __init__(self):
        super().__init__("cpp")
        self._counter = 0

    def visit_string_literal(self, node: tree_sitter.Node):       
        string = node.text.decode()
        is_unicode = string.startswith('L')
        
        if is_unicode:
            string = self.unescape(string[2:-1])
        else: string = self.unescape(string[1:-1])

        self._counter += 1

        type_name = 'WCHAR' if is_unicode else 'CHAR'
        buffer_name = "__buf_{0}".format(self._counter)
        buffer_len = len(string) + 1

        insert_point = self.find_parent(node, "compound_statement")
        insert_point = insert_point.children[1]

        decrypt_name = 'DecryptStringW' if is_unicode else 'DecryptStringA'
        insert  = '{0} {1}[{2}];\n'.format(type_name, buffer_name, buffer_len)
        insert += '{0}({1}, /* encrypted */, {2});\n'.format(decrypt_name, buffer_name, buffer_len)

        self.insert_before(insert_point, insert)
        self.replace_node(node, buffer_name)
        return False

src = '''
#include <windows.h>

int main(int argc, char** argv) {
    MessageBoxA(NULL, "Hello World!", NULL, MB_OK);
    MessageBoxW(NULL, L"Hello World!", NULL, MB_OK);
    return 0;
}
'''

#tree_rewriter.Dumper("cpp").visit(src)
print(StringEncryptor().visit(src))

Чтобы определить, куда нам нужно вставить код с выделением буфера на стеке и расшифровки строки, нам нужно найти ближайшего к строковому литералу родителя типа compound_statement (это, условно, блок кода, находящегося между фигурными скобками). Для этого у нас есть метод find_parent. Если у узла нет родителя нужного типа, то метод вернет None, при желании можно сделать проверку на это, а можно и просто упасть в эксепшн. Затем у этого родителя нам нужно взять потомка с индексом 1, так как индекс 0 — это сама фигурная скобка, и вставить перед ним определение буфера и вызов кода для расшифровки строки, а сам строковый литерал мы можем заменить на имя переменной буфера:
Код:
> python example5.py

#include <windows.h>

int main(int argc, char** argv) {
    CHAR __buf_1[13];
DecryptStringA(__buf_1, /* encrypted */, 13);
WCHAR __buf_2[13];
DecryptStringW(__buf_2, /* encrypted */, 13);
MessageBoxA(NULL, __buf_1, NULL, MB_OK);
    MessageBoxW(NULL, __buf_2, NULL, MB_OK);
    return 0;
}

Пока я умолчал о ряде других проблем с шифрованием строк в Цэ и Плюсах, но этот код в принципе показывает также, как отличить ANSI-строку от юникода, как получить размер для буфера на стеке и так далее. Элитные разберутся, а мы с вами переходим к следующему примеру. В скриптовых языках при обфускации не забывают о переименовании идентификаторов, чтобы товарищу реверсеру не было так легко жить. Я думаю, что причины этого ясны, давайте рассмотрим example6:
Python:
import tree_rewriter
import tree_sitter

class RenameIdentifiers(tree_rewriter.Rewriter):
    def __init__(self):
        super().__init__("php")

    def visit_name(self, node: tree_sitter.Node):
        old_name = node.text.decode()
        new_name = "new_name_for_{0}".format(old_name)
        self.replace_node(node, new_name)

        return False

src = '''
<?php
function function1($a, $b) {
    return $a + $b;
}

function function2($b, $c) {
    return $b - $c;
}
'''

#tree_rewriter.Dumper("php").visit(src)
print(RenameIdentifiers().visit(src))

Здесь для каждого идентификатора (узла name в PHP-парсере) я генерирую демонстративно другое имя. Алгоритмы генерации имен, как минимальных, так и нечитаемых, так и по словарю не являются рокет-саенсом и давно уже есть на гитхабах. Посмотрим, что мы в итоге получили:
Код:
> python example6.py

<?php
function new_name_for_function1($new_name_for_a, $new_name_for_b) {
    return $new_name_for_a + $new_name_for_b;
}

function new_name_for_function2($new_name_for_b, $new_name_for_c) {
    return $new_name_for_b - $new_name_for_c;
}

В следующем примере я взял один файл из исходников ратника Quasar, чтобы показать на примере, как можно разбавлять исходные коды мусорными кодами. Мы просто берем все блоки кода и перед всеми их детишками (кроме комментов) вставляем сгенерированный мусорный код. Ну в моем случае это — просто комментарий, опять же реализацию алгоритма генерации мусорного кода я оставляю за вами:
Python:
import tree_rewriter
import tree_sitter

class TrashGenerator(tree_rewriter.Rewriter):
    def __init__(self):
        super().__init__("c_sharp")

    def visit_block(self, node: tree_sitter.Node):
        for i in range(1, len(node.children) - 1):
            if node.children[i].type == "comment": continue
            self.insert_before(node.children[i], self.generate_trash())

    def generate_trash(self):
        return "/* trash code here */ "

src = '''
using System;
using System.IO;
using System.Security.Cryptography;
using System.Text;

namespace Quasar.Common.Cryptography
{
    public class Aes256
    {
        private const int KeyLength = 32;
        private const int AuthKeyLength = 64;
        private const int IvLength = 16;
        private const int HmacSha256Length = 32;
        private readonly byte[] _key;
        private readonly byte[] _authKey;

        private static readonly byte[] Salt =
        {
            0xBF, 0xEB, 0x1E, 0x56, 0xFB, 0xCD, 0x97, 0x3B, 0xB2, 0x19, 0x2, 0x24, 0x30, 0xA5, 0x78, 0x43, 0x0, 0x3D, 0x56,
            0x44, 0xD2, 0x1E, 0x62, 0xB9, 0xD4, 0xF1, 0x80, 0xE7, 0xE6, 0xC3, 0x39, 0x41
        };

        public Aes256(string masterKey)
        {
            if (string.IsNullOrEmpty(masterKey))
                throw new ArgumentException($"{nameof(masterKey)} can not be null or empty.");

            using (Rfc2898DeriveBytes derive = new Rfc2898DeriveBytes(masterKey, Salt, 50000))
            {
                _key = derive.GetBytes(KeyLength);
                _authKey = derive.GetBytes(AuthKeyLength);
            }
        }

        public string Encrypt(string input)
        {
            return Convert.ToBase64String(Encrypt(Encoding.UTF8.GetBytes(input)));
        }

        /* FORMAT
         * ----------------------------------------
         * |     HMAC     |   IV   |  CIPHERTEXT  |
         * ----------------------------------------
         *     32 bytes    16 bytes
         */
        public byte[] Encrypt(byte[] input)
        {
            if (input == null)
                throw new ArgumentNullException($"{nameof(input)} can not be null.");

            using (var ms = new MemoryStream())
            {
                ms.Position = HmacSha256Length; // reserve first 32 bytes for HMAC
                using (var aesProvider = new AesCryptoServiceProvider())
                {
                    aesProvider.KeySize = 256;
                    aesProvider.BlockSize = 128;
                    aesProvider.Mode = CipherMode.CBC;
                    aesProvider.Padding = PaddingMode.PKCS7;
                    aesProvider.Key = _key;
                    aesProvider.GenerateIV();

                    using (var cs = new CryptoStream(ms, aesProvider.CreateEncryptor(), CryptoStreamMode.Write))
                    {
                        ms.Write(aesProvider.IV, 0, aesProvider.IV.Length); // write next 16 bytes the IV, followed by ciphertext
                        cs.Write(input, 0, input.Length);
                        cs.FlushFinalBlock();

                        using (var hmac = new HMACSHA256(_authKey))
                        {
                            byte[] hash = hmac.ComputeHash(ms.ToArray(), HmacSha256Length, ms.ToArray().Length - HmacSha256Length); // compute the HMAC of IV and ciphertext
                            ms.Position = 0; // write hash at beginning
                            ms.Write(hash, 0, hash.Length);
                        }
                    }
                }

                return ms.ToArray();
            }
        }

        public string Decrypt(string input)
        {
            return Encoding.UTF8.GetString(Decrypt(Convert.FromBase64String(input)));
        }

        public byte[] Decrypt(byte[] input)
        {
            if (input == null)
                throw new ArgumentNullException($"{nameof(input)} can not be null.");

            using (var ms = new MemoryStream(input))
            {
                using (var aesProvider = new AesCryptoServiceProvider())
                {
                    aesProvider.KeySize = 256;
                    aesProvider.BlockSize = 128;
                    aesProvider.Mode = CipherMode.CBC;
                    aesProvider.Padding = PaddingMode.PKCS7;
                    aesProvider.Key = _key;

                    // read first 32 bytes for HMAC
                    using (var hmac = new HMACSHA256(_authKey))
                    {
                        var hash = hmac.ComputeHash(ms.ToArray(), HmacSha256Length, ms.ToArray().Length - HmacSha256Length);
                        byte[] receivedHash = new byte[HmacSha256Length];
                        ms.Read(receivedHash, 0, receivedHash.Length);

                        if (!SafeComparison.AreEqual(hash, receivedHash))
                            throw new CryptographicException("Invalid message authentication code (MAC).");
                    }

                    byte[] iv = new byte[IvLength];
                    ms.Read(iv, 0, IvLength); // read next 16 bytes for IV, followed by ciphertext
                    aesProvider.IV = iv;

                    using (var cs = new CryptoStream(ms, aesProvider.CreateDecryptor(), CryptoStreamMode.Read))
                    {
                        byte[] temp = new byte[ms.Length - IvLength + 1];
                        byte[] data = new byte[cs.Read(temp, 0, temp.Length)];
                        Buffer.BlockCopy(temp, 0, data, 0, data.Length);
                        return data;
                    }
                }
            }
        }
    }
}
'''

#tree_rewriter.Dumper("c_sharp").visit(src)
print(TrashGenerator().visit(src))
Код:
> python example7.py

using System;
using System.IO;
using System.Security.Cryptography;
using System.Text;

namespace Quasar.Common.Cryptography
{
    public class Aes256
    {
        private const int KeyLength = 32;
        private const int AuthKeyLength = 64;
        private const int IvLength = 16;
        private const int HmacSha256Length = 32;
        private readonly byte[] _key;
        private readonly byte[] _authKey;

        private static readonly byte[] Salt =
        {
            0xBF, 0xEB, 0x1E, 0x56, 0xFB, 0xCD, 0x97, 0x3B, 0xB2, 0x19, 0x2, 0x24, 0x30, 0xA5, 0x78, 0x43, 0x0, 0x3D, 0x56,
            0x44, 0xD2, 0x1E, 0x62, 0xB9, 0xD4, 0xF1, 0x80, 0xE7, 0xE6, 0xC3, 0x39, 0x41
        };

        public Aes256(string masterKey)
        {
            /* trash code here */ if (string.IsNullOrEmpty(masterKey))
                throw new ArgumentException($"{nameof(masterKey)} can not be null or empty.");

            /* trash code here */ using (Rfc2898DeriveBytes derive = new Rfc2898DeriveBytes(masterKey, Salt, 50000))
            {
                /* trash code here */ _key = derive.GetBytes(KeyLength);
                /* trash code here */ _authKey = derive.GetBytes(AuthKeyLength);
            }
        }

        public string Encrypt(string input)
        {
            /* trash code here */ return Convert.ToBase64String(Encrypt(Encoding.UTF8.GetBytes(input)));
        }

        /* FORMAT
         * ----------------------------------------
         * |     HMAC     |   IV   |  CIPHERTEXT  |
         * ----------------------------------------
         *     32 bytes    16 bytes
         */
        public byte[] Encrypt(byte[] input)
        {
            /* trash code here */ if (input == null)
                throw new ArgumentNullException($"{nameof(input)} can not be null.");

            /* trash code here */ using (var ms = new MemoryStream())
            {
                /* trash code here */ ms.Position = HmacSha256Length; // reserve first 32 bytes for HMAC
                /* trash code here */ using (var aesProvider = new AesCryptoServiceProvider())
                {
                    /* trash code here */ aesProvider.KeySize = 256;
                    /* trash code here */ aesProvider.BlockSize = 128;
                    /* trash code here */ aesProvider.Mode = CipherMode.CBC;
                    /* trash code here */ aesProvider.Padding = PaddingMode.PKCS7;
                    /* trash code here */ aesProvider.Key = _key;
                    /* trash code here */ aesProvider.GenerateIV();

                    /* trash code here */ using (var cs = new CryptoStream(ms, aesProvider.CreateEncryptor(), CryptoStreamMode.Write))
                    {
                        /* trash code here */ ms.Write(aesProvider.IV, 0, aesProvider.IV.Length); // write next 16 bytes the IV, followed by ciphertext
                        /* trash code here */ cs.Write(input, 0, input.Length);
                        /* trash code here */ cs.FlushFinalBlock();

                        /* trash code here */ using (var hmac = new HMACSHA256(_authKey))
                        {
                            /* trash code here */ byte[] hash = hmac.ComputeHash(ms.ToArray(), HmacSha256Length, ms.ToArray().Length - HmacSha256Length); // compute the HMAC of IV and ciphertext
                            /* trash code here */ ms.Position = 0; // write hash at beginning
                            /* trash code here */ ms.Write(hash, 0, hash.Length);
                        }
                    }
                }

                /* trash code here */ return ms.ToArray();
            }
        }

        public string Decrypt(string input)
        {
            /* trash code here */ return Encoding.UTF8.GetString(Decrypt(Convert.FromBase64String(input)));
        }

        public byte[] Decrypt(byte[] input)
        {
            /* trash code here */ if (input == null)
                throw new ArgumentNullException($"{nameof(input)} can not be null.");

            /* trash code here */ using (var ms = new MemoryStream(input))
            {
                /* trash code here */ using (var aesProvider = new AesCryptoServiceProvider())
                {
                    /* trash code here */ aesProvider.KeySize = 256;
                    /* trash code here */ aesProvider.BlockSize = 128;
                    /* trash code here */ aesProvider.Mode = CipherMode.CBC;
                    /* trash code here */ aesProvider.Padding = PaddingMode.PKCS7;
                    /* trash code here */ aesProvider.Key = _key;

                    // read first 32 bytes for HMAC
                    /* trash code here */ using (var hmac = new HMACSHA256(_authKey))
                    {
                        /* trash code here */ var hash = hmac.ComputeHash(ms.ToArray(), HmacSha256Length, ms.ToArray().Length - HmacSha256Length);
                        /* trash code here */ byte[] receivedHash = new byte[HmacSha256Length];
                        /* trash code here */ ms.Read(receivedHash, 0, receivedHash.Length);

                        /* trash code here */ if (!SafeComparison.AreEqual(hash, receivedHash))
                            throw new CryptographicException("Invalid message authentication code (MAC).");
                    }

                    /* trash code here */ byte[] iv = new byte[IvLength];
                    /* trash code here */ ms.Read(iv, 0, IvLength); // read next 16 bytes for IV, followed by ciphertext
                    /* trash code here */ aesProvider.IV = iv;

                    /* trash code here */ using (var cs = new CryptoStream(ms, aesProvider.CreateDecryptor(), CryptoStreamMode.Read))
                    {
                        /* trash code here */ byte[] temp = new byte[ms.Length - IvLength + 1];
                        /* trash code here */ byte[] data = new byte[cs.Read(temp, 0, temp.Length)];
                        /* trash code here */ Buffer.BlockCopy(temp, 0, data, 0, data.Length);
                        /* trash code here */ return data;
                    }
                }
            }
        }
    }
}

Ну и в качестве заключения я расскажу, как мне кажется самым адекватным организовывать архитектуру проектов обфускаторов и морферов. Разделите весь процесс трансформации кода на небольшие проходы, каждый из которых трансформирует код по своему алгоритму. Затем соберите все реализованные алгоритмы в список и примените их друг за другом. Таким образом, например, просто закомментировав алгоритм в списке вы можете его отключить, это становится очень удобным для отладки проблем, когда ваш алгоритм морфинга что-то поломает в исходном коде. Такие ситуации случаются, поверьте мне. Смотрим example8.py:
Python:
import tree_rewriter

class StringEncryption(tree_rewriter.Rewriter):
    def __init__(self):
        super().__init__("language")

    # implementation here

class ControlFlowFlattening(tree_rewriter.Rewriter):
    def __init__(self):
        super().__init__("language")

    # implementation here

class TrashGeneration(tree_rewriter.Rewriter):
    def __init__(self):
        super().__init__("language")

    # implementation here
    
class NamesRenaming(tree_rewriter.Rewriter):
    def __init__(self):
        super().__init__("language")

    # implementation here

class SomeOtherPass1(tree_rewriter.Rewriter):
    def __init__(self):
        super().__init__("language")

    # implementation here

class SomeOtherPass2(tree_rewriter.Rewriter):
    def __init__(self):
        super().__init__("language")

    # implementation here

with open("input_path", "rb") as source_file:
    source = source_file.read()

passes = [
    StringEncryption(),
    ControlFlowFlattening(),
    #TrashGeneration(),
    NamesRenaming(),
    SomeOtherPass1(),
    SomeOtherPass2(),
]

for one_pass in passes:
    source = one_pass.visit(source)

with open("output_path", "wb") as output_file:
    output_file.write(source)

Вот, собственно, и всё. Желаю вам удачи и терпения в изучении алгоритмов обфускации и морфинга кода. Надеюсь, что моя маленькая библиотечка покажется вам удобной и полезной. Если что, репортите баги и задавайте вопросы. Эта статья была написана специально для вас, моего любимого комьюнити xss.pro! ❤️❤️❤️
 

Вложения

  • TreeRewriter.zip
    6.1 КБ · Просмотры: 127
Пожалуйста, обратите внимание, что пользователь заблокирован
если этому парню не дадут призовых, отправлю подарок сам. Спасибо старина. напиши мне в пм кош в бтц, будь так любезен.
 
Последнее редактирование:
Нельзя ли показать реальный пример полученного обфусцированного кода, потому что просто /* trash code here */ в каждой строке - это же смешно, такое можно и регулярками вставить. Создание АСД для КС-грамматических языков это в принципе тривиальная задача, с ней обычный средний программист справится примерно также быстро, как с прикручиванием и отладкой заброшенной библиотеки. То есть преимущества использования этой штуки не очевидны.
 
Пожалуйста, обратите внимание, что пользователь заблокирован
Нельзя ли показать реальный пример полученного обфусцированного кода, потому что просто /* trash code here */ в каждой строке - это же смешно, такое можно и регулярками вставить
Ну покажи мне, как ты регулярками вставишь trash code here в нужные места, допустим в тот же исходник на С#, не поломав его.

Создание АСД для КС-грамматических языков это в принципе тривиальная задача, с ней обычный средний программист справится примерно также быстро, как с прикручиванием и отладкой заброшенной библиотеки
Опять же, попробуй написать корректные парсеры для все приведенных в примерах языков, а потом расскажи мне, сколько времени у тебя это заняло.
 
Ну покажи мне, как ты регулярками вставишь trash code here в нужные места, допустим в тот же исходник на С#, не поломав его.
Ответ вопросом на вопрос что означает? Что реально никакого морфера то нету? :) Как регулярками вставлю - показываю псевдокод:
10 разбить текст на список выражений
20 вставить мусор
30 собрать текст
40 при неправильной вставке, подправить пункт 10
Думаю на всё про всё полдня уйдёт.
Опять же, попробуй написать корректные парсеры для все приведенных в примерах языков, а потом расскажи мне, сколько времени у тебя это заняло.
Зачем мне парсеры для ВСЕХ языков? Для нужных уже есть, заняло времени - из книжки перепечатать.
 
Пожалуйста, обратите внимание, что пользователь заблокирован
10 разбить текст на список выражений
Вот уже это ты не сделаешь регулярными выражениями в общем случае. Ты можешь либо руками вписать комменты или еще какие-то вещи, которые помогут тебе разобрать код регулярками, или делать каскады из регулярок, которые рекурсивно вызывают регулярки, но ты просто замучаешься, но не получишь и десятой части результата, который тебе даст полноценный парсер. Если не понимаешь этого, то тебе, чтобы понять, нужно попробовать это сделать. Поэтому я тебе и предлагаю это реализовать, а не писать словами алгоритмов, которые не будут работать.

Зачем мне парсеры для ВСЕХ языков? Для нужных уже есть, заняло времени - из книжки перепечатать
Держи у курсе. Эта библиотека решает задачу парсинга на достаточном уровне почти любого языка, кроме самой жуткой маргинальщины. Тебе не нужно, мне нужно.

Что реально никакого морфера то нету?
Здесь не будет.
 
Вот уже это ты не сделаешь регулярными выражениями в общем случае.
В общем нет, на практике покрывающей 80% случаев - да и легко. В общем случае скорей всего и библиотека имеет какие-то изъяны, особенно с языками которые активно перепиливают.
alex778 сказал(а):

Что реально никакого морфера то нету?

Здесь не будет.
Жаль, потому что это реально самое интересное, что могло бы быть в статье.
 
Пожалуйста, обратите внимание, что пользователь заблокирован
В общем нет, на практике покрывающей 80% случаев - да и легко
Я бы сказал, что это очень оптимистичный прогноз. Возьми, например, пример с шифрованием строк в Цэ/Плюсах. Вот, допустим, ты нашел регуляркой строковый литерал, хотя для этого потребуется нетривиальная регулярка (тк она должна учитывать, что, например, в строке могут быть эскейп символы, включая кавычку). Как ты регуляркой найдешь родительский блок от этой строки, чтобы вставить в него код с расшифровкой? Тебе надо будет сначала отделить все блоки, а уже в них искать строки. Но блоки могут включать другие блоки, и твоя регулярка должна как-то определять, какая фигурная скобка закрывает текущий блок, а какая вложенный в него блок. Это должна быть довольно замороченная регулярка, если такое вообще возможно в регулярках выразить. Рекурсия в тексте - это вообще не про регулярки, мне кажется.

В общем случае скорей всего и библиотека имеет какие-то изъяны, особенно с языками которые активно перепиливают
Ну вопрос в том, какие языки активно перепиливают, и есть ли смысл писать что-то на языках, которые могут перепелить, что твои тысячи строк перестанут собираться с каждой новой версией? Но в целом да, в любом парсере или в грамматике может быть ошибка. Как альтернативу можно было бы рассмотреть ANTLR, но это менее удобная библиотека, чем tree-sitter на мой вкус.
 
Пожалуйста, обратите внимание, что пользователь заблокирован
В целом такое можно сделать и для С\С++ на LLVM да и наверно для любого компилятора.

Выглядеть будет так

source --> AST --> IR --> binary file

Так вот на этапе IR можно инструментировать код. Получится морфер по технологии Compile-Time Instrumentation (CTI).
Далеко ходить не надо, по такому принципу работает AddressSanitizer он инструментирует код. т.е. вставляет в код проверки. Думаю на его основе можно сделать ключик для компилятора который будет морфить в процессе компиляции ну вы поняли =)

Дальше уже в планировщик пихаем запуск компилятора с ключом и грузим его на фтп со скриптом автозабора файла и вуаля.
 
В целом такое можно сделать и для С\С++ на LLVM да и наверно для любого компилятора.

Выглядеть будет так

source --> AST --> IR --> binary file

Так вот на этапе IR можно инструментировать код. Получится морфер по технологии Compile-Time Instrumentation (CTI).
Далеко ходить не надо, по такому принципу работает AddressSanitizer он инструментирует код. т.е. вставляет в код проверки. Думаю на его основе можно сделать ключик для компилятора который будет морфить в процессе компиляции ну вы поняли =)

Дальше уже в планировщик пихаем запуск компилятора с ключом и грузим его на фтп со скриптом автозабора файла и вуаля.
https://github.com/tree-sitter/tree-sitter-cpp может то что нада

даже для Rust есть https://crates.io/crates/tree-sitter-cpp

пример запуска main.rs
Код:
extern crate tree_sitter;
extern crate tree_sitter_cpp;

use tree_sitter::{Parser, Node};

fn main() {
    let code = r#"
    #include <iostream>
    #include <vector>
    
    class Solution {
    public:
        double findMedianSortedArrays(std::vector<int>& nums1, std::vector<int>& nums2) {
            int m = nums1.size(), n = nums2.size();
            if(m > n) return findMedianSortedArrays(nums2, nums1);
            int lo = 0, hi = m, mid = (m + n + 1)/2;
            while(lo <= hi){
                int i = (lo + hi)/2;
                int j = mid - i;
                if(i < m && nums2[j - 1] > nums1[i])
                    lo = i + 1;
                else if(i > 0  && nums1[i - 1] > nums2[j])
                    hi = i - 1;
                else{
                    int maxLeft = (i == 0) ? nums2[j - 1] : (j == 0) ? nums1[i - 1] : std::max(nums1[i - 1], nums2[j - 1]);
                    int minRight = (i == m) ? nums2[j] : (j == n) ? nums1[i] : std::min(nums1[i], nums2[j]);
                    return (m + n) % 2 ? maxLeft : (maxLeft + minRight) / 2.0;
                }
            }
        }
    };
    
    int main() {
        std::vector<int> nums1 = {1, 3};
        std::vector<int> nums2 = {2};
        Solution solution;
        double median = solution.findMedianSortedArrays(nums1, nums2);
        std::cout << "Median: " << median << std::endl;
        return 0;
    }
        "#;

    let mut parser = Parser::new();
    parser.set_language(tree_sitter_cpp::language()).expect("Error loading CPP grammar");
    let parsed = parser.parse(code, None).expect("Parsing error");

    let root_node = parsed.root_node();
    println!("Root node: {:?}", root_node);

    // Traverse the syntax tree
    traverse_node(root_node, 0, code);
}

fn traverse_node(node: Node, level: usize, code: &str) {
    // Print information about the current node
    println!("{:indent$}Node: {} {}", "", node.kind(), node.utf8_text(code.as_bytes()).unwrap(), indent = level * 2);

    // Iterate over named children of the node
    let mut cursor = node.walk();
    for child in node.named_children(&mut cursor) {
        // Recursively call traverse_node with the child node, increased indentation level, and the same code reference
        traverse_node(child, level + 1, code);
    }
}
Cargo.toml
Код:
tree-sitter = "0.20.10"
tree-sitter-cpp = "0.20.3"

выдаст в консоли
Код:
Root node: {Node translation_unit (1, 4) - (34, 8)}
Node: translation_unit #include <iostream>
    #include <vector>
    
    class Solution {
    public:
        double findMedianSortedArrays(std::vector<int>& nums1, std::vector<int>& nums2) {
            int m = nums1.size(), n = nums2.size();
            if(m > n) return findMedianSortedArrays(nums2, nums1);
            int lo = 0, hi = m, mid = (m + n + 1)/2;
            while(lo <= hi){
                int i = (lo + hi)/2;
                int j = mid - i;
                if(i < m && nums2[j - 1] > nums1[i])
                    lo = i + 1;
                else if(i > 0  && nums1[i - 1] > nums2[j])
                    hi = i - 1;
                else{
                    int maxLeft = (i == 0) ? nums2[j - 1] : (j == 0) ? nums1[i - 1] : std::max(nums1[i - 1], nums2[j - 1]);
                    int minRight = (i == m) ? nums2[j] : (j == n) ? nums1[i] : std::min(nums1[i], nums2[j]);
                    return (m + n) % 2 ? maxLeft : (maxLeft + minRight) / 2.0;
                }
            }
        }
    };

    int main() {
        std::vector<int> nums1 = {1, 3};
        std::vector<int> nums2 = {2};
        Solution solution;
        double median = solution.findMedianSortedArrays(nums1, nums2);
        std::cout << "Median: " << median << std::endl;
        return 0;
    }

  Node: preproc_include #include <iostream>

    Node: system_lib_string <iostream>
  Node: preproc_include #include <vector>

    Node: system_lib_string <vector>
  Node: class_specifier class Solution {
    public:
        double findMedianSortedArrays(std::vector<int>& nums1, std::vector<int>& nums2) {
            int m = nums1.size(), n = nums2.size();
            if(m > n) return findMedianSortedArrays(nums2, nums1);
            int lo = 0, hi = m, mid = (m + n + 1)/2;
            while(lo <= hi){
                int i = (lo + hi)/2;
                int j = mid - i;
                if(i < m && nums2[j - 1] > nums1[i])
                    lo = i + 1;
                else if(i > 0  && nums1[i - 1] > nums2[j])
                    hi = i - 1;
                else{
                    int maxLeft = (i == 0) ? nums2[j - 1] : (j == 0) ? nums1[i - 1] : std::max(nums1[i - 1], nums2[j - 1]);
                    int minRight = (i == m) ? nums2[j] : (j == n) ? nums1[i] : std::min(nums1[i], nums2[j]);
                    return (m + n) % 2 ? maxLeft : (maxLeft + minRight) / 2.0;
                }
            }
        }
    }
    Node: type_identifier Solution
    Node: field_declaration_list {
    public:
        double findMedianSortedArrays(std::vector<int>& nums1, std::vector<int>& nums2) {
            int m = nums1.size(), n = nums2.size();
            if(m > n) return findMedianSortedArrays(nums2, nums1);
            int lo = 0, hi = m, mid = (m + n + 1)/2;
            while(lo <= hi){
                int i = (lo + hi)/2;
                int j = mid - i;
                if(i < m && nums2[j - 1] > nums1[i])
                    lo = i + 1;
                else if(i > 0  && nums1[i - 1] > nums2[j])
                    hi = i - 1;
                else{
                    int maxLeft = (i == 0) ? nums2[j - 1] : (j == 0) ? nums1[i - 1] : std::max(nums1[i - 1], nums2[j - 1]);
                    int minRight = (i == m) ? nums2[j] : (j == n) ? nums1[i] : std::min(nums1[i], nums2[j]);
                    return (m + n) % 2 ? maxLeft : (maxLeft + minRight) / 2.0;
                }
            }
        }
    }
      Node: access_specifier public
      Node: function_definition double findMedianSortedArrays(std::vector<int>& nums1, std::vector<int>& nums2) {
            int m = nums1.size(), n = nums2.size();
            if(m > n) return findMedianSortedArrays(nums2, nums1);
            int lo = 0, hi = m, mid = (m + n + 1)/2;
            while(lo <= hi){
                int i = (lo + hi)/2;
                int j = mid - i;
                if(i < m && nums2[j - 1] > nums1[i])
                    lo = i + 1;
                else if(i > 0  && nums1[i - 1] > nums2[j])
                    hi = i - 1;
                else{
                    int maxLeft = (i == 0) ? nums2[j - 1] : (j == 0) ? nums1[i - 1] : std::max(nums1[i - 1], nums2[j - 1]);
                    int minRight = (i == m) ? nums2[j] : (j == n) ? nums1[i] : std::min(nums1[i], nums2[j]);
                    return (m + n) % 2 ? maxLeft : (maxLeft + minRight) / 2.0;
                }
            }
        }
        Node: primitive_type double
        Node: function_declarator findMedianSortedArrays(std::vector<int>& nums1, std::vector<int>& nums2)
          Node: field_identifier findMedianSortedArrays
          Node: parameter_list (std::vector<int>& nums1, std::vector<int>& nums2)
            Node: parameter_declaration std::vector<int>& nums1
              Node: qualified_identifier std::vector<int>
                Node: namespace_identifier std
                Node: template_type vector<int>
                  Node: type_identifier vector
                  Node: template_argument_list <int>
                    Node: type_descriptor int
                      Node: primitive_type int
              Node: reference_declarator & nums1
                Node: identifier nums1
            Node: parameter_declaration std::vector<int>& nums2
              Node: qualified_identifier std::vector<int>
                Node: namespace_identifier std
                Node: template_type vector<int>
                  Node: type_identifier vector
                  Node: template_argument_list <int>
                    Node: type_descriptor int
                      Node: primitive_type int
              Node: reference_declarator & nums2
                Node: identifier nums2
        Node: compound_statement {
            int m = nums1.size(), n = nums2.size();
            if(m > n) return findMedianSortedArrays(nums2, nums1);
            int lo = 0, hi = m, mid = (m + n + 1)/2;
            while(lo <= hi){
                int i = (lo + hi)/2;
                int j = mid - i;
                if(i < m && nums2[j - 1] > nums1[i])
                    lo = i + 1;
                else if(i > 0  && nums1[i - 1] > nums2[j])
                    hi = i - 1;
                else{
                    int maxLeft = (i == 0) ? nums2[j - 1] : (j == 0) ? nums1[i - 1] : std::max(nums1[i - 1], nums2[j - 1]);
                    int minRight = (i == m) ? nums2[j] : (j == n) ? nums1[i] : std::min(nums1[i], nums2[j]);
                    return (m + n) % 2 ? maxLeft : (maxLeft + minRight) / 2.0;
                }
            }
        }
          Node: declaration int m = nums1.size(), n = nums2.size();
            Node: primitive_type int
            Node: init_declarator m = nums1.size()
              Node: identifier m
              Node: call_expression nums1.size()
                Node: field_expression nums1.size
                  Node: identifier nums1
                  Node: field_identifier size
                Node: argument_list ()
            Node: init_declarator n = nums2.size()
              Node: identifier n
              Node: call_expression nums2.size()
                Node: field_expression nums2.size
                  Node: identifier nums2
                  Node: field_identifier size
                Node: argument_list ()
          Node: if_statement if(m > n) return findMedianSortedArrays(nums2, nums1);
            Node: condition_clause (m > n)
              Node: binary_expression m > n
                Node: identifier m
                Node: identifier n
            Node: return_statement return findMedianSortedArrays(nums2, nums1);
              Node: call_expression findMedianSortedArrays(nums2, nums1)
                Node: identifier findMedianSortedArrays
                Node: argument_list (nums2, nums1)
                  Node: identifier nums2
                  Node: identifier nums1
          Node: declaration int lo = 0, hi = m, mid = (m + n + 1)/2;
            Node: primitive_type int
            Node: init_declarator lo = 0
              Node: identifier lo
              Node: number_literal 0
            Node: init_declarator hi = m
              Node: identifier hi
              Node: identifier m
            Node: init_declarator mid = (m + n + 1)/2
              Node: identifier mid
              Node: binary_expression (m + n + 1)/2
                Node: parenthesized_expression (m + n + 1)
                  Node: binary_expression m + n + 1
                    Node: binary_expression m + n
                      Node: identifier m
                      Node: identifier n
                    Node: number_literal 1
                Node: number_literal 2
          Node: while_statement while(lo <= hi){
                int i = (lo + hi)/2;
                int j = mid - i;
                if(i < m && nums2[j - 1] > nums1[i])
                    lo = i + 1;
                else if(i > 0  && nums1[i - 1] > nums2[j])
                    hi = i - 1;
                else{
                    int maxLeft = (i == 0) ? nums2[j - 1] : (j == 0) ? nums1[i - 1] : std::max(nums1[i - 1], nums2[j - 1]);
                    int minRight = (i == m) ? nums2[j] : (j == n) ? nums1[i] : std::min(nums1[i], nums2[j]);
                    return (m + n) % 2 ? maxLeft : (maxLeft + minRight) / 2.0;
                }
            }
            Node: condition_clause (lo <= hi)
              Node: binary_expression lo <= hi
                Node: identifier lo
                Node: identifier hi
            Node: compound_statement {
                int i = (lo + hi)/2;
                int j = mid - i;
                if(i < m && nums2[j - 1] > nums1[i])
                    lo = i + 1;
                else if(i > 0  && nums1[i - 1] > nums2[j])
                    hi = i - 1;
                else{
                    int maxLeft = (i == 0) ? nums2[j - 1] : (j == 0) ? nums1[i - 1] : std::max(nums1[i - 1], nums2[j - 1]);
                    int minRight = (i == m) ? nums2[j] : (j == n) ? nums1[i] : std::min(nums1[i], nums2[j]);
                    return (m + n) % 2 ? maxLeft : (maxLeft + minRight) / 2.0;
                }
            }
              Node: declaration int i = (lo + hi)/2;
                Node: primitive_type int
                Node: init_declarator i = (lo + hi)/2
                  Node: identifier i
                  Node: binary_expression (lo + hi)/2
                    Node: parenthesized_expression (lo + hi)
                      Node: binary_expression lo + hi
                        Node: identifier lo
                        Node: identifier hi
                    Node: number_literal 2
              Node: declaration int j = mid - i;
                Node: primitive_type int
                Node: init_declarator j = mid - i
                  Node: identifier j
                  Node: binary_expression mid - i
                    Node: identifier mid
                    Node: identifier i
              Node: if_statement if(i < m && nums2[j - 1] > nums1[i])
                    lo = i + 1;
                else if(i > 0  && nums1[i - 1] > nums2[j])
                    hi = i - 1;
                else{
                    int maxLeft = (i == 0) ? nums2[j - 1] : (j == 0) ? nums1[i - 1] : std::max(nums1[i - 1], nums2[j - 1]);
                    int minRight = (i == m) ? nums2[j] : (j == n) ? nums1[i] : std::min(nums1[i], nums2[j]);
                    return (m + n) % 2 ? maxLeft : (maxLeft + minRight) / 2.0;
                }
                Node: condition_clause (i < m && nums2[j - 1] > nums1[i])
                  Node: binary_expression i < m && nums2[j - 1] > nums1[i]
                    Node: binary_expression i < m
                      Node: identifier i
                      Node: identifier m
                    Node: binary_expression nums2[j - 1] > nums1[i]
                      Node: subscript_expression nums2[j - 1]
                        Node: identifier nums2
                        Node: subscript_argument_list [j - 1]
                          Node: binary_expression j - 1
                            Node: identifier j
                            Node: number_literal 1
                      Node: subscript_expression nums1[i]
                        Node: identifier nums1
                        Node: subscript_argument_list [i]
                          Node: identifier i
                Node: expression_statement lo = i + 1;
                  Node: assignment_expression lo = i + 1
                    Node: identifier lo
                    Node: binary_expression i + 1
                      Node: identifier i
                      Node: number_literal 1
                Node: else_clause else if(i > 0  && nums1[i - 1] > nums2[j])
                    hi = i - 1;
                else{
                    int maxLeft = (i == 0) ? nums2[j - 1] : (j == 0) ? nums1[i - 1] : std::max(nums1[i - 1], nums2[j - 1]);
                    int minRight = (i == m) ? nums2[j] : (j == n) ? nums1[i] : std::min(nums1[i], nums2[j]);
                    return (m + n) % 2 ? maxLeft : (maxLeft + minRight) / 2.0;
                }
                  Node: if_statement if(i > 0  && nums1[i - 1] > nums2[j])
                    hi = i - 1;
                else{
                    int maxLeft = (i == 0) ? nums2[j - 1] : (j == 0) ? nums1[i - 1] : std::max(nums1[i - 1], nums2[j - 1]);
                    int minRight = (i == m) ? nums2[j] : (j == n) ? nums1[i] : std::min(nums1[i], nums2[j]);
                    return (m + n) % 2 ? maxLeft : (maxLeft + minRight) / 2.0;
                }
                    Node: condition_clause (i > 0  && nums1[i - 1] > nums2[j])
                      Node: binary_expression i > 0  && nums1[i - 1] > nums2[j]
                        Node: binary_expression i > 0
                          Node: identifier i
                          Node: number_literal 0
                        Node: binary_expression nums1[i - 1] > nums2[j]
                          Node: subscript_expression nums1[i - 1]
                            Node: identifier nums1
                            Node: subscript_argument_list [i - 1]
                              Node: binary_expression i - 1
                                Node: identifier i
                                Node: number_literal 1
                          Node: subscript_expression nums2[j]
                            Node: identifier nums2
                            Node: subscript_argument_list [j]
                              Node: identifier j
                    Node: expression_statement hi = i - 1;
                      Node: assignment_expression hi = i - 1
                        Node: identifier hi
                        Node: binary_expression i - 1
                          Node: identifier i
                          Node: number_literal 1
                    Node: else_clause else{
                    int maxLeft = (i == 0) ? nums2[j - 1] : (j == 0) ? nums1[i - 1] : std::max(nums1[i - 1], nums2[j - 1]);
                    int minRight = (i == m) ? nums2[j] : (j == n) ? nums1[i] : std::min(nums1[i], nums2[j]);
                    return (m + n) % 2 ? maxLeft : (maxLeft + minRight) / 2.0;
                }
                      Node: compound_statement {
                    int maxLeft = (i == 0) ? nums2[j - 1] : (j == 0) ? nums1[i - 1] : std::max(nums1[i - 1], nums2[j - 1]);
                    int minRight = (i == m) ? nums2[j] : (j == n) ? nums1[i] : std::min(nums1[i], nums2[j]);
                    return (m + n) % 2 ? maxLeft : (maxLeft + minRight) / 2.0;
                }
                        Node: declaration int maxLeft = (i == 0) ? nums2[j - 1] : (j == 0) ? nums1[i - 1] : std::max(nums1[i - 1], nums2[j - 1]);
                          Node: primitive_type int
                          Node: init_declarator maxLeft = (i == 0) ? nums2[j - 1] : (j == 0) ? nums1[i - 1] : std::max(nums1[i - 1], nums2[j - 1])
                            Node: identifier maxLeft
                            Node: conditional_expression (i == 0) ? nums2[j - 1] : (j == 0) ? nums1[i - 1] : std::max(nums1[i - 1], nums2[j - 1])
                              Node: parenthesized_expression (i == 0)
                                Node: binary_expression i == 0
                                  Node: identifier i
                                  Node: number_literal 0
                              Node: subscript_expression nums2[j - 1]
                                Node: identifier nums2
                                Node: subscript_argument_list [j - 1]
                                  Node: binary_expression j - 1
                                    Node: identifier j
                                    Node: number_literal 1
                              Node: conditional_expression (j == 0) ? nums1[i - 1] : std::max(nums1[i - 1], nums2[j - 1])
                                Node: parenthesized_expression (j == 0)
                                  Node: binary_expression j == 0
                                    Node: identifier j
                                    Node: number_literal 0
                                Node: subscript_expression nums1[i - 1]
                                  Node: identifier nums1
                                  Node: subscript_argument_list [i - 1]
                                    Node: binary_expression i - 1
                                      Node: identifier i
                                      Node: number_literal 1
                                Node: call_expression std::max(nums1[i - 1], nums2[j - 1])
                                  Node: qualified_identifier std::max
                                    Node: namespace_identifier std
                                    Node: identifier max
                                  Node: argument_list (nums1[i - 1], nums2[j - 1])
                                    Node: subscript_expression nums1[i - 1]
                                      Node: identifier nums1
                                      Node: subscript_argument_list [i - 1]
                                        Node: binary_expression i - 1
                                          Node: identifier i
                                          Node: number_literal 1
                                    Node: subscript_expression nums2[j - 1]
                                      Node: identifier nums2
                                      Node: subscript_argument_list [j - 1]
                                        Node: binary_expression j - 1
                                          Node: identifier j
                                          Node: number_literal 1
                        Node: declaration int minRight = (i == m) ? nums2[j] : (j == n) ? nums1[i] : std::min(nums1[i], nums2[j]);
                          Node: primitive_type int
                          Node: init_declarator minRight = (i == m) ? nums2[j] : (j == n) ? nums1[i] : std::min(nums1[i], nums2[j])
                            Node: identifier minRight
                            Node: conditional_expression (i == m) ? nums2[j] : (j == n) ? nums1[i] : std::min(nums1[i], nums2[j])
                              Node: parenthesized_expression (i == m)
                                Node: binary_expression i == m
                                  Node: identifier i
                                  Node: identifier m
                              Node: subscript_expression nums2[j]
                                Node: identifier nums2
                                Node: subscript_argument_list [j]
                                  Node: identifier j
                              Node: conditional_expression (j == n) ? nums1[i] : std::min(nums1[i], nums2[j])
                                Node: parenthesized_expression (j == n)
                                  Node: binary_expression j == n
                                    Node: identifier j
                                    Node: identifier n
                                Node: subscript_expression nums1[i]
                                  Node: identifier nums1
                                  Node: subscript_argument_list [i]
                                    Node: identifier i
                                Node: call_expression std::min(nums1[i], nums2[j])
                                  Node: qualified_identifier std::min
                                    Node: namespace_identifier std
                                    Node: identifier min
                                  Node: argument_list (nums1[i], nums2[j])
                                    Node: subscript_expression nums1[i]
                                      Node: identifier nums1
                                      Node: subscript_argument_list [i]
                                        Node: identifier i
                                    Node: subscript_expression nums2[j]
                                      Node: identifier nums2
                                      Node: subscript_argument_list [j]
                                        Node: identifier j
                        Node: return_statement return (m + n) % 2 ? maxLeft : (maxLeft + minRight) / 2.0;
                          Node: conditional_expression (m + n) % 2 ? maxLeft : (maxLeft + minRight) / 2.0
                            Node: binary_expression (m + n) % 2
                              Node: parenthesized_expression (m + n)
                                Node: binary_expression m + n
                                  Node: identifier m
                                  Node: identifier n
                              Node: number_literal 2
                            Node: identifier maxLeft
                            Node: binary_expression (maxLeft + minRight) / 2.0
                              Node: parenthesized_expression (maxLeft + minRight)
                                Node: binary_expression maxLeft + minRight
                                  Node: identifier maxLeft
                                  Node: identifier minRight
                              Node: number_literal 2.0
  Node: function_definition int main() {
        std::vector<int> nums1 = {1, 3};
        std::vector<int> nums2 = {2};
        Solution solution;
        double median = solution.findMedianSortedArrays(nums1, nums2);
        std::cout << "Median: " << median << std::endl;
        return 0;
    }
    Node: primitive_type int
    Node: function_declarator main()
      Node: identifier main
      Node: parameter_list ()
    Node: compound_statement {
        std::vector<int> nums1 = {1, 3};
        std::vector<int> nums2 = {2};
        Solution solution;
        double median = solution.findMedianSortedArrays(nums1, nums2);
        std::cout << "Median: " << median << std::endl;
        return 0;
    }
      Node: declaration std::vector<int> nums1 = {1, 3};
        Node: qualified_identifier std::vector<int>
          Node: namespace_identifier std
          Node: template_type vector<int>
            Node: type_identifier vector
            Node: template_argument_list <int>
              Node: type_descriptor int
                Node: primitive_type int
        Node: init_declarator nums1 = {1, 3}
          Node: identifier nums1
          Node: initializer_list {1, 3}
            Node: number_literal 1
            Node: number_literal 3
      Node: declaration std::vector<int> nums2 = {2};
        Node: qualified_identifier std::vector<int>
          Node: namespace_identifier std
          Node: template_type vector<int>
            Node: type_identifier vector
            Node: template_argument_list <int>
              Node: type_descriptor int
                Node: primitive_type int
        Node: init_declarator nums2 = {2}
          Node: identifier nums2
          Node: initializer_list {2}
            Node: number_literal 2
      Node: declaration Solution solution;
        Node: type_identifier Solution
        Node: identifier solution
      Node: declaration double median = solution.findMedianSortedArrays(nums1, nums2);
        Node: primitive_type double
        Node: init_declarator median = solution.findMedianSortedArrays(nums1, nums2)
          Node: identifier median
          Node: call_expression solution.findMedianSortedArrays(nums1, nums2)
            Node: field_expression solution.findMedianSortedArrays
              Node: identifier solution
              Node: field_identifier findMedianSortedArrays
            Node: argument_list (nums1, nums2)
              Node: identifier nums1
              Node: identifier nums2
      Node: expression_statement std::cout << "Median: " << median << std::endl;
        Node: binary_expression std::cout << "Median: " << median << std::endl
          Node: binary_expression std::cout << "Median: " << median
            Node: binary_expression std::cout << "Median: "
              Node: qualified_identifier std::cout
                Node: namespace_identifier std
                Node: identifier cout
              Node: string_literal "Median: "
                Node: string_content Median:
            Node: identifier median
          Node: qualified_identifier std::endl
            Node: namespace_identifier std
            Node: identifier endl
      Node: return_statement return 0;
        Node: number_literal 0
 
Последнее редактирование:
Пожалуйста, обратите внимание, что пользователь заблокирован
salsa20 я просто предложил идею, реализовывать пока не собираюсь. Так как давно видел подобные темы.
 
Затем соберите все реализованные алгоритмы в список и примените их друг за другом.
Этот паттерн зовется Chain of Responsibility ;)
 
salsa20 я просто предложил идею, реализовывать пока не собираюсь. Так как давно видел подобные темы.
ну это работает да, для x64 тестил все ок, код всегда разный под морфом после пассов.
 
Пожалуйста, обратите внимание, что пользователь заблокирован
Этот паттерн зовется Chain of Responsibility
Ну я бы сказал, что это скорее Pipeline паттерн, тк в цепочке обязанностей больше условных, например, какой-то из модулей может приостановить обработку следующих модулей. Но это уже о том, кто и как понимает терминологию.

может то что нада
Ну вот, а они говорят "никому не нада, никому не интересна". Я еще видел на nuget.org байндинги для Шарпов, если кому Шарпы были бы удобнее, чем Петон. Но я не смотрел, насколько они старые. Так-то, поскольку дерева сидетель - это цэшная библиотека, ее можно из много чего использовать, вопрос только в том, чтобы найти или сделать нормальные байндинги.

я просто предложил идею
Вопрос в подходе. В аст морфинг порог входа, наверное, меньше, тк не нужно изучать LLVM IR или GIMPLE (gcc), достаточно знать язык, который ты хочешь морфить. Какие-то условные phi ноды в LLVM: нужно потратить некоторое время, чтобы в них разобраться. В целом, на уровне ассемблера или какого-то из промежуточных представлений возможностей больше, но надо подумать, а нужны ли эти возможности? Я уже писал когда-то, что для аверов код, который не похож на типичный, сгенерированный, например сишным компилятором, может сразу посчитаться вредоносным. Но тогда зачем нам уходить на уровень ниже самого языка, чтобы потом морфить код так, чтобы он был похож на код самого языка. Хотя никого ни к чему не принуждаю.
 
Модификация исходника дает очень важный бонус, результат можно смотреть без дизассемблирования, наглядность на этом уровне важна.
Еще предложенный подход позволяет расставить в исходнике метки в виде коментов, которые будут подсказывать обфускатору как именно можно конкретный участок кода обфусцировать или его вообще нельзя обфусцировать. Ну и удобный универсальный подход для разных язков.
Просто, практично, легко в освоении и обслуживании.
 
Пожалуйста, обратите внимание, что пользователь заблокирован
Модификация исходника дает очень важный бонус, результат можно смотреть без дизассемблирования, наглядность на этом уровне важна.
CTI позволяет оптимизировать на уровне исходников. Поэтому одинаковый подход. И бонуса никакого нет.

Ты наверно перепутал DBI подход где это невозможно. Но CTI умеет так и это переимущество перед DBI =)

Тогда следующий вопрос, если мы морфим на атапе AST то как известно на этапе IR происходит оптимизация не будет ли так, что после оптимизации морфленный код уйдет в оптимизацию и от него не будет толку, так как компилятор подумает, а вот это лишниее вырезаем, а вот это оптимизируем. В том же время в асане инструментация происходит уже после оптимизации кода.
 
Последнее редактирование:
CTI позволяет оптимизировать на уровне исходников. Поэтому одинаковый подход.

Тогда следующий вопрос, если мы морфим на атапе AST то как известно на этапе IR происходит оптимизация не будет ли так, что после оптимизации морфленный код уйдет в оптимизацию и от него не будет толку, так как компилятор подумает, а вот это лишниее вырезаем, а вот это оптимизируем. В том же время в асане инструментация происходит уже после оптимизации кода.
Ну вопервых это уже вопрос не к парсеру и не к морферу а к паттернам морфера.
Во вторых проблемы начинаются там где не вырезано то что должно было бы быть вырезанным.
Смотреть на asm output конечно же надо будет, но повторюсь это уже про паттерны морфа.
 
Пожалуйста, обратите внимание, что пользователь заблокирован
Ну вопервых это уже вопрос не к парсеру и не к морферу а к паттернам морфера.
Во вторых проблемы начинаются там где не вырезано то что должно было бы быть вырезанным.
Смотреть на asm output конечно же надо будет, но повторюсь это уже про паттерны морфа.
Какие бы патерны не были. Оптимизация все равно будет ты ведь не сможешь обойти IR этап. Даже если ты укажешь флаг.

Другими словами нужен скриншот как это работает на практике. Поэтому я подожду, когда кто-нибудь зальет скрин =)
 


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