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

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

Ты у меня спрашиваешь? Я человек простой, встретил литерал внутри функции/метода - заморфил его. Какие-то типичные литералы, типа 0 (если мы говорим о числах), наверное, можно не трогать, так как они часто компилятся во что-то типа условного "xor eax, eax", но опять же это зависит от ситуации.
Ты как поймешь что это литерал который можно и нужно приморфить?
foo(SOMEWHERE_DEFINED_LITERAL_OR_MAY_BE_ANYTHING_ELSE_LIKE_MACRO_OR_CONSTEXPR)
А ведь всякая криптоебала и прочая ебала за которую будут детектить - мд5 найден, рсц4 найден, и прочее такое, именно так в сорцах выглядит.
 
Пожалуйста, обратите внимание, что пользователь заблокирован
Ты как поймешь что это литерал который можно и нужно приморфить?
foo(SOMEWHERE_DEFINED_LITERAL_OR_MAY_BE_ANYTHING_ELSE_LIKE_MACRO_OR_CONSTEXPR)
Constexp, возможно, и не пойму, если нужны макросы, то через препроцессор прогнать и морфить сорсы после препроцессора (как мы собственно и делали в статье про либшланг). Да и потом, если нужно, какую-то "криптоебалу" морфить, то можно ее и подогнать под морфер, всякие сбоксы и константы сделать литералами. Вообще, нет лучше способа замедлить компиляцию, чем генерировать всякую "метапрограмебалу" на этапе компиляции.
 
Constexp, возможно, и не пойму, если нужны макросы, то через препроцессор прогнать и морфить сорсы после препроцессора (как мы собственно и делали в статье про либшланг). Да и потом, если нужно, какую-то "криптоебалу" морфить, то можно ее и подогнать под морфер, всякие сбоксы и константы сделать литералами. Вообще, нет лучше способа замедлить компиляцию, чем генерировать всякую "метапрограмебалу" на этапе компиляции.
Воот видишь уже нельзя взять какой то код и притулить к своему прожекту, а надо по нему ползать и возиться со всем этим говном, говна можеть сельно больше чем хочеться и когда ползаешь и рукми это как то правишь то в к глове нашептывает голос - а не дурак ли я?
А всякая крипто ебала там макрос на макросе и все как раза такое заоптимизированное до упора. Но в любом случае вот это припейр всего что тянешь в проект это боль, про апдейты этого левого кода вообще молчу.
 
Пожалуйста, обратите внимание, что пользователь заблокирован
Возвращаясь к вопросу Whisper вот тут: https://xss.pro/threads/110129/post-771091 (не понимаю, будет ли в той теме это оффтопом или нет, вероятно, там ищут готовое решение, а не способ писать свой обфускатор, поэтому на всякий случай запощу ответ в своей теме, чтобы людям не мешаться). Вопрос был про то, что если мы переименовали атрибуты класса, то дальше в коде мы не можем со 100 процентной вероятностью сказать, нужно ли нам также переименовывать обращение к атрибуту, который мы ранее переименовали. Да, это проблема. Я придумал решение, не уверен, что оно будет работать во всех кейсах Петона, но для конкретного кейса - вроде работает. Плюс мы бонусом получили сокрытие вызовов и обращений к атрибутам, смотрим код:
Python:
import pathlib
import zlib

# находит аттрибут у объекта по хешу имени
def find_attribute_by_hash(call_object, name_hash):
    for attribute in dir(call_object):
        att_hash = zlib.adler32(attribute.encode())
        if att_hash == name_hash:
            return getattr(call_object, attribute)
       
    return None

