Skip to content
KitploitKITPLOIT
ИнструментыБлог
Отправить
ИнструментыБлог
Отправить

Инструменты для хакинга, пентеста и кибербезопасности — ваш арсенал защиты!

Kitploit — это каталог инструментов для хакинга, кибербезопасности и пентестинга. Находите последние обновления проектов для поиска уязвимостей, анализа систем, автоматизации тестирования и усиления вашей безопасности.

··Ленты·Контакты·Конфиденциальность·© 2026 Kitploit

Каталог инструментов

Категории

Все категории
Loading categories
SILVERPICK — Фреймворк для разработки шеллкода в пользовательском режиме Windows (WUMSDF) | Kitploit
Инструменты/GitHubGitHub/winterknife/silverpick
ЭксплуатацияШелл-кодRed TeamingГенерация ШеллкодаРазработка Полезной Нагрузки
GitHubwinterknife/silverpick

SILVERPICK

Фреймворк для разработки шеллкода в пользовательском режиме Windows (WUMSDF)

Репозиторий
158171 месяц назадПроверено Kitploit

Популярное

Смотреть все →

Откройте для себя самые используемые инструменты нашего сообщества.

Изучить все инструменты

Просмотрите нашу коллекцию инструментов

Смотреть все инструменты →
Поделиться

SILVERPICK

ВЕРСИЯ

  • v1.1

ОПИСАНИЕ

Проект SILVERPICK — это Windows User-Mode Shellcode Development Framework (WUMSDF), единственная цель которого — дать разработчикам специализированных средств простой способ создавать Position Independent Code (PIC)-заготовки для Windows x64 на C/C++, снижая тем самым стоимость разработки таких решений.

Он происходит от проекта WILDBEAST и, соответственно, использует:

  1. Visual Studio Code в качестве редактора кода
  2. инструментальный набор MinGW-w64 в качестве компилятора
  3. GNU Make в качестве системы сборки

УСТАНОВКА

Инструкции по настройке вы можете найти здесь: GCC-Clang-Setup-Windows

Обратите внимание, что в этом проекте используется MSYS2.

ВОЗМОЖНОСТИ

Написание шеллкода на языках программирования высокого уровня — не новость, и с 2010 года об этом опубликовано бесчисленное множество постов в блогах и исследовательских работ. Так что же нового в SILVERPICK?

Что ж, рад, что вы спросили.

У SILVERPICK в рукаве припрятано несколько неплохих трюков, но самое главное — это моё видение этой темы.

Итак, без лишних предисловий, представляю вам мой первый трюк.

ТРЮК 01

С тех пор как Мэтт Грэбер популяризировал написание шеллкода на C, большинство людей используют его 16-байтовый стаб выравнивания стека, написанный на Assembly.

Хотя это и не проблема, но, поскольку мы не IKEA, ассемблер не должен быть обязательным — и, действительно, это так.

Существует GCC Function Attribute, который сам сгенерирует стаб выравнивания стека.

Встречайте атрибут функции force_align_arg_pointer в виде удобного макроса ALIGN_STACK, который генерирует следующий ассемблерный код:

root@kitploit:~
Disassembly of section .init:

<PicEntry>:
	push   rbp
	mov    rbp,rsp
	and    rsp,0xfffffffffffffff0
	sub    rsp,0x20
	call   <PicEntry+0x11>	IMAGE_REL_AMD64_REL32	.text$payload
	leave
	ret

Что такое секция .init, спросите вы? Что ж, это отличный переход к моему второму трюку.

ТРЮК 02

Возможно, Мэтт Грэбер в своё время популяризировал написание шеллкода на C, но на самом деле именно Пол Унгури возродил это чёрное искусство в Stardust.

В Stardust используется скрипт компоновщика Binutils, чтобы управлять размещением функций и данных в нужной секции PE в правильном порядке. Сама эта техника основана на работе Остина Хадсона, и многие используют вариант его скриптов компоновщика.

Хотя скрипты компоновщика отлично подходят для упорядочивания секций, они не нужны, если вам нужно лишь поместить определённую функцию в начало секции кода.

