Skip to content
KitploitKITPLOIT
工具博客
提交
工具博客
提交

黑客、渗透测试和网络安全工具,武装您的安全武器库!

Kitploit 是一个黑客、网络安全和渗透测试工具的目录。发现最新的项目更新,查找漏洞、分析系统、自动化测试并加强你的安全。

··订阅源·联系·隐私·© 2026 Kitploit

工具目录

分类

查看所有分类
Loading categories
SILVERPICK — Windows 用户模式 Shellcode 开发框架 (WUMSDF) | Kitploit
工具/GitHubGitHub/winterknife/silverpick
漏洞利用Shellcode红队Shellcode 生成Payload 开发
GitHubwinterknife/silverpick

SILVERPICK

Windows 用户模式 Shellcode 开发框架 (WUMSDF)

查看仓库
1581751个月前Kitploit 审核通过

最受欢迎

查看全部 →

发现我们社区最常用的工具。

探索所有工具

浏览我们的工具集合

查看所有工具 →
分享

SILVERPICK

版本

  • v1.1

简介

项目 SILVERPICK 是一个 Windows 用户态 Shellcode 开发框架 (WUMSDF),其唯一目的是让能力开发者能够以简单的方式使用 C/C++ 为 Windows x64 构建 位置无关代码 (PIC) 二进制块,从而降低此类开发工作的成本。

它衍生自项目 WILDBEAST,因此依赖:

  1. 使用 Visual Studio Code 作为代码编辑器
  2. 使用 MinGW-w64 工具链作为编译工具链
  3. 使用 GNU Make 作为构建系统

环境搭建

你可以从这里找到环境搭建说明:GCC-Clang-Setup-Windows

请注意,该项目使用 MSYS2。

功能特性

使用高级编程语言编写 shellcode 并不是什么新鲜事,自 2010 年以来,已有无数博客文章和研究论文对此进行过讨论。那么,SILVERPICK 有什么新意呢?

嗯,很高兴你问这个问题。

SILVERPICK 的袖子里藏着一袋小妙招,但最重要的是,这是我对这个主题的个人见解。

那么,闲话少说,下面为你呈上我的第一个妙招。

技巧 01

自从 Matt Graeber 推广用 C 编写 shellcode 以来,大多数人一直在使用他的 用 汇编 语言编写的 16 字节栈对齐桩代码。

虽然这本身不是问题,但既然我们不是 IKEA,汇编就不应该是必需的,事实上也确实不是。

GCC 函数属性 中有一个属性可以替你生成栈对齐桩代码。

向你介绍 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

Matt Graeber 或许在某个时期推广了用 C 编写 shellcode,但实际上,真正复兴这一黑色艺术的是 Paul Ungur,他带来了 Stardust。

如今,Stardust 使用 Binutils 链接器脚本 来控制函数和数据以正确的顺序放入合适的 PE 节。该技术本身源于 Austin Hudson 的工作,许多人都在使用他的链接脚本的变体。

虽然链接器脚本非常适合控制链接器节的排序,但如果你的需求仅仅是将某个函数放在代码节的开头,那它们就是多余的。

这时就该 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" };

然而,面对编译器优化时,这两种技术_如果_字符串字面量足够大,都会变得毫无用处;而我们的方案则不同,得益于 Can Bölük 所贡献的巧妙 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 在现有公开实现的基础上提供了一些改进。

首先,我们使用流行的 FNV-1a 非加密哈希函数的 64 位变体,以降低哈希碰撞攻击成功的概率。

其次,我们为该哈希函数修改了一个参数,以抵御预计算哈希表查找,例如 HashDB。关键的是,这_不会_改变哈希函数的特性。

要在运行时哈希一个短字符串,只需使用 HASH_STRING_RUN_TIME 宏。

要在编译期哈希一个短字符串字面量,只需使用 HASH_STRING_COMPILE_TIME 宏。通过 consteval 保证仅进行编译期求值。