# находит аттрибут по хешу имени
def hidden_call(call_object, name_hash):
    # таблица хешей переименованных имен
    # хеш_старого_имени => хеш_нового_имени
    # генерируется обфусктором
    renamed_table = {}
    renamed_table[0x91402a1] = 0x2caf05dc # [adler32("exists")] = adler32("exists_renamed")

    # проверяем, было ли переименовано это имя
    if name_hash in renamed_table:
        # если да, то пытаемся найти имя по
        # переименованному хешу из таблицы
        renamed_hash = renamed_table[name_hash]
        attribute = find_attribute_by_hash(call_object, renamed_hash)
        if attribute is not None: return attribute

    # если не было переименованного имени, или если переименованное
    # имя не нашлось по хешу, то ищем по хешу оригинальное имя  
    attribute = find_attribute_by_hash(call_object, name_hash)
    if attribute is None: raise Exception(f"Shitty hash: {hex(name_hash)}")
   
    return attribute

# До переименования
class Test1:
    def exists(self):
        return True

# После переименования
class Test1_renamed:
    def exists_renamed(self):
        return True

pat = pathlib.Path("./test.txt")

# до обфускации
def test_dat_shit_before(obj):
    print(obj.exists())

# после обфускации все конструкции
# с доступом к аттрибутам заменяются
# one.two => hidden_call(one, adler32("two"))
def test_dat_shit_after(obj):
    print(hidden_call(obj, 0x91402a1)())  

test_dat_shit_before(Test1())
test_dat_shit_before(pat)

test_dat_shit_after(Test1_renamed())
test_dat_shit_after(pat)

Про качество кода не орите, это демонстрационный пример, чтобы был понятен подход. В общем, сначала мы делаем проход, который переименовывает идентификаторы. Если идентификатор в объявлении метода или атрибута класса, то снимаем хеш от имени до переименование и после переименования, и затем составляем из хешей словарь соответствия. Вставляем этот словарь в метод для получения атрибута по хешу. Затем нужные нам сабскрипт операторы (one.two) заменяем на вызов метода hidden_call(one, 0x<hash_of_two>). Метод hidden_call проверяет наличие хеша в словаре соответствия, если он есть, то сначала пытается найти атрибут по хешу нового имени. Если нашел, возвращает его. Если не нашел пытается найти по хешу старого имени (например, этот кейс будет работать для атрибутов классов стандартной библиотеки, как для Path, например).

Плюсы: вроде работает; по моей логике кейс решает; скрывает обращение к атрибутам объектов классов через хеш вместо имени. Минусы: если будет коллизия хешей, то нам пизда (я использовал adler32 для примера, так делать не стоит в продакшене); надо подумать как в дерева переписывателе правильно решить ситуацию one.two.three => hidden_call(hidden_call(one, 0x<hash_two>), 0x<hash_three>), тк в случае замены их друг за другом поломается код перезаписи (тк один узел вложен в другой, может просто не ебать мозг и сделать несколько одинаковых проходов); надо протестить другие кейсы, типа обращения к модулям и допилить, если это не будет нормально работать.
 
Возвращаясь к вопросу Whisper вот тут: https://xss.pro/threads/110129/post-771091 (не понимаю, будет ли в той теме это оффтопом или нет, вероятно, там ищут готовое решение, а не способ писать свой обфускатор, поэтому на всякий случай запощу ответ в своей теме, чтобы людям не мешаться). Вопрос был про то, что если мы переименовали атрибуты класса, то дальше в коде мы не можем со 100 процентной вероятностью сказать, нужно ли нам также переименовывать обращение к атрибуту, который мы ранее переименовали. Да, это проблема. Я придумал решение, не уверен, что оно будет работать во всех кейсах Петона, но для конкретного кейса - вроде работает. Плюс мы бонусом получили сокрытие вызовов и обращений к атрибутам, смотрим код:
Python:
import pathlib
import zlib

# находит аттрибут у объекта по хешу имени
def find_attribute_by_hash(call_object, name_hash):
    for attribute in dir(call_object):
        att_hash = zlib.adler32(attribute.encode())
        if att_hash == name_hash:
            return getattr(call_object, attribute)
     
    return None