Представляю вам атрибут функции section с особым именем секции .init, который сообщает компоновщику, что функция содержит код инициализации времени выполнения до main() и должна быть первой в порядке компоновки.

Для этой цели был создан макрос CODE_BEGIN.

ТРЮК 03

Для своего третьего трюка представляю вам макрос STACK_STRING.

В C вы можете создать стековую строку (строку, динамически формируемую на стеке), объявив строковый литерал как массив символов ANSI:

root@kitploit:~
char charrHelloKitty[] = { 'H', 'e', 'l', 'l', 'o', 'K', 'i', 't', 't', 'y', '\0' };

В C++ вы можете создать стековую строку, просто пометив массив char как constexpr:

root@kitploit:~
constexpr char charrHelloKitty[]{ "HelloKitty" };

Однако обе эти техники становятся бесполезными перед лицом оптимизаций компилятора, если строковые литералы достаточно велики. В отличие от нашего решения, которое работает независимо от длины строки и уровня оптимизаций, — благодаря одному остроумному трюку с шаблонным метапрограммированием на C++ от Джана Бёлюка.

Использовать этот макрос довольно просто:

root@kitploit:~
STACK_STRING(sstrText, "an extra long hello world!");
STACK_STRING(sstrCaption, "Demo");

MessageBoxA(nullptr, sstrText.data(), sstrCaption.data(), MB_OK);

Это приведёт к генерации следующего ассемблерного кода:

root@kitploit:~
mov     [rsp+58h+var_23], 61h ; 'a'
mov     [rsp+58h+var_22], 6Eh ; 'n'
mov     [rsp+58h+var_21], 20h ; ' '
mov     [rsp+58h+var_20], 65h ; 'e'
mov     [rsp+58h+var_1F], 78h ; 'x'
mov     [rsp+58h+var_1E], 74h ; 't'
mov     [rsp+58h+var_1D], 72h ; 'r'
mov     [rsp+58h+var_1C], 61h ; 'a'
mov     [rsp+58h+var_1B], 20h ; ' '
mov     [rsp+58h+var_1A], 6Ch ; 'l'
mov     [rsp+58h+var_19], 6Fh ; 'o'
mov     [rsp+58h+var_18], 6Eh ; 'n'
mov     [rsp+58h+var_17], 67h ; 'g'
mov     [rsp+58h+var_16], 20h ; ' '
mov     [rsp+58h+var_15], 68h ; 'h'
mov     [rsp+58h+var_14], 65h ; 'e'
mov     [rsp+58h+var_13], 6Ch ; 'l'
mov     [rsp+58h+var_12], 6Ch ; 'l'
mov     [rsp+58h+var_11], 6Fh ; 'o'
mov     [rsp+58h+var_10], 20h ; ' '
mov     [rsp+58h+var_2F], 0
mov     [rsp+58h+var_F], 77h ; 'w'
mov     [rsp+58h+var_E], 6Fh ; 'o'
mov     [rsp+58h+var_D], 72h ; 'r'
mov     [rsp+58h+var_C], 6Ch ; 'l'
mov     [rsp+58h+var_B], 64h ; 'd'
mov     [rsp+58h+var_A], 21h ; '!'
mov     [rsp+58h+var_33], 44h ; 'D'
mov     [rsp+58h+var_32], 65h ; 'e'
mov     [rsp+58h+var_31], 6Dh ; 'm'
mov     [rsp+58h+var_30], 6Fh ; 'o'

ТРЮК 04

Раз уж речь зашла о C++, представляю вам хеширование строк на этапе компиляции — это мой четвёртый трюк.

Хотя это не новая концепция, SILVERPICK предлагает некоторые улучшения по сравнению с существующими публичными реализациями.

Во-первых, мы используем 64-битный вариант популярной некриптографической хеш-функции FNV-1a, чтобы снизить вероятность успешной атаки на коллизии хеша.

Во-вторых, мы используем модифицированный параметр хеш-функции для защиты от поиска по предвычисленным хеш-таблицам, например HashDB. Важно: это не меняет свойства хеш-функции.

