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

Статья Обфускация С/С++ кода с помощью Python и libclang

Возможно в будущем имеет смысл разделить конкурсы, на тех. статьи, истории и т.д.

Просто так получается, что оцениваются разные области, например кардинг, конкурирует с лоулевел, также как есть и статьи про пентестик, исследование уязвимостей сайтов, сети и т.д.

Это всё хорошо, но понятно что на форуме думаю 90% как-раз людей, которые и занимаются пентестингом, кардингом, ну и или просто кто-то интересуется чем-то и они очень далеки от ассемблера, си и т.д., в итоге и получается что статьи лоулевел направленности мало кто по хорошему оценит и проголосует, т.к. в целом таких специалистов даже в вайт сфере немного, а в сети и подавно, поэтому если будет конкурирующая статья в другой области, хорошо написана, то она и победит.

Тут незнаю нужно что-то придумывать, иначе сложные лоулевел статьи на конкурс не будет мотивации писать, например того-же Мердока можно понять, у него вполне достойные статьи были, а проголосовало очень мало.:(
 
Пожалуйста, обратите внимание, что пользователь заблокирован
Тут незнаю нужно что-то придумывать, иначе сложные лоулевел статьи на конкурс не будет мотивации писать, например того-же Мердока можно понять, у него вполне достойные статьи были, а проголосовало очень мало.
Да, согласен. А что за статьи? Я не в курсе, так как на прошлом конкурсе еще не был тут зареган.
 
Крутая идея кстати для статьи на будущие конкурсы по написанию своей метаморфной ВМки).
Тут да, тут уровень нужен...
Интересна не только вмка, но и компилятор под нее)
Интересен кстати прототип базонезависимой вм, пока глубоко не размышлял, но в теории было бы неплохо через неё пропускать байт-код, который образовался из компиляции шк. Либо же как второй кейс - инжект в чужой процесс полноценного интерпретатора какого либо байт-кода, который перед этим собирается компилятором с хайлевел языка. Нафантазировал конечно знатно, но пока не думал о возможностях реализации подобного, так что не пинайте)
 
А что за статьи? Я не в курсе, так как на прошлом конкурсе еще не был тут зареган.
Честно он вроде-бы много писал, я тоже тогда ещё зареган не был, так из под гостя читал, сейчас тяжело найти...:(

Вот это не знаю на конкурс, нет:

https://xss.pro/threads/34080/

Вот это, точно на конкурс:

https://xss.pro/threads/34601/

Там ещё что-то было про VirtualBox, но не помню как называется статья.:(
 
Пожалуйста, обратите внимание, что пользователь заблокирован
Интересен кстати прототип базонезависимой вм, пока глубоко не размышлял, но в теории было бы неплохо через неё пропускать байт-код, который образовался из компиляции шк. Либо же как второй кейс - инжект в чужой процесс полноценного интерпретатора какого либо байт-кода, который перед этим собирается компилятором с хайлевел языка. Нафантазировал конечно знатно, но пока не думал о возможностях реализации подобного, так что не пинайте)
Да можно взять какой-нить сравнительно простой язык, в реализации которого ВМ и компилятор отдельны, и попробовать из ВМ сделать базонезависимый код. Я последнее время слежу за развитием языка Umka, мне кажется, что он может подойти для этого: https://github.com/vtereshkov/umka-lang - ну или взять какую-нить пресловутую Lua или JS (duktape, v7 или quickjs).
 
Пожалуйста, обратите внимание, что пользователь заблокирован
https://xss.pro/threads/34080/
Это по типу AVLeak'а что-то, тоже была идея что-то такое запилить, но пока не было необходимости.
 
Пожалуйста, обратите внимание, что пользователь заблокирован
Это всё хорошо, но понятно что на форуме думаю 90% как-раз людей, которые и занимаются пентестингом, кардингом, ну и или просто кто-то интересуется чем-то и они очень далеки от ассемблера, си и т.д.,
97% сидящих тут не поймут даже примерно о том, о чем писали постами выше.
Ясен х#й выиграет статья под типу "Зарабатываем бабки быстро и легко" или какая-нибудь история из жизни.

Но таким нубам как я, подобные статьи ценный материал.

Кажись во всем андерграунде работают 10% малварь-кодеров, а все остальные это же монетезируют.
 
Интересен кстати прототип базонезависимой вм, пока глубоко не размышлял, но в теории было бы неплохо через неё пропускать байт-код, который образовался из компиляции шк.
Не любой шеллкод это PI код. PI код нужно уметь готовить, а значит не универсально.
Хотя с другой стороны вмпрот, темида и прочие как-то ведь науличись на уровне компиляции это делать...

Либо же как второй кейс - инжект в чужой процесс полноценного интерпретатора какого либо байт-кода, который перед этим собирается компилятором с хайлевел языка. Нафантазировал конечно знатно, но пока не думал о возможностях реализации подобного, так что не пинайте)
Можно в таком случае взять какой нибудь MicroPython как VM и офбускатор питон кода на сервере, далее оттуда дергать и отдавать каждый раз разный код..
Задача по сигнатурному\эвристическому детекту решается.
Задача по сокрытию алгоритма нет.
 
