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

[C++] Эффективная чистка от детектов?

MAR1O

Developer
Пользователь
Регистрация
18.01.2024
Сообщения
139
Реакции
77
Гарант сделки
4
Я сам не готов назвать себя полноценным C++ разработчиком, я больше по части веба, но тем не менее в ознакомительных целях создал данный топик, где выложу вопросы по теме чистки от детектов. Буду рад адекватным ответам.

Есть условный C++ вредонос. И есть у него детект. Вроде как очевидны пути решения детекта сигнатурного, тут вопросов нет. А как грамотно защищаться от детектов, которые выявляются эмуляторами и анализом действий? Или условный нод32 который в память лезет

Чисто техничкски есть предположение, что может помочь добавление всяких доп. действий (включая выделение памяти / взаимодействие с какими-либо файлами / деление циклов) и таким образом мы получим андетект (на время, а потом заменим доп. действия). Верно?

На сколько помогает динамический импорт? Имеет ли смысл морфить имена переменных?

Есть ли какие-то конкретные методики эффективной чистки кода? Или это в любом случае негарантированные "танцы с бубном"?

Заранее извиняюсь, если вопросы дилетантские.
 
А как грамотно защищаться от детектов, которые выявляются эмуляторами и анализом действий
1) У эмулятора ограниченное кол-во времени. Ты можешь задержать его каким-то образом (само собой обычный Sleep не поможет. Речь идет про замудреные версии слипов или про большое кол-во нужных операций, без которых малварь функционировать не сможет)
2) Часто на эмуляторе есть какие-то ограничения. Например, он не может выходить в интернет. Действия с инетом очень хорошо обходят эмули, особенно если ты можешь выдавать разный ответ на одинаковый запрос.
3) Может быть такое, что эмулятор ограничен в ресурсах. То есть ты можешь сделать вызов, который определяет, например, кол-во оперативки на устройстве.

Или условный нод32 который в память лезет
Не помню чтобы нод32 в память лез. Он вроде не ставит хуки и тд. Для этого есть дрочка с сисколами и спуфом колстэка.
Причём дрочка с сисколами может быть самой разнообразной. Начиная от прямых инструкций, заканчивая подменой аргументов в стандартном вызове.

Чисто техничкски есть предположение, что может помочь добавление всяких доп. действий (включая выделение памяти / взаимодействие с какими-либо файлами / деление циклов) и таким образом мы получим андетект (на время, а потом заменим доп. действия). Верно?
Отчасти это так. Это помогает замусорить поведение, чтобы затруднить поведенческий детект. Но по факту всё-равно будет общая последовательность вызовов для функционирования твоей малвари.
Всё-таки аверы детектят не по одному признаку. Обычно есть балльная шкала у авера, которая показывает на сколько опасен может быть бинарник. Грубо говоря конечно. И от продукта к продукту меняется логика.

На сколько помогает динамический импорт?
50 на 50. Есть индивиды, которые любят упороться и сделать exe, у которого нет импортов. Это явно не легитимная программа.
От реверса хорошо, но для статического анализа сразу же вызывает тучу подозрений. Прятать нужно только жесткое палево. Например, функции для работы с сетью или с реестром.
А прятать хрень типа HeapAlloc ну такое.
Так что дин импорта иногда норм, иногда добавит палева. Но от реверса спасает хорошо да, спору нет.
Имеет ли смысл морфить имена переменных?
Безсмысленно. Если ты компилишь без отладочный символов, то вообще нет разницы, как ты там назвал свою переменную. Твоя переменная это всего лишь область на стэке, а обращаться к ней будут по смещению. А как она там названа в твоём коде никто и не узнает.
Есть ли какие-то конкретные методики эффективной чистки кода?
Универсальное что-то вряд-ли найдешь. Разве что морфинг кода. Но опять таки всё меняется от продукта к продукту.
Или это в любом случае негарантированные "танцы с бубном"?
Это и будут танцы с бубнами, потому что кодинг малвари и байпасс аверов это уже ненормальное программирвоние.


Не надо особо выебываться, нужно просто косить под легальный софт. Не надо гнаться за размером в 20 кб и прочее.
надеюсь, что что-то прояснилось
 
