栈溢出
演示
1 | #include <stdio.h> |
编译函数
1 | -fno-stack-protector:关闭栈溢出保护 |
查看$rip
1
2b main
r 1
p $rip
如图可以看出 $rip指向main地址,说明接下来第一个指令会是main
反汇编
1 | main函数,看指向 |
单步执行指令->看rip自动跳转
1 | ni |
C语言函数调用栈
函数调用栈是指程序运行时内存一段连续的区域,它用来保存函数与运行时的状态信息,包括函数参数以及局部变量等
称之为栈是因为发生函数调用时,调用函数的状态被保存在栈内,被调用函数的状态被压入调用栈的栈顶
在函数调用结束时,栈顶的函数状态被弹出,栈顶恢复到调用函数的状态
函数调用栈在内存中从高地址向低地址生长,所以栈顶对应的内存地址在压栈时变小,退栈时变大
保护机制
NX(DEP)
数据执行防护
栈上的数据没有执行权限,防止攻击手段:栈溢出 + 跳到栈上执行shellcode
Canary(FS)
栈溢出保护
在函数开始时就随机产生一个值,将这个值CANARY放到栈上紧挨ebp的上一个位置,当攻击者想通过缓冲区溢出覆盖ebp或者ebp下方的返回地址时,一定会覆盖掉CANARY的值;当程序结束时,程序会检查CANARY这个值和之前的是否一致,如果不一致,则不会往下运行,从而避免了缓冲区溢出攻击。
防止攻击手段:所有单纯的栈溢出
RELRO(ASLR)
地址随机化
防止攻击手段:所有需要用到堆栈精确地址的攻击,要想成功,必须用提前泄露地址
PIE
代码地址随机化
防止攻击手段:构造ROP链攻击
栈溢出
危险点来了,主要是由于栈的设计:
- 栈的地址是由高向低生成的
- 数据写入栈时时由低向高的
此时加上gets()不检查输入长度,就导致了”栈溢出”的漏洞——我们可以通过输出过长的数据,超出buf的16字节,进而覆盖后面的Saved RBP和返回地址
返回到什么地址?
一般来说,多数情况下我们只需要让程序执行这一段代码:system("/bin/sh")
也就是说在远程机器上开一个命令终端,这样就可以控制目标机
ret2text
理想情况下,程序中有一段代码直接就能满足我们的需求
我们只需把执行流劫持到代码即可
ret2shellcode
如果程序中没有代码怎么办
我们可以自己写shellcode
shellcode就是一段可以独立运行开启shell的一段汇编代码
前提:NX关闭
思路
如果程序中存在让用户向一段长度足够的缓冲区中输入数据,我们向其中输入shellcode,将程序劫持到shellcode上即可
纯手写
asm(shellcraft.amd64.linux.execve("/bin/sh", 0, 0))
好处:生成的机器码体积极度精简(通常只有 20 多字节)。
缺点:攻击性不高,容易被阻拦
快捷指令
asm(shellcraft.sh())
好处:为了保证在各种复杂或奇葩的漏洞场景下都能 100% 弹 Shell 成功,pwntools 会在这个宏里面塞入一些“防御性/初始化代码”(比如主动清空一些可能会干扰运行的寄存器、提升权限等)。
缺点:正因为它想得太周到,导致生成的机器码体积比较大(在 64 位下通常在 40 到 50 字节左右)。
ret2libc
有时候,我们需要调用一些系统函数,就比如system或者execv等
程序中可能不会提供一些现成的函数
如果我们能拿到libc中的地址,就可以直接调用libc中的函数
只需要传递好参数,然后call即可
- 如何调用
system(/bin/sh);- 只需要将rdi设置成/bin/sh字符串地址,然后
call system即可 pop rdi ret + /bin/sh地址 + system
- 只需要将rdi设置成/bin/sh字符串地址,然后
ROP
函数调用过程:
- 调用函数:只需要将
rip压栈,即push rip,然后将rip赋值为被调用函数的起始地址,这一操作被隐形的内置在call指令中 - 被调用函数:
push rbp; move rbp rsp; sub rsp 0xxx。即保存调用函数的rbp指针,将自己的rbp指针指向栈顶,然后开辟栈空间给自己用,此时rbp就变成了被调用函数的栈底 - 函数返回:
leave ; ret,意思是:mov rsp rbp; pop rbp; pop rip;即恢复栈顶,返回调用函数的返回地址
很多情况下,程序中我们能利用的只有栈,也就是说,程序中没有一个可读可写可执行的区域让我们输入shellcode
同时,大多数题目也不会给你留一个后门函数直接执行system,那么这个时候就需要rop
rop称为返回导向编程,说人话就是程序以一堆ret来完成代码逻辑,我们需要利用程序中的一些指令片段,一点点拼接出来,拼成我们想要的样子
拿system("/bin/sh");举例,我们要将rdi改成/bin/sh这个字符串的地址,然后call system,但是我们不能执行shellcode,所以需要用栈来导向pop rdi ret + /bin/sh地址 + system
不能shellcode,构造:padding+pop rdi;ret+/bin/sh+system
工具:ropper和ROPgadgets
使用:1
2ROPgadget --binary file
ropper --file file
通用ROP
在64位程序中,函数的前6个参数都是通过寄存器传递的,但是大多数时候,我们很难找到一个寄存器对应的gadgets
这时候,就需要利用__libc_csu_init中的gadgets
这个函数是用来对libc进行初始化操作的,而一般的程序都会调用libc函数,所以这个函数一定存在
ret2syscall
和正常函数调用没什么区别,找一下系统调用表,想调用哪个函数就把rax设置成那个数,然后syscall就行