Пожалуйста, обратите внимание, что пользователь заблокирован
Задача по сокрытию алгоритма нет.
Виртуальную машину и/или байт-код разместить в программном анклаве (software enclave, Intel SGX) и тогда только эксплойтами в Intel SGX из нее что-то доставать, а это уже далеко не тривиальная задача для среднестатистического реверсера.
 
Пожалуйста, обратите внимание, что пользователь заблокирован
индий ты?
Нет, но я именно благодаря Индию и его снобству немножко проникся этой темой. Можно еще и VBS анклав попробовать, если не поддерживается SGX, но есть виртуализация. Но там немного по другому нужно действовать.
 

DildoFagins,

Прочитав статью более вдумчиво, появились вопросы:

1. Я так понимаю, в данной реализации (примере) получится кодировать строки (ксор и сложением) и генерировать простой мусорный код?

класс с помощью метода generate_trash генерирует мусорный код, а с помощью метода insert_trash_before вставляет его в те места, какие мы посчитали безопасными для вставки мусорного кода
Мусорный код, который не участвует в процессе прохода по основному коду, на сколько я понимаю, может легко быть свернут аверским алгоритмом.
Если не сложно, выложи пожалуйста выхлоп кода с этого обфускатора а не дизасм.

2.
Поскольку байндинги либшланга достаточно ограничены в том, что они могут вытворять, в частности нельзя производить модификации узлов дерева, а потом просто сдампить дерево обратно в исходный код, нам придется делать модификации исходного кода самим.
На самом деле да, вся прелесть построения AST дерева пропадает из за таких ограничений и костыли выглядят аналогом написания своего парсера, который даст более гибкий доступ к каждому элементу дерева.
Быстрый гуглинг показал, что существуют и другие решения, например astgen (формат для C кода старый C99) или pycparser, чем обусловлен выбор именно libclang?
Кстати вроде бы cppast, основанный также на libclang решает часть ограничений:
Parser based on libclang: While libclang does have its limitations and/or bugs, the implemented parser uses various workarounds/hacks to provide a parser that breaks only in rare edge cases you won't notice. See issues tagged with libclang-parser for a list;
Не смотрел его?

3. Чем обсусловлена генерация такого кода вручную
Python:
        self._tpl  = 'unsigned char {enam}[{elen}] = {{ {estr} }};'
        self._tpl += '{btyp} {bnam}[{elen}+1];'
        self._tpl += 'for(int {inam} = 0; {inam} < {elen}; {inam}++) {{'
        self._tpl +=   '{bnam}[{inam}] = ({btyp})({enam}[{inam}] ^ 0x{key:02X});'
        self._tpl += '}}'
        self._tpl += '{bnam}[{elen}] = ({btyp})0;'
перед (например) cgen?
Его препроцессор тоже довольно богат.
Генерация тоже выглядит более логичной.

Примеры:
Python:
import cgen as c

func = c.FunctionBody(
    c.FunctionDeclaration(c.Const(c.Pointer(c.Value("char", "greet"))), []),
    c.Block([
        c.Statement('return "hello world"')
        ])
    )

print(func)

Вывод:
C:
char const *greet()
{
  return "hello world";
}

Python:
from cgen import (
        POD, Struct, FunctionBody, FunctionDeclaration,
        For, If, Assign, Value, Block, ArrayOf, Comment,
        Template)

import numpy as np
import random

# генерация структуры:

s = Struct("yuck", [
        POD(np.float32, "h", ),
        POD(np.float32, "order"),
        POD(np.float32, "face_jacobian"),
        ArrayOf(POD(np.float32, "normal"), 17),
        POD(np.uint16, "a_base"),
        POD(np.uint16, "b_base"),
        POD(np.uint8, "b_ilist_number"),
        POD(np.uint8, "bdry_flux_number"),  # 0 if not on boundary
        POD(np.uint8, "reserved"),
        POD(np.uint32, "b_global_base"),
        ])