技巧 05

事实证明,使用 x86 字符串指令可以实现相当多的 C 运行时库 (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 指令。

哦,我是不是忘了提到,你还可以使用 scan_memory 例程编写自己的更安全的 msvcrt!strlen 版本,如下所示:

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 中还有许多其他小宏,用于抽象掉一些驾驭编译器的复杂性。

UserModuleBase.cpp 中提供了 GetModuleHandle 函数的无依赖实现。为简化使用,创建了一个名为 GET_USER_MODULE_BASE 的实用宏。

类似地,PEParse.cpp 中提供了 GetProcAddress 函数的无依赖实现,并被封装在一个恰如其分的便捷宏 GET_EXPORTED_SYMBOL_ADDRESS 中。此外,还提供了另外两个宏来辅助运行时动态链接——INITIALIZE_FUNCTION_POINTER 用于声明并将函数指针初始化为 0,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;
}

看起来很简单,不是吗?

在使用 SILVERPICK 框架以 C/C++ 编写 PIC 时,你需要考虑以下规则:

  1. 将 payload 函数视为传统程序中的 main 函数,即 (伪)入口点。
  2. 所有字符串字面量都必须声明为 栈字符串。
  3. 代码中任何地方都不能使用 全局变量。
  4. Windows API 或 Native API 函数只能通过 运行时动态链接 使用,并需确保相应的头文件中提供了该函数原型。

未来增强

本节包含一个非详尽的计划增强列表,这些增强将集成到未来的项目中。

  • 切换到 Clang/LLVM 工具链。
  • 切换到不同的构建系统。
  • 可与栈字符串配合使用且能抵御 FLOSS 的编译期字符串混淆。
  • 使用带种子的非加密自定义哈希函数进行编译期字符串哈希。
  • 检索 GS 段基址的替代方法。
  • 绕过 导出地址过滤 (EAF) 利用缓解措施的能力。

参考文献

以下是一份按时间顺序排列的参考文献列表,它们在我的研究过程中被证明极具价值,并广泛启发了我这个项目:

  1. 使用 C 编译器编写 Shellcode 作者:Nick Harbour (2010)

  2. 用 C 编译器编写 Shellcode 作者:Didier Stevens (2010)

  3. 使用 C 编写优化的 Windows Shellcode 作者:Matt Graeber (2013)

  4. 更好的 Shellcode 编写方式,或者说如何直接使用你的编译器 作者:Justin Fisher (2016)

  5. ShellcodeStdio 作者:Jack Ullrich (2016)

  6. Shellcode:使用 RSA-2048 密钥交换、AES-256、SHA-3 的 Windows PIC 作者:Odzhan (2016)

  7. 编写优化的 Windows Shellcode 作者:Dimitri Fourny (2017)

  8. 使用 C 编写和编译 Shellcode 作者:Aleksandra Doniec 和 Mantvydas Baranauskas (2021)

  9. 使用 Visual Studio 和 C++ 从任意代码创建 Shellcode 作者:Hamid Memar (2021)

  10. 使用 C 编写优化的 Windows Shellcode 作者:Philip Woldhek (2021)

  11. 从 C、通过内联汇编,到 shellcode 作者:Steve Salinas (2023)

  12. 如何使用 Visual Studio 打造你自己的 Windows x86/64 Shellcode 作者:Yazid Benjamaa (2023)

  13. 现代植入体设计:位置无关恶意软件开发 作者:Paul Ungur (2024)

  14. 从 C 到 shellcode(简单方法) 作者:Print3M (2024)

勘误

VirusTotal

让我感到奇怪的是 Microsoft Defender 将其归类为 VirTool:Win64/Silepesz.A。

以下是该签名覆盖的字节:

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 哈希的重要性。

下载工具

relocatable 作者:Tijme Gommers (2025)

  • PIC 开发速成课程 作者:Raphael Mudge (2025)

  • scfw 作者:Petr Beneš (2026)