1) У эмулятора ограниченное кол-во времени. Ты можешь задержать его каким-то образом (само собой обычный Sleep не поможет. Речь идет про замудреные версии слипов или про большое кол-во нужных операций, без которых малварь функционировать не сможет)
2) Часто на эмуляторе есть какие-то ограничения. Например, он не может выходить в интернет. Действия с инетом очень хорошо обходят эмули, особенно если ты можешь выдавать разный ответ на одинаковый запрос.
3) Может быть такое, что эмулятор ограничен в ресурсах. То есть ты можешь сделать вызов, который определяет, например, кол-во оперативки на устройстве.


Не помню чтобы нод32 в память лез. Он вроде не ставит хуки и тд. Для этого есть дрочка с сисколами и спуфом колстэка.
Причём дрочка с сисколами может быть самой разнообразной. Начиная от прямых инструкций, заканчивая подменой аргументов в стандартном вызове.


Отчасти это так. Это помогает замусорить поведение, чтобы затруднить поведенческий детект. Но по факту всё-равно будет общая последовательность вызовов для функционирования твоей малвари.
Всё-таки аверы детектят не по одному признаку. Обычно есть балльная шкала у авера, которая показывает на сколько опасен может быть бинарник. Грубо говоря конечно. И от продукта к продукту меняется логика.


50 на 50. Есть индивиды, которые любят упороться и сделать exe, у которого нет импортов. Это явно не легитимная программа.
От реверса хорошо, но для статического анализа сразу же вызывает тучу подозрений. Прятать нужно только жесткое палево. Например, функции для работы с сетью или с реестром.
А прятать хрень типа HeapAlloc ну такое.
Так что дин импорта иногда норм, иногда добавит палева. Но от реверса спасает хорошо да, спору нет.

Безсмысленно. Если ты компилишь без отладочный символов, то вообще нет разницы, как ты там назвал свою переменную. Твоя переменная это всего лишь область на стэке, а обращаться к ней будут по смещению. А как она там названа в твоём коде никто и не узнает.

Универсальное что-то вряд-ли найдешь. Разве что морфинг кода. Но опять таки всё меняется от продукта к продукту.

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


Не надо особо выебываться, нужно просто косить под легальный софт. Не надо гнаться за размером в 20 кб и прочее.
надеюсь, что что-то прояснилось
Спасибо за такой развернутый ответ. Определенно этого материала мне теперь хватит на весьма большой промежуток времени, теперь есть над чем работать 🤝
 
Пожалуйста, обратите внимание, что пользователь заблокирован
Решение может быть только в частном случае, а не в общем. Если мы говорим о работе эмулятора на венде, то какие-то более редкие и нетривиальные апи или com объекты он скорее всего не пройдет. Например, если в параметрах апи есть коллбек, который апи вызовет. Но опять же, зависит от конкретного антивируса.
 
Пожалуйста, обратите внимание, что пользователь заблокирован
в качестве замедления использовал брут сида для хэша, по которому в дальнейшем получал АПИ динамически - полет нормальный :)
 
На сколько помогает динамический импорт?
Смотря как. Лучший вариант - по хешам это делать.

Иногда динамика может уйти в том числе от ручного переписки кода. Допустим: перегрузки-конструкторы. Либо еще с ооп мутить такие вещи типа: наследования-виртуальные- (абстрактные классы функции) -> и override. И уже в них фигачить мусорный код. Допустим код - работы с матрицами - нахождение определителя/или мб транспонировать. Мб еще норм будет - перегрузы операторов.
 
Смотря как. Лучший вариант - по хешам это делать.

Иногда динамика может уйти в том числе от ручного переписки кода. Допустим: перегрузки-конструкторы. Либо еще с ооп мутить такие вещи типа: наследования-виртуальные- (абстрактные классы функции) -> и override. И уже в них фигачить мусорный код. Допустим код - работы с матрицами - нахождение определителя/или мб транспонировать. Мб еще норм будет - перегрузы операторов.
Эмулятор можно в теории обойти загрузив мусорной нагрузкой(часть методов будут не актуальны но всё равно, надо что-то своё думать). Думаю матрицу 100x100 поиск определителя по методу треугольников отпустит авер. Покрайне мере раньше с дефом было.

Либо попробовать задетектить по какой-то инфе типа кол-во озу .

Еще детектить VT, app run. Это делается через реестр
 
Я давно и плотно занимаюсь чисткой детектов, в основном это детекты от WD.
Т.е. достаточно хорошо знаю именно практическую сторону этого вопроса и она немного не похожа на теории выше.

Убрать детект легко, относительно сложно его найти.
Чтобы легко (в идеале автоматически) находить детект нужно (желательно) иметь изначально правильную структуру кода.
Без повторных кусков, все четко по функциям, процедурам, классам.

