0%

网络攻防-缓冲区溢出

网络攻防-软件漏洞1-缓冲区溢出,涉及实验:SEED Labs – Buffer Overflow Attack Lab,代码见:https://github.com/Seanxz401/seed-labs

理论

软件漏洞

漏洞:可能被攻击者利用的系统弱点

典型漏洞类型:栈溢出、堆溢出、格式化串、整型溢出、释放再使用

漏洞攻击的步骤:1.漏洞发现;2.漏洞分析;3.漏洞利用

可能导致:以匿名身份直接获取系统最高权限;2.从普通用户提升为管理员;实施远程dos攻击

栈溢出

当程序运行时,计算机会在内存区域中开辟一段连续的内存块, 包括代码段(.text)、数据段(.bss+.data)和堆栈段三部分。

程序在内存中的存放形式:image-20221227145514985

  • 对代码段进行写操作会导致段错误(Segmentation Fault)
  • 数据段在编译时分配
  • 堆中的变量由malloc、new这样的内存分配函数实现,用free、delete释放内存
  • 栈用来存储函数调用时的临时信息:参数、返回地址、函数内局部变量……,不需要时自动清除
  • 栈的特性:先入后出FILO
  • 函数栈帧由高地址到低地址(数组由低地址到高地址)
  • 栈顶指针ESP,基地址指针EBP

函数fun()被调用时,压栈情况:

高地址 传递的参数
退出fun函数后的返回地址RET
即调用fun的下一条指令的地址
调用fun函数之前的EBP
也就是上一个函数栈帧的EBP
低地址
(后入栈)
函数中的局部变量:
buf[10]
……
buf[0]

当局部变量buf发生溢出,可能会覆盖掉EBP和RET

tips:这一块儿文字描述不好,建议找视频观看,还需要一点点寄存器的相关知识(EIP,ESP,EBP)

栈溢出实例gets

1
2
3
4
5
6
7
8
9
10
void fun(){
char name[16];
gets(name);
for(int i=0;i<16&&name[i];i++)
printf("%c",name[i]);
}
int main(){
fun();
printf("EIP");
}

调用fun函数时:

  1. 将返回地址压入栈,也就是fun()的下一条指令printf(“EIP”)的地址
  2. 将EBP压入栈(此处是main函数栈帧的栈底地址),并将EBP修改为当前ESP(此处EBP内容变更为fun函数栈帧的栈底地址)
  3. ESP减16,即向低地址扩展16个字节,用来存放name数组
  4. 执行gets获取输入,假设输入”hello world”,然后for循环打印,直至0x00截止符
  5. 从fun返回到main
    1. 令ESP=EBP,回收name数组空间,并使ESP指向当前EBP。当前EBP中的内容为main函数栈帧的栈底地址。
    2. 程序将这个值弹出并赋给EBP,使EBP重新指向main()函数栈帧的栈底
    3. 再弹出现在位于栈顶的返回地址RET,赋给EIP,CPU继续执行EIP所指向的命令,也就是printf(“EIP”)。

栈溢出情况:当输入的字符串超过16字节后,多出的内容会继续向高地址蔓延,导致覆盖掉EBP和RET,那么返回时会把新的内容视作EBP和RET,CPU会执行新的RET中地址指向的内容。

溢出漏洞利用

攻击流程:1.注入恶意数据;2.溢出缓冲区;3.控制流重定向;4.执行有效载荷

  • 溢出点定位
    • 探测法:构造数据,根据出错情况来判断
    • 反汇编分析
  • 覆盖执行控制地址:返回地址、函数指针变量、异常处理结构
  • 覆盖异常处理结构
  • 跳转地址的确定:用户空间的任意地址、系统dll、进程代码段、PEB、 TEB
  • Shellcode的定位和跳转:具体见shellcode实验

实验

SEED Labs – Buffer Overflow Attack Lab (Server Version)

实验环境:本实验将提供四台不同的服务器,每台服务器运行一个带有缓冲区溢出漏洞的程序。

实验目的:开发一个利用漏洞的程序,并最终获得这些服务器上的root权限。除了进行这些攻击实验之外,还将尝试几种对抗缓冲区溢出攻击的对策,然后需要评估这些办法是否有效,并解释原因。