# находит аттрибут по хешу имени
def hidden_call(call_object, name_hash):
    # таблица хешей переименованных имен
    # хеш_старого_имени => хеш_нового_имени
    # генерируется обфусктором
    renamed_table = {}
    renamed_table[0x91402a1] = 0x2caf05dc # [adler32("exists")] = adler32("exists_renamed")

    # проверяем, было ли переименовано это имя
    if name_hash in renamed_table:
        # если да, то пытаемся найти имя по
        # переименованному хешу из таблицы
        renamed_hash = renamed_table[name_hash]
        attribute = find_attribute_by_hash(call_object, renamed_hash)
        if attribute is not None: return attribute

    # если не было переименованного имени, или если переименованное
    # имя не нашлось по хешу, то ищем по хешу оригинальное имя
    attribute = find_attribute_by_hash(call_object, name_hash)
    if attribute is None: raise Exception(f"Shitty hash: {hex(name_hash)}")
 
    return attribute

# До переименования
class Test1:
    def exists(self):
        return True

# После переименования
class Test1_renamed:
    def exists_renamed(self):
        return True

pat = pathlib.Path("./test.txt")

# до обфускации
def test_dat_shit_before(obj):
    print(obj.exists())

# после обфускации все конструкции
# с доступом к аттрибутам заменяются
# one.two => hidden_call(one, adler32("two"))
def test_dat_shit_after(obj):
    print(hidden_call(obj, 0x91402a1)())

test_dat_shit_before(Test1())
test_dat_shit_before(pat)

test_dat_shit_after(Test1_renamed())
test_dat_shit_after(pat)

Про качество кода не орите, это демонстрационный пример, чтобы был понятен подход. В общем, сначала мы делаем проход, который переименовывает идентификаторы. Если идентификатор в объявлении метода или атрибута класса, то снимаем хеш от имени до переименование и после переименования, и затем составляем из хешей словарь соответствия. Вставляем этот словарь в метод для получения атрибута по хешу. Затем нужные нам сабскрипт операторы (one.two) заменяем на вызов метода hidden_call(one, 0x<hash_of_two>). Метод hidden_call проверяет наличие хеша в словаре соответствия, если он есть, то сначала пытается найти атрибут по хешу нового имени. Если нашел, возвращает его. Если не нашел пытается найти по хешу старого имени (например, этот кейс будет работать для атрибутов классов стандартной библиотеки, как для Path, например).

Плюсы: вроде работает; по моей логике кейс решает; скрывает обращение к атрибутам объектов классов через хеш вместо имени. Минусы: если будет коллизия хешей, то нам пизда (я использовал adler32 для примера, так делать не стоит в продакшене); надо подумать как в дерева переписывателе правильно решить ситуацию one.two.three => hidden_call(hidden_call(one, 0x<hash_two>), 0x<hash_three>), тк в случае замены их друг за другом поломается код перезаписи (тк один узел вложен в другой, может просто не ебать мозг и сделать несколько одинаковых проходов); надо протестить другие кейсы, типа обращения к модулям и допилить, если это не будет нормально работать.
Это хороший подход, но мы оба понимаем что не везде и не всегда и не панацея, а значит с большим и сложным проектом будет много боли, ты если с таким не столкнулся значит либо больших проектов не морфил, либо писал проекты спец образом. Первое что приходит на ум __getattr__(self, name). Да и еще что то полезет. Хз попробуй так поморфить что то большое не обязательно свое, импакет там или еще что. Но подход хороший.
 
Самое дряное наверное будет при установке атрибута и добрасывание метода в класс, или записи в жейсон, ты сразу 2 будешь делать на всякий случай?
 
в данной строке find_attribute_by_hash(call_object, name_hash) была ошибка

Python:
import pathlib
import zlib

# находит аттрибут у объекта по хешу имени
def find_attribute_by_hash(call_object, name_hash):
    for attribute in dir(call_object):
        att_hash = zlib.adler32(attribute.encode())
        if att_hash == name_hash:
            return getattr(call_object, attribute)
      
    return None