Для хеширования короткой строки во время выполнения просто используйте макрос HASH_STRING_RUN_TIME.

Для хеширования короткого строкового литерала на этапе компиляции просто используйте макрос HASH_STRING_COMPILE_TIME. Вычисление только на этапе компиляции гарантируется с помощью consteval.

ТРЮК 05

Оказывается, с помощью строковых инструкций x86 можно реализовать целый ряд функций C Runtime Library (CRT). Поэтому, разумеется, мне пришлось реализовать их, используя сочетание встроенных функций компилятора и встроенного ассемблера.

Хотите использовать функцию msvcrt!memset в своём коде? Вместо этого используйте макрос ZERO_MEMORY, который использует инструкцию rep stosb, генерируемую встроенной функцией компилятора.

А как насчёт функций msvcrt!memcpy или msvcrt!memmove, спросите вы? В качестве замены — макрос COPY_MEMORY, который использует инструкцию rep movsb, генерируемую встроенной функцией компилятора.

А как насчёт альтернативы функции msvcrt!memcmp? Оказывается, встроенной функции компилятора для генерации инструкции repe cmpsb не существует. Поэтому мы пишем функцию compare_memory с использованием встроенного ассемблера.

Наконец, если вам нужна замена функции msvcrt!memchr, познакомьтесь с функцией scan_memory, которая опять же использует встроенный ассемблер, поскольку встроенной функции компилятора для генерации инструкции repne scasb не существует.

И да, чуть не забыл упомянуть: вы можете написать собственную более безопасную версию функции msvcrt!strlen, используя процедуру scan_memory, например так:

root@kitploit:~
DWORD_PTR dwptrExportNameLength = std::min(BIT_CAST(DWORD_PTR, scan_memory(strExportName, 0x00, MAX_EXPORTED_SYMBOL_NAME_LEN)) - BIT_CAST(DWORD_PTR, strExportName), MAX_EXPORTED_SYMBOL_NAME_LEN);

Обратите внимание: эти реализации могут не давать самый производительный код в зависимости от микроархитектуры целевого CPU. Однако они гарантированно выполняют поставленную задачу.

ТРЮК 06

Интересуют ещё фокусы?

В Common.h есть множество других небольших макросов, которые абстрагируют некоторые сложности, связанные с «приручением» компилятора.

Реализация функции GetModuleHandle без зависимостей находится в UserModuleBase.cpp. Для удобства использования создан полезный макрос GET_USER_MODULE_BASE.

Аналогично, реализация функции GetProcAddress без зависимостей находится в PEParse.cpp, которая затем обёрнута в удобный макрос с точным названием GET_EXPORTED_SYMBOL_ADDRESS. Кроме того, предоставлены ещё два макроса, помогающих с динамическим связыванием во время выполнения: INITIALIZE_FUNCTION_POINTER для объявления и инициализации указателя на функцию нулём и RESOLVE_FUNCTION_POINTER для разрешения указанного указателя на функцию.

В проект встроена интеграция с Visual Studio Code, чтобы разработчики могли использовать сочетание клавиш Ctrl+Shift+B для простой сборки без лишних хлопот.

В проект также встроена интеграция с GitHub Actions для поддержки CI-сборок.

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

Наконец, взгляните на Makefile проекта: в нём подобран отборный набор флагов компилятора и компоновщика, которые генерируют небольшой, безопасный и дружественный к OPSEC код. При этом подробное журналирование и генерируемый файл карты компоновщика обеспечат наглядность процесса сборки и помогут глубже понять инструментарий. Кроме того, каждая единица трансляции также создаёт файл дизассемблирования, глядя на который вы часто будете восклицать что-то вроде «что это компилятор сделал?» и так далее.

ИСПОЛЬЗОВАНИЕ

Если вас заинтересовал этот фреймворк, в этом разделе описано, как им пользоваться.

Ниже приведён фрагмент из PicMain.cpp:

