Skip to content
KitploitKITPLOIT
工具漏洞利用博客
Log in
提交
工具漏洞利用博客
提交

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

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

订阅源联系隐私© 2026 Kitploit

工具目录

分类

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

SILVERPICK

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

查看仓库
15817253个月前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 宏的形式呈现,可生成以下汇编代码:

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 字符数组来创建栈字符串(一种在栈上动态构建的字符串):

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

在 C++ 中,你可以只需将 char 数组标记为 constexpr 来创建栈字符串:

constexpr char charrHelloKitty[]{ "HelloKitty" };

然而,面对编译器优化时,这两种技术_如果_字符串字面量足够大,都会变得毫无用处;而我们的方案则不同,得益于 Can Bölük 所贡献的巧妙 C++ 模板元编程技巧,无论字符串长度如何、编译器优化级别多高,它都能正常工作。

使用这个宏非常简单:

STACK_STRING(sstrText, "an extra long hello world!");
STACK_STRING(sstrCaption, "Demo");

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

这将生成以下汇编代码:

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 版本,如下所示:

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 的示例:

/// @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;
下载工具