实验准备

首先关闭实验环境(SEED虚拟机)中的随机化地址策略,此步骤是为了保证程序每次执行时的初始地址不变,以便于进行多次实验和调试:

1
sudo /sbin/sysctl -w kernel.randomize_va_space=0

原理

首先要清楚函数栈帧的结构,包括参数压栈、返回地址、ebp/rbp等。

server

server.c中接收来自客户端的TCP连接并把TCP连接重定向至服务器的标准输入

1
dup2(socket_fd, STDIN_FILENO);

然后执行stack(PROGRAM)程序

1
execle(PROGRAM, PROGRAM, (char *)NULL, generate_random_env(random_n));

stack

stack从server的标准输入获取数据存入str[517],而server的标准输入现在是来自TCP连接,相当于str存入的是客户端传来的指令。

在bof函数中执行strcpy:

1
strcpy(buffer, str);

strcpy不检查缓冲区边界,由于源字符串str[517]大于目的字符串buffer[200]时会发生缓冲区溢出。因此可以在分配给buffer大小以外的部分构造攻击代码,溢出部分拥有当前用户权限来执行指令。

服务端代码编译

1
gcc -DBUF_SIZE=$(L1) -o stack -z execstack -fno-stack-protector stack.c

编译时需要加上两个选项-DBUF_SIZE-fno-stack-protector来关闭堆栈保护器和不可执行的堆栈保护。然后通过Makefile安装容器环境。

1
2
make
make install

docker配置

1
2
3
4
5
6
# 在docker-compose.yaml所属目录下编译
docker-compose build
# 运行容器
docker-compose up
# 查看是否开启成功
docker ps -a

image-20221017172048539

可以看到4个容器已经在运行了

Task1

修改shellcode脚本中的命令为删除文件(先创建好这个文件),注意命令末尾*符号的位置要保持不变。(就是保持shellcode长度不变)image-20221022141354995

运行脚本生成codefile→make→运行.out可执行文件结果如图,在执行命令行test.txt文件被删除了:image-20221022141656663

Task2

向容器10.9.0.5发送消息:echo hello | nc 10.9.0.5 9090,观察容器端打印出的信息:

image-20221022144845070

  • ebp:0xffffd5a8
  • buffer开始的位置:0xffffd538

2.1 shellcode编写

需要修改的地方:

  • shellcode:可以参照Task1的shellcode(32和64不一样,对应的后面步长不一样,以下内容参考32位的shellcode);修改如下:image-20221022154927382
  • start:517-len(shellcode),把content(badfile)中的后面部分替换为攻击代码
  • ret:覆盖返回地址,ebp的地址+大于等于8的数量(ps.填充若干长度的 \x90这个机器码对应的指令是 NOP (No Operation),也就是告诉 CPU 什么也不做,然后跳到下一条指令。有了这一段 NOP 的填充,只要返回地址能够命中这一段中的任意位置,最后都可以跳转到 shellcode 的起始处。);
  • offset:ebp-buffer+4

执行脚本生成badfile,并将badfile发送给服务器(容器):

1
cat badfile | nc 10.9.0.5 9090

观察容器的输出,可以看到badfile中嵌入的代码已经被执行:image-20221022155407589

2.2 reverse shell

2.1只能实现让服务器执行shellcode中既定的指令,因此我们要将shellcode改为reverse shell,使攻击者能远程连接上被攻击的服务器,拿到root权限。

修改shellcode如下:image-20221022161637203

  • 10.9.0.1是攻击端的IP(从容器的打印输出可以看到);
  • 0<&1,0表示标准输入stdout,1表示标准输出stdin,即将stdout重定向到stdin,由于服务器的stdout重定向到了tcp连接,因此最终效果是将tcp连接中攻击者的输入定向到stdin
  • 2>&1,2表示标准错误输出stderr