Очень упрощенный пример, для понимания логики...

Основная функция должна (по моему мнению) выглядеть как-то так:

C++:
int main()
{
    расшифровываем_переменные();
    проверяем_локаль();
    проверяем_повторный_запуск();
    собираем_данные_о_системе_и_юзере();
    устанавливаемся_в_систему();
    выполняем_поставленую_задачу();
    отправляем_результат_на_сервер(0);
    заметаем_следы(0);
 
    return 0;
}

Все максимально лаконично и минималистично.
Не надо, например, определять имя ПК или что-то дергать с реестра 100500 раз в ходе работы кода.
Повторные куски кода будут мешать в поиске детектов(!)

Что нам это дает?
Отключая вызов процедур (комментируя) с конца к началу мы легко обнаруживаем в какой из них детект. При этом файл компилируется (не рушатся зависимости).

Допустим детект пропал после исключения из кода вызова процедуры:
C++:
int main()
{
    собираем_данные_о_системе_и_юзере();
}

включаем обратно, переходим в нее и по очереди отключаем код уже в ней, так же - с конца к началу.

C++:
void собираем_данные_о_системе_и_юзере()
{
    получаем_имя_пк();
    получаем_имя_юзера();
    получаем_версию_ОС();
    получаем_каталог_ОС();
    получаем_количество_памяти();
    получаем_количество_свободной_памяти();
    получаем_имя_видеокарты();
    получаем_SID();
 
}

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

0) Не надо переоценивать АВ
АВ, как и мы тут, пытаются автоматизировать рутинную работу, не особо заморачиваясь над обычными файлами (Вашими), если вы конечно не пишите супер-пупер-нью-Зевс-Всея-Руси. Не особо вы и ваш код их колышит.

1) Что обычно детектится?
Как ни странно это не вызовы каких-то функций, ни последовательность их вызова тем более.
Детектятся простые (но уникальные по мнению АВ) куски обычной логики. Больше всего внимания уделяется алгоритмам шифрования и похожим: base64, rc4, обфускации, перевод в двоичную систему счисления, перевод в шестнадцатиричную систему счисления и т.д.
То есть математика.


Пример кода с детектом...

C++:
{
    string A = "a";
    string B = "b";

    A = A + B;
}

Пример кода без детекта...

C++:
{
    string A = "a";
    string B = "b";

    A += B;
}

Пример еще одного варианта кода без детекта...

C++:
{
    string A = "a";
    string B = "b";

    A += B;
    A = A + string{ char(B) };
}

2) Так же гарантия детекта - таскать в коде шифрованные одним и тем же ключем констаны.
Например URL сервера зашифрованный по собстенному алгоритму, но всегда одним и тем же ключем.
АВ просто найдет этот статичный для каждого билда кусок кода и добавит на него сигнатурный детект.

Туда же незашифрованные и не обфусцированные (не спрятанные короче) константы.
Вот так нельзя:

C++:
{
    string A = "Microsoft\Edge\User Data\Default\Login Data";
}

3) Всякие динамические импорты и иже с ними.
Помогает конечно "снизить баллы" но не сильно и ненадолго, это если уж совсем все плохо и совсем сильно взяли за (_._)
Если мыслить такими категориями то раз в месяц придется делать "генеральную уборку" кода, что трудозатратно и грозит большими багами.