# генерация функции:

        f_decl = FunctionDeclaration(POD(np.uint16, func_name + str(random.randint(5,15)) ), [
            POD(np.uint8, "reserved"),
            POD(np.uint32, "b_global_base"),
            ])

        f_body = FunctionBody(f_decl, Block([
            POD(np.uint32, "i"),
            For("i = 0", "i < 17", "++i",
                If(
                    "a > b",
                    Assign("a", "b"),
                    Block([
                        Assign("a", "b-1"),
                        #Break(),
                        ])
                    ),
                ),
            #BlankLine(),
            Comment("all done"),
            ]))

        print(f_body)


на выходе даст:
C:
struct yuck
{
  float h;
  float order;
  float face_jacobian;
  float normal[17];
  short unsigned int a_base;
  short unsigned int b_base;
  unsigned char b_ilist_number;
  unsigned char bdry_flux_number;
  unsigned char reserved;
  unsigned int b_global_base;
} ;
short unsigned int func_14(unsigned char reserved, unsigned int b_global_base)
{
  unsigned int i;
  for (i = 0; i < 17; ++i)
    if (a > b)
      a = b;
    else
    {
      a = b-1;
    }
  /* all done */
}

4. В самом простом варианте, как должен выглядеть обфускатор на libclang, через который можно именно заморфить (а не разбавить мусорным треш-кодом) какой-нибудь код, например мой загрузчик?
 
Последнее редактирование:
Поскольку байндинги либшланга достаточно ограничены в том, что они могут вытворять, в частности нельзя производить модификации узлов дерева, а потом просто сдампить дерево обратно в исходный код, нам придется делать модификации исходного кода самим.
Такая тема встречается кста много где. В том же пш, к примеру. Узел дерева содержит Length и offset(позиция элемента относительно всего скрипта). Ну и приходится ковырять руками реплейсы.
Основной тут подводный камень в том, что необходимо продумывать алгоритм, для реплейса N значений за 1 проход. Поскольку после изменения узла на новое значение, позиции остальных спаршенных, аналогичных нод, будут смещены на разницу длины нового и старого значения узла. Делать же перепарс всего аста после каждого изменения - слишком большая вычислительная сложность, в случае если элементов реально много(например вшит шеллкод в текст программы).
 
Кстати, никто здесь не пробовал морфить через llvmlite? Это биндинги на питоне, содержащие функционал сугубо для парсинга/работы с IR кодом. Смотрел их документацию, сильно упрощают процесс в сравнении с работой непосредственно с самим ллвм.
 
Интересная затея еще модифицировать какой-нибудь опенсурсный сишный компилятор с целью автоморфинга и генерации идеального мусорного кода, взаимодействия с модифицируемым кодом.
Кто-нибудь баловался таким?
 
Интересная затея еще модифицировать какой-нибудь опенсурсный сишный компилятор с целью автоморфинга и генерации идеального мусорного кода, взаимодействия с модифицируемым кодом.
Кто-нибудь баловался таким?
В целом не лучшая затея. Но вообще я знал людей, которые писали проект по морфингу из под студии, модифицируя их компилятор. Так и не завершили, кстати говоря.
 
Пожалуйста, обратите внимание, что пользователь заблокирован
Я так понимаю, в данной реализации (примере) получится кодировать строки (ксор и сложением) и генерировать простой мусорный код?
Да именно так, как я писал в статье, целью статьи является рассказать как происходит обфускация на уровне абстрактного синтаксического дерева, и в показать в принципе, как выглядит типичная архитектура такого обфускатора: в частности реализация нескольких алгоритмов с единым интерфейсом и выбор псевдо-случайного из них. Реализованные алгоритмы шифрования строк и генерации мусорного кода приведены в виде примера, для реального коммерческого обфускатора нужно что-то получше. Но и в принципе в текущей реализации генерируется более менее приличный код, который может позволить там обойти сигнатруный детект по коду и статическим строкам например.

Мусорный код, который не участвует в процессе прохода по основному коду, на сколько я понимаю, может легко быть свернут аверским алгоритмом.
Ну теоретически да, если алгоритм достаточно умен, умеет там графы строить по коду и их анализировать. В общем случае алгоритмы деобфускации через оптимизацию (который есть в частности в декомпиляторе HexRays и которые достаточно простые) не должны удалять этот мусорный код, тк там используются глобальные переменные, а они могут изменяться из других потоков например. Старый как мир трюк на самом деле.