重新生成badfile,先在攻击端1打开端口等待连接nc -lnv 9090,再新建终端2上传badfile。在终端1可以看到已经拿到shell的root权限(命令提示符为#),用ifconfig测试可以看到确实是10.9.0.5image-20221022162614135

Task3

向容器10.9.0.6发送消息:echo hello | nc 10.9.0.6 9090,观察容器端打印出的信息,此次没有提示ebp的位置:image-20221022163100118

题目中给出了buffer_size的限制在:[100, 300]。需要修改的:

  • ret:buffer+大于等于(300+8)的值
  • offset:由于不确定,因此将这个范围内[100+4,300+4]都填充为返回地址,以4为步长image-20221022183913421

生成badfile上传,拿到root shell:image-20221023191343260

Task4

前两节都是32bit,现在切换到64bit。向10.9.0.7发送消息可以看到rbpbuffer的地址信息,是64bit。image-20221023180721934

ps.Task的难点在于64位计算机中的地址范围为0x00~`0x00007FFFFFFFFFFF,因此所有地址最高位的两个字节都是0x00`。而strcpy函数遇到0会停止,如果和前面的方法一样,则shellcode不会被copy到缓冲区。因此解决办法是把shellcode移到badfile的前面部分,ret的值指向前面部分。由于是小端存储,在截止前ret的非零部分已经被copy到了缓冲区。需要如下:image-20221023183759737

  • shellcode:参考shellcode_64
  • start:很小的值(0),使shellcode位于缓冲区的前一部分
  • ret:buffer+start的位置,这样覆盖后的返回地址就是shellcode的位置
  • offset:rbp-buffer+8,后面将ret转为字节码的部分改为以8为步长

生成badfile并上传到10.9.0.7,nc连接,顺利拿到root shell。

Task5

10.9.0.8发送消息可得:image-20221023185512118

rbp与buffer之间的距离只有96bytes,修改如下:image-20221023194046014

  • 将shellcode放在高位。
  • offset=rbp-buffer+8;
  • ret:取一个较大值,在 1184到 1424之间。由于\x00截断了strcpy函数,因此需要触发的shellcode并没有被拷贝到缓冲区,因此ret指向的位置需是主函数中str数组中shellcode的位置。
    • 调试L4级别的stack.c,可以获取到str数组的地址,我们需要跳转到str数组中的ret和shellcode中间的NOP中。
    • str的地址+offset+8-rbp=1184
    • str的地址+517-165-rbp=1424;shellcode是165个字节。

生成badfile上传,nc连接。

Task6

关闭地址随机化:

1
sudo /sbin/sysctl -w kernel.randomize_va_space=2

向容器多次发送消息可以看到每次得到的地址都在变化。因此使用爆破的办法,选定一个作为返回地址,一旦命中就停止。自动化sh脚本在实验环境中有。

Task7

7.1栈溢出保护

修改stack.c,使badfile作为fread的输入:

1
2
FILE *file=fopen("badfile","rb");
int length = fread(str, sizeof(char), 517, file);

编译stack.c时不使用-fno-stack-protector

1
gcc -DBUF_SIZE=80 -o stack -z execstack stack.c

可以看到错误提示:stack smashing

7.2栈不可执行

编译call_shellcode.c时不使用-z execstack

1
gcc -m32 -o a32.out call_shellcode.c

运行a32.out可以看到segmentation fault

结果分析

实验中共提到了三种栈溢出攻击的防御措施:

  • 开启地址随机化:开启后较难猜中想要跳转的地址,但是我们在Task6中通过爆破还是能攻击成功;
  • 栈保护措施:开启后能检测到程序有栈溢出的风险,不允许执行。不保证能百分百检测出有栈溢出的点;
  • 栈不可执行措施:将数据所在内存页标识为不可执行,当程序溢出成功转入shellcode时,程序会尝试在数据页面上执行指令,此时CPU就会抛出异常,而不是去执行恶意指令。可以尝试ROP攻击。

参考

labs:https://www.361shipin.com/blog/1566897080167825408,https://blog.csdn.net/qq_39678161/article/details/119907828

strcpy栈溢出:https://www.33ip.com/support/16.html

函数栈帧及栈溢出攻击原理:https://paper.seebug.org/271/,https://paper.seebug.org/272/

linux程序保护机制&gcc编译选项:https://www.jianshu.com/p/91fae054f922