一、基础知识
栈的结构概述
编译器使用堆栈传递函数参数、保存返回地址、临时保存寄存器原有值(即函数调用的上下文)以备恢复以及存储本地局部变量
栈的特点:后进先出的数据结构,栈的结构是从进程高地址向进程低地址生长
调用约定:
x32:函数的参数在函数返回地址上方,压栈的方式传参
x64:
.
rdi,rsi,rdx,rcx
,r8,r9,(push to stack)
rdi,rsi,rdx,r10
,r8,r9,(push to stack)for system call
内存地址小于0x00007FFFFFFFFFFF。6个字节长度
return values is stored in rax
Stack Frame
function prologue
call 4004e7 //将下一行地址压入栈中
push rbp
mov rbp,rsp
sub rsp,0x10
function epilogue
leave == mov rsp,rbp
pop rbp
ret == pop rip
整个流程执行完,栈的结构不变
Protection
Stack Guard(canary)
做完function prologue的时候会将随机生成的乱数塞入栈中,fuction return前会检查乱数是否有被动过,若发现动过立即结束程序
DEP(NX)
Data execution prevention
可执行的地方不能写,可写的地方不能执行
ASLR
Address Space Layout Randomization
每次程序执行时stack,heap,library的位置不一样
PIE
code和data一起跳来跳去
一句生成无保护程序的gcc
gcc ret2sc.c -m32 -fno-stack-protector -no-pie -z execstack -o ret2sc
ASLR关闭 改kernel文件
二、栈溢出类型
核心:通过控制返回地址控制程序的执行流程
(一)基础缓冲区溢出
条件:
程序必需向栈上写入数据
写入的数据大小没有被控制
栈溢出重要步骤
1.寻找危险函数
- 输入
gets,直接读取一行,忽略’\x00’
scanf
vscanf - 输出
sprintf - 字符串
strcpy,字符串复制,遇到’\x00’停止
strcat,字符串拼接,遇到’\x00’停止
bcopy
2.计算填充
打开IDA,看变量距EBP的值
我们的覆盖需求:
- 覆盖函数返回地址
- 覆盖栈上某变量的值
- 覆盖bss段某变量的内容
- 根据现实情况,覆盖特定的变量或地址的内容
(二)Basic Rop
ROP攻击的条件
-程序存在栈溢出,并且可以控制返回地址
-可以找到满足条件的gadgets以及相对的gadgets地址
(如果gadgets每次的地址是不固定的,我们就需要想办法获取对应的地址)
1.ret2text
有现成的system(’/bin/sh’),直接覆盖返回地址。
2.ret2shellcode
自己在某段可读可写的地方写shellcode,覆盖返回地址到此处
#自己构造开shell的syscall
sc=asm(
'''
mov rbx,0x68732f6e69622f
push rbx
mov rdi,rsp
xor rsi,rsi
xor rdx,rdx
mov rax.0x3b
syscall
''')
#利用pwntools的shellcode
sc=asm(shellcraft.sh())
3.GOT Hijacking
绕过canary
Lazy Binding
因为不一定每个library function都会被执行到,所以采用lazy binding机制,当第一次执行到library function才会去寻找真正的address 并运行 binding
Global Offset Table
GOT为library function的指标阵列,因为lazy binding机制因此一开始不会知道真实位置,取而代之的是plt段的code
Lazy Binding Procedure
第一次加载
以后加载
GOT Hijacking
劫持got地址的值,程序执行常规的库函数时其实执行的是我们的shellcode。
RELRO(Relocation Read-Only)
- Partial RELRO
GOT 可写 - Full RELRO
load time时会将所有function resolve完毕
GOT 不可写
ROP系列
ret2syscall
ret2libc
4.ret2syscall
对抗NX
Return Oriented Programming
通过不断去执行包含ret的代码片段来达到想要的操作
这些包含ret的代码片段被称为gadget
利用ROPgadget找到相应gadget按下面排列
5.ret2plt
程序中有system()函数
6.ret2libc
程序里没有execve(),system(),或者后门
开着NX
开着ASLR
把libc当gadget来用,通过实际地址减偏移找到libc_base
在libc里找gadget,这里的gadget都是偏移地址,所以实际地址是偏移地址+libc_base,叠gadget
或者用 one_gadget libc-2.27.so