Если не сложно, выложи пожалуйста выхлоп кода с этого обфускатора а не дизасм.
Ну вот, это какой-то из примеров, для которого я приводил дизасм. Сначала применяется шифрование строк, потом сверху кода с шифрованием строк применяется генератор мусорного кода, чтобы чуток разбавить код дешифровки, в принципе можно было бы применить и в обратном порядке. Код добавляется в одну строку, чтобы не сбивать всякие директивы типа __LINE__, если они есть в коде, поэтому сгенерированный кодец не особо читаемый.
C++:
int main(int argc, char** argv) {static unsigned long long __tvar_00000000__ = 0xA54BD30BF23FD92F; static unsigned long long __tvar_00000001__ = 0x0D11BC9FD42DCAD7; static unsigned int __tvar_00000002__ = 0xFF9C1EE7; static unsigned char __tvar_00000003__ = 0x9F; static unsigned short __tvar_00000004__ = 0xBEEA;__tvar_00000002__ = (unsigned int)__tvar_00000001__ + 0x0F710288;__tvar_00000002__ = (unsigned int)__tvar_00000001__ * 0x3391D262;unsigned char __enc_00000000__[12] = { 0xD7,0xF4,0xFB,0xFB,0xFE,0xAF,0xE6,0xFE,0x01,0xFB,0xF3,0xB0 };__tvar_00000004__ = (unsigned short)__tvar_00000000__ - 0x81AD;__tvar_00000001__ = (unsigned long long)__tvar_00000003__ + 0x1E81A4A08B529C21;char __buf_00000000__[12+1];__tvar_00000001__ = (unsigned long long)__tvar_00000000__ & 0xB37BD81D0EFBEE5C;__tvar_00000001__ = (unsigned long long)__tvar_00000000__ - 0x7EB5D45A60C3F976;for(int __i_00000000__ = 0; __i_00000000__ < 12; __i_00000000__++) {__tvar_00000002__ = (unsigned int)__tvar_00000002__ ^ 0x339E93BE;__tvar_00000001__ = (unsigned long long)__tvar_00000004__ ^ 0x4498C345ADC8A721;unsigned int __tmp_00000000__ = __enc_00000000__[__i_00000000__] + 0x71;__tvar_00000002__ = (unsigned int)__tvar_00000004__ + 0x619C0A36;__tvar_00000002__ = (unsigned int)__tvar_00000002__ & 0xD9449D5A;__buf_00000000__[__i_00000000__] = (char)(__tmp_00000000__ & 0xFF);}__tvar_00000000__ = (unsigned long long)__tvar_00000002__ - 0xE8CAB3629F063C03;__tvar_00000003__ = (unsigned char)__tvar_00000004__ ^ 0x61;__buf_00000000__[12] = (char)0;__tvar_00000003__ = (unsigned char)__tvar_00000001__ & 0x7A;__tvar_00000000__ = (unsigned long long)__tvar_00000001__ | 0x0C43BBF60B498B66;unsigned char __enc_00000001__[14] = { 0xCE,0xE6,0xE6,0xED,0xEB,0xF0,0xEC,0xA9,0xDE,0xE6,0xFB,0xE5,0xED,0xA8 };__tvar_00000004__ = (unsigned short)__tvar_00000003__ ^ 0x7E9D;__tvar_00000001__ = (unsigned long long)__tvar_00000000__ | 0x259B308940F68E7B;__tvar_00000001__ = (unsigned long long)__tvar_00000000__ + 0x2FCC915DF00B27BA;char __buf_00000001__[14+1];__tvar_00000003__ = (unsigned char)__tvar_00000003__ + 0x19;__tvar_00000004__ = (unsigned short)__tvar_00000003__ - 0x6BB3;for(int __i_00000001__ = 0; __i_00000001__ < 14; __i_00000001__++) {__tvar_00000004__ = (unsigned short)__tvar_00000004__ * 0xA342;__tvar_00000004__ = (unsigned short)__tvar_00000004__ - 0x09BA;__tvar_00000004__ = (unsigned short)__tvar_00000004__ - 0xEDFD;__tvar_00000003__ = (unsigned char)__tvar_00000000__ | 0x8E;__tvar_00000000__ = (unsigned long long)__tvar_00000004__ & 0xB28B9037C7590FA7;__buf_00000001__[__i_00000001__] = (char)(__enc_00000001__[__i_00000001__] ^ 0x89);}__tvar_00000003__ = (unsigned char)__tvar_00000002__ & 0x03;__buf_00000001__[14] = (char)0;
    __tvar_00000003__ = (unsigned char)__tvar_00000000__ - 0x13;puts(__buf_00000000__);
    __tvar_00000000__ = (unsigned long long)__tvar_00000003__ & 0xF5F0300BEDE0CB1C;puts(__buf_00000001__);
    __tvar_00000003__ = (unsigned char)__tvar_00000004__ ^ 0xA8;return 0;
}