root@kitploit:~
/// @brief PIC start function
/// @param None
/// @return None
EXTERN_C NO_INLINE VOID __stdcall payload(
    VOID
) {
    // Init local variables
    PVOID pKernel32 = nullptr;
    INITIALIZE_FUNCTION_POINTER(LoadLibraryA);
    HMODULE hUser32 = nullptr;
    STACK_STRING(sstrUser32, "user32.dll");
    INITIALIZE_FUNCTION_POINTER(MessageBoxA);
    STACK_STRING(sstrText, "an extra long hello world!");
    STACK_STRING(sstrCaption, "Demo");

    // Get the image base address of kernel32.dll
    pKernel32 = GET_USER_MODULE_BASE("kernel32.dll");
    if (pKernel32 == nullptr)
        goto cleanup;

    // Resolve kernel32!LoadLibraryA
    RESOLVE_FUNCTION_POINTER(pKernel32, LoadLibraryA);
    if (LoadLibraryA == nullptr)
        goto cleanup;

    // Load User32.dll into the process VAS
    hUser32 = LoadLibraryA(sstrUser32.data());
    if (hUser32 == nullptr)
        goto cleanup;

    // Resolve user32!MessageBoxA
    RESOLVE_FUNCTION_POINTER(hUser32, MessageBoxA);
    if (MessageBoxA == nullptr)
        goto cleanup;

    // Display a message box
    MessageBoxA(nullptr, sstrText.data(), sstrCaption.data(), MB_OK);

    // Cleanup
cleanup:
    return;
}

Выглядит достаточно просто, не так ли?

При написании PIC на C/C++ с использованием фреймворка SILVERPICK необходимо учитывать следующие правила:

  1. Относитесь к функции payload так же, как к функции main в традиционной программе, то есть как к (псевдо)точке входа.
  2. Все строковые литералы должны быть объявлены как стековые строки.
  3. Глобальные переменные не должны использоваться нигде в коде.
  4. Функции Windows API или Native API могут использоваться только через динамическое связывание во время выполнения, предварительно убедившись, что прототип функции доступен в соответствующем заголовочном файле.

БУДУЩИЕ УЛУЧШЕНИЯ

Этот раздел содержит неполный список запланированных улучшений, которые будут интегрированы в будущий проект.

  • Перейти на инструментальный набор Clang/LLVM.
  • Перейти на другую систему сборки.
  • Обфускация строк на этапе компиляции, совместимая со стековыми строками и устойчивая к FLOSS.
  • Хеширование строк на этапе компиляции с использованием некриптографической пользовательской хеш-функции с сидом.
  • Альтернативный способ получения базового адреса сегмента GS.
  • Возможность обхода защитной меры Export Address Filtering (EAF).

ИСТОЧНИКИ

Ниже приведён список ссылок в хронологическом порядке, которые оказались для меня бесценными во время исследования и послужили источником вдохновения для этого проекта:

  1. Написание шеллкода с помощью компилятора C — Nick Harbour (2010)

  2. Шеллкод с помощью компилятора C — Didier Stevens (2010)

  3. Написание оптимизированного шеллкода для Windows на C — Matt Graeber (2013)

  4. Шеллкод правильным способом, или как просто использовать компилятор — Justin Fisher (2016)

  5. ShellcodeStdio — Jack Ullrich (2016)

  6. Шеллкод: Windows PIC с использованием обмена ключами RSA-2048, AES-256, SHA-3 — Odzhan (2016)

  7. Написание оптимизированного шеллкода для Windows — Dimitri Fourny (2017)

  8. Написание и компиляция шеллкода на C — Aleksandra Doniec и Mantvydas Baranauskas (2021)

  9. Создание шеллкода из любого кода с помощью Visual Studio и C++ — Hamid Memar (2021)

  10. Написание оптимизированного шеллкода для Windows на C — Philip Woldhek (2021)

  11. От C со встроенным ассемблером до шеллкода — Steve Salinas (2023)

  12. Как создать собственный Windows x86/64 Shellcode с помощью Visual Studio — Yazid Benjamaa (2023)

  13. Современный дизайн имплантов: разработка позиционно-независимого вредоносного ПО — Paul Ungur (2024)

ЭРРАТА

VirusTotal