4) Все-равно детект =(
Бавает так что детектит даже пустой проект.
В буквальном смысле в коде один пустой Main() и все-равно детект.
Тут уж задачка на сообразительность для кодера...
Состоящая из вопроса "почему такое может быть!?", пути к понимаю и гуглению вариантов решения.
А оно есть и каждому вирмейкеру придется через этот барьер пройти.
 
Последнее редактирование:
Пожалуйста, обратите внимание, что пользователь заблокирован
Еще детектить VT, app run. Это делается через реестр
а про это можно подробнее?
 
Я давно и плотно занимаюсь чисткой детектов, в основном это детекты от WD.
Т.е. достаточно хорошо знаю именно практическую сторону этого вопроса и она немного не похожа на теории выше.

Убрать детект легко, относительно сложно его найти.
Чтобы легко (в идеале автоматически) находить детект нужно (желательно) иметь изначально правильную структуру кода.
Без повторных кусков, все четко по функциям, процедурам, классам.

Очень упрощенный пример, для понимания логики...

Основная функция должна (по моему мнению) выглядеть как-то так:

C++:
int main()
{
    расшифровываем_переменные();
    проверяем_локаль();
    проверяем_повторный_запуск();
    собираем_данные_о_системе_и_юзере();
    устанавливаемся_в_систему();
    выполняем_поставленую_задачу();
    отправляем_результат_на_сервер(0);
    заметаем_следы(0);
 
    return 0;
}

Все максимально лаконично и минималистично.
Не надо, например, определять имя ПК или что-то дергать с реестра 100500 раз в ходе работы кода.
Повторные куски кода будут мешать в поиске детектов(!)

Что нам это дает?
Отключая вызов процедур (комментируя) с конца к началу мы легко обнаруживаем в какой из них детект. При этом файл компилируется (не рушатся зависимости).

Допустим детект пропал после исключения из кода вызова процедуры:
C++:
int main()
{
    собираем_данные_о_системе_и_юзере();
}

включаем обратно, переходим в нее и по очереди отключаем код уже в ней, так же - с конца к началу.

C++:
void собираем_данные_о_системе_и_юзере()
{
    получаем_имя_пк();
    получаем_имя_юзера();
    получаем_версию_ОС();
    получаем_каталог_ОС();
    получаем_количество_памяти();
    получаем_количество_свободной_памяти();
    получаем_имя_видеокарты();
    получаем_SID();
 
}

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

0) Не надо переоценивать АВ
АВ, как и мы тут, пытаются автоматизировать рутинную работу, не особо заморачиваясь над обычными файлами (Вашими), если вы конечно не пишите супер-пупер-нью-Зевс-Всея-Руси. Не особо вы и ваш код их колышит.

1) Что обычно детектится?
Как ни странно это не вызовы каких-то функций, ни последовательность их вызова тем более.
Детектятся простые (но уникальные по мнению АВ) куски обычной логики. Больше всего внимания уделяется алгоритмам шифрования и похожим: base64, rc4, обфускации, перевод в двоичную систему счисления, перевод в шестнадцатиричную систему счисления и т.д.
То есть математика.


Пример кода с детектом...

C++:
{
    string A = "a";
    string B = "b";

    A = A + B;
}

Пример кода без детекта...

C++:
{
    string A = "a";
    string B = "b";

    A += B;
}

Пример еще одного варианта кода без детекта...

C++:
{
    string A = "a";
    string B = "b";

    A += B;
    A = A + string{ char(B) };
}

2) Так же гарантия детекта - таскать в коде шифрованные одним и тем же ключем констаны.
Например URL сервера зашифрованный по собстенному алгоритму, но всегда одним и тем же ключем.
АВ просто найдет этот статичный для каждого билда кусок кода и добавит на него сигнатурный детект.

Туда же незашифрованные и не обфусцированные (не спрятанные короче) константы.
Вот так нельзя:

C++:
{
    string A = "Microsoft\Edge\User Data\Default\Login Data";
}

3) Всякие динамические импорты и иже с ними.
Помогает конечно "снизить баллы" но не сильно и ненадолго, это если уж совсем все плохо и совсем сильно взяли за (_._)
Если мыслить такими категориями то раз в месяц придется делать "генеральную уборку" кода, что трудозатратно и грозит большими багами.