# находит аттрибут по хешу имени
def hidden_call(call_object, name_hash):
    # таблица хешей переименованных имен
    # хеш_старого_имени => хеш_нового_имени
    # генерируется обфусктором
    renamed_table = {}
    renamed_table[0x91402a1] = 0x2caf05dc # [adler32("exists")] = adler32("exists_renamed")

    # проверяем, было ли переименовано это имя
    if name_hash in renamed_table:
        # если да, то пытаемся найти имя по
        # переименованному хешу из таблицы
        renamed_hash = renamed_table[name_hash]
        attribute = find_attribute_by_hash(call_object, renamed_hash)
        if attribute is not None: return attribute

    # если не было переименованного имени, или если переименованное
    # имя не нашлось по хешу, то ищем по хешу оригинальное имя 
    attribute = find_attribute_by_hash(call_object, name_hash)
    if attribute is None: raise Exception(f"Shitty hash: {hex(name_hash)}")
  
    return attribute

# До переименования
class Test1:
    def exists(self):
        return True

# После переименования
class Test1_renamed:
    def exists_renamed(self):
        return True

pat = pathlib.Path("./test.txt")

# до обфускации
def test_dat_shit_before(obj):
    print(obj.exists())

# после обфускации все конструкции
# с доступом к аттрибутам заменяются
# one.two => hidden_call(one, adler32("two"))
def test_dat_shit_after(obj):
    print(hidden_call(obj, zlib.adler32("exists".encode()))()) 

test_dat_shit_before(Test1())
test_dat_shit_before(pat)

test_dat_shit_after(Test1_renamed())
test_dat_shit_after(pat)
 
Пожалуйста, обратите внимание, что пользователь заблокирован
Самое дряное наверное будет при установке атрибута и добрасывание метода в класс, или записи в жейсон, ты сразу 2 будешь делать на всякий случай?
Опять же не совсем понимаю проблему, когда ты распарсил жесон, то получил дикт, а значения дикта по ключам ты извлекаешь и устанавливаешь с помощью dict["key"] - просто зашифруй строку "key" и все, не?

в данной строке find_attribute_by_hash(call_object, name_hash) была ошибка
Где и какая ошибка?
 
Опять же не совсем понимаю проблему, когда ты распарсил жесон, то получил дикт, а значения дикта по ключам ты извлекаешь и устанавливаешь с помощью dict["key"] - просто зашифруй строку "key" и все, не?
Увы нет. Когда точно нельзя предсказать что к чему относится и чем является и во что потом буде сериализовано, и тд. тп, то это просто нерешаемая задача. Ты со всем этим столкнешся при по пытке большие сложные либы поморфить. Я нашел единственный путь это маркировать все что можно морфить, иначе будешь вылавливать проблемы пинцетом и доводить до бешенства всех кто твой код пользует. Подчеркну я про большие проекты где много абстракции и много что делается через меджик методы. Например __getattr__(self, name) срабатывает только если в классе на обнаружен атрибут, с отладчика это выглядит так что атрибута у класса нет но обратится к нему можно. В случае с распределенными системами у тебя дофига будет сериализаций и десереализаций между леерами и без маркеров на именах будет пичаль. Но может это просто я совсем тупой, тут хз, у тебя вроде все гладко раз ты не разделяешь мою боль.
 
Пожалуйста, обратите внимание, что пользователь заблокирован
Но может это просто я совсем тупой, тут хз, у тебя вроде все гладко раз ты не разделяешь мою боль
Ну я сделал порядочно обфускаторов разных, но все они заточены под мой код, или под код, который я в достаточной степени понимаю, чтобы что-то поменять, если что-то пойдет не так. Обычно я начинаю проект именно с обфускатора, набрасываю проходы, которые мне нужны, потом уже приступаю к основному коду, добавляя функционал итеративно небольшими модулями. Так, если вдруг обфускация начинает что-то ломать, я примерно понимаю, в каком небольшом фрагменте кода это происходит. Так куда проще это отдебажить.
 