Чем обсусловлена генерация такого кода вручную
Ну это проще, чем генерировать AST-структуру (даже во многих языках с метапрограммированием реализовали квази цитирование, чтобы не генерировать монструозные структуры, а просто писать код примерно так, как он будет выглядеть в итоге, например quote стейтмент в языке Nim), но ничего не мешает тебе делать это и таким образом.

В самом простом варианте как должен выглядеть обфускатор на libclang, через который можно именно заморфить (а не разбавить мусорным треш-кодом) какой-нибудь код, например мой загрузчик?
Есть варианты с control flow flattening, можно заменять конкретные выражения на аналоги (но это лучше опять же сделать как то через глобальные переменные, чтобы оптимизаторы не могли их вырезать), можно разбивать функции псевдо-случайным образом на лямбды (для С++11 и старше), любые константы нужно шифровать само собой, ну и так далее.

Основной тут подводный камень в том, что необходимо продумывать алгоритм, для реплейса N значений за 1 проход. Поскольку после изменения узла на новое значение, позиции остальных спаршенных, аналогичных нод, будут смещены на разницу длины нового и старого значения узла.
Вот в статье как раз и реализован такой алгоритм.

Кстати, никто здесь не пробовал морфить через llvmlite? Это биндинги на питоне, содержащие функционал сугубо для парсинга/работы с IR кодом. Смотрел их документацию, сильно упрощают процесс в сравнении с работой непосредственно с самим ллвм.
LLVM IR более низкоуровневое представление. Все можно решить и на уровне LLVM IR, но имхо проще делать на уровне AST. Например, на уровне AST я могу быть уверен, что строковый литерал находится внутри функции и могу генерировать расшифровку на стек прямо в теле функции. На уровне LLVM все строковые литералы становятся константными глобальными переменными, следовательно нужно либо проходить все функции всего проекта и искать, где текущий строковый литерал используется, либо просто вставлять дешифратор в статические конструкторы, но это не так секьюрно.

Интересная затея еще модифицировать какой-нибудь опенсурсный сишный компилятор с целью автоморфинга и генерации идеального мусорного кода, взаимодействия с модифицируемым кодом.
Кто-нибудь баловался таким?
Ну я делал плагин к clang'у, получилось неплохо. Ну и экспериментировал с модификацией компилятора языка Nim и FreeBasic, там достаточно просто добавить дополнительный проход (compiler pass) в компилятор. Но эксперименты я до продакшн реди состояния не довел. Хорошая тема для следующего конкурса наверное, для языка Nim само собой, FreeBasic такой себе язык.
 
Пожалуйста, обратите внимание, что пользователь заблокирован
В целом не лучшая затея.
Ну если это сделать нормально, то это хорошая затея. В том плане, что ты находишься в пайплайне компилятора и тебе не надо парсить сорсы, ты просто модифицируешь AST или какое-то другое внутреннее представление. Касательно Nim, FreeBasic и плагина к Clang мы вставляем наши проходы в начало пайплайна и модифицируем AST, потом модифицированное AST передается дальше компилятору. Но нужно канеш еще предусмотреть дампинг кода после обфускации, чтобы иметь возможность отлаживать какие то проблемы.
 
Пожалуйста, обратите внимание, что пользователь заблокирован
Кстати еще чего подумал. В апи компилятора Nim есть интерпретатор для собственного AST, это используется для метапрограммирования. Но для наших целей мы можем его использовать в генераторе мусорного кода для эмуляции и вычисления непрозрачных предикатов.
 
Есть варианты с control flow flattening, можно заменять конкретные выражения на аналоги (но это лучше опять же сделать как то через глобальные переменные, чтобы оптимизаторы не могли их вырезать), можно разбивать функции псевдо-случайным образом на лямбды (для С++11 и старше), любые константы нужно шифровать само собой, ну и так далее.
Я специально привел в пример морфинг загрузчика (читай будущего шеллкода).
Морфинг шеллкода накладывает на себя ряд ограничений: такие как отсутствие возможности использовать глобальные переменные, лямбды и шаблоны.

Но для наших целей мы можем его использовать в генераторе мусорного кода для эмуляции и вычисления непрозрачных предикатов.
По сути дела всё именно так в движке моего криптора из статьи.

Вообще кодогенерация это второй вопрос.
Меня интересует непосредственно морфинг кода, а не размыливание строк безопасными выражениями,
т.е. на примере загрузчика морфим функции, условия, выражения, циклы, константы и т.д.
Вот на уровне AST это можно делать, но пока удобного красивого решения не вижу.
Ограничения libclang выглядят убедительными, чтобы искать другое решение - по этому я выше спросил за модификацию компилятора..
 


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