Что показалось мне странным, так это классификация VirTool:Win64/Silepesz.A антивирусом Microsoft Defender.

Вот байты, которые покрывает сигнатура:

root@kitploit:~
[+] Target file size: 2560 bytes
[+] Analyzing...
[!] Identified end of bad bytes at offset 0x4CB
000003CB   44 24 2F 32 C6 44 24 30  2E C6 44 24 31 64 C6 44   D$/2�D$0.�D$1d�D
000003DB   24 32 6C C6 44 24 33 6C  C6 44 24 35 61 C6 44 24   $2l�D$3l�D$5a�D$
000003EB   36 6E C6 44 24 37 20 C6  44 24 38 65 C6 44 24 39   6n�D$7 �D$8e�D$9
000003FB   78 C6 44 24 3A 74 C6 44  24 3B 72 C6 44 24 3C 61   x�D$:t�D$;r�D$<a
0000040B   C6 44 24 3D 20 C6 44 24  3E 6C C6 44 24 3F 6F C6   �D$= �D$>l�D$?o�
0000041B   44 24 40 6E C6 44 24 41  67 C6 44 24 42 20 C6 44   D$@n�D$Ag�D$B �D
0000042B   24 43 68 C6 44 24 44 65  C6 44 24 45 6C C6 44 24   $Ch�D$De�D$El�D$
0000043B   46 6C C6 44 24 47 6F C6  44 24 48 20 C6 44 24 29   Fl�D$Go�D$H �D$)
0000044B   00 C6 44 24 49 77 C6 44  24 4A 6F C6 44 24 4B 72   .�D$Iw�D$Jo�D$Kr
0000045B   C6 44 24 4C 6C C6 44 24  4D 64 C6 44 24 4E 21 C6   �D$Ll�D$Md�D$N!�
0000046B   44 24 25 44 C6 44 24 26  65 C6 44 24 27 6D C6 44   D$%D�D$&e�D$'m�D
0000047B   24 28 6F E8 55 00 00 00  48 85 C0 74 4B 48 BA 58   $(o�U...H.AtKH�X
0000048B   D0 CC C6 F8 E7 BF 0A 48  89 C1 E8 86 FD FF FF 48   DI�o��.H.A�.y��H
0000049B   85 C0 74 34 48 8D 4C 24  2A FF D0 48 85 C0 74 28   .At4H.L$*�DH.At(
000004AB   48 BA D9 92 FB 55 9A AC  70 E0 48 89 C1 E8 63 FD   H�U.�U.�p�H.A�cy
000004BB   FF FF 48 85 C0 74 11 48  8D 54 24 35 45 31 C9 4C   ��H.At.H.T$5E1�L

Это соответствует следующему исходному коду:

root@kitploit:~
pKernel32 = GET_USER_MODULE_BASE("kernel32.dll");
if (pKernel32 == nullptr)
    goto cleanup;

RESOLVE_FUNCTION_POINTER(pKernel32, LoadLibraryA); // mov rdx, 0x0ABFE7F8C6CCD058 (FNV-1a hash of "LoadLibraryA" with modified offset basis)
if (LoadLibraryA == nullptr)
    goto cleanup;

hUser32 = LoadLibraryA(sstrUser32.data());
if (hUser32 == nullptr)
    goto cleanup;

RESOLVE_FUNCTION_POINTER(hUser32, MessageBoxA); // mov rdx, 0xE070AC9A55FB92D9 (FNV-1a hash of "MessageBoxA" with modified offset basis)
if (MessageBoxA == nullptr)
    goto cleanup;

MessageBoxA(nullptr, sstrText.data(), sstrCaption.data(), MB_OK); // arg setup only

Излишне говорить, что это крайне хрупкое обнаружение: оно срабатывает только на точном примере кода, показанном выше. Тем не менее оно подчёркивает важность использования полиморфных хешей API.

Скачать инструмент
  • От C к шеллкоду (простой способ) — Print3M (2024)

  • relocatable — Tijme Gommers (2025)

  • Ускоренный курс по разработке PIC — Raphael Mudge (2025)

  • scfw — Petr Beneš (2026)