Ну я сделал порядочно обфускаторов разных, но все они заточены под мой код, или под код, который я в достаточной степени понимаю, чтобы что-то поменять, если что-то пойдет не так. Обычно я начинаю проект именно с обфускатора, набрасываю проходы, которые мне нужны, потом уже приступаю к основному коду, добавляя функционал итеративно небольшими модулями. Так, если вдруг обфускация начинает что-то ломать, я примерно понимаю, в каком небольшом фрагменте кода это происходит. Так куда проще это отдебажить.
Ну вот я к этому и вел, что чудес не бывает. И надо заранее продумывать и в процессе постоянно тестировать како оно вместе рабоает. Но имена я так понял ты не маркируешь, просто при написании потостоянно держишь в уме возможные ситуации с морфом.
 
Предлагаю обсудить а что делать со строками.
Вот сервер сайд допусти формирует строку что бы отправить клиенту
dbg_msg = f"Some should to be hidden {value}, this is should be hidden too"
Что насчет например такого решения когда части строк хешируются и укладываются в базу
получим что то типа
dbg_msg = f"#2423#{value}#10234#"
Так мы можем например получать дебаг логи и реверсер по этим строкам никакой ползезной инфы не получит.
Рассматривай пример не как что то для питона а как общую задачу, иногда ведь надо раздать на таргеты дебаг версию продукта, слить с меседжами реверсерам на анализ такое себе.
Ну и что то желательно делать со строками которые нужны будут прге в процессе работы, типа формирования хттп запросов и тд, ясно что их 100% не укрыть но затруднить анализ как то нужно а скриптах это не просто если ты опять же не подстраивал все под крипт заранее, но и подстраивать надо с умом..
Интересно как эти вопросы придумал решать ты.
 
dbg_msg = f"#2423#{value}#10234#"
Не знаю как в других ЯП, я на расте просто использую энум со всеми вариантами сообщений на клиенте и сервере, вида:
C-подобный:
// код для (де)сереализации сгенерируется автоматически
#[derive(bincode::Encode, bincode::Decode)]
pub enum DbgMessage {
    FoundBrowser(String),
    GrabbedBy(u64),
    ...
}

...
let path = some_fn_returned_runtime_string();
let msg = bincode_encode(DbgMessage::FoundBrowser(path));
// msg == [*enum tag as u8*, *enum variant value serialized as string*]

...
let msg = bincode_encode(DbgMessage::GrabbedBy(123));
// msg == [*enum tag as u8* , *enum variant value serialized as big/little endian*]

...
let msg: DbgMessage = bincode_decode(&[*encoded buffer*]);
// msg == DbgMessage::GrabbedBy(123)
В случае bincode - варианты энума сериализуются как целые числа, которые не несут никакой информативности без доступа к исходному энуму.
 
Не знаю как в других ЯП, я на расте просто использую энум со всеми вариантами сообщений на клиенте и сервере, вида:
C-подобный:
// код для (де)сереализации сгенерируется автоматически
#[derive(bincode::Encode, bincode::Decode)]
pub enum DbgMessage {
    FoundBrowser(String),
    GrabbedBy(u64),
    ...
}

...
let path = some_fn_returned_runtime_string();
let msg = bincode_encode(DbgMessage::FoundBrowser(path));
// msg == [*enum tag as u8*, *enum variant value serialized as string*]

...
let msg = bincode_encode(DbgMessage::GrabbedBy(123));
// msg == [*enum tag as u8* , *enum variant value serialized as big/little endian*]

...
let msg: DbgMessage = bincode_decode(&[*encoded buffer*]);
// msg == DbgMessage::GrabbedBy(123)
В случае bincode - варианты энума сериализуются как целые числа, которые не несут никакой информативности без доступа к исходному энуму.
Ну я подобное и полагал, просто интересно как это все тс делает в парсере. Что бы понять какую строку надо раздергать на хеши а хеши кинуть в базу если их там еще нет, а какую как то иначе обработать. Я понемногу свожу мысль к тому что молоток то офигенный но к нему еще хорошо бы набор готовых паттернов использования и технику безопасности.
 
Пожалуйста, обратите внимание, что пользователь заблокирован
dbg_msg = f"#2423#{value}#10234#"
Ну в целом я считаю это хорошей идеей, только не используй в обфускаторе один и тот же алгоритм хеширования, ну или просто используй разные зерна для алгоритмов в самой малварке и в обфускаторе, чтобы аверу не пришло в голову пытаться сбрутить их.
 
Ну в целом я считаю это хорошей идеей, только не используй в обфускаторе один и тот же алгоритм хеширования, ну или просто используй разные зерна для алгоритмов в самой малварке и в обфускаторе, чтобы аверу не пришло в голову пытаться сбрутить их.
А это в прямо в хеш вкладывается, например первые пару байт это модификатор раскрывающий остальные. Не зная алг хеширования для авера будет все уникально. Да там еще ноп хеши должны быть, иначе будет уникальный повоторяющийся паттерн из последовательностей хешей и велью.
 
Последнее редактирование:
Пожалуйста, обратите внимание, что пользователь заблокирован
А это в прямо в хеш вкладывается, например первые пару байт это модификатор раскрывающий остальные
Ну ты всегда можешь, допустим, 32-битный хеш поксорить с рандомным 32-битным числом, склеить их в одно 64-битное число и использовать их, аля хеш. То есть, у тебя при каждой пересборке будут уникальные хеши, при этом аверы, наверное, будут думать, что это 64-битные хеши, если вдруг решат их брутить.
 
Ну ты всегда можешь, допустим, 32-битный хеш поксорить с рандомным 32-битным числом, склеить их в одно 64-битное число и использовать их, аля хеш. То есть, у тебя при каждой пересборке будут уникальные хеши, при этом аверы, наверное, будут думать, что это 64-битные хеши, если вдруг решат их брутить.
Твой метод тоже хороший и быстрый, особенно если кроме соли(первых 32бит) использовать и секретную часть ключа.
Но думаею так круче, хотя и медленнее.

формат
(пара символов соль)(далее хеш)

псевдокод
encrypted_hash = "12456"
secret_phrase = "avery lohi"
key = crc32_md5_or_some_like_this(b"{encrypted_hash[:2]}{secret_phrase}")
decrypted_hash = decrypt_hash(key, encrypted_hash[2:])
string_of_hash = HashBase(decrypted_hash)
 
Пожалуйста, обратите внимание, что пользователь заблокирован
crc32_md5_or_some_like_this
MD5 или SHA* для этих целей использовать мало смысла, достаточно примитивного 32-битного хеша, главное, когда будешь хешировать строки, проверяй, что нет коллизий: просто держи в словаре хеши и исходные строки, для каждой новой строки проверяй, что хеш уже есть в словаре, и если это так сверяй строки. Если будет коллизия, то просто замени на другой 32-битный хеш. А то потом будешь гадать, чего за херня случилась, когда тебе вернется не та строка с ошибкой, или что-то в этом духе.
 
MD5 или SHA* для этих целей использовать мало смысла, достаточно примитивного 32-битного хеша, главное, когда будешь хешировать строки, проверяй, что нет коллизий: просто держи в словаре хеши и исходные строки, для каждой новой строки проверяй, что хеш уже есть в словаре, и если это так сверяй строки. Если будет коллизия, то просто замени на другой 32-битный хеш. А то потом будешь гадать, чего за херня случилась, когда тебе вернется не та строка с ошибкой, или что-то в этом духе.
Сталкивался, знаю, 32бит брать не рекомендую(без разницы какой алг, на 48битах(48бит от црц64, 16 бит заменяем на соль) уже не сталкивался с коллизиями). Но в общем и целом меня интересуют не реалиции идей(они банальны), а сами идеи и архитектуры, что бы все это было удобно в эксплуатации.
 


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