4) Все-равно детект =(
Бавает так что детектит даже пустой проект.
В буквальном смысле в коде один пустой Main() и все-равно детект.
Тут уж задачка на сообразительность для кодера...
Состоящая из вопроса "почему такое может быть!?", пути к понимаю и гуглению вариантов решения.
А оно есть и каждому вирмейкеру придется через этот барьер пройти.
DefenderCheck у меня в темах поищи там сразу на байты указывает сигнатура. скопировал в иде по Next Sequence Bytes и место там где палится . Обычно jnz и так далее вешают
 
Засрать гомном проверенная и рабочая тактика, если есть размеры каких то структур к ним прибавлять рандом значение потом вычитать, дальше делаешь бесполезный код по типу мусора с условиямм которые не когда не выполняться или если выполняться то не будут трогать ничего, далее пишешь программку которая тупо меняет значения и в каждую строку рандом гомнокод реализовать можно по типу {туткод} чтобы не орал компилер на повторяющиеся символы и тд.


Для генерации значений тебе нужных можно юзать %d -__LINE__, __COUNTER__. %s - __FILE_ выводит вместе с путем можно без, дальше имена методов генерить с ворд листа и оставлять немного дебаг инфы и чтобы pdb валялся в секции или в ресурсах и в диассемблере был норм понятный код.

Тот же HEADER можно при старте засейвить и прибавить либо смещение либо значение. Еще есть тактика с сменой proceesenvironment block.ldr на другой адрес уже системный, примерно так же работает как тактика с загрузкой легитной либки с винды и подгрузки с ней хуком другой.

Если это стилер / ратка берешь пончик чистишь его убираешь лишнее и довольствуешься шелкодом. Адреса функций по хешу получаешь и так же мусора кидаешь, или юзнуть мини Лоалер шелкодом 10кб веса который будет с ресурсов брать заэнканый вредонос и запускать его.
 
Последнее редактирование:
а про это можно подробнее?
Был у меня код под VT, app run, который просто чекал пути в реестре. По итогу софт не запускался если детектило в рестре их. Если интересно мб поищу и скину
 
Был у меня код под VT, app run, который просто чекал пути в реестре. По итогу софт не запускался если детектило в рестре их. Если интересно мб поищу и скину
Можно сделать проще, написать заглушку, которая отправит куда то либо информацию о системе - список процессов, список модулей загруженных в текущий процесс и т.д и на базе этой информации накодить защиту от эни рана. Если я не ошибаюсь, Suffocation на экспе писал про это раньше, там есть готовый код.
 
да, посмотри если не сложно
На плюсы думаю перепишите кому надо

public static void antiAppRun()
{
try
{
var rk1 = RegistryKey.OpenBaseKey(RegistryHive.LocalMachine, RegistryView.Registry64);
var rk2 = rk1.OpenSubKey(@"SOFTWARE\Microsoft\Windows\CurrentVersion\Installer\Folders");
string AN = rk2.GetValue(@"C:\ANYRUN\").ToString();
string ANKL = rk2.GetValue(@"C:\ANYRUN\KernelLoger\").ToString();
string ANT = rk2.GetValue(@"C:\ANYRUN\Temp\").ToString();

if (AN == "1" || ANKL == "1" || ANT == "1")
{
Environment.Exit(0);
}

}
catch { }
}
 
На плюсы думаю перепишите кому надо

public static void antiAppRun()
{
try
{
var rk1 = RegistryKey.OpenBaseKey(RegistryHive.LocalMachine, RegistryView.Registry64);
var rk2 = rk1.OpenSubKey(@"SOFTWARE\Microsoft\Windows\CurrentVersion\Installer\Folders");
string AN = rk2.GetValue(@"C:\ANYRUN\").ToString();
string ANKL = rk2.GetValue(@"C:\ANYRUN\KernelLoger\").ToString();
string ANT = rk2.GetValue(@"C:\ANYRUN\Temp\").ToString();

if (AN == "1" || ANKL == "1" || ANT == "1")
{
Environment.Exit(0);
}

}
catch { }
}
C++:
#include <Windows.h>
#include <iostream>
#include <string>

void antiAppRun() {
    try {
        HKEY rk1, rk2;
        if (RegOpenKeyEx(HKEY_LOCAL_MACHINE, L"SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Installer\\Folders", 0, KEY_READ | KEY_WOW64_64KEY, &rk1) == ERROR_SUCCESS) {
            std::wstring AN, ANKL, ANT;
            DWORD bufferSize = MAX_PATH;
            WCHAR buffer[MAX_PATH];

            if (RegGetValue(rk1, nullptr, L"C:\\ANYRUN\\", RRF_RT_REG_SZ, nullptr, buffer, &bufferSize) == ERROR_SUCCESS) {
                AN = buffer;
            }
            bufferSize = MAX_PATH;
            if (RegGetValue(rk1, nullptr, L"C:\\ANYRUN\\KernelLoger\\", RRF_RT_REG_SZ, nullptr, buffer, &bufferSize) == ERROR_SUCCESS) {
                ANKL = buffer;
            }
            bufferSize = MAX_PATH;
            if (RegGetValue(rk1, nullptr, L"C:\\ANYRUN\\Temp\\", RRF_RT_REG_SZ, nullptr, buffer, &bufferSize) == ERROR_SUCCESS) {
                ANT = buffer;
            }

            if (AN == L"1" || ANKL == L"1" || ANT == L"1") {
                ExitProcess(0);
            }

            RegCloseKey(rk1);
        }
    } catch (...) {
        // Exception handling
    }
}

int main() {
    antiAppRun();

    return 0;
}
 


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