手搓Windowsx86Shellcode?这位研究员把全流程都给你扒光了

admin 2026-04-29 05:18:43 网络安全文章 来源:ZONE.CI 全球网 0 阅读模式

文章总结: 该文档详细解析了手写x86WindowsShellcode的全流程,核心是通过PEBWalking动态获取kernel32.dll基址绕过ASLR,结合ROR13哈希算法解析API函数地址避免特征检测。文章演示了从MessageBox弹窗到反弹Shell的实战实现,重点涵盖栈上结构体构造、NULL字节规避技巧及Python工具链应用,为漏洞利用和红队研究提供可操作的底层技术细节。 综合评分: 92 文章分类: 红队,二进制安全,内网渗透,漏洞分析,实战经验


cover_image

手搓 Windows x86 Shellcode?这位研究员把全流程都给你扒光了

原创

JunYi JunYi

毅心安全

2026年4月22日 09:47 广东

在小说阅读器读本章

去阅读

今天带大家看一篇硬核到骨子里的文章。

来自印尼安全研究员 Maland 的一篇实战记录,主题是——从零手写 x86 Windows Shellcode,并实现动态 API 解析,最终落地到一个真实的反弹 Shell。

没有用 msfvenom,没有用任何生成工具,全是汇编硬撸。

我们来拆一拆他到底干了什么,重点在哪。


背景:为什么要自己写 Shellcode?

现代 Windows 有两道坎让 Shellcode 开发者头疼:

1. ASLR(地址空间随机化) 每次程序启动,系统 DLL 的地址都会变。你没法把 kernel32.dll 的地址硬编码进去。

2. Windows 的系统调用机制 Linux 下可以直接用 syscall,Windows 不行。Windows 的 syscall 编号版本间不固定,必须通过 Win32 API 来间接调用。

所以问题变成了:Shellcode 执行时,它怎么知道 LoadLibraryA 在哪?怎么知道 CreateProcessA 在哪?

这就是这篇文章的核心命题。


核心技术:PEB Walking + Hash-Based API 解析

第一步:通过 PEB 找到 kernel32.dll

每个 Windows 进程线程都有一个 TEB(线程环境块),其中存有指向 PEB(进程环境块)的指针。 screetsec

在 32 位系统下,通过 FS:[0x30] 就能拿到 PEB 地址,然后沿着以下链路走:

TEB → PEB → PEB_LDR_DATA → InInitializationOrderModuleList

这个链表包含已加载的模块,通常按初始化顺序排序——先是程序本身(.exe),然后是 ntdll.dll,再是 kernel32.dll。 screetsec

研究员遍历这个链表,对比每个模块名的第 12 个 Unicode 字符是否为空(kernel32.dll 正好 12 个字符),找到就停下。

一句汇编搞定定位:

mov esi, fs:[ecx + 0x30]   ; ESI = PEB
mov esi, [esi + 0x0C]      ; ESI = PEB->Ldr
mov esi, [esi + 0x1C]      ; ESI = InInitializationOrderModuleList

第二步:解析 Export Table,动态获取函数地址

拿到 kernel32.dll 基址后,怎么找到 LoadLibraryA 的地址?

答案是手动 PE 文件解析——直接在内存中走 PE 结构:

PE Header → Export Directory → AddressOfNames → 遍历函数名 → 对比 Hash → 找到 ordinal → 拿到函数地址

第三步:ROR13 Hash 算法防止字符串明文暴露

函数名不能直接写进 Shellcode(会被特征检测)。

研究员使用 ROR13 算法,将函数名字符串转换为一个 4 字节的哈希值,用于在 Shellcode 中比对目标函数。 screetsec

比如:

  • • TerminateProcess → 0x78b5b983
  • • LoadLibraryA → 0xec0e4e8e
  • • CreateProcessA → 0x16b3fe72

找函数的时候,只需要 push 对应 hash,调用解析器,地址就回来了。这是整个 Shellcode 的引擎核心。

实战一:MessageBox Shellcode

验证完基础框架后,研究员先做了一个简单的概念验证——

通过 LoadLibraryA 加载 user32.dll,再用 Hash 解析出 MessageBoxA 的地址,然后在栈上手动构造字符串参数(注意 x86 小端序),最后调用弹窗。

整个过程无任何硬编码地址,完全位置无关(PIC)。

实战二:反弹 Shell Shellcode(核心高潮)

这才是重头戏。研究员完整实现了一个反弹 Shell,调用链如下:

WSAStartup → WSASocketA → WSAConnect → CreateProcessA

几个关键细节值得关注:

① 避免 NULL 字节

整个汇编里大量使用了如下技巧避免 0x00 出现在 opcode 中(因为很多漏洞利用场景 NULL 是坏字符):

; 不写 mov eax, 0x100,而是:
mov al, 0x80
mov cx, 0x80
add eax, ecx     ; 0x80 + 0x80 = 0x100,完美

② I/O 重定向到 Socket

hStdInput、hStdOutput 和 hStdError 被重定向到 socket,这样 cmd.exe 进程的所有输入输出都通过网络连接路由,攻击者可以直接通过该 socket 进行交互。 screetsec

③ STARTUPINFOA 手动构造

不依赖任何库,全部在栈上手动 push 每一个结构体字段,包括对齐 padding。

④ IP/Port 的小端序处理

IP 192.168.45.197、Port 443 在汇编中表示为:

push 0xc52da8c0   ; sin_addr
mov  ax, 0xbb01   ; sin_port (443)

研究员还贴心写了一个 Python 脚本帮你自动转换。


工具链:纯 Python 驱动

整个 Shellcode 开发链全部用 Python 完成:

  • • Keystone Engine:将汇编代码编译为 opcode 字节序列
  • • ctypes:通过 VirtualAlloc 申请 RWX 内存,RtlMoveMemory 复制 Shellcode,CreateThread 执行
  • • 自写脚本:Hash 计算器、字符串小端序转换器、坏字符检测器

Badchar 处理:工程化的最后一步

如果在利用过程中 Shellcode 包含坏字符,可能导致 payload 失败。因此需要通过替换产生相同值但不含坏字符的指令来重新定制汇编代码。 screetsec

研究员还写了一个自动化脚本,结合 Capstone 反汇编器,逐字节对比标记出哪条指令产生了坏字符,高亮输出,方便修改。


总结一下:这篇文章的核心价值

| 技术点 | 意义 | | — | — | | PEB Walking | 绕过 ASLR,动态获取模块基址 | | ROR13 Hash API 解析 | 无需明文函数名,规避特征检测 | | 栈上字符串构造(小端序) | 避免硬编码,保持位置无关性 | | NULL 字节规避技巧 | 适配 strcpy 类漏洞利用场景 | | Python + Keystone 工具链 | 快速迭代、实时测试 |

这是一篇难得把”为什么这样写”讲清楚的文章,不是简单贴代码,而是从 Windows 内存结构出发,一步步推导出每一行汇编存在的原因。

对于在做 Exploit Development、红队研究、或者 Windows 底层安全的朋友来说,值得反复精读。

原文:https://screetsec.com/blog/custom-x86-windows-shellcode-dynamic-api-resolution

中文版(已翻译)

https://1829438095.share.123pan.cn/123pan/3YQcjv-4Abh


免责声明:

本文所载程序、技术方法仅面向合法合规的安全研究与教学场景,旨在提升网络安全防护能力,具有明确的技术研究属性。

任何单位或个人未经授权,将本文内容用于攻击、破坏等非法用途的,由此引发的全部法律责任、民事赔偿及连带责任,均由行为人独立承担,本站不承担任何连带责任。

本站内容均为技术交流与知识分享目的发布,若存在版权侵权或其他异议,请通过邮件联系处理,具体联系方式可点击页面上方的联系我

本文转载自:毅心安全 JunYi JunYi《手搓 Windows x86 Shellcode?这位研究员把全流程都给你扒光了》

评论:0